darkplaces/0000775000175000017500000000000013070766633012172 5ustar kalevkalevdarkplaces/progdefs.h0000664000175000017500000000633513067716222014156 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* file generated by qcc, do not modify */ #ifndef PROGDEFS_H #define PROGDEFS_H typedef struct globalvars_s { int pad[28]; int self; int other; int world; float time; float frametime; float force_retouch; string_t mapname; float deathmatch; float coop; float teamplay; float serverflags; float total_secrets; float total_monsters; float found_secrets; float killed_monsters; float parm1; float parm2; float parm3; float parm4; float parm5; float parm6; float parm7; float parm8; float parm9; float parm10; float parm11; float parm12; float parm13; float parm14; float parm15; float parm16; vec3_t v_forward; vec3_t v_up; vec3_t v_right; float trace_allsolid; float trace_startsolid; float trace_fraction; vec3_t trace_endpos; vec3_t trace_plane_normal; float trace_plane_dist; int trace_ent; float trace_inopen; float trace_inwater; int msg_entity; func_t main; func_t StartFrame; func_t PlayerPreThink; func_t PlayerPostThink; func_t ClientKill; func_t ClientConnect; func_t PutClientInServer; func_t ClientDisconnect; func_t SetNewParms; func_t SetChangeParms; } globalvars_t; typedef struct entvars_s { float modelindex; vec3_t absmin; vec3_t absmax; float ltime; float movetype; float solid; vec3_t origin; vec3_t oldorigin; vec3_t velocity; vec3_t angles; vec3_t avelocity; vec3_t punchangle; string_t classname; string_t model; float frame; float skin; float effects; vec3_t mins; vec3_t maxs; vec3_t size; func_t touch; func_t use; func_t think; func_t blocked; float nextthink; int groundentity; float health; float frags; float weapon; string_t weaponmodel; float weaponframe; float currentammo; float ammo_shells; float ammo_nails; float ammo_rockets; float ammo_cells; float items; float takedamage; int chain; float deadflag; vec3_t view_ofs; float button0; float button1; float button2; float impulse; float fixangle; vec3_t v_angle; float idealpitch; string_t netname; int enemy; float flags; float colormap; float team; float max_health; float teleport_time; float armortype; float armorvalue; float waterlevel; float watertype; float ideal_yaw; float yaw_speed; int aiment; int goalentity; float spawnflags; string_t target; string_t targetname; float dmg_take; float dmg_save; int dmg_inflictor; int owner; vec3_t movedir; string_t message; float sounds; string_t noise; string_t noise1; string_t noise2; string_t noise3; } entvars_t; #define PROGHEADER_CRC 5927 #define PROGHEADER_CRC_TENEBRAE 32401 #endif darkplaces/ft2.c0000664000175000017500000014171613067716220013034 0ustar kalevkalev/* FreeType 2 and UTF-8 encoding support for * DarkPlaces */ #include "quakedef.h" #include "ft2.h" #include "ft2_defs.h" #include "ft2_fontdefs.h" #include "image.h" static int img_fontmap[256] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // shift+digit line 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // digits 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // caps 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // small 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // specials 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // faces 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* ================================================================================ CVars introduced with the freetype extension ================================================================================ */ cvar_t r_font_disable_freetype = {CVAR_SAVE, "r_font_disable_freetype", "1", "disable freetype support for fonts entirely"}; cvar_t r_font_use_alpha_textures = {CVAR_SAVE, "r_font_use_alpha_textures", "0", "use alpha-textures for font rendering, this should safe memory"}; cvar_t r_font_size_snapping = {CVAR_SAVE, "r_font_size_snapping", "1", "stick to good looking font sizes whenever possible - bad when the mod doesn't support it!"}; cvar_t r_font_kerning = {CVAR_SAVE, "r_font_kerning", "1", "Use kerning if available"}; cvar_t r_font_diskcache = {CVAR_SAVE, "r_font_diskcache", "0", "save font textures to disk for future loading rather than generating them every time"}; cvar_t r_font_compress = {CVAR_SAVE, "r_font_compress", "0", "use texture compression on font textures to save video memory"}; cvar_t r_font_nonpoweroftwo = {CVAR_SAVE, "r_font_nonpoweroftwo", "1", "use nonpoweroftwo textures for font (saves memory, potentially slower)"}; cvar_t developer_font = {CVAR_SAVE, "developer_font", "0", "prints debug messages about fonts"}; #ifndef DP_FREETYPE_STATIC /* ================================================================================ Function definitions. Taken from the freetype2 headers. ================================================================================ */ FT_EXPORT( FT_Error ) (*qFT_Init_FreeType)( FT_Library *alibrary ); FT_EXPORT( FT_Error ) (*qFT_Done_FreeType)( FT_Library library ); /* FT_EXPORT( FT_Error ) (*qFT_New_Face)( FT_Library library, const char* filepathname, FT_Long face_index, FT_Face *aface ); */ FT_EXPORT( FT_Error ) (*qFT_New_Memory_Face)( FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface ); FT_EXPORT( FT_Error ) (*qFT_Done_Face)( FT_Face face ); FT_EXPORT( FT_Error ) (*qFT_Select_Size)( FT_Face face, FT_Int strike_index ); FT_EXPORT( FT_Error ) (*qFT_Request_Size)( FT_Face face, FT_Size_Request req ); FT_EXPORT( FT_Error ) (*qFT_Set_Char_Size)( FT_Face face, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt horz_resolution, FT_UInt vert_resolution ); FT_EXPORT( FT_Error ) (*qFT_Set_Pixel_Sizes)( FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height ); FT_EXPORT( FT_Error ) (*qFT_Load_Glyph)( FT_Face face, FT_UInt glyph_index, FT_Int32 load_flags ); FT_EXPORT( FT_Error ) (*qFT_Load_Char)( FT_Face face, FT_ULong char_code, FT_Int32 load_flags ); FT_EXPORT( FT_UInt ) (*qFT_Get_Char_Index)( FT_Face face, FT_ULong charcode ); FT_EXPORT( FT_Error ) (*qFT_Render_Glyph)( FT_GlyphSlot slot, FT_Render_Mode render_mode ); FT_EXPORT( FT_Error ) (*qFT_Get_Kerning)( FT_Face face, FT_UInt left_glyph, FT_UInt right_glyph, FT_UInt kern_mode, FT_Vector *akerning ); FT_EXPORT( FT_Error ) (*qFT_Attach_Stream)( FT_Face face, FT_Open_Args* parameters ); /* ================================================================================ Support for dynamically loading the FreeType2 library ================================================================================ */ static dllfunction_t ft2funcs[] = { {"FT_Init_FreeType", (void **) &qFT_Init_FreeType}, {"FT_Done_FreeType", (void **) &qFT_Done_FreeType}, //{"FT_New_Face", (void **) &qFT_New_Face}, {"FT_New_Memory_Face", (void **) &qFT_New_Memory_Face}, {"FT_Done_Face", (void **) &qFT_Done_Face}, {"FT_Select_Size", (void **) &qFT_Select_Size}, {"FT_Request_Size", (void **) &qFT_Request_Size}, {"FT_Set_Char_Size", (void **) &qFT_Set_Char_Size}, {"FT_Set_Pixel_Sizes", (void **) &qFT_Set_Pixel_Sizes}, {"FT_Load_Glyph", (void **) &qFT_Load_Glyph}, {"FT_Load_Char", (void **) &qFT_Load_Char}, {"FT_Get_Char_Index", (void **) &qFT_Get_Char_Index}, {"FT_Render_Glyph", (void **) &qFT_Render_Glyph}, {"FT_Get_Kerning", (void **) &qFT_Get_Kerning}, {"FT_Attach_Stream", (void **) &qFT_Attach_Stream}, {NULL, NULL} }; /// Handle for FreeType2 DLL static dllhandle_t ft2_dll = NULL; #else FT_EXPORT( FT_Error ) (FT_Init_FreeType)( FT_Library *alibrary ); FT_EXPORT( FT_Error ) (FT_Done_FreeType)( FT_Library library ); /* FT_EXPORT( FT_Error ) (FT_New_Face)( FT_Library library, const char* filepathname, FT_Long face_index, FT_Face *aface ); */ FT_EXPORT( FT_Error ) (FT_New_Memory_Face)( FT_Library library, const FT_Byte* file_base, FT_Long file_size, FT_Long face_index, FT_Face *aface ); FT_EXPORT( FT_Error ) (FT_Done_Face)( FT_Face face ); FT_EXPORT( FT_Error ) (FT_Select_Size)( FT_Face face, FT_Int strike_index ); FT_EXPORT( FT_Error ) (FT_Request_Size)( FT_Face face, FT_Size_Request req ); FT_EXPORT( FT_Error ) (FT_Set_Char_Size)( FT_Face face, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt horz_resolution, FT_UInt vert_resolution ); FT_EXPORT( FT_Error ) (FT_Set_Pixel_Sizes)( FT_Face face, FT_UInt pixel_width, FT_UInt pixel_height ); FT_EXPORT( FT_Error ) (FT_Load_Glyph)( FT_Face face, FT_UInt glyph_index, FT_Int32 load_flags ); FT_EXPORT( FT_Error ) (FT_Load_Char)( FT_Face face, FT_ULong char_code, FT_Int32 load_flags ); FT_EXPORT( FT_UInt ) (FT_Get_Char_Index)( FT_Face face, FT_ULong charcode ); FT_EXPORT( FT_Error ) (FT_Render_Glyph)( FT_GlyphSlot slot, FT_Render_Mode render_mode ); FT_EXPORT( FT_Error ) (FT_Get_Kerning)( FT_Face face, FT_UInt left_glyph, FT_UInt right_glyph, FT_UInt kern_mode, FT_Vector *akerning ); FT_EXPORT( FT_Error ) (FT_Attach_Stream)( FT_Face face, FT_Open_Args* parameters ); #define qFT_Init_FreeType FT_Init_FreeType #define qFT_Done_FreeType FT_Done_FreeType //#define qFT_New_Face FT_New_Face #define qFT_New_Memory_Face FT_New_Memory_Face #define qFT_Done_Face FT_Done_Face #define qFT_Select_Size FT_Select_Size #define qFT_Request_Size FT_Request_Size #define qFT_Set_Char_Size FT_Set_Char_Size #define qFT_Set_Pixel_Sizes FT_Set_Pixel_Sizes #define qFT_Load_Glyph FT_Load_Glyph #define qFT_Load_Char FT_Load_Char #define qFT_Get_Char_Index FT_Get_Char_Index #define qFT_Render_Glyph FT_Render_Glyph #define qFT_Get_Kerning FT_Get_Kerning #define qFT_Attach_Stream FT_Attach_Stream #endif /// Memory pool for fonts static mempool_t *font_mempool= NULL; /// FreeType library handle static FT_Library font_ft2lib = NULL; #define POSTPROCESS_MAXRADIUS 8 typedef struct { unsigned char *buf, *buf2; int bufsize, bufwidth, bufheight, bufpitch; float blur, outline, shadowx, shadowy, shadowz; int padding_t, padding_b, padding_l, padding_r, blurpadding_lt, blurpadding_rb, outlinepadding_t, outlinepadding_b, outlinepadding_l, outlinepadding_r; unsigned char circlematrix[2*POSTPROCESS_MAXRADIUS+1][2*POSTPROCESS_MAXRADIUS+1]; unsigned char gausstable[2*POSTPROCESS_MAXRADIUS+1]; } font_postprocess_t; static font_postprocess_t pp; typedef struct fontfilecache_s { unsigned char *buf; fs_offset_t len; int refcount; char path[MAX_QPATH]; } fontfilecache_t; #define MAX_FONTFILES 8 static fontfilecache_t fontfiles[MAX_FONTFILES]; static const unsigned char *fontfilecache_LoadFile(const char *path, qboolean quiet, fs_offset_t *filesizepointer) { int i; unsigned char *buf; for(i = 0; i < MAX_FONTFILES; ++i) { if(fontfiles[i].refcount > 0) if(!strcmp(path, fontfiles[i].path)) { *filesizepointer = fontfiles[i].len; ++fontfiles[i].refcount; return fontfiles[i].buf; } } buf = FS_LoadFile(path, font_mempool, quiet, filesizepointer); if(buf) { for(i = 0; i < MAX_FONTFILES; ++i) if(fontfiles[i].refcount <= 0) { strlcpy(fontfiles[i].path, path, sizeof(fontfiles[i].path)); fontfiles[i].len = *filesizepointer; fontfiles[i].buf = buf; fontfiles[i].refcount = 1; return buf; } } return buf; } static void fontfilecache_Free(const unsigned char *buf) { int i; for(i = 0; i < MAX_FONTFILES; ++i) { if(fontfiles[i].refcount > 0) if(fontfiles[i].buf == buf) { if(--fontfiles[i].refcount <= 0) { Mem_Free(fontfiles[i].buf); fontfiles[i].buf = NULL; } return; } } // if we get here, it used regular allocation Mem_Free((void *) buf); } static void fontfilecache_FreeAll(void) { int i; for(i = 0; i < MAX_FONTFILES; ++i) { if(fontfiles[i].refcount > 0) Mem_Free(fontfiles[i].buf); fontfiles[i].buf = NULL; fontfiles[i].refcount = 0; } } /* ==================== Font_CloseLibrary Unload the FreeType2 DLL ==================== */ void Font_CloseLibrary (void) { fontfilecache_FreeAll(); if (font_mempool) Mem_FreePool(&font_mempool); if (font_ft2lib && qFT_Done_FreeType) { qFT_Done_FreeType(font_ft2lib); font_ft2lib = NULL; } #ifndef DP_FREETYPE_STATIC Sys_UnloadLibrary (&ft2_dll); #endif pp.buf = NULL; } /* ==================== Font_OpenLibrary Try to load the FreeType2 DLL ==================== */ qboolean Font_OpenLibrary (void) { #ifndef DP_FREETYPE_STATIC const char* dllnames [] = { #if defined(WIN32) "libfreetype-6.dll", "freetype6.dll", #elif defined(MACOSX) "libfreetype.6.dylib", "libfreetype.dylib", #else "libfreetype.so.6", "libfreetype.so", #endif NULL }; #endif if (r_font_disable_freetype.integer) return false; #ifndef DP_FREETYPE_STATIC // Already loaded? if (ft2_dll) return true; // Load the DLL if (!Sys_LoadLibrary (dllnames, &ft2_dll, ft2funcs)) return false; #endif return true; } /* ==================== Font_Init Initialize the freetype2 font subsystem ==================== */ void font_start(void) { if (!Font_OpenLibrary()) return; if (qFT_Init_FreeType(&font_ft2lib)) { Con_Print("ERROR: Failed to initialize the FreeType2 library!\n"); Font_CloseLibrary(); return; } font_mempool = Mem_AllocPool("FONT", 0, NULL); if (!font_mempool) { Con_Print("ERROR: Failed to allocate FONT memory pool!\n"); Font_CloseLibrary(); return; } } void font_shutdown(void) { int i; for (i = 0; i < dp_fonts.maxsize; ++i) { if (dp_fonts.f[i].ft2) { Font_UnloadFont(dp_fonts.f[i].ft2); dp_fonts.f[i].ft2 = NULL; } } Font_CloseLibrary(); } void font_newmap(void) { } void Font_Init(void) { Cvar_RegisterVariable(&r_font_nonpoweroftwo); Cvar_RegisterVariable(&r_font_disable_freetype); Cvar_RegisterVariable(&r_font_use_alpha_textures); Cvar_RegisterVariable(&r_font_size_snapping); Cvar_RegisterVariable(&r_font_kerning); Cvar_RegisterVariable(&r_font_diskcache); Cvar_RegisterVariable(&r_font_compress); Cvar_RegisterVariable(&developer_font); // let's open it at startup already Font_OpenLibrary(); } /* ================================================================================ Implementation of a more or less lazy font loading and rendering code. ================================================================================ */ #include "ft2_fontdefs.h" ft2_font_t *Font_Alloc(void) { #ifndef DP_FREETYPE_STATIC if (!ft2_dll) #else if (r_font_disable_freetype.integer) #endif return NULL; return (ft2_font_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_t)); } static qboolean Font_Attach(ft2_font_t *font, ft2_attachment_t *attachment) { ft2_attachment_t *na; font->attachmentcount++; na = (ft2_attachment_t*)Mem_Alloc(font_mempool, sizeof(font->attachments[0]) * font->attachmentcount); if (na == NULL) return false; if (font->attachments && font->attachmentcount > 1) { memcpy(na, font->attachments, sizeof(font->attachments[0]) * (font->attachmentcount - 1)); Mem_Free(font->attachments); } memcpy(na + sizeof(font->attachments[0]) * (font->attachmentcount - 1), attachment, sizeof(*attachment)); font->attachments = na; return true; } float Font_VirtualToRealSize(float sz) { int vh; //int vw; int si; float sn; if(sz < 0) return sz; //vw = ((vid.width > 0) ? vid.width : vid_width.value); vh = ((vid.height > 0) ? vid.height : vid_height.value); // now try to scale to our actual size: sn = sz * vh / vid_conheight.value; si = (int)sn; if ( sn - (float)si >= 0.5 ) ++si; return si; } float Font_SnapTo(float val, float snapwidth) { return floor(val / snapwidth + 0.5f) * snapwidth; } static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font); static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only); qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt) { int s, count, i; ft2_font_t *ft2, *fbfont, *fb; char vabuf[1024]; ft2 = Font_Alloc(); if (!ft2) { dpfnt->ft2 = NULL; return false; } // check if a fallback font has been specified, if it has been, and the // font fails to load, use the image font as main font for (i = 0; i < MAX_FONT_FALLBACKS; ++i) { if (dpfnt->fallbacks[i][0]) break; } if (!Font_LoadFile(name, dpfnt->req_face, &dpfnt->settings, ft2)) { if (i >= MAX_FONT_FALLBACKS) { dpfnt->ft2 = NULL; Mem_Free(ft2); return false; } strlcpy(ft2->name, name, sizeof(ft2->name)); ft2->image_font = true; ft2->has_kerning = false; } else { ft2->image_font = false; } // attempt to load fallback fonts: fbfont = ft2; for (i = 0; i < MAX_FONT_FALLBACKS; ++i) { if (!dpfnt->fallbacks[i][0]) break; if (! (fb = Font_Alloc()) ) { Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); break; } if (!Font_LoadFile(dpfnt->fallbacks[i], dpfnt->fallback_faces[i], &dpfnt->settings, fb)) { if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.tga", dpfnt->fallbacks[i]))) if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.png", dpfnt->fallbacks[i]))) if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.jpg", dpfnt->fallbacks[i]))) if(!FS_FileExists(va(vabuf, sizeof(vabuf), "%s.pcx", dpfnt->fallbacks[i]))) Con_Printf("Failed to load font %s for fallback %i of font %s\n", dpfnt->fallbacks[i], i, name); Mem_Free(fb); continue; } count = 0; for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) { if (Font_LoadSize(fb, Font_VirtualToRealSize(dpfnt->req_sizes[s]), true)) ++count; } if (!count) { Con_Printf("Failed to allocate font for fallback %i of font %s\n", i, name); Font_UnloadFont(fb); Mem_Free(fb); break; } // at least one size of the fallback font loaded successfully // link it: fbfont->next = fb; fbfont = fb; } if (fbfont == ft2 && ft2->image_font) { // no fallbacks were loaded successfully: dpfnt->ft2 = NULL; Mem_Free(ft2); return false; } count = 0; for (s = 0; s < MAX_FONT_SIZES && dpfnt->req_sizes[s] >= 0; ++s) { if (Font_LoadSize(ft2, Font_VirtualToRealSize(dpfnt->req_sizes[s]), false)) ++count; } if (!count) { // loading failed for every requested size Font_UnloadFont(ft2); Mem_Free(ft2); dpfnt->ft2 = NULL; return false; } //Con_Printf("%i sizes loaded\n", count); dpfnt->ft2 = ft2; return true; } static qboolean Font_LoadFile(const char *name, int _face, ft2_settings_t *settings, ft2_font_t *font) { size_t namelen; char filename[MAX_QPATH]; int status; size_t i; const unsigned char *data; fs_offset_t datasize; memset(font, 0, sizeof(*font)); if (!Font_OpenLibrary()) { if (!r_font_disable_freetype.integer) { Con_Printf("WARNING: can't open load font %s\n" "You need the FreeType2 DLL to load font files\n", name); } return false; } font->settings = settings; namelen = strlen(name); if (namelen + 5 > sizeof(filename)) { Con_Printf("WARNING: too long font name. Cannot load this.\n"); return false; } // try load direct file memcpy(filename, name, namelen+1); data = fontfilecache_LoadFile(filename, false, &datasize); // try load .ttf if (!data) { memcpy(filename + namelen, ".ttf", 5); data = fontfilecache_LoadFile(filename, false, &datasize); } // try load .otf if (!data) { memcpy(filename + namelen, ".otf", 5); data = fontfilecache_LoadFile(filename, false, &datasize); } // try load .pfb/afm if (!data) { ft2_attachment_t afm; memcpy(filename + namelen, ".pfb", 5); data = fontfilecache_LoadFile(filename, false, &datasize); if (data) { memcpy(filename + namelen, ".afm", 5); afm.data = fontfilecache_LoadFile(filename, false, &afm.size); if (afm.data) Font_Attach(font, &afm); } } if (!data) { // FS_LoadFile being not-quiet should print an error :) return false; } Con_DPrintf("Loading font %s face %i...\n", filename, _face); status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, _face, (FT_Face*)&font->face); if (status && _face != 0) { Con_Printf("Failed to load face %i of %s. Falling back to face 0\n", _face, name); _face = 0; status = qFT_New_Memory_Face(font_ft2lib, (FT_Bytes)data, datasize, _face, (FT_Face*)&font->face); } font->data = data; if (status) { Con_Printf("ERROR: can't create face for %s\n" "Error %i\n", // TODO: error strings name, status); Font_UnloadFont(font); return false; } // add the attachments for (i = 0; i < font->attachmentcount; ++i) { FT_Open_Args args; memset(&args, 0, sizeof(args)); args.flags = FT_OPEN_MEMORY; args.memory_base = (const FT_Byte*)font->attachments[i].data; args.memory_size = font->attachments[i].size; if (qFT_Attach_Stream((FT_Face)font->face, &args)) Con_Printf("Failed to add attachment %u to %s\n", (unsigned)i, font->name); } strlcpy(font->name, name, sizeof(font->name)); font->image_font = false; font->has_kerning = !!(((FT_Face)(font->face))->face_flags & FT_FACE_FLAG_KERNING); return true; } static void Font_Postprocess_Update(ft2_font_t *fnt, int bpp, int w, int h) { int needed, x, y; float gausstable[2*POSTPROCESS_MAXRADIUS+1]; qboolean need_gauss = (!pp.buf || pp.blur != fnt->settings->blur || pp.shadowz != fnt->settings->shadowz); qboolean need_circle = (!pp.buf || pp.outline != fnt->settings->outline || pp.shadowx != fnt->settings->shadowx || pp.shadowy != fnt->settings->shadowy); pp.blur = fnt->settings->blur; pp.outline = fnt->settings->outline; pp.shadowx = fnt->settings->shadowx; pp.shadowy = fnt->settings->shadowy; pp.shadowz = fnt->settings->shadowz; pp.outlinepadding_l = bound(0, ceil(pp.outline - pp.shadowx), POSTPROCESS_MAXRADIUS); pp.outlinepadding_r = bound(0, ceil(pp.outline + pp.shadowx), POSTPROCESS_MAXRADIUS); pp.outlinepadding_t = bound(0, ceil(pp.outline - pp.shadowy), POSTPROCESS_MAXRADIUS); pp.outlinepadding_b = bound(0, ceil(pp.outline + pp.shadowy), POSTPROCESS_MAXRADIUS); pp.blurpadding_lt = bound(0, ceil(pp.blur - pp.shadowz), POSTPROCESS_MAXRADIUS); pp.blurpadding_rb = bound(0, ceil(pp.blur + pp.shadowz), POSTPROCESS_MAXRADIUS); pp.padding_l = pp.blurpadding_lt + pp.outlinepadding_l; pp.padding_r = pp.blurpadding_rb + pp.outlinepadding_r; pp.padding_t = pp.blurpadding_lt + pp.outlinepadding_t; pp.padding_b = pp.blurpadding_rb + pp.outlinepadding_b; if(need_gauss) { float sum = 0; for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) gausstable[POSTPROCESS_MAXRADIUS+x] = (pp.blur > 0 ? exp(-(pow(x + pp.shadowz, 2))/(pp.blur*pp.blur * 2)) : (floor(x + pp.shadowz + 0.5) == 0)); for(x = -pp.blurpadding_rb; x <= pp.blurpadding_lt; ++x) sum += gausstable[POSTPROCESS_MAXRADIUS+x]; for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) pp.gausstable[POSTPROCESS_MAXRADIUS+x] = floor(gausstable[POSTPROCESS_MAXRADIUS+x] / sum * 255 + 0.5); } if(need_circle) { for(y = -POSTPROCESS_MAXRADIUS; y <= POSTPROCESS_MAXRADIUS; ++y) for(x = -POSTPROCESS_MAXRADIUS; x <= POSTPROCESS_MAXRADIUS; ++x) { float d = pp.outline + 1 - sqrt(pow(x + pp.shadowx, 2) + pow(y + pp.shadowy, 2)); pp.circlematrix[POSTPROCESS_MAXRADIUS+y][POSTPROCESS_MAXRADIUS+x] = (d >= 1) ? 255 : (d <= 0) ? 0 : floor(d * 255 + 0.5); } } pp.bufwidth = w + pp.padding_l + pp.padding_r; pp.bufheight = h + pp.padding_t + pp.padding_b; pp.bufpitch = pp.bufwidth; needed = pp.bufwidth * pp.bufheight; if(!pp.buf || pp.bufsize < needed * 2) { if(pp.buf) Mem_Free(pp.buf); pp.bufsize = needed * 4; pp.buf = (unsigned char *)Mem_Alloc(font_mempool, pp.bufsize); pp.buf2 = pp.buf + needed; } } static void Font_Postprocess(ft2_font_t *fnt, unsigned char *imagedata, int pitch, int bpp, int w, int h, int *pad_l, int *pad_r, int *pad_t, int *pad_b) { int x, y; // calculate gauss table Font_Postprocess_Update(fnt, bpp, w, h); if(imagedata) { // enlarge buffer // perform operation, not exceeding the passed padding values, // but possibly reducing them *pad_l = min(*pad_l, pp.padding_l); *pad_r = min(*pad_r, pp.padding_r); *pad_t = min(*pad_t, pp.padding_t); *pad_b = min(*pad_b, pp.padding_b); // outline the font (RGBA only) if(bpp == 4 && (pp.outline > 0 || pp.blur > 0 || pp.shadowx != 0 || pp.shadowy != 0 || pp.shadowz != 0)) // we can only do this in BGRA { // this is like mplayer subtitle rendering // bbuffer, bitmap buffer: this is our font // abuffer, alpha buffer: this is pp.buf // tmp: this is pp.buf2 // create outline buffer memset(pp.buf, 0, pp.bufwidth * pp.bufheight); for(y = -*pad_t; y < h + *pad_b; ++y) for(x = -*pad_l; x < w + *pad_r; ++x) { int x1 = max(-x, -pp.outlinepadding_r); int y1 = max(-y, -pp.outlinepadding_b); int x2 = min(pp.outlinepadding_l, w-1-x); int y2 = min(pp.outlinepadding_t, h-1-y); int mx, my; int cur = 0; int highest = 0; for(my = y1; my <= y2; ++my) for(mx = x1; mx <= x2; ++mx) { cur = pp.circlematrix[POSTPROCESS_MAXRADIUS+my][POSTPROCESS_MAXRADIUS+mx] * (int)imagedata[(x+mx) * bpp + pitch * (y+my) + (bpp - 1)]; if(cur > highest) highest = cur; } pp.buf[((x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t))] = (highest + 128) / 255; } // blur the outline buffer if(pp.blur > 0 || pp.shadowz != 0) { // horizontal blur for(y = 0; y < pp.bufheight; ++y) for(x = 0; x < pp.bufwidth; ++x) { int x1 = max(-x, -pp.blurpadding_rb); int x2 = min(pp.blurpadding_lt, pp.bufwidth-1-x); int mx; int blurred = 0; for(mx = x1; mx <= x2; ++mx) blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+mx] * (int)pp.buf[(x+mx) + pp.bufpitch * y]; pp.buf2[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; } // vertical blur for(y = 0; y < pp.bufheight; ++y) for(x = 0; x < pp.bufwidth; ++x) { int y1 = max(-y, -pp.blurpadding_rb); int y2 = min(pp.blurpadding_lt, pp.bufheight-1-y); int my; int blurred = 0; for(my = y1; my <= y2; ++my) blurred += pp.gausstable[POSTPROCESS_MAXRADIUS+my] * (int)pp.buf2[x + pp.bufpitch * (y+my)]; pp.buf[x + pp.bufpitch * y] = bound(0, blurred, 65025) / 255; } } // paste the outline below the font for(y = -*pad_t; y < h + *pad_b; ++y) for(x = -*pad_l; x < w + *pad_r; ++x) { unsigned char outlinealpha = pp.buf[(x + pp.padding_l) + pp.bufpitch * (y + pp.padding_t)]; if(outlinealpha > 0) { unsigned char oldalpha = imagedata[x * bpp + pitch * y + (bpp - 1)]; // a' = 1 - (1 - a1) (1 - a2) unsigned char newalpha = 255 - ((255 - (int)outlinealpha) * (255 - (int)oldalpha)) / 255; // this is >= oldalpha // c' = (a2 c2 - a1 a2 c1 + a1 c1) / a' = (a2 c2 + a1 (1 - a2) c1) / a' unsigned char oldfactor = (255 * (int)oldalpha) / newalpha; //unsigned char outlinefactor = ((255 - oldalpha) * (int)outlinealpha) / newalpha; int i; for(i = 0; i < bpp-1; ++i) { unsigned char c = imagedata[x * bpp + pitch * y + i]; c = (c * (int)oldfactor) / 255 /* + outlinecolor[i] * (int)outlinefactor */; imagedata[x * bpp + pitch * y + i] = c; } imagedata[x * bpp + pitch * y + (bpp - 1)] = newalpha; } //imagedata[x * bpp + pitch * y + (bpp - 1)] |= 0x80; } } } else if(pitch) { // perform operation, not exceeding the passed padding values, // but possibly reducing them *pad_l = min(*pad_l, pp.padding_l); *pad_r = min(*pad_r, pp.padding_r); *pad_t = min(*pad_t, pp.padding_t); *pad_b = min(*pad_b, pp.padding_b); } else { // just calculate parameters *pad_l = pp.padding_l; *pad_r = pp.padding_r; *pad_t = pp.padding_t; *pad_b = pp.padding_b; } } static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size); static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap); static qboolean Font_LoadSize(ft2_font_t *font, float size, qboolean check_only) { int map_index; ft2_font_map_t *fmap, temp; int gpad_l, gpad_r, gpad_t, gpad_b; if (!(size > 0.001f && size < 1000.0f)) size = 0; if (!size) size = 16; if (size < 2) // bogus sizes are not allowed - and they screw up our allocations return false; for (map_index = 0; map_index < MAX_FONT_SIZES; ++map_index) { if (!font->font_maps[map_index]) break; // if a similar size has already been loaded, ignore this one //abs(font->font_maps[map_index]->size - size) < 4 if (font->font_maps[map_index]->size == size) return true; } if (map_index >= MAX_FONT_SIZES) return false; if (check_only) { FT_Face fontface; if (font->image_font) fontface = (FT_Face)font->next->face; else fontface = (FT_Face)font->face; return (Font_SearchSize(font, fontface, size) > 0); } Font_Postprocess(font, NULL, 0, 4, size*2, size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); memset(&temp, 0, sizeof(temp)); temp.size = size; temp.glyphSize = size*2 + max(gpad_l + gpad_r, gpad_t + gpad_b); if (!(r_font_nonpoweroftwo.integer && vid.support.arb_texture_non_power_of_two)) temp.glyphSize = CeilPowerOf2(temp.glyphSize); temp.sfx = (1.0/64.0)/(double)size; temp.sfy = (1.0/64.0)/(double)size; temp.intSize = -1; // negative value: LoadMap must search now :) if (!Font_LoadMap(font, &temp, 0, &fmap)) { Con_Printf("ERROR: can't load the first character map for %s\n" "This is fatal\n", font->name); Font_UnloadFont(font); return false; } font->font_maps[map_index] = temp.next; fmap->sfx = temp.sfx; fmap->sfy = temp.sfy; // load the default kerning vector: if (font->has_kerning) { Uchar l, r; FT_Vector kernvec; for (l = 0; l < 256; ++l) { for (r = 0; r < 256; ++r) { FT_ULong ul, ur; ul = qFT_Get_Char_Index((FT_Face)font->face, l); ur = qFT_Get_Char_Index((FT_Face)font->face, r); if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) { fmap->kerning.kerning[l][r][0] = 0; fmap->kerning.kerning[l][r][1] = 0; } else { fmap->kerning.kerning[l][r][0] = Font_SnapTo((kernvec.x / 64.0) / fmap->size, 1 / fmap->size); fmap->kerning.kerning[l][r][1] = Font_SnapTo((kernvec.y / 64.0) / fmap->size, 1 / fmap->size); } } } } return true; } int Font_IndexForSize(ft2_font_t *font, float _fsize, float *outw, float *outh) { int match = -1; float value = 1000000; float nval; int matchsize = -10000; int m; float fsize_x, fsize_y; ft2_font_map_t **maps = font->font_maps; fsize_x = fsize_y = _fsize * vid.height / vid_conheight.value; if(outw && *outw) fsize_x = *outw * vid.width / vid_conwidth.value; if(outh && *outh) fsize_y = *outh * vid.height / vid_conheight.value; if (fsize_x < 0) { if(fsize_y < 0) fsize_x = fsize_y = 16; else fsize_x = fsize_y; } else { if(fsize_y < 0) fsize_y = fsize_x; } for (m = 0; m < MAX_FONT_SIZES; ++m) { if (!maps[m]) continue; // "round up" to the bigger size if two equally-valued matches exist nval = 0.5 * (fabs(maps[m]->size - fsize_x) + fabs(maps[m]->size - fsize_y)); if (match == -1 || nval < value || (nval == value && matchsize < maps[m]->size)) { value = nval; match = m; matchsize = maps[m]->size; if (value == 0) // there is no better match break; } } if (value <= r_font_size_snapping.value) { // do NOT keep the aspect for perfect rendering if (outh) *outh = maps[match]->size * vid_conheight.value / vid.height; if (outw) *outw = maps[match]->size * vid_conwidth.value / vid.width; } return match; } ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index) { if (index < 0 || index >= MAX_FONT_SIZES) return NULL; return font->font_maps[index]; } static qboolean Font_SetSize(ft2_font_t *font, float w, float h) { if (font->currenth == h && ((!w && (!font->currentw || font->currentw == font->currenth)) || // check if w==h when w is not set font->currentw == w)) // same size has been requested { return true; } // sorry, but freetype doesn't seem to care about other sizes w = (int)w; h = (int)h; if (font->image_font) { if (qFT_Set_Char_Size((FT_Face)font->next->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) return false; } else { if (qFT_Set_Char_Size((FT_Face)font->face, (FT_F26Dot6)(w*64), (FT_F26Dot6)(h*64), 72, 72)) return false; } font->currentw = w; font->currenth = h; return true; } qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy) { ft2_font_map_t *fmap; if (!font->has_kerning || !r_font_kerning.integer) return false; if (map_index < 0 || map_index >= MAX_FONT_SIZES) return false; fmap = font->font_maps[map_index]; if (!fmap) return false; if (left < 256 && right < 256) { //Con_Printf("%g : %f, %f, %f :: %f\n", (w / (float)fmap->size), w, fmap->size, fmap->intSize, Font_VirtualToRealSize(w)); // quick-kerning, be aware of the size: scale it if (outx) *outx = fmap->kerning.kerning[left][right][0];// * (w / (float)fmap->size); if (outy) *outy = fmap->kerning.kerning[left][right][1];// * (h / (float)fmap->size); return true; } else { FT_Vector kernvec; FT_ULong ul, ur; //if (qFT_Set_Pixel_Sizes((FT_Face)font->face, 0, fmap->size)) #if 0 if (!Font_SetSize(font, w, h)) { // this deserves an error message Con_Printf("Failed to get kerning for %s\n", font->name); return false; } ul = qFT_Get_Char_Index(font->face, left); ur = qFT_Get_Char_Index(font->face, right); if (qFT_Get_Kerning(font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) { if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size); if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size); return true; } #endif if (!Font_SetSize(font, fmap->intSize, fmap->intSize)) { // this deserves an error message Con_Printf("Failed to get kerning for %s\n", font->name); return false; } ul = qFT_Get_Char_Index((FT_Face)font->face, left); ur = qFT_Get_Char_Index((FT_Face)font->face, right); if (qFT_Get_Kerning((FT_Face)font->face, ul, ur, FT_KERNING_DEFAULT, &kernvec)) { if (outx) *outx = Font_SnapTo(kernvec.x * fmap->sfx, 1 / fmap->size);// * (w / (float)fmap->size); if (outy) *outy = Font_SnapTo(kernvec.y * fmap->sfy, 1 / fmap->size);// * (h / (float)fmap->size); return true; } return false; } } qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy) { return Font_GetKerningForMap(font, Font_IndexForSize(font, h, NULL, NULL), w, h, left, right, outx, outy); } static void UnloadMapRec(ft2_font_map_t *map) { if (map->pic) { //Draw_FreePic(map->pic); // FIXME: refcounting needed... map->pic = NULL; } if (map->next) UnloadMapRec(map->next); Mem_Free(map); } void Font_UnloadFont(ft2_font_t *font) { int i; // unload fallbacks if(font->next) Font_UnloadFont(font->next); if (font->attachments && font->attachmentcount) { for (i = 0; i < (int)font->attachmentcount; ++i) { if (font->attachments[i].data) fontfilecache_Free(font->attachments[i].data); } Mem_Free(font->attachments); font->attachmentcount = 0; font->attachments = NULL; } for (i = 0; i < MAX_FONT_SIZES; ++i) { if (font->font_maps[i]) { UnloadMapRec(font->font_maps[i]); font->font_maps[i] = NULL; } } #ifndef DP_FREETYPE_STATIC if (ft2_dll) #else if (!r_font_disable_freetype.integer) #endif { if (font->face) { qFT_Done_Face((FT_Face)font->face); font->face = NULL; } } if (font->data) { fontfilecache_Free(font->data); font->data = NULL; } } static float Font_SearchSize(ft2_font_t *font, FT_Face fontface, float size) { float intSize = size; while (1) { if (!Font_SetSize(font, intSize, intSize)) { Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, size, intSize); return -1; } if ((fontface->size->metrics.height>>6) <= size) return intSize; if (intSize < 2) { Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, size); return -1; } --intSize; } } static qboolean Font_LoadMap(ft2_font_t *font, ft2_font_map_t *mapstart, Uchar _ch, ft2_font_map_t **outmap) { char map_identifier[MAX_QPATH]; unsigned long mapidx = _ch / FONT_CHARS_PER_MAP; unsigned char *data = NULL; FT_ULong ch, mapch; int status; int tp; FT_Int32 load_flags; int gpad_l, gpad_r, gpad_t, gpad_b; char vabuf[1024]; int pitch; int gR, gC; // glyph position: row and column ft2_font_map_t *map, *next; ft2_font_t *usefont; FT_Face fontface; int bytesPerPixel = 4; // change the conversion loop too if you change this! if (outmap) *outmap = NULL; if (r_font_use_alpha_textures.integer) bytesPerPixel = 1; if (font->image_font) fontface = (FT_Face)font->next->face; else fontface = (FT_Face)font->face; switch(font->settings->antialias) { case 0: switch(font->settings->hinting) { case 0: load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; break; case 1: case 2: load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; break; default: case 3: load_flags = FT_LOAD_TARGET_MONO | FT_LOAD_MONOCHROME; break; } break; default: case 1: switch(font->settings->hinting) { case 0: load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_TARGET_NORMAL; break; case 1: load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_LIGHT; break; case 2: load_flags = FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL; break; default: case 3: load_flags = FT_LOAD_TARGET_NORMAL; break; } break; } //status = qFT_Set_Pixel_Sizes((FT_Face)font->face, /*size*/0, mapstart->size); //if (status) if (font->image_font && mapstart->intSize < 0) mapstart->intSize = mapstart->size; if (mapstart->intSize < 0) { /* mapstart->intSize = mapstart->size; while (1) { if (!Font_SetSize(font, mapstart->intSize, mapstart->intSize)) { Con_Printf("ERROR: can't set size for font %s: %f ((%f))\n", font->name, mapstart->size, mapstart->intSize); return false; } if ((fontface->size->metrics.height>>6) <= mapstart->size) break; if (mapstart->intSize < 2) { Con_Printf("ERROR: no appropriate size found for font %s: %f\n", font->name, mapstart->size); return false; } --mapstart->intSize; } */ if ((mapstart->intSize = Font_SearchSize(font, fontface, mapstart->size)) <= 0) return false; Con_DPrintf("Using size: %f for requested size %f\n", mapstart->intSize, mapstart->size); } if (!font->image_font && !Font_SetSize(font, mapstart->intSize, mapstart->intSize)) { Con_Printf("ERROR: can't set sizes for font %s: %f\n", font->name, mapstart->size); return false; } map = (ft2_font_map_t *)Mem_Alloc(font_mempool, sizeof(ft2_font_map_t)); if (!map) { Con_Printf("ERROR: Out of memory when loading fontmap for %s\n", font->name); return false; } // create a totally unique name for this map, then we will use it to make a unique cachepic_t to avoid redundant textures dpsnprintf(map_identifier, sizeof(map_identifier), "%s_cache_%g_%d_%g_%g_%g_%g_%g_%u", font->name, (double) mapstart->intSize, (int) load_flags, (double) font->settings->blur, (double) font->settings->outline, (double) font->settings->shadowx, (double) font->settings->shadowy, (double) font->settings->shadowz, (unsigned) mapidx); // create a cachepic_t from the data now, or reuse an existing one map->pic = Draw_CachePic_Flags(map_identifier, CACHEPICFLAG_QUIET); if (developer_font.integer) { if (map->pic->tex == r_texture_notexture) Con_Printf("Generating font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); else Con_Printf("Using cached font map %s (size: %.1f MB)\n", map_identifier, mapstart->glyphSize * (256 * 4 / 1048576.0) * mapstart->glyphSize); } Font_Postprocess(font, NULL, 0, bytesPerPixel, mapstart->size*2, mapstart->size*2, &gpad_l, &gpad_r, &gpad_t, &gpad_b); // copy over the information map->size = mapstart->size; map->intSize = mapstart->intSize; map->glyphSize = mapstart->glyphSize; map->sfx = mapstart->sfx; map->sfy = mapstart->sfy; pitch = map->glyphSize * FONT_CHARS_PER_LINE * bytesPerPixel; if (map->pic->tex == r_texture_notexture) { data = (unsigned char *)Mem_Alloc(font_mempool, (FONT_CHAR_LINES * map->glyphSize) * pitch); if (!data) { Con_Printf("ERROR: Failed to allocate memory for font %s size %g\n", font->name, map->size); Mem_Free(map); return false; } // initialize as white texture with zero alpha tp = 0; while (tp < (FONT_CHAR_LINES * map->glyphSize) * pitch) { if (bytesPerPixel == 4) { data[tp++] = 0xFF; data[tp++] = 0xFF; data[tp++] = 0xFF; } data[tp++] = 0x00; } } memset(map->width_of, 0, sizeof(map->width_of)); // insert the map map->start = mapidx * FONT_CHARS_PER_MAP; next = mapstart; while(next->next && next->next->start < map->start) next = next->next; map->next = next->next; next->next = map; gR = 0; gC = -1; for (ch = map->start; ch < (FT_ULong)map->start + FONT_CHARS_PER_MAP; ++ch) { FT_ULong glyphIndex; int w, h, x, y; FT_GlyphSlot glyph; FT_Bitmap *bmp; unsigned char *imagedata = NULL, *dst, *src; glyph_slot_t *mapglyph; FT_Face face; int pad_l, pad_r, pad_t, pad_b; mapch = ch - map->start; if (developer_font.integer) Con_DPrint("glyphinfo: ------------- GLYPH INFO -----------------\n"); ++gC; if (gC >= FONT_CHARS_PER_LINE) { gC -= FONT_CHARS_PER_LINE; ++gR; } if (data) { imagedata = data + gR * pitch * map->glyphSize + gC * map->glyphSize * bytesPerPixel; imagedata += gpad_t * pitch + gpad_l * bytesPerPixel; } //status = qFT_Load_Char(face, ch, FT_LOAD_RENDER); // we need the glyphIndex face = (FT_Face)font->face; usefont = NULL; if (font->image_font && mapch == ch && img_fontmap[mapch]) { map->glyphs[mapch].image = true; continue; } glyphIndex = qFT_Get_Char_Index(face, ch); if (glyphIndex == 0) { // by convention, 0 is the "missing-glyph"-glyph // try to load from a fallback font for(usefont = font->next; usefont != NULL; usefont = usefont->next) { if (!Font_SetSize(usefont, mapstart->intSize, mapstart->intSize)) continue; // try that glyph face = (FT_Face)usefont->face; glyphIndex = qFT_Get_Char_Index(face, ch); if (glyphIndex == 0) continue; status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); if (status) continue; break; } if (!usefont) { //Con_Printf("failed to load fallback glyph for char %lx from font %s\n", (unsigned long)ch, font->name); // now we let it use the "missing-glyph"-glyph face = (FT_Face)font->face; glyphIndex = 0; } } if (!usefont) { usefont = font; face = (FT_Face)font->face; status = qFT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER | load_flags); if (status) { //Con_Printf("failed to load glyph %lu for %s\n", glyphIndex, font->name); Con_DPrintf("failed to load glyph for char %lx from font %s\n", (unsigned long)ch, font->name); continue; } } glyph = face->glyph; bmp = &glyph->bitmap; w = bmp->width; h = bmp->rows; if (w > (map->glyphSize - gpad_l - gpad_r) || h > (map->glyphSize - gpad_t - gpad_b)) { Con_Printf("WARNING: Glyph %lu is too big in font %s, size %g: %i x %i\n", ch, font->name, map->size, w, h); if (w > map->glyphSize) w = map->glyphSize - gpad_l - gpad_r; if (h > map->glyphSize) h = map->glyphSize; } if (imagedata) { switch (bmp->pixel_mode) { case FT_PIXEL_MODE_MONO: if (developer_font.integer) Con_DPrint("glyphinfo: Pixel Mode: MONO\n"); break; case FT_PIXEL_MODE_GRAY2: if (developer_font.integer) Con_DPrint("glyphinfo: Pixel Mode: GRAY2\n"); break; case FT_PIXEL_MODE_GRAY4: if (developer_font.integer) Con_DPrint("glyphinfo: Pixel Mode: GRAY4\n"); break; case FT_PIXEL_MODE_GRAY: if (developer_font.integer) Con_DPrint("glyphinfo: Pixel Mode: GRAY\n"); break; default: if (developer_font.integer) Con_DPrintf("glyphinfo: Pixel Mode: Unknown: %i\n", bmp->pixel_mode); Mem_Free(data); Con_Printf("ERROR: Unrecognized pixel mode for font %s size %f: %i\n", font->name, mapstart->size, bmp->pixel_mode); return false; } for (y = 0; y < h; ++y) { dst = imagedata + y * pitch; src = bmp->buffer + y * bmp->pitch; switch (bmp->pixel_mode) { case FT_PIXEL_MODE_MONO: dst += bytesPerPixel - 1; // shift to alpha byte for (x = 0; x < bmp->width; x += 8) { unsigned char c = *src++; *dst = 255 * !!((c & 0x80) >> 7); dst += bytesPerPixel; *dst = 255 * !!((c & 0x40) >> 6); dst += bytesPerPixel; *dst = 255 * !!((c & 0x20) >> 5); dst += bytesPerPixel; *dst = 255 * !!((c & 0x10) >> 4); dst += bytesPerPixel; *dst = 255 * !!((c & 0x08) >> 3); dst += bytesPerPixel; *dst = 255 * !!((c & 0x04) >> 2); dst += bytesPerPixel; *dst = 255 * !!((c & 0x02) >> 1); dst += bytesPerPixel; *dst = 255 * !!((c & 0x01) >> 0); dst += bytesPerPixel; } break; case FT_PIXEL_MODE_GRAY2: dst += bytesPerPixel - 1; // shift to alpha byte for (x = 0; x < bmp->width; x += 4) { unsigned char c = *src++; *dst = ( ((c & 0xA0) >> 6) * 0x55 ); c <<= 2; dst += bytesPerPixel; *dst = ( ((c & 0xA0) >> 6) * 0x55 ); c <<= 2; dst += bytesPerPixel; *dst = ( ((c & 0xA0) >> 6) * 0x55 ); c <<= 2; dst += bytesPerPixel; *dst = ( ((c & 0xA0) >> 6) * 0x55 ); c <<= 2; dst += bytesPerPixel; } break; case FT_PIXEL_MODE_GRAY4: dst += bytesPerPixel - 1; // shift to alpha byte for (x = 0; x < bmp->width; x += 2) { unsigned char c = *src++; *dst = ( ((c & 0xF0) >> 4) * 0x11); dst += bytesPerPixel; *dst = ( ((c & 0x0F) ) * 0x11); dst += bytesPerPixel; } break; case FT_PIXEL_MODE_GRAY: // in this case pitch should equal width for (tp = 0; tp < bmp->pitch; ++tp) dst[(bytesPerPixel - 1) + tp*bytesPerPixel] = src[tp]; // copy the grey value into the alpha bytes //memcpy((void*)dst, (void*)src, bmp->pitch); //dst += bmp->pitch; break; default: break; } } pad_l = gpad_l; pad_r = gpad_r; pad_t = gpad_t; pad_b = gpad_b; Font_Postprocess(font, imagedata, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); } else { pad_l = gpad_l; pad_r = gpad_r; pad_t = gpad_t; pad_b = gpad_b; Font_Postprocess(font, NULL, pitch, bytesPerPixel, w, h, &pad_l, &pad_r, &pad_t, &pad_b); } // now fill map->glyphs[ch - map->start] mapglyph = &map->glyphs[mapch]; { // old way // double advance = (double)glyph->metrics.horiAdvance * map->sfx; double bearingX = (glyph->metrics.horiBearingX / 64.0) / map->size; //double bearingY = (glyph->metrics.horiBearingY >> 6) / map->size; double advance = (glyph->advance.x / 64.0) / map->size; //double mWidth = (glyph->metrics.width >> 6) / map->size; //double mHeight = (glyph->metrics.height >> 6) / map->size; mapglyph->txmin = ( (double)(gC * map->glyphSize) + (double)(gpad_l - pad_l) ) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); mapglyph->txmax = mapglyph->txmin + (double)(bmp->width + pad_l + pad_r) / ( (double)(map->glyphSize * FONT_CHARS_PER_LINE) ); mapglyph->tymin = ( (double)(gR * map->glyphSize) + (double)(gpad_r - pad_r) ) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); mapglyph->tymax = mapglyph->tymin + (double)(bmp->rows + pad_t + pad_b) / ( (double)(map->glyphSize * FONT_CHAR_LINES) ); //mapglyph->vxmin = bearingX; //mapglyph->vxmax = bearingX + mWidth; mapglyph->vxmin = (glyph->bitmap_left - pad_l) / map->size; mapglyph->vxmax = mapglyph->vxmin + (bmp->width + pad_l + pad_r) / map->size; // don't ask //mapglyph->vymin = -bearingY; //mapglyph->vymax = mHeight - bearingY; mapglyph->vymin = (-glyph->bitmap_top - pad_t) / map->size; mapglyph->vymax = mapglyph->vymin + (bmp->rows + pad_t + pad_b) / map->size; //Con_Printf("dpi = %f %f (%f %d) %d %d\n", bmp->width / (mapglyph->vxmax - mapglyph->vxmin), bmp->rows / (mapglyph->vymax - mapglyph->vymin), map->size, map->glyphSize, (int)fontface->size->metrics.x_ppem, (int)fontface->size->metrics.y_ppem); //mapglyph->advance_x = advance * usefont->size; //mapglyph->advance_x = advance; mapglyph->advance_x = Font_SnapTo(advance, 1 / map->size); mapglyph->advance_y = 0; if (developer_font.integer) { Con_DPrintf("glyphinfo: Glyph: %lu at (%i, %i)\n", (unsigned long)ch, gC, gR); Con_DPrintf("glyphinfo: %f, %f, %lu\n", bearingX, map->sfx, (unsigned long)glyph->metrics.horiBearingX); if (ch >= 32 && ch <= 128) Con_DPrintf("glyphinfo: Character: %c\n", (int)ch); Con_DPrintf("glyphinfo: Vertex info:\n"); Con_DPrintf("glyphinfo: X: ( %f -- %f )\n", mapglyph->vxmin, mapglyph->vxmax); Con_DPrintf("glyphinfo: Y: ( %f -- %f )\n", mapglyph->vymin, mapglyph->vymax); Con_DPrintf("glyphinfo: Texture info:\n"); Con_DPrintf("glyphinfo: S: ( %f -- %f )\n", mapglyph->txmin, mapglyph->txmax); Con_DPrintf("glyphinfo: T: ( %f -- %f )\n", mapglyph->tymin, mapglyph->tymax); Con_DPrintf("glyphinfo: Advance: %f, %f\n", mapglyph->advance_x, mapglyph->advance_y); } } map->glyphs[mapch].image = false; } if (map->pic->tex == r_texture_notexture) { int w = map->glyphSize * FONT_CHARS_PER_LINE; int h = map->glyphSize * FONT_CHAR_LINES; rtexture_t *tex; // abuse the Draw_CachePic system to keep track of this texture tex = R_LoadTexture2D(drawtexturepool, map_identifier, w, h, data, r_font_use_alpha_textures.integer ? TEXTYPE_ALPHA : TEXTYPE_RGBA, TEXF_ALPHA | (r_font_compress.integer > 0 ? TEXF_COMPRESS : 0), -1, NULL); // if tex is NULL for any reason, the pic->tex will remain set to r_texture_notexture if (tex) map->pic->tex = tex; if (r_font_diskcache.integer >= 1) { // swap to BGRA for tga writing... int s = w * h; int x; int b; for (x = 0;x < s;x++) { b = data[x*4+0]; data[x*4+0] = data[x*4+2]; data[x*4+2] = b; } Image_WriteTGABGRA(va(vabuf, sizeof(vabuf), "%s.tga", map_identifier), w, h, data); #ifndef USE_GLES2 if (r_font_compress.integer && qglGetCompressedTexImageARB && tex) R_SaveTextureDDSFile(tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", map_identifier), r_texture_dds_save.integer < 2, true); #endif } } if(data) Mem_Free(data); if (map->pic->tex == r_texture_notexture) { // if the first try isn't successful, keep it with a broken texture // otherwise we retry to load it every single frame where ft2 rendering is used // this would be bad... // only `data' must be freed Con_Printf("ERROR: Failed to generate texture for font %s size %f map %lu\n", font->name, mapstart->size, mapidx); return false; } if (outmap) *outmap = map; return true; } qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap) { if (map_index < 0 || map_index >= MAX_FONT_SIZES) return false; // the first map must have been loaded already if (!font->font_maps[map_index]) return false; return Font_LoadMap(font, font->font_maps[map_index], _ch, outmap); } ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch) { while (start && start->start + FONT_CHARS_PER_MAP <= ch) start = start->next; if (start && start->start > ch) return NULL; return start; } darkplaces/dpvsimpledecode.h0000664000175000017500000000312113067716220015500 0ustar kalevkalev #ifndef DPVSIMPLEDECODE_H #define DPVSIMPLEDECODE_H #include "cl_video.h" #define DPVSIMPLEDECODEERROR_NONE 0 #define DPVSIMPLEDECODEERROR_EOF 1 #define DPVSIMPLEDECODEERROR_READERROR 2 #define DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL 3 #define DPVSIMPLEDECODEERROR_INVALIDRMASK 4 #define DPVSIMPLEDECODEERROR_INVALIDGMASK 5 #define DPVSIMPLEDECODEERROR_INVALIDBMASK 6 #define DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP 7 #define DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP 8 #define DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP 9 // opening and closing streams // opens a stream void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring); // closes a stream void dpvsimpledecode_close(void *stream); // utilitarian functions // returns the current error number for the stream, and resets the error // number to DPVDECODEERROR_NONE // if the supplied string pointer variable is not NULL, it will be set to the // error message int dpvsimpledecode_error(void *stream, const char **errorstring); // returns the width of the image data unsigned int dpvsimpledecode_getwidth(void *stream); // returns the height of the image data unsigned int dpvsimpledecode_getheight(void *stream); // returns the framerate of the stream double dpvsimpledecode_getframerate(void *stream); // returns aspect ratio of the stream double dpvsimpledecode_getaspectratio(void *stream); // decodes a video frame to the supplied output pixels int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); #endif darkplaces/snd_alsa.c0000664000175000017500000003027113067716222014120 0ustar kalevkalev/* Copyright (C) 2006 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ // ALSA module, used by Linux #include "quakedef.h" #include #include "snd_main.h" #define NB_PERIODS 4 static snd_pcm_t* pcm_handle = NULL; static snd_pcm_sframes_t expected_delay = 0; static unsigned int alsasoundtime; static snd_seq_t* seq_handle = NULL; /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { const char* pcm_name, *seq_name; int i, err, seq_client, seq_port; snd_pcm_hw_params_t* hw_params = NULL; snd_pcm_format_t snd_pcm_format; snd_pcm_uframes_t buffer_size; Con_Print ("SndSys_Init: using the ALSA module\n"); seq_name = NULL; // COMMANDLINEOPTION: Linux ALSA Sound: -sndseqin : selects which sequencer port to use for input, by default no sequencer port is used (MIDI note events from that port get mapped to MIDINOTE keys that can be bound) i = COM_CheckParm ("-sndseqin"); // TODO turn this into a cvar, maybe if (i != 0 && i < com_argc - 1) seq_name = com_argv[i + 1]; if(seq_name) { seq_client = atoi(seq_name); seq_port = 0; if(strchr(seq_name, ':')) seq_port = atoi(strchr(seq_name, ':') + 1); Con_Printf ("SndSys_Init: seq input port has been set to \"%d:%d\". Enabling sequencer input...\n", seq_client, seq_port); err = snd_seq_open (&seq_handle, "default", SND_SEQ_OPEN_INPUT, 0); if (err < 0) { Con_Print ("SndSys_Init: can't open seq device\n"); goto seqdone; } err = snd_seq_set_client_name(seq_handle, gamename); if (err < 0) { Con_Print ("SndSys_Init: can't set name of seq device\n"); goto seqerror; } err = snd_seq_create_simple_port(seq_handle, gamename, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); if(err < 0) { Con_Print ("SndSys_Init: can't create seq port\n"); goto seqerror; } err = snd_seq_connect_from(seq_handle, 0, seq_client, seq_port); if(err < 0) { Con_Printf ("SndSys_Init: can't connect to seq port \"%d:%d\"\n", seq_client, seq_port); goto seqerror; } err = snd_seq_nonblock(seq_handle, 1); if(err < 0) { Con_Print ("SndSys_Init: can't make seq nonblocking\n"); goto seqerror; } goto seqdone; seqerror: snd_seq_close(seq_handle); seq_handle = NULL; } seqdone: // Check the requested sound format if (requested->width < 1 || requested->width > 2) { Con_Printf ("SndSys_Init: invalid sound width (%hu)\n", requested->width); if (suggested != NULL) { memcpy (suggested, requested, sizeof (*suggested)); if (requested->width < 1) suggested->width = 1; else suggested->width = 2; Con_Printf ("SndSys_Init: suggesting sound width = %hu\n", suggested->width); } return false; } if (pcm_handle != NULL) { Con_Print ("SndSys_Init: WARNING: Init called before Shutdown!\n"); SndSys_Shutdown (); } // Determine the name of the PCM handle we'll use switch (requested->channels) { case 4: pcm_name = "surround40"; break; case 6: pcm_name = "surround51"; break; case 8: pcm_name = "surround71"; break; default: pcm_name = "default"; break; } // COMMANDLINEOPTION: Linux ALSA Sound: -sndpcm selects which pcm device to use, default is "default" i = COM_CheckParm ("-sndpcm"); if (i != 0 && i < com_argc - 1) pcm_name = com_argv[i + 1]; // Open the audio device Con_Printf ("SndSys_Init: PCM device is \"%s\"\n", pcm_name); err = snd_pcm_open (&pcm_handle, pcm_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) { Con_Printf ("SndSys_Init: can't open audio device \"%s\" (%s)\n", pcm_name, snd_strerror (err)); return false; } // Allocate the hardware parameters err = snd_pcm_hw_params_malloc (&hw_params); if (err < 0) { Con_Printf ("SndSys_Init: can't allocate hardware parameters (%s)\n", snd_strerror (err)); goto init_error; } err = snd_pcm_hw_params_any (pcm_handle, hw_params); if (err < 0) { Con_Printf ("SndSys_Init: can't initialize hardware parameters (%s)\n", snd_strerror (err)); goto init_error; } // Set the access type err = snd_pcm_hw_params_set_access (pcm_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if (err < 0) { Con_Printf ("SndSys_Init: can't set access type (%s)\n", snd_strerror (err)); goto init_error; } // Set the sound width if (requested->width == 1) snd_pcm_format = SND_PCM_FORMAT_U8; else snd_pcm_format = SND_PCM_FORMAT_S16; err = snd_pcm_hw_params_set_format (pcm_handle, hw_params, snd_pcm_format); if (err < 0) { Con_Printf ("SndSys_Init: can't set sound width to %hu (%s)\n", requested->width, snd_strerror (err)); goto init_error; } // Set the sound channels err = snd_pcm_hw_params_set_channels (pcm_handle, hw_params, requested->channels); if (err < 0) { Con_Printf ("SndSys_Init: can't set sound channels to %hu (%s)\n", requested->channels, snd_strerror (err)); goto init_error; } // Set the sound speed err = snd_pcm_hw_params_set_rate (pcm_handle, hw_params, requested->speed, 0); if (err < 0) { Con_Printf ("SndSys_Init: can't set sound speed to %u (%s)\n", requested->speed, snd_strerror (err)); goto init_error; } // pick a buffer size that is a power of 2 (by masking off low bits) buffer_size = i = (int)(requested->speed * 0.15f); while (buffer_size & (buffer_size-1)) buffer_size &= (buffer_size-1); // then check if it is the nearest power of 2 and bump it up if not if (i - buffer_size >= buffer_size >> 1) buffer_size *= 2; err = snd_pcm_hw_params_set_buffer_size_near (pcm_handle, hw_params, &buffer_size); if (err < 0) { Con_Printf ("SndSys_Init: can't set sound buffer size to %lu (%s)\n", buffer_size, snd_strerror (err)); goto init_error; } // pick a period size near the buffer_size we got from ALSA snd_pcm_hw_params_get_buffer_size (hw_params, &buffer_size); buffer_size /= NB_PERIODS; err = snd_pcm_hw_params_set_period_size_near(pcm_handle, hw_params, &buffer_size, 0); if (err < 0) { Con_Printf ("SndSys_Init: can't set sound period size to %lu (%s)\n", buffer_size, snd_strerror (err)); goto init_error; } err = snd_pcm_hw_params (pcm_handle, hw_params); if (err < 0) { Con_Printf ("SndSys_Init: can't set hardware parameters (%s)\n", snd_strerror (err)); goto init_error; } snd_pcm_hw_params_free (hw_params); snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); expected_delay = 0; alsasoundtime = 0; if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA); return true; // It's not very clean, but it avoids a lot of duplicated code. init_error: if (hw_params != NULL) snd_pcm_hw_params_free (hw_params); snd_pcm_close(pcm_handle); pcm_handle = NULL; return false; } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown (void) { if (seq_handle != NULL) { snd_seq_close(seq_handle); seq_handle = NULL; } if (pcm_handle != NULL) { snd_pcm_close(pcm_handle); pcm_handle = NULL; } if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } } /* ==================== SndSys_Recover Try to recover from errors ==================== */ static qboolean SndSys_Recover (int err_num) { int err; // We can only do something on underrun ("broken pipe") errors if (err_num != -EPIPE) return false; err = snd_pcm_prepare (pcm_handle); if (err < 0) { Con_Printf ("SndSys_Recover: unable to recover (%s)\n", snd_strerror (err)); // TOCHECK: should we stop the playback ? return false; } return true; } /* ==================== SndSys_Write ==================== */ static snd_pcm_sframes_t SndSys_Write (const unsigned char* buffer, unsigned int nbframes) { snd_pcm_sframes_t written; written = snd_pcm_writei (pcm_handle, buffer, nbframes); if (written < 0) { if (developer_insane.integer && vid_activewindow) Con_DPrintf ("SndSys_Write: audio write returned %ld (%s)!\n", written, snd_strerror (written)); if (SndSys_Recover (written)) { written = snd_pcm_writei (pcm_handle, buffer, nbframes); if (written < 0) Con_DPrintf ("SndSys_Write: audio write failed again (error %ld: %s)!\n", written, snd_strerror (written)); } } if (written > 0) { snd_renderbuffer->startframe += written; expected_delay += written; } return written; } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { unsigned int startoffset, factor; snd_pcm_uframes_t limit, nbframes; snd_pcm_sframes_t written; if (pcm_handle == NULL || snd_renderbuffer->startframe == snd_renderbuffer->endframe) return; startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; limit = snd_renderbuffer->maxframes - startoffset; nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; if (nbframes > limit) { written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit); if (written < 0 || (snd_pcm_uframes_t)written != limit) return; nbframes -= limit; startoffset = 0; } written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes); if (written < 0) return; } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { snd_pcm_sframes_t delay, timediff; int err; if (pcm_handle == NULL) return 0; err = snd_pcm_delay (pcm_handle, &delay); if (err < 0) { if (developer_insane.integer && vid_activewindow) Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay (%s)\n", snd_strerror (err)); if (! SndSys_Recover (err)) return 0; err = snd_pcm_delay (pcm_handle, &delay); if (err < 0) { Con_DPrintf ("SndSys_GetSoundTime: can't get playback delay, again (%s)\n", snd_strerror (err)); return 0; } } if (expected_delay < delay) { Con_DPrintf ("SndSys_GetSoundTime: expected_delay(%ld) < delay(%ld)\n", expected_delay, delay); timediff = 0; } else timediff = expected_delay - delay; expected_delay = delay; alsasoundtime += (unsigned int)timediff; return alsasoundtime; } /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { // Nothing to do return true; } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { // Nothing to do } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { snd_seq_event_t *event; if(!seq_handle) return; for(;;) { if(snd_seq_event_input(seq_handle, &event) <= 0) break; if(event) { switch(event->type) { case SND_SEQ_EVENT_NOTEON: if(event->data.note.velocity) { Key_Event(K_MIDINOTE0 + event->data.note.note, 0, true); break; } case SND_SEQ_EVENT_NOTEOFF: Key_Event(K_MIDINOTE0 + event->data.note.note, 0, false); break; } } } } darkplaces/darkplaces-vs2015.sln0000664000175000017500000000554113067716220015755 0ustar kalevkalev Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl-vs2015", "darkplaces-wgl-vs2015.vcxproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated-vs2015", "darkplaces-dedicated-vs2015.vcxproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl2-vs2015", "darkplaces-sdl2-vs2015.vcxproj", "{72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|Win32.ActiveCfg = Debug|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|Win32.Build.0 = Debug|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|x64.ActiveCfg = Debug|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|x64.Build.0 = Debug|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|Win32.ActiveCfg = Release|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|Win32.Build.0 = Release|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|x64.ActiveCfg = Release|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal darkplaces/makefile0000664000175000017500000002342113067716220013665 0ustar kalevkalev##### DP_MAKE_TARGET autodetection and arch specific variables ##### ifndef DP_MAKE_TARGET # Win32 ifdef WINDIR DP_MAKE_TARGET=mingw else # UNIXes DP_ARCH:=$(shell uname) ifneq ($(filter %BSD,$(DP_ARCH)),) DP_MAKE_TARGET=bsd else ifeq ($(DP_ARCH), Darwin) DP_MAKE_TARGET=macosx else ifeq ($(DP_ARCH), SunOS) DP_MAKE_TARGET=sunos else DP_MAKE_TARGET=linux endif # ifeq ($(DP_ARCH), SunOS) endif # ifeq ($(DP_ARCH), Darwin) endif # ifneq ($(filter %BSD,$(DP_ARCH)),) endif # ifdef windir endif # ifndef DP_MAKE_TARGET # If we're targeting an x86 CPU we want to enable DP_SSE (CFLAGS_SSE and SSE2) ifeq ($(DP_MAKE_TARGET), mingw) DP_SSE:=1 else DP_MACHINE:=$(shell uname -m) ifeq ($(DP_MACHINE),x86_64) DP_SSE:=1 else ifeq ($(DP_MACHINE),i686) DP_SSE:=1 else ifeq ($(DP_MACHINE),i386) DP_SSE:=1 else DP_SSE:=0 endif # ifeq ($(DP_MACHINE),i386) endif # ifeq ($(DP_MACHINE),i686) endif # ifeq ($(DP_MACHINE),x86_64) endif # Makefile name MAKEFILE=makefile # Commands ifdef windir CMD_RM=del CMD_CP=copy /y CMD_MKDIR=mkdir else CMD_RM=$(CMD_UNIXRM) CMD_CP=$(CMD_UNIXCP) CMD_MKDIR=$(CMD_UNIXMKDIR) endif # 64bits AMD CPUs use another lib directory ifeq ($(DP_MACHINE),x86_64) UNIX_X11LIBPATH:=/usr/X11R6/lib64 else UNIX_X11LIBPATH:=/usr/X11R6/lib endif # default targets TARGETS_DEBUG=sv-debug cl-debug sdl-debug TARGETS_PROFILE=sv-profile cl-profile sdl-profile TARGETS_RELEASE=sv-release cl-release sdl-release TARGETS_RELEASE_PROFILE=sv-release-profile cl-release-profile sdl-release-profile TARGETS_NEXUIZ=sv-nexuiz cl-nexuiz sdl-nexuiz ###### Optional features ##### DP_CDDA?=enabled ifeq ($(DP_CDDA), enabled) OBJ_SDLCD=$(OBJ_CD_COMMON) cd_sdl.o OBJ_LINUXCD=$(OBJ_CD_COMMON) cd_linux.o OBJ_BSDCD=$(OBJ_CD_COMMON) cd_bsd.o OBJ_WINCD=$(OBJ_CD_COMMON) cd_win.o else OBJ_SDLCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) OBJ_LINUXCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) OBJ_BSDCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) OBJ_WINCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) endif DP_VIDEO_CAPTURE?=enabled ifeq ($(DP_VIDEO_CAPTURE), enabled) CFLAGS_VIDEO_CAPTURE=-DCONFIG_VIDEO_CAPTURE OBJ_VIDEO_CAPTURE= cap_avi.o cap_ogg.o else CFLAGS_VIDEO_CAPTURE= OBJ_VIDEO_CAPTURE= endif # Linux configuration ifeq ($(DP_MAKE_TARGET), linux) DEFAULT_SNDAPI=ALSA OBJ_CD=$(OBJ_LINUXCD) OBJ_CL=$(OBJ_GLX) OBJ_ICON= OBJ_ICON_NEXUIZ= LDFLAGS_CL=$(LDFLAGS_LINUXCL) LDFLAGS_SV=$(LDFLAGS_LINUXSV) LDFLAGS_SDL=$(LDFLAGS_LINUXSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) EXE_CL=$(EXE_UNIXCL) EXE_SV=$(EXE_UNIXSV) EXE_SDL=$(EXE_UNIXSDL) EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) DP_LINK_ZLIB?=shared DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen endif # Mac OS X configuration ifeq ($(DP_MAKE_TARGET), macosx) DEFAULT_SNDAPI=COREAUDIO OBJ_CD=$(OBJ_MACOSXCD) OBJ_CL=$(OBJ_AGL) OBJ_ICON= OBJ_ICON_NEXUIZ= LDFLAGS_CL=$(LDFLAGS_MACOSXCL) LDFLAGS_SV=$(LDFLAGS_MACOSXSV) LDFLAGS_SDL=$(LDFLAGS_MACOSXSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_MACOSXCFLAGS) SDLCONFIG_LIBS=$(SDLCONFIG_MACOSXLIBS) SDLCONFIG_STATICLIBS=$(SDLCONFIG_MACOSXSTATICLIBS) EXE_CL=$(EXE_MACOSXCL) EXE_SV=$(EXE_UNIXSV) EXE_SDL=$(EXE_UNIXSDL) EXE_CLNEXUIZ=$(EXE_MACOSXCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) ifeq ($(word 2, $(filter -arch, $(CC))), -arch) CFLAGS_MAKEDEP= endif DP_LINK_ZLIB?=shared DP_LINK_JPEG?=dlopen DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen # on OS X, we don't build the CL by default because it uses deprecated # and not-implemented-in-64bit Carbon TARGETS_DEBUG=sv-debug sdl-debug TARGETS_PROFILE=sv-profile sdl-profile TARGETS_RELEASE=sv-release sdl-release TARGETS_RELEASE_PROFILE=sv-release-profile sdl-release-profile TARGETS_NEXUIZ=sv-nexuiz sdl-nexuiz endif # SunOS configuration (Solaris) ifeq ($(DP_MAKE_TARGET), sunos) DEFAULT_SNDAPI=BSD OBJ_CD=$(OBJ_SUNOSCD) OBJ_CL=$(OBJ_GLX) OBJ_ICON= OBJ_ICON_NEXUIZ= CFLAGS_EXTRA=$(CFLAGS_SUNOS) LDFLAGS_CL=$(LDFLAGS_SUNOSCL) LDFLAGS_SV=$(LDFLAGS_SUNOSSV) LDFLAGS_SDL=$(LDFLAGS_SUNOSSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) EXE_CL=$(EXE_UNIXCL) EXE_SV=$(EXE_UNIXSV) EXE_SDL=$(EXE_UNIXSDL) EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) DP_LINK_ZLIB?=shared DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen endif # BSD configuration ifeq ($(DP_MAKE_TARGET), bsd) ifeq ($(DP_ARCH),FreeBSD) DEFAULT_SNDAPI=OSS else DEFAULT_SNDAPI=BSD endif OBJ_CD=$(OBJ_BSDCD) OBJ_CL=$(OBJ_GLX) OBJ_ICON= OBJ_ICON_NEXUIZ= LDFLAGS_CL=$(LDFLAGS_BSDCL) LDFLAGS_SV=$(LDFLAGS_BSDSV) LDFLAGS_SDL=$(LDFLAGS_BSDSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) EXE_CL=$(EXE_UNIXCL) EXE_SV=$(EXE_UNIXSV) EXE_SDL=$(EXE_UNIXSDL) EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) DP_LINK_ZLIB?=shared DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen endif # Win32 configuration ifeq ($(WIN32RELEASE), 1) # TARGET=i686-pc-mingw32 # CC=$(TARGET)-g++ # WINDRES=$(TARGET)-windres CPUOPTIMIZATIONS=-march=pentium3 -mfpmath=sse -fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fno-trapping-math # CPUOPTIMIZATIONS+=-DUSE_WSPIAPI_H -DSUPPORTIPV6 LDFLAGS_WINCOMMON=-Wl,--large-address-aware else LDFLAGS_WINCOMMON= endif ifeq ($(WIN64RELEASE), 1) # TARGET=x86_64-pc-mingw32 # CC=$(TARGET)-g++ # WINDRES=$(TARGET)-windres endif ifeq ($(D3D), 1) CFLAGS_D3D=-DSUPPORTD3D -DSUPPORTDIRECTX CFLAGS_WARNINGS=-Wall LDFLAGS_D3D=-ld3d9 else CFLAGS_D3D= CFLAGS_WARNINGS=-Wall -Wold-style-definition -Wstrict-prototypes -Wsign-compare -Wdeclaration-after-statement -Wmissing-prototypes LDFLAGS_D3D= endif ifeq ($(DP_MAKE_TARGET), mingw) DEFAULT_SNDAPI=WIN OBJ_CD=$(OBJ_WINCD) OBJ_CL=$(OBJ_WGL) OBJ_ICON=darkplaces.o OBJ_ICON_NEXUIZ=nexuiz.o LDFLAGS_CL=$(LDFLAGS_WINCL) LDFLAGS_SV=$(LDFLAGS_WINSV) LDFLAGS_SDL=$(LDFLAGS_WINSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) EXE_CL=$(EXE_WINCL) EXE_SV=$(EXE_WINSV) EXE_SDL=$(EXE_WINSDL) EXE_CLNEXUIZ=$(EXE_WINCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_WINSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_WINSDLNEXUIZ) DP_LINK_ZLIB?=dlopen DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen endif # set these to "" if you want to use dynamic loading instead # zlib ifeq ($(DP_LINK_ZLIB), shared) CFLAGS_LIBZ=-DLINK_TO_ZLIB LIB_Z=-lz endif ifeq ($(DP_LINK_ZLIB), dlopen) CFLAGS_LIBZ= LIB_Z= endif # jpeg ifeq ($(DP_LINK_JPEG), shared) CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG LIB_JPEG=-ljpeg endif ifeq ($(DP_LINK_JPEG), dlopen) CFLAGS_LIBJPEG= LIB_JPEG= endif # ode ifeq ($(DP_LINK_ODE), shared) ODE_CONFIG?=ode-config LIB_ODE=`$(ODE_CONFIG) --libs` CFLAGS_ODE=`$(ODE_CONFIG) --cflags` -DUSEODE -DLINK_TO_LIBODE endif ifeq ($(DP_LINK_ODE), dlopen) LIB_ODE= CFLAGS_ODE=-DUSEODE endif # d0_blind_id ifeq ($(DP_LINK_CRYPTO), shared) LIB_CRYPTO=-ld0_blind_id CFLAGS_CRYPTO=-DLINK_TO_CRYPTO endif ifeq ($(DP_LINK_CRYPTO), dlopen) LIB_CRYPTO= CFLAGS_CRYPTO= endif ifeq ($(DP_LINK_CRYPTO_RIJNDAEL), shared) LIB_CRYPTO_RIJNDAEL=-ld0_rijndael CFLAGS_CRYPTO_RIJNDAEL=-DLINK_TO_CRYPTO_RIJNDAEL endif ifeq ($(DP_LINK_CRYPTO_RIJNDAEL), dlopen) LIB_CRYPTO_RIJNDAEL= CFLAGS_CRYPTO_RIJNDAEL= endif ##### Sound configuration ##### ifndef DP_SOUND_API DP_SOUND_API=$(DEFAULT_SNDAPI) endif # NULL: no sound ifeq ($(DP_SOUND_API), NULL) OBJ_SOUND=$(OBJ_SND_NULL) LIB_SOUND=$(LIB_SND_NULL) endif # OSS: Open Sound System ifeq ($(DP_SOUND_API), OSS) OBJ_SOUND=$(OBJ_SND_OSS) LIB_SOUND=$(LIB_SND_OSS) endif # ALSA: Advanced Linux Sound Architecture ifeq ($(DP_SOUND_API), ALSA) OBJ_SOUND=$(OBJ_SND_ALSA) LIB_SOUND=$(LIB_SND_ALSA) endif # COREAUDIO: Core Audio ifeq ($(DP_SOUND_API), COREAUDIO) OBJ_SOUND=$(OBJ_SND_COREAUDIO) LIB_SOUND=$(LIB_SND_COREAUDIO) endif # BSD: BSD / Sun audio API ifeq ($(DP_SOUND_API), BSD) OBJ_SOUND=$(OBJ_SND_BSD) LIB_SOUND=$(LIB_SND_BSD) endif # WIN: DirectX and Win32 WAVE output ifeq ($(DP_SOUND_API), WIN) OBJ_SOUND=$(OBJ_SND_WIN) LIB_SOUND=$(LIB_SND_WIN) endif ifeq ($(DP_SOUND_API),3DRAS) OBJ_SOUND=$(OBJ_SND_3DRAS) LIB_SOUND=$(LIB_SND_3DRAS) endif ##### Extra CFLAGS ##### CFLAGS_MAKEDEP?=-MMD ifdef DP_FS_BASEDIR CFLAGS_FS=-DDP_FS_BASEDIR=\"$(DP_FS_BASEDIR)\" else CFLAGS_FS= endif CFLAGS_PRELOAD= ifneq ($(DP_MAKE_TARGET), mingw) ifdef DP_PRELOAD_DEPENDENCIES # DP_PRELOAD_DEPENDENCIES: when set, link against the libraries needed using -l # dynamically so they won't get loaded at runtime using dlopen LDFLAGS_CL+=$(LDFLAGS_UNIXCL_PRELOAD) LDFLAGS_SV+=$(LDFLAGS_UNIXSV_PRELOAD) LDFLAGS_SDL+=$(LDFLAGS_UNIXSDL_PRELOAD) CFLAGS_PRELOAD=$(CFLAGS_UNIX_PRELOAD) endif endif CFLAGS_NET= # Systems without IPv6 support should uncomment this: #CFLAGS_NET+=-DNOSUPPORTIPV6 ##### GNU Make specific definitions ##### DO_LD=$(CC) -o ../../../$@ $^ $(LDFLAGS) ##### Definitions shared by all makefiles ##### include makefile.inc ##### Dependency files ##### -include *.d # hack to deal with no-longer-needed .h files %.h: @echo @echo "NOTE: file $@ mentioned in dependencies missing, continuing..." @echo "HINT: consider 'make clean'" @echo darkplaces/cl_video.h0000664000175000017500000000527713067716216014140 0ustar kalevkalev #ifndef CL_VIDEO_H #define CL_VIDEO_H #include "cl_dyntexture.h" // yields DYNAMIC_TEXTURE_PATH_PREFIX CLVIDEOPREFIX video name for a path #define CLVIDEOPREFIX CLDYNTEXTUREPREFIX "video/" #define CLTHRESHOLD 2.0 #define MENUOWNER 1 typedef enum clvideostate_e { CLVIDEO_UNUSED, CLVIDEO_PLAY, CLVIDEO_LOOP, CLVIDEO_PAUSE, CLVIDEO_FIRSTFRAME, CLVIDEO_RESETONWAKEUP, CLVIDEO_STATECOUNT } clvideostate_t; #define CLVIDEO_MAX_SUBTITLES 512 extern cvar_t cl_video_subtitles; extern cvar_t cl_video_subtitles_lines; extern cvar_t cl_video_subtitles_textsize; extern cvar_t cl_video_scale; extern cvar_t cl_video_scale_vpos; extern cvar_t cl_video_stipple; extern cvar_t cl_video_brightness; extern cvar_t cl_video_keepaspectratio; typedef struct clvideo_s { int ownertag; clvideostate_t state; // private stuff void *stream; double starttime; int framenum; double framerate; void *imagedata; cachepic_t cpif; // VorteX: subtitles array int subtitles; char *subtitle_text[CLVIDEO_MAX_SUBTITLES]; float subtitle_start[CLVIDEO_MAX_SUBTITLES]; float subtitle_end[CLVIDEO_MAX_SUBTITLES]; // this functions gets filled by video format module void (*close) (void *stream); unsigned int (*getwidth) (void *stream); unsigned int (*getheight) (void *stream); double (*getframerate) (void *stream); double (*getaspectratio) (void *stream); int (*decodeframe) (void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); // if a video is suspended, it is automatically paused (else we'd still have to process the frames) // used to determine whether the video's resources should be freed or not double lasttime; // when lasttime - realtime > THRESHOLD, all but the stream is freed qboolean suspended; char filename[MAX_QPATH]; } clvideo_t; clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ); clvideo_t* CL_GetVideoByName( const char *name ); void CL_SetVideoState( clvideo_t *video, clvideostate_t state ); void CL_RestartVideo( clvideo_t *video ); void CL_CloseVideo( clvideo_t * video ); void CL_PurgeOwner( int owner ); void CL_Video_Frame( void ); // update all videos void CL_Video_Init( void ); void CL_Video_Shutdown( void ); // old interface extern int cl_videoplaying; void CL_DrawVideo( void ); void CL_VideoStart( char *filename, const char *subtitlesfile ); void CL_VideoStop( void ); // new function used for fullscreen videos // TODO: Andreas Kirsch: move this subsystem somewhere else (preferably host) since the cl_video system shouldnt do such work like managing key events.. void CL_Video_KeyEvent( int key, int ascii, qboolean down ); #endif darkplaces/darkplaces.sln0000775000175000017500000000534513067716220015024 0ustar kalevkalev Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl", "darkplaces-wgl.vcproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl", "darkplaces-sdl.vcproj", "{7470C8B3-FCA7-42D3-9909-5F9E735C7C51}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated", "darkplaces-dedicated.vcproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.ActiveCfg = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.Build.0 = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.ActiveCfg = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.Build.0 = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.ActiveCfg = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.Build.0 = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.ActiveCfg = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.Build.0 = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal darkplaces/darkplaces-sdl2-vs2010.vcxproj0000664000175000017500000003217113067716220017510 0ustar kalevkalev Debug Win32 Release Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51} darkplacessdl2 Win32Proj Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(ProjectName) $(ProjectName) Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 darkplaces/hmac.h0000664000175000017500000000067513067716220013254 0ustar kalevkalev#ifndef HMAC_H #define HMAC_H typedef void (*hashfunc_t) (unsigned char *out, const unsigned char *in, int n); qboolean hmac( hashfunc_t hfunc, int hlen, int hblock, unsigned char *out, const unsigned char *in, int n, const unsigned char *key, int k ); #define HMAC_MDFOUR_16BYTES(out, in, n, key, k) hmac(mdfour, 16, 64, out, in, n, key, k) #define HMAC_SHA256_32BYTES(out, in, n, key, k) hmac(sha256, 32, 64, out, in, n, key, k) #endif darkplaces/darkplaces-dedicated.vcproj0000775000175000017500000004536213067716220017442 0ustar kalevkalev darkplaces/clprogdefs.h0000664000175000017500000000352613067716216014477 0ustar kalevkalev/* file generated by qcc, do not modify */ #ifndef CLPROGDEFS_H #define CLPROGDEFS_H /* typedef struct cl_globalvars_s { int pad[28]; int self; int other; int world; float time; float frametime; float player_localentnum; float player_localnum; float maxclients; float clientcommandframe; float servercommandframe; string_t mapname; vec3_t v_forward; vec3_t v_up; vec3_t v_right; float trace_allsolid; float trace_startsolid; float trace_fraction; vec3_t trace_endpos; vec3_t trace_plane_normal; float trace_plane_dist; int trace_ent; float trace_inopen; float trace_inwater; func_t CSQC_Init; func_t CSQC_Shutdown; func_t CSQC_InputEvent; func_t CSQC_UpdateView; func_t CSQC_ConsoleCommand; vec3_t pmove_org; vec3_t pmove_vel; vec3_t pmove_mins; vec3_t pmove_maxs; float input_timelength; vec3_t input_angles; vec3_t input_movevalues; float input_buttons; float movevar_gravity; float movevar_stopspeed; float movevar_maxspeed; float movevar_spectatormaxspeed; float movevar_accelerate; float movevar_airaccelerate; float movevar_wateraccelerate; float movevar_friction; float movevar_waterfriction; float movevar_entgravity; } cl_globalvars_t; typedef struct cl_entvars_s { float modelindex; vec3_t absmin; vec3_t absmax; float entnum; float drawmask; func_t predraw; float movetype; float solid; vec3_t origin; vec3_t oldorigin; vec3_t velocity; vec3_t angles; vec3_t avelocity; string_t classname; string_t model; float frame; float skin; float effects; vec3_t mins; vec3_t maxs; vec3_t size; func_t touch; func_t use; func_t think; func_t blocked; float nextthink; int chain; string_t netname; int enemy; float flags; float colormap; int owner; } cl_entvars_t; #define CL_PROGHEADER_CRC 52195 */ #endif darkplaces/cl_dyntexture.c0000664000175000017500000000526313067716216015233 0ustar kalevkalev// Andreas Kirsch 07 #include "quakedef.h" #include "cl_dyntexture.h" typedef struct dyntexture_s { // everything after DYNAMIC_TEXTURE_PATH_PREFIX char name[ MAX_QPATH + 32 ]; // texture pointer (points to r_texture_white at first) rtexture_t *texture; } dyntexture_t; static dyntexture_t dyntextures[ MAX_DYNAMIC_TEXTURE_COUNT ]; static unsigned dyntexturecount; #define DEFAULT_DYNTEXTURE r_texture_grey128 static dyntexture_t * cl_finddyntexture( const char *name, qboolean warnonfailure ) { unsigned i; dyntexture_t *dyntexture = NULL; // sanity checks - make sure its actually a dynamic texture path if( !name || !*name || strncmp( name, CLDYNTEXTUREPREFIX, sizeof( CLDYNTEXTUREPREFIX ) - 1 ) != 0 ) { // TODO: print a warning or something if (warnonfailure) Con_Printf( "cl_finddyntexture: Bad dynamic texture name '%s'\n", name ); return NULL; } for( i = 0 ; i < dyntexturecount ; i++ ) { dyntexture = &dyntextures[ i ]; if( dyntexture->name && strcmp( dyntexture->name, name ) == 0 ) { return dyntexture; } } if( dyntexturecount == MAX_DYNAMIC_TEXTURE_COUNT ) { // TODO: warn or expand the array, etc. return NULL; } dyntexture = &dyntextures[ dyntexturecount++ ]; strlcpy( dyntexture->name, name, sizeof( dyntexture->name ) ); dyntexture->texture = DEFAULT_DYNTEXTURE; return dyntexture; } rtexture_t * CL_GetDynTexture( const char *name ) { dyntexture_t *dyntexture = cl_finddyntexture( name, false ); if( dyntexture ) { return dyntexture->texture; } else { return NULL; } } void CL_LinkDynTexture( const char *name, rtexture_t *texture ) { dyntexture_t *dyntexture; cachepic_t *cachepic; skinframe_t *skinframe; dyntexture = cl_finddyntexture( name, true ); if( !dyntexture ) { Con_Printf( "CL_LinkDynTexture: internal error in cl_finddyntexture!\n" ); return; } // TODO: assert dyntexture != NULL! if( dyntexture->texture != texture ) { dyntexture->texture = texture; cachepic = Draw_CachePic_Flags( name, CACHEPICFLAG_NOTPERSISTENT ); // TODO: assert cachepic and skinframe should be valid pointers... // TODO: assert cachepic->tex = dyntexture->texture cachepic->tex = texture; // update cachepic's size, too cachepic->width = R_TextureWidth( texture ); cachepic->height = R_TextureHeight( texture ); // update skinframes skinframe = NULL; while( (skinframe = R_SkinFrame_FindNextByName( skinframe, name )) != NULL ) { skinframe->base = texture; // simply reset the compare* attributes of skinframe skinframe->comparecrc = 0; skinframe->comparewidth = skinframe->compareheight = 0; // this is kind of hacky } } } void CL_UnlinkDynTexture( const char *name ) { CL_LinkDynTexture( name, DEFAULT_DYNTEXTURE ); } darkplaces/mingw_note.txt0000664000175000017500000000155713067716220015102 0ustar kalevkalevFor compiling Darkplaces with MinGW, you need the following files which do not come in the standard MinGW installation: - include/ddraw.h - include/dinput.h - include/dsound.h They are part of the DirectX SDK but can also be found in the original release of Quake 1 source code (ftp://ftp.idsoftware.com/idstuff/source/q1source.zip). Assuming the MinGW binaries are in your PATH, you compile Darkplaces by typing "make release". Note that "make" may be named "mingw32-make", so you may want to try "mingw32-make release" if the first command fails to run. For cross-compiling Win32 binaries on Linux using MinGW, you need to force the makefile to use the MinGW compilation parameters, otherwise it will autodetect the operating system it runs on and will use the corresponding parameters. You can force it by appending "DP_MAKE_TARGET=mingw" at the end of the command line. darkplaces/client.h0000664000175000017500000017157613067716216013640 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // client.h #ifndef CLIENT_H #define CLIENT_H #include "matrixlib.h" #include "snd_main.h" // NOTE: r_stat_name[] must match this indexing typedef enum r_stat_e { r_stat_timedelta, r_stat_quality, r_stat_renders, r_stat_entities, r_stat_entities_surfaces, r_stat_entities_triangles, r_stat_world_leafs, r_stat_world_portals, r_stat_world_surfaces, r_stat_world_triangles, r_stat_lightmapupdates, r_stat_lightmapupdatepixels, r_stat_particles, r_stat_drawndecals, r_stat_totaldecals, r_stat_draws, r_stat_draws_vertices, r_stat_draws_elements, r_stat_lights, r_stat_lights_clears, r_stat_lights_scissored, r_stat_lights_lighttriangles, r_stat_lights_shadowtriangles, r_stat_lights_dynamicshadowtriangles, r_stat_bouncegrid_lights, r_stat_bouncegrid_particles, r_stat_bouncegrid_traces, r_stat_bouncegrid_hits, r_stat_bouncegrid_splats, r_stat_bouncegrid_bounces, r_stat_photoncache_animated, r_stat_photoncache_cached, r_stat_photoncache_traced, r_stat_bloom, r_stat_bloom_copypixels, r_stat_bloom_drawpixels, r_stat_indexbufferuploadcount, r_stat_indexbufferuploadsize, r_stat_vertexbufferuploadcount, r_stat_vertexbufferuploadsize, r_stat_framedatacurrent, r_stat_framedatasize, r_stat_bufferdatacurrent_vertex, // R_BUFFERDATA_ types are added to this index r_stat_bufferdatacurrent_index16, r_stat_bufferdatacurrent_index32, r_stat_bufferdatacurrent_uniform, r_stat_bufferdatasize_vertex, // R_BUFFERDATA_ types are added to this index r_stat_bufferdatasize_index16, r_stat_bufferdatasize_index32, r_stat_bufferdatasize_uniform, r_stat_animcache_vertexmesh_count, r_stat_animcache_vertexmesh_vertices, r_stat_animcache_vertexmesh_maxvertices, r_stat_animcache_skeletal_count, r_stat_animcache_skeletal_bones, r_stat_animcache_skeletal_maxbones, r_stat_animcache_shade_count, r_stat_animcache_shade_vertices, r_stat_animcache_shade_maxvertices, r_stat_animcache_shape_count, r_stat_animcache_shape_vertices, r_stat_animcache_shape_maxvertices, r_stat_batch_batches, r_stat_batch_withgaps, r_stat_batch_surfaces, r_stat_batch_vertices, r_stat_batch_triangles, r_stat_batch_fast_batches, r_stat_batch_fast_surfaces, r_stat_batch_fast_vertices, r_stat_batch_fast_triangles, r_stat_batch_copytriangles_batches, r_stat_batch_copytriangles_surfaces, r_stat_batch_copytriangles_vertices, r_stat_batch_copytriangles_triangles, r_stat_batch_dynamic_batches, r_stat_batch_dynamic_surfaces, r_stat_batch_dynamic_vertices, r_stat_batch_dynamic_triangles, r_stat_batch_dynamicskeletal_batches, r_stat_batch_dynamicskeletal_surfaces, r_stat_batch_dynamicskeletal_vertices, r_stat_batch_dynamicskeletal_triangles, r_stat_batch_dynamic_batches_because_cvar, r_stat_batch_dynamic_surfaces_because_cvar, r_stat_batch_dynamic_vertices_because_cvar, r_stat_batch_dynamic_triangles_because_cvar, r_stat_batch_dynamic_batches_because_lightmapvertex, r_stat_batch_dynamic_surfaces_because_lightmapvertex, r_stat_batch_dynamic_vertices_because_lightmapvertex, r_stat_batch_dynamic_triangles_because_lightmapvertex, r_stat_batch_dynamic_batches_because_deformvertexes_autosprite, r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite, r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite, r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite, r_stat_batch_dynamic_batches_because_deformvertexes_autosprite2, r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite2, r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite2, r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite2, r_stat_batch_dynamic_batches_because_deformvertexes_normal, r_stat_batch_dynamic_surfaces_because_deformvertexes_normal, r_stat_batch_dynamic_vertices_because_deformvertexes_normal, r_stat_batch_dynamic_triangles_because_deformvertexes_normal, r_stat_batch_dynamic_batches_because_deformvertexes_wave, r_stat_batch_dynamic_surfaces_because_deformvertexes_wave, r_stat_batch_dynamic_vertices_because_deformvertexes_wave, r_stat_batch_dynamic_triangles_because_deformvertexes_wave, r_stat_batch_dynamic_batches_because_deformvertexes_bulge, r_stat_batch_dynamic_surfaces_because_deformvertexes_bulge, r_stat_batch_dynamic_vertices_because_deformvertexes_bulge, r_stat_batch_dynamic_triangles_because_deformvertexes_bulge, r_stat_batch_dynamic_batches_because_deformvertexes_move, r_stat_batch_dynamic_surfaces_because_deformvertexes_move, r_stat_batch_dynamic_vertices_because_deformvertexes_move, r_stat_batch_dynamic_triangles_because_deformvertexes_move, r_stat_batch_dynamic_batches_because_tcgen_lightmap, r_stat_batch_dynamic_surfaces_because_tcgen_lightmap, r_stat_batch_dynamic_vertices_because_tcgen_lightmap, r_stat_batch_dynamic_triangles_because_tcgen_lightmap, r_stat_batch_dynamic_batches_because_tcgen_vector, r_stat_batch_dynamic_surfaces_because_tcgen_vector, r_stat_batch_dynamic_vertices_because_tcgen_vector, r_stat_batch_dynamic_triangles_because_tcgen_vector, r_stat_batch_dynamic_batches_because_tcgen_environment, r_stat_batch_dynamic_surfaces_because_tcgen_environment, r_stat_batch_dynamic_vertices_because_tcgen_environment, r_stat_batch_dynamic_triangles_because_tcgen_environment, r_stat_batch_dynamic_batches_because_tcmod_turbulent, r_stat_batch_dynamic_surfaces_because_tcmod_turbulent, r_stat_batch_dynamic_vertices_because_tcmod_turbulent, r_stat_batch_dynamic_triangles_because_tcmod_turbulent, r_stat_batch_dynamic_batches_because_interleavedarrays, r_stat_batch_dynamic_surfaces_because_interleavedarrays, r_stat_batch_dynamic_vertices_because_interleavedarrays, r_stat_batch_dynamic_triangles_because_interleavedarrays, r_stat_batch_dynamic_batches_because_nogaps, r_stat_batch_dynamic_surfaces_because_nogaps, r_stat_batch_dynamic_vertices_because_nogaps, r_stat_batch_dynamic_triangles_because_nogaps, r_stat_batch_dynamic_batches_because_derived, r_stat_batch_dynamic_surfaces_because_derived, r_stat_batch_dynamic_vertices_because_derived, r_stat_batch_dynamic_triangles_because_derived, r_stat_batch_entitycache_count, r_stat_batch_entitycache_surfaces, r_stat_batch_entitycache_vertices, r_stat_batch_entitycache_triangles, r_stat_batch_entityanimate_count, r_stat_batch_entityanimate_surfaces, r_stat_batch_entityanimate_vertices, r_stat_batch_entityanimate_triangles, r_stat_batch_entityskeletal_count, r_stat_batch_entityskeletal_surfaces, r_stat_batch_entityskeletal_vertices, r_stat_batch_entityskeletal_triangles, r_stat_batch_entitystatic_count, r_stat_batch_entitystatic_surfaces, r_stat_batch_entitystatic_vertices, r_stat_batch_entitystatic_triangles, r_stat_batch_entitycustom_count, r_stat_batch_entitycustom_surfaces, r_stat_batch_entitycustom_vertices, r_stat_batch_entitycustom_triangles, r_stat_count // size of array } r_stat_t; // flags for rtlight rendering #define LIGHTFLAG_NORMALMODE 1 #define LIGHTFLAG_REALTIMEMODE 2 typedef struct tridecal_s { // color and initial alpha value float texcoord2f[3][2]; float vertex3f[3][3]; float color4f[3][4]; float plane[4]; // backface culling // how long this decal has lived so far (the actual fade begins at cl_decals_time) float lived; // if >= 0 this indicates the decal should follow an animated triangle int triangleindex; // for visibility culling int surfaceindex; // old decals are killed to obey cl_decals_max unsigned int decalsequence; } tridecal_t; typedef struct decalsystem_s { dp_model_t *model; double lastupdatetime; int maxdecals; int freedecal; int numdecals; tridecal_t *decals; float *vertex3f; float *texcoord2f; float *color4f; int *element3i; unsigned short *element3s; } decalsystem_t; typedef struct effect_s { int active; vec3_t origin; double starttime; float framerate; int modelindex; int startframe; int endframe; // these are for interpolation int frame; double frame1time; double frame2time; } cl_effect_t; typedef struct beam_s { int entity; // draw this as lightning polygons, or a model? int lightning; struct model_s *model; float endtime; vec3_t start, end; } beam_t; typedef struct rtlight_particle_s { float origin[3]; float color[3]; } rtlight_particle_t; typedef struct rtlight_s { // shadow volumes are done entirely in model space, so there are no matrices for dealing with them... they just use the origin // note that the world to light matrices are inversely scaled (divided) by lightradius // core properties /// matrix for transforming light filter coordinates to world coordinates matrix4x4_t matrix_lighttoworld; /// matrix for transforming world coordinates to light filter coordinates matrix4x4_t matrix_worldtolight; /// typically 1 1 1, can be lower (dim) or higher (overbright) vec3_t color; /// size of the light (remove?) vec_t radius; /// light filter char cubemapname[64]; /// light style to monitor for brightness int style; /// whether light should render shadows int shadow; /// intensity of corona to render vec_t corona; /// radius scale of corona to render (1.0 means same as light radius) vec_t coronasizescale; /// ambient intensity to render vec_t ambientscale; /// diffuse intensity to render vec_t diffusescale; /// specular intensity to render vec_t specularscale; /// LIGHTFLAG_* flags int flags; // generated properties /// used only for shadow volumes vec3_t shadoworigin; /// culling vec3_t cullmins; vec3_t cullmaxs; // culling //vec_t cullradius; // squared cullradius //vec_t cullradius2; // rendering properties, updated each time a light is rendered // this is rtlight->color * d_lightstylevalue vec3_t currentcolor; /// used by corona updates, due to occlusion query float corona_visibility; unsigned int corona_queryindex_visiblepixels; unsigned int corona_queryindex_allpixels; /// this is R_GetCubemap(rtlight->cubemapname) rtexture_t *currentcubemap; /// set by R_Shadow_PrepareLight to decide whether R_Shadow_DrawLight should draw it qboolean draw; /// these fields are set by R_Shadow_PrepareLight for later drawing int cached_numlightentities; int cached_numlightentities_noselfshadow; int cached_numshadowentities; int cached_numshadowentities_noselfshadow; int cached_numsurfaces; struct entity_render_s **cached_lightentities; struct entity_render_s **cached_lightentities_noselfshadow; struct entity_render_s **cached_shadowentities; struct entity_render_s **cached_shadowentities_noselfshadow; unsigned char *cached_shadowtrispvs; unsigned char *cached_lighttrispvs; int *cached_surfacelist; // reduced light cullbox from GetLightInfo vec3_t cached_cullmins; vec3_t cached_cullmaxs; // current shadow-caster culling planes based on view // (any geometry outside these planes can not contribute to the visible // shadows in any way, and thus can be culled safely) int cached_numfrustumplanes; mplane_t cached_frustumplanes[5]; // see R_Shadow_ComputeShadowCasterCullingPlanes /// static light info /// true if this light should be compiled as a static light int isstatic; /// true if this is a compiled world light, cleared if the light changes int compiled; /// the shadowing mode used to compile this light int shadowmode; /// premade shadow volumes to render for world entity shadowmesh_t *static_meshchain_shadow_zpass; shadowmesh_t *static_meshchain_shadow_zfail; shadowmesh_t *static_meshchain_shadow_shadowmap; /// used for visibility testing (more exact than bbox) int static_numleafs; int static_numleafpvsbytes; int *static_leaflist; unsigned char *static_leafpvs; /// surfaces seen by light int static_numsurfaces; int *static_surfacelist; /// flag bits indicating which triangles of the world model should cast /// shadows, and which ones should be lit /// /// this avoids redundantly scanning the triangles in each surface twice /// for whether they should cast shadows, once in culling and once in the /// actual shadowmarklist production. int static_numshadowtrispvsbytes; unsigned char *static_shadowtrispvs; /// this allows the lighting batch code to skip backfaces andother culled /// triangles not relevant for lighting /// (important on big surfaces such as terrain) int static_numlighttrispvsbytes; unsigned char *static_lighttrispvs; /// masks of all shadowmap sides that have any potential static receivers or casters int static_shadowmap_receivers; int static_shadowmap_casters; /// particle-tracing cache for global illumination int particlecache_numparticles; int particlecache_maxparticles; int particlecache_updateparticle; rtlight_particle_t *particlecache_particles; /// bouncegrid light info float photoncolor[3]; float photons; } rtlight_t; typedef struct dlight_s { // destroy light after this time // (dlight only) vec_t die; // the entity that owns this light (can be NULL) // (dlight only) struct entity_render_s *ent; // location // (worldlight: saved to .rtlights file) vec3_t origin; // worldlight orientation // (worldlight only) // (worldlight: saved to .rtlights file) vec3_t angles; // dlight orientation/scaling/location // (dlight only) matrix4x4_t matrix; // color of light // (worldlight: saved to .rtlights file) vec3_t color; // cubemap name to use on this light // (worldlight: saved to .rtlights file) char cubemapname[64]; // make light flash while selected // (worldlight only) int selected; // brightness (not really radius anymore) // (worldlight: saved to .rtlights file) vec_t radius; // drop intensity this much each second // (dlight only) vec_t decay; // intensity value which is dropped over time // (dlight only) vec_t intensity; // initial values for intensity to modify // (dlight only) vec_t initialradius; vec3_t initialcolor; // light style which controls intensity of this light // (worldlight: saved to .rtlights file) int style; // cast shadows // (worldlight: saved to .rtlights file) int shadow; // corona intensity // (worldlight: saved to .rtlights file) vec_t corona; // radius scale of corona to render (1.0 means same as light radius) // (worldlight: saved to .rtlights file) vec_t coronasizescale; // ambient intensity to render // (worldlight: saved to .rtlights file) vec_t ambientscale; // diffuse intensity to render // (worldlight: saved to .rtlights file) vec_t diffusescale; // specular intensity to render // (worldlight: saved to .rtlights file) vec_t specularscale; // LIGHTFLAG_* flags // (worldlight: saved to .rtlights file) int flags; // linked list of world lights // (worldlight only) struct dlight_s *next; // embedded rtlight struct for renderer // (worldlight only) rtlight_t rtlight; } dlight_t; // this is derived from processing of the framegroupblend array // note: technically each framegroupblend can produce two of these, but that // never happens in practice because no one blends between more than 2 // framegroups at once #define MAX_FRAMEBLENDS (MAX_FRAMEGROUPBLENDS * 2) typedef struct frameblend_s { int subframe; float lerp; } frameblend_t; // LordHavoc: this struct is intended for the renderer but some fields are // used by the client. // // The renderer should not rely on any changes to this struct to be persistent // across multiple frames because temp entities are wiped every frame, but it // is acceptable to cache things in this struct that are not critical. // // For example the r_cullentities_trace code does such caching. typedef struct entity_render_s { // location //vec3_t origin; // orientation //vec3_t angles; // transform matrix for model to world matrix4x4_t matrix; // transform matrix for world to model matrix4x4_t inversematrix; // opacity (alpha) of the model float alpha; // size the model is shown float scale; // transparent sorting offset float transparent_offset; // NULL = no model dp_model_t *model; // number of the entity represents, or 0 for non-network entities int entitynumber; // literal colormap colors for renderer, if both are 0 0 0 it is not colormapped vec3_t colormap_pantscolor; vec3_t colormap_shirtcolor; // light, particles, etc int effects; // qw CTF flags and other internal-use-only effect bits int internaleffects; // for Alias models int skinnum; // render flags int flags; // colormod tinting of models float colormod[3]; float glowmod[3]; // interpolated animation - active framegroups and blend factors framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; // time of last model change (for shader animations) double shadertime; // calculated by the renderer (but not persistent) // calculated during R_AddModelEntities vec3_t mins, maxs; // subframe numbers (-1 if not used) and their blending scalers (0-1), if interpolation is not desired, use subframeblend[0].subframe frameblend_t frameblend[MAX_FRAMEBLENDS]; // skeletal animation data (if skeleton.relativetransforms is not NULL, it overrides frameblend) skeleton_t *skeleton; // animation cache (pointers allocated using R_FrameData_Alloc) // ONLY valid during R_RenderView! may be NULL (not cached) float *animcache_vertex3f; r_meshbuffer_t *animcache_vertex3f_vertexbuffer; int animcache_vertex3f_bufferoffset; float *animcache_normal3f; r_meshbuffer_t *animcache_normal3f_vertexbuffer; int animcache_normal3f_bufferoffset; float *animcache_svector3f; r_meshbuffer_t *animcache_svector3f_vertexbuffer; int animcache_svector3f_bufferoffset; float *animcache_tvector3f; r_meshbuffer_t *animcache_tvector3f_vertexbuffer; int animcache_tvector3f_bufferoffset; // interleaved arrays for rendering and dynamic vertex buffers for them r_vertexmesh_t *animcache_vertexmesh; r_meshbuffer_t *animcache_vertexmesh_vertexbuffer; int animcache_vertexmesh_bufferoffset; // gpu-skinning shader needs transforms in a certain format, we have to // upload this to a uniform buffer for the shader to use, and also keep a // backup copy in system memory for the dynamic batch fallback code // if this is not NULL, the other animcache variables are NULL float *animcache_skeletaltransform3x4; r_meshbuffer_t *animcache_skeletaltransform3x4buffer; int animcache_skeletaltransform3x4offset; int animcache_skeletaltransform3x4size; // current lighting from map (updated ONLY by client code, not renderer) vec3_t modellight_ambient; vec3_t modellight_diffuse; // q3bsp vec3_t modellight_lightdir; // q3bsp // storage of decals on this entity // (note: if allowdecals is set, be sure to call R_DecalSystem_Reset on removal!) int allowdecals; decalsystem_t decalsystem; // FIELDS UPDATED BY RENDERER: // last time visible during trace culling double last_trace_visibility; // user wavefunc parameters (from csqc) vec_t userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; } entity_render_t; typedef struct entity_persistent_s { vec3_t trail_origin; // previous position for particle trail spawning vec3_t oldorigin; // lerp vec3_t oldangles; // lerp vec3_t neworigin; // lerp vec3_t newangles; // lerp vec_t lerpstarttime; // lerp vec_t lerpdeltatime; // lerp float muzzleflash; // muzzleflash intensity, fades over time float trail_time; // residual error accumulation for particle trail spawning (to keep spacing across frames) qboolean trail_allowed; // set to false by teleports, true by update code, prevents bad lerps } entity_persistent_t; typedef struct entity_s { // baseline state (default values) entity_state_t state_baseline; // previous state (interpolating from this) entity_state_t state_previous; // current state (interpolating to this) entity_state_t state_current; // used for regenerating parts of render entity_persistent_t persistent; // the only data the renderer should know about entity_render_t render; } entity_t; typedef struct usercmd_s { vec3_t viewangles; // intended velocities float forwardmove; float sidemove; float upmove; vec3_t cursor_screen; vec3_t cursor_start; vec3_t cursor_end; vec3_t cursor_impact; vec3_t cursor_normal; vec_t cursor_fraction; int cursor_entitynumber; double time; // time the move is executed for (cl_movement: clienttime, non-cl_movement: receivetime) double receivetime; // time the move was received at double clienttime; // time to which server state the move corresponds to int msec; // for predicted moves int buttons; int impulse; unsigned int sequence; qboolean applied; // if false we're still accumulating a move qboolean predicted; // if true the sequence should be sent as 0 // derived properties double frametime; qboolean canjump; qboolean jump; qboolean crouch; } usercmd_t; typedef struct lightstyle_s { int length; char map[MAX_STYLESTRING]; } lightstyle_t; typedef struct scoreboard_s { char name[MAX_SCOREBOARDNAME]; int frags; int colors; // two 4 bit fields // QW fields: int qw_userid; char qw_userinfo[MAX_USERINFO_STRING]; float qw_entertime; int qw_ping; int qw_packetloss; int qw_movementloss; int qw_spectator; char qw_team[8]; char qw_skin[MAX_QPATH]; } scoreboard_t; typedef struct cshift_s { float destcolor[3]; float percent; // 0-255 float alphafade; // (any speed) } cshift_t; #define CSHIFT_CONTENTS 0 #define CSHIFT_DAMAGE 1 #define CSHIFT_BONUS 2 #define CSHIFT_POWERUP 3 #define CSHIFT_VCSHIFT 4 #define NUM_CSHIFTS 5 #define NAME_LENGTH 64 // // client_state_t should hold all pieces of the client state // #define SIGNONS 4 // signon messages to receive before connected typedef enum cactive_e { ca_uninitialized, // during early startup ca_dedicated, // a dedicated server with no ability to start a client ca_disconnected, // full screen console with no connection ca_connected // valid netcon, talking to a server } cactive_t; typedef enum qw_downloadtype_e { dl_none, dl_single, dl_skin, dl_model, dl_sound } qw_downloadtype_t; typedef enum capturevideoformat_e { CAPTUREVIDEOFORMAT_AVI_I420, CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA } capturevideoformat_t; typedef struct capturevideostate_s { double startrealtime; double framerate; int framestep; int framestepframe; qboolean active; qboolean realtime; qboolean error; int soundrate; int soundchannels; int frame; double starttime; double lastfpstime; int lastfpsframe; int soundsampleframe; unsigned char *screenbuffer; unsigned char *outbuffer; char basename[MAX_QPATH]; int width, height; // precomputed RGB to YUV tables // converts the RGB values to YUV (see cap_avi.c for how to use them) short rgbtoyuvscaletable[3][3][256]; unsigned char yuvnormalizetable[3][256]; // precomputed gamma ramp (only needed if the capturevideo module uses RGB output) // note: to map from these values to RGB24, you have to multiply by 255.0/65535.0, then add 0.5, then cast to integer unsigned short vidramp[256 * 3]; // stuff to be filled in by the video format module capturevideoformat_t format; const char *formatextension; qfile_t *videofile; // always use this: // cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); void (*endvideo) (void); void (*videoframes) (int num); void (*soundframe) (const portable_sampleframe_t *paintbuffer, size_t length); // format specific data void *formatspecific; } capturevideostate_t; #define CL_MAX_DOWNLOADACKS 4 typedef struct cl_downloadack_s { int start, size; } cl_downloadack_t; typedef struct cl_soundstats_s { int mixedsounds; int totalsounds; int latency_milliseconds; } cl_soundstats_t; // // the client_static_t structure is persistent through an arbitrary number // of server connections // typedef struct client_static_s { cactive_t state; // all client memory allocations go in these pools mempool_t *levelmempool; mempool_t *permanentmempool; // demo loop control // -1 = don't play demos int demonum; // list of demos in loop char demos[MAX_DEMOS][MAX_DEMONAME]; // the actively playing demo (set by CL_PlayDemo_f) char demoname[MAX_QPATH]; // demo recording info must be here, because record is started before // entering a map (and clearing client_state_t) qboolean demorecording; fs_offset_t demo_lastcsprogssize; int demo_lastcsprogscrc; qboolean demoplayback; qboolean demostarting; // set if currently starting a demo, to stop -demo from quitting when switching to another demo qboolean timedemo; // -1 = use normal cd track int forcetrack; qfile_t *demofile; // realtime at second frame of timedemo (LordHavoc: changed to double) double td_starttime; int td_frames; // total frames parsed double td_onesecondnexttime; double td_onesecondframes; double td_onesecondrealtime; double td_onesecondminfps; double td_onesecondmaxfps; double td_onesecondavgfps; int td_onesecondavgcount; // LordHavoc: pausedemo qboolean demopaused; // sound mixer statistics for showsound display cl_soundstats_t soundstats; qboolean connect_trying; int connect_remainingtries; double connect_nextsendtime; lhnetsocket_t *connect_mysocket; lhnetaddress_t connect_address; lhnetaddress_t rcon_address; // protocol version of the server we're connected to // (kept outside client_state_t because it's used between levels) protocolversion_t protocol; #define MAX_RCONS 16 int rcon_trying; lhnetaddress_t rcon_addresses[MAX_RCONS]; char rcon_commands[MAX_RCONS][MAX_INPUTLINE]; double rcon_timeout[MAX_RCONS]; int rcon_ringpos; // connection information // 0 to SIGNONS int signon; // network connection netconn_t *netcon; // download information // (note: qw_download variables are also used) cl_downloadack_t dp_downloadack[CL_MAX_DOWNLOADACKS]; // input sequence numbers are not reset on level change, only connect unsigned int servermovesequence; // quakeworld stuff below // value of "qport" cvar at time of connection int qw_qport; // copied from cls.netcon->qw. variables every time they change, or set by demos (which have no cls.netcon) unsigned int qw_incoming_sequence; unsigned int qw_outgoing_sequence; // current file download buffer (only saved when file is completed) char qw_downloadname[MAX_QPATH]; unsigned char *qw_downloadmemory; int qw_downloadmemorycursize; int qw_downloadmemorymaxsize; int qw_downloadnumber; int qw_downloadpercent; qw_downloadtype_t qw_downloadtype; // transfer rate display double qw_downloadspeedtime; int qw_downloadspeedcount; int qw_downloadspeedrate; qboolean qw_download_deflate; // current file upload buffer (for uploading screenshots to server) unsigned char *qw_uploaddata; int qw_uploadsize; int qw_uploadpos; // user infostring // this normally contains the following keys in quakeworld: // password spectator name team skin topcolor bottomcolor rate noaim msg *ver *ip char userinfo[MAX_USERINFO_STRING]; // extra user info for the "connect" command char connect_userinfo[MAX_USERINFO_STRING]; // video capture stuff capturevideostate_t capturevideo; // crypto channel crypto_t crypto; // ProQuake compatibility stuff int proquake_servermod; // 0 = not proquake, 1 = proquake int proquake_serverversion; // actual proquake server version * 10 (3.40 = 34, etc) int proquake_serverflags; // 0 (PQF_CHEATFREE not supported) // don't write-then-read csprogs.dat (useful for demo playback) unsigned char *caughtcsprogsdata; fs_offset_t caughtcsprogsdatasize; int r_speeds_graph_length; int r_speeds_graph_current; int *r_speeds_graph_data; // graph scales int r_speeds_graph_datamin[r_stat_count]; int r_speeds_graph_datamax[r_stat_count]; } client_static_t; extern client_static_t cls; //[515]: csqc typedef struct { qboolean drawworld; qboolean drawenginesbar; qboolean drawcrosshair; }csqc_vidvars_t; typedef enum { PARTICLE_BILLBOARD = 0, PARTICLE_SPARK = 1, PARTICLE_ORIENTED_DOUBLESIDED = 2, PARTICLE_VBEAM = 3, PARTICLE_HBEAM = 4, PARTICLE_INVALID = -1 } porientation_t; typedef enum { PBLEND_ALPHA = 0, PBLEND_ADD = 1, PBLEND_INVMOD = 2, PBLEND_INVALID = -1 } pblend_t; typedef struct particletype_s { pblend_t blendmode; porientation_t orientation; qboolean lighting; } particletype_t; typedef enum ptype_e { pt_dead, pt_alphastatic, pt_static, pt_spark, pt_beam, pt_rain, pt_raindecal, pt_snow, pt_bubble, pt_blood, pt_smoke, pt_decal, pt_entityparticle, pt_total } ptype_t; typedef struct decal_s { // fields used by rendering: (44 bytes) unsigned short typeindex; unsigned short texnum; unsigned int decalsequence; vec3_t org; vec3_t normal; float size; float alpha; // 0-255 unsigned char color[3]; unsigned char unused1; int clusterindex; // cheap culling by pvs // fields not used by rendering: (36 bytes in 32bit, 40 bytes in 64bit) float time2; // used for decal fade unsigned int owner; // decal stuck to this entity dp_model_t *ownermodel; // model the decal is stuck to (used to make sure the entity is still alive) vec3_t relativeorigin; // decal at this location in entity's coordinate space vec3_t relativenormal; // decal oriented this way relative to entity's coordinate space } decal_t; typedef struct particle_s { // for faster batch rendering, particles are rendered in groups by effect (resulting in less perfect sorting but far less state changes) // fields used by rendering: (48 bytes) vec3_t sortorigin; // sort by this group origin, not particle org vec3_t org; vec3_t vel; // velocity of particle, or orientation of decal, or end point of beam float size; float alpha; // 0-255 float stretch; // only for sparks // fields not used by rendering: (44 bytes) float stainsize; float stainalpha; float sizeincrease; // rate of size change per second float alphafade; // how much alpha reduces per second float time2; // used for snow fluttering and decal fade float bounce; // how much bounce-back from a surface the particle hits (0 = no physics, 1 = stop and slide, 2 = keep bouncing forever, 1.5 is typical) float gravity; // how much gravity affects this particle (1.0 = normal gravity, 0.0 = none) float airfriction; // how much air friction affects this object (objects with a low mass/size ratio tend to get more air friction) float liquidfriction; // how much liquid friction affects this object (objects with a low mass/size ratio tend to get more liquid friction) // float delayedcollisions; // time that p->bounce becomes active float delayedspawn; // time that particle appears and begins moving float die; // time when this particle should be removed, regardless of alpha // short variables grouped to save memory (4 bytes) short angle; // base rotation of particle short spin; // geometry rotation speed around the particle center normal // byte variables grouped to save memory (12 bytes) unsigned char color[3]; unsigned char qualityreduction; // enables skipping of this particle according to r_refdef.view.qualityreduction unsigned char typeindex; unsigned char blendmode; unsigned char orientation; unsigned char texnum; unsigned char staincolor[3]; signed char staintexnum; } particle_t; typedef enum cl_parsingtextmode_e { CL_PARSETEXTMODE_NONE, CL_PARSETEXTMODE_PING, CL_PARSETEXTMODE_STATUS, CL_PARSETEXTMODE_STATUS_PLAYERID, CL_PARSETEXTMODE_STATUS_PLAYERIP } cl_parsingtextmode_t; typedef struct cl_locnode_s { struct cl_locnode_s *next; char *name; vec3_t mins, maxs; } cl_locnode_t; typedef struct showlmp_s { qboolean isactive; float x; float y; char label[32]; char pic[128]; } showlmp_t; // // the client_state_t structure is wiped completely at every // server signon // typedef struct client_state_s { // true if playing in a local game and no one else is connected int islocalgame; // send a clc_nop periodically until connected float sendnoptime; // current input being accumulated by mouse/joystick/etc input usercmd_t cmd; // latest moves sent to the server that have not been confirmed yet usercmd_t movecmd[CL_MAX_USERCMDS]; // information for local display // health, etc int stats[MAX_CL_STATS]; float *statsf; // points to stats[] array // last known inventory bit flags, for blinking int olditems; // cl.time of acquiring item, for blinking float item_gettime[32]; // last known STAT_ACTIVEWEAPON int activeweapon; // cl.time of changing STAT_ACTIVEWEAPON float weapontime; // use pain anim frame if cl.time < this float faceanimtime; // for stair smoothing float stairsmoothz; double stairsmoothtime; // color shifts for damage, powerups cshift_t cshifts[NUM_CSHIFTS]; // and content types cshift_t prev_cshifts[NUM_CSHIFTS]; // the client maintains its own idea of view angles, which are // sent to the server each frame. The server sets punchangle when // the view is temporarily offset, and an angle reset commands at the start // of each level and after teleporting. // mviewangles is read from demo // viewangles is either client controlled or lerped from mviewangles vec3_t mviewangles[2], viewangles; // update by server, used by qc to do weapon recoil vec3_t mpunchangle[2], punchangle; // update by server, can be used by mods to kick view around vec3_t mpunchvector[2], punchvector; // update by server, used for lean+bob (0 is newest) vec3_t mvelocity[2], velocity; // update by server, can be used by mods for zooming vec_t mviewzoom[2], viewzoom; // if true interpolation the mviewangles and other interpolation of the // player is disabled until the next network packet // this is used primarily by teleporters, and when spectating players // special checking of the old fixangle[1] is used to differentiate // between teleporting and spectating qboolean fixangle[2]; // client movement simulation // these fields are only updated by CL_ClientMovement (called by CL_SendMove after parsing each network packet) // set by CL_ClientMovement_Replay functions qboolean movement_predicted; // if true the CL_ClientMovement_Replay function will update origin, etc qboolean movement_replay; // simulated data (this is valid even if cl.movement is false) vec3_t movement_origin; vec3_t movement_velocity; // whether the replay should allow a jump at the first sequence qboolean movement_replay_canjump; // previous gun angles (for leaning effects) vec3_t gunangles_prev; vec3_t gunangles_highpass; vec3_t gunangles_adjustment_lowpass; vec3_t gunangles_adjustment_highpass; // previous gun angles (for leaning effects) vec3_t gunorg_prev; vec3_t gunorg_highpass; vec3_t gunorg_adjustment_lowpass; vec3_t gunorg_adjustment_highpass; // pitch drifting vars float idealpitch; float pitchvel; qboolean nodrift; float driftmove; double laststop; //[515]: added for csqc purposes float sensitivityscale; csqc_vidvars_t csqc_vidvars; //[515]: these parms must be set to true by default qboolean csqc_wantsmousemove; qboolean csqc_paused; // vortex: int because could be flags struct model_s *csqc_model_precache[MAX_MODELS]; // local amount for smoothing stepups //float crouch; // sent by server qboolean paused; qboolean onground; qboolean inwater; // used by bob qboolean oldonground; double lastongroundtime; double hitgroundtime; float bob2_smooth; float bobfall_speed; float bobfall_swing; double calcrefdef_prevtime; // don't change view angle, full screen, etc int intermission; // latched at intermission start double completed_time; // the timestamp of the last two messages double mtime[2]; // clients view of time, time should be between mtime[0] and mtime[1] to // generate a lerp point for other data, oldtime is the previous frame's // value of time, frametime is the difference between time and oldtime // note: cl.time may be beyond cl.mtime[0] if packet loss is occuring, it // is only forcefully limited when a packet is received double time, oldtime; // how long it has been since the previous client frame in real time // (not game time, for that use cl.time - cl.oldtime) double realframetime; // fade var for fading while dead float deathfade; // motionblur alpha level variable float motionbluralpha; // copy of realtime from last recieved message, for net trouble icon float last_received_message; // information that is static for the entire time connected to a server struct model_s *model_precache[MAX_MODELS]; struct sfx_s *sound_precache[MAX_SOUNDS]; // FIXME: this is a lot of memory to be keeping around, this really should be dynamically allocated and freed somehow char model_name[MAX_MODELS][MAX_QPATH]; char sound_name[MAX_SOUNDS][MAX_QPATH]; // for display on solo scoreboard char worldmessage[40]; // map title (not related to filename) // variants of map name char worldbasename[MAX_QPATH]; // %s char worldname[MAX_QPATH]; // maps/%s.bsp char worldnamenoextension[MAX_QPATH]; // maps/%s // cl_entitites[cl.viewentity] = player int viewentity; // the real player entity (normally same as viewentity, // different than viewentity if mod uses chasecam or other tricks) int realplayerentity; // this is updated to match cl.viewentity whenever it is in the clients // range, basically this is used in preference to cl.realplayerentity for // most purposes because when spectating another player it should show // their information rather than yours int playerentity; // max players that can be in this game int maxclients; // type of game (deathmatch, coop, singleplayer) int gametype; // models and sounds used by engine code (particularly cl_parse.c) dp_model_t *model_bolt; dp_model_t *model_bolt2; dp_model_t *model_bolt3; dp_model_t *model_beam; sfx_t *sfx_wizhit; sfx_t *sfx_knighthit; sfx_t *sfx_tink1; sfx_t *sfx_ric1; sfx_t *sfx_ric2; sfx_t *sfx_ric3; sfx_t *sfx_r_exp3; // indicates that the file "sound/misc/talk2.wav" was found (for use by team chat messages) qboolean foundtalk2wav; // refresh related state // cl_entitites[0].model struct model_s *worldmodel; // the gun model entity_t viewent; // cd audio int cdtrack, looptrack; // frag scoreboard // [cl.maxclients] scoreboard_t *scores; // keep track of svc_print parsing state (analyzes ping reports and status reports) cl_parsingtextmode_t parsingtextmode; int parsingtextplayerindex; // set by scoreboard code when sending ping command, this causes the next ping results to be hidden // (which could eat the wrong ping report if the player issues one // manually, but they would still see a ping report, just a later one // caused by the scoreboard code rather than the one they intentionally // issued) int parsingtextexpectingpingforscores; // entity database stuff // latest received entity frame numbers #define LATESTFRAMENUMS 32 int latestframenumsposition; int latestframenums[LATESTFRAMENUMS]; unsigned int latestsendnums[LATESTFRAMENUMS]; entityframe_database_t *entitydatabase; entityframe4_database_t *entitydatabase4; entityframeqw_database_t *entitydatabaseqw; // keep track of quake entities because they need to be killed if they get stale int lastquakeentity; unsigned char isquakeentity[MAX_EDICTS]; // bounding boxes for clientside movement vec3_t playerstandmins; vec3_t playerstandmaxs; vec3_t playercrouchmins; vec3_t playercrouchmaxs; // old decals are killed based on this unsigned int decalsequence; int max_entities; int max_csqcrenderentities; int max_static_entities; int max_effects; int max_beams; int max_dlights; int max_lightstyle; int max_brushmodel_entities; int max_particles; int max_decals; int max_showlmps; entity_t *entities; entity_render_t *csqcrenderentities; unsigned char *entities_active; entity_t *static_entities; cl_effect_t *effects; beam_t *beams; dlight_t *dlights; lightstyle_t *lightstyle; int *brushmodel_entities; particle_t *particles; decal_t *decals; showlmp_t *showlmps; int num_entities; int num_static_entities; int num_brushmodel_entities; int num_effects; int num_beams; int num_dlights; int num_particles; int num_decals; int num_showlmps; double particles_updatetime; double decals_updatetime; int free_particle; int free_decal; // cl_serverextension_download feature int loadmodel_current; int downloadmodel_current; int loadmodel_total; int loadsound_current; int downloadsound_current; int loadsound_total; qboolean downloadcsqc; qboolean loadcsqc; qboolean loadbegun; qboolean loadfinished; // quakeworld stuff // local copy of the server infostring char qw_serverinfo[MAX_SERVERINFO_STRING]; // time of last qw "pings" command sent to server while showing scores double last_ping_request; // used during connect int qw_servercount; // updated from serverinfo int qw_teamplay; // unused: indicates whether the player is spectating // use cl.scores[cl.playerentity-1].qw_spectator instead //qboolean qw_spectator; // last time an input packet was sent double lastpackettime; // movement parameters for client prediction unsigned int moveflags; float movevars_wallfriction; float movevars_waterfriction; float movevars_friction; float movevars_timescale; float movevars_gravity; float movevars_stopspeed; float movevars_maxspeed; float movevars_spectatormaxspeed; float movevars_accelerate; float movevars_airaccelerate; float movevars_wateraccelerate; float movevars_entgravity; float movevars_jumpvelocity; float movevars_edgefriction; float movevars_maxairspeed; float movevars_stepheight; float movevars_airaccel_qw; float movevars_airaccel_qw_stretchfactor; float movevars_airaccel_sideways_friction; float movevars_airstopaccelerate; float movevars_airstrafeaccelerate; float movevars_maxairstrafespeed; float movevars_airstrafeaccel_qw; float movevars_aircontrol; float movevars_aircontrol_power; float movevars_aircontrol_penalty; float movevars_warsowbunny_airforwardaccel; float movevars_warsowbunny_accel; float movevars_warsowbunny_topspeed; float movevars_warsowbunny_turnaccel; float movevars_warsowbunny_backtosideratio; float movevars_ticrate; float movevars_airspeedlimit_nonqw; // models used by qw protocol int qw_modelindex_spike; int qw_modelindex_player; int qw_modelindex_flag; int qw_modelindex_s_explod; vec3_t qw_intermission_origin; vec3_t qw_intermission_angles; // 255 is the most nails the QW protocol could send int qw_num_nails; vec_t qw_nails[255][6]; float qw_weaponkick; unsigned int qw_validsequence; unsigned int qw_deltasequence[QW_UPDATE_BACKUP]; // csqc stuff: // server entity number corresponding to a clientside entity unsigned short csqc_server2csqcentitynumber[MAX_EDICTS]; qboolean csqc_loaded; vec3_t csqc_vieworigin; vec3_t csqc_viewangles; vec3_t csqc_vieworiginfromengine; vec3_t csqc_viewanglesfromengine; matrix4x4_t csqc_viewmodelmatrixfromengine; qboolean csqc_usecsqclistener; matrix4x4_t csqc_listenermatrix; char csqc_printtextbuf[MAX_INPUTLINE]; // collision culling data world_t world; // loc file stuff (points and boxes describing locations in the level) cl_locnode_t *locnodes; // this is updated to cl.movement_origin whenever health is < 1 // used by %d print in say/say_team messages if cl_locs_enable is on vec3_t lastdeathorigin; // processing buffer used by R_BuildLightMap, reallocated as needed, // freed on each level change size_t buildlightmapmemorysize; unsigned char *buildlightmapmemory; // used by EntityState5_ReadUpdate skeleton_t *engineskeletonobjects; } client_state_t; // // cvars // extern cvar_t cl_name; extern cvar_t cl_color; extern cvar_t cl_rate; extern cvar_t cl_rate_burstsize; extern cvar_t cl_pmodel; extern cvar_t cl_playermodel; extern cvar_t cl_playerskin; extern cvar_t rcon_password; extern cvar_t rcon_address; extern cvar_t cl_upspeed; extern cvar_t cl_forwardspeed; extern cvar_t cl_backspeed; extern cvar_t cl_sidespeed; extern cvar_t cl_movespeedkey; extern cvar_t cl_yawspeed; extern cvar_t cl_pitchspeed; extern cvar_t cl_anglespeedkey; extern cvar_t cl_autofire; extern cvar_t cl_shownet; extern cvar_t cl_nolerp; extern cvar_t cl_nettimesyncfactor; extern cvar_t cl_nettimesyncboundmode; extern cvar_t cl_nettimesyncboundtolerance; extern cvar_t cl_pitchdriftspeed; extern cvar_t lookspring; extern cvar_t lookstrafe; extern cvar_t sensitivity; extern cvar_t freelook; extern cvar_t m_pitch; extern cvar_t m_yaw; extern cvar_t m_forward; extern cvar_t m_side; extern cvar_t cl_autodemo; extern cvar_t cl_autodemo_nameformat; extern cvar_t cl_autodemo_delete; extern cvar_t r_draweffects; extern cvar_t cl_explosions_alpha_start; extern cvar_t cl_explosions_alpha_end; extern cvar_t cl_explosions_size_start; extern cvar_t cl_explosions_size_end; extern cvar_t cl_explosions_lifetime; extern cvar_t cl_stainmaps; extern cvar_t cl_stainmaps_clearonload; extern cvar_t cl_prydoncursor; extern cvar_t cl_prydoncursor_notrace; extern cvar_t cl_locs_enable; extern client_state_t cl; extern void CL_AllocLightFlash (entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); cl_locnode_t *CL_Locs_FindNearest(const vec3_t point); void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point); //============================================================================= // // cl_main // void CL_Shutdown (void); void CL_Init (void); void CL_EstablishConnection(const char *host, int firstarg); void CL_Disconnect (void); void CL_Disconnect_f (void); void CL_UpdateRenderEntity(entity_render_t *ent); void CL_SetEntityColormapColors(entity_render_t *ent, int colormap); void CL_UpdateViewEntities(void); // // cl_input // typedef struct kbutton_s { int down[2]; // key nums holding it down int state; // low bit is down state } kbutton_t; extern kbutton_t in_mlook, in_klook; extern kbutton_t in_strafe; extern kbutton_t in_speed; void CL_InitInput (void); void CL_SendMove (void); void CL_ValidateState(entity_state_t *s); void CL_MoveLerpEntityStates(entity_t *ent); void CL_LerpUpdate(entity_t *e); void CL_ParseTEnt (void); void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning); void CL_RelinkBeams (void); void CL_Beam_CalculatePositions (const beam_t *b, vec3_t start, vec3_t end); void CL_ClientMovement_Replay(void); void CL_ClearTempEntities (void); entity_render_t *CL_NewTempEntity (double shadertime); void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate); void CL_ClearState (void); void CL_ExpandEntities(int num); void CL_ExpandCSQCRenderEntities(int num); void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet); void CL_UpdateWorld (void); void CL_WriteToServer (void); void CL_Input (void); extern int cl_ignoremousemoves; float CL_KeyState (kbutton_t *key); const char *Key_KeynumToString (int keynum, char *buf, size_t buflength); int Key_StringToKeynum (const char *str); // // cl_demo.c // void CL_StopPlayback(void); void CL_ReadDemoMessage(void); void CL_WriteDemoMessage(sizebuf_t *mesage); void CL_CutDemo(unsigned char **buf, fs_offset_t *filesize); void CL_PasteDemo(unsigned char **buf, fs_offset_t *filesize); void CL_NextDemo(void); void CL_Stop_f(void); void CL_Record_f(void); void CL_PlayDemo_f(void); void CL_TimeDemo_f(void); // // cl_parse.c // void CL_Parse_Init(void); void CL_Parse_Shutdown(void); void CL_ParseServerMessage(void); void CL_Parse_DumpPacket(void); void CL_Parse_ErrorCleanUp(void); void QW_CL_StartUpload(unsigned char *data, int size); extern cvar_t qport; void CL_KeepaliveMessage(qboolean readmessages); // call this during loading of large content // // view // void V_StartPitchDrift (void); void V_StopPitchDrift (void); void V_Init (void); float V_CalcRoll (const vec3_t angles, const vec3_t velocity); void V_UpdateBlends (void); void V_ParseDamage (void); // // cl_part // extern cvar_t cl_particles; extern cvar_t cl_particles_quality; extern cvar_t cl_particles_size; extern cvar_t cl_particles_quake; extern cvar_t cl_particles_blood; extern cvar_t cl_particles_blood_alpha; extern cvar_t cl_particles_blood_decal_alpha; extern cvar_t cl_particles_blood_decal_scalemin; extern cvar_t cl_particles_blood_decal_scalemax; extern cvar_t cl_particles_blood_bloodhack; extern cvar_t cl_particles_bulletimpacts; extern cvar_t cl_particles_explosions_sparks; extern cvar_t cl_particles_explosions_shell; extern cvar_t cl_particles_rain; extern cvar_t cl_particles_snow; extern cvar_t cl_particles_smoke; extern cvar_t cl_particles_smoke_alpha; extern cvar_t cl_particles_smoke_alphafade; extern cvar_t cl_particles_sparks; extern cvar_t cl_particles_bubbles; extern cvar_t cl_decals; extern cvar_t cl_decals_time; extern cvar_t cl_decals_fadetime; void CL_Particles_Clear(void); void CL_Particles_Init(void); void CL_Particles_Shutdown(void); particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]); typedef enum effectnameindex_s { EFFECT_NONE, EFFECT_TE_GUNSHOT, EFFECT_TE_GUNSHOTQUAD, EFFECT_TE_SPIKE, EFFECT_TE_SPIKEQUAD, EFFECT_TE_SUPERSPIKE, EFFECT_TE_SUPERSPIKEQUAD, EFFECT_TE_WIZSPIKE, EFFECT_TE_KNIGHTSPIKE, EFFECT_TE_EXPLOSION, EFFECT_TE_EXPLOSIONQUAD, EFFECT_TE_TAREXPLOSION, EFFECT_TE_TELEPORT, EFFECT_TE_LAVASPLASH, EFFECT_TE_SMALLFLASH, EFFECT_TE_FLAMEJET, EFFECT_EF_FLAME, EFFECT_TE_BLOOD, EFFECT_TE_SPARK, EFFECT_TE_PLASMABURN, EFFECT_TE_TEI_G3, EFFECT_TE_TEI_SMOKE, EFFECT_TE_TEI_BIGEXPLOSION, EFFECT_TE_TEI_PLASMAHIT, EFFECT_EF_STARDUST, EFFECT_TR_ROCKET, EFFECT_TR_GRENADE, EFFECT_TR_BLOOD, EFFECT_TR_WIZSPIKE, EFFECT_TR_SLIGHTBLOOD, EFFECT_TR_KNIGHTSPIKE, EFFECT_TR_VORESPIKE, EFFECT_TR_NEHAHRASMOKE, EFFECT_TR_NEXUIZPLASMA, EFFECT_TR_GLOWTRAIL, EFFECT_SVC_PARTICLE, EFFECT_TOTAL } effectnameindex_t; int CL_ParticleEffectIndexForName(const char *name); const char *CL_ParticleEffectNameForIndex(int i); void CL_ParticleEffect(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor); void CL_ParticleTrail(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade); void CL_ParticleBox(int effectindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade); void CL_ParseParticleEffect (void); void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel); void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type); void CL_EntityParticles (const entity_t *ent); void CL_ParticleExplosion (const vec3_t org); void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength); void R_NewExplosion(const vec3_t org); void Debug_PolygonBegin(const char *picname, int flags); void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a); void Debug_PolygonEnd(void); #include "cl_screen.h" extern qboolean sb_showscores; float RSurf_FogVertex(const vec3_t p); float RSurf_FogPoint(const vec3_t p); typedef enum r_viewport_type_e { R_VIEWPORTTYPE_ORTHO, R_VIEWPORTTYPE_PERSPECTIVE, R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP, R_VIEWPORTTYPE_PERSPECTIVECUBESIDE, R_VIEWPORTTYPE_TOTAL } r_viewport_type_t; typedef struct r_viewport_s { matrix4x4_t cameramatrix; // from entity (transforms from camera entity to world) matrix4x4_t viewmatrix; // actual matrix for rendering (transforms to viewspace) matrix4x4_t projectmatrix; // actual projection matrix (transforms from viewspace to screen) int x; int y; int z; int width; int height; int depth; r_viewport_type_t type; float screentodepth[2]; // used by deferred renderer to calculate linear depth from device depth coordinates } r_viewport_t; typedef struct r_refdef_view_s { // view information (changes multiple times per frame) // if any of these variables change then r_refdef.viewcache must be regenerated // by calling R_View_Update // (which also updates viewport, scissor, colormask) // it is safe and expected to copy this into a structure on the stack and // call the renderer recursively, then restore from the stack afterward // (as long as R_View_Update is called) // eye position information matrix4x4_t matrix, inverse_matrix; vec3_t origin; vec3_t forward; vec3_t left; vec3_t right; vec3_t up; int numfrustumplanes; mplane_t frustum[6]; qboolean useclipplane; qboolean usecustompvs; // uses r_refdef.viewcache.pvsbits as-is rather than computing it mplane_t clipplane; float frustum_x, frustum_y; vec3_t frustumcorner[4]; // if turned off it renders an ortho view int useperspective; float ortho_x, ortho_y; // screen area to render in int x; int y; int z; int width; int height; int depth; r_viewport_t viewport; // note: if r_viewscale is used, the viewport.width and viewport.height may be less than width and height // which color components to allow (for anaglyph glasses) int colormask[4]; // global RGB color multiplier for rendering float colorscale; // whether to call R_ClearScreen before rendering stuff qboolean clear; // if true, don't clear or do any post process effects (bloom, etc) qboolean isoverlay; // if true, this is the MAIN view (which is, after CSQC, copied into the scene for use e.g. by r_speeds 1, showtex, prydon cursor) qboolean ismain; // whether to draw r_showtris and such, this is only true for the main // view render, all secondary renders (mirrors, portals, cameras, // distortion effects, etc) omit such debugging information qboolean showdebug; // these define which values to use in GL_CullFace calls to request frontface or backface culling int cullface_front; int cullface_back; // render quality (0 to 1) - affects r_drawparticles_drawdistance and others float quality; } r_refdef_view_t; typedef struct r_refdef_viewcache_s { // updated by gl_main_newmap() int maxentities; int world_numclusters; int world_numclusterbytes; int world_numleafs; int world_numsurfaces; // these properties are generated by R_View_Update() // which entities are currently visible for this viewpoint // (the used range is 0...r_refdef.scene.numentities) unsigned char *entityvisible; // flag arrays used for visibility checking on world model // (all other entities have no per-surface/per-leaf visibility checks) unsigned char *world_pvsbits; unsigned char *world_leafvisible; unsigned char *world_surfacevisible; // if true, the view is currently in a leaf without pvs data qboolean world_novis; } r_refdef_viewcache_t; // TODO: really think about which fields should go into scene and which one should stay in refdef [1/7/2008 Black] // maybe also refactor some of the functions to support different setting sources (ie. fogenabled, etc.) for different scenes typedef struct r_refdef_scene_s { // whether to call S_ExtraUpdate during render to reduce sound chop qboolean extraupdate; // (client gameworld) time for rendering time based effects double time; // the world entity_render_t *worldentity; // same as worldentity->model dp_model_t *worldmodel; // renderable entities (excluding world) entity_render_t **entities; int numentities; int maxentities; // field of temporary entities that is reset each (client) frame entity_render_t *tempentities; int numtempentities; int maxtempentities; qboolean expandtempentities; // renderable dynamic lights rtlight_t *lights[MAX_DLIGHTS]; rtlight_t templights[MAX_DLIGHTS]; int numlights; // intensities for light styles right now, controls rtlights float rtlightstylevalue[MAX_LIGHTSTYLES]; // float fraction of base light value // 8.8bit fixed point intensities for light styles // controls intensity lightmap layers unsigned short lightstylevalue[MAX_LIGHTSTYLES]; // 8.8 fraction of base light value float ambient; qboolean rtworld; qboolean rtworldshadows; qboolean rtdlight; qboolean rtdlightshadows; } r_refdef_scene_t; typedef struct r_refdef_s { // these fields define the basic rendering information for the world // but not the view, which could change multiple times in one rendered // frame (for example when rendering textures for certain effects) // these are set for water warping before // frustum_x/frustum_y are calculated float frustumscale_x, frustumscale_y; // current view settings (these get reset a few times during rendering because of water rendering, reflections, etc) r_refdef_view_t view; r_refdef_viewcache_t viewcache; // minimum visible distance (pixels closer than this disappear) double nearclip; // maximum visible distance (pixels further than this disappear in 16bpp modes, // in 32bpp an infinite-farclip matrix is used instead) double farclip; // fullscreen color blend float viewblend[4]; r_refdef_scene_t scene; float fogplane[4]; float fogplaneviewdist; qboolean fogplaneviewabove; float fogheightfade; float fogcolor[3]; float fogrange; float fograngerecip; float fogmasktabledistmultiplier; #define FOGMASKTABLEWIDTH 1024 float fogmasktable[FOGMASKTABLEWIDTH]; float fogmasktable_start, fogmasktable_alpha, fogmasktable_range, fogmasktable_density; float fog_density; float fog_red; float fog_green; float fog_blue; float fog_alpha; float fog_start; float fog_end; float fog_height; float fog_fadedepth; qboolean fogenabled; qboolean oldgl_fogenable; // new flexible texture height fog (overrides normal fog) char fog_height_texturename[64]; // note: must be 64 for the sscanf code unsigned char *fog_height_table1d; unsigned char *fog_height_table2d; int fog_height_tablesize; // enable float fog_height_tablescale; float fog_height_texcoordscale; char fogheighttexturename[64]; // detects changes to active fog height texture int draw2dstage; // 0 = no, 1 = yes, other value = needs setting up again // true during envmap command capture qboolean envmap; // brightness of world lightmaps and related lighting // (often reduced when world rtlights are enabled) float lightmapintensity; // whether to draw world lights realtime, dlights realtime, and their shadows float polygonfactor; float polygonoffset; float shadowpolygonfactor; float shadowpolygonoffset; // how long R_RenderView took on the previous frame double lastdrawscreentime; // rendering stats for r_speeds display // (these are incremented in many places) int stats[r_stat_count]; } r_refdef_t; extern r_refdef_t r_refdef; typedef enum waterlevel_e { WATERLEVEL_NONE, WATERLEVEL_WETFEET, WATERLEVEL_SWIMMING, WATERLEVEL_SUBMERGED } waterlevel_t; typedef struct cl_clientmovement_state_s { // entity to be ignored for movement struct prvm_edict_s *self; // position vec3_t origin; vec3_t velocity; // current bounding box (different if crouched vs standing) vec3_t mins; vec3_t maxs; // currently on the ground qboolean onground; // currently crouching qboolean crouched; // what kind of water (SUPERCONTENTS_LAVA for instance) int watertype; // how deep waterlevel_t waterlevel; // weird hacks when jumping out of water // (this is in seconds and counts down to 0) float waterjumptime; // user command usercmd_t cmd; } cl_clientmovement_state_t; void CL_ClientMovement_PlayerMove_Frame(cl_clientmovement_state_t *s); // warpzone prediction hack (CSQC builtin) void CL_RotateMoves(const matrix4x4_t *m); void CL_NewFrameReceived(int num); void CL_ParseEntityLump(char *entitystring); void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius); void CL_RelinkLightFlashes(void); void Sbar_ShowFPS(void); void Sbar_ShowFPS_Update(void); void Host_SaveConfig(void); void Host_LoadConfig_f(void); void CL_UpdateMoveVars(void); void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length); void V_DriftPitch(void); void V_FadeViewFlashs(void); void V_CalcViewBlend(void); void V_CalcRefdefUsing (const matrix4x4_t *entrendermatrix, const vec3_t clviewangles, qboolean teleported, qboolean clonground, qboolean clcmdjump, float clstatsviewheight, qboolean cldead, qboolean clintermission, const vec3_t clvelocity); void V_CalcRefdef(void); void CL_Locs_Reload_f(void); #endif darkplaces/darkplaces-sdl.vcproj0000775000175000017500000004621713067716220016316 0ustar kalevkalev darkplaces/image_png.h0000664000175000017500000000203613067716220014263 0ustar kalevkalev/* Copyright (C) 2006 Serge "(515)" Ziryukin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef PNG_H #define PNG_H qboolean PNG_OpenLibrary (void); void PNG_CloseLibrary (void); unsigned char* PNG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data); #endif darkplaces/menu.h0000664000175000017500000000426713067716220013311 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MENU_H #define MENU_H enum m_state_e { m_none, m_main, m_demo, m_singleplayer, m_transfusion_episode, m_transfusion_skill, m_load, m_save, m_multiplayer, m_setup, m_options, m_video, m_keys, m_help, m_credits, m_quit, m_lanconfig, m_gameoptions, m_slist, m_options_effects, m_options_graphics, m_options_colorcontrol, m_reset, m_modlist }; extern enum m_state_e m_state; extern char m_return_reason[128]; void M_Update_Return_Reason(const char *s); /* // hard-coded menus // void M_Init (void); void M_KeyEvent (int key); void M_Draw (void); void M_ToggleMenu (int mode); // // menu prog menu // void MP_Init (void); void MP_KeyEvent (int key); void MP_Draw (void); void MP_ToggleMenu (int mode); void MP_Shutdown (void);*/ // // menu router // void MR_Init_Commands (void); void MR_Init (void); void MR_Restart (void); extern void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); extern void (*MR_Draw) (void); extern void (*MR_ToggleMenu) (int mode); extern void (*MR_Shutdown) (void); extern void (*MR_NewMap) (void); extern int (*MR_GetServerListEntryCategory) (const serverlist_entry_t *entry); typedef struct video_resolution_s { const char *type; int width, height; int conwidth, conheight; double pixelheight; ///< pixel aspect } video_resolution_t; extern video_resolution_t *video_resolutions; extern int video_resolutions_count; extern video_resolution_t video_resolutions_hardcoded[]; extern int video_resolutions_hardcoded_count; #endif darkplaces/mprogdefs.h0000664000175000017500000000037513067716222014331 0ustar kalevkalev #ifndef MPROGDEFS_H #define MPROGDEFS_H /* file generated by qcc, do not modify */ /* typedef struct m_globalvars_s { int pad[28]; int self; } m_globalvars_t; typedef struct m_entvars_s { } m_entvars_t; #define M_PROGHEADER_CRC 10020 */ #endif darkplaces/BSDmakefile0000664000175000017500000000726113067716216014227 0ustar kalevkalev##### DP_MAKE_TARGET autodetection and arch specific variables ##### .ifndef DP_MAKE_TARGET DP_MAKE_TARGET=bsd .endif DP_ARCH != uname # Makefile name MAKEFILE=BSDmakefile # Commands CMD_RM=$(CMD_UNIXRM) CMD_CP=$(CMD_UNIXCP) CMD_MKDIR=$(CMD_UNIXMKDIR) # default targets TARGETS_DEBUG=sv-debug cl-debug sdl-debug TARGETS_PROFILE=sv-profile cl-profile sdl-profile TARGETS_RELEASE=sv-release cl-release sdl-release TARGETS_RELEASE_PROFILE=sv-release-profile cl-release-profile sdl-release-profile TARGETS_NEXUIZ=sv-nexuiz cl-nexuiz sdl-nexuiz # Link options DP_LINK_ZLIB?=shared DP_LINK_JPEG?=shared DP_LINK_ODE?=dlopen DP_LINK_CRYPTO?=dlopen DP_LINK_CRYPTO_RIJNDAEL?=dlopen ###### Optional features ##### DP_CDDA?=enabled .if $(DP_CDDA) == "enabled" OBJ_SDLCD=$(OBJ_CD_COMMON) cd_sdl.o OBJ_BSDCD=$(OBJ_CD_COMMON) cd_bsd.o .else OBJ_SDLCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) OBJ_BSDCD=$(OBJ_CD_COMMON) $(OBJ_NOCD) .endif DP_VIDEO_CAPTURE?=enabled .if $(DP_VIDEO_CAPTURE) == "enabled" CFLAGS_VIDEO_CAPTURE=-DCONFIG_VIDEO_CAPTURE OBJ_VIDEO_CAPTURE = cap_avi.o cap_ogg.o .else CFLAGS_VIDEO_CAPTURE= OBJ_VIDEO_CAPTURE = .endif # X11 libs UNIX_X11LIBPATH=/usr/X11R6/lib # BSD configuration .if $(DP_MAKE_TARGET) == "bsd" # FreeBSD uses OSS .if $(DP_ARCH) == "FreeBSD" DEFAULT_SNDAPI=OSS .else DEFAULT_SNDAPI=BSD .endif OBJ_CD=$(OBJ_BSDCD) OBJ_CL=$(OBJ_GLX) OBJ_ICON= OBJ_ICON_NEXUIZ= LDFLAGS_CL=$(LDFLAGS_BSDCL) LDFLAGS_SV=$(LDFLAGS_BSDSV) LDFLAGS_SDL=$(LDFLAGS_BSDSDL) SDLCONFIG_CFLAGS=$(SDLCONFIG_UNIXCFLAGS) $(SDLCONFIG_UNIXCFLAGS_X11) SDLCONFIG_LIBS=$(SDLCONFIG_UNIXLIBS) $(SDLCONFIG_UNIXLIBS_X11) SDLCONFIG_STATICLIBS=$(SDLCONFIG_UNIXSTATICLIBS) $(SDLCONFIG_UNIXSTATICLIBS_X11) EXE_CL=$(EXE_UNIXCL) EXE_SV=$(EXE_UNIXSV) EXE_SDL=$(EXE_UNIXSDL) EXE_CLNEXUIZ=$(EXE_UNIXCLNEXUIZ) EXE_SVNEXUIZ=$(EXE_UNIXSVNEXUIZ) EXE_SDLNEXUIZ=$(EXE_UNIXSDLNEXUIZ) # set these to "" if you want to use dynamic loading instead # zlib .if $(DP_LINK_ZLIB) == "shared" CFLAGS_LIBZ=-DLINK_TO_ZLIB LIB_Z=-lz .else CFLAGS_LIBZ= LIB_Z= .endif # jpeg .if $(DP_LINK_JPEG) == "shared" CFLAGS_LIBJPEG=-DLINK_TO_LIBJPEG LIB_JPEG=-ljpeg .else CFLAGS_LIBJPEG= LIB_JPEG= .endif # ode .if $(DP_LINK_ODE) == "shared" ODE_CONFIG?=ode-config LIB_ODE=`$(ODE_CONFIG) --libs` CFLAGS_ODE=`$(ODE_CONFIG) --cflags` -DUSEODE -DLINK_TO_LIBODE .else LIB_ODE= CFLAGS_ODE=-DUSEODE .endif # d0_blind_id .if $(DP_LINK_CRYPTO) == "shared" LIB_CRYPTO=-ld0_blind_id CFLAGS_CRYPTO=-DLINK_TO_CRYPTO .else LIB_CRYPTO= CFLAGS_CRYPTO= .endif .if $(DP_LINK_CRYPTO_RIJNDAEL) == "shared" LIB_CRYPTO_RIJNDAEL=-ld0_rijndael CFLAGS_CRYPTO_RIJNDAEL=-DLINK_TO_CRYPTO_RIJNDAEL .else LIB_CRYPTO_RIJNDAEL= CFLAGS_CRYPTO_RIJNDAEL= .endif .endif ##### Sound configuration ##### .ifndef DP_SOUND_API DP_SOUND_API=$(DEFAULT_SNDAPI) .endif # NULL: no sound .if $(DP_SOUND_API) == "NULL" OBJ_SOUND=$(OBJ_SND_NULL) LIB_SOUND=$(LIB_SND_NULL) .endif # OSS: Open Sound System .if $(DP_SOUND_API) == "OSS" OBJ_SOUND=$(OBJ_SND_OSS) LIB_SOUND=$(LIB_SND_OSS) .endif # BSD: BSD / Sun audio API .if $(DP_SOUND_API) == "BSD" OBJ_SOUND=$(OBJ_SND_BSD) LIB_SOUND=$(LIB_SND_BSD) .endif ##### Extra CFLAGS ##### CFLAGS_MAKEDEP=-MD .ifdef DP_FS_BASEDIR CFLAGS_FS=-DDP_FS_BASEDIR='\"$(DP_FS_BASEDIR)\"' .else CFLAGS_FS= .endif CFLAGS_PRELOAD= .ifdef DP_PRELOAD_DEPENDENCIES LDFLAGS_CL+=$(LDFLAGS_UNIXCL_PRELOAD) LDFLAGS_SV+=$(LDFLAGS_UNIXSV_PRELOAD) LDFLAGS_SDL+=$(LDFLAGS_UNIXSDL_PRELOAD) CFLAGS_PRELOAD=$(CFLAGS_UNIX_PRELOAD) .endif CFLAGS_NET= # Systems without IPv6 support should uncomment this: #CFLAGS_NET+=-DNOSUPPORTIPV6 ##### BSD Make specific definitions ##### MAKE:=$(MAKE) -f BSDmakefile DO_LD=$(CC) -o ../../../$@ $> $(LDFLAGS) ##### Definitions shared by all makefiles ##### .include "makefile.inc" darkplaces/zone.c0000664000175000017500000007413713067716222013320 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Z_zone.c #include "quakedef.h" #include "thread.h" #ifdef WIN32 #include #include #else #include #endif #ifdef _MSC_VER #include #else #include #endif #define MEMHEADER_SENTINEL_FOR_ADDRESS(p) ((sentinel_seed ^ (unsigned int) (uintptr_t) (p)) + sentinel_seed) unsigned int sentinel_seed; qboolean mem_bigendian = false; void *mem_mutex = NULL; // divVerent: enables file backed malloc using mmap to conserve swap space (instead of malloc) #ifndef FILE_BACKED_MALLOC # define FILE_BACKED_MALLOC 0 #endif // LordHavoc: enables our own low-level allocator (instead of malloc) #ifndef MEMCLUMPING # define MEMCLUMPING 0 #endif #ifndef MEMCLUMPING_FREECLUMPS # define MEMCLUMPING_FREECLUMPS 0 #endif #if MEMCLUMPING // smallest unit we care about is this many bytes #define MEMUNIT 128 // try to do 32MB clumps, but overhead eats into this #ifndef MEMWANTCLUMPSIZE # define MEMWANTCLUMPSIZE (1<<27) #endif // give malloc padding so we can't waste most of a page at the end #define MEMCLUMPSIZE (MEMWANTCLUMPSIZE - MEMWANTCLUMPSIZE/MEMUNIT/32 - 128) #define MEMBITS (MEMCLUMPSIZE / MEMUNIT) #define MEMBITINTS (MEMBITS / 32) typedef struct memclump_s { // contents of the clump unsigned char block[MEMCLUMPSIZE]; // should always be MEMCLUMP_SENTINEL unsigned int sentinel1; // if a bit is on, it means that the MEMUNIT bytes it represents are // allocated, otherwise free unsigned int bits[MEMBITINTS]; // should always be MEMCLUMP_SENTINEL unsigned int sentinel2; // if this drops to 0, the clump is freed size_t blocksinuse; // largest block of memory available (this is reset to an optimistic // number when anything is freed, and updated when alloc fails the clump) size_t largestavailable; // next clump in the chain struct memclump_s *chain; } memclump_t; #if MEMCLUMPING == 2 static memclump_t masterclump; #endif static memclump_t *clumpchain = NULL; #endif cvar_t developer_memory = {0, "developer_memory", "0", "prints debugging information about memory allocations"}; cvar_t developer_memorydebug = {0, "developer_memorydebug", "0", "enables memory corruption checks (very slow)"}; cvar_t developer_memoryreportlargerthanmb = {0, "developer_memorylargerthanmb", "16", "prints debugging information about memory allocations over this size"}; cvar_t sys_memsize_physical = {CVAR_READONLY, "sys_memsize_physical", "", "physical memory size in MB (or empty if unknown)"}; cvar_t sys_memsize_virtual = {CVAR_READONLY, "sys_memsize_virtual", "", "virtual memory size in MB (or empty if unknown)"}; static mempool_t *poolchain = NULL; void Mem_PrintStats(void); void Mem_PrintList(size_t minallocationsize); #if FILE_BACKED_MALLOC #include #include typedef struct mmap_data_s { size_t len; } mmap_data_t; static void *mmap_malloc(size_t size) { char vabuf[MAX_OSPATH + 1]; char *tmpdir = getenv("TEMP"); mmap_data_t *data; int fd; size += sizeof(mmap_data_t); // waste block dpsnprintf(vabuf, sizeof(vabuf), "%s/darkplaces.XXXXXX", tmpdir ? tmpdir : "/tmp"); fd = mkstemp(vabuf); if(fd < 0) return NULL; ftruncate(fd, size); data = (unsigned char *) mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0); close(fd); unlink(vabuf); if(!data) return NULL; data->len = size; return (void *) (data + 1); } static void mmap_free(void *mem) { mmap_data_t *data; if(!mem) return; data = ((mmap_data_t *) mem) - 1; munmap(data, data->len); } #define malloc mmap_malloc #define free mmap_free #endif #if MEMCLUMPING != 2 // some platforms have a malloc that returns NULL but succeeds later // (Windows growing its swapfile for example) static void *attempt_malloc(size_t size) { void *base; // try for half a second or so unsigned int attempts = 500; while (attempts--) { base = (void *)malloc(size); if (base) return base; Sys_Sleep(1000); } return NULL; } #endif #if MEMCLUMPING static memclump_t *Clump_NewClump(void) { memclump_t **clumpchainpointer; memclump_t *clump; #if MEMCLUMPING == 2 if (clumpchain) return NULL; clump = &masterclump; #else clump = (memclump_t*)attempt_malloc(sizeof(memclump_t)); if (!clump) return NULL; #endif // initialize clump if (developer_memorydebug.integer) memset(clump, 0xEF, sizeof(*clump)); clump->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1); memset(clump->bits, 0, sizeof(clump->bits)); clump->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2); clump->blocksinuse = 0; clump->largestavailable = 0; clump->chain = NULL; // link clump into chain for (clumpchainpointer = &clumpchain;*clumpchainpointer;clumpchainpointer = &(*clumpchainpointer)->chain) ; *clumpchainpointer = clump; return clump; } #endif // low level clumping functions, all other memory functions use these static void *Clump_AllocBlock(size_t size) { unsigned char *base; #if MEMCLUMPING if (size <= MEMCLUMPSIZE) { int index; unsigned int bit; unsigned int needbits; unsigned int startbit; unsigned int endbit; unsigned int needints; int startindex; int endindex; unsigned int value; unsigned int mask; unsigned int *array; memclump_t **clumpchainpointer; memclump_t *clump; needbits = (size + MEMUNIT - 1) / MEMUNIT; needints = (needbits+31)>>5; for (clumpchainpointer = &clumpchain;;clumpchainpointer = &(*clumpchainpointer)->chain) { clump = *clumpchainpointer; if (!clump) { clump = Clump_NewClump(); if (!clump) return NULL; } if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) Sys_Error("Clump_AllocBlock: trashed sentinel1\n"); if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) Sys_Error("Clump_AllocBlock: trashed sentinel2\n"); startbit = 0; endbit = startbit + needbits; array = clump->bits; // do as fast a search as possible, even if it means crude alignment if (needbits >= 32) { // large allocations are aligned to large boundaries // furthermore, they are allocated downward from the top... endindex = MEMBITINTS; startindex = endindex - needints; index = endindex; while (--index >= startindex) { if (array[index]) { endindex = index; startindex = endindex - needints; if (startindex < 0) goto nofreeblock; } } startbit = startindex*32; goto foundblock; } else { // search for a multi-bit gap in a single int // (not dealing with the cases that cross two ints) mask = (1<bits[bit>>5] & (1<<(bit & 31))) Sys_Error("Clump_AllocBlock: internal error (%i needbits)\n", needbits); for (bit = startbit;bit < endbit;bit++) clump->bits[bit>>5] |= (1<<(bit & 31)); clump->blocksinuse += needbits; base = clump->block + startbit * MEMUNIT; if (developer_memorydebug.integer) memset(base, 0xBF, needbits * MEMUNIT); return base; nofreeblock: ; } // never reached return NULL; } // too big, allocate it directly #endif #if MEMCLUMPING == 2 return NULL; #else base = (unsigned char *)attempt_malloc(size); if (base && developer_memorydebug.integer) memset(base, 0xAF, size); return base; #endif } static void Clump_FreeBlock(void *base, size_t size) { #if MEMCLUMPING unsigned int needbits; unsigned int startbit; unsigned int endbit; unsigned int bit; memclump_t **clumpchainpointer; memclump_t *clump; unsigned char *start = (unsigned char *)base; for (clumpchainpointer = &clumpchain;(clump = *clumpchainpointer);clumpchainpointer = &(*clumpchainpointer)->chain) { if (start >= clump->block && start < clump->block + MEMCLUMPSIZE) { if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) Sys_Error("Clump_FreeBlock: trashed sentinel1\n"); if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) Sys_Error("Clump_FreeBlock: trashed sentinel2\n"); if (start + size > clump->block + MEMCLUMPSIZE) Sys_Error("Clump_FreeBlock: block overrun\n"); // the block belongs to this clump, clear the range needbits = (size + MEMUNIT - 1) / MEMUNIT; startbit = (start - clump->block) / MEMUNIT; endbit = startbit + needbits; // first verify all bits are set, otherwise this may be misaligned or a double free for (bit = startbit;bit < endbit;bit++) if ((clump->bits[bit>>5] & (1<<(bit & 31))) == 0) Sys_Error("Clump_FreeBlock: double free\n"); for (bit = startbit;bit < endbit;bit++) clump->bits[bit>>5] &= ~(1<<(bit & 31)); clump->blocksinuse -= needbits; memset(base, 0xFF, needbits * MEMUNIT); // if all has been freed, free the clump itself if (clump->blocksinuse == 0) { *clumpchainpointer = clump->chain; if (developer_memorydebug.integer) memset(clump, 0xFF, sizeof(*clump)); #if MEMCLUMPING != 2 free(clump); #endif } return; } } // does not belong to any known chunk... assume it was a direct allocation #endif #if MEMCLUMPING != 2 memset(base, 0xFF, size); free(base); #endif } void *_Mem_Alloc(mempool_t *pool, void *olddata, size_t size, size_t alignment, const char *filename, int fileline) { unsigned int sentinel1; unsigned int sentinel2; size_t realsize; size_t sharedsize; size_t remainsize; memheader_t *mem; memheader_t *oldmem; unsigned char *base; if (size <= 0) { if (olddata) _Mem_Free(olddata, filename, fileline); return NULL; } if (pool == NULL) { if(olddata) pool = ((memheader_t *)((unsigned char *) olddata - sizeof(memheader_t)))->pool; else Sys_Error("Mem_Alloc: pool == NULL (alloc at %s:%i)", filename, fileline); } if (mem_mutex) Thread_LockMutex(mem_mutex); if (developer_memory.integer || size >= developer_memoryreportlargerthanmb.value * 1048576) Con_DPrintf("Mem_Alloc: pool %s, file %s:%i, size %f bytes (%f MB)\n", pool->name, filename, fileline, (double)size, (double)size / 1048576.0f); //if (developer.integer > 0 && developer_memorydebug.integer) // _Mem_CheckSentinelsGlobal(filename, fileline); pool->totalsize += size; realsize = alignment + sizeof(memheader_t) + size + sizeof(sentinel2); pool->realsize += realsize; base = (unsigned char *)Clump_AllocBlock(realsize); if (base == NULL) { Mem_PrintList(0); Mem_PrintStats(); Mem_PrintList(1<<30); Mem_PrintStats(); Sys_Error("Mem_Alloc: out of memory (alloc of size %f (%.3fMB) at %s:%i)", (double)realsize, (double)realsize / (1 << 20), filename, fileline); } // calculate address that aligns the end of the memheader_t to the specified alignment mem = (memheader_t*)((((size_t)base + sizeof(memheader_t) + (alignment-1)) & ~(alignment-1)) - sizeof(memheader_t)); mem->baseaddress = (void*)base; mem->filename = filename; mem->fileline = fileline; mem->size = size; mem->pool = pool; // calculate sentinels (detects buffer overruns, in a way that is hard to exploit) sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); mem->sentinel = sentinel1; memcpy((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2)); // append to head of list mem->next = pool->chain; mem->prev = NULL; pool->chain = mem; if (mem->next) mem->next->prev = mem; if (mem_mutex) Thread_UnlockMutex(mem_mutex); // copy the shared portion in the case of a realloc, then memset the rest sharedsize = 0; remainsize = size; if (olddata) { oldmem = (memheader_t*)olddata - 1; sharedsize = min(oldmem->size, size); memcpy((void *)((unsigned char *) mem + sizeof(memheader_t)), olddata, sharedsize); remainsize -= sharedsize; _Mem_Free(olddata, filename, fileline); } memset((void *)((unsigned char *) mem + sizeof(memheader_t) + sharedsize), 0, remainsize); return (void *)((unsigned char *) mem + sizeof(memheader_t)); } // only used by _Mem_Free and _Mem_FreePool static void _Mem_FreeBlock(memheader_t *mem, const char *filename, int fileline) { mempool_t *pool; size_t size; size_t realsize; unsigned int sentinel1; unsigned int sentinel2; // check sentinels (detects buffer overruns, in a way that is hard to exploit) sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); if (mem->sentinel != sentinel1) Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, free at %s:%i)", mem->filename, mem->fileline, filename, fileline); pool = mem->pool; if (developer_memory.integer) Con_DPrintf("Mem_Free: pool %s, alloc %s:%i, free %s:%i, size %i bytes\n", pool->name, mem->filename, mem->fileline, filename, fileline, (int)(mem->size)); // unlink memheader from doubly linked list if ((mem->prev ? mem->prev->next != mem : pool->chain != mem) || (mem->next && mem->next->prev != mem)) Sys_Error("Mem_Free: not allocated or double freed (free at %s:%i)", filename, fileline); if (mem_mutex) Thread_LockMutex(mem_mutex); if (mem->prev) mem->prev->next = mem->next; else pool->chain = mem->next; if (mem->next) mem->next->prev = mem->prev; // memheader has been unlinked, do the actual free now size = mem->size; realsize = sizeof(memheader_t) + size + sizeof(sentinel2); pool->totalsize -= size; pool->realsize -= realsize; Clump_FreeBlock(mem->baseaddress, realsize); if (mem_mutex) Thread_UnlockMutex(mem_mutex); } void _Mem_Free(void *data, const char *filename, int fileline) { if (data == NULL) { Con_DPrintf("Mem_Free: data == NULL (called at %s:%i)\n", filename, fileline); return; } if (developer_memorydebug.integer) { //_Mem_CheckSentinelsGlobal(filename, fileline); if (!Mem_IsAllocated(NULL, data)) Sys_Error("Mem_Free: data is not allocated (called at %s:%i)", filename, fileline); } _Mem_FreeBlock((memheader_t *)((unsigned char *) data - sizeof(memheader_t)), filename, fileline); } mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline) { mempool_t *pool; if (developer_memorydebug.integer) _Mem_CheckSentinelsGlobal(filename, fileline); pool = (mempool_t *)Clump_AllocBlock(sizeof(mempool_t)); if (pool == NULL) { Mem_PrintList(0); Mem_PrintStats(); Mem_PrintList(1<<30); Mem_PrintStats(); Sys_Error("Mem_AllocPool: out of memory (allocpool at %s:%i)", filename, fileline); } memset(pool, 0, sizeof(mempool_t)); pool->sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1); pool->sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2); pool->filename = filename; pool->fileline = fileline; pool->flags = flags; pool->chain = NULL; pool->totalsize = 0; pool->realsize = sizeof(mempool_t); strlcpy (pool->name, name, sizeof (pool->name)); pool->parent = parent; pool->next = poolchain; poolchain = pool; return pool; } void _Mem_FreePool(mempool_t **poolpointer, const char *filename, int fileline) { mempool_t *pool = *poolpointer; mempool_t **chainaddress, *iter, *temp; if (developer_memorydebug.integer) _Mem_CheckSentinelsGlobal(filename, fileline); if (pool) { // unlink pool from chain for (chainaddress = &poolchain;*chainaddress && *chainaddress != pool;chainaddress = &((*chainaddress)->next)); if (*chainaddress != pool) Sys_Error("Mem_FreePool: pool already free (freepool at %s:%i)", filename, fileline); if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) Sys_Error("Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) Sys_Error("Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)", pool->filename, pool->fileline, filename, fileline); *chainaddress = pool->next; // free memory owned by the pool while (pool->chain) _Mem_FreeBlock(pool->chain, filename, fileline); // free child pools, too for(iter = poolchain; iter; iter = temp) { temp = iter->next; if(iter->parent == pool) _Mem_FreePool(&temp, filename, fileline); } // free the pool itself Clump_FreeBlock(pool, sizeof(*pool)); *poolpointer = NULL; } } void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline) { mempool_t *chainaddress; if (developer_memorydebug.integer) { //_Mem_CheckSentinelsGlobal(filename, fileline); // check if this pool is in the poolchain for (chainaddress = poolchain;chainaddress;chainaddress = chainaddress->next) if (chainaddress == pool) break; if (!chainaddress) Sys_Error("Mem_EmptyPool: pool is already free (emptypool at %s:%i)", filename, fileline); } if (pool == NULL) Sys_Error("Mem_EmptyPool: pool == NULL (emptypool at %s:%i)", filename, fileline); if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) Sys_Error("Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) Sys_Error("Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)", pool->filename, pool->fileline, filename, fileline); // free memory owned by the pool while (pool->chain) _Mem_FreeBlock(pool->chain, filename, fileline); // empty child pools, too for(chainaddress = poolchain; chainaddress; chainaddress = chainaddress->next) if(chainaddress->parent == pool) _Mem_EmptyPool(chainaddress, filename, fileline); } void _Mem_CheckSentinels(void *data, const char *filename, int fileline) { memheader_t *mem; unsigned int sentinel1; unsigned int sentinel2; if (data == NULL) Sys_Error("Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)", filename, fileline); mem = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); sentinel1 = MEMHEADER_SENTINEL_FOR_ADDRESS(&mem->sentinel); sentinel2 = MEMHEADER_SENTINEL_FOR_ADDRESS((unsigned char *) mem + sizeof(memheader_t) + mem->size); if (mem->sentinel != sentinel1) Sys_Error("Mem_Free: trashed head sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); if (memcmp((unsigned char *) mem + sizeof(memheader_t) + mem->size, &sentinel2, sizeof(sentinel2))) Sys_Error("Mem_Free: trashed tail sentinel (alloc at %s:%i, sentinel check at %s:%i)", mem->filename, mem->fileline, filename, fileline); } #if MEMCLUMPING static void _Mem_CheckClumpSentinels(memclump_t *clump, const char *filename, int fileline) { // this isn't really very useful if (clump->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel1)) Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)", filename, fileline); if (clump->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&clump->sentinel2)) Sys_Error("Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)", filename, fileline); } #endif void _Mem_CheckSentinelsGlobal(const char *filename, int fileline) { memheader_t *mem; #if MEMCLUMPING memclump_t *clump; #endif mempool_t *pool; for (pool = poolchain;pool;pool = pool->next) { if (pool->sentinel1 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel1)) Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); if (pool->sentinel2 != MEMHEADER_SENTINEL_FOR_ADDRESS(&pool->sentinel2)) Sys_Error("Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)", pool->filename, pool->fileline, filename, fileline); } for (pool = poolchain;pool;pool = pool->next) for (mem = pool->chain;mem;mem = mem->next) _Mem_CheckSentinels((void *)((unsigned char *) mem + sizeof(memheader_t)), filename, fileline); #if MEMCLUMPING for (pool = poolchain;pool;pool = pool->next) for (clump = clumpchain;clump;clump = clump->chain) _Mem_CheckClumpSentinels(clump, filename, fileline); #endif } qboolean Mem_IsAllocated(mempool_t *pool, void *data) { memheader_t *header; memheader_t *target; if (pool) { // search only one pool target = (memheader_t *)((unsigned char *) data - sizeof(memheader_t)); for( header = pool->chain ; header ; header = header->next ) if( header == target ) return true; } else { // search all pools for (pool = poolchain;pool;pool = pool->next) if (Mem_IsAllocated(pool, data)) return true; } return false; } void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray) { memset(l, 0, sizeof(*l)); l->mempool = mempool; l->recordsize = recordsize; l->numrecordsperarray = numrecordsperarray; } void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l) { size_t i; if (l->maxarrays) { for (i = 0;i != l->numarrays;i++) Mem_Free(l->arrays[i].data); Mem_Free(l->arrays); } memset(l, 0, sizeof(*l)); } void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l) { size_t i, j; for (i = 0;;i++) { if (i == l->numarrays) { if (l->numarrays == l->maxarrays) { memexpandablearray_array_t *oldarrays = l->arrays; l->maxarrays = max(l->maxarrays * 2, 128); l->arrays = (memexpandablearray_array_t*) Mem_Alloc(l->mempool, l->maxarrays * sizeof(*l->arrays)); if (oldarrays) { memcpy(l->arrays, oldarrays, l->numarrays * sizeof(*l->arrays)); Mem_Free(oldarrays); } } l->arrays[i].numflaggedrecords = 0; l->arrays[i].data = (unsigned char *) Mem_Alloc(l->mempool, (l->recordsize + 1) * l->numrecordsperarray); l->arrays[i].allocflags = l->arrays[i].data + l->recordsize * l->numrecordsperarray; l->numarrays++; } if (l->arrays[i].numflaggedrecords < l->numrecordsperarray) { for (j = 0;j < l->numrecordsperarray;j++) { if (!l->arrays[i].allocflags[j]) { l->arrays[i].allocflags[j] = true; l->arrays[i].numflaggedrecords++; memset(l->arrays[i].data + l->recordsize * j, 0, l->recordsize); return (void *)(l->arrays[i].data + l->recordsize * j); } } } } } /***************************************************************************** * IF YOU EDIT THIS: * If this function was to change the size of the "expandable" array, you have * to update r_shadow.c * Just do a search for "range =", R_ShadowClearWorldLights would be the first * function to look at. (And also seems like the only one?) You might have to * move the call to Mem_ExpandableArray_IndexRange back into for(...) loop's * condition */ void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record) // const! { size_t i, j; unsigned char *p = (unsigned char *)record; for (i = 0;i != l->numarrays;i++) { if (p >= l->arrays[i].data && p < (l->arrays[i].data + l->recordsize * l->numrecordsperarray)) { j = (p - l->arrays[i].data) / l->recordsize; if (p != l->arrays[i].data + j * l->recordsize) Sys_Error("Mem_ExpandableArray_FreeRecord: no such record %p\n", p); if (!l->arrays[i].allocflags[j]) Sys_Error("Mem_ExpandableArray_FreeRecord: record %p is already free!\n", p); l->arrays[i].allocflags[j] = false; l->arrays[i].numflaggedrecords--; return; } } } size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) { size_t i, j, k, end = 0; for (i = 0;i < l->numarrays;i++) { for (j = 0, k = 0;k < l->arrays[i].numflaggedrecords;j++) { if (l->arrays[i].allocflags[j]) { end = l->numrecordsperarray * i + j + 1; k++; } } } return end; } void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) { size_t i, j; i = index / l->numrecordsperarray; j = index % l->numrecordsperarray; if (i >= l->numarrays || !l->arrays[i].allocflags[j]) return NULL; return (void *)(l->arrays[i].data + j * l->recordsize); } // used for temporary memory allocations around the engine, not for longterm // storage, if anything in this pool stays allocated during gameplay, it is // considered a leak mempool_t *tempmempool; // only for zone mempool_t *zonemempool; void Mem_PrintStats(void) { size_t count = 0, size = 0, realsize = 0; mempool_t *pool; memheader_t *mem; Mem_CheckSentinelsGlobal(); for (pool = poolchain;pool;pool = pool->next) { count++; size += pool->totalsize; realsize += pool->realsize; } Con_Printf("%lu memory pools, totalling %lu bytes (%.3fMB)\n", (unsigned long)count, (unsigned long)size, size / 1048576.0); Con_Printf("total allocated size: %lu bytes (%.3fMB)\n", (unsigned long)realsize, realsize / 1048576.0); for (pool = poolchain;pool;pool = pool->next) { if ((pool->flags & POOLFLAG_TEMP) && pool->chain) { Con_Printf("Memory pool %p has sprung a leak totalling %lu bytes (%.3fMB)! Listing contents...\n", (void *)pool, (unsigned long)pool->totalsize, pool->totalsize / 1048576.0); for (mem = pool->chain;mem;mem = mem->next) Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); } } } void Mem_PrintList(size_t minallocationsize) { mempool_t *pool; memheader_t *mem; Mem_CheckSentinelsGlobal(); Con_Print("memory pool list:\n" "size name\n"); for (pool = poolchain;pool;pool = pool->next) { Con_Printf("%10luk (%10luk actual) %s (%+li byte change) %s\n", (unsigned long) ((pool->totalsize + 1023) / 1024), (unsigned long)((pool->realsize + 1023) / 1024), pool->name, (long)(pool->totalsize - pool->lastchecksize), (pool->flags & POOLFLAG_TEMP) ? "TEMP" : ""); pool->lastchecksize = pool->totalsize; for (mem = pool->chain;mem;mem = mem->next) if (mem->size >= minallocationsize) Con_Printf("%10lu bytes allocated at %s:%i\n", (unsigned long)mem->size, mem->filename, mem->fileline); } } static void MemList_f(void) { switch(Cmd_Argc()) { case 1: Mem_PrintList(1<<30); Mem_PrintStats(); break; case 2: Mem_PrintList(atoi(Cmd_Argv(1)) * 1024); Mem_PrintStats(); break; default: Con_Print("MemList_f: unrecognized options\nusage: memlist [all]\n"); break; } } static void MemStats_f(void) { Mem_CheckSentinelsGlobal(); R_TextureStats_Print(false, false, true); GL_Mesh_ListVBOs(false); Mem_PrintStats(); } char* Mem_strdup (mempool_t *pool, const char* s) { char* p; size_t sz; if (s == NULL) return NULL; sz = strlen (s) + 1; p = (char*)Mem_Alloc (pool, sz); strlcpy (p, s, sz); return p; } /* ======================== Memory_Init ======================== */ void Memory_Init (void) { static union {unsigned short s;unsigned char b[2];} u; u.s = 0x100; mem_bigendian = u.b[0] != 0; sentinel_seed = rand(); poolchain = NULL; tempmempool = Mem_AllocPool("Temporary Memory", POOLFLAG_TEMP, NULL); zonemempool = Mem_AllocPool("Zone", 0, NULL); if (Thread_HasThreads()) mem_mutex = Thread_CreateMutex(); } void Memory_Shutdown (void) { // Mem_FreePool (&zonemempool); // Mem_FreePool (&tempmempool); if (mem_mutex) Thread_DestroyMutex(mem_mutex); mem_mutex = NULL; } void Memory_Init_Commands (void) { Cmd_AddCommand ("memstats", MemStats_f, "prints memory system statistics"); Cmd_AddCommand ("memlist", MemList_f, "prints memory pool information (or if used as memlist 5 lists individual allocations of 5K or larger, 0 lists all allocations)"); Cvar_RegisterVariable (&developer_memory); Cvar_RegisterVariable (&developer_memorydebug); Cvar_RegisterVariable (&developer_memoryreportlargerthanmb); Cvar_RegisterVariable (&sys_memsize_physical); Cvar_RegisterVariable (&sys_memsize_virtual); #if defined(WIN32) #ifdef _WIN64 { MEMORYSTATUSEX status; // first guess Cvar_SetValueQuick(&sys_memsize_virtual, 8388608); // then improve status.dwLength = sizeof(status); if(GlobalMemoryStatusEx(&status)) { Cvar_SetValueQuick(&sys_memsize_physical, status.ullTotalPhys / 1048576.0); Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.ullTotalVirtual / 1048576.0)); } } #else { MEMORYSTATUS status; // first guess Cvar_SetValueQuick(&sys_memsize_virtual, 2048); // then improve status.dwLength = sizeof(status); GlobalMemoryStatus(&status); Cvar_SetValueQuick(&sys_memsize_physical, status.dwTotalPhys / 1048576.0); Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value, status.dwTotalVirtual / 1048576.0)); } #endif #else { // first guess Cvar_SetValueQuick(&sys_memsize_virtual, (sizeof(void*) == 4) ? 2048 : 268435456); // then improve { // Linux, and BSD with linprocfs mounted FILE *f = fopen("/proc/meminfo", "r"); if(f) { static char buf[1024]; while(fgets(buf, sizeof(buf), f)) { const char *p = buf; if(!COM_ParseToken_Console(&p)) continue; if(!strcmp(com_token, "MemTotal:")) { if(!COM_ParseToken_Console(&p)) continue; Cvar_SetValueQuick(&sys_memsize_physical, atof(com_token) / 1024.0); } if(!strcmp(com_token, "SwapTotal:")) { if(!COM_ParseToken_Console(&p)) continue; Cvar_SetValueQuick(&sys_memsize_virtual, min(sys_memsize_virtual.value , atof(com_token) / 1024.0 + sys_memsize_physical.value)); } } fclose(f); } } } #endif } darkplaces/darkplaces.dsw0000664000175000017500000000166113067716220015017 0ustar kalevkalevMicrosoft Developer Studio Workspace File, Format Version 6.00 # WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! ############################################################################### Project: "darkplaces"=".\darkplaces.dsp" - Package Owner=<4> Package=<5> {{{ }}} Package=<4> {{{ }}} ############################################################################### Project: "dedicated"=".\darkplaces-dedicated.dsp" - Package Owner=<4> Package=<5> {{{ }}} Package=<4> {{{ }}} ############################################################################### Project: "sdl"=".\darkplaces-sdl.dsp" - Package Owner=<4> Package=<5> {{{ }}} Package=<4> {{{ }}} ############################################################################### Global: Package=<5> {{{ }}} Package=<3> {{{ }}} ############################################################################### darkplaces/darkplaces-sdl-vs2013.vcxproj0000664000175000017500000004461313067716220017435 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51} darkplacessdl Win32Proj darkplaces-sdl-vs2013 Application v120 MultiByte true Application v120 MultiByte Application v120 MultiByte true Application v120 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/host.c0000664000175000017500000012373413067716220013316 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // host.c -- coordinates spawning and killing of local servers #include "quakedef.h" #include #include "libcurl.h" #ifdef CONFIG_CD #include "cdaudio.h" #endif #include "cl_video.h" #include "progsvm.h" #include "csprogs.h" #include "sv_demo.h" #include "snd_main.h" #include "thread.h" #include "utf8lib.h" /* A server can always be started, even if the system started out as a client to a remote system. A client can NOT be started if the system started as a dedicated server. Memory is cleared / released when a server or client begins, not when they end. */ // how many frames have occurred // (checked by Host_Error and Host_SaveConfig_f) int host_framecount = 0; // LordHavoc: set when quit is executed qboolean host_shuttingdown = false; // the accumulated mainloop time since application started (with filtering), without any slowmo or clamping double realtime; // the main loop wall time for this frame double host_dirtytime; // current client client_t *host_client; jmp_buf host_abortframe; // pretend frames take this amount of time (in seconds), 0 = realtime cvar_t host_framerate = {0, "host_framerate","0", "locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed"}; cvar_t cl_maxphysicsframesperserverframe = {0, "cl_maxphysicsframesperserverframe","10", "maximum number of physics frames per server frame"}; // shows time used by certain subsystems cvar_t host_speeds = {0, "host_speeds","0", "reports how much time is used in server/graphics/sound"}; cvar_t host_maxwait = {0, "host_maxwait","1000", "maximum sleep time requested from the operating system in millisecond. Larger sleeps will be done using multiple host_maxwait length sleeps. Lowering this value will increase CPU load, but may help working around problems with accuracy of sleep times."}; cvar_t cl_minfps = {CVAR_SAVE, "cl_minfps", "40", "minimum fps target - while the rendering performance is below this, it will drift toward lower quality"}; cvar_t cl_minfps_fade = {CVAR_SAVE, "cl_minfps_fade", "1", "how fast the quality adapts to varying framerate"}; cvar_t cl_minfps_qualitymax = {CVAR_SAVE, "cl_minfps_qualitymax", "1", "highest allowed drawdistance multiplier"}; cvar_t cl_minfps_qualitymin = {CVAR_SAVE, "cl_minfps_qualitymin", "0.25", "lowest allowed drawdistance multiplier"}; cvar_t cl_minfps_qualitymultiply = {CVAR_SAVE, "cl_minfps_qualitymultiply", "0.2", "multiplier for quality changes in quality change per second render time (1 assumes linearity of quality and render time)"}; cvar_t cl_minfps_qualityhysteresis = {CVAR_SAVE, "cl_minfps_qualityhysteresis", "0.05", "reduce all quality increments by this to reduce flickering"}; cvar_t cl_minfps_qualitystepmax = {CVAR_SAVE, "cl_minfps_qualitystepmax", "0.1", "maximum quality change in a single frame"}; cvar_t cl_minfps_force = {0, "cl_minfps_force", "0", "also apply quality reductions in timedemo/capturevideo"}; cvar_t cl_maxfps = {CVAR_SAVE, "cl_maxfps", "0", "maximum fps cap, 0 = unlimited, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs)"}; cvar_t cl_maxfps_alwayssleep = {0, "cl_maxfps_alwayssleep","1", "gives up some processing time to other applications each frame, value in milliseconds, disabled if cl_maxfps is 0"}; cvar_t cl_maxidlefps = {CVAR_SAVE, "cl_maxidlefps", "20", "maximum fps cap when the game is not the active window (makes cpu time available to other programs"}; cvar_t developer = {CVAR_SAVE, "developer","0", "shows debugging messages and information (recommended for all developers and level designers); the value -1 also suppresses buffering and logging these messages"}; cvar_t developer_extra = {0, "developer_extra", "0", "prints additional debugging messages, often very verbose!"}; cvar_t developer_insane = {0, "developer_insane", "0", "prints huge streams of information about internal workings, entire contents of files being read/written, etc. Not recommended!"}; cvar_t developer_loadfile = {0, "developer_loadfile","0", "prints name and size of every file loaded via the FS_LoadFile function (which is almost everything)"}; cvar_t developer_loading = {0, "developer_loading","0", "prints information about files as they are loaded or unloaded successfully"}; cvar_t developer_entityparsing = {0, "developer_entityparsing", "0", "prints detailed network entities information each time a packet is received"}; cvar_t timestamps = {CVAR_SAVE, "timestamps", "0", "prints timestamps on console messages"}; cvar_t timeformat = {CVAR_SAVE, "timeformat", "[%Y-%m-%d %H:%M:%S] ", "time format to use on timestamped console messages"}; cvar_t sessionid = {CVAR_READONLY, "sessionid", "", "ID of the current session (use the -sessionid parameter to set it); this is always either empty or begins with a dot (.)"}; cvar_t locksession = {0, "locksession", "0", "Lock the session? 0 = no, 1 = yes and abort on failure, 2 = yes and continue on failure"}; /* ================ Host_AbortCurrentFrame aborts the current host frame and goes on with the next one ================ */ void Host_AbortCurrentFrame(void) DP_FUNC_NORETURN; void Host_AbortCurrentFrame(void) { // in case we were previously nice, make us mean again Sys_MakeProcessMean(); longjmp (host_abortframe, 1); } /* ================ Host_Error This shuts down both the client and server ================ */ void Host_Error (const char *error, ...) { static char hosterrorstring1[MAX_INPUTLINE]; // THREAD UNSAFE static char hosterrorstring2[MAX_INPUTLINE]; // THREAD UNSAFE static qboolean hosterror = false; va_list argptr; // turn off rcon redirect if it was active when the crash occurred // to prevent loops when it is a networking problem Con_Rcon_Redirect_Abort(); va_start (argptr,error); dpvsnprintf (hosterrorstring1,sizeof(hosterrorstring1),error,argptr); va_end (argptr); Con_Printf("Host_Error: %s\n", hosterrorstring1); // LordHavoc: if crashing very early, or currently shutting down, do // Sys_Error instead if (host_framecount < 3 || host_shuttingdown) Sys_Error ("Host_Error: %s", hosterrorstring1); if (hosterror) Sys_Error ("Host_Error: recursively entered (original error was: %s new error is: %s)", hosterrorstring2, hosterrorstring1); hosterror = true; strlcpy(hosterrorstring2, hosterrorstring1, sizeof(hosterrorstring2)); CL_Parse_DumpPacket(); CL_Parse_ErrorCleanUp(); //PR_Crash(); // print out where the crash happened, if it was caused by QC (and do a cleanup) PRVM_Crash(SVVM_prog); PRVM_Crash(CLVM_prog); #ifdef CONFIG_MENU PRVM_Crash(MVM_prog); #endif cl.csqc_loaded = false; Cvar_SetValueQuick(&csqc_progcrc, -1); Cvar_SetValueQuick(&csqc_progsize, -1); SV_LockThreadMutex(); Host_ShutdownServer (); SV_UnlockThreadMutex(); if (cls.state == ca_dedicated) Sys_Error ("Host_Error: %s",hosterrorstring2); // dedicated servers exit CL_Disconnect (); cls.demonum = -1; hosterror = false; Host_AbortCurrentFrame(); } static void Host_ServerOptions (void) { int i; // general default svs.maxclients = 8; // COMMANDLINEOPTION: Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 // COMMANDLINEOPTION: Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 // if no client is in the executable or -dedicated is specified on // commandline, start a dedicated server i = COM_CheckParm ("-dedicated"); if (i || !cl_available) { cls.state = ca_dedicated; // check for -dedicated specifying how many players if (i && i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) svs.maxclients = atoi (com_argv[i+1]); if (COM_CheckParm ("-listen")) Con_Printf ("Only one of -dedicated or -listen can be specified\n"); // default sv_public on for dedicated servers (often hosted by serious administrators), off for listen servers (often hosted by clueless users) Cvar_SetValue("sv_public", 1); } else if (cl_available) { // client exists and not dedicated, check if -listen is specified cls.state = ca_disconnected; i = COM_CheckParm ("-listen"); if (i) { // default players unless specified if (i + 1 < com_argc && atoi (com_argv[i+1]) >= 1) svs.maxclients = atoi (com_argv[i+1]); } else { // default players in some games, singleplayer in most if (gamemode != GAME_GOODVSBAD2 && !IS_NEXUIZ_DERIVED(gamemode) && gamemode != GAME_BATTLEMECH) svs.maxclients = 1; } } svs.maxclients = svs.maxclients_next = bound(1, svs.maxclients, MAX_SCOREBOARD); svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); if (svs.maxclients > 1 && !deathmatch.integer && !coop.integer) Cvar_SetValueQuick(&deathmatch, 1); } /* ======================= Host_InitLocal ====================== */ void Host_SaveConfig_f(void); void Host_LoadConfig_f(void); extern cvar_t sv_writepicture_quality; extern cvar_t r_texture_jpeg_fastpicmip; static void Host_InitLocal (void) { Cmd_AddCommand("saveconfig", Host_SaveConfig_f, "save settings to config.cfg (or a specified filename) immediately (also automatic when quitting)"); Cmd_AddCommand("loadconfig", Host_LoadConfig_f, "reset everything and reload configs"); Cvar_RegisterVariable (&cl_maxphysicsframesperserverframe); Cvar_RegisterVariable (&host_framerate); Cvar_RegisterVariable (&host_speeds); Cvar_RegisterVariable (&host_maxwait); Cvar_RegisterVariable (&cl_minfps); Cvar_RegisterVariable (&cl_minfps_fade); Cvar_RegisterVariable (&cl_minfps_qualitymax); Cvar_RegisterVariable (&cl_minfps_qualitymin); Cvar_RegisterVariable (&cl_minfps_qualitystepmax); Cvar_RegisterVariable (&cl_minfps_qualityhysteresis); Cvar_RegisterVariable (&cl_minfps_qualitymultiply); Cvar_RegisterVariable (&cl_minfps_force); Cvar_RegisterVariable (&cl_maxfps); Cvar_RegisterVariable (&cl_maxfps_alwayssleep); Cvar_RegisterVariable (&cl_maxidlefps); Cvar_RegisterVariable (&developer); Cvar_RegisterVariable (&developer_extra); Cvar_RegisterVariable (&developer_insane); Cvar_RegisterVariable (&developer_loadfile); Cvar_RegisterVariable (&developer_loading); Cvar_RegisterVariable (&developer_entityparsing); Cvar_RegisterVariable (×tamps); Cvar_RegisterVariable (&timeformat); Cvar_RegisterVariable (&sv_writepicture_quality); Cvar_RegisterVariable (&r_texture_jpeg_fastpicmip); } /* =============== Host_SaveConfig_f Writes key bindings and archived cvars to config.cfg =============== */ static void Host_SaveConfig_to(const char *file) { qfile_t *f; // dedicated servers initialize the host but don't parse and set the // config.cfg cvars // LordHavoc: don't save a config if it crashed in startup if (host_framecount >= 3 && cls.state != ca_dedicated && !COM_CheckParm("-benchmark") && !COM_CheckParm("-capturedemo")) { f = FS_OpenRealFile(file, "wb", false); if (!f) { Con_Printf("Couldn't write %s.\n", file); return; } Key_WriteBindings (f); Cvar_WriteVariables (f); FS_Close (f); } } void Host_SaveConfig(void) { Host_SaveConfig_to(CONFIGFILENAME); } void Host_SaveConfig_f(void) { const char *file = CONFIGFILENAME; if(Cmd_Argc() >= 2) { file = Cmd_Argv(1); Con_Printf("Saving to %s\n", file); } Host_SaveConfig_to(file); } static void Host_AddConfigText(void) { // set up the default startmap_sp and startmap_dm aliases (mods can // override these) and then execute the quake.rc startup script if (gamemode == GAME_NEHAHRA) Cbuf_InsertText("alias startmap_sp \"map nehstart\"\nalias startmap_dm \"map nehstart\"\nexec " STARTCONFIGFILENAME "\n"); else if (gamemode == GAME_TRANSFUSION) Cbuf_InsertText("alias startmap_sp \"map e1m1\"\n""alias startmap_dm \"map bb1\"\nexec " STARTCONFIGFILENAME "\n"); else if (gamemode == GAME_TEU) Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec teu.rc\n"); else Cbuf_InsertText("alias startmap_sp \"map start\"\nalias startmap_dm \"map start\"\nexec " STARTCONFIGFILENAME "\n"); } /* =============== Host_LoadConfig_f Resets key bindings and cvars to defaults and then reloads scripts =============== */ void Host_LoadConfig_f(void) { // reset all cvars, commands and aliases to init values Cmd_RestoreInitState(); #ifdef CONFIG_MENU // prepend a menu restart command to execute after the config Cbuf_InsertText("\nmenu_restart\n"); #endif // reset cvars to their defaults, and then exec startup scripts again Host_AddConfigText(); } /* ================= SV_ClientPrint Sends text across to be displayed FIXME: make this just a stuffed echo? ================= */ void SV_ClientPrint(const char *msg) { if (host_client->netconnection) { MSG_WriteByte(&host_client->netconnection->message, svc_print); MSG_WriteString(&host_client->netconnection->message, msg); } } /* ================= SV_ClientPrintf Sends text across to be displayed FIXME: make this just a stuffed echo? ================= */ void SV_ClientPrintf(const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); SV_ClientPrint(msg); } /* ================= SV_BroadcastPrint Sends text to all active clients ================= */ void SV_BroadcastPrint(const char *msg) { int i; client_t *client; for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (client->active && client->netconnection) { MSG_WriteByte(&client->netconnection->message, svc_print); MSG_WriteString(&client->netconnection->message, msg); } } if (sv_echobprint.integer && cls.state == ca_dedicated) Con_Print(msg); } /* ================= SV_BroadcastPrintf Sends text to all active clients ================= */ void SV_BroadcastPrintf(const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); SV_BroadcastPrint(msg); } /* ================= Host_ClientCommands Send text over to the client to be executed ================= */ void Host_ClientCommands(const char *fmt, ...) { va_list argptr; char string[MAX_INPUTLINE]; if (!host_client->netconnection) return; va_start(argptr,fmt); dpvsnprintf(string, sizeof(string), fmt, argptr); va_end(argptr); MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); MSG_WriteString(&host_client->netconnection->message, string); } /* ===================== SV_DropClient Called when the player is getting totally kicked off the host if (crash = true), don't bother sending signofs ===================== */ void SV_DropClient(qboolean crash) { prvm_prog_t *prog = SVVM_prog; int i; Con_Printf("Client \"%s\" dropped\n", host_client->name); SV_StopDemoRecording(host_client); // make sure edict is not corrupt (from a level change for example) host_client->edict = PRVM_EDICT_NUM(host_client - svs.clients + 1); if (host_client->netconnection) { // tell the client to be gone if (!crash) { // LordHavoc: no opportunity for resending, so use unreliable 3 times unsigned char bufdata[8]; sizebuf_t buf; memset(&buf, 0, sizeof(buf)); buf.data = bufdata; buf.maxsize = sizeof(bufdata); MSG_WriteByte(&buf, svc_disconnect); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(host_client->netconnection, &buf, sv.protocol, 10000, 0, false); } } // call qc ClientDisconnect function // LordHavoc: don't call QC if server is dead (avoids recursive // Host_Error in some mods when they run out of edicts) if (host_client->clientconnectcalled && sv.active && host_client->edict) { // call the prog function for removing a client // this will set the body to a dead frame, among other things int saveSelf = PRVM_serverglobaledict(self); host_client->clientconnectcalled = false; PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(ClientDisconnect), "QC function ClientDisconnect is missing"); PRVM_serverglobaledict(self) = saveSelf; } if (host_client->netconnection) { // break the net connection NetConn_Close(host_client->netconnection); host_client->netconnection = NULL; } // if a download is active, close it if (host_client->download_file) { Con_DPrintf("Download of %s aborted when %s dropped\n", host_client->download_name, host_client->name); FS_Close(host_client->download_file); host_client->download_file = NULL; host_client->download_name[0] = 0; host_client->download_expectedposition = 0; host_client->download_started = false; } // remove leaving player from scoreboard host_client->name[0] = 0; host_client->colors = 0; host_client->frags = 0; // send notification to all clients // get number of client manually just to make sure we get it right... i = host_client - svs.clients; MSG_WriteByte (&sv.reliable_datagram, svc_updatename); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, host_client->name); MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteByte (&sv.reliable_datagram, host_client->colors); MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteShort (&sv.reliable_datagram, host_client->frags); // free the client now if (host_client->entitydatabase) EntityFrame_FreeDatabase(host_client->entitydatabase); if (host_client->entitydatabase4) EntityFrame4_FreeDatabase(host_client->entitydatabase4); if (host_client->entitydatabase5) EntityFrame5_FreeDatabase(host_client->entitydatabase5); if (sv.active) { // clear a fields that matter to DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS, and also frags PRVM_ED_ClearEdict(prog, host_client->edict); } // clear the client struct (this sets active to false) memset(host_client, 0, sizeof(*host_client)); // update server listing on the master because player count changed // (which the master uses for filtering empty/full servers) NetConn_Heartbeat(1); if (sv.loadgame) { for (i = 0;i < svs.maxclients;i++) if (svs.clients[i].active && !svs.clients[i].spawned) break; if (i == svs.maxclients) { Con_Printf("Loaded game, everyone rejoined - unpausing\n"); sv.paused = sv.loadgame = false; // we're basically done with loading now } } } /* ================== Host_ShutdownServer This only happens at the end of a game, not between levels ================== */ void Host_ShutdownServer(void) { prvm_prog_t *prog = SVVM_prog; int i; Con_DPrintf("Host_ShutdownServer\n"); if (!sv.active) return; NetConn_Heartbeat(2); NetConn_Heartbeat(2); // make sure all the clients know we're disconnecting World_End(&sv.world); if(prog->loaded) { if(PRVM_serverfunction(SV_Shutdown)) { func_t s = PRVM_serverfunction(SV_Shutdown); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); } } for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if (host_client->active) SV_DropClient(false); // server shutdown NetConn_CloseServerPorts(); sv.active = false; // // clear structures // memset(&sv, 0, sizeof(sv)); memset(svs.clients, 0, svs.maxclients*sizeof(client_t)); cl.islocalgame = false; } //============================================================================ /* =================== Host_GetConsoleCommands Add them exactly as if they had been typed at the console =================== */ static void Host_GetConsoleCommands (void) { char *cmd; while (1) { cmd = Sys_ConsoleInput (); if (!cmd) break; Cbuf_AddText (cmd); } } /* ================== Host_TimeReport Returns a time report string, for example for ================== */ const char *Host_TimingReport(char *buf, size_t buflen) { return va(buf, buflen, "%.1f%% CPU, %.2f%% lost, offset avg %.1fms, max %.1fms, sdev %.1fms", svs.perf_cpuload * 100, svs.perf_lost * 100, svs.perf_offset_avg * 1000, svs.perf_offset_max * 1000, svs.perf_offset_sdev * 1000); } /* ================== Host_Frame Runs all active servers ================== */ static void Host_Init(void); void Host_Main(void) { double time1 = 0; double time2 = 0; double time3 = 0; double cl_timer = 0, sv_timer = 0; double clframetime, deltacleantime, olddirtytime, dirtytime; double wait; int pass1, pass2, pass3, i; char vabuf[1024]; qboolean playing; Host_Init(); realtime = 0; host_dirtytime = Sys_DirtyTime(); for (;;) { if (setjmp(host_abortframe)) { SCR_ClearLoadingScreen(false); continue; // something bad happened, or the server disconnected } olddirtytime = host_dirtytime; dirtytime = Sys_DirtyTime(); deltacleantime = dirtytime - olddirtytime; if (deltacleantime < 0) { // warn if it's significant if (deltacleantime < -0.01) Con_Printf("Host_Mingled: time stepped backwards (went from %f to %f, difference %f)\n", olddirtytime, dirtytime, deltacleantime); deltacleantime = 0; } else if (deltacleantime >= 1800) { Con_Printf("Host_Mingled: time stepped forward (went from %f to %f, difference %f)\n", olddirtytime, dirtytime, deltacleantime); deltacleantime = 0; } realtime += deltacleantime; host_dirtytime = dirtytime; cl_timer += deltacleantime; sv_timer += deltacleantime; if (!svs.threaded) { svs.perf_acc_realtime += deltacleantime; // Look for clients who have spawned playing = false; for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if(host_client->begun) if(host_client->netconnection) playing = true; if(sv.time < 10) { // don't accumulate time for the first 10 seconds of a match // so things can settle svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; } else if(svs.perf_acc_realtime > 5) { svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; if(svs.perf_acc_offset_samples > 0) { svs.perf_offset_max = svs.perf_acc_offset_max; svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); } if(svs.perf_lost > 0 && developer_extra.integer) if(playing) // only complain if anyone is looking Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; } } if (slowmo.value < 0.00001 && slowmo.value != 0) Cvar_SetValue("slowmo", 0); if (host_framerate.value < 0.00001 && host_framerate.value != 0) Cvar_SetValue("host_framerate", 0); // keep the random time dependent, but not when playing demos/benchmarking if(!*sv_random_seed.string && !cls.demoplayback) rand(); // get new key events Key_EventQueue_Unblock(); SndSys_SendKeyEvents(); Sys_SendKeyEvents(); NetConn_UpdateSockets(); Log_DestBuffer_Flush(); // receive packets on each main loop iteration, as the main loop may // be undersleeping due to select() detecting a new packet if (sv.active && !svs.threaded) NetConn_ServerFrame(); Curl_Run(); // check for commands typed to the host Host_GetConsoleCommands(); // when a server is running we only execute console commands on server frames // (this mainly allows frikbot .way config files to work properly by staying in sync with the server qc) // otherwise we execute them on client frames if (sv.active ? sv_timer > 0 : cl_timer > 0) { // process console commands // R_TimeReport("preconsole"); CL_VM_PreventInformationLeaks(); Cbuf_Frame(); // R_TimeReport("console"); } //Con_Printf("%6.0f %6.0f\n", cl_timer * 1000000.0, sv_timer * 1000000.0); // if the accumulators haven't become positive yet, wait a while if (cls.state == ca_dedicated) wait = sv_timer * -1000000.0; else if (!sv.active || svs.threaded) wait = cl_timer * -1000000.0; else wait = max(cl_timer, sv_timer) * -1000000.0; if (!cls.timedemo && wait >= 1) { double time0, delta; if(host_maxwait.value <= 0) wait = min(wait, 1000000.0); else wait = min(wait, host_maxwait.value * 1000.0); if(wait < 1) wait = 1; // because we cast to int time0 = Sys_DirtyTime(); if (sv_checkforpacketsduringsleep.integer && !sys_usenoclockbutbenchmark.integer && !svs.threaded) { NetConn_SleepMicroseconds((int)wait); if (cls.state != ca_dedicated) NetConn_ClientFrame(); // helps server browser get good ping values // TODO can we do the same for ServerFrame? Probably not. } else Sys_Sleep((int)wait); delta = Sys_DirtyTime() - time0; if (delta < 0 || delta >= 1800) delta = 0; if (!svs.threaded) svs.perf_acc_sleeptime += delta; // R_TimeReport("sleep"); continue; } // limit the frametime steps to no more than 100ms each if (cl_timer > 0.1) cl_timer = 0.1; if (sv_timer > 0.1) { if (!svs.threaded) svs.perf_acc_lost += (sv_timer - 0.1); sv_timer = 0.1; } R_TimeReport("---"); //------------------- // // server operations // //------------------- // limit the frametime steps to no more than 100ms each if (sv.active && sv_timer > 0 && !svs.threaded) { // execute one or more server frames, with an upper limit on how much // execution time to spend on server frames to avoid freezing the game if // the server is overloaded, this execution time limit means the game will // slow down if the server is taking too long. int framecount, framelimit = 1; double advancetime, aborttime = 0; float offset; prvm_prog_t *prog = SVVM_prog; // run the world state // don't allow simulation to run too fast or too slow or logic glitches can occur // stop running server frames if the wall time reaches this value if (sys_ticrate.value <= 0) advancetime = sv_timer; else if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) { // synchronize to the client frametime, but no less than 10ms and no more than 100ms advancetime = bound(0.01, cl_timer, 0.1); } else { advancetime = sys_ticrate.value; // listen servers can run multiple server frames per client frame framelimit = cl_maxphysicsframesperserverframe.integer; aborttime = Sys_DirtyTime() + 0.1; } if(slowmo.value > 0 && slowmo.value < 1) advancetime = min(advancetime, 0.1 / slowmo.value); else advancetime = min(advancetime, 0.1); if(advancetime > 0) { offset = Sys_DirtyTime() - dirtytime;if (offset < 0 || offset >= 1800) offset = 0; offset += sv_timer; ++svs.perf_acc_offset_samples; svs.perf_acc_offset += offset; svs.perf_acc_offset_squared += offset * offset; if(svs.perf_acc_offset_max < offset) svs.perf_acc_offset_max = offset; } // only advance time if not paused // the game also pauses in singleplayer when menu or console is used sv.frametime = advancetime * slowmo.value; if (host_framerate.value) sv.frametime = host_framerate.value; if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) sv.frametime = 0; for (framecount = 0;framecount < framelimit && sv_timer > 0;framecount++) { sv_timer -= advancetime; // move things around and think unless paused if (sv.frametime) SV_Physics(); // if this server frame took too long, break out of the loop if (framelimit > 1 && Sys_DirtyTime() >= aborttime) break; } R_TimeReport("serverphysics"); // send all messages to the clients SV_SendClientMessages(); if (sv.paused == 1 && realtime > sv.pausedstart && sv.pausedstart > 0) { prog->globals.fp[OFS_PARM0] = realtime - sv.pausedstart; PRVM_serverglobalfloat(time) = sv.time; prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); } // send an heartbeat if enough time has passed since the last one NetConn_Heartbeat(0); R_TimeReport("servernetwork"); } else if (!svs.threaded) { // don't let r_speeds display jump around R_TimeReport("serverphysics"); R_TimeReport("servernetwork"); } //------------------- // // client operations // //------------------- if (cls.state != ca_dedicated && (cl_timer > 0 || cls.timedemo || ((vid_activewindow ? cl_maxfps : cl_maxidlefps).value < 1))) { R_TimeReport("---"); Collision_Cache_NewFrame(); R_TimeReport("photoncache"); // decide the simulation time if (cls.capturevideo.active) { //*** if (cls.capturevideo.realtime) clframetime = cl.realframetime = max(cl_timer, 1.0 / cls.capturevideo.framerate); else { clframetime = 1.0 / cls.capturevideo.framerate; cl.realframetime = max(cl_timer, clframetime); } } else if (vid_activewindow && cl_maxfps.value >= 1 && !cls.timedemo) { clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxfps.value); // when running slow, we need to sleep to keep input responsive wait = bound(0, cl_maxfps_alwayssleep.value * 1000, 100000); if (wait > 0) Sys_Sleep((int)wait); } else if (!vid_activewindow && cl_maxidlefps.value >= 1 && !cls.timedemo) clframetime = cl.realframetime = max(cl_timer, 1.0 / cl_maxidlefps.value); else clframetime = cl.realframetime = cl_timer; // apply slowmo scaling clframetime *= cl.movevars_timescale; // scale playback speed of demos by slowmo cvar if (cls.demoplayback) { clframetime *= slowmo.value; // if demo playback is paused, don't advance time at all if (cls.demopaused) clframetime = 0; } else { // host_framerate overrides all else if (host_framerate.value) clframetime = host_framerate.value; if (cl.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) clframetime = 0; } if (cls.timedemo) clframetime = cl.realframetime = cl_timer; // deduct the frame time from the accumulator cl_timer -= cl.realframetime; cl.oldtime = cl.time; cl.time += clframetime; // update video if (host_speeds.integer) time1 = Sys_DirtyTime(); R_TimeReport("pre-input"); // Collect input into cmd CL_Input(); R_TimeReport("input"); // check for new packets NetConn_ClientFrame(); // read a new frame from a demo if needed CL_ReadDemoMessage(); R_TimeReport("clientnetwork"); // now that packets have been read, send input to server CL_SendMove(); R_TimeReport("sendmove"); // update client world (interpolate entities, create trails, etc) CL_UpdateWorld(); R_TimeReport("lerpworld"); CL_Video_Frame(); R_TimeReport("client"); CL_UpdateScreen(); R_TimeReport("render"); if (host_speeds.integer) time2 = Sys_DirtyTime(); // update audio if(cl.csqc_usecsqclistener) { S_Update(&cl.csqc_listenermatrix); cl.csqc_usecsqclistener = false; } else S_Update(&r_refdef.view.matrix); #ifdef CONFIG_CD CDAudio_Update(); R_TimeReport("audio"); #endif // reset gathering of mouse input in_mouse_x = in_mouse_y = 0; if (host_speeds.integer) { pass1 = (int)((time1 - time3)*1000000); time3 = Sys_DirtyTime(); pass2 = (int)((time2 - time1)*1000000); pass3 = (int)((time3 - time2)*1000000); Con_Printf("%6ius total %6ius server %6ius gfx %6ius snd\n", pass1+pass2+pass3, pass1, pass2, pass3); } } #if MEMPARANOIA Mem_CheckSentinelsGlobal(); #else if (developer_memorydebug.integer) Mem_CheckSentinelsGlobal(); #endif // if there is some time remaining from this frame, reset the timers if (cl_timer >= 0) cl_timer = 0; if (sv_timer >= 0) { if (!svs.threaded) svs.perf_acc_lost += sv_timer; sv_timer = 0; } host_framecount++; } } //============================================================================ qboolean vid_opened = false; void Host_StartVideo(void) { if (!vid_opened && cls.state != ca_dedicated) { vid_opened = true; // make sure we open sockets before opening video because the Windows Firewall "unblock?" dialog can screw up the graphics context on some graphics drivers NetConn_UpdateSockets(); VID_Start(); #ifdef CONFIG_CD CDAudio_Startup(); #endif } } char engineversion[128]; qboolean sys_nostdout = false; extern qboolean host_stuffcmdsrun; static qfile_t *locksession_fh = NULL; static qboolean locksession_run = false; static void Host_InitSession(void) { int i; Cvar_RegisterVariable(&sessionid); Cvar_RegisterVariable(&locksession); // load the session ID into the read-only cvar if ((i = COM_CheckParm("-sessionid")) && (i + 1 < com_argc)) { char vabuf[1024]; if(com_argv[i+1][0] == '.') Cvar_SetQuick(&sessionid, com_argv[i+1]); else Cvar_SetQuick(&sessionid, va(vabuf, sizeof(vabuf), ".%s", com_argv[i+1])); } } void Host_LockSession(void) { if(locksession_run) return; locksession_run = true; if(locksession.integer != 0 && !COM_CheckParm("-readonly")) { char vabuf[1024]; char *p = va(vabuf, sizeof(vabuf), "%slock%s", *fs_userdir ? fs_userdir : fs_basedir, sessionid.string); FS_CreatePath(p); locksession_fh = FS_SysOpen(p, "wl", false); // TODO maybe write the pid into the lockfile, while we are at it? may help server management tools if(!locksession_fh) { if(locksession.integer == 2) { Con_Printf("WARNING: session lock %s could not be acquired. Please run with -sessionid and an unique session name. Continuing anyway.\n", p); } else { Sys_Error("session lock %s could not be acquired. Please run with -sessionid and an unique session name.\n", p); } } } } void Host_UnlockSession(void) { if(!locksession_run) return; locksession_run = false; if(locksession_fh) { FS_Close(locksession_fh); // NOTE: we can NOT unlink the lock here, as doing so would // create a race condition if another process created it // between our close and our unlink locksession_fh = NULL; } } /* ==================== Host_Init ==================== */ static void Host_Init (void) { int i; const char* os; char vabuf[1024]; if (COM_CheckParm("-profilegameonly")) Sys_AllowProfiling(false); // LordHavoc: quake never seeded the random number generator before... heh if (COM_CheckParm("-benchmark")) srand(0); // predictable random sequence for -benchmark else srand((unsigned int)time(NULL)); // FIXME: this is evil, but possibly temporary // LordHavoc: doesn't seem very temporary... // LordHavoc: made this a saved cvar // COMMANDLINEOPTION: Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) if (COM_CheckParm("-developer")) { developer.value = developer.integer = 1; developer.string = "1"; } if (COM_CheckParm("-developer2") || COM_CheckParm("-developer3")) { developer.value = developer.integer = 1; developer.string = "1"; developer_extra.value = developer_extra.integer = 1; developer_extra.string = "1"; developer_insane.value = developer_insane.integer = 1; developer_insane.string = "1"; developer_memory.value = developer_memory.integer = 1; developer_memory.string = "1"; developer_memorydebug.value = developer_memorydebug.integer = 1; developer_memorydebug.string = "1"; } if (COM_CheckParm("-developer3")) { gl_paranoid.integer = 1;gl_paranoid.string = "1"; gl_printcheckerror.integer = 1;gl_printcheckerror.string = "1"; } // COMMANDLINEOPTION: Console: -nostdout disables text output to the terminal the game was launched from if (COM_CheckParm("-nostdout")) sys_nostdout = 1; // used by everything Memory_Init(); // initialize console command/cvar/alias/command execution systems Cmd_Init(); // initialize memory subsystem cvars/commands Memory_Init_Commands(); // initialize console and logging and its cvars/commands Con_Init(); // initialize various cvars that could not be initialized earlier u8_Init(); Curl_Init_Commands(); Cmd_Init_Commands(); Sys_Init_Commands(); COM_Init_Commands(); FS_Init_Commands(); // initialize console window (only used by sys_win.c) Sys_InitConsole(); // initialize the self-pack (must be before COM_InitGameType as it may add command line options) FS_Init_SelfPack(); // detect gamemode from commandline options or executable name COM_InitGameType(); // construct a version string for the corner of the console os = DP_OS_NAME; dpsnprintf (engineversion, sizeof (engineversion), "%s %s %s", gamename, os, buildstring); Con_Printf("%s\n", engineversion); // initialize process nice level Sys_InitProcessNice(); // initialize ixtable Mathlib_Init(); // initialize filesystem (including fs_basedir, fs_gamedir, -game, scr_screenshot_name) FS_Init(); // register the cvars for session locking Host_InitSession(); // must be after FS_Init Crypto_Init(); Crypto_Init_Commands(); NetConn_Init(); Curl_Init(); //PR_Init(); //PR_Cmd_Init(); PRVM_Init(); Mod_Init(); World_Init(); SV_Init(); V_Init(); // some cvars needed by server player physics (cl_rollangle etc) Host_InitCommands(); Host_InitLocal(); Host_ServerOptions(); Thread_Init(); if (cls.state == ca_dedicated) Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); else { Con_DPrintf("Initializing client\n"); R_Modules_Init(); Palette_Init(); #ifdef CONFIG_MENU MR_Init_Commands(); #endif VID_Shared_Init(); VID_Init(); Render_Init(); S_Init(); #ifdef CONFIG_CD CDAudio_Init(); #endif Key_Init(); CL_Init(); } // save off current state of aliases, commands and cvars for later restore if FS_GameDir_f is called // NOTE: menu commands are freed by Cmd_RestoreInitState Cmd_SaveInitState(); // FIXME: put this into some neat design, but the menu should be allowed to crash // without crashing the whole game, so this should just be a short-time solution // here comes the not so critical stuff if (setjmp(host_abortframe)) { return; } Host_AddConfigText(); Cbuf_Execute(); // if stuffcmds wasn't run, then quake.rc is probably missing, use default if (!host_stuffcmdsrun) { Cbuf_AddText("exec default.cfg\nexec " CONFIGFILENAME "\nexec autoexec.cfg\nstuffcmds\n"); Cbuf_Execute(); } // put up the loading image so the user doesn't stare at a black screen... SCR_BeginLoadingPlaque(true); #ifdef CONFIG_MENU if (cls.state != ca_dedicated) { MR_Init(); } #endif // check for special benchmark mode // COMMANDLINEOPTION: Client: -benchmark runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) i = COM_CheckParm("-benchmark"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", com_argv[i + 1])); Cbuf_Execute(); } // check for special demo mode // COMMANDLINEOPTION: Client: -demo runs a playdemo and quits i = COM_CheckParm("-demo"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\n", com_argv[i + 1])); Cbuf_Execute(); } // COMMANDLINEOPTION: Client: -capturedemo captures a playdemo and quits i = COM_CheckParm("-capturedemo"); if (i && i + 1 < com_argc) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText(va(vabuf, sizeof(vabuf), "playdemo %s\ncl_capturevideo 1\n", com_argv[i + 1])); Cbuf_Execute(); } if (cls.state == ca_dedicated || COM_CheckParm("-listen")) if (!sv.active && !cls.demoplayback && !cls.connect_trying) { Cbuf_AddText("startmap_dm\n"); Cbuf_Execute(); } if (!sv.active && !cls.demoplayback && !cls.connect_trying) { #ifdef CONFIG_MENU Cbuf_AddText("togglemenu 1\n"); #endif Cbuf_Execute(); } Con_DPrint("========Initialized=========\n"); //Host_StartVideo(); if (cls.state != ca_dedicated) SV_StartThread(); } /* =============== Host_Shutdown FIXME: this is a callback from Sys_Quit and Sys_Error. It would be better to run quit through here before the final handoff to the sys code. =============== */ void Host_Shutdown(void) { static qboolean isdown = false; if (isdown) { Con_Print("recursive shutdown\n"); return; } if (setjmp(host_abortframe)) { Con_Print("aborted the quitting frame?!?\n"); return; } isdown = true; // be quiet while shutting down S_StopAllSounds(); // end the server thread if (svs.threaded) SV_StopThread(); // disconnect client from server if active CL_Disconnect(); // shut down local server if active SV_LockThreadMutex(); Host_ShutdownServer (); SV_UnlockThreadMutex(); #ifdef CONFIG_MENU // Shutdown menu if(MR_Shutdown) MR_Shutdown(); #endif // AK shutdown PRVM // AK hmm, no PRVM_Shutdown(); yet CL_Video_Shutdown(); Host_SaveConfig(); #ifdef CONFIG_CD CDAudio_Shutdown (); #endif S_Terminate (); Curl_Shutdown (); NetConn_Shutdown (); //PR_Shutdown (); if (cls.state != ca_dedicated) { R_Modules_Shutdown(); VID_Shutdown(); } SV_StopThread(); Thread_Shutdown(); Cmd_Shutdown(); Key_Shutdown(); CL_Shutdown(); Sys_Shutdown(); Log_Close(); Crypto_Shutdown(); Host_UnlockSession(); S_Shutdown(); Con_Shutdown(); Memory_Shutdown(); } darkplaces/cd_null.c0000664000175000017500000000251513067716216013757 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "cdaudio.h" void CDAudio_SysEject (void) { } void CDAudio_SysCloseDoor (void) { } int CDAudio_SysGetAudioDiskInfo (void) { return -1; } float CDAudio_SysGetVolume (void) { return -1.0f; } void CDAudio_SysSetVolume (float fvolume) { } int CDAudio_SysPlay (int track) { return -1; } int CDAudio_SysStop (void) { return -1; } int CDAudio_SysPause (void) { return -1; } int CDAudio_SysResume (void) { return -1; } int CDAudio_SysUpdate (void) { return -1; } void CDAudio_SysInit (void) { } int CDAudio_SysStartup (void) { return -1; } void CDAudio_SysShutdown (void) { } darkplaces/qtypes.h0000664000175000017500000000215613067716222013667 0ustar kalevkalev #ifndef QTYPES_H #define QTYPES_H #undef true #undef false #ifndef __cplusplus typedef enum qboolean_e {false, true} qboolean; #else typedef bool qboolean; #endif #ifndef NULL #define NULL ((void *)0) #endif #ifndef FALSE #define FALSE false #define TRUE true #endif // up / down #define PITCH 0 // left / right #define YAW 1 // fall over #define ROLL 2 #if defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1400) #define RESTRICT __restrict #else #define RESTRICT #endif // LordHavoc: upgrade the prvm to double precision for better time values // LordHavoc: to be enabled when bugs are worked out... //#define PRVM_64 #ifdef PRVM_64 typedef double prvm_vec_t; typedef long long prvm_int_t; typedef unsigned long long prvm_uint_t; #else typedef float prvm_vec_t; typedef int prvm_int_t; typedef unsigned int prvm_uint_t; #endif typedef prvm_vec_t prvm_vec3_t[3]; #ifdef VEC_64 typedef double vec_t; #else typedef float vec_t; #endif typedef vec_t vec2_t[2]; typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; typedef vec_t vec5_t[5]; typedef vec_t vec6_t[6]; typedef vec_t vec7_t[7]; typedef vec_t vec8_t[8]; #endif darkplaces/darkplaces-dedicated.dev0000664000175000017500000005147213067716220016711 0ustar kalevkalev[Project] FileName=darkplaces-dedicated.dev Name=DarkPlaces UnitCount=165 Type=1 Ver=1 ObjFiles= Includes= Libs= PrivateResource=darkplaces-dedicated_private.rc ResourceIncludes= MakeIncludes= Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@_ CppCompiler= Linker=-lwinmm -lws2_32_@@_ IsCpp=0 Icon=darkplaces.ico ExeOutput= ObjectOutput= OverrideOutput=1 OverrideOutputName=darkplaces-dedicated.exe HostApplication= Folders="Header Files","Source Files" CommandLine= UseCustomMakefile=0 CustomMakefile= IncludeVersionInfo=1 SupportXPThemes=0 CompilerSet=0 CompilerSettings=0000000000000000000100 [Unit1] FileName=dpvsimpledecode.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit2] FileName=cdaudio.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit3] FileName=cl_collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit4] FileName=cl_screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit5] FileName=cl_video.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit6] FileName=client.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit7] FileName=clprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit8] FileName=cmd.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit9] FileName=collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit10] FileName=common.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit11] FileName=console.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit12] FileName=curves.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit13] FileName=cvar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit16] FileName=fs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit17] FileName=gl_backend.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit18] FileName=polygon.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit19] FileName=glquake.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit20] FileName=image.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit21] FileName=input.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit22] FileName=jpeg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit23] FileName=keys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit24] FileName=lhnet.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit25] FileName=mathlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit26] FileName=matrixlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit27] FileName=menu.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit28] FileName=meshqueue.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit29] FileName=model_alias.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit30] FileName=model_brush.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit31] FileName=model_shared.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit32] FileName=model_sprite.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit33] FileName=model_zymotic.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit34] FileName=modelgen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit35] FileName=mprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit36] FileName=netconn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit37] FileName=palette.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit38] FileName=portals.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit39] FileName=pr_comp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit40] FileName=progdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit41] FileName=progs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit42] FileName=progsvm.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit43] FileName=protocol.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit44] FileName=prvm_execprogram.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit45] FileName=qtypes.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit46] FileName=quakedef.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit47] FileName=r_lerpanim.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit48] FileName=r_modules.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit49] FileName=r_shadow.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit50] FileName=r_textures.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit51] FileName=render.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit52] FileName=resource.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit53] FileName=sbar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit54] FileName=screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit55] FileName=server.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit56] FileName=sound.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit57] FileName=spritegn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit58] FileName=sys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit59] FileName=vid.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit60] FileName=wad.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit62] FileName=zone.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit63] FileName=zone.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit64] FileName=cd_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit65] FileName=cd_null.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit66] FileName=cl_collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit67] FileName=cl_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit68] FileName=cl_input.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit69] FileName=cl_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit70] FileName=cl_parse.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit71] FileName=cl_particles.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit72] FileName=cl_screen.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit73] FileName=cl_video.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit74] FileName=cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit75] FileName=collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit76] FileName=common.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit77] FileName=console.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit78] FileName=polygon.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit79] FileName=curves.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit80] FileName=cvar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit81] FileName=dpvsimpledecode.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit82] FileName=filematch.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit83] FileName=fractalnoise.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit84] FileName=fs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit85] FileName=gl_backend.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit86] FileName=gl_draw.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit87] FileName=gl_rmain.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit88] FileName=gl_rsurf.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit89] FileName=gl_textures.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit91] FileName=host_cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit92] FileName=image.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit93] FileName=jpeg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit94] FileName=keys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit95] FileName=lhnet.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit96] FileName=mathlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit97] FileName=matrixlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit98] FileName=menu.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit99] FileName=meshqueue.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit100] FileName=model_alias.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit101] FileName=model_brush.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit102] FileName=model_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit103] FileName=model_sprite.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit104] FileName=netconn.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit105] FileName=palette.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit106] FileName=portals.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit107] FileName=protocol.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit108] FileName=prvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit109] FileName=prvm_edict.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit110] FileName=prvm_exec.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit111] FileName=builddate.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit113] FileName=r_lerpanim.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit114] FileName=r_lightning.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit115] FileName=r_modules.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit116] FileName=r_shadow.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit118] FileName=r_sprites.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit119] FileName=sbar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit120] FileName=snd_null.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit121] FileName=sv_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit122] FileName=sv_move.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit123] FileName=sv_phys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit124] FileName=sv_user.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit125] FileName=sys_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit126] FileName=sys_linux.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit127] FileName=vid_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit128] FileName=vid_null.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit133] FileName=image_png.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit134] FileName=lhfont.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit135] FileName=mdfour.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit136] FileName=model_dpmodel.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit137] FileName=model_psk.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit139] FileName=csprogs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit140] FileName=mdfour.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit141] FileName=image_png.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit143] FileName=wad.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit145] FileName=cl_dyntexture.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit148] FileName=cl_gecko.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit149] FileName=clvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit150] FileName=libcurl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit151] FileName=libcurl.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit152] FileName=sv_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit153] FileName=sv_demo.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit154] FileName=svbsp.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit155] FileName=svbsp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit156] FileName=timing.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit157] FileName=hmac.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit112] FileName=r_explosion.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [VersionInfo] Major=1 Minor=0 Release=0 Build=0 LanguageID=1033 CharsetID=1252 CompanyName=Forest Hale Digital Services FileVersion=1.0 FileDescription=DarkPlaces Game Engine InternalName=darkplaces.exe LegalCopyright=id Software, Forest Hale, and contributors LegalTrademarks= OriginalFilename=darkplaces.exe ProductName=DarkPlaces ProductVersion=1.0 AutoIncBuildNr=0 [Unit90] FileName=host.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit138] FileName=clvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit14] FileName=bspfile.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit15] FileName=draw.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit61] FileName=world.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit117] FileName=r_sky.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit158] FileName=hmac.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit159] FileName=cap_avi.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit160] FileName=cap_avi.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit161] FileName=cap_ogg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit162] FileName=cap_ogg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit163] FileName=utf8lib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit164] FileName=ft2.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit165] FileName=bih.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit129] FileName=svvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit130] FileName=mvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit131] FileName=prvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit132] FileName=csprogs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit144] FileName=world.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit146] FileName=cl_dyntexture.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit147] FileName=cl_gecko.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit142] FileName=view.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= darkplaces/vid.h0000664000175000017500000002340413067716222013123 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // vid.h -- video driver defs #ifndef VID_H #define VID_H #define ENGINE_ICON ( (gamemode == GAME_NEXUIZ) ? nexuiz_xpm : darkplaces_xpm ) extern int cl_available; #define MAX_TEXTUREUNITS 16 typedef enum renderpath_e { RENDERPATH_GL11, RENDERPATH_GL13, RENDERPATH_GL20, RENDERPATH_D3D9, RENDERPATH_D3D10, RENDERPATH_D3D11, RENDERPATH_SOFT, RENDERPATH_GLES1, RENDERPATH_GLES2 } renderpath_t; typedef struct viddef_support_s { qboolean gl20shaders; qboolean gl20shaders130; // indicates glBindFragDataLocation is available int glshaderversion; // typical values: 100 110 120 130 140 ... qboolean amd_texture_texture4; qboolean arb_depth_texture; qboolean arb_draw_buffers; qboolean arb_framebuffer_object; qboolean arb_multitexture; qboolean arb_occlusion_query; qboolean arb_query_buffer_object; qboolean arb_shadow; qboolean arb_texture_compression; qboolean arb_texture_cube_map; qboolean arb_texture_env_combine; qboolean arb_texture_gather; qboolean arb_texture_non_power_of_two; qboolean arb_vertex_buffer_object; qboolean arb_uniform_buffer_object; qboolean ati_separate_stencil; qboolean ext_blend_minmax; qboolean ext_blend_subtract; qboolean ext_blend_func_separate; qboolean ext_draw_range_elements; qboolean ext_framebuffer_object; qboolean ext_packed_depth_stencil; qboolean ext_stencil_two_side; qboolean ext_texture_3d; qboolean ext_texture_compression_s3tc; qboolean ext_texture_edge_clamp; qboolean ext_texture_filter_anisotropic; qboolean ext_texture_srgb; qboolean arb_texture_float; qboolean arb_half_float_pixel; qboolean arb_half_float_vertex; qboolean arb_multisample; } viddef_support_t; typedef struct viddef_mode_s { int width; int height; int bitsperpixel; qboolean fullscreen; float refreshrate; qboolean userefreshrate; qboolean stereobuffer; int samples; } viddef_mode_t; typedef struct viddef_s { // these are set by VID_Mode viddef_mode_t mode; // used in many locations in the renderer int width; int height; int bitsperpixel; qboolean fullscreen; float refreshrate; qboolean userefreshrate; qboolean stereobuffer; int samples; qboolean stencil; qboolean sRGB2D; // whether 2D rendering is sRGB corrected (based on sRGBcapable2D) qboolean sRGB3D; // whether 3D rendering is sRGB corrected (based on sRGBcapable3D) qboolean sRGBcapable2D; // whether 2D rendering can be sRGB corrected (renderpath, v_hwgamma) qboolean sRGBcapable3D; // whether 3D rendering can be sRGB corrected (renderpath, v_hwgamma) renderpath_t renderpath; qboolean forcevbo; // some renderpaths can not operate without it qboolean useinterleavedarrays; // required by some renderpaths qboolean allowalphatocoverage; // indicates the GL_AlphaToCoverage function works on this renderpath and framebuffer unsigned int texunits; unsigned int teximageunits; unsigned int texarrayunits; unsigned int drawrangeelements_maxvertices; unsigned int drawrangeelements_maxindices; unsigned int maxtexturesize_2d; unsigned int maxtexturesize_3d; unsigned int maxtexturesize_cubemap; unsigned int max_anisotropy; unsigned int maxdrawbuffers; viddef_support_t support; // in RENDERPATH_SOFT this is a 32bpp native-endian ARGB framebuffer // (native-endian ARGB meaning that in little endian it is BGRA bytes, // in big endian it is ARGB byte order, the format is converted during // blit to the window) unsigned int *softpixels; unsigned int *softdepthpixels; int forcetextype; // always use GL_BGRA for D3D, always use GL_RGBA for GLES, etc } viddef_t; // global video state extern viddef_t vid; extern void (*vid_menudrawfn)(void); extern void (*vid_menukeyfn)(int key); #define MAXJOYAXIS 16 // if this is changed, the corresponding code in vid_shared.c must be updated #define MAXJOYBUTTON 36 typedef struct vid_joystate_s { float axis[MAXJOYAXIS]; // -1 to +1 unsigned char button[MAXJOYBUTTON]; // 0 or 1 qboolean is360; // indicates this joystick is a Microsoft Xbox 360 Controller For Windows } vid_joystate_t; extern vid_joystate_t vid_joystate; extern cvar_t joy_index; extern cvar_t joy_enable; extern cvar_t joy_detected; extern cvar_t joy_active; float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float sensitivity, float deadzone); void VID_ApplyJoyState(vid_joystate_t *joystate); void VID_BuildJoyState(vid_joystate_t *joystate); void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate); void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate); int VID_Shared_SetJoystick(int index); qboolean VID_JoyBlockEmulatedKeys(int keycode); void VID_EnableJoystick(qboolean enable); extern qboolean vid_hidden; extern qboolean vid_activewindow; extern cvar_t vid_hardwaregammasupported; extern qboolean vid_usinghwgamma; extern qboolean vid_supportrefreshrate; extern cvar_t vid_soft; extern cvar_t vid_soft_threads; extern cvar_t vid_soft_interlace; extern cvar_t vid_fullscreen; extern cvar_t vid_width; extern cvar_t vid_height; extern cvar_t vid_bitsperpixel; extern cvar_t vid_samples; extern cvar_t vid_refreshrate; extern cvar_t vid_userefreshrate; extern cvar_t vid_touchscreen_density; extern cvar_t vid_touchscreen_xdpi; extern cvar_t vid_touchscreen_ydpi; extern cvar_t vid_vsync; extern cvar_t vid_mouse; extern cvar_t vid_grabkeyboard; extern cvar_t vid_touchscreen; extern cvar_t vid_touchscreen_showkeyboard; extern cvar_t vid_touchscreen_supportshowkeyboard; extern cvar_t vid_stick_mouse; extern cvar_t vid_resizable; extern cvar_t vid_desktopfullscreen; extern cvar_t vid_minwidth; extern cvar_t vid_minheight; extern cvar_t vid_sRGB; extern cvar_t vid_sRGB_fallback; extern cvar_t gl_finish; extern cvar_t v_gamma; extern cvar_t v_contrast; extern cvar_t v_brightness; extern cvar_t v_color_enable; extern cvar_t v_color_black_r; extern cvar_t v_color_black_g; extern cvar_t v_color_black_b; extern cvar_t v_color_grey_r; extern cvar_t v_color_grey_g; extern cvar_t v_color_grey_b; extern cvar_t v_color_white_r; extern cvar_t v_color_white_g; extern cvar_t v_color_white_b; extern cvar_t v_hwgamma; // brand of graphics chip extern const char *gl_vendor; // graphics chip model and other information extern const char *gl_renderer; // begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 extern const char *gl_version; // extensions list, space separated extern const char *gl_extensions; // WGL, GLX, or AGL extern const char *gl_platform; // another extensions list, containing platform-specific extensions that are // not in the main list extern const char *gl_platformextensions; // name of driver library (opengl32.dll, libGL.so.1, or whatever) extern char gl_driver[256]; void *GL_GetProcAddress(const char *name); qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent); void VID_Shared_Init(void); void GL_Init (void); void VID_ClearExtensions(void); void VID_CheckExtensions(void); void VID_Init (void); // Called at startup void VID_Shutdown (void); // Called at shutdown int VID_SetMode (int modenum); // sets the mode; only used by the Quake engine for resetting to mode 0 (the // base mode) on memory allocation failures qboolean VID_InitMode(viddef_mode_t *mode); // allocates and opens an appropriate OpenGL context (and its window) // sets hardware gamma correction, returns false if the device does not // support gamma control // (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) int VID_SetGamma(unsigned short *ramps, int rampsize); // gets hardware gamma correction, returns false if the device does not // support gamma control // (ONLY called by VID_UpdateGamma and VID_RestoreSystemGamma) int VID_GetGamma(unsigned short *ramps, int rampsize); // makes sure ramp arrays are big enough and calls VID_GetGamma/VID_SetGamma // (ONLY to be called from VID_Finish!) void VID_UpdateGamma(qboolean force, int rampsize); // turns off hardware gamma ramps immediately // (called from various shutdown/deactivation functions) void VID_RestoreSystemGamma(void); qboolean VID_HasScreenKeyboardSupport(void); void VID_ShowKeyboard(qboolean show); qboolean VID_ShowingKeyboard(void); void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor); void VID_Finish (void); void VID_Restart_f(void); void VID_Start(void); void VID_Stop(void); extern unsigned int vid_gammatables_serial; // so other subsystems can poll if gamma parameters have changed; this starts with 0 and gets increased by 1 each time the gamma parameters get changed and VID_BuildGammaTables should be called again extern qboolean vid_gammatables_trivial; // this is set to true if all color control values are at default setting, and it therefore would make no sense to use the gamma table void VID_BuildGammaTables(unsigned short *ramps, int rampsize); // builds the current gamma tables into an array (needs 3*rampsize items) typedef struct { int width, height, bpp, refreshrate; int pixelheight_num, pixelheight_denom; } vid_mode_t; vid_mode_t *VID_GetDesktopMode(void); size_t VID_ListModes(vid_mode_t *modes, size_t maxcount); size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect); void VID_Soft_SharedSetup(void); #endif darkplaces/snd_3dras.c0000664000175000017500000010150313067716222014211 0ustar kalevkalev// BSD #include "quakedef.h" #include "snd_3dras_typedefs.h" #include "snd_3dras.h" cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "1", "master volume"}; cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; static dllhandle_t ras_dll = NULL; // This values is used as a multiple version support check AND to check if the lib has been loaded with success (its 0 if it failed) int ras_version; static mempool_t *snd_mempool; static sfx_t sfx_list ={//This is a header, never contains only useful data, only the first next (makes the code less complex, later) NULL, //next "", //name[MAX_QPATH]; NULL, //sounddata 0, //locks 0 //flags //0, //loopstart, //0 //total_length }; static unsigned int channel_id_count=0; static channel_t channel_list={ NULL, //next NULL, //soundevent 0, //entnum 0, //entchannel 0 //id }; static entnum_t entnum_list={ NULL,// *next; 0, // entnum; {0.0,0.0,0.0}, //lastloc NULL,// *soundsource;// This is also used to indicate a unused slot (when it's pointing to 0) }; int updatecount=0; int soundblocked=0; int openframe; void* soundworld; void* listener; float listener_location [3]; //1 qu = 0.0381 meter aka 38.1 mm //2^17 qu's is the max map size in DP //3DRAS uses atleast 32 bit to store it's locations. //So the smallest possible step is 0.0381*2^17/2^(32) // =~ 1.16 nm so let's pick 2 to be safe static float DP_Ras_UnitSize=(float)2/1000/1000; //2 nm static float DP_Ras_VolumeScale=0.075; //static float QU_Size = 0.0381; //meter //static float DP_QU_Ras_Scale=QU_Size/DP_Ras_UnitSize; static float DP_QU_Ras_Scale=19050; static void* (*ras_delete )(void*); static int (*ras_getversion )(); static void* (*ras_soundworld_new )(SampleRate, WaveLength); static void (*ras_soundworld_destroy )(void*); static void (*ras_soundworld_endframe )(void*); static int (*ras_soundworld_beginframe )(void*); static void (*ras_soundworld_setmainlistener )(void*,void*); static void (*ras_soundworld_setscale )(void*,const Scale); static void* (*ras_fileinputwhole_new )(unsigned char*, Index); static void* (*ras_audiodecoderwav_new )(void*, int); static void* (*ras_audiodecoderogg_new )(void*); static void* (*ras_sounddataoneshot_new )(void*,WaveLength,Amount); static void* (*ras_sounddataloop_new )(void*,WaveLength,Amount); static void* (*ras_listener_new )(void*,Location*,Scalar*); static void* (*ras_listener_setlocation )(void*,Location*); static void* (*ras_listener_setrotation )(void*,Scalar *,Scalar*,Scalar*); static void* (*ras_soundsource_new )(void*,SoundVolume,Location*); static int (*ras_soundsource_ended )(void*); static void (*ras_soundsource_setlocation )(void*,Location*); static void* (*ras_soundevent_new )(void*,void*,void*,SoundPower,Ratio); static void (*ras_soundevent_setsoundpower )(void*,SoundPower); static int (*ras_soundevent_ended )(void*); static int (*ras_setcoordinatesystem )(Location*,Location*,Location*); static int (*ras_testrotation )(Scalar *,Scalar *,Scalar *); // #define RAS_PRINT //Comment out for to not print extra crap. static dllfunction_t ras_funcs[] = { {"Delete" ,(void**) &ras_delete }, {"SetCoordinateSystem" ,(void**) &ras_setcoordinatesystem }, {"TestRotation" ,(void**) &ras_testrotation }, {"GetVersion" ,(void**) &ras_getversion }, {"SoundWorld_New" ,(void**) &ras_soundworld_new }, {"SoundWorld_Destroy" ,(void**) &ras_soundworld_destroy }, {"SoundWorld_EndFrame" ,(void**) &ras_soundworld_endframe }, {"SoundWorld_BeginFrame" ,(void**) &ras_soundworld_beginframe }, {"FileInputWhile_New" ,(void**) &ras_fileinputwhole_new }, {"AudioDecoderFileWav_New" ,(void**) &ras_audiodecoderwav_new }, {"AudioDecoderFileOgg_New" ,(void**) &ras_audiodecoderogg_new }, {"SoundDataAudioDecoderOneShot_New" ,(void**) &ras_sounddataoneshot_new }, //{"SoundDataAudioDecoderFileLoop_New" ,(void**) &ras_sounddataloop_new }, {"SoundWorld_SetMainListener" ,(void**) &ras_soundworld_setmainlistener }, {"SoundWorld_SetScale" ,(void**) &ras_soundworld_setscale }, {"ListenerPlayer_New" ,(void**) &ras_listener_new }, {"ListenerPlayer_SetLocation" ,(void**) &ras_listener_setlocation }, {"ListenerPlayer_SetRotation_InVectors" ,(void**) &ras_listener_setrotation }, {"SoundSource_Ended" ,(void**) &ras_soundsource_ended }, {"SoundSourcePoint_New" ,(void**) &ras_soundsource_new }, {"SoundSourcePoint_SetLocation" ,(void**) &ras_soundsource_setlocation }, {"SoundEvent_New" ,(void**) &ras_soundevent_new }, {"SoundEvent_Ended" ,(void**) &ras_soundevent_ended }, {"SoundEvent_SetSoundPower" ,(void**) &ras_soundevent_setsoundpower }, { NULL , NULL } }; static const char* ras_dllname [] = { #if defined(WIN32) "3dras32.dll", #elif defined(MACOSX) "3dras.dylib", #else "3dras.so", #endif NULL }; // --- entnum_t List functions ---- void entnum_new(entnum_t** prev, entnum_t** new){ //Adds a new item to the start of the list and sets the pointers. (*new)=Mem_Alloc(snd_mempool,sizeof(entnum_t)); if(&new){ (*new)->next=entnum_list.next; entnum_list.next=(*new); (*prev)=&entnum_list; }else{ Con_Printf("Could not allocate memory for a new entnum_t"); } } void entnum_begin(entnum_t** prev, entnum_t** now){ //Goes to the beginning of the list and sets the pointers. (*prev)=&entnum_list; (*now )=entnum_list.next; } void entnum_next(entnum_t** prev, entnum_t** now){ //Goes to the next element (*prev)=(*now); (*now )=(*now)->next; } void entnum_delete_and_next(entnum_t** prev, entnum_t** now){ //Deletes the element and goes to the next element entnum_t* next; next=(*now)->next; if((*now)->rasptr) ras_delete((*now)->rasptr); Mem_Free(*now); (*now)=next; (*prev)->next=(*now); } // --- End Of entnum_t List functions ---- // --- channel_t List functions ---- void channel_new(channel_t** prev, channel_t** new){ //Adds a new item to the start of the list and sets the pointers. (*new)=Mem_Alloc(snd_mempool,sizeof(channel_t)); if(&new){ (*new)->next=channel_list.next; channel_list.next=(*new); (*prev)=&channel_list; }else{ Con_Printf("Could not allocate memory for a new channel_t"); } } void channel_begin(channel_t** prev, channel_t** now){ //Goes to the beginning of the list and sets the pointers. (*prev)=&channel_list; (*now )=channel_list.next; } void channel_next(channel_t** prev, channel_t** now){ //Goes to the next element (*prev)=(*now ); (*now )=(*now)->next; } void channel_delete_and_next(channel_t** prev, channel_t** now){ //Deletes the element and goes to the next element channel_t* next; next=(*now)->next; if((*now)->rasptr) ras_delete((*now)->rasptr); Mem_Free(*now); (*now)=next; (*prev)->next=(*now); } // --- End Of channel_t List functions ---- // --- sfx_t List functions ---- void sfx_new(sfx_t** prev, sfx_t** new){ //Adds a new item to the start of the list and sets the pointers. (*new)=Mem_Alloc(snd_mempool,sizeof(sfx_t)); if(&new){ (*new)->next=sfx_list.next; sfx_list.next=(*new); (*prev)=&sfx_list; }else{ Con_Printf("Could not allocate memory for a new sfx_t"); } } void sfx_begin(sfx_t** prev, sfx_t** now){ //Goes to the beginning of the list and sets the pointers. (*prev)=&sfx_list; (*now )=sfx_list.next; } void sfx_next(sfx_t** prev, sfx_t** now){ //Goes to the next element (*prev)=(*now ); (*now )=(*now)->next; } void sfx_delete_and_next(sfx_t** prev, sfx_t** now){ //Deletes the element and goes to the next element sfx_t* next; next=(*now)->next; if((*now)->rasptr) ras_delete((*now)->rasptr); Mem_Free(*now); (*now)=next; (*prev)->next=(*now); } // --- End Of sfx_t List functions ---- void channel_new_smart(channel_t** prev, channel_t** now){ channel_new(prev,now); ++channel_id_count; (*now)->id=channel_id_count; } void Free_Unlocked_Sfx(void){ sfx_t *prev, *now; sfx_begin(&prev,&now); while(now){ if( !(now->flags & SFXFLAG_SERVERSOUND) && now->locks<=0 ){ sfx_delete_and_next(&prev,&now); }else{ sfx_next(&prev,&now); } } } void DP_To_Ras_Location(const float in[3],Location out[3]){ out[0]=(Location)(in[0]*DP_QU_Ras_Scale ); out[1]=(Location)(in[1]*DP_QU_Ras_Scale ); out[2]=(Location)(in[2]*DP_QU_Ras_Scale ); } void S_Restart_f(void){ S_Shutdown(); S_Startup(); } static void S_Play_Common (float fvol, float attenuation){ int i; char name [MAX_QPATH]; sfx_t *sfx; if(ras_version>0 && ras_dll){ #ifdef RAS_PRINT Con_Printf("Called S_Play_Common\n"); Con_Printf("Does this need to be location in depend channel ?\n"); #endif i = 1; while (i < Cmd_Argc ()) { // Get the name, and appends ".wav" as an extension if there's none strlcpy (name, Cmd_Argv (i), sizeof (name)); if (!strrchr (name, '.')) strlcat (name, ".wav", sizeof (name)); i++; // If we need to get the volume from the command line if (fvol == -1.0f) { fvol = atof (Cmd_Argv (i)); i++; } sfx = S_PrecacheSound (name, true, true); if (sfx) S_StartSound (-1, 0, sfx, listener_location, fvol, attenuation); } } } static void S_Play_f(void){ S_Play_Common (1.0f, 1.0f); } static void S_Play2_f(void){ S_Play_Common (1.0f, 0.0f); } static void S_PlayVol_f(void){ S_Play_Common (-1.0f, 0.0f); } static void S_SoundList_f(void){ channel_t *prev_c, *now_c; sfx_t *prev_s, *now_s; entnum_t *prev_e, *now_e; int count_c,count_s,count_e; if(ras_version>0 && ras_dll){ Con_Printf("Sfx (SoundDatas) :\n" "------------------\n" "Locks\tflags\tpointer\tName\n"); count_s=0; sfx_begin(&prev_s,&now_s); while(now_s){ ++count_s; Con_Printf("%i\t%i\t%i\t%s\n", now_s->locks, now_s->flags, now_s->rasptr!=NULL, now_s->name ); sfx_next(&prev_s,&now_s); } Con_Printf("Entnum (SoundSources) :\n" "-----------------------\n" "Ent\tpointer\n"); count_e=0; entnum_begin(&prev_e,&now_e); while(now_e){ ++count_e; Con_Printf("%i\t%i\n", now_e->entnum, now_e->rasptr!=NULL ); entnum_next(&prev_e,&now_e); } Con_Printf("Channels (SoundEvents) :\n" "------------------------\n" "Ent\tChannel\tID\tpointer\n"); count_c=0; channel_begin(&prev_c,&now_c); while(now_c){ ++count_c; Con_Printf("%i\t%i\t%i\t%i\n", now_c->entnum, now_c->entchannel, now_c->id, now_c->rasptr!=NULL ); channel_next(&prev_c,&now_c); } Con_Printf( "Count:\n" "------\n" "Channels: %i\n" "Sfx's: %i\n" "Entities: %i\n", count_c,count_s,count_e ); } } void Free_All_sfx(){ sfx_t *prev, *now; sfx_begin(&prev,&now); while(now) sfx_delete_and_next(&prev,&now); } void Free_All_channel(){ channel_t *prev, *now; channel_begin(&prev,&now); while(now) channel_delete_and_next(&prev,&now); } void Free_All_entnum(){ entnum_t *prev, *now; entnum_begin(&prev,&now); while(now) entnum_delete_and_next(&prev,&now); } void S_Init (void){ Location up[3],right[3],front[3]; ras_version=0; snd_mempool = Mem_AllocPool("sound", 0, NULL); if(ras_dll) Con_Printf( "3D RAS already loaded ... (this indicates a bug)\n"); if (Sys_LoadLibrary (ras_dllname, &ras_dll, ras_funcs)) { Con_Printf ("Loading 3D RAS succeeded\n"); Con_Printf ("Checking the lib version\n"); ras_version=ras_getversion(); if (ras_version>0){ Con_Printf ("Version %i found\n",ras_version); Cvar_RegisterVariable(&volume); Cvar_RegisterVariable(&bgmvolume); Cvar_RegisterVariable(&mastervolume); Cvar_RegisterVariable(&snd_staticvolume); Cvar_RegisterVariable(&snd_precache); Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); Cmd_AddCommand("snd_play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); Cmd_AddCommand("snd_play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); Cmd_AddCommand("snd_playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); Cmd_AddCommand("snd_stopsound", S_StopAllSounds, "silence"); Cmd_AddCommand("snd_soundlist", S_SoundList_f, "list loaded sounds"); Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); Cmd_AddCommand("snd_shutdown", S_Shutdown, "shutdown the sound system keeping the dll loaded"); Cmd_AddCommand("snd_startup", S_Startup, "start the sound system"); Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); //Set the coordinate system inside the lib equal to the one inside dp: up[0]= 0 , up[1]= 0 , up[2]=1; right[0]= 0 ,right[1]=-1 , right[2]=0; front[0]= 1 ,front[1]= 0 , front[2]=0; if(ras_setcoordinatesystem(right,up,front)==0){ Con_Printf("Failed to set the Coordinate System\n"); ras_version=0; } }else{ Con_Printf ("Failed to get the lib version\n"); Sys_UnloadLibrary (&ras_dll); ras_dll=0; } }else{ ras_dll=0; Con_Printf ("Loading 3D RAS failed\n"); Sys_UnloadLibrary (&ras_dll); } } void S_Terminate (void){ if(ras_dll){ S_Shutdown(); Free_All_sfx(); // <= The only valid place to free the sfx. Sys_UnloadLibrary(&ras_dll); ras_dll=0; ras_version=0; } } void S_Startup (void){ Location loc[3]={0, 0, 0}; Scalar rot[4]={1.0, 0, 0, 0}; if(ras_version>0 && ras_dll){ channel_id_count=1; soundworld= ras_soundworld_new(48000,0.1); if(soundworld==0){ Con_Printf("Failed to start a SoundWorld\n"); }else{ Con_Printf("Succeeded in starting a new SoundWorld\n"); listener=ras_listener_new(soundworld,loc,rot); ras_soundworld_setmainlistener(soundworld,listener); openframe = ras_soundworld_beginframe(soundworld); ras_soundworld_setscale(soundworld,DP_Ras_UnitSize); } } } void S_Shutdown (void){ if(ras_version>0 && ras_dll && soundworld){ if(openframe) ras_soundworld_endframe(soundworld); //Order doesn't really matter because the lib takes care of the references //Free_All_sfx(); <= DO NOT FREE SFX ... they just keep sending in the old sfx causing havoc. Free_All_channel(); Free_All_entnum(); ras_soundworld_destroy(soundworld); soundworld=ras_delete(soundworld); if(soundworld){ Con_Printf("Failed to stop the SoundWorld\n"); }else{ Con_Printf("Succeeded in stopping the SoundWorld\n"); } } } void S_UnloadAllSounds_f(void){ if(ras_version>0 && ras_dll){ Free_All_sfx(); } } void S_Update(const matrix4x4_t *listener_matrix){ float forward [3]; float left [3]; float up [3]; float float3 [3]; Location location3 [3]; entnum_t *prev_e, *now_e; channel_t *prev_c, *now_c; if(ras_version>0 && ras_dll && soundworld){ Matrix4x4_ToVectors(listener_matrix,forward,left,up,listener_location); //Add the new player location. if(openframe){ VectorNegate(left,left); DP_To_Ras_Location(listener_location,location3); ras_listener_setlocation(listener,location3); ras_listener_setrotation(listener,left,up,forward); /* Con_Printf( "DP: Left={%f|%f|%f} Up={%f|%f|%f} Front={%f|%f|%f}\n", left[0], left[1], left[2], up[0], up[1], up[2], forward[0],forward[1],forward[2] ); ras_testrotation(left,up,forward); Con_Printf( "RAS: Left={%f|%f|%f} Up={%f|%f|%f} Front={%f|%f|%f}\n", left[0], left[1], left[2], up[0], up[1], up[2], forward[0],forward[1],forward[2] ); */ if(updatecount>100){ updatecount=0; #ifdef RAS_PRINT Con_Printf("S_Update: Add a callback to SCR_CaptureVideo_SoundFrame.\n"); Con_Printf("S_Update: Add Slomo.\n"); Con_Printf("S_Update: Add BlockedSoundCheck.\n"); Con_Printf("S_Update: Add Slomo(as a cvar) and pauze.\n"); #endif }else{ ++updatecount; } //(15:20:31) div0: (at the moment, you can extend it to multichannel) //(15:20:40) div0: see S_CaptureAVISound() if(cl.entities){ //if there is a list of ents //Update all entities there position into the sound sources. entnum_begin(&prev_e,&now_e); while(now_e){ if(!now_e->rasptr){ Con_Printf("S_Update: Found an entnum_t without a valid RAS-ptr... This indicates a bug.\n"); entnum_delete_and_next(&prev_e,&now_e); }else{ //Look for unused ent and drop them. if(now_e->entnum!=-1){ //Talking about an ent ? Or a static sound source ? if(ras_soundsource_ended(now_e->rasptr)){ VectorCopy(cl.entities[now_e->entnum].state_current.origin,float3); VectorCopy(now_e->lastloc,float3); DP_To_Ras_Location(float3,location3); ras_soundsource_setlocation(now_e->rasptr,location3); entnum_next(&prev_e,&now_e); }else{ entnum_delete_and_next(&prev_e,&now_e); } }else{ if(ras_soundsource_ended(now_e->rasptr)){ entnum_delete_and_next(&prev_e,&now_e); }else{ entnum_next(&prev_e,&now_e); } } } } }else{ Free_All_entnum(); } channel_begin(&prev_c,&now_c); while(now_c){ if(!now_c->rasptr){ Con_Printf("S_Update: Found an channel_t without a valid RAS-ptr... This indicates a bug.\n"); channel_delete_and_next(&prev_c,&now_c); }else{ //Look for stopped sound channels and free them if(ras_soundevent_ended(now_c->rasptr)){ channel_delete_and_next(&prev_c,&now_c); }else{ channel_next(&prev_c,&now_c); } } } ras_soundworld_endframe (soundworld); } openframe =ras_soundworld_beginframe(soundworld); } } void S_ExtraUpdate (void){ // This lib is unable to use any extra updates. //if(ras_version>0 && ras_dll){ //} } sfx_t* S_FindName (const char *name){ sfx_t *prev,*now; if(ras_version>0 && ras_dll){ #ifdef RAS_PRINT Con_Printf("Called S_FindName %s\n",name); #endif if (strlen (name) >= sizeof (now->name)) { Con_Printf ("S_FindName: sound name too long (%s)\n", name); return NULL; } sfx_begin(&prev,&now); // Seek in list while (now){ if(strcmp (now->name, name)==0) return now; sfx_next(&prev,&now); } // None found in the list, // Add a sfx_t struct for this sound sfx_new(&prev,&now); now->locks=0; now->flags=0; now->rasptr=0; //sfx->looptstart=0; //sfx->total_length=0; strlcpy (now->name, name, sizeof (now->name)); return now; } return NULL; } int S_LoadSound(sfx_t *sfx, int complain){ // TODO add SCR_PushLoadingScreen, SCR_PopLoadingScreen calls to this fs_offset_t filesize; char namebuffer[MAX_QPATH +16 ]; char filename [MAX_QPATH +16+4]; char fileext [4]; size_t len; unsigned char *data=NULL; void* file_ptr=NULL; void* decoder_ptr=NULL; if(ras_version>0 && ras_dll){ fileext[3]=0; //Terminator // See if already loaded if (sfx->rasptr) return true; // LordHavoc: if the sound filename does not begin with sound/, try adding it if (!data && strncasecmp(sfx->name, "sound/", 6)) { len = dpsnprintf (namebuffer, sizeof(namebuffer), "sound/%s", sfx->name); if (len < 0){ // name too long Con_DPrintf("S_LoadSound: name \"%s\" is too long\n", sfx->name); return false; } if(!data){ data = FS_LoadFile(namebuffer, snd_mempool, false, &filesize); if(data) memcpy(fileext,namebuffer+len-3,3); //Copy the extention } if(!data){ //Stick .wav to the end and try again memcpy(filename,namebuffer,len); memcpy(filename+len-4,".wav",5); data = FS_LoadFile(filename, snd_mempool, false, &filesize); if(data) memcpy(fileext,"wav",3); } if(!data){ //Stick .ogg to the end and try again memcpy(filename,namebuffer,len); memcpy(filename+len-4,".ogg",5); data = FS_LoadFile(filename, snd_mempool, false, &filesize); if(data) memcpy(fileext,"ogg",3); } } if(!data){ // LordHavoc: then try without the added sound/ as wav and ogg len = dpsnprintf (namebuffer, sizeof(namebuffer), "%s", sfx->name); if (len < 0){ // name too long Con_DPrintf("S_LoadSound: name \"%s\" is too long\n", sfx->name); return false; } if(!data){ data = FS_LoadFile(namebuffer, snd_mempool, false, &filesize); if(data) memcpy(fileext,namebuffer+len-3,3); //Copy the file extention } if(!data){ //Stick .wav to the end memcpy(filename,namebuffer,len); memcpy(filename+len-4,".wav",5); data = FS_LoadFile(filename, snd_mempool, false, &filesize); if(data) memcpy(fileext,"wav",3); } if(!data){ //Stick .ogg to the end memcpy(filename,namebuffer,len); memcpy(filename+len-4,".ogg",5); data = FS_LoadFile(filename, snd_mempool, false, &filesize); if(data) memcpy(fileext,"ogg",3); } } if (!data){ if(complain) Con_Printf("Failed attempt load file '%s'\n",namebuffer); }else{ //if the file loaded: pass to RAS 3D file_ptr=ras_fileinputwhole_new(data,filesize); // There we transfered to file to RAS 3D // So lets free up data shall we ? FS_Close(data); if(!file_ptr){ Con_Printf("Failed to upload file to audio lib\n"); }else{ if(0==strncasecmp(fileext,"wav",3)){ decoder_ptr=ras_audiodecoderwav_new(file_ptr,true); //(true)use seek mode: some quake files are broken. } if(0==strncasecmp(fileext,"ogg",3)){ decoder_ptr=ras_audiodecoderogg_new(file_ptr); } if(!decoder_ptr){ Con_Printf("File succeeded to load, but no decoder available for '%s'\n",fileext); }else{ #ifdef RAS_PRINT Con_Printf("ToDo: Add a cvar to configure the cache size and number of cache blocks.\n"); Con_Printf("ToDo: Add support for looping sounds.\n"); #endif sfx->rasptr=ras_sounddataoneshot_new(decoder_ptr,0.05,8); } file_ptr=ras_delete(file_ptr); } return !(sfx->rasptr); } return false; } return false; } sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean serversound){ sfx_t *sfx; if(ras_version>0 && ras_dll){ #ifdef RAS_PRINT Con_Printf("Called S_PrecacheSound %s, %i, %i\n",name,complain,serversound); #endif if (name == NULL || name[0] == 0) return NULL; sfx = S_FindName (name); if (sfx == NULL) return NULL; if (lock) ++(sfx->locks); if (snd_precache.integer) S_LoadSound(sfx, complain); return sfx; } return NULL; } void S_ClearUsed (void){ sfx_t *prev_s, *now_s; unsigned int i; if(ras_version>0 && ras_dll){ Con_Printf("Called S_ClearUsed\n"); for(i=0;iflags & SFXFLAG_SERVERSOUND) now_s->flags &= ~SFXFLAG_SERVERSOUND; sfx_next(&prev_s,&now_s); } } } } void S_PurgeUnused(void){ Free_Unlocked_Sfx(); } qboolean S_IsSoundPrecached (const sfx_t *sfx){ if(ras_version>0 && ras_dll){ return !sfx->rasptr; } return 0; } void S_KillChannel (channel_t *now){ //Silences a SoundEvent if(now->rasptr){ ras_soundevent_setsoundpower(now->rasptr,0); ras_delete(now->rasptr); now->rasptr=0; }else{ Con_Printf("S_KillChannel: Warning pointer was 0 ... this indicates a bug.\n"); } } int S_StartSound_OnEnt (int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation){ entnum_t *prev_e, *now_e; channel_t *prev_c, *now_c; Location tmp_location[3]; //If there is a game world if(!cl.entities){ Con_Printf("S_StartSound_OnEnt: no entity list exists\n"); return -1; } // Look for the correct ent_t entnum_begin(&prev_e,&now_e); while(now_e){ if(now_e->entnum==entnum) break; entnum_next(&prev_e,&now_e); } //We found no ent ... lets make one... if(!now_e){ entnum_new(&prev_e,&now_e); if(!now_e){ Con_Printf("S_StartSound_OnEnt: could not make new entnum_t\n"); return -1; } VectorCopy(cl.entities[entnum].state_current.origin, now_e->lastloc); DP_To_Ras_Location(now_e->lastloc,tmp_location); now_e->rasptr=ras_soundsource_new(soundworld,1.0,tmp_location); if(!now_e->rasptr){ Con_Printf("S_StartSound_OnEnt: could not create a new sound source\n"); return -1; } } //Ok now lets look for the channel. channel_begin(&prev_c,&now_c); while(now_c){ if( now_c->entnum==entnum && now_c->entchannel==entchannel ) break; channel_next(&prev_c,&now_c); } if(now_c){ //O dear the channel excists .... S_KillChannel(now_c); }else{ //We found no channel .... So we need to make a new one ... channel_new_smart(&prev_c,&now_c); if(!now_c){ Con_Printf("S_StartSound_OnEnt: could not make new channel_t\n"); channel_delete_and_next(&prev_c,&now_c); return -1; } now_c->entnum =entnum; now_c->entchannel=entchannel; } //Lets start the sound on the acquired sound source and channel now_c->rasptr=ras_soundevent_new( soundworld,now_e->rasptr,sfx->rasptr,fvol*DP_Ras_VolumeScale,1.0 ); if(!now_c->rasptr){ //Whoops, failed, lets delete this channel then. channel_delete_and_next(&prev_c,&now_c); Con_Printf("S_StartSound_OnEnt: could not make a new soundevent.\n"); return -1; } return now_c->id; } int S_StartSound_OnLocation (sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ entnum_t *prev_e, *now_e; channel_t *prev_c, *now_c; Location tmp_location[3]; DP_To_Ras_Location(origin,tmp_location); entnum_new (&prev_e,&now_e); VectorCopy(now_e->lastloc,origin); now_e->entnum=-1; now_e->rasptr=ras_soundsource_new(soundworld,1.0,tmp_location); if(!now_e->rasptr){ Con_Printf("S_StartSound_OnLocation: Could not make a new soundsource.\n"); entnum_delete_and_next(&prev_e,&now_e); return -1; } channel_new_smart(&prev_c,&now_c); now_c->entnum=-1; now_c->entchannel=-1; now_c->rasptr =ras_soundevent_new(soundworld,now_e->rasptr,sfx->rasptr,fvol*DP_Ras_VolumeScale,1.0); if(!now_c->rasptr){ entnum_delete_and_next(&prev_e,&now_e); channel_delete_and_next(&prev_c,&now_c); Con_Printf("S_StartSound_OnLocation: Could not make a new soundevent.\n"); return -1; } return now_c->id; } // Qantourisc on the wicked-quake-sound-system: // -------------------------------------------- // entnum can be Zero or lower => This means "use the origin" so it's not tracked. // entnum -1 is a "world" containing more then 1 soundsource. // If channel != 0 try to overwrite the requested channel. Otherwise play it on some random channel. // If channel == -1 overwrite the first track of the ent. // If a channel replacement is requested, only allow overwriting if it's owned by the same channel. // If no channel can be replaced, pick a new one. // Also when you overwrite a channel, that one has to stop dead. // This function returns the channel it was played on (so it can be stopped later) // This starts CD-music: S_StartSound (-1, 0, sfx, vec3_origin, cdvolume, 0); // The channel you return then, can later be requested to be stopped. int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ sfx_t *prev_s,*now_s; int sfx_ok; if(ras_version>0 && ras_dll && soundworld){ #ifdef RAS_PRINT Con_Printf("Called S_StartSound %i, %i, %f, %f\n",entnum,entchannel,fvol,attenuation); #endif if(sfx==NULL){ // They pass this to me ... but WHY ? it makes no sense ! #ifdef RAS_PRINT Con_Printf("S_StartSound: forgot to mention a sfx!\n"); #endif return -1; } sfx_ok=0; sfx_begin(&prev_s,&now_s); while(now_s){ if(now_s==sfx){ sfx_ok=1; break; } sfx_next(&prev_s,&now_s); } if(!sfx_ok){ Con_Printf("S_StartSound: passed illegal sfx_t!\n"); return -1; } if (!S_LoadSound(sfx,true)) return -1; if(entnum!=-1){ //If we are talking about an ent return S_StartSound_OnEnt(entnum,entchannel,sfx,fvol,attenuation); }else{ return S_StartSound_OnLocation( sfx,origin,fvol,attenuation); } } Con_Printf("S_StartSound: engine not stated\n"); return -1; } qboolean S_LocalSound (const char *s){ sfx_t *sfx; int ch_ind; if(ras_version>0 && ras_dll){ #ifdef RAS_PRINT Con_Printf("Called S_LocalSound %s\n",s); #endif sfx = S_PrecacheSound (s, true, true); if (!sfx) { Con_Printf("S_LocalSound: can't precache %s\n", s); return false; } // Local sounds must not be freed sfx->flags |= SFXFLAG_PERMANENTLOCK; #ifdef RAS_PRINT Con_Printf("S_LocalSound: this is still a small hack\n"); #endif ch_ind = S_StartSound (cl.viewentity, 0, sfx, listener_location, 1, 0); if (ch_ind < 0) return false; //channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; return true; } return 0; } void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation){ //Static sounds should not be looped if(ras_version>0 && ras_dll){ #ifdef RAS_PRINT Con_Printf("Called S_StaticSound\n"); Con_Printf("Waiting on Qantourisc to add Static sounds in his lib.\n"); #endif //Static sounds are sounds that are not pauzed, and or played locally. } } void S_StopSound (int entnum, int entchannel){ channel_t *prev, *now; if(ras_version>0 && ras_dll){ //Con_Printf("Called S_StopSound %i, %i\n",entnum,entchannel); channel_begin(&prev,&now); while(now){ if(now->entnum==entnum && now->entchannel==entchannel) break; channel_next(&prev,&now); } if(now){ //If we found our to delete sound. S_KillChannel(now); channel_delete_and_next(&prev,&now); }else{ Con_Printf("S_StopSound: Could not find the requested entnum-entchannel sound\n"); } } } void S_StopAllSounds (void){ channel_t *prev, *now; if(ras_version>0 && ras_dll){ //Con_Printf("Called S_StopAllSounds\n"); channel_begin(&prev,&now); while(now){ S_KillChannel(now); channel_next(&prev,&now); } } } void S_PauseGameSounds (qboolean toggle){ if(ras_version>0 && ras_dll){ Con_Printf("Called S_PauseGameSounds %i\n",toggle); //Localsounds should not be pauzed } } void S_StopChannel (unsigned int channel_ind){ channel_t *prev,*now; if(ras_version>0 && ras_dll){ channel_begin(&prev,&now); while(now){ if(now->id==channel_ind){ S_KillChannel(now); channel_delete_and_next(&prev,&now); }else{ channel_next(&prev,&now); } } } } qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value){ if(ras_version>0 && ras_dll){ Con_Printf("Called S_SetChannelFlag %u, %u, %i\n",ch_ind, flag, value); } return 0; } void S_SetChannelVolume (unsigned int ch_ind, float fvol){ channel_t *prev,*now; if(ras_version>0 && ras_dll){ Con_Printf("Called S_SetChannelVolume %u, %f\n",ch_ind, fvol); channel_begin(&prev,&now); while(now){ if(now->id==ch_ind){ if(now->rasptr){ ras_soundevent_setsoundpower(now->rasptr,fvol*DP_Ras_VolumeScale); }else{ Con_Printf("S_StopChannel: Warning pointer was 0 ... this indicates a bug.\n"); } } channel_next(&prev,&now); } } } float S_GetChannelPosition (unsigned int ch_ind) { // FIXME unsupported return -1; } void S_BlockSound (void){ soundblocked++; } void S_UnblockSound (void){ soundblocked--; if(soundblocked<0){ Con_Printf("S_UnblockSound: Requested more S_UnblockSound then S_BlockSound.\n"); } } int S_GetSoundRate (void){ Con_Printf("Inside 3DRAS, the soundrate of the end-user is NONE of the dev's concern.\n"); Con_Printf("So let's assume 44100.\n"); return 44100; } int S_GetSoundChannels (void){ Con_Printf("Inside 3DRAS, the soundrate of the end-user is NONE of the dev's concern.\n"); Con_Printf("So let's assume 2.\n"); return 2; } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/darkplaces.cbp0000664000175000017500000003034213067716220014764 0ustar kalevkalev darkplaces/.gitattributes0000664000175000017500000001022013067716216015056 0ustar kalevkalev* -crlf *.0 -diff -crlf *.1 crlf=input *.3 crlf=input *.7z -diff -crlf *.ac crlf=input *.a -diff -crlf *.afm crlf=input *.aft crlf=input *.ai -diff -crlf *.aliases crlf=input all crlf=input *.am crlf=input *.animinfo crlf=input *.aps -diff -crlf *.asc -diff -crlf *.ase -crlf *.bat -crlf *.bgs crlf=input *.blend1 -diff -crlf *.blend -diff -crlf blind_id -diff -crlf *.bmp -diff -crlf branch-manager crlf=input *.brand crlf=input BSDmakefile crlf=input bsp2ent crlf=input *.bsp -diff -crlf *.cache -diff -crlf *.cbp -crlf *.cbp -diff -crlf *.c crlf=input *.cfg crlf=input *.cg crlf=input ChangeLog crlf=input CHANGES crlf=input cjpeg -diff -crlf COMPILING crlf=input compress-texture crlf=input *.conf crlf=input CONTRIBUTORS crlf=input COPYING crlf=input *.cpp crlf=input create crlf=input *.cron crlf=input *.css crlf=input *.cvswrappers crlf=input *.d0pk -diff -crlf *.db -diff -crlf *.default crlf=input *.def crlf=input *.dem -diff -crlf *.dev -crlf dir -diff -crlf djpeg -diff -crlf *.dll -diff -crlf DOCS -diff -crlf *.dot crlf=input DoxyConfig crlf=input doxyfile crlf=input Doxyfile crlf=input *.doxygen crlf=input *.dpm -diff -crlf *.dsp -crlf *.dsw -crlf *.dtd crlf=input *.dylib -diff -crlf empty -diff -crlf *.EncoderPlugin crlf=input *.flac -diff -crlf *.form crlf=input *.framegroups crlf=input *.game crlf=input *.gdb crlf=input gendox crlf=input gendoxfunctions crlf=input genDoxyfile crlf=input *.gif -diff -crlf *.gitattributes crlf=input git-branch-manager crlf=input git-filter-index crlf=input git-filter-repository crlf=input *.gitignore crlf=input git-pk3-import crlf=input git-pk3-merge crlf=input git-pullall crlf=input git-recurse crlf=input git-split-repository crlf=input git-svn-checkout crlf=input git-svn-update crlf=input git-update-octopus crlf=input *.glp crlf=input *.glsl crlf=input GPL crlf=input *.hardwired crlf=input *.h crlf=input *.hs crlf=input *.html crlf=input *.html-part crlf=input *.icns -diff -crlf *.ico -diff -crlf *.idl crlf=input *.idsoftware crlf=input *.inc crlf=input *.in crlf=input *.info-1 -diff -crlf *.info-2 -diff -crlf *.info -diff -crlf *.inl crlf=input *.instantaction crlf=input *.iqm -diff -crlf *.java crlf=input *.jhm crlf=input *.jnlp crlf=input jpegtran -diff -crlf *.jpg -diff -crlf *.jsmooth crlf=input *.la crlf=input LGPL crlf=input LICENSE crlf=input *.lmp -diff -crlf *.loaders crlf=input *.lso -diff -crlf *.m4 crlf=input makefile crlf=input Makefile crlf=input makespr32 crlf=input *.map -crlf filter=mapclean *.mapinfo crlf=input *.m crlf=input *.md3 -diff -crlf *.md5anim -crlf *.md5mesh -crlf *.mdl -diff -crlf *.med crlf=input *.mf crlf=input *.mid -diff -crlf *.mk crlf=input *.mkdir -diff -crlf *.mmpz -diff -crlf *.modules crlf=input *.nib -crlf *.obj -crlf OFFSETS -diff -crlf *.ogg -diff -crlf *.options crlf=input pangorc crlf=input *.patch crlf=input *.patchsets crlf=input *.pc crlf=input *.pcx -diff -crlf *.pfb -diff -crlf *.pfm -diff -crlf *.pk3 -diff -crlf PkgInfo crlf=input *.pl crlf=input *.plist crlf=input *.pm crlf=input *.png -diff -crlf POSITIONS -diff -crlf *.proj -crlf *.properties crlf=input *.psd -diff -crlf *.py crlf=input *.q3map1 crlf=input *.qc crlf=input *.qdt crlf=input *.qh crlf=input *.rb crlf=input *.rc2 crlf=input *.rc -crlf rdjpgcom -diff -crlf *.readme crlf=input README crlf=input *.rtlights -diff -crlf SCHEMA crlf=input *.scm crlf=input sdl-config crlf=input SDL -diff -crlf *.shader crlf=input *.sh crlf=input *.skin crlf=input *.sln -crlf *.sounds crlf=input *.sp2 -diff -crlf *.spr32 -diff -crlf *.spr -diff -crlf *.src crlf=input *.strings crlf=input strip crlf=input *.svg -diff -crlf *.TAB -diff -crlf *.tga -diff -crlf TMAP -diff -crlf todo crlf=input TODO crlf=input *.ttf -diff -crlf *.TTF -diff -crlf *.txt crlf=input update-shaderlists crlf=input *.vbs -crlf *.vcproj -crlf versionbuilder crlf=input *.wav -diff -crlf *.waypoints -diff -crlf w crlf=input *.width crlf=input *.workspace -crlf wrjpgcom -diff -crlf *.xcf -diff -crlf *.xlink crlf=input *.xml crlf=input xonotic-map-compiler-autobuild crlf=input xonotic-map-compiler crlf=input xonotic-map-screenshot crlf=input xonotic-osx-agl crlf=input xonotic-osx-sdl crlf=input *.xpm crlf=input *.zip -diff -crlf zipdiff crlf=input *.zym -diff -crlf darkplaces/r_modules.c0000664000175000017500000000527113067716222014327 0ustar kalevkalev #include "quakedef.h" #define MAXRENDERMODULES 20 typedef struct rendermodule_s { int active; // set by start, cleared by shutdown const char *name; void(*start)(void); void(*shutdown)(void); void(*newmap)(void); void(*devicelost)(void); void(*devicerestored)(void); } rendermodule_t; rendermodule_t rendermodule[MAXRENDERMODULES]; void R_Modules_Init(void) { Cmd_AddCommand("r_restart", R_Modules_Restart, "restarts renderer"); } void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)) { int i; for (i = 0;i < MAXRENDERMODULES;i++) { if (rendermodule[i].name == NULL) break; if (!strcmp(name, rendermodule[i].name)) { Con_Printf("R_RegisterModule: module \"%s\" registered twice\n", name); return; } } if (i >= MAXRENDERMODULES) Sys_Error("R_RegisterModule: ran out of renderer module slots (%i)", MAXRENDERMODULES); rendermodule[i].active = 0; rendermodule[i].name = name; rendermodule[i].start = start; rendermodule[i].shutdown = shutdown; rendermodule[i].newmap = newmap; rendermodule[i].devicelost = devicelost; rendermodule[i].devicerestored = devicerestored; } void R_Modules_Start(void) { int i; for (i = 0;i < MAXRENDERMODULES;i++) { if (rendermodule[i].name == NULL) continue; if (rendermodule[i].active) { Con_Printf ("R_StartModules: module \"%s\" already active\n", rendermodule[i].name); continue; } rendermodule[i].active = 1; rendermodule[i].start(); } } void R_Modules_Shutdown(void) { int i; // shutdown in reverse for (i = MAXRENDERMODULES - 1;i >= 0;i--) { if (rendermodule[i].name == NULL) continue; if (!rendermodule[i].active) continue; rendermodule[i].active = 0; rendermodule[i].shutdown(); } } void R_Modules_Restart(void) { Host_StartVideo(); Con_Print("restarting renderer\n"); R_Modules_Shutdown(); R_Modules_Start(); } void R_Modules_NewMap(void) { int i; R_SkinFrame_PrepareForPurge(); for (i = 0;i < MAXRENDERMODULES;i++) { if (rendermodule[i].name == NULL) continue; if (!rendermodule[i].active) continue; rendermodule[i].newmap(); } R_SkinFrame_Purge(); } void R_Modules_DeviceLost(void) { int i; for (i = 0;i < MAXRENDERMODULES;i++) { if (rendermodule[i].name == NULL) continue; if (!rendermodule[i].active) continue; if (!rendermodule[i].devicelost) continue; rendermodule[i].devicelost(); } } void R_Modules_DeviceRestored(void) { int i; for (i = 0;i < MAXRENDERMODULES;i++) { if (rendermodule[i].name == NULL) continue; if (!rendermodule[i].active) continue; if (!rendermodule[i].devicerestored) continue; rendermodule[i].devicerestored(); } } darkplaces/palette.c0000664000175000017500000003474413067716222014003 0ustar kalevkalev #include "quakedef.h" #include "image.h" cvar_t r_colormap_palette = {0, "r_colormap_palette", "gfx/colormap_palette.lmp", "name of a palette lmp file to override the shirt/pants colors of player models. It consists of 16 shirt colors, 16 scoreboard shirt colors, 16 pants colors and 16 scoreboard pants colors"}; unsigned char palette_rgb[256][3]; unsigned char palette_rgb_pantscolormap[16][3]; unsigned char palette_rgb_shirtcolormap[16][3]; unsigned char palette_rgb_pantsscoreboard[16][3]; unsigned char palette_rgb_shirtscoreboard[16][3]; unsigned int palette_bgra_complete[256]; unsigned int palette_bgra_font[256]; unsigned int palette_bgra_alpha[256]; unsigned int palette_bgra_nocolormap[256]; unsigned int palette_bgra_nocolormapnofullbrights[256]; unsigned int palette_bgra_nofullbrights[256]; unsigned int palette_bgra_nofullbrights_transparent[256]; unsigned int palette_bgra_onlyfullbrights[256]; unsigned int palette_bgra_onlyfullbrights_transparent[256]; unsigned int palette_bgra_pantsaswhite[256]; unsigned int palette_bgra_shirtaswhite[256]; unsigned int palette_bgra_transparent[256]; unsigned int palette_bgra_embeddedpic[256]; unsigned char palette_featureflags[256]; unsigned int q2palette_bgra_complete[256]; // John Carmack said the quake palette.lmp can be considered public domain because it is not an important asset to id, so I include it here as a fallback if no external palette file is found. unsigned char host_quakepal[768] = { // marked: colormap colors: cb = (colormap & 0xF0);cb += (cb >= 128 && cb < 224) ? 4 : 12; // 0x0* 0,0,0, 15,15,15, 31,31,31, 47,47,47, 63,63,63, 75,75,75, 91,91,91, 107,107,107, 123,123,123, 139,139,139, 155,155,155, 171,171,171, 187,187,187, 203,203,203, 219,219,219, 235,235,235, // 0x1* 0 ^ 15,11,7, 23,15,11, 31,23,11, 39,27,15, 47,35,19, 55,43,23, 63,47,23, 75,55,27, 83,59,27, 91,67,31, 99,75,31, 107,83,31, 115,87,31, 123,95,35, 131,103,35, 143,111,35, // 0x2* 1 ^ 11,11,15, 19,19,27, 27,27,39, 39,39,51, 47,47,63, 55,55,75, 63,63,87, 71,71,103, 79,79,115, 91,91,127, 99,99,139, 107,107,151, 115,115,163, 123,123,175, 131,131,187, 139,139,203, // 0x3* 2 ^ 0,0,0, 7,7,0, 11,11,0, 19,19,0, 27,27,0, 35,35,0, 43,43,7, 47,47,7, 55,55,7, 63,63,7, 71,71,7, 75,75,11, 83,83,11, 91,91,11, 99,99,11, 107,107,15, // 0x4* 3 ^ 7,0,0, 15,0,0, 23,0,0, 31,0,0, 39,0,0, 47,0,0, 55,0,0, 63,0,0, 71,0,0, 79,0,0, 87,0,0, 95,0,0, 103,0,0, 111,0,0, 119,0,0, 127,0,0, // 0x5* 4 ^ 19,19,0, 27,27,0, 35,35,0, 47,43,0, 55,47,0, 67,55,0, 75,59,7, 87,67,7, 95,71,7, 107,75,11, 119,83,15, 131,87,19, 139,91,19, 151,95,27, 163,99,31, 175,103,35, // 0x6* 5 ^ 35,19,7, 47,23,11, 59,31,15, 75,35,19, 87,43,23, 99,47,31, 115,55,35, 127,59,43, 143,67,51, 159,79,51, 175,99,47, 191,119,47, 207,143,43, 223,171,39, 239,203,31, 255,243,27, // 0x7* 6 ^ 11,7,0, 27,19,0, 43,35,15, 55,43,19, 71,51,27, 83,55,35, 99,63,43, 111,71,51, 127,83,63, 139,95,71, 155,107,83, 167,123,95, 183,135,107, 195,147,123, 211,163,139, 227,179,151, // 0x8* 7 ^ v 8 171,139,163, 159,127,151, 147,115,135, 139,103,123, 127,91,111, 119,83,99, 107,75,87, 95,63,75, 87,55,67, 75,47,55, 67,39,47, 55,31,35, 43,23,27, 35,19,19, 23,11,11, 15,7,7, // 0x9* 9 v 187,115,159, 175,107,143, 163,95,131, 151,87,119, 139,79,107, 127,75,95, 115,67,83, 107,59,75, 95,51,63, 83,43,55, 71,35,43, 59,31,35, 47,23,27, 35,19,19, 23,11,11, 15,7,7, // 0xA* 10 v 219,195,187, 203,179,167, 191,163,155, 175,151,139, 163,135,123, 151,123,111, 135,111,95, 123,99,83, 107,87,71, 95,75,59, 83,63,51, 67,51,39, 55,43,31, 39,31,23, 27,19,15, 15,11,7, // 0xB* 11 v 111,131,123, 103,123,111, 95,115,103, 87,107,95, 79,99,87, 71,91,79, 63,83,71, 55,75,63, 47,67,55, 43,59,47, 35,51,39, 31,43,31, 23,35,23, 15,27,19, 11,19,11, 7,11,7, // 0xC* 12 v 255,243,27, 239,223,23, 219,203,19, 203,183,15, 187,167,15, 171,151,11, 155,131,7, 139,115,7, 123,99,7, 107,83,0, 91,71,0, 75,55,0, 59,43,0, 43,31,0, 27,15,0, 11,7,0, // 0xD* 13 v 0,0,255, 11,11,239, 19,19,223, 27,27,207, 35,35,191, 43,43,175, 47,47,159, 47,47,143, 47,47,127, 47,47,111, 47,47,95, 43,43,79, 35,35,63, 27,27,47, 19,19,31, 11,11,15, // 0xE* 43,0,0, 59,0,0, 75,7,0, 95,7,0, 111,15,0, 127,23,7, 147,31,7, 163,39,11, 183,51,15, 195,75,27, 207,99,43, 219,127,59, 227,151,79, 231,171,95, 239,191,119, 247,211,139, // 0xF* 14 ^ 167,123,59, 183,155,55, 199,195,55, 231,227,87, 127,191,255, 171,231,255, 215,255,255, 103,0,0, 139,0,0, 179,0,0, 215,0,0, 255,0,0, 255,243,147, 255,247,199, 255,255,255, 159,91,83 }; // 15 ^ static void Palette_SetupSpecialPalettes(void) { int i; int fullbright_start, fullbright_end; int pants_start, pants_end; int shirt_start, shirt_end; int reversed_start, reversed_end; int transparentcolor; unsigned char *colormap; fs_offset_t filesize; union { int i; unsigned char b[4]; } u; colormap = FS_LoadFile("gfx/colormap.lmp", tempmempool, true, &filesize); if (colormap && filesize >= 16385) fullbright_start = 256 - colormap[16384]; else fullbright_start = 256; if (colormap) Mem_Free(colormap); fullbright_end = 256; pants_start = 96; pants_end = 112; shirt_start = 16; shirt_end = 32; reversed_start = 128; reversed_end = 224; transparentcolor = 255; for (i = 0;i < 256;i++) palette_featureflags[i] = PALETTEFEATURE_STANDARD; for (i = reversed_start;i < reversed_end;i++) palette_featureflags[i] = PALETTEFEATURE_REVERSED; for (i = pants_start;i < pants_end;i++) palette_featureflags[i] = PALETTEFEATURE_PANTS; for (i = shirt_start;i < shirt_end;i++) palette_featureflags[i] = PALETTEFEATURE_SHIRT; for (i = fullbright_start;i < fullbright_end;i++) palette_featureflags[i] = PALETTEFEATURE_GLOW; palette_featureflags[0] = PALETTEFEATURE_ZERO; palette_featureflags[transparentcolor] = PALETTEFEATURE_TRANSPARENT; for (i = 0;i < 256;i++) palette_bgra_transparent[i] = palette_bgra_complete[i]; palette_bgra_transparent[transparentcolor] = 0; for (i = 0;i < fullbright_start;i++) palette_bgra_nofullbrights[i] = palette_bgra_complete[i]; for (i = fullbright_start;i < fullbright_end;i++) palette_bgra_nofullbrights[i] = palette_bgra_complete[0]; for (i = 0;i < 256;i++) palette_bgra_nofullbrights_transparent[i] = palette_bgra_nofullbrights[i]; palette_bgra_nofullbrights_transparent[transparentcolor] = 0; for (i = 0;i < 256;i++) palette_bgra_onlyfullbrights[i] = 0; for (i = fullbright_start;i < fullbright_end;i++) palette_bgra_onlyfullbrights[i] = palette_bgra_complete[i]; for (i = 0;i < 256;i++) palette_bgra_onlyfullbrights_transparent[i] = palette_bgra_onlyfullbrights[i]; palette_bgra_onlyfullbrights_transparent[transparentcolor] = 0; for (i = 0;i < 256;i++) palette_bgra_nocolormapnofullbrights[i] = palette_bgra_complete[i]; for (i = pants_start;i < pants_end;i++) palette_bgra_nocolormapnofullbrights[i] = 0; for (i = shirt_start;i < shirt_end;i++) palette_bgra_nocolormapnofullbrights[i] = 0; for (i = fullbright_start;i < fullbright_end;i++) palette_bgra_nocolormapnofullbrights[i] = 0; for (i = 0;i < 256;i++) palette_bgra_nocolormap[i] = palette_bgra_complete[i]; for (i = pants_start;i < pants_end;i++) palette_bgra_nocolormap[i] = 0; for (i = shirt_start;i < shirt_end;i++) palette_bgra_nocolormap[i] = 0; for (i = 0;i < 256;i++) palette_bgra_pantsaswhite[i] = 0; for (i = pants_start;i < pants_end;i++) { if (i >= reversed_start && i < reversed_end) palette_bgra_pantsaswhite[i] = palette_bgra_complete[15 - (i - pants_start)]; else palette_bgra_pantsaswhite[i] = palette_bgra_complete[i - pants_start]; } for (i = 0;i < 256;i++) palette_bgra_shirtaswhite[i] = 0; for (i = shirt_start;i < shirt_end;i++) { if (i >= reversed_start && i < reversed_end) palette_bgra_shirtaswhite[i] = palette_bgra_complete[15 - (i - shirt_start)]; else palette_bgra_shirtaswhite[i] = palette_bgra_complete[i - shirt_start]; } for (i = 0;i < 256;i++) palette_bgra_alpha[i] = 0xFFFFFFFF; u.i = 0xFFFFFFFF; u.b[3] = 0; palette_bgra_alpha[transparentcolor] = u.i; for (i = 0;i < 256;i++) palette_bgra_font[i] = palette_bgra_complete[i]; palette_bgra_font[0] = 0; } static void Palette_LoadQ2Colormap(void) { fs_offset_t filesize; unsigned char * q2colormapfile = FS_LoadFile("pics/colormap.pcx", tempmempool, true, &filesize); if (q2colormapfile && filesize >= 768) { unsigned char q2palette_rgb[256][3]; unsigned char *out = (unsigned char *) q2palette_bgra_complete; // palette is accessed as 32bit for speed reasons, but is created as 8bit bytes int i; LoadPCX_PaletteOnly(q2colormapfile, filesize, q2palette_rgb[0]); // this stops at color 255 because it is a pink transparent color that we don't actually want to preserve color on. for (i = 0;i < 255;i++) { out[i*4+2] = q2palette_rgb[i][0]; out[i*4+1] = q2palette_rgb[i][1]; out[i*4+0] = q2palette_rgb[i][2]; out[i*4+3] = 255; } Mem_Free(q2colormapfile); } } void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize) { int i, adjusted; double invgamma; double t, d; invgamma = 1.0 / gamma; prescale /= (double) (rampsize - 1); for (i = 0;i < rampsize;i++) { t = i * prescale; d = ((contrastboost - 1) * t + 1); if(d == 0) t = 0; // we could just as well assume 1 here, depending on which side of the division by zero we want to be else t = contrastboost * t / d; adjusted = (int) (255.0 * (pow(t, invgamma) * scale + base) + 0.5); out[i] = bound(0, adjusted, 255); } } void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize) { int i, adjusted; double invgamma; double t; invgamma = 1.0 / gamma; prescale /= (double) (rampsize - 1); for (i = 0;i < rampsize;i++) { t = i * prescale; t = contrastboost * t / ((contrastboost - 1) * t + 1); adjusted = (int) (65535.0 * (pow(t, invgamma) * scale + base) + 0.5); out[i] = bound(0, adjusted, 65535); } } static void Palette_Shutdown(void) { } static void Palette_NewMap(void) { } static void Palette_Load(void) { int i; unsigned char *out; float gamma, scale, base; fs_offset_t filesize; unsigned char *palfile; unsigned char texturegammaramp[256]; union { unsigned char b[4]; unsigned int i; } bgra; gamma = 1; scale = 1; base = 0; // COMMANDLINEOPTION: Client: -texgamma sets the quake palette gamma, allowing you to make quake textures brighter/darker, not recommended i = COM_CheckParm("-texgamma"); if (i) gamma = atof(com_argv[i + 1]); // COMMANDLINEOPTION: Client: -texcontrast sets the quake palette contrast, allowing you to make quake textures brighter/darker, not recommended i = COM_CheckParm("-texcontrast"); if (i) scale = atof(com_argv[i + 1]); // COMMANDLINEOPTION: Client: -texbrightness sets the quake palette brightness (brightness of black), allowing you to make quake textures brighter/darker, not recommended i = COM_CheckParm("-texbrightness"); if (i) base = atof(com_argv[i + 1]); gamma = bound(0.01, gamma, 10.0); scale = bound(0.01, scale, 10.0); base = bound(0, base, 0.95); BuildGammaTable8(1.0f, gamma, scale, base, 1, texturegammaramp, 256); palfile = (unsigned char *)FS_LoadFile ("gfx/palette.lmp", tempmempool, false, &filesize); if (palfile && filesize >= 768) memcpy(palette_rgb, palfile, 768); else { Con_DPrint("Couldn't load gfx/palette.lmp, falling back on internal palette\n"); memcpy(palette_rgb, host_quakepal, 768); } if (palfile) Mem_Free(palfile); out = (unsigned char *) palette_bgra_complete; // palette is accessed as 32bit for speed reasons, but is created as 8bit bytes for (i = 0;i < 256;i++) { out[i*4+2] = texturegammaramp[palette_rgb[i][0]]; out[i*4+1] = texturegammaramp[palette_rgb[i][1]]; out[i*4+0] = texturegammaramp[palette_rgb[i][2]]; out[i*4+3] = 255; } if(*r_colormap_palette.string) palfile = (unsigned char *)FS_LoadFile (r_colormap_palette.string, tempmempool, false, &filesize); else palfile = NULL; if (palfile && filesize >= 48*2) { memcpy(palette_rgb_shirtcolormap[0], palfile, 48); memcpy(palette_rgb_shirtscoreboard[0], palfile + 48, 48); } else { for(i = 0;i < 16;i++) { VectorCopy(palette_rgb[(i << 4) | ((i >= 8 && i <= 13) ? 0x04 : 0x0C)], palette_rgb_shirtcolormap[i]); VectorCopy(palette_rgb[(i << 4) | 0x08], palette_rgb_shirtscoreboard[i]); } } if (palfile && filesize >= 48*4) { memcpy(palette_rgb_pantscolormap[0], palfile + 48*2, 48); memcpy(palette_rgb_pantsscoreboard[0], palfile + 48*3, 48); } else { memcpy(palette_rgb_pantscolormap, palette_rgb_shirtcolormap, sizeof(palette_rgb_pantscolormap)); memcpy(palette_rgb_pantsscoreboard, palette_rgb_shirtscoreboard, sizeof(palette_rgb_pantsscoreboard)); } if(palfile) Mem_Free(palfile); memset(palette_bgra_embeddedpic, 0, sizeof(palette_bgra_embeddedpic)); for (i = '1';i <= '7';i++) { Vector4Set(bgra.b, 255, 255, 255, (i - '0') * 255 / 7); palette_bgra_embeddedpic[i] = bgra.i; } Palette_SetupSpecialPalettes(); Palette_LoadQ2Colormap(); } void Palette_Init(void) { R_RegisterModule("Palette", Palette_Load, Palette_Shutdown, Palette_NewMap, NULL, NULL); Cvar_RegisterVariable(&r_colormap_palette); Palette_Load(); } darkplaces/prvm_cmds.h0000664000175000017500000003775013067716222014344 0ustar kalevkalev #ifndef PRVM_CMDS_H #define PRVM_CMDS_H // AK // Basically every vm builtin cmd should be in here. // All 3 builtin and extension lists can be found here // cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds // also applies here /* ============================================================================ common cmd list: ================= checkextension(string) error(...[string]) objerror(...[string) print(...[strings]) bprint(...[string]) sprint(float clientnum,...[string]) centerprint(...[string]) vector normalize(vector) float vlen(vector) float vectoyaw(vector) vector vectoangles(vector) float random() cmd(string) float cvar (string) cvar_set (string,string) dprint(...[string]) string ftos(float) float fabs(float) string vtos(vector) string etos(entity) float stof(...[string]) entity spawn() remove(entity e) entity find(entity start, .string field, string match) entity findfloat(entity start, .float field, float match) entity findentity(entity start, .entity field, entity match) entity findchain(.string field, string match) entity findchainfloat(.string field, float match) entity findchainentity(.string field, entity match) string precache_file(string) string precache_sound (string sample) coredump() traceon() traceoff() eprint(entity e) float rint(float) float floor(float) float ceil(float) entity nextent(entity) float sin(float) float cos(float) float sqrt(float) vector randomvec() float registercvar (string name, string value, float flags) float min(float a, float b, ...[float]) float max(float a, float b, ...[float]) float bound(float min, float value, float max) float pow(float a, float b) copyentity(entity src, entity dst) float fopen(string filename, float mode) fclose(float fhandle) string fgets(float fhandle) fputs(float fhandle, string s) float strlen(string s) string strcat(string,string,...[string]) string substring(string s, float start, float length) vector stov(string s) string strzone(string s) strunzone(string s) float tokenize(string s) string argv(float n) float isserver() float clientcount() float clientstate() clientcommand(float client, string s) (for client and menu) changelevel(string map) localsound(string sample) vector getmousepos() float gettime() loadfromdata(string data) loadfromfile(string file) parseentitydata(entity ent, string data) float mod(float val, float m) const string cvar_string (string) float cvar_type (string) crash() stackdump() float search_begin(string pattern, float caseinsensitive, float quiet) void search_end(float handle) float search_getsize(float handle) string search_getfilename(float handle, float num) string chr(float ascii) float itof(intt ent) entity ftoe(float num) -------will be removed soon---------- float altstr_count(string) string altstr_prepare(string) string altstr_get(string,float) string altstr_set(string altstr, float num, string set) string altstr_ins(string altstr, float num, string set) -------------------------------------- entity findflags(entity start, .float field, float match) entity findchainflags(.float field, float match) const string VM_cvar_defstring (string) perhaps only : Menu : WriteMsg =============================== WriteByte(float data, float dest, float desto) WriteChar(float data, float dest, float desto) WriteShort(float data, float dest, float desto) WriteLong(float data, float dest, float desto) WriteAngle(float data, float dest, float desto) WriteCoord(float data, float dest, float desto) WriteString(string data, float dest, float desto) WriteEntity(entity data, float dest, float desto) Client & Menu : draw functions & video functions (& gecko functions) =================================================== float iscachedpic(string pic) string precache_pic(string pic) freepic(string s) float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) float stringwidth(string text, float handleColors) float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) float drawfill(vector position, vector size, vector rgb, float alpha, float flag) drawsetcliparea(float x, float y, float width, float height) drawresetcliparea() vector getimagesize(string pic) float cin_open(string file, string name) void cin_close(string name) void cin_setstate(string name, float type) float cin_getstate(string name) void cin_restart(string name) float[bool] gecko_create( string name ) void gecko_destroy( string name ) void gecko_navigate( string name, string URI ) float[bool] gecko_keyevent( string name, float key, float eventtype ) void gecko_mousemove( string name, float x, float y ) ============================================================================== menu cmd list: =============== setkeydest(float dest) float getkeydest() setmousetarget(float target) float getmousetarget() callfunction(...,string function_name) writetofile(float fhandle, entity ent) float isfunction(string function_name) vector getresolution(float number) string keynumtostring(float keynum) string findkeysforcommand(string command) float getserverliststat(float type) string getserverliststring(float fld, float hostnr) float stringtokeynum(string key) resetserverlistmasks() setserverlistmaskstring(float mask, float fld, string str) setserverlistmasknumber(float mask, float fld, float num, float op) resortserverlist() setserverlistsort(float field, float descending) refreshserverlist() float getserverlistnumber(float fld, float hostnr) float getserverlistindexforkey(string key) addwantedserverlistkey(string key) */ #include "quakedef.h" #include "progdefs.h" #include "progsvm.h" #include "clprogdefs.h" #include "mprogdefs.h" #include "cl_video.h" //============================================================================ // nice helper macros #ifndef VM_NOPARMCHECK #define VM_SAFEPARMCOUNTRANGE(p1,p2,f) if(prog->argc < p1 || prog->argc > p2) prog->error_cmd(#f " wrong parameter count %i (" #p1 " to " #p2 " expected ) !", prog->argc) #define VM_SAFEPARMCOUNT(p,f) if(prog->argc != p) prog->error_cmd(#f " wrong parameter count %i (" #p " expected ) !", prog->argc) #else #define VM_SAFEPARMCOUNTRANGE(p1,p2,f) #define VM_SAFEPARMCOUNT(p,f) #endif #define VM_RETURN_EDICT(e) (prog->globals.ip[OFS_RETURN] = PRVM_EDICT_TO_PROG(e)) #define VM_STRINGTEMP_LENGTH MAX_INPUTLINE // init code void PR_Cmd_Init(void); // general functions void VM_CheckEmptyString (prvm_prog_t *prog, const char *s); void VM_VarString(prvm_prog_t *prog, int first, char *out, int outlength); prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, const char *format); void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str); void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer); void BufStr_Flush(prvm_prog_t *prog); // builtins void VM_checkextension (prvm_prog_t *prog); void VM_error (prvm_prog_t *prog); void VM_objerror (prvm_prog_t *prog); void VM_print (prvm_prog_t *prog); void VM_bprint (prvm_prog_t *prog); void VM_sprint (prvm_prog_t *prog); void VM_centerprint (prvm_prog_t *prog); void VM_normalize (prvm_prog_t *prog); void VM_vlen (prvm_prog_t *prog); void VM_vectoyaw (prvm_prog_t *prog); void VM_vectoangles (prvm_prog_t *prog); void VM_random (prvm_prog_t *prog); void VM_localsound(prvm_prog_t *prog); void VM_break (prvm_prog_t *prog); void VM_localcmd (prvm_prog_t *prog); void VM_cvar (prvm_prog_t *prog); void VM_cvar_string(prvm_prog_t *prog); void VM_cvar_type (prvm_prog_t *prog); void VM_cvar_defstring (prvm_prog_t *prog); void VM_cvar_set (prvm_prog_t *prog); void VM_dprint (prvm_prog_t *prog); void VM_ftos (prvm_prog_t *prog); void VM_fabs (prvm_prog_t *prog); void VM_vtos (prvm_prog_t *prog); void VM_etos (prvm_prog_t *prog); void VM_stof(prvm_prog_t *prog); void VM_itof(prvm_prog_t *prog); void VM_ftoe(prvm_prog_t *prog); void VM_strftime(prvm_prog_t *prog); void VM_spawn (prvm_prog_t *prog); void VM_remove (prvm_prog_t *prog); void VM_find (prvm_prog_t *prog); void VM_findfloat (prvm_prog_t *prog); void VM_findchain (prvm_prog_t *prog); void VM_findchainfloat (prvm_prog_t *prog); void VM_findflags (prvm_prog_t *prog); void VM_findchainflags (prvm_prog_t *prog); void VM_precache_file (prvm_prog_t *prog); void VM_precache_sound (prvm_prog_t *prog); void VM_coredump (prvm_prog_t *prog); void VM_stackdump (prvm_prog_t *prog); void VM_crash(prvm_prog_t *prog); // REMOVE IT void VM_traceon (prvm_prog_t *prog); void VM_traceoff (prvm_prog_t *prog); void VM_eprint (prvm_prog_t *prog); void VM_rint (prvm_prog_t *prog); void VM_floor (prvm_prog_t *prog); void VM_ceil (prvm_prog_t *prog); void VM_nextent (prvm_prog_t *prog); void VM_changelevel (prvm_prog_t *prog); void VM_sin (prvm_prog_t *prog); void VM_cos (prvm_prog_t *prog); void VM_sqrt (prvm_prog_t *prog); void VM_randomvec (prvm_prog_t *prog); void VM_registercvar (prvm_prog_t *prog); void VM_min (prvm_prog_t *prog); void VM_max (prvm_prog_t *prog); void VM_bound (prvm_prog_t *prog); void VM_pow (prvm_prog_t *prog); void VM_log (prvm_prog_t *prog); void VM_asin (prvm_prog_t *prog); void VM_acos (prvm_prog_t *prog); void VM_atan (prvm_prog_t *prog); void VM_atan2 (prvm_prog_t *prog); void VM_tan (prvm_prog_t *prog); void VM_Files_Init(prvm_prog_t *prog); void VM_Files_CloseAll(prvm_prog_t *prog); void VM_fopen(prvm_prog_t *prog); void VM_fclose(prvm_prog_t *prog); void VM_fgets(prvm_prog_t *prog); void VM_fputs(prvm_prog_t *prog); void VM_writetofile(prvm_prog_t *prog); // only used by menu void VM_strlen(prvm_prog_t *prog); void VM_strcat(prvm_prog_t *prog); void VM_substring(prvm_prog_t *prog); void VM_stov(prvm_prog_t *prog); void VM_strzone(prvm_prog_t *prog); void VM_strunzone(prvm_prog_t *prog); // KrimZon - DP_QC_ENTITYDATA void VM_numentityfields(prvm_prog_t *prog); void VM_entityfieldname(prvm_prog_t *prog); void VM_entityfieldtype(prvm_prog_t *prog); void VM_getentityfieldstring(prvm_prog_t *prog); void VM_putentityfieldstring(prvm_prog_t *prog); // DRESK - String Length (not counting color codes) void VM_strlennocol(prvm_prog_t *prog); // DRESK - Decolorized String void VM_strdecolorize(prvm_prog_t *prog); // DRESK - String Uppercase and Lowercase Support void VM_strtolower(prvm_prog_t *prog); void VM_strtoupper(prvm_prog_t *prog); void VM_clcommand (prvm_prog_t *prog); void VM_tokenize (prvm_prog_t *prog); void VM_tokenizebyseparator (prvm_prog_t *prog); void VM_argv (prvm_prog_t *prog); void VM_isserver(prvm_prog_t *prog); void VM_clientcount(prvm_prog_t *prog); void VM_clientstate(prvm_prog_t *prog); // not used at the moment -> not included in the common list void VM_getostype(prvm_prog_t *prog); void VM_getmousepos(prvm_prog_t *prog); void VM_gettime(prvm_prog_t *prog); void VM_getsoundtime(prvm_prog_t *prog); void VM_soundlength(prvm_prog_t *prog); void VM_loadfromdata(prvm_prog_t *prog); void VM_parseentitydata(prvm_prog_t *prog); void VM_loadfromfile(prvm_prog_t *prog); void VM_modulo(prvm_prog_t *prog); void VM_search_begin(prvm_prog_t *prog); void VM_search_end(prvm_prog_t *prog); void VM_search_getsize(prvm_prog_t *prog); void VM_search_getfilename(prvm_prog_t *prog); void VM_chr(prvm_prog_t *prog); void VM_iscachedpic(prvm_prog_t *prog); void VM_precache_pic(prvm_prog_t *prog); void VM_freepic(prvm_prog_t *prog); void VM_drawcharacter(prvm_prog_t *prog); void VM_drawstring(prvm_prog_t *prog); void VM_drawcolorcodedstring(prvm_prog_t *prog); void VM_stringwidth(prvm_prog_t *prog); void VM_drawpic(prvm_prog_t *prog); void VM_drawrotpic(prvm_prog_t *prog); void VM_drawsubpic(prvm_prog_t *prog); void VM_drawfill(prvm_prog_t *prog); void VM_drawsetcliparea(prvm_prog_t *prog); void VM_drawresetcliparea(prvm_prog_t *prog); void VM_getimagesize(prvm_prog_t *prog); void VM_findfont(prvm_prog_t *prog); void VM_loadfont(prvm_prog_t *prog); void VM_makevectors (prvm_prog_t *prog); void VM_vectorvectors (prvm_prog_t *prog); void VM_keynumtostring (prvm_prog_t *prog); void VM_getkeybind (prvm_prog_t *prog); void VM_findkeysforcommand (prvm_prog_t *prog); void VM_stringtokeynum (prvm_prog_t *prog); void VM_setkeybind (prvm_prog_t *prog); void VM_getbindmaps (prvm_prog_t *prog); void VM_setbindmaps (prvm_prog_t *prog); void VM_cin_open(prvm_prog_t *prog); void VM_cin_close(prvm_prog_t *prog); void VM_cin_setstate(prvm_prog_t *prog); void VM_cin_getstate(prvm_prog_t *prog); void VM_cin_restart(prvm_prog_t *prog); void VM_gecko_create(prvm_prog_t *prog); void VM_gecko_destroy(prvm_prog_t *prog); void VM_gecko_navigate(prvm_prog_t *prog); void VM_gecko_keyevent(prvm_prog_t *prog); void VM_gecko_movemouse(prvm_prog_t *prog); void VM_gecko_resize(prvm_prog_t *prog); void VM_gecko_get_texture_extent(prvm_prog_t *prog); void VM_drawline (prvm_prog_t *prog); void VM_bitshift (prvm_prog_t *prog); void VM_altstr_count(prvm_prog_t *prog); void VM_altstr_prepare(prvm_prog_t *prog); void VM_altstr_get(prvm_prog_t *prog); void VM_altstr_set(prvm_prog_t *prog); void VM_altstr_ins(prvm_prog_t *prog); void VM_buf_create(prvm_prog_t *prog); void VM_buf_del (prvm_prog_t *prog); void VM_buf_getsize (prvm_prog_t *prog); void VM_buf_copy (prvm_prog_t *prog); void VM_buf_sort (prvm_prog_t *prog); void VM_buf_implode (prvm_prog_t *prog); void VM_bufstr_get (prvm_prog_t *prog); void VM_bufstr_set (prvm_prog_t *prog); void VM_bufstr_add (prvm_prog_t *prog); void VM_bufstr_free (prvm_prog_t *prog); void VM_buf_loadfile(prvm_prog_t *prog); void VM_buf_writefile(prvm_prog_t *prog); void VM_bufstr_find(prvm_prog_t *prog); void VM_matchpattern(prvm_prog_t *prog); void VM_changeyaw (prvm_prog_t *prog); void VM_changepitch (prvm_prog_t *prog); void VM_uncolorstring (prvm_prog_t *prog); void VM_strstrofs (prvm_prog_t *prog); void VM_str2chr (prvm_prog_t *prog); void VM_chr2str (prvm_prog_t *prog); void VM_strconv (prvm_prog_t *prog); void VM_strpad (prvm_prog_t *prog); void VM_infoadd (prvm_prog_t *prog); void VM_infoget (prvm_prog_t *prog); void VM_strncmp (prvm_prog_t *prog); void VM_strncmp (prvm_prog_t *prog); void VM_strncasecmp (prvm_prog_t *prog); void VM_registercvar (prvm_prog_t *prog); void VM_wasfreed (prvm_prog_t *prog); void VM_strreplace (prvm_prog_t *prog); void VM_strireplace (prvm_prog_t *prog); void VM_crc16(prvm_prog_t *prog); void VM_digest_hex(prvm_prog_t *prog); void VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace); void VM_ClearTraceGlobals(prvm_prog_t *prog); void VM_uri_escape (prvm_prog_t *prog); void VM_uri_unescape (prvm_prog_t *prog); void VM_whichpack (prvm_prog_t *prog); void VM_etof (prvm_prog_t *prog); void VM_uri_get (prvm_prog_t *prog); void VM_netaddress_resolve (prvm_prog_t *prog); void VM_tokenize_console (prvm_prog_t *prog); void VM_argv_start_index (prvm_prog_t *prog); void VM_argv_end_index (prvm_prog_t *prog); void VM_buf_cvarlist(prvm_prog_t *prog); void VM_cvar_description(prvm_prog_t *prog); void VM_CL_getextresponse (prvm_prog_t *prog); void VM_SV_getextresponse (prvm_prog_t *prog); // Common functions between menu.dat and clsprogs void VM_CL_isdemo (prvm_prog_t *prog); void VM_CL_videoplaying (prvm_prog_t *prog); void VM_isfunction(prvm_prog_t *prog); void VM_callfunction(prvm_prog_t *prog); void VM_sprintf(prvm_prog_t *prog); void VM_getsurfacenumpoints(prvm_prog_t *prog); void VM_getsurfacepoint(prvm_prog_t *prog); void VM_getsurfacepointattribute(prvm_prog_t *prog); void VM_getsurfacenormal(prvm_prog_t *prog); void VM_getsurfacetexture(prvm_prog_t *prog); void VM_getsurfacenearpoint(prvm_prog_t *prog); void VM_getsurfaceclippedpoint(prvm_prog_t *prog); void VM_getsurfacenumtriangles(prvm_prog_t *prog); void VM_getsurfacetriangle(prvm_prog_t *prog); // physics builtins void VM_physics_enable(prvm_prog_t *prog); void VM_physics_addforce(prvm_prog_t *prog); void VM_physics_addtorque(prvm_prog_t *prog); void VM_coverage(prvm_prog_t *prog); #endif darkplaces/model_psk.h0000664000175000017500000000443013067716222014314 0ustar kalevkalev #ifndef MODEL_PSK_H #define MODEL_PSK_H typedef struct pskchunk_s { // id is one of the following: // .psk: // ACTRHEAD (recordsize = 0, numrecords = 0) // PNTS0000 (recordsize = 12, pskpnts_t) // VTXW0000 (recordsize = 16, pskvtxw_t) // FACE0000 (recordsize = 12, pskface_t) // MATT0000 (recordsize = 88, pskmatt_t) // REFSKELT (recordsize = 120, pskboneinfo_t) // RAWWEIGHTS (recordsize = 12, pskrawweights_t) // .psa: // ANIMHEAD (recordsize = 0, numrecords = 0) // BONENAMES (recordsize = 120, pskboneinfo_t) // ANIMINFO (recordsize = 168, pskaniminfo_t) // ANIMKEYS (recordsize = 32, pskanimkeys_t) char id[20]; // in .psk always 0x1e83b9 // in .psa always 0x2e int version; int recordsize; int numrecords; } pskchunk_t; typedef struct pskpnts_s { float origin[3]; } pskpnts_t; typedef struct pskvtxw_s { unsigned short pntsindex; // index into PNTS0000 chunk unsigned char unknown1[2]; // seems to be garbage float texcoord[2]; unsigned char mattindex; // index into MATT0000 chunk unsigned char unknown2; // always 0? unsigned char unknown3[2]; // seems to be garbage } pskvtxw_t; typedef struct pskface_s { unsigned short vtxwindex[3]; // triangle unsigned char mattindex; // index into MATT0000 chunk unsigned char unknown; // seems to be garbage unsigned int group; // faces seem to be grouped, possibly for smoothing? } pskface_t; typedef struct pskmatt_s { char name[64]; int unknown[6]; // observed 0 0 0 0 5 0 } pskmatt_t; typedef struct pskpose_s { float quat[4]; float origin[3]; float unknown; // probably a float, always seems to be 0 float size[3]; } pskpose_t; typedef struct pskboneinfo_s { char name[64]; int unknown1; int numchildren; int parent; // root bones have 0 here pskpose_t basepose; } pskboneinfo_t; typedef struct pskrawweights_s { float weight; int pntsindex; int boneindex; } pskrawweights_t; typedef struct pskaniminfo_s { char name[64]; char group[64]; int numbones; int unknown1; int unknown2; int unknown3; float unknown4; float playtime; // not really needed float fps; // frames per second int unknown5; int firstframe; int numframes; // firstanimkeys = (firstframe + frameindex) * numbones } pskaniminfo_t; typedef struct pskanimkeys_s { float origin[3]; float quat[4]; float frametime; } pskanimkeys_t; #endif darkplaces/modelgen.h0000664000175000017500000000540013067716222014127 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // // modelgen.h: header file for model generation program // // ********************************************************* // * This file must be identical in the modelgen directory * // * and in the Quake directory, because it's used to * // * pass data from one to the other via model files. * // ********************************************************* #ifndef MODELGEN_H #define MODELGEN_H #define ALIAS_VERSION 6 #define ALIAS_ONSEAM 0x0020 typedef enum aliasframetype_e { ALIAS_SINGLE=0, ALIAS_GROUP } aliasframetype_t; typedef enum aliasskintype_e { ALIAS_SKIN_SINGLE=0, ALIAS_SKIN_GROUP } aliasskintype_t; typedef struct mdl_s { int ident; int version; vec3_t scale; vec3_t scale_origin; float boundingradius; vec3_t eyeposition; int numskins; int skinwidth; int skinheight; int numverts; int numtris; int numframes; synctype_t synctype; int flags; float size; } mdl_t; // TODO: could be shorts typedef struct stvert_s { int onseam; int s; int t; } stvert_t; typedef struct dtriangle_s { int facesfront; int vertindex[3]; } dtriangle_t; #define DT_FACES_FRONT 0x0010 // This mirrors trivert_t in trilib.h, is present so Quake knows how to // load this data typedef struct trivertx_s { unsigned char v[3]; unsigned char lightnormalindex; } trivertx_t; typedef struct daliasframe_s { trivertx_t bboxmin; // lightnormal isn't used trivertx_t bboxmax; // lightnormal isn't used char name[16]; // frame name from grabbing } daliasframe_t; typedef struct daliasgroup_s { int numframes; trivertx_t bboxmin; // lightnormal isn't used trivertx_t bboxmax; // lightnormal isn't used } daliasgroup_t; typedef struct daliasskingroup_s { int numskins; } daliasskingroup_t; typedef struct daliasinterval_s { float interval; } daliasinterval_t; typedef struct daliasskininterval_s { float interval; } daliasskininterval_t; typedef struct daliasframetype_s { aliasframetype_t type; } daliasframetype_t; typedef struct daliasskintype_s { aliasskintype_t type; } daliasskintype_t; #endif darkplaces/r_lerpanim.h0000664000175000017500000000000013067716222014454 0ustar kalevkalevdarkplaces/vs2012_win32.props0000664000175000017500000000130213067716222015225 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL-1.2\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;C:\dev\SDL-1.2\lib\x86;$(LibraryPath) <_PropertySheetDisplayName>vs2012_win32 darkplaces/cap_avi.h0000664000175000017500000000005413067716216013742 0ustar kalevkalevvoid SCR_CaptureVideo_Avi_BeginVideo(void); darkplaces/hmac.c0000664000175000017500000000236413067716220013244 0ustar kalevkalev#include "quakedef.h" #include "hmac.h" qboolean hmac( hashfunc_t hfunc, int hlen, int hblock, unsigned char *out, const unsigned char *in, int n, const unsigned char *key, int k ) { unsigned char hashbuf[32]; unsigned char k_xor_ipad[128]; unsigned char k_xor_opad[128]; unsigned char *catbuf; int i; if(sizeof(hashbuf) < (size_t) hlen) return false; if(sizeof(k_xor_ipad) < (size_t) hblock) return false; if(sizeof(k_xor_ipad) < (size_t) hlen) return false; catbuf = (unsigned char *)Mem_Alloc(tempmempool, (size_t) hblock + max((size_t) hlen, (size_t) n)); if(k > hblock) { // hash the key if it is too long hfunc(k_xor_opad, key, k); key = k_xor_opad; k = hlen; } if(k < hblock) { // zero pad the key if it is too short if(key != k_xor_opad) memcpy(k_xor_opad, key, k); for(i = k; i < hblock; ++i) k_xor_opad[i] = 0; key = k_xor_opad; k = hblock; } for(i = 0; i < hblock; ++i) { k_xor_ipad[i] = key[i] ^ 0x36; k_xor_opad[i] = key[i] ^ 0x5c; } memcpy(catbuf, k_xor_ipad, hblock); memcpy(catbuf + hblock, in, n); hfunc(hashbuf, catbuf, hblock + n); memcpy(catbuf, k_xor_opad, hblock); memcpy(catbuf + hblock, hashbuf, hlen); hfunc(out, catbuf, hblock + hlen); Mem_Free(catbuf); return true; } darkplaces/sys_linux.c0000664000175000017500000000674113067716222014376 0ustar kalevkalev #ifdef WIN32 #include #include #include #include "conio.h" #else #include #include #include #endif #include #include "quakedef.h" // ======================================================================= // General routines // ======================================================================= void Sys_Shutdown (void) { #ifdef FNDELAY fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); #endif fflush(stdout); } void Sys_Error (const char *error, ...) { va_list argptr; char string[MAX_INPUTLINE]; // change stdin to non blocking #ifdef FNDELAY fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); #endif va_start (argptr,error); dpvsnprintf (string, sizeof (string), error, argptr); va_end (argptr); Con_Printf ("Quake Error: %s\n", string); Host_Shutdown (); exit (1); } static int outfd = 1; void Sys_PrintToTerminal(const char *text) { if(outfd < 0) return; #ifdef FNDELAY // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). // this is because both go to /dev/tty by default! { int origflags = fcntl (outfd, F_GETFL, 0); fcntl (outfd, F_SETFL, origflags & ~FNDELAY); #endif #ifdef WIN32 #define write _write #endif while(*text) { fs_offset_t written = (fs_offset_t)write(outfd, text, (int)strlen(text)); if(written <= 0) break; // sorry, I cannot do anything about this error - without an output text += written; } #ifdef FNDELAY fcntl (outfd, F_SETFL, origflags); } #endif //fprintf(stdout, "%s", text); } char *Sys_ConsoleInput(void) { //if (cls.state == ca_dedicated) { static char text[MAX_INPUTLINE]; static unsigned int len = 0; #ifdef WIN32 int c; // read a line out while (_kbhit ()) { c = _getch (); if (c == '\r') { text[len] = '\0'; _putch ('\n'); len = 0; return text; } if (c == '\b') { if (len) { _putch (c); _putch (' '); _putch (c); len--; } continue; } if (len < sizeof (text) - 1) { _putch (c); text[len] = c; len++; } } #else fd_set fdset; struct timeval timeout; FD_ZERO(&fdset); FD_SET(0, &fdset); // stdin timeout.tv_sec = 0; timeout.tv_usec = 0; if (select (1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(0, &fdset)) { len = read (0, text, sizeof(text) - 1); if (len >= 1) { // rip off the \n and terminate // div0: WHY? console code can deal with \n just fine // this caused problems with pasting stuff into a terminal window // so, not ripping off the \n, but STILL keeping a NUL terminator text[len] = 0; return text; } } #endif } return NULL; } char *Sys_GetClipboardData (void) { return NULL; } void Sys_InitConsole (void) { } int main (int argc, char **argv) { signal(SIGFPE, SIG_IGN); com_argc = argc; com_argv = (const char **)argv; Sys_ProvideSelfFD(); // COMMANDLINEOPTION: sdl: -noterminal disables console output on stdout if(COM_CheckParm("-noterminal")) outfd = -1; // COMMANDLINEOPTION: sdl: -stderr moves console output to stderr else if(COM_CheckParm("-stderr")) outfd = 2; else outfd = 1; #ifdef FNDELAY fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); #endif Host_Main(); return 0; } qboolean sys_supportsdlgetticks = false; unsigned int Sys_SDL_GetTicks (void) { Sys_Error("Called Sys_SDL_GetTicks on non-SDL target"); return 0; } void Sys_SDL_Delay (unsigned int milliseconds) { Sys_Error("Called Sys_SDL_Delay on non-SDL target"); } darkplaces/cap_avi.c0000664000175000017500000006775513067716216013762 0ustar kalevkalev#include "quakedef.h" #include "cap_avi.h" #define AVI_MASTER_INDEX_SIZE 640 // GB ought to be enough for anyone typedef struct capturevideostate_avi_formatspecific_s { // AVI stuff fs_offset_t videofile_firstchunkframes_offset; fs_offset_t videofile_totalframes_offset1; fs_offset_t videofile_totalframes_offset2; fs_offset_t videofile_totalsampleframes_offset; int videofile_ix_master_audio_inuse; fs_offset_t videofile_ix_master_audio_inuse_offset; fs_offset_t videofile_ix_master_audio_start_offset; int videofile_ix_master_video_inuse; fs_offset_t videofile_ix_master_video_inuse_offset; fs_offset_t videofile_ix_master_video_start_offset; fs_offset_t videofile_ix_movistart; fs_offset_t position; qboolean canseek; sizebuf_t riffbuffer; unsigned char riffbufferdata[128]; sizebuf_t riffindexbuffer; int riffstacklevel; fs_offset_t riffstackstartoffset[4]; fs_offset_t riffstacksizehint[4]; const char *riffstackfourcc[4]; } capturevideostate_avi_formatspecific_t; #define LOAD_FORMATSPECIFIC_AVI() capturevideostate_avi_formatspecific_t *format = (capturevideostate_avi_formatspecific_t *) cls.capturevideo.formatspecific static void SCR_CaptureVideo_RIFF_Start(void) { LOAD_FORMATSPECIFIC_AVI(); memset(&format->riffbuffer, 0, sizeof(sizebuf_t)); format->riffbuffer.maxsize = sizeof(format->riffbufferdata); format->riffbuffer.data = format->riffbufferdata; format->position = 0; } static void SCR_CaptureVideo_RIFF_Flush(void) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize > 0) { if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize)) cls.capturevideo.error = true; format->position += format->riffbuffer.cursize; format->riffbuffer.cursize = 0; format->riffbuffer.overflowed = false; } } static void SCR_CaptureVideo_RIFF_FlushNoIncrease(void) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize > 0) { if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize)) cls.capturevideo.error = true; format->riffbuffer.cursize = 0; format->riffbuffer.overflowed = false; } } static void SCR_CaptureVideo_RIFF_WriteBytes(const unsigned char *data, size_t size) { LOAD_FORMATSPECIFIC_AVI(); SCR_CaptureVideo_RIFF_Flush(); if (!FS_Write(cls.capturevideo.videofile, data, size)) cls.capturevideo.error = true; format->position += size; } static void SCR_CaptureVideo_RIFF_Write32(int n) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize + 4 > format->riffbuffer.maxsize) SCR_CaptureVideo_RIFF_Flush(); MSG_WriteLong(&format->riffbuffer, n); } static void SCR_CaptureVideo_RIFF_Write16(int n) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize + 2 > format->riffbuffer.maxsize) SCR_CaptureVideo_RIFF_Flush(); MSG_WriteShort(&format->riffbuffer, n); } static void SCR_CaptureVideo_RIFF_WriteFourCC(const char *chunkfourcc) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize + (int)strlen(chunkfourcc) > format->riffbuffer.maxsize) SCR_CaptureVideo_RIFF_Flush(); MSG_WriteUnterminatedString(&format->riffbuffer, chunkfourcc); } static void SCR_CaptureVideo_RIFF_WriteTerminatedString(const char *string) { LOAD_FORMATSPECIFIC_AVI(); if (format->riffbuffer.cursize + (int)strlen(string) > format->riffbuffer.maxsize) SCR_CaptureVideo_RIFF_Flush(); MSG_WriteString(&format->riffbuffer, string); } static fs_offset_t SCR_CaptureVideo_RIFF_GetPosition(void) { LOAD_FORMATSPECIFIC_AVI(); SCR_CaptureVideo_RIFF_Flush(); //return FS_Tell(cls.capturevideo.videofile); return format->position; } static void SCR_CaptureVideo_RIFF_Push(const char *chunkfourcc, const char *listtypefourcc, fs_offset_t sizeHint) { LOAD_FORMATSPECIFIC_AVI(); if (listtypefourcc && sizeHint >= 0) sizeHint += 4; // size hint is for INNER size SCR_CaptureVideo_RIFF_WriteFourCC(chunkfourcc); SCR_CaptureVideo_RIFF_Write32(sizeHint); SCR_CaptureVideo_RIFF_Flush(); format->riffstacksizehint[format->riffstacklevel] = sizeHint; format->riffstackstartoffset[format->riffstacklevel] = SCR_CaptureVideo_RIFF_GetPosition(); format->riffstackfourcc[format->riffstacklevel] = chunkfourcc; ++format->riffstacklevel; if (listtypefourcc) SCR_CaptureVideo_RIFF_WriteFourCC(listtypefourcc); } static void SCR_CaptureVideo_RIFF_Pop(void) { LOAD_FORMATSPECIFIC_AVI(); fs_offset_t offset, sizehint; int x; unsigned char sizebytes[4]; // write out the chunk size and then return to the current file position format->riffstacklevel--; offset = SCR_CaptureVideo_RIFF_GetPosition(); sizehint = format->riffstacksizehint[format->riffstacklevel]; x = (int)(offset - (format->riffstackstartoffset[format->riffstacklevel])); if(x != sizehint) { if(sizehint != -1) { int i; Con_Printf("WARNING: invalid size hint %d when writing video data (actual size: %d)\n", (int) sizehint, x); for(i = 0; i <= format->riffstacklevel; ++i) { Con_Printf(" RIFF level %d = %s\n", i, format->riffstackfourcc[i]); } } sizebytes[0] = (x) & 0xff;sizebytes[1] = (x >> 8) & 0xff;sizebytes[2] = (x >> 16) & 0xff;sizebytes[3] = (x >> 24) & 0xff; if(FS_Seek(cls.capturevideo.videofile, -(x + 4), SEEK_END) >= 0) { FS_Write(cls.capturevideo.videofile, sizebytes, 4); } FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); } if (offset & 1) { SCR_CaptureVideo_RIFF_WriteBytes((unsigned char *) "\0", 1); } } static void GrowBuf(sizebuf_t *buf, int extralen) { if(buf->cursize + extralen > buf->maxsize) { int oldsize = buf->maxsize; unsigned char *olddata; olddata = buf->data; buf->maxsize = max(buf->maxsize * 2, 4096); buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize); if(olddata) { memcpy(buf->data, olddata, oldsize); Mem_Free(olddata); } } } static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags) { LOAD_FORMATSPECIFIC_AVI(); if(!format->canseek) Sys_Error("SCR_CaptureVideo_RIFF_IndexEntry called on non-seekable AVI"); if (format->riffstacklevel != 2) Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", format->riffstacklevel); GrowBuf(&format->riffindexbuffer, 16); SCR_CaptureVideo_RIFF_Flush(); MSG_WriteUnterminatedString(&format->riffindexbuffer, chunkfourcc); MSG_WriteLong(&format->riffindexbuffer, flags); MSG_WriteLong(&format->riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - format->riffstackstartoffset[1]); MSG_WriteLong(&format->riffindexbuffer, chunksize); } static void SCR_CaptureVideo_RIFF_MakeIxChunk(const char *fcc, const char *dwChunkId, fs_offset_t masteridx_counter, int *masteridx_count, fs_offset_t masteridx_start) { LOAD_FORMATSPECIFIC_AVI(); int nMatching; int i; fs_offset_t ix = SCR_CaptureVideo_RIFF_GetPosition(); fs_offset_t pos, sz; if(!format->canseek) Sys_Error("SCR_CaptureVideo_RIFF_MakeIxChunk called on non-seekable AVI"); if(*masteridx_count >= AVI_MASTER_INDEX_SIZE) return; nMatching = 0; // go through index and enumerate them for(i = 0; i < format->riffindexbuffer.cursize; i += 16) if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) ++nMatching; sz = 2+2+4+4+4+4+4; for(i = 0; i < format->riffindexbuffer.cursize; i += 16) if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) sz += 8; SCR_CaptureVideo_RIFF_Push(fcc, NULL, sz); SCR_CaptureVideo_RIFF_Write16(2); // wLongsPerEntry SCR_CaptureVideo_RIFF_Write16(0x0100); // bIndexType=1, bIndexSubType=0 SCR_CaptureVideo_RIFF_Write32(nMatching); // nEntriesInUse SCR_CaptureVideo_RIFF_WriteFourCC(dwChunkId); // dwChunkId SCR_CaptureVideo_RIFF_Write32(format->videofile_ix_movistart & (fs_offset_t) 0xFFFFFFFFu); SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) format->videofile_ix_movistart) >> 32); SCR_CaptureVideo_RIFF_Write32(0); // dwReserved for(i = 0; i < format->riffindexbuffer.cursize; i += 16) if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4)) { unsigned int *p = (unsigned int *) (format->riffindexbuffer.data + i); unsigned int flags = p[1]; unsigned int rpos = p[2]; unsigned int size = p[3]; size &= ~0x80000000; if(!(flags & 0x10)) // no keyframe? size |= 0x80000000; SCR_CaptureVideo_RIFF_Write32(rpos + 8); SCR_CaptureVideo_RIFF_Write32(size); } SCR_CaptureVideo_RIFF_Flush(); SCR_CaptureVideo_RIFF_Pop(); pos = SCR_CaptureVideo_RIFF_GetPosition(); if(FS_Seek(cls.capturevideo.videofile, masteridx_start + 16 * *masteridx_count, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(ix & (fs_offset_t) 0xFFFFFFFFu); SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) ix) >> 32); SCR_CaptureVideo_RIFF_Write32(pos - ix); SCR_CaptureVideo_RIFF_Write32(nMatching); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } if(FS_Seek(cls.capturevideo.videofile, masteridx_counter, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(++*masteridx_count); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); // return value doesn't matter here } static void SCR_CaptureVideo_RIFF_Finish(qboolean final) { LOAD_FORMATSPECIFIC_AVI(); // close the "movi" list SCR_CaptureVideo_RIFF_Pop(); if(format->videofile_ix_master_video_inuse_offset) SCR_CaptureVideo_RIFF_MakeIxChunk("ix00", "00dc", format->videofile_ix_master_video_inuse_offset, &format->videofile_ix_master_video_inuse, format->videofile_ix_master_video_start_offset); if(format->videofile_ix_master_audio_inuse_offset) SCR_CaptureVideo_RIFF_MakeIxChunk("ix01", "01wb", format->videofile_ix_master_audio_inuse_offset, &format->videofile_ix_master_audio_inuse, format->videofile_ix_master_audio_start_offset); // write the idx1 chunk that we've been building while saving the frames (for old style players) if(final && format->videofile_firstchunkframes_offset) // TODO replace index creating by OpenDML ix##/##ix/indx chunk so it works for more than one AVI part too { SCR_CaptureVideo_RIFF_Push("idx1", NULL, format->riffindexbuffer.cursize); SCR_CaptureVideo_RIFF_WriteBytes(format->riffindexbuffer.data, format->riffindexbuffer.cursize); SCR_CaptureVideo_RIFF_Pop(); } format->riffindexbuffer.cursize = 0; // pop the RIFF chunk itself while (format->riffstacklevel > 0) SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Flush(); if(format->videofile_firstchunkframes_offset) { Con_DPrintf("Finishing first chunk (%d frames)\n", cls.capturevideo.frame); if(FS_Seek(cls.capturevideo.videofile, format->videofile_firstchunkframes_offset, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); format->videofile_firstchunkframes_offset = 0; } else Con_DPrintf("Finishing another chunk (%d frames)\n", cls.capturevideo.frame); } static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize) { LOAD_FORMATSPECIFIC_AVI(); fs_offset_t cursize; //fs_offset_t curfilesize; if (format->riffstacklevel != 2) Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n"); if(!format->canseek) return; // check where we are in the file SCR_CaptureVideo_RIFF_Flush(); cursize = SCR_CaptureVideo_RIFF_GetPosition() - format->riffstackstartoffset[0]; //curfilesize = SCR_CaptureVideo_RIFF_GetPosition(); // if this would overflow the windows limit of 1GB per RIFF chunk, we need // to close the current RIFF chunk and open another for future frames if (8 + cursize + framesize + format->riffindexbuffer.cursize + 8 + format->riffindexbuffer.cursize + 64 > 1<<30) // note that the Ix buffer takes less space... I just don't dare to / 2 here now... sorry, maybe later { SCR_CaptureVideo_RIFF_Finish(false); // begin a new 1GB extended section of the AVI SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", -1); SCR_CaptureVideo_RIFF_Push("LIST", "movi", -1); format->videofile_ix_movistart = format->riffstackstartoffset[1]; } } // converts from BGRA32 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows static void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart) { int x, y; int blockr, blockg, blockb; int outoffset = (width/2)*(height/2); unsigned char *b, *out; // process one line at a time, and CbCr every other line at 2 pixel intervals for (y = 0;y < height;y++) { // 1x1 Y for (b = instart + (height-1-y)*width*4, out = outstart + y*width, x = 0;x < width;x++, b += 4, out++) { blockr = b[2]; blockg = b[1]; blockb = b[0]; *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; } if ((y & 1) == 0 && y/2 < height/2) // if h is odd, this skips the last row { // 2x2 Cr and Cb planes int inpitch = width*4; for (b = instart + (height-2-y)*width*4, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 8, out++) { blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; // Cr out[0 ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; // Cb out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; } } } } static void SCR_CaptureVideo_Avi_VideoFrames(int num) { LOAD_FORMATSPECIFIC_AVI(); int x = 0, width = cls.capturevideo.width, height = cls.capturevideo.height; unsigned char *in, *out; // FIXME: width/height must be multiple of 2, enforce this? in = cls.capturevideo.outbuffer; out = cls.capturevideo.outbuffer + width*height*4; SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out); x = width*height+(width/2)*(height/2)*2; while(num-- > 0) { if(format->canseek) { SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); SCR_CaptureVideo_RIFF_IndexEntry("00dc", x, 0x10); // AVIIF_KEYFRAME } if(!format->canseek) { SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x); SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x); } SCR_CaptureVideo_RIFF_Push("00dc", NULL, x); SCR_CaptureVideo_RIFF_WriteBytes(out, x); SCR_CaptureVideo_RIFF_Pop(); if(!format->canseek) { SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); } } } static void SCR_CaptureVideo_Avi_EndVideo(void) { LOAD_FORMATSPECIFIC_AVI(); if(format->canseek) { // close any open chunks SCR_CaptureVideo_RIFF_Finish(true); // go back and fix the video frames and audio samples fields if(format->videofile_totalframes_offset1) if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset1, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } if(format->videofile_totalframes_offset2) if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset2, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } if (cls.capturevideo.soundrate) { if(format->videofile_totalsampleframes_offset) if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalsampleframes_offset, SEEK_SET) >= 0) { SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundsampleframe); SCR_CaptureVideo_RIFF_FlushNoIncrease(); } } } if (format->riffindexbuffer.data) { Mem_Free(format->riffindexbuffer.data); format->riffindexbuffer.data = NULL; } FS_Close(cls.capturevideo.videofile); cls.capturevideo.videofile = NULL; Mem_Free(format); } static void SCR_CaptureVideo_Avi_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) { LOAD_FORMATSPECIFIC_AVI(); int x; unsigned char bufstereo16le[PAINTBUFFER_SIZE * 4]; unsigned char* out_ptr; size_t i; // write the sound buffer as little endian 16bit interleaved stereo for(i = 0, out_ptr = bufstereo16le; i < length; i++, out_ptr += 4) { int n0, n1; n0 = paintbuffer[i].sample[0] * 32768.0f; n0 = bound(-32768, n0, 32767); out_ptr[0] = (unsigned char)n0; out_ptr[1] = (unsigned char)(n0 >> 8); n1 = paintbuffer[i].sample[1] * 32768.0f; n1 = bound(-32768, n1, 32767); out_ptr[2] = (unsigned char)n1; out_ptr[3] = (unsigned char)(n1 >> 8); } x = (int)length*4; if(format->canseek) { SCR_CaptureVideo_RIFF_OverflowCheck(8 + x); SCR_CaptureVideo_RIFF_IndexEntry("01wb", x, 0x10); // AVIIF_KEYFRAME } if(!format->canseek) { SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x); SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x); } SCR_CaptureVideo_RIFF_Push("01wb", NULL, x); SCR_CaptureVideo_RIFF_WriteBytes(bufstereo16le, x); SCR_CaptureVideo_RIFF_Pop(); if(!format->canseek) { SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); } } void SCR_CaptureVideo_Avi_BeginVideo(void) { int width = cls.capturevideo.width; int height = cls.capturevideo.height; int n, d; unsigned int i; double aspect; char vabuf[1024]; aspect = vid.width / (vid.height * vid_pixelheight.value); cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420; cls.capturevideo.formatextension = "avi"; cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); cls.capturevideo.endvideo = SCR_CaptureVideo_Avi_EndVideo; cls.capturevideo.videoframes = SCR_CaptureVideo_Avi_VideoFrames; cls.capturevideo.soundframe = SCR_CaptureVideo_Avi_SoundFrame; cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_avi_formatspecific_t)); { LOAD_FORMATSPECIFIC_AVI(); format->canseek = (FS_Seek(cls.capturevideo.videofile, 0, SEEK_SET) == 0); SCR_CaptureVideo_RIFF_Start(); // enclosing RIFF chunk (there can be multiple of these in >1GB files, the later ones are "AVIX" instead of "AVI " and have no header/stream info) SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ", format->canseek ? -1 : 12+(8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4))+12+(8+(((int) strlen(engineversion) | 1) + 1))+12); // AVI main header SCR_CaptureVideo_RIFF_Push("LIST", "hdrl", format->canseek ? -1 : 8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4)); SCR_CaptureVideo_RIFF_Push("avih", NULL, 56); SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / (cls.capturevideo.framerate / cls.capturevideo.framestep))); // microseconds per frame SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second SCR_CaptureVideo_RIFF_Write32(0); // padding granularity SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE) format->videofile_firstchunkframes_offset = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0); // total frames SCR_CaptureVideo_RIFF_Write32(0); // initial frames if (cls.capturevideo.soundrate) SCR_CaptureVideo_RIFF_Write32(2); // number of streams else SCR_CaptureVideo_RIFF_Write32(1); // number of streams SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size SCR_CaptureVideo_RIFF_Write32(width); // width SCR_CaptureVideo_RIFF_Write32(height); // height SCR_CaptureVideo_RIFF_Write32(0); // reserved[0] SCR_CaptureVideo_RIFF_Write32(0); // reserved[1] SCR_CaptureVideo_RIFF_Write32(0); // reserved[2] SCR_CaptureVideo_RIFF_Write32(0); // reserved[3] SCR_CaptureVideo_RIFF_Pop(); // video stream info SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+40+8+68); SCR_CaptureVideo_RIFF_Push("strh", "vids", 52); SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed) SCR_CaptureVideo_RIFF_Write32(0); // flags SCR_CaptureVideo_RIFF_Write16(0); // priority SCR_CaptureVideo_RIFF_Write16(0); // language SCR_CaptureVideo_RIFF_Write32(0); // initial frames // find an ideal divisor for the framerate FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &n, &d, 1000); SCR_CaptureVideo_RIFF_Write32(d); // samples/second divisor SCR_CaptureVideo_RIFF_Write32(n); // samples/second multiplied by divisor SCR_CaptureVideo_RIFF_Write32(0); // start format->videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size SCR_CaptureVideo_RIFF_Write32(0); // quality SCR_CaptureVideo_RIFF_Write32(0); // sample size SCR_CaptureVideo_RIFF_Write16(0); // frame left SCR_CaptureVideo_RIFF_Write16(0); // frame top SCR_CaptureVideo_RIFF_Write16(width); // frame right SCR_CaptureVideo_RIFF_Write16(height); // frame bottom SCR_CaptureVideo_RIFF_Pop(); // video stream format SCR_CaptureVideo_RIFF_Push("strf", NULL, 40); SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size SCR_CaptureVideo_RIFF_Write32(width); // width SCR_CaptureVideo_RIFF_Write32(height); // height SCR_CaptureVideo_RIFF_Write16(3); // planes SCR_CaptureVideo_RIFF_Write16(12); // bitcount SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter SCR_CaptureVideo_RIFF_Write32(0); // color used SCR_CaptureVideo_RIFF_Write32(0); // color important SCR_CaptureVideo_RIFF_Pop(); // master index if(format->canseek) { SCR_CaptureVideo_RIFF_Push("indx", NULL, -1); SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 format->videofile_ix_master_video_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse SCR_CaptureVideo_RIFF_WriteFourCC("00dc"); // dwChunkId SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 format->videofile_ix_master_video_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) SCR_CaptureVideo_RIFF_Write32(0); // fill up later SCR_CaptureVideo_RIFF_Pop(); } // extended format (aspect!) SCR_CaptureVideo_RIFF_Push("vprp", NULL, 68); SCR_CaptureVideo_RIFF_Write32(0); // VideoFormatToken SCR_CaptureVideo_RIFF_Write32(0); // VideoStandard SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate / cls.capturevideo.framestep)); // dwVerticalRefreshRate (bogus) SCR_CaptureVideo_RIFF_Write32(width); // dwHTotalInT SCR_CaptureVideo_RIFF_Write32(height); // dwVTotalInLines FindFraction(aspect, &n, &d, 1000); SCR_CaptureVideo_RIFF_Write32((n << 16) | d); // dwFrameAspectRatio // TODO a word SCR_CaptureVideo_RIFF_Write32(width); // dwFrameWidthInPixels SCR_CaptureVideo_RIFF_Write32(height); // dwFrameHeightInLines SCR_CaptureVideo_RIFF_Write32(1); // nFieldPerFrame SCR_CaptureVideo_RIFF_Write32(width); // CompressedBMWidth SCR_CaptureVideo_RIFF_Write32(height); // CompressedBMHeight SCR_CaptureVideo_RIFF_Write32(width); // ValidBMHeight SCR_CaptureVideo_RIFF_Write32(height); // ValidBMWidth SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffset SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYOffset SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffsetInT SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYValidStartLine SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); if (cls.capturevideo.soundrate) { // audio stream info SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+18); SCR_CaptureVideo_RIFF_Push("strh", "auds", 52); SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed) SCR_CaptureVideo_RIFF_Write32(0); // flags SCR_CaptureVideo_RIFF_Write16(0); // priority SCR_CaptureVideo_RIFF_Write16(0); // language SCR_CaptureVideo_RIFF_Write32(0); // initial frames SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor SCR_CaptureVideo_RIFF_Write32(0); // start format->videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second) SCR_CaptureVideo_RIFF_Write32(0); // quality SCR_CaptureVideo_RIFF_Write32(4); // sample size SCR_CaptureVideo_RIFF_Write16(0); // frame left SCR_CaptureVideo_RIFF_Write16(0); // frame top SCR_CaptureVideo_RIFF_Write16(0); // frame right SCR_CaptureVideo_RIFF_Write16(0); // frame bottom SCR_CaptureVideo_RIFF_Pop(); // audio stream format SCR_CaptureVideo_RIFF_Push("strf", NULL, 18); SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?) SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo) SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second SCR_CaptureVideo_RIFF_Write16(4); // block align SCR_CaptureVideo_RIFF_Write16(16); // bits per sample SCR_CaptureVideo_RIFF_Write16(0); // size SCR_CaptureVideo_RIFF_Pop(); // master index if(format->canseek) { SCR_CaptureVideo_RIFF_Push("indx", NULL, -1); SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0 format->videofile_ix_master_audio_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse SCR_CaptureVideo_RIFF_WriteFourCC("01wb"); // dwChunkId SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2 SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3 format->videofile_ix_master_audio_start_offset = SCR_CaptureVideo_RIFF_GetPosition(); for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i) SCR_CaptureVideo_RIFF_Write32(0); // fill up later SCR_CaptureVideo_RIFF_Pop(); } SCR_CaptureVideo_RIFF_Pop(); } format->videofile_ix_master_audio_inuse = format->videofile_ix_master_video_inuse = 0; // extended header (for total #frames) SCR_CaptureVideo_RIFF_Push("LIST", "odml", 8+4); SCR_CaptureVideo_RIFF_Push("dmlh", NULL, 4); format->videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition(); SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); SCR_CaptureVideo_RIFF_Pop(); SCR_CaptureVideo_RIFF_Pop(); // close the AVI header list SCR_CaptureVideo_RIFF_Pop(); // software that produced this AVI video file SCR_CaptureVideo_RIFF_Push("LIST", "INFO", 8+((strlen(engineversion) | 1) + 1)); SCR_CaptureVideo_RIFF_Push("ISFT", NULL, strlen(engineversion) + 1); SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion); SCR_CaptureVideo_RIFF_Pop(); // enable this junk filler if you like the LIST movi to always begin at 4KB in the file (why?) #if 0 SCR_CaptureVideo_RIFF_Push("JUNK", NULL); x = 4096 - SCR_CaptureVideo_RIFF_GetPosition(); while (x > 0) { const char *junkfiller = "[ DarkPlaces junk data ]"; int i = min(x, (int)strlen(junkfiller)); SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i); x -= i; } SCR_CaptureVideo_RIFF_Pop(); #endif SCR_CaptureVideo_RIFF_Pop(); // begin the actual video section now SCR_CaptureVideo_RIFF_Push("LIST", "movi", format->canseek ? -1 : 0); format->videofile_ix_movistart = format->riffstackstartoffset[1]; // we're done with the headers now... SCR_CaptureVideo_RIFF_Flush(); if (format->riffstacklevel != 2) Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", format->riffstacklevel); if(!format->canseek) { // close the movi immediately SCR_CaptureVideo_RIFF_Pop(); // close the AVI immediately (we'll put all frames into AVIX) SCR_CaptureVideo_RIFF_Pop(); } } } darkplaces/snd_ogg.c0000664000175000017500000004437113067716222013762 0ustar kalevkalev/* Copyright (C) 2003-2005 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "quakedef.h" #include "snd_main.h" #include "snd_ogg.h" #include "snd_wav.h" #ifdef LINK_TO_LIBVORBIS #define OV_EXCLUDE_STATIC_CALLBACKS #include #include #define qov_clear ov_clear #define qov_info ov_info #define qov_comment ov_comment #define qov_open_callbacks ov_open_callbacks #define qov_pcm_seek ov_pcm_seek #define qov_pcm_total ov_pcm_total #define qov_read ov_read #define qvorbis_comment_query vorbis_comment_query qboolean OGG_OpenLibrary (void) {return true;} void OGG_CloseLibrary (void) {} #else /* ================================================================= Minimal set of definitions from the Ogg Vorbis lib (C) COPYRIGHT 1994-2001 by the XIPHOPHORUS Company http://www.xiph.org/ WARNING: for a matter of simplicity, several pointer types are casted to "void*", and most enumerated values are not included ================================================================= */ #ifdef _MSC_VER typedef __int64 ogg_int64_t; #else typedef long long ogg_int64_t; #endif typedef struct { size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource); int (*seek_func) (void *datasource, ogg_int64_t offset, int whence); int (*close_func) (void *datasource); long (*tell_func) (void *datasource); } ov_callbacks; typedef struct { unsigned char *data; int storage; int fill; int returned; int unsynced; int headerbytes; int bodybytes; } ogg_sync_state; typedef struct { int version; int channels; long rate; long bitrate_upper; long bitrate_nominal; long bitrate_lower; long bitrate_window; void *codec_setup; } vorbis_info; typedef struct { unsigned char *body_data; long body_storage; long body_fill; long body_returned; int *lacing_vals; ogg_int64_t *granule_vals; long lacing_storage; long lacing_fill; long lacing_packet; long lacing_returned; unsigned char header[282]; int header_fill; int e_o_s; int b_o_s; long serialno; long pageno; ogg_int64_t packetno; ogg_int64_t granulepos; } ogg_stream_state; typedef struct { int analysisp; vorbis_info *vi; float **pcm; float **pcmret; int pcm_storage; int pcm_current; int pcm_returned; int preextrapolate; int eofflag; long lW; long W; long nW; long centerW; ogg_int64_t granulepos; ogg_int64_t sequence; ogg_int64_t glue_bits; ogg_int64_t time_bits; ogg_int64_t floor_bits; ogg_int64_t res_bits; void *backend_state; } vorbis_dsp_state; typedef struct { long endbyte; int endbit; unsigned char *buffer; unsigned char *ptr; long storage; } oggpack_buffer; typedef struct { float **pcm; oggpack_buffer opb; long lW; long W; long nW; int pcmend; int mode; int eofflag; ogg_int64_t granulepos; ogg_int64_t sequence; vorbis_dsp_state *vd; void *localstore; long localtop; long localalloc; long totaluse; void *reap; // VOIDED POINTER long glue_bits; long time_bits; long floor_bits; long res_bits; void *internal; } vorbis_block; typedef struct { char **user_comments; int *comment_lengths; int comments; char *vendor; } vorbis_comment; typedef struct { void *datasource; int seekable; ogg_int64_t offset; ogg_int64_t end; ogg_sync_state oy; int links; ogg_int64_t *offsets; ogg_int64_t *dataoffsets; long *serialnos; ogg_int64_t *pcmlengths; vorbis_info *vi; vorbis_comment *vc; ogg_int64_t pcm_offset; int ready_state; long current_serialno; int current_link; double bittrack; double samptrack; ogg_stream_state os; vorbis_dsp_state vd; vorbis_block vb; ov_callbacks callbacks; } OggVorbis_File; /* ================================================================= DarkPlaces definitions ================================================================= */ // Functions exported from the vorbisfile library static int (*qov_clear) (OggVorbis_File *vf); static vorbis_info* (*qov_info) (OggVorbis_File *vf,int link); static vorbis_comment* (*qov_comment) (OggVorbis_File *vf,int link); static char * (*qvorbis_comment_query) (vorbis_comment *vc, const char *tag, int count); static int (*qov_open_callbacks) (void *datasource, OggVorbis_File *vf, char *initial, long ibytes, ov_callbacks callbacks); static int (*qov_pcm_seek) (OggVorbis_File *vf,ogg_int64_t pos); static ogg_int64_t (*qov_pcm_total) (OggVorbis_File *vf,int i); static long (*qov_read) (OggVorbis_File *vf,char *buffer,int length, int bigendianp,int word,int sgned,int *bitstream); static dllfunction_t vorbisfilefuncs[] = { {"ov_clear", (void **) &qov_clear}, {"ov_info", (void **) &qov_info}, {"ov_comment", (void **) &qov_comment}, {"ov_open_callbacks", (void **) &qov_open_callbacks}, {"ov_pcm_seek", (void **) &qov_pcm_seek}, {"ov_pcm_total", (void **) &qov_pcm_total}, {"ov_read", (void **) &qov_read}, {NULL, NULL} }; static dllfunction_t vorbisfuncs[] = { {"vorbis_comment_query", (void **) &qvorbis_comment_query}, {NULL, NULL} }; // Handles for the Vorbis and Vorbisfile DLLs static dllhandle_t vo_dll = NULL; static dllhandle_t vf_dll = NULL; /* ================================================================= DLL load & unload ================================================================= */ /* ==================== OGG_OpenLibrary Try to load the VorbisFile DLL ==================== */ qboolean OGG_OpenLibrary (void) { const char* dllnames_vo [] = { #if defined(WIN32) "libvorbis-0.dll", "libvorbis.dll", "vorbis.dll", #elif defined(MACOSX) "libvorbis.dylib", #else "libvorbis.so.0", "libvorbis.so", #endif NULL }; const char* dllnames_vf [] = { #if defined(WIN32) "libvorbisfile-3.dll", "libvorbisfile.dll", "vorbisfile.dll", #elif defined(MACOSX) "libvorbisfile.dylib", #else "libvorbisfile.so.3", "libvorbisfile.so", #endif NULL }; // Already loaded? if (vf_dll) return true; // COMMANDLINEOPTION: Sound: -novorbis disables ogg vorbis sound support if (COM_CheckParm("-novorbis")) return false; // Load the DLLs // We need to load both by hand because some OSes seem to not load // the vorbis DLL automatically when loading the VorbisFile DLL return Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) && Sys_LoadLibrary (dllnames_vf, &vf_dll, vorbisfilefuncs); } /* ==================== OGG_CloseLibrary Unload the VorbisFile DLL ==================== */ void OGG_CloseLibrary (void) { Sys_UnloadLibrary (&vf_dll); Sys_UnloadLibrary (&vo_dll); } #endif /* ================================================================= Ogg Vorbis decoding ================================================================= */ typedef struct { unsigned char *buffer; ogg_int64_t ind, buffsize; } ov_decode_t; static size_t ovcb_read (void *ptr, size_t size, size_t nb, void *datasource) { ov_decode_t *ov_decode = (ov_decode_t*)datasource; size_t remain, len; remain = ov_decode->buffsize - ov_decode->ind; len = size * nb; if (remain < len) len = remain - remain % size; memcpy (ptr, ov_decode->buffer + ov_decode->ind, len); ov_decode->ind += len; return len / size; } static int ovcb_seek (void *datasource, ogg_int64_t offset, int whence) { ov_decode_t *ov_decode = (ov_decode_t*)datasource; switch (whence) { case SEEK_SET: break; case SEEK_CUR: offset += ov_decode->ind; break; case SEEK_END: offset += ov_decode->buffsize; break; default: return -1; } if (offset < 0 || offset > ov_decode->buffsize) return -1; ov_decode->ind = offset; return 0; } static int ovcb_close (void *ov_decode) { return 0; } static long ovcb_tell (void *ov_decode) { return ((ov_decode_t*)ov_decode)->ind; } // Per-sfx data structure typedef struct { unsigned char *file; size_t filesize; } ogg_stream_persfx_t; // Per-channel data structure typedef struct { OggVorbis_File vf; ov_decode_t ov_decode; int bs; int buffer_firstframe; int buffer_numframes; unsigned char buffer[STREAM_BUFFERSIZE*4]; } ogg_stream_perchannel_t; static const ov_callbacks callbacks = {ovcb_read, ovcb_seek, ovcb_close, ovcb_tell}; /* ==================== OGG_GetSamplesFloat ==================== */ static void OGG_GetSamplesFloat (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) { ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; int f = sfx->format.width * sfx->format.channels; // bytes per frame in the buffer short *buf; int i, len; int newlength, done, ret; // if this channel does not yet have a channel fetcher, make one if (per_ch == NULL) { // allocate a struct to keep track of our file position and buffer per_ch = (ogg_stream_perchannel_t *)Mem_Alloc(snd_mempool, sizeof(*per_ch)); // begin decoding the file per_ch->ov_decode.buffer = per_sfx->file; per_ch->ov_decode.ind = 0; per_ch->ov_decode.buffsize = per_sfx->filesize; if (qov_open_callbacks(&per_ch->ov_decode, &per_ch->vf, NULL, 0, callbacks) < 0) { // this never happens - this function succeeded earlier on the same data Mem_Free(per_ch); return; } per_ch->bs = 0; per_ch->buffer_firstframe = 0; per_ch->buffer_numframes = 0; // attach the struct to our channel ch->fetcher_data = (void *)per_ch; } // if the request is too large for our buffer, loop... while (numsampleframes * f > (int)sizeof(per_ch->buffer)) { done = sizeof(per_ch->buffer) / f; OGG_GetSamplesFloat(ch, sfx, firstsampleframe, done, outsamplesfloat); firstsampleframe += done; numsampleframes -= done; outsamplesfloat += done * sfx->format.channels; } // seek if the request is before the current buffer (loop back) // seek if the request starts beyond the current buffer by at least one frame (channel was zero volume for a while) // do not seek if the request overlaps the buffer end at all (expected behavior) if (per_ch->buffer_firstframe > firstsampleframe || per_ch->buffer_firstframe + per_ch->buffer_numframes < firstsampleframe) { // we expect to decode forward from here so this will be our new buffer start per_ch->buffer_firstframe = firstsampleframe; per_ch->buffer_numframes = 0; ret = qov_pcm_seek(&per_ch->vf, (ogg_int64_t)firstsampleframe); if (ret != 0) { // LordHavoc: we can't Con_Printf here, not thread safe... //Con_Printf("OGG_FetchSound: qov_pcm_seek(..., %d) returned %d\n", firstsampleframe, ret); return; } } // decompress the file as needed if (firstsampleframe + numsampleframes > per_ch->buffer_firstframe + per_ch->buffer_numframes) { // first slide the buffer back, discarding any data preceding the range we care about int offset = firstsampleframe - per_ch->buffer_firstframe; int keeplength = per_ch->buffer_numframes - offset; if (keeplength > 0) memmove(per_ch->buffer, per_ch->buffer + offset * sfx->format.width * sfx->format.channels, keeplength * sfx->format.width * sfx->format.channels); per_ch->buffer_firstframe = firstsampleframe; per_ch->buffer_numframes -= offset; // decompress as much as we can fit in the buffer newlength = sizeof(per_ch->buffer) - per_ch->buffer_numframes * f; done = 0; while (newlength > done && (ret = qov_read(&per_ch->vf, (char *)per_ch->buffer + per_ch->buffer_numframes * f + done, (int)(newlength - done), mem_bigendian, 2, 1, &per_ch->bs)) > 0) done += ret; // clear the missing space if any if (done < newlength) memset(per_ch->buffer + done, 0, newlength - done); // we now have more data in the buffer per_ch->buffer_numframes += done / f; } // convert the sample format for the caller buf = (short *)((char *)per_ch->buffer + (firstsampleframe - per_ch->buffer_firstframe) * f); len = numsampleframes * sfx->format.channels; for (i = 0;i < len;i++) outsamplesfloat[i] = buf[i] * (1.0f / 32768.0f); } /* ==================== OGG_StopChannel ==================== */ static void OGG_StopChannel(channel_t *ch) { ogg_stream_perchannel_t *per_ch = (ogg_stream_perchannel_t *)ch->fetcher_data; if (per_ch != NULL) { // release the vorbis decompressor qov_clear(&per_ch->vf); Mem_Free(per_ch); } } /* ==================== OGG_FreeSfx ==================== */ static void OGG_FreeSfx(sfx_t *sfx) { ogg_stream_persfx_t *per_sfx = (ogg_stream_persfx_t *)sfx->fetcher_data; // free the complete file we were keeping around Mem_Free(per_sfx->file); // free the file information structure Mem_Free(per_sfx); } static const snd_fetcher_t ogg_fetcher = {OGG_GetSamplesFloat, OGG_StopChannel, OGG_FreeSfx}; static void OGG_DecodeTags(vorbis_comment *vc, unsigned int *start, unsigned int *length, unsigned int numsamples, double *peak, double *gaindb) { const char *startcomment = NULL, *lengthcomment = NULL, *endcomment = NULL, *thiscomment = NULL; *start = numsamples; *length = numsamples; *peak = 0.0; *gaindb = 0.0; if(!vc) return; thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_PEAK", 0); if(thiscomment) *peak = atof(thiscomment); thiscomment = qvorbis_comment_query(vc, "REPLAYGAIN_TRACK_GAIN", 0); if(thiscomment) *gaindb = atof(thiscomment); startcomment = qvorbis_comment_query(vc, "LOOP_START", 0); // DarkPlaces, and some Japanese app if(startcomment) { endcomment = qvorbis_comment_query(vc, "LOOP_END", 0); if(!endcomment) lengthcomment = qvorbis_comment_query(vc, "LOOP_LENGTH", 0); } else { startcomment = qvorbis_comment_query(vc, "LOOPSTART", 0); // RPG Maker VX if(startcomment) { lengthcomment = qvorbis_comment_query(vc, "LOOPLENGTH", 0); if(!lengthcomment) endcomment = qvorbis_comment_query(vc, "LOOPEND", 0); } else { startcomment = qvorbis_comment_query(vc, "LOOPPOINT", 0); // Sonic Robo Blast 2 } } if(startcomment) { *start = (unsigned int) bound(0, atof(startcomment), numsamples); if(endcomment) *length = (unsigned int) bound(0, atof(endcomment), numsamples); else if(lengthcomment) *length = (unsigned int) bound(0, *start + atof(lengthcomment), numsamples); } } /* ==================== OGG_LoadVorbisFile Load an Ogg Vorbis file into memory ==================== */ qboolean OGG_LoadVorbisFile(const char *filename, sfx_t *sfx) { unsigned char *data; fs_offset_t filesize; ov_decode_t ov_decode; OggVorbis_File vf; vorbis_info *vi; vorbis_comment *vc; double peak, gaindb; #ifndef LINK_TO_LIBVORBIS if (!vf_dll) return false; #endif // Return if already loaded if (sfx->fetcher != NULL) return true; // Load the file completely data = FS_LoadFile(filename, snd_mempool, false, &filesize); if (data == NULL) return false; if (developer_loading.integer >= 2) Con_Printf("Loading Ogg Vorbis file \"%s\"\n", filename); // Open it with the VorbisFile API ov_decode.buffer = data; ov_decode.ind = 0; ov_decode.buffsize = filesize; if (qov_open_callbacks(&ov_decode, &vf, NULL, 0, callbacks) < 0) { Con_Printf("error while opening Ogg Vorbis file \"%s\"\n", filename); Mem_Free(data); return false; } // Get the stream information vi = qov_info(&vf, -1); if (vi->channels < 1 || vi->channels > 2) { Con_Printf("%s has an unsupported number of channels (%i)\n", sfx->name, vi->channels); qov_clear (&vf); Mem_Free(data); return false; } sfx->format.speed = vi->rate; sfx->format.channels = vi->channels; sfx->format.width = 2; // We always work with 16 bits samples sfx->total_length = qov_pcm_total(&vf, -1); if (snd_streaming.integer && (snd_streaming.integer >= 2 || sfx->total_length > max(sizeof(ogg_stream_perchannel_t), snd_streaming_length.value * sfx->format.speed))) { // large sounds use the OGG fetcher to decode the file on demand (but the entire file is held in memory) ogg_stream_persfx_t* per_sfx; if (developer_loading.integer >= 2) Con_Printf("Ogg sound file \"%s\" will be streamed\n", filename); per_sfx = (ogg_stream_persfx_t *)Mem_Alloc(snd_mempool, sizeof(*per_sfx)); sfx->memsize += sizeof (*per_sfx); per_sfx->file = data; per_sfx->filesize = filesize; sfx->memsize += filesize; sfx->fetcher_data = per_sfx; sfx->fetcher = &ogg_fetcher; sfx->flags |= SFXFLAG_STREAMED; vc = qov_comment(&vf, -1); OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); qov_clear(&vf); } else { // small sounds are entirely loaded and use the PCM fetcher char *buff; ogg_int64_t len; ogg_int64_t done; int bs; long ret; if (developer_loading.integer >= 2) Con_Printf ("Ogg sound file \"%s\" will be cached\n", filename); len = sfx->total_length * sfx->format.channels * sfx->format.width; sfx->flags &= ~SFXFLAG_STREAMED; sfx->memsize += len; sfx->fetcher = &wav_fetcher; sfx->fetcher_data = Mem_Alloc(snd_mempool, (size_t)len); buff = (char *)sfx->fetcher_data; done = 0; bs = 0; while ((ret = qov_read(&vf, &buff[done], (int)(len - done), mem_bigendian, 2, 1, &bs)) > 0) done += ret; vc = qov_comment(&vf, -1); OGG_DecodeTags(vc, &sfx->loopstart, &sfx->total_length, sfx->total_length, &peak, &gaindb); qov_clear(&vf); Mem_Free(data); } if(peak) { sfx->volume_mult = min(1.0f / peak, exp(gaindb * 0.05f * log(10.0f))); sfx->volume_peak = peak; if (developer_loading.integer >= 2) Con_Printf ("Ogg sound file \"%s\" uses ReplayGain (gain %f, peak %f)\n", filename, sfx->volume_mult, sfx->volume_peak); } else if(gaindb != 0) { sfx->volume_mult = min(1.0f / peak, exp(gaindb * 0.05f * log(10.0f))); sfx->volume_peak = 1.0; // if peak is not defined, we won't trust it if (developer_loading.integer >= 2) Con_Printf ("Ogg sound file \"%s\" uses ReplayGain (gain %f, peak not defined and assumed to be %f)\n", filename, sfx->volume_mult, sfx->volume_peak); } return true; } darkplaces/snd_wav.h0000664000175000017500000000157113067716222014003 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef SND_WAV_H #define SND_WAV_H extern const snd_fetcher_t wav_fetcher; qboolean S_LoadWavFile (const char *filename, sfx_t *sfx); #endif darkplaces/darkplaces-vs2013.sln0000664000175000017500000000715713067716220015760 0ustar kalevkalev Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl-vs2013", "darkplaces-wgl-vs2013.vcxproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl-vs2013", "darkplaces-sdl-vs2013.vcxproj", "{7470C8B3-FCA7-42D3-9909-5F9E735C7C51}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated-vs2013", "darkplaces-dedicated-vs2013.vcxproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl2-vs2013", "darkplaces-sdl2-vs2013.vcxproj", "{72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.ActiveCfg = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.Build.0 = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.ActiveCfg = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.Build.0 = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.ActiveCfg = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.Build.0 = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.ActiveCfg = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.Build.0 = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|Win32.ActiveCfg = Debug|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|Win32.Build.0 = Debug|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|x64.ActiveCfg = Debug|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Debug|x64.Build.0 = Debug|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|Win32.ActiveCfg = Release|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|Win32.Build.0 = Release|Win32 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|x64.ActiveCfg = Release|x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal darkplaces/crypto.h0000664000175000017500000002211213067716216013657 0ustar kalevkalev#ifndef CRYPTO_H #define CRYPTO_H extern cvar_t crypto_developer; extern cvar_t crypto_aeslevel; #define ENCRYPTION_REQUIRED (crypto_aeslevel.integer >= 3) extern int crypto_keyfp_recommended_length; // applies to LOCAL IDs, and to ALL keys #define CRYPTO_HEADERSIZE 31 // AES case causes 16 to 31 bytes overhead // SHA256 case causes 16 bytes overhead as we truncate to 128bit #include "lhnet.h" #define FP64_SIZE 44 #define DHKEY_SIZE 16 typedef struct { unsigned char dhkey[DHKEY_SIZE]; // shared key, not NUL terminated char client_idfp[FP64_SIZE+1]; char client_keyfp[FP64_SIZE+1]; qboolean client_issigned; char server_idfp[FP64_SIZE+1]; char server_keyfp[FP64_SIZE+1]; qboolean server_issigned; qboolean authenticated; qboolean use_aes; void *data; } crypto_t; void Crypto_Init(void); void Crypto_Init_Commands(void); void Crypto_LoadKeys(void); // NOTE: when this is called, the SV_LockThreadMutex MUST be active void Crypto_Shutdown(void); qboolean Crypto_Available(void); void sha256(unsigned char *out, const unsigned char *in, int n); // may ONLY be called if Crypto_Available() const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len); #define CRYPTO_NOMATCH 0 // process as usual (packet was not used) #define CRYPTO_MATCH 1 // process as usual (packet was used) #define CRYPTO_DISCARD 2 // discard this packet #define CRYPTO_REPLACE 3 // make the buffer the current packet int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress); // if len_out is nonzero, the packet is to be sent to the client qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen); crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress); qboolean Crypto_FinishInstance(crypto_t *out, crypto_t *in); // also clears allocated memory, and frees the instance received by ServerGetInstance const char *Crypto_GetInfoResponseDataString(void); // retrieves a host key for an address (can be exposed to menuqc, or used by the engine to look up stored keys e.g. for server bookmarking) // pointers may be NULL qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel, qboolean *issigned); int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, qboolean *issigned); // return value: -1 if more to come, +1 if valid, 0 if end of list size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size); // netconn protocol: // non-crypto: // getchallenge > // < challenge // connect > // < accept (or: reject) // crypto: // getchallenge > // < challenge SP NUL vlen d0pk NUL NUL // // IF serverfp: // d0pk\cnt\0\challenge\\aeslevel\ NUL NUL // > // check if client would get accepted; if not, do "reject" now // require non-control packets to be encrypted require non-control packets to be encrypted // do not send anything yet do not send anything yet // RESET to serverfp RESET to serverfp // d0_blind_id_authenticate_with_private_id_start() = 1 // < d0pk\cnt\1\aes\ NUL *startdata* // d0_blind_id_authenticate_with_private_id_challenge() = 1 // d0pk\cnt\2 NUL *challengedata* > // d0_blind_id_authenticate_with_private_id_response() = 0 // < d0pk\cnt\3 NUL *responsedata* // d0_blind_id_authenticate_with_private_id_verify() = 1 // store server's fingerprint NOW // d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 // // IF clientfp AND NOT serverfp: // RESET to clientfp RESET to clientfp // d0_blind_id_authenticate_with_private_id_start() = 1 // d0pk\cnt\0\challenge\\aeslevel\ NUL NUL NUL *startdata* // > // check if client would get accepted; if not, do "reject" now // require non-control packets to be encrypted require non-control packets to be encrypted // d0_blind_id_authenticate_with_private_id_challenge() = 1 // < d0pk\cnt\5\aes\ NUL *challengedata* // // IF clientfp AND serverfp: // RESET to clientfp RESET to clientfp // d0_blind_id_authenticate_with_private_id_start() = 1 // d0pk\cnt\4 NUL *startdata* > // d0_blind_id_authenticate_with_private_id_challenge() = 1 // < d0pk\cnt\5 NUL *challengedata* // // IF clientfp: // d0_blind_id_authenticate_with_private_id_response() = 0 // d0pk\cnt\6 NUL *responsedata* > // d0_blind_id_authenticate_with_private_id_verify() = 1 // store client's fingerprint NOW // d0_blind_id_sessionkey_public_id() = 1 d0_blind_id_sessionkey_public_id() = 1 // note: the ... is the "connect" message, except without the challenge. Reinterpret as regular connect message on server side // // enforce encrypted transmission (key is XOR of the two DH keys) // // IF clientfp: // < challenge (mere sync message) // // connect\... > // < accept (ALWAYS accept if connection is encrypted, ignore challenge as it had been checked before) // // commence with ingame protocol // in short: // server: // getchallenge NUL d0_blind_id: reply with challenge with added fingerprints // cnt=0: IF server will auth, cnt=1, ELSE cnt=5 // cnt=2: cnt=3 // cnt=4: cnt=5 // cnt=6: send "challenge" // client: // challenge with added fingerprints: cnt=0; if client will auth but not server, append client auth start // cnt=1: cnt=2 // cnt=3: IF client will auth, cnt=4, ELSE rewrite as "challenge" // cnt=5: cnt=6, server will continue by sending "challenge" (let's avoid sending two packets as response to one) // other change: // accept empty "challenge", and challenge-less connect in case crypto protocol has executed and finished // statusResponse and infoResponse get an added d0_blind_id key that lists // the keys the server can auth with and to in key@ca SPACE key@ca notation // any d0pk\ message has an appended "id" parameter; messages with an unexpected "id" are ignored to prevent errors from multiple concurrent auth runs // comparison to OTR: // - encryption: yes // - authentication: yes // - deniability: no (attacker requires the temporary session key to prove you // have sent a specific message, the private key itself does not suffice), no // measures are taken to provide forgeability to even provide deniability // against an attacker who knows the temporary session key, as using CTR mode // for the encryption - which, together with deriving the MAC key from the // encryption key, and MACing the ciphertexts instead of the plaintexts, // would provide forgeability and thus deniability - requires longer // encrypted packets and deniability was not a goal of this, as we may e.g. // reserve the right to capture packet dumps + extra state info to prove a // client/server has sent specific packets to prove cheating) // - perfect forward secrecy: yes (session key is derived via DH key exchange) #endif darkplaces/mdfour.h0000664000175000017500000000252713067716220013636 0ustar kalevkalev/* mdfour.h an implementation of MD4 designed for use in the SMB authentication protocol Copyright (C) Andrew Tridgell 1997-1998 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef _MDFOUR_H #define _MDFOUR_H #ifndef int32 #define int32 int #endif #if SIZEOF_INT > 4 #define LARGE_INT32 #endif #ifndef uint32 #define uint32 unsigned int32 #endif struct mdfour { uint32 A, B, C, D; uint32 totalN; }; void mdfour_begin(struct mdfour *md); // old: MD4Init void mdfour_update(struct mdfour *md, const unsigned char *in, int n); //old: MD4Update void mdfour_result(struct mdfour *md, unsigned char *out); // old: MD4Final void mdfour(unsigned char *out, const unsigned char *in, int n); #endif // _MDFOUR_H darkplaces/view.c0000664000175000017500000013404313067716222013310 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // view.c -- player eye positioning #include "quakedef.h" #include "cl_collision.h" #include "image.h" /* The view is allowed to move slightly from it's true position for bobbing, but if it exceeds 8 pixels linear distance (spherical, not box), the list of entities sent from the server may not include everything in the pvs, especially when crossing a water boudnary. */ cvar_t cl_rollspeed = {0, "cl_rollspeed", "200", "how much strafing is necessary to tilt the view"}; cvar_t cl_rollangle = {0, "cl_rollangle", "2.0", "how much to tilt the view when strafing"}; cvar_t cl_bob = {CVAR_SAVE, "cl_bob","0.02", "view bobbing amount"}; cvar_t cl_bobcycle = {CVAR_SAVE, "cl_bobcycle","0.6", "view bobbing speed"}; cvar_t cl_bobup = {CVAR_SAVE, "cl_bobup","0.5", "view bobbing adjustment that makes the up or down swing of the bob last longer"}; cvar_t cl_bob2 = {CVAR_SAVE, "cl_bob2","0", "sideways view bobbing amount"}; cvar_t cl_bob2cycle = {CVAR_SAVE, "cl_bob2cycle","0.6", "sideways view bobbing speed"}; cvar_t cl_bob2smooth = {CVAR_SAVE, "cl_bob2smooth","0.05", "how fast the view goes back when you stop touching the ground"}; cvar_t cl_bobfall = {CVAR_SAVE, "cl_bobfall","0", "how much the view swings down when falling (influenced by the speed you hit the ground with)"}; cvar_t cl_bobfallcycle = {CVAR_SAVE, "cl_bobfallcycle","3", "speed of the bobfall swing"}; cvar_t cl_bobfallminspeed = {CVAR_SAVE, "cl_bobfallminspeed","200", "necessary amount of speed for bob-falling to occur"}; cvar_t cl_bobmodel = {CVAR_SAVE, "cl_bobmodel", "1", "enables gun bobbing"}; cvar_t cl_bobmodel_side = {CVAR_SAVE, "cl_bobmodel_side", "0.15", "gun bobbing sideways sway amount"}; cvar_t cl_bobmodel_up = {CVAR_SAVE, "cl_bobmodel_up", "0.06", "gun bobbing upward movement amount"}; cvar_t cl_bobmodel_speed = {CVAR_SAVE, "cl_bobmodel_speed", "7", "gun bobbing speed"}; cvar_t cl_bob_limit = {CVAR_SAVE, "cl_bob_limit", "7", "limits bobbing to this much distance from view_ofs"}; cvar_t cl_bob_limit_heightcheck = {CVAR_SAVE, "cl_bob_limit_heightcheck", "0", "check ceiling and floor height against cl_bob_limit and scale down all view bobbing if could result in camera being in solid"}; cvar_t cl_bob_limit_heightcheck_dontcrosswatersurface = {CVAR_SAVE, "cl_bob_limit_heightcheck_dontcrosswatersurface", "1", "limit cl_bob_limit to not crossing liquid surfaces also"}; cvar_t cl_bob_velocity_limit = {CVAR_SAVE, "cl_bob_velocity_limit", "400", "limits the xyspeed value in the bobbing code"}; cvar_t cl_leanmodel = {CVAR_SAVE, "cl_leanmodel", "0", "enables gun leaning"}; cvar_t cl_leanmodel_side_speed = {CVAR_SAVE, "cl_leanmodel_side_speed", "0.7", "gun leaning sideways speed"}; cvar_t cl_leanmodel_side_limit = {CVAR_SAVE, "cl_leanmodel_side_limit", "35", "gun leaning sideways limit"}; cvar_t cl_leanmodel_side_highpass1 = {CVAR_SAVE, "cl_leanmodel_side_highpass1", "30", "gun leaning sideways pre-highpass in 1/s"}; cvar_t cl_leanmodel_side_highpass = {CVAR_SAVE, "cl_leanmodel_side_highpass", "3", "gun leaning sideways highpass in 1/s"}; cvar_t cl_leanmodel_side_lowpass = {CVAR_SAVE, "cl_leanmodel_side_lowpass", "20", "gun leaning sideways lowpass in 1/s"}; cvar_t cl_leanmodel_up_speed = {CVAR_SAVE, "cl_leanmodel_up_speed", "0.65", "gun leaning upward speed"}; cvar_t cl_leanmodel_up_limit = {CVAR_SAVE, "cl_leanmodel_up_limit", "50", "gun leaning upward limit"}; cvar_t cl_leanmodel_up_highpass1 = {CVAR_SAVE, "cl_leanmodel_up_highpass1", "5", "gun leaning upward pre-highpass in 1/s"}; cvar_t cl_leanmodel_up_highpass = {CVAR_SAVE, "cl_leanmodel_up_highpass", "15", "gun leaning upward highpass in 1/s"}; cvar_t cl_leanmodel_up_lowpass = {CVAR_SAVE, "cl_leanmodel_up_lowpass", "20", "gun leaning upward lowpass in 1/s"}; cvar_t cl_followmodel = {CVAR_SAVE, "cl_followmodel", "0", "enables gun following"}; cvar_t cl_followmodel_side_speed = {CVAR_SAVE, "cl_followmodel_side_speed", "0.25", "gun following sideways speed"}; cvar_t cl_followmodel_side_limit = {CVAR_SAVE, "cl_followmodel_side_limit", "6", "gun following sideways limit"}; cvar_t cl_followmodel_side_highpass1 = {CVAR_SAVE, "cl_followmodel_side_highpass1", "30", "gun following sideways pre-highpass in 1/s"}; cvar_t cl_followmodel_side_highpass = {CVAR_SAVE, "cl_followmodel_side_highpass", "5", "gun following sideways highpass in 1/s"}; cvar_t cl_followmodel_side_lowpass = {CVAR_SAVE, "cl_followmodel_side_lowpass", "10", "gun following sideways lowpass in 1/s"}; cvar_t cl_followmodel_up_speed = {CVAR_SAVE, "cl_followmodel_up_speed", "0.5", "gun following upward speed"}; cvar_t cl_followmodel_up_limit = {CVAR_SAVE, "cl_followmodel_up_limit", "5", "gun following upward limit"}; cvar_t cl_followmodel_up_highpass1 = {CVAR_SAVE, "cl_followmodel_up_highpass1", "60", "gun following upward pre-highpass in 1/s"}; cvar_t cl_followmodel_up_highpass = {CVAR_SAVE, "cl_followmodel_up_highpass", "2", "gun following upward highpass in 1/s"}; cvar_t cl_followmodel_up_lowpass = {CVAR_SAVE, "cl_followmodel_up_lowpass", "10", "gun following upward lowpass in 1/s"}; cvar_t cl_viewmodel_scale = {0, "cl_viewmodel_scale", "1", "changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible"}; cvar_t v_kicktime = {0, "v_kicktime", "0.5", "how long a view kick from damage lasts"}; cvar_t v_kickroll = {0, "v_kickroll", "0.6", "how much a view kick from damage rolls your view"}; cvar_t v_kickpitch = {0, "v_kickpitch", "0.6", "how much a view kick from damage pitches your view"}; cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2", "v_idlescale yaw speed"}; cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5", "v_idlescale roll speed"}; cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1", "v_idlescale pitch speed"}; cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3", "v_idlescale yaw amount"}; cvar_t v_iroll_level = {0, "v_iroll_level", "0.1", "v_idlescale roll amount"}; cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3", "v_idlescale pitch amount"}; cvar_t v_idlescale = {0, "v_idlescale", "0", "how much of the quake 'drunken view' effect to use"}; cvar_t crosshair = {CVAR_SAVE, "crosshair", "0", "selects crosshair to use (0 is none)"}; cvar_t v_centermove = {0, "v_centermove", "0.15", "how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off)"}; cvar_t v_centerspeed = {0, "v_centerspeed","500", "how fast the view centers itself"}; cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160", "how fast your view moves upward/downward when running up/down stairs"}; cvar_t cl_smoothviewheight = {CVAR_SAVE, "cl_smoothviewheight", "0", "time of the averaging to the viewheight value so that it creates a smooth transition. higher values = longer transition, 0 for instant transition."}; cvar_t chase_back = {CVAR_SAVE, "chase_back", "48", "chase cam distance from the player"}; cvar_t chase_up = {CVAR_SAVE, "chase_up", "24", "chase cam distance from the player"}; cvar_t chase_active = {CVAR_SAVE, "chase_active", "0", "enables chase cam"}; cvar_t chase_overhead = {CVAR_SAVE, "chase_overhead", "0", "chase cam looks straight down if this is not zero"}; // GAME_GOODVSBAD2 cvar_t chase_stevie = {0, "chase_stevie", "0", "(GOODVSBAD2 only) chase cam view from above"}; cvar_t v_deathtilt = {0, "v_deathtilt", "1", "whether to use sideways view when dead"}; cvar_t v_deathtiltangle = {0, "v_deathtiltangle", "80", "what roll angle to use when tilting the view while dead"}; // Prophecy camera pitchangle by Alexander "motorsep" Zubov cvar_t chase_pitchangle = {CVAR_SAVE, "chase_pitchangle", "55", "chase cam pitch angle"}; cvar_t v_yshearing = {0, "v_yshearing", "0", "be all out of gum (set this to the maximum angle to allow Y shearing for - try values like 75)"}; float v_dmg_time, v_dmg_roll, v_dmg_pitch; /* =============== V_CalcRoll Used by view and sv_user =============== */ float V_CalcRoll (const vec3_t angles, const vec3_t velocity) { vec3_t right; float sign; float side; float value; AngleVectors (angles, NULL, right, NULL); side = DotProduct (velocity, right); sign = side < 0 ? -1 : 1; side = fabs(side); value = cl_rollangle.value; if (side < cl_rollspeed.value) side = side * value / cl_rollspeed.value; else side = value; return side*sign; } void V_StartPitchDrift (void) { if (cl.laststop == cl.time) return; // something else is keeping it from drifting if (cl.nodrift || !cl.pitchvel) { cl.pitchvel = v_centerspeed.value; cl.nodrift = false; cl.driftmove = 0; } } void V_StopPitchDrift (void) { cl.laststop = cl.time; cl.nodrift = true; cl.pitchvel = 0; } /* =============== V_DriftPitch Moves the client pitch angle towards cl.idealpitch sent by the server. If the user is adjusting pitch manually, either with lookup/lookdown, mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. Drifting is enabled when the center view key is hit, mlook is released and lookspring is non 0, or when =============== */ void V_DriftPitch (void) { float delta, move; if (noclip_anglehack || !cl.onground || cls.demoplayback ) { cl.driftmove = 0; cl.pitchvel = 0; return; } // don't count small mouse motion if (cl.nodrift) { if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value) cl.driftmove = 0; else cl.driftmove += cl.realframetime; if ( cl.driftmove > v_centermove.value) { V_StartPitchDrift (); } return; } delta = cl.idealpitch - cl.viewangles[PITCH]; if (!delta) { cl.pitchvel = 0; return; } move = cl.realframetime * cl.pitchvel; cl.pitchvel += cl.realframetime * v_centerspeed.value; if (delta > 0) { if (move > delta) { cl.pitchvel = 0; move = delta; } cl.viewangles[PITCH] += move; } else if (delta < 0) { if (move > -delta) { cl.pitchvel = 0; move = -delta; } cl.viewangles[PITCH] -= move; } } /* ============================================================================== SCREEN FLASHES ============================================================================== */ /* =============== V_ParseDamage =============== */ void V_ParseDamage (void) { int armor, blood; vec3_t from; //vec3_t forward, right; vec3_t localfrom; entity_t *ent; //float side; float count; armor = MSG_ReadByte(&cl_message); blood = MSG_ReadByte(&cl_message); MSG_ReadVector(&cl_message, from, cls.protocol); // Send the Dmg Globals to CSQC CL_VM_UpdateDmgGlobals(blood, armor, from); count = blood*0.5 + armor*0.5; if (count < 10) count = 10; cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame cl.cshifts[CSHIFT_DAMAGE].percent += 3*count; cl.cshifts[CSHIFT_DAMAGE].alphafade = 150; if (cl.cshifts[CSHIFT_DAMAGE].percent < 0) cl.cshifts[CSHIFT_DAMAGE].percent = 0; if (cl.cshifts[CSHIFT_DAMAGE].percent > 150) cl.cshifts[CSHIFT_DAMAGE].percent = 150; if (armor > blood) { cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200; cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100; cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100; } else if (armor) { cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220; cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50; cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50; } else { cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255; cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0; cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0; } // calculate view angle kicks if (cl.entities[cl.viewentity].state_current.active) { ent = &cl.entities[cl.viewentity]; Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom); VectorNormalize(localfrom); v_dmg_pitch = count * localfrom[0] * v_kickpitch.value; v_dmg_roll = count * localfrom[1] * v_kickroll.value; v_dmg_time = v_kicktime.value; } } static cshift_t v_cshift; /* ================== V_cshift_f ================== */ static void V_cshift_f (void) { v_cshift.destcolor[0] = atof(Cmd_Argv(1)); v_cshift.destcolor[1] = atof(Cmd_Argv(2)); v_cshift.destcolor[2] = atof(Cmd_Argv(3)); v_cshift.percent = atof(Cmd_Argv(4)); } /* ================== V_BonusFlash_f When you run over an item, the server sends this command ================== */ static void V_BonusFlash_f (void) { if(Cmd_Argc() == 1) { cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215; cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186; cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69; cl.cshifts[CSHIFT_BONUS].percent = 50; cl.cshifts[CSHIFT_BONUS].alphafade = 100; } else if(Cmd_Argc() >= 4 && Cmd_Argc() <= 6) { cl.cshifts[CSHIFT_BONUS].destcolor[0] = atof(Cmd_Argv(1)) * 255; cl.cshifts[CSHIFT_BONUS].destcolor[1] = atof(Cmd_Argv(2)) * 255; cl.cshifts[CSHIFT_BONUS].destcolor[2] = atof(Cmd_Argv(3)) * 255; if(Cmd_Argc() >= 5) cl.cshifts[CSHIFT_BONUS].percent = atof(Cmd_Argv(4)) * 255; // yes, these are HEXADECIMAL percent ;) else cl.cshifts[CSHIFT_BONUS].percent = 50; if(Cmd_Argc() >= 6) cl.cshifts[CSHIFT_BONUS].alphafade = atof(Cmd_Argv(5)) * 255; else cl.cshifts[CSHIFT_BONUS].alphafade = 100; } else Con_Printf("usage:\nbf, or bf R G B [A [alphafade]]\n"); } /* ============================================================================== VIEW RENDERING ============================================================================== */ extern matrix4x4_t viewmodelmatrix_nobob; extern matrix4x4_t viewmodelmatrix_withbob; #include "cl_collision.h" #include "csprogs.h" /* ================== V_CalcRefdef ================== */ #if 0 static vec3_t eyeboxmins = {-16, -16, -24}; static vec3_t eyeboxmaxs = { 16, 16, 32}; #endif static vec_t lowpass(vec_t value, vec_t frac, vec_t *store) { frac = bound(0, frac, 1); return (*store = *store * (1 - frac) + value * frac); } static vec_t lowpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) { lowpass(value, frac, store); return (*store = bound(value - limit, *store, value + limit)); } static vec_t highpass(vec_t value, vec_t frac, vec_t *store) { return value - lowpass(value, frac, store); } static vec_t highpass_limited(vec_t value, vec_t frac, vec_t limit, vec_t *store) { return value - lowpass_limited(value, frac, limit, store); } static void lowpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) { out[0] = lowpass(value[0], fracx, &store[0]); out[1] = lowpass(value[1], fracy, &store[1]); out[2] = lowpass(value[2], fracz, &store[2]); } static void highpass3(vec3_t value, vec_t fracx, vec_t fracy, vec_t fracz, vec3_t store, vec3_t out) { out[0] = highpass(value[0], fracx, &store[0]); out[1] = highpass(value[1], fracy, &store[1]); out[2] = highpass(value[2], fracz, &store[2]); } static void highpass3_limited(vec3_t value, vec_t fracx, vec_t limitx, vec_t fracy, vec_t limity, vec_t fracz, vec_t limitz, vec3_t store, vec3_t out) { out[0] = highpass_limited(value[0], fracx, limitx, &store[0]); out[1] = highpass_limited(value[1], fracy, limity, &store[1]); out[2] = highpass_limited(value[2], fracz, limitz, &store[2]); } /* * State: * cl.bob2_smooth * cl.bobfall_speed * cl.bobfall_swing * cl.gunangles_adjustment_highpass * cl.gunangles_adjustment_lowpass * cl.gunangles_highpass * cl.gunangles_prev * cl.gunorg_adjustment_highpass * cl.gunorg_adjustment_lowpass * cl.gunorg_highpass * cl.gunorg_prev * cl.hitgroundtime * cl.lastongroundtime * cl.oldongrounbd * cl.stairsmoothtime * cl.stairsmoothz * cl.calcrefdef_prevtime * Extra input: * cl.movecmd[0].time * cl.movevars_stepheight * cl.movevars_timescale * cl.oldtime * cl.punchangle * cl.punchvector * cl.qw_intermission_angles * cl.qw_intermission_origin * cl.qw_weaponkick * cls.protocol * cl.time * Output: * cl.csqc_viewanglesfromengine * cl.csqc_viewmodelmatrixfromengine * cl.csqc_vieworiginfromengine * r_refdef.view.matrix * viewmodelmatrix_nobob * viewmodelmatrix_withbob */ void V_CalcRefdefUsing (const matrix4x4_t *entrendermatrix, const vec3_t clviewangles, qboolean teleported, qboolean clonground, qboolean clcmdjump, float clstatsviewheight, qboolean cldead, qboolean clintermission, const vec3_t clvelocity) { float vieworg[3], viewangles[3], smoothtime; float gunorg[3], gunangles[3]; matrix4x4_t tmpmatrix; static float viewheightavg; float viewheight; #if 0 // begin of chase camera bounding box size for proper collisions by Alexander Zubov vec3_t camboxmins = {-3, -3, -3}; vec3_t camboxmaxs = {3, 3, 3}; // end of chase camera bounding box size for proper collisions by Alexander Zubov #endif trace_t trace; // react to clonground state changes (for gun bob) if (clonground) { if (!cl.oldonground) cl.hitgroundtime = cl.movecmd[0].time; cl.lastongroundtime = cl.movecmd[0].time; } cl.oldonground = clonground; cl.calcrefdef_prevtime = max(cl.calcrefdef_prevtime, cl.oldtime); VectorClear(gunangles); VectorClear(gunorg); viewmodelmatrix_nobob = identitymatrix; viewmodelmatrix_withbob = identitymatrix; r_refdef.view.matrix = identitymatrix; // player can look around, so take the origin from the entity, // and the angles from the input system Matrix4x4_OriginFromMatrix(entrendermatrix, vieworg); VectorCopy(clviewangles, viewangles); // calculate how much time has passed since the last V_CalcRefdef smoothtime = bound(0, cl.time - cl.stairsmoothtime, 0.1); cl.stairsmoothtime = cl.time; // fade damage flash if (v_dmg_time > 0) v_dmg_time -= bound(0, smoothtime, 0.1); if (clintermission) { // entity is a fixed camera, just copy the matrix if (cls.protocol == PROTOCOL_QUAKEWORLD) Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1); else { r_refdef.view.matrix = *entrendermatrix; Matrix4x4_AdjustOrigin(&r_refdef.view.matrix, 0, 0, clstatsviewheight); } if (v_yshearing.value > 0) Matrix4x4_QuakeToDuke3D(&r_refdef.view.matrix, &r_refdef.view.matrix, v_yshearing.value); Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); Matrix4x4_Copy(&viewmodelmatrix_withbob, &viewmodelmatrix_nobob); VectorCopy(vieworg, cl.csqc_vieworiginfromengine); VectorCopy(viewangles, cl.csqc_viewanglesfromengine); Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); Matrix4x4_CreateScale(&cl.csqc_viewmodelmatrixfromengine, cl_viewmodel_scale.value); } else { // smooth stair stepping, but only if clonground and enabled if (!clonground || cl_stairsmoothspeed.value <= 0 || teleported) cl.stairsmoothz = vieworg[2]; else { if (cl.stairsmoothz < vieworg[2]) vieworg[2] = cl.stairsmoothz = bound(vieworg[2] - cl.movevars_stepheight, cl.stairsmoothz + smoothtime * cl_stairsmoothspeed.value, vieworg[2]); else if (cl.stairsmoothz > vieworg[2]) vieworg[2] = cl.stairsmoothz = bound(vieworg[2], cl.stairsmoothz - smoothtime * cl_stairsmoothspeed.value, vieworg[2] + cl.movevars_stepheight); } // apply qw weapon recoil effect (this did not work in QW) // TODO: add a cvar to disable this viewangles[PITCH] += cl.qw_weaponkick; // apply the viewofs (even if chasecam is used) // Samual: Lets add smoothing for this too so that things like crouching are done with a transition. viewheight = bound(0, (cl.time - cl.calcrefdef_prevtime) / max(0.0001, cl_smoothviewheight.value), 1); viewheightavg = viewheightavg * (1 - viewheight) + clstatsviewheight * viewheight; vieworg[2] += viewheightavg; if (chase_active.value) { // observing entity from third person. Added "campitch" by Alexander "motorsep" Zubov vec_t camback, camup, dist, campitch, forward[3], chase_dest[3]; camback = chase_back.value; camup = chase_up.value; campitch = chase_pitchangle.value; AngleVectors(viewangles, forward, NULL, NULL); if (chase_overhead.integer) { #if 1 vec3_t offset; vec3_t bestvieworg; #endif vec3_t up; viewangles[PITCH] = 0; AngleVectors(viewangles, forward, NULL, up); // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup; chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup; chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup; #if 0 #if 1 //trace = CL_TraceLine(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, true, false, NULL, false); trace = CL_TraceLine(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, true, false, NULL, false); #else //trace = CL_TraceBox(vieworg, eyeboxmins, eyeboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, true, false, NULL, false); trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, true, false, NULL, false); #endif VectorCopy(trace.endpos, vieworg); vieworg[2] -= 8; #else // trace from first person view location to our chosen third person view location #if 1 trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false, true); #else trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false); #endif VectorCopy(trace.endpos, bestvieworg); offset[2] = 0; for (offset[0] = -16;offset[0] <= 16;offset[0] += 8) { for (offset[1] = -16;offset[1] <= 16;offset[1] += 8) { AngleVectors(viewangles, NULL, NULL, up); chase_dest[0] = vieworg[0] - forward[0] * camback + up[0] * camup + offset[0]; chase_dest[1] = vieworg[1] - forward[1] * camback + up[1] * camup + offset[1]; chase_dest[2] = vieworg[2] - forward[2] * camback + up[2] * camup + offset[2]; #if 1 trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false, true); #else trace = CL_TraceBox(vieworg, camboxmins, camboxmaxs, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false); #endif if (bestvieworg[2] > trace.endpos[2]) bestvieworg[2] = trace.endpos[2]; } } bestvieworg[2] -= 8; VectorCopy(bestvieworg, vieworg); #endif viewangles[PITCH] = campitch; } else { if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer) { // look straight down from high above viewangles[PITCH] = 90; camback = 2048; VectorSet(forward, 0, 0, -1); } // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range) dist = -camback - 8; chase_dest[0] = vieworg[0] + forward[0] * dist; chase_dest[1] = vieworg[1] + forward[1] * dist; chase_dest[2] = vieworg[2] + forward[2] * dist + camup; trace = CL_TraceLine(vieworg, chase_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false, true); VectorMAMAM(1, trace.endpos, 8, forward, 4, trace.plane.normal, vieworg); } } else { // first person view from entity // angles if (cldead && v_deathtilt.integer) viewangles[ROLL] = v_deathtiltangle.value; VectorAdd(viewangles, cl.punchangle, viewangles); viewangles[ROLL] += V_CalcRoll(clviewangles, clvelocity); if (v_dmg_time > 0) { viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll; viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch; } // origin VectorAdd(vieworg, cl.punchvector, vieworg); if (!cldead) { double xyspeed, bob, bobfall; double cycle; // double-precision because cl.time can be a very large number, where float would get stuttery at high time values vec_t frametime; frametime = (cl.time - cl.calcrefdef_prevtime) * cl.movevars_timescale; // 1. if we teleported, clear the frametime... the lowpass will recover the previous value then if(teleported) { // try to fix the first highpass; result is NOT // perfect! TODO find a better fix VectorCopy(viewangles, cl.gunangles_prev); VectorCopy(vieworg, cl.gunorg_prev); } // 2. for the gun origin, only keep the high frequency (non-DC) parts, which is "somewhat like velocity" VectorAdd(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); highpass3_limited(vieworg, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_side_highpass1.value, cl_followmodel_side_limit.value, frametime*cl_followmodel_up_highpass1.value, cl_followmodel_up_limit.value, cl.gunorg_highpass, gunorg); VectorCopy(vieworg, cl.gunorg_prev); VectorSubtract(cl.gunorg_highpass, cl.gunorg_prev, cl.gunorg_highpass); // in the highpass, we _store_ the DIFFERENCE to the actual view angles... VectorAdd(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); cl.gunangles_highpass[PITCH] += 360 * floor((viewangles[PITCH] - cl.gunangles_highpass[PITCH]) / 360 + 0.5); cl.gunangles_highpass[YAW] += 360 * floor((viewangles[YAW] - cl.gunangles_highpass[YAW]) / 360 + 0.5); cl.gunangles_highpass[ROLL] += 360 * floor((viewangles[ROLL] - cl.gunangles_highpass[ROLL]) / 360 + 0.5); highpass3_limited(viewangles, frametime*cl_leanmodel_up_highpass1.value, cl_leanmodel_up_limit.value, frametime*cl_leanmodel_side_highpass1.value, cl_leanmodel_side_limit.value, 0, 0, cl.gunangles_highpass, gunangles); VectorCopy(viewangles, cl.gunangles_prev); VectorSubtract(cl.gunangles_highpass, cl.gunangles_prev, cl.gunangles_highpass); // 3. calculate the RAW adjustment vectors gunorg[0] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); gunorg[1] *= (cl_followmodel.value ? -cl_followmodel_side_speed.value : 0); gunorg[2] *= (cl_followmodel.value ? -cl_followmodel_up_speed.value : 0); gunangles[PITCH] *= (cl_leanmodel.value ? -cl_leanmodel_up_speed.value : 0); gunangles[YAW] *= (cl_leanmodel.value ? -cl_leanmodel_side_speed.value : 0); gunangles[ROLL] = 0; // 4. perform highpass/lowpass on the adjustment vectors (turning velocity into acceleration!) // trick: we must do the lowpass LAST, so the lowpass vector IS the final vector! highpass3(gunorg, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_side_highpass.value, frametime*cl_followmodel_up_highpass.value, cl.gunorg_adjustment_highpass, gunorg); lowpass3(gunorg, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_side_lowpass.value, frametime*cl_followmodel_up_lowpass.value, cl.gunorg_adjustment_lowpass, gunorg); // we assume here: PITCH = 0, YAW = 1, ROLL = 2 highpass3(gunangles, frametime*cl_leanmodel_up_highpass.value, frametime*cl_leanmodel_side_highpass.value, 0, cl.gunangles_adjustment_highpass, gunangles); lowpass3(gunangles, frametime*cl_leanmodel_up_lowpass.value, frametime*cl_leanmodel_side_lowpass.value, 0, cl.gunangles_adjustment_lowpass, gunangles); // 5. use the adjusted vectors VectorAdd(vieworg, gunorg, gunorg); VectorAdd(viewangles, gunangles, gunangles); // bounded XY speed, used by several effects below xyspeed = bound (0, sqrt(clvelocity[0]*clvelocity[0] + clvelocity[1]*clvelocity[1]), cl_bob_velocity_limit.value); // vertical view bobbing code if (cl_bob.value && cl_bobcycle.value) { float bob_limit = cl_bob_limit.value; if (cl_bob_limit_heightcheck.integer) { // use traces to determine what range the view can bob in, and scale down the bob as needed float trace1fraction; float trace2fraction; vec3_t bob_height_check_dest; // these multipliers are expanded a bit (the actual bob sin range is from -0.4 to 1.0) to reduce nearclip issues, especially on water surfaces bob_height_check_dest[0] = vieworg[0]; bob_height_check_dest[1] = vieworg[1]; bob_height_check_dest[2] = vieworg[2] + cl_bob_limit.value * 1.1f; trace = CL_TraceLine(vieworg, bob_height_check_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY | (cl_bob_limit_heightcheck_dontcrosswatersurface.integer ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, collision_extendmovelength.value, true, false, NULL, false, true); trace1fraction = trace.fraction; bob_height_check_dest[0] = vieworg[0]; bob_height_check_dest[1] = vieworg[1]; bob_height_check_dest[2] = vieworg[2] + cl_bob_limit.value * -0.5f; trace = CL_TraceLine(vieworg, bob_height_check_dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_SKY | (cl_bob_limit_heightcheck_dontcrosswatersurface.integer ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, collision_extendmovelength.value, true, false, NULL, false, true); trace2fraction = trace.fraction; bob_limit *= min(trace1fraction, trace2fraction); } // LordHavoc: this code is *weird*, but not replacable (I think it // should be done in QC on the server, but oh well, quake is quake) // LordHavoc: figured out bobup: the time at which the sin is at 180 // degrees (which allows lengthening or squishing the peak or valley) cycle = cl.time / cl_bobcycle.value; cycle -= (int) cycle; if (cycle < cl_bobup.value) cycle = sin(M_PI * cycle / cl_bobup.value); else cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); // bob is proportional to velocity in the xy plane // (don't count Z, or jumping messes it up) bob = xyspeed * cl_bob.value; bob = bound(0, bob, bob_limit); bob = bob*0.3 + bob*0.7*cycle; vieworg[2] += bob; // we also need to adjust gunorg, or this appears like pushing the gun! // In the old code, this was applied to vieworg BEFORE copying to gunorg, // but this is not viable with the new followmodel code as that would mean // that followmodel would work on the munged-by-bob vieworg and do feedback gunorg[2] += bob; } // horizontal view bobbing code if (cl_bob2.value && cl_bob2cycle.value) { vec3_t bob2vel; vec3_t forward, right, up; float side, front; cycle = cl.time / cl_bob2cycle.value; cycle -= (int) cycle; if (cycle < 0.5) cycle = cos(M_PI * cycle / 0.5); // cos looks better here with the other view bobbing using sin else cycle = cos(M_PI + M_PI * (cycle-0.5)/0.5); bob = cl_bob2.value * cycle; // this value slowly decreases from 1 to 0 when we stop touching the ground. // The cycle is later multiplied with it so the view smooths back to normal if (clonground && !clcmdjump) // also block the effect while the jump button is pressed, to avoid twitches when bunny-hopping cl.bob2_smooth = 1; else { if(cl.bob2_smooth > 0) cl.bob2_smooth -= bound(0, cl_bob2smooth.value, 1); else cl.bob2_smooth = 0; } // calculate the front and side of the player between the X and Y axes AngleVectors(viewangles, forward, right, up); // now get the speed based on those angles. The bounds should match the same value as xyspeed's side = bound(-cl_bob_velocity_limit.value, DotProduct (clvelocity, right) * cl.bob2_smooth, cl_bob_velocity_limit.value); front = bound(-cl_bob_velocity_limit.value, DotProduct (clvelocity, forward) * cl.bob2_smooth, cl_bob_velocity_limit.value); VectorScale(forward, bob, forward); VectorScale(right, bob, right); // we use side with forward and front with right, so the bobbing goes // to the side when we walk forward and to the front when we strafe VectorMAMAM(side, forward, front, right, 0, up, bob2vel); vieworg[0] += bob2vel[0]; vieworg[1] += bob2vel[1]; // we also need to adjust gunorg, or this appears like pushing the gun! // In the old code, this was applied to vieworg BEFORE copying to gunorg, // but this is not viable with the new followmodel code as that would mean // that followmodel would work on the munged-by-bob vieworg and do feedback gunorg[0] += bob2vel[0]; gunorg[1] += bob2vel[1]; } // fall bobbing code // causes the view to swing down and back up when touching the ground if (cl_bobfall.value && cl_bobfallcycle.value) { if (!clonground) { cl.bobfall_speed = bound(-400, clvelocity[2], 0) * bound(0, cl_bobfall.value, 0.1); if (clvelocity[2] < -cl_bobfallminspeed.value) cl.bobfall_swing = 1; else cl.bobfall_swing = 0; // TODO really? } else { cl.bobfall_swing = max(0, cl.bobfall_swing - cl_bobfallcycle.value * frametime); bobfall = sin(M_PI * cl.bobfall_swing) * cl.bobfall_speed; vieworg[2] += bobfall; gunorg[2] += bobfall; } } // gun model bobbing code if (cl_bobmodel.value) { // calculate for swinging gun model // the gun bobs when running on the ground, but doesn't bob when you're in the air. // Sajt: I tried to smooth out the transitions between bob and no bob, which works // for the most part, but for some reason when you go through a message trigger or // pick up an item or anything like that it will momentarily jolt the gun. vec3_t forward, right, up; float bspeed; float s; float t; s = cl.time * cl_bobmodel_speed.value; if (clonground) { if (cl.time - cl.hitgroundtime < 0.2) { // just hit the ground, speed the bob back up over the next 0.2 seconds t = cl.time - cl.hitgroundtime; t = bound(0, t, 0.2); t *= 5; } else t = 1; } else { // recently left the ground, slow the bob down over the next 0.2 seconds t = cl.time - cl.lastongroundtime; t = 0.2 - bound(0, t, 0.2); t *= 5; } bspeed = xyspeed * 0.01f; AngleVectors (gunangles, forward, right, up); bob = bspeed * cl_bobmodel_side.value * cl_viewmodel_scale.value * sin (s) * t; VectorMA (gunorg, bob, right, gunorg); bob = bspeed * cl_bobmodel_up.value * cl_viewmodel_scale.value * cos (s * 2) * t; VectorMA (gunorg, bob, up, gunorg); } } } // calculate a view matrix for rendering the scene if (v_idlescale.value) { viewangles[0] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value; viewangles[1] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value; viewangles[2] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value; } Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2], 1); if (v_yshearing.value > 0) Matrix4x4_QuakeToDuke3D(&r_refdef.view.matrix, &r_refdef.view.matrix, v_yshearing.value); // calculate a viewmodel matrix for use in view-attached entities Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix_withbob, gunorg[0], gunorg[1], gunorg[2], gunangles[0], gunangles[1], gunangles[2], cl_viewmodel_scale.value); if (v_yshearing.value > 0) Matrix4x4_QuakeToDuke3D(&viewmodelmatrix_withbob, &viewmodelmatrix_withbob, v_yshearing.value); VectorCopy(vieworg, cl.csqc_vieworiginfromengine); VectorCopy(viewangles, cl.csqc_viewanglesfromengine); Matrix4x4_Invert_Simple(&tmpmatrix, &r_refdef.view.matrix); Matrix4x4_Concat(&cl.csqc_viewmodelmatrixfromengine, &tmpmatrix, &viewmodelmatrix_withbob); } cl.calcrefdef_prevtime = cl.time; } void V_CalcRefdef (void) { entity_t *ent; qboolean cldead; if (cls.state == ca_connected && cls.signon == SIGNONS && !cl.csqc_server2csqcentitynumber[cl.viewentity]) { // ent is the view entity (visible when out of body) ent = &cl.entities[cl.viewentity]; cldead = (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && cl.stats[STAT_HEALTH] != -2342); V_CalcRefdefUsing(&ent->render.matrix, cl.viewangles, !ent->persistent.trail_allowed, cl.onground, cl.cmd.jump, cl.stats[STAT_VIEWHEIGHT], cldead, cl.intermission != 0, cl.velocity); // FIXME use a better way to detect teleport/warp than trail_allowed } else { viewmodelmatrix_nobob = identitymatrix; viewmodelmatrix_withbob = identitymatrix; cl.csqc_viewmodelmatrixfromengine = identitymatrix; r_refdef.view.matrix = identitymatrix; VectorClear(cl.csqc_vieworiginfromengine); VectorCopy(cl.viewangles, cl.csqc_viewanglesfromengine); } } void V_FadeViewFlashs(void) { // don't flash if time steps backwards if (cl.time <= cl.oldtime) return; // drop the damage value cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_DAMAGE].alphafade; if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0) cl.cshifts[CSHIFT_DAMAGE].percent = 0; // drop the bonus value cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*cl.cshifts[CSHIFT_BONUS].alphafade; if (cl.cshifts[CSHIFT_BONUS].percent <= 0) cl.cshifts[CSHIFT_BONUS].percent = 0; } void V_CalcViewBlend(void) { float a2; int j; r_refdef.viewblend[0] = 0; r_refdef.viewblend[1] = 0; r_refdef.viewblend[2] = 0; r_refdef.viewblend[3] = 0; r_refdef.frustumscale_x = 1; r_refdef.frustumscale_y = 1; if (cls.state == ca_connected && cls.signon == SIGNONS) { // set contents color int supercontents; vec3_t vieworigin; Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); supercontents = CL_PointSuperContents(vieworigin); if (supercontents & SUPERCONTENTS_LIQUIDSMASK) { r_refdef.frustumscale_x *= 1 - (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value); r_refdef.frustumscale_y *= 1 - (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value); if (supercontents & SUPERCONTENTS_LAVA) { cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255; cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; } else if (supercontents & SUPERCONTENTS_SLIME) { cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25; cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5; } else { cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130; cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80; cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50; } cl.cshifts[CSHIFT_CONTENTS].percent = 150 * 0.5; } else { cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0; cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 0; cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0; cl.cshifts[CSHIFT_CONTENTS].percent = 0; } if (gamemode != GAME_TRANSFUSION) { if (cl.stats[STAT_ITEMS] & IT_QUAD) { cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0; cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255; cl.cshifts[CSHIFT_POWERUP].percent = 30; } else if (cl.stats[STAT_ITEMS] & IT_SUIT) { cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0; cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; cl.cshifts[CSHIFT_POWERUP].percent = 20; } else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) { cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100; cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100; cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100; cl.cshifts[CSHIFT_POWERUP].percent = 100; } else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) { cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255; cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255; cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0; cl.cshifts[CSHIFT_POWERUP].percent = 30; } else cl.cshifts[CSHIFT_POWERUP].percent = 0; } cl.cshifts[CSHIFT_VCSHIFT].destcolor[0] = v_cshift.destcolor[0]; cl.cshifts[CSHIFT_VCSHIFT].destcolor[1] = v_cshift.destcolor[1]; cl.cshifts[CSHIFT_VCSHIFT].destcolor[2] = v_cshift.destcolor[2]; cl.cshifts[CSHIFT_VCSHIFT].percent = v_cshift.percent; // LordHavoc: fixed V_CalcBlend for (j = 0;j < NUM_CSHIFTS;j++) { a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f); if (a2 > 0) { VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend); r_refdef.viewblend[3] = (1 - (1 - r_refdef.viewblend[3]) * (1 - a2)); // correct alpha multiply... took a while to find it on the web } } // saturate color (to avoid blending in black) if (r_refdef.viewblend[3]) { a2 = 1 / r_refdef.viewblend[3]; VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend); } r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0], 255.0f); r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1], 255.0f); r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2], 255.0f); r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3] * gl_polyblend.value, 1.0f); if (vid.sRGB3D) { r_refdef.viewblend[0] = Image_LinearFloatFromsRGB(r_refdef.viewblend[0]); r_refdef.viewblend[1] = Image_LinearFloatFromsRGB(r_refdef.viewblend[1]); r_refdef.viewblend[2] = Image_LinearFloatFromsRGB(r_refdef.viewblend[2]); } else { r_refdef.viewblend[0] *= (1.0f/256.0f); r_refdef.viewblend[1] *= (1.0f/256.0f); r_refdef.viewblend[2] *= (1.0f/256.0f); } // Samual: Ugly hack, I know. But it's the best we can do since // there is no way to detect client states from the engine. if (cl.stats[STAT_HEALTH] <= 0 && cl.stats[STAT_HEALTH] != -666 && cl.stats[STAT_HEALTH] != -2342 && cl_deathfade.value > 0) { cl.deathfade += cl_deathfade.value * max(0.00001, cl.time - cl.oldtime); cl.deathfade = bound(0.0f, cl.deathfade, 0.9f); } else cl.deathfade = 0.0f; if(cl.deathfade > 0) { float a; float deathfadevec[3] = {0.3f, 0.0f, 0.0f}; a = r_refdef.viewblend[3] + cl.deathfade - r_refdef.viewblend[3]*cl.deathfade; if(a > 0) VectorMAM(r_refdef.viewblend[3] * (1 - cl.deathfade) / a, r_refdef.viewblend, cl.deathfade / a, deathfadevec, r_refdef.viewblend); r_refdef.viewblend[3] = a; } } } //============================================================================ /* ============= V_Init ============= */ void V_Init (void) { Cmd_AddCommand ("v_cshift", V_cshift_f, "sets tint color of view"); Cmd_AddCommand ("bf", V_BonusFlash_f, "briefly flashes a bright color tint on view (used when items are picked up); optionally takes R G B [A [alphafade]] arguments to specify how the flash looks"); Cmd_AddCommand ("centerview", V_StartPitchDrift, "gradually recenter view (stop looking up/down)"); Cvar_RegisterVariable (&v_centermove); Cvar_RegisterVariable (&v_centerspeed); Cvar_RegisterVariable (&v_iyaw_cycle); Cvar_RegisterVariable (&v_iroll_cycle); Cvar_RegisterVariable (&v_ipitch_cycle); Cvar_RegisterVariable (&v_iyaw_level); Cvar_RegisterVariable (&v_iroll_level); Cvar_RegisterVariable (&v_ipitch_level); Cvar_RegisterVariable (&v_idlescale); Cvar_RegisterVariable (&crosshair); Cvar_RegisterVariable (&cl_rollspeed); Cvar_RegisterVariable (&cl_rollangle); Cvar_RegisterVariable (&cl_bob); Cvar_RegisterVariable (&cl_bobcycle); Cvar_RegisterVariable (&cl_bobup); Cvar_RegisterVariable (&cl_bob2); Cvar_RegisterVariable (&cl_bob2cycle); Cvar_RegisterVariable (&cl_bob2smooth); Cvar_RegisterVariable (&cl_bobfall); Cvar_RegisterVariable (&cl_bobfallcycle); Cvar_RegisterVariable (&cl_bobfallminspeed); Cvar_RegisterVariable (&cl_bobmodel); Cvar_RegisterVariable (&cl_bobmodel_side); Cvar_RegisterVariable (&cl_bobmodel_up); Cvar_RegisterVariable (&cl_bobmodel_speed); Cvar_RegisterVariable (&cl_bob_limit); Cvar_RegisterVariable (&cl_bob_limit_heightcheck); Cvar_RegisterVariable (&cl_bob_limit_heightcheck_dontcrosswatersurface); Cvar_RegisterVariable (&cl_bob_velocity_limit); Cvar_RegisterVariable (&cl_leanmodel); Cvar_RegisterVariable (&cl_leanmodel_side_speed); Cvar_RegisterVariable (&cl_leanmodel_side_limit); Cvar_RegisterVariable (&cl_leanmodel_side_highpass1); Cvar_RegisterVariable (&cl_leanmodel_side_lowpass); Cvar_RegisterVariable (&cl_leanmodel_side_highpass); Cvar_RegisterVariable (&cl_leanmodel_up_speed); Cvar_RegisterVariable (&cl_leanmodel_up_limit); Cvar_RegisterVariable (&cl_leanmodel_up_highpass1); Cvar_RegisterVariable (&cl_leanmodel_up_lowpass); Cvar_RegisterVariable (&cl_leanmodel_up_highpass); Cvar_RegisterVariable (&cl_followmodel); Cvar_RegisterVariable (&cl_followmodel_side_speed); Cvar_RegisterVariable (&cl_followmodel_side_limit); Cvar_RegisterVariable (&cl_followmodel_side_highpass1); Cvar_RegisterVariable (&cl_followmodel_side_lowpass); Cvar_RegisterVariable (&cl_followmodel_side_highpass); Cvar_RegisterVariable (&cl_followmodel_up_speed); Cvar_RegisterVariable (&cl_followmodel_up_limit); Cvar_RegisterVariable (&cl_followmodel_up_highpass1); Cvar_RegisterVariable (&cl_followmodel_up_lowpass); Cvar_RegisterVariable (&cl_followmodel_up_highpass); Cvar_RegisterVariable (&cl_viewmodel_scale); Cvar_RegisterVariable (&v_kicktime); Cvar_RegisterVariable (&v_kickroll); Cvar_RegisterVariable (&v_kickpitch); Cvar_RegisterVariable (&cl_stairsmoothspeed); Cvar_RegisterVariable (&cl_smoothviewheight); Cvar_RegisterVariable (&chase_back); Cvar_RegisterVariable (&chase_up); Cvar_RegisterVariable (&chase_active); Cvar_RegisterVariable (&chase_overhead); Cvar_RegisterVariable (&chase_pitchangle); Cvar_RegisterVariable (&chase_stevie); Cvar_RegisterVariable (&v_deathtilt); Cvar_RegisterVariable (&v_deathtiltangle); Cvar_RegisterVariable (&v_yshearing); } darkplaces/curves.h0000664000175000017500000000336713067716216013661 0ustar kalevkalev #ifndef CURVES_H #define CURVES_H #define PATCH_LODS_NUM 2 #define PATCH_LOD_COLLISION 0 #define PATCH_LOD_VISUAL 1 typedef struct patchinfo_s { int xsize, ysize; struct { int xtess, ytess; } lods[PATCH_LODS_NUM]; } patchinfo_t; // Calculate number of resulting vertex rows/columns by given patch size and tesselation factor // When tess=0 it means that we reduce detalization of base 3x3 patches by removing middle row and column // "DimForTess" is "DIMension FOR TESSelation factor" int Q3PatchDimForTess(int size, int tess); // usage: // to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: // Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight); // returns how much tesselation of each segment is needed to remain under tolerance int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance); // returns how much tesselation of each segment is needed to remain under tolerance int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance); // calculates elements for a grid of vertices // (such as those produced by Q3PatchTesselate) // (note: width and height are the actual vertex size, this produces // (width-1)*(height-1)*2 triangles, 3 elements each) void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex); int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2); #endif darkplaces/menu.c0000664000175000017500000050507013067716220013302 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #ifdef CONFIG_CD #include "cdaudio.h" #endif #include "image.h" #include "progsvm.h" #include "mprogdefs.h" #define TYPE_DEMO 1 #define TYPE_GAME 2 #define TYPE_BOTH 3 static cvar_t forceqmenu = { 0, "forceqmenu", "0", "enables the quake menu instead of the quakec menu.dat (if present)" }; static cvar_t menu_progs = { 0, "menu_progs", "menu.dat", "name of quakec menu.dat file" }; static int NehGameType; enum m_state_e m_state; char m_return_reason[128]; void M_Menu_Main_f (void); void M_Menu_SinglePlayer_f (void); void M_Menu_Transfusion_Episode_f (void); void M_Menu_Transfusion_Skill_f (void); void M_Menu_Load_f (void); void M_Menu_Save_f (void); void M_Menu_MultiPlayer_f (void); void M_Menu_Setup_f (void); void M_Menu_Options_f (void); void M_Menu_Options_Effects_f (void); void M_Menu_Options_Graphics_f (void); void M_Menu_Options_ColorControl_f (void); void M_Menu_Keys_f (void); void M_Menu_Reset_f (void); void M_Menu_Video_f (void); void M_Menu_Help_f (void); void M_Menu_Credits_f (void); void M_Menu_Quit_f (void); void M_Menu_LanConfig_f (void); void M_Menu_GameOptions_f (void); void M_Menu_ServerList_f (void); void M_Menu_ModList_f (void); static void M_Main_Draw (void); static void M_SinglePlayer_Draw (void); static void M_Transfusion_Episode_Draw (void); static void M_Transfusion_Skill_Draw (void); static void M_Load_Draw (void); static void M_Save_Draw (void); static void M_MultiPlayer_Draw (void); static void M_Setup_Draw (void); static void M_Options_Draw (void); static void M_Options_Effects_Draw (void); static void M_Options_Graphics_Draw (void); static void M_Options_ColorControl_Draw (void); static void M_Keys_Draw (void); static void M_Reset_Draw (void); static void M_Video_Draw (void); static void M_Help_Draw (void); static void M_Credits_Draw (void); static void M_Quit_Draw (void); static void M_LanConfig_Draw (void); static void M_GameOptions_Draw (void); static void M_ServerList_Draw (void); static void M_ModList_Draw (void); static void M_Main_Key (int key, int ascii); static void M_SinglePlayer_Key (int key, int ascii); static void M_Transfusion_Episode_Key (int key, int ascii); static void M_Transfusion_Skill_Key (int key, int ascii); static void M_Load_Key (int key, int ascii); static void M_Save_Key (int key, int ascii); static void M_MultiPlayer_Key (int key, int ascii); static void M_Setup_Key (int key, int ascii); static void M_Options_Key (int key, int ascii); static void M_Options_Effects_Key (int key, int ascii); static void M_Options_Graphics_Key (int key, int ascii); static void M_Options_ColorControl_Key (int key, int ascii); static void M_Keys_Key (int key, int ascii); static void M_Reset_Key (int key, int ascii); static void M_Video_Key (int key, int ascii); static void M_Help_Key (int key, int ascii); static void M_Credits_Key (int key, int ascii); static void M_Quit_Key (int key, int ascii); static void M_LanConfig_Key (int key, int ascii); static void M_GameOptions_Key (int key, int ascii); static void M_ServerList_Key (int key, int ascii); static void M_ModList_Key (int key, int ascii); static qboolean m_entersound; ///< play after drawing a frame, so caching won't disrupt the sound void M_Update_Return_Reason(const char *s) { strlcpy(m_return_reason, s, sizeof(m_return_reason)); if (s) Con_DPrintf("%s\n", s); } #define StartingGame (m_multiplayer_cursor == 1) #define JoiningGame (m_multiplayer_cursor == 0) // Nehahra #define NumberOfNehahraDemos 34 typedef struct nehahrademonames_s { const char *name; const char *desc; } nehahrademonames_t; static nehahrademonames_t NehahraDemos[NumberOfNehahraDemos] = { {"intro", "Prologue"}, {"genf", "The Beginning"}, {"genlab", "A Doomed Project"}, {"nehcre", "The New Recruits"}, {"maxneh", "Breakthrough"}, {"maxchar", "Renewal and Duty"}, {"crisis", "Worlds Collide"}, {"postcris", "Darkening Skies"}, {"hearing", "The Hearing"}, {"getjack", "On a Mexican Radio"}, {"prelude", "Honor and Justice"}, {"abase", "A Message Sent"}, {"effect", "The Other Side"}, {"uhoh", "Missing in Action"}, {"prepare", "The Response"}, {"vision", "Farsighted Eyes"}, {"maxturns", "Enter the Immortal"}, {"backlot", "Separate Ways"}, {"maxside", "The Ancient Runes"}, {"counter", "The New Initiative"}, {"warprep", "Ghosts to the World"}, {"counter1", "A Fate Worse Than Death"}, {"counter2", "Friendly Fire"}, {"counter3", "Minor Setback"}, {"madmax", "Scores to Settle"}, {"quake", "One Man"}, {"cthmm", "Shattered Masks"}, {"shades", "Deal with the Dead"}, {"gophil", "An Unlikely Hero"}, {"cstrike", "War in Hell"}, {"shubset", "The Conspiracy"}, {"shubdie", "Even Death May Die"}, {"newranks", "An Empty Throne"}, {"seal", "The Seal is Broken"} }; static float menu_x, menu_y, menu_width, menu_height; static void M_Background(int width, int height) { menu_width = bound(1.0f, (float)width, vid_conwidth.value); menu_height = bound(1.0f, (float)height, vid_conheight.value); menu_x = (vid_conwidth.integer - menu_width) * 0.5; menu_y = (vid_conheight.integer - menu_height) * 0.5; //DrawQ_Fill(menu_x, menu_y, menu_width, menu_height, 0, 0, 0, 0.5, 0); DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 0.5, 0); } /* ================ M_DrawCharacter Draws one solid graphics character ================ */ static void M_DrawCharacter (float cx, float cy, int num) { char temp[2]; temp[0] = num; temp[1] = 0; DrawQ_String(menu_x + cx, menu_y + cy, temp, 1, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); } static void M_PrintColored(float cx, float cy, const char *str) { DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_MENU); } static void M_Print(float cx, float cy, const char *str) { DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); } static void M_PrintRed(float cx, float cy, const char *str) { DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 0, 0, 1, 0, NULL, true, FONT_MENU); } static void M_ItemPrint(float cx, float cy, const char *str, int unghosted) { if (unghosted) DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_MENU); else DrawQ_String(menu_x + cx, menu_y + cy, str, 0, 8, 8, 0.4, 0.4, 0.4, 1, 0, NULL, true, FONT_MENU); } static void M_DrawPic(float cx, float cy, const char *picname) { DrawQ_Pic(menu_x + cx, menu_y + cy, Draw_CachePic (picname), 0, 0, 1, 1, 1, 1, 0); } static void M_DrawTextBox(float x, float y, float width, float height) { int n; float cx, cy; // draw left side cx = x; cy = y; M_DrawPic (cx, cy, "gfx/box_tl"); for (n = 0; n < height; n++) { cy += 8; M_DrawPic (cx, cy, "gfx/box_ml"); } M_DrawPic (cx, cy+8, "gfx/box_bl"); // draw middle cx += 8; while (width > 0) { cy = y; M_DrawPic (cx, cy, "gfx/box_tm"); for (n = 0; n < height; n++) { cy += 8; if (n >= 1) M_DrawPic (cx, cy, "gfx/box_mm2"); else M_DrawPic (cx, cy, "gfx/box_mm"); } M_DrawPic (cx, cy+8, "gfx/box_bm"); width -= 2; cx += 16; } // draw right side cy = y; M_DrawPic (cx, cy, "gfx/box_tr"); for (n = 0; n < height; n++) { cy += 8; M_DrawPic (cx, cy, "gfx/box_mr"); } M_DrawPic (cx, cy+8, "gfx/box_br"); } //============================================================================= //int m_save_demonum; /* ================ M_ToggleMenu ================ */ static void M_ToggleMenu(int mode) { m_entersound = true; if ((key_dest != key_menu && key_dest != key_menu_grabbed) || m_state != m_main) { if(mode == 0) return; // the menu is off, and we want it off M_Menu_Main_f (); } else { if(mode == 1) return; // the menu is on, and we want it on key_dest = key_game; m_state = m_none; } } static int demo_cursor; static void M_Demo_Draw (void) { int i; M_Background(320, 200); for (i = 0;i < NumberOfNehahraDemos;i++) M_Print(16, 16 + 8*i, NehahraDemos[i].desc); // line cursor M_DrawCharacter (8, 16 + demo_cursor*8, 12+((int)(realtime*4)&1)); } static void M_Menu_Demos_f (void) { key_dest = key_menu; m_state = m_demo; m_entersound = true; } static void M_Demo_Key (int k, int ascii) { char vabuf[1024]; switch (k) { case K_ESCAPE: M_Menu_Main_f (); break; case K_ENTER: S_LocalSound ("sound/misc/menu2.wav"); m_state = m_none; key_dest = key_game; Cbuf_AddText (va(vabuf, sizeof(vabuf), "playdemo %s\n", NehahraDemos[demo_cursor].name)); return; case K_UPARROW: case K_LEFTARROW: S_LocalSound ("sound/misc/menu1.wav"); demo_cursor--; if (demo_cursor < 0) demo_cursor = NumberOfNehahraDemos-1; break; case K_DOWNARROW: case K_RIGHTARROW: S_LocalSound ("sound/misc/menu1.wav"); demo_cursor++; if (demo_cursor >= NumberOfNehahraDemos) demo_cursor = 0; break; } } //============================================================================= /* MAIN MENU */ static int m_main_cursor; static qboolean m_missingdata = false; static int MAIN_ITEMS = 4; // Nehahra: Menu Disable void M_Menu_Main_f (void) { const char *s; s = "gfx/mainmenu"; if (gamemode == GAME_NEHAHRA) { if (FS_FileExists("maps/neh1m4.bsp")) { if (FS_FileExists("hearing.dem")) { Con_DPrint("Main menu: Nehahra movie and game detected.\n"); NehGameType = TYPE_BOTH; } else { Con_DPrint("Nehahra game detected.\n"); NehGameType = TYPE_GAME; } } else { if (FS_FileExists("hearing.dem")) { Con_DPrint("Nehahra movie detected.\n"); NehGameType = TYPE_DEMO; } else { Con_DPrint("Nehahra not found.\n"); NehGameType = TYPE_GAME; // could just complain, but... } } if (NehGameType == TYPE_DEMO) MAIN_ITEMS = 4; else if (NehGameType == TYPE_GAME) MAIN_ITEMS = 5; else MAIN_ITEMS = 6; } else if (gamemode == GAME_TRANSFUSION) { s = "gfx/menu/mainmenu1"; if (sv.active && !cl.intermission && cl.islocalgame) MAIN_ITEMS = 8; else MAIN_ITEMS = 7; } else MAIN_ITEMS = 5; // check if the game data is missing and use a different main menu if so m_missingdata = !forceqmenu.integer && Draw_CachePic (s)->tex == r_texture_notexture; if (m_missingdata) MAIN_ITEMS = 2; /* if (key_dest != key_menu) { m_save_demonum = cls.demonum; cls.demonum = -1; } */ key_dest = key_menu; m_state = m_main; m_entersound = true; } static void M_Main_Draw (void) { int f; cachepic_t *p; char vabuf[1024]; if (m_missingdata) { float y; const char *s; M_Background(640, 480); //fall back is always to 640x480, this makes it most readable at that. y = 480/3-16; s = "You have reached this menu due to missing or unlocatable content/data";M_PrintRed ((640-strlen(s)*8)*0.5, (480/3)-16, s);y+=8; y+=8; s = "You may consider adding";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; s = "-basedir /path/to/game";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; s = "to your launch commandline";M_Print ((640-strlen(s)*8)*0.5, y, s);y+=8; M_Print (640/2 - 48, 480/2, "Open Console"); //The console usually better shows errors (failures) M_Print (640/2 - 48, 480/2 + 8, "Quit"); M_DrawCharacter(640/2 - 56, 480/2 + (8 * m_main_cursor), 12+((int)(realtime*4)&1)); return; } if (gamemode == GAME_TRANSFUSION) { int y1, y2, y3; M_Background(640, 480); p = Draw_CachePic ("gfx/menu/tb-transfusion"); M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-transfusion"); y2 = 120; // 8 rather than MAIN_ITEMS to skip a number and not miss the last option for (y1 = 1; y1 <= 8; y1++) { if (MAIN_ITEMS == 7 && y1 == 4) y1++; M_DrawPic (0, y2, va(vabuf, sizeof(vabuf), "gfx/menu/mainmenu%i", y1)); y2 += 40; } if (MAIN_ITEMS == 7 && m_main_cursor > 2) y3 = m_main_cursor + 2; else y3 = m_main_cursor + 1; M_DrawPic (0, 120 + m_main_cursor * 40, va(vabuf, sizeof(vabuf), "gfx/menu/mainmenu%iselected", y3)); return; } M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/ttl_main"); M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_main"); // Nehahra if (gamemode == GAME_NEHAHRA) { if (NehGameType == TYPE_BOTH) M_DrawPic (72, 32, "gfx/mainmenu"); else if (NehGameType == TYPE_GAME) M_DrawPic (72, 32, "gfx/gamemenu"); else M_DrawPic (72, 32, "gfx/demomenu"); } else M_DrawPic (72, 32, "gfx/mainmenu"); f = (int)(realtime * 10)%6; M_DrawPic (54, 32 + m_main_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); } static void M_Main_Key (int key, int ascii) { switch (key) { case K_ESCAPE: key_dest = key_game; m_state = m_none; //cls.demonum = m_save_demonum; //if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) // CL_NextDemo (); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); if (++m_main_cursor >= MAIN_ITEMS) m_main_cursor = 0; break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); if (--m_main_cursor < 0) m_main_cursor = MAIN_ITEMS - 1; break; case K_ENTER: m_entersound = true; if (m_missingdata) { switch (m_main_cursor) { case 0: if (cls.state == ca_connected) { m_state = m_none; key_dest = key_game; } Con_ToggleConsole_f (); break; case 1: M_Menu_Quit_f (); break; } } else if (gamemode == GAME_NEHAHRA) { switch (NehGameType) { case TYPE_BOTH: switch (m_main_cursor) { case 0: M_Menu_SinglePlayer_f (); break; case 1: M_Menu_Demos_f (); break; case 2: M_Menu_MultiPlayer_f (); break; case 3: M_Menu_Options_f (); break; case 4: key_dest = key_game; if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("playdemo endcred\n"); break; case 5: M_Menu_Quit_f (); break; } break; case TYPE_GAME: switch (m_main_cursor) { case 0: M_Menu_SinglePlayer_f (); break; case 1: M_Menu_MultiPlayer_f (); break; case 2: M_Menu_Options_f (); break; case 3: key_dest = key_game; if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("playdemo endcred\n"); break; case 4: M_Menu_Quit_f (); break; } break; case TYPE_DEMO: switch (m_main_cursor) { case 0: M_Menu_Demos_f (); break; case 1: key_dest = key_game; if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("playdemo endcred\n"); break; case 2: M_Menu_Options_f (); break; case 3: M_Menu_Quit_f (); break; } break; } } else if (gamemode == GAME_TRANSFUSION) { if (MAIN_ITEMS == 7) { switch (m_main_cursor) { case 0: M_Menu_Transfusion_Episode_f (); break; case 1: M_Menu_MultiPlayer_f (); break; case 2: M_Menu_Options_f (); break; case 3: M_Menu_Load_f (); break; case 4: M_Menu_Help_f (); break; case 5: M_Menu_Credits_f (); break; case 6: M_Menu_Quit_f (); break; } } else { switch (m_main_cursor) { case 0: M_Menu_Transfusion_Episode_f (); break; case 1: M_Menu_MultiPlayer_f (); break; case 2: M_Menu_Options_f (); break; case 3: M_Menu_Save_f (); break; case 4: M_Menu_Load_f (); break; case 5: M_Menu_Help_f (); break; case 6: M_Menu_Credits_f (); break; case 7: M_Menu_Quit_f (); break; } } } else { switch (m_main_cursor) { case 0: M_Menu_SinglePlayer_f (); break; case 1: M_Menu_MultiPlayer_f (); break; case 2: M_Menu_Options_f (); break; case 3: M_Menu_Help_f (); break; case 4: M_Menu_Quit_f (); break; } } } } //============================================================================= /* SINGLE PLAYER MENU */ static int m_singleplayer_cursor; #define SINGLEPLAYER_ITEMS 3 void M_Menu_SinglePlayer_f (void) { key_dest = key_menu; m_state = m_singleplayer; m_entersound = true; } static void M_SinglePlayer_Draw (void) { cachepic_t *p; char vabuf[1024]; M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/ttl_sgl"); // Some mods don't have a single player mode if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) { M_DrawPic ((320 - p->width) / 2, 4, "gfx/ttl_sgl"); M_DrawTextBox (60, 8 * 8, 23, 4); if (gamemode == GAME_GOODVSBAD2) M_Print(95, 10 * 8, "Good Vs Bad 2 is for"); else // if (gamemode == GAME_BATTLEMECH) M_Print(95, 10 * 8, "Battlemech is for"); M_Print(83, 11 * 8, "multiplayer play only"); } else { int f; M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_sgl"); M_DrawPic (72, 32, "gfx/sp_menu"); f = (int)(realtime * 10)%6; M_DrawPic (54, 32 + m_singleplayer_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); } } static void M_SinglePlayer_Key (int key, int ascii) { if (gamemode == GAME_GOODVSBAD2 || gamemode == GAME_BATTLEMECH) { if (key == K_ESCAPE || key == K_ENTER) m_state = m_main; return; } switch (key) { case K_ESCAPE: M_Menu_Main_f (); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); if (++m_singleplayer_cursor >= SINGLEPLAYER_ITEMS) m_singleplayer_cursor = 0; break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); if (--m_singleplayer_cursor < 0) m_singleplayer_cursor = SINGLEPLAYER_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch (m_singleplayer_cursor) { case 0: key_dest = key_game; if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("maxplayers 1\n"); Cbuf_AddText ("deathmatch 0\n"); Cbuf_AddText ("coop 0\n"); if (gamemode == GAME_TRANSFUSION) { key_dest = key_menu; M_Menu_Transfusion_Episode_f (); break; } Cbuf_AddText ("startmap_sp\n"); break; case 1: M_Menu_Load_f (); break; case 2: M_Menu_Save_f (); break; } } } //============================================================================= /* LOAD/SAVE MENU */ static int load_cursor; ///< 0 < load_cursor < MAX_SAVEGAMES static char m_filenames[MAX_SAVEGAMES][SAVEGAME_COMMENT_LENGTH+1]; static int loadable[MAX_SAVEGAMES]; static void M_ScanSaves (void) { int i, j; size_t len; char name[MAX_OSPATH]; char buf[SAVEGAME_COMMENT_LENGTH + 256]; const char *t; qfile_t *f; // int version; for (i=0 ; iwidth)/2, 4, "gfx/p_load" ); for (i=0 ; i< MAX_SAVEGAMES; i++) M_Print(16, 32 + 8*i, m_filenames[i]); // line cursor M_DrawCharacter (8, 32 + load_cursor*8, 12+((int)(realtime*4)&1)); } static void M_Save_Draw (void) { int i; cachepic_t *p; M_Background(320, 200); p = Draw_CachePic ("gfx/p_save"); M_DrawPic ( (320-p->width)/2, 4, "gfx/p_save"); for (i=0 ; i= MAX_SAVEGAMES) load_cursor = 0; break; } } static void M_Save_Key (int k, int ascii) { char vabuf[1024]; switch (k) { case K_ESCAPE: if (gamemode == GAME_TRANSFUSION) M_Menu_Main_f (); else M_Menu_SinglePlayer_f (); break; case K_ENTER: m_state = m_none; key_dest = key_game; Cbuf_AddText (va(vabuf, sizeof(vabuf), "save s%i\n", load_cursor)); return; case K_UPARROW: case K_LEFTARROW: S_LocalSound ("sound/misc/menu1.wav"); load_cursor--; if (load_cursor < 0) load_cursor = MAX_SAVEGAMES-1; break; case K_DOWNARROW: case K_RIGHTARROW: S_LocalSound ("sound/misc/menu1.wav"); load_cursor++; if (load_cursor >= MAX_SAVEGAMES) load_cursor = 0; break; } } //============================================================================= /* Transfusion Single Player Episode Menu */ static int m_episode_cursor; #define EPISODE_ITEMS 6 void M_Menu_Transfusion_Episode_f (void) { m_entersound = true; m_state = m_transfusion_episode; key_dest = key_menu; } static void M_Transfusion_Episode_Draw (void) { int y; cachepic_t *p; char vabuf[1024]; M_Background(640, 480); p = Draw_CachePic ("gfx/menu/tb-episodes"); M_DrawPic (640/2 - p->width/2, 40, "gfx/menu/tb-episodes"); for (y = 0; y < EPISODE_ITEMS; y++){ M_DrawPic (0, 160 + y * 40, va(vabuf, sizeof(vabuf), "gfx/menu/episode%i", y+1)); } M_DrawPic (0, 120 + (m_episode_cursor + 1) * 40, va(vabuf, sizeof(vabuf), "gfx/menu/episode%iselected", m_episode_cursor + 1)); } static void M_Transfusion_Episode_Key (int key, int ascii) { switch (key) { case K_ESCAPE: M_Menu_Main_f (); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); m_episode_cursor++; if (m_episode_cursor >= EPISODE_ITEMS) m_episode_cursor = 0; break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); m_episode_cursor--; if (m_episode_cursor < 0) m_episode_cursor = EPISODE_ITEMS - 1; break; case K_ENTER: Cbuf_AddText ("deathmatch 0\n"); m_entersound = true; M_Menu_Transfusion_Skill_f (); } } //============================================================================= /* Transfusion Single Player Skill Menu */ static int m_skill_cursor = 2; #define SKILL_ITEMS 5 void M_Menu_Transfusion_Skill_f (void) { m_entersound = true; m_state = m_transfusion_skill; key_dest = key_menu; } static void M_Transfusion_Skill_Draw (void) { int y; cachepic_t *p; char vabuf[1024]; M_Background(640, 480); p = Draw_CachePic ("gfx/menu/tb-difficulty"); M_DrawPic(640/2 - p->width/2, 40, "gfx/menu/tb-difficulty"); for (y = 0; y < SKILL_ITEMS; y++) { M_DrawPic (0, 180 + y * 40, va(vabuf, sizeof(vabuf), "gfx/menu/difficulty%i", y+1)); } M_DrawPic (0, 140 + (m_skill_cursor + 1) *40, va(vabuf, sizeof(vabuf), "gfx/menu/difficulty%iselected", m_skill_cursor + 1)); } static void M_Transfusion_Skill_Key (int key, int ascii) { switch (key) { case K_ESCAPE: M_Menu_Transfusion_Episode_f (); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); m_skill_cursor++; if (m_skill_cursor >= SKILL_ITEMS) m_skill_cursor = 0; break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); m_skill_cursor--; if (m_skill_cursor < 0) m_skill_cursor = SKILL_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch (m_skill_cursor) { case 0: Cbuf_AddText ("skill 1\n"); break; case 1: Cbuf_AddText ("skill 2\n"); break; case 2: Cbuf_AddText ("skill 3\n"); break; case 3: Cbuf_AddText ("skill 4\n"); break; case 4: Cbuf_AddText ("skill 5\n"); break; } key_dest = key_game; if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("maxplayers 1\n"); Cbuf_AddText ("deathmatch 0\n"); Cbuf_AddText ("coop 0\n"); switch (m_episode_cursor) { case 0: Cbuf_AddText ("map e1m1\n"); break; case 1: Cbuf_AddText ("map e2m1\n"); break; case 2: Cbuf_AddText ("map e3m1\n"); break; case 3: Cbuf_AddText ("map e4m1\n"); break; case 4: Cbuf_AddText ("map e6m1\n"); break; case 5: Cbuf_AddText ("map cp01\n"); break; } } } //============================================================================= /* MULTIPLAYER MENU */ static int m_multiplayer_cursor; #define MULTIPLAYER_ITEMS 3 void M_Menu_MultiPlayer_f (void) { key_dest = key_menu; m_state = m_multiplayer; m_entersound = true; } static void M_MultiPlayer_Draw (void) { int f; cachepic_t *p; char vabuf[1024]; if (gamemode == GAME_TRANSFUSION) { M_Background(640, 480); p = Draw_CachePic ("gfx/menu/tb-online"); M_DrawPic (640/2 - p->width/2, 140, "gfx/menu/tb-online"); for (f = 1; f <= MULTIPLAYER_ITEMS; f++) M_DrawPic (0, 180 + f*40, va(vabuf, sizeof(vabuf), "gfx/menu/online%i", f)); M_DrawPic (0, 220 + m_multiplayer_cursor * 40, va(vabuf, sizeof(vabuf), "gfx/menu/online%iselected", m_multiplayer_cursor + 1)); return; } M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_multi"); M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); M_DrawPic (72, 32, "gfx/mp_menu"); f = (int)(realtime * 10)%6; M_DrawPic (54, 32 + m_multiplayer_cursor * 20, va(vabuf, sizeof(vabuf), "gfx/menudot%i", f+1)); } static void M_MultiPlayer_Key (int key, int ascii) { switch (key) { case K_ESCAPE: M_Menu_Main_f (); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); if (++m_multiplayer_cursor >= MULTIPLAYER_ITEMS) m_multiplayer_cursor = 0; break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); if (--m_multiplayer_cursor < 0) m_multiplayer_cursor = MULTIPLAYER_ITEMS - 1; break; case K_ENTER: m_entersound = true; switch (m_multiplayer_cursor) { case 0: case 1: M_Menu_LanConfig_f (); break; case 2: M_Menu_Setup_f (); break; } } } //============================================================================= /* SETUP MENU */ static int setup_cursor = 4; static int setup_cursor_table[] = {40, 64, 88, 124, 140}; static char setup_myname[MAX_SCOREBOARDNAME]; static int setup_oldtop; static int setup_oldbottom; static int setup_top; static int setup_bottom; static int setup_rate; static int setup_oldrate; #define NUM_SETUP_CMDS 5 void M_Menu_Setup_f (void) { key_dest = key_menu; m_state = m_setup; m_entersound = true; strlcpy(setup_myname, cl_name.string, sizeof(setup_myname)); setup_top = setup_oldtop = cl_color.integer >> 4; setup_bottom = setup_oldbottom = cl_color.integer & 15; setup_rate = cl_rate.integer; } static int menuplyr_width, menuplyr_height, menuplyr_top, menuplyr_bottom, menuplyr_load; static unsigned char *menuplyr_pixels; static unsigned int *menuplyr_translated; typedef struct ratetable_s { int rate; const char *name; } ratetable_t; #define RATES ((int)(sizeof(setup_ratetable)/sizeof(setup_ratetable[0]))) static ratetable_t setup_ratetable[] = { {1000, "28.8 bad"}, {1500, "28.8 mediocre"}, {2000, "28.8 good"}, {2500, "33.6 mediocre"}, {3000, "33.6 good"}, {3500, "56k bad"}, {4000, "56k mediocre"}, {4500, "56k adequate"}, {5000, "56k good"}, {7000, "64k ISDN"}, {15000, "128k ISDN"}, {25000, "broadband"} }; static int setup_rateindex(int rate) { int i; for (i = 0;i < RATES;i++) if (setup_ratetable[i].rate > setup_rate) break; return bound(1, i, RATES) - 1; } static void M_Setup_Draw (void) { int i, j; cachepic_t *p; char vabuf[1024]; M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_multi"); M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); M_Print(64, 40, "Your name"); M_DrawTextBox (160, 32, 16, 1); M_PrintColored(168, 40, setup_myname); if (gamemode != GAME_GOODVSBAD2) { M_Print(64, 64, "Shirt color"); M_Print(64, 88, "Pants color"); } M_Print(64, 124-8, "Network speed limit"); M_Print(168, 124, va(vabuf, sizeof(vabuf), "%i (%s)", setup_rate, setup_ratetable[setup_rateindex(setup_rate)].name)); M_DrawTextBox (64, 140-8, 14, 1); M_Print(72, 140, "Accept Changes"); // LordHavoc: rewrote this code greatly if (menuplyr_load) { unsigned char *f; fs_offset_t filesize; menuplyr_load = false; menuplyr_top = -1; menuplyr_bottom = -1; f = FS_LoadFile("gfx/menuplyr.lmp", tempmempool, true, &filesize); if (f && filesize >= 9) { int width, height; width = f[0] + f[1] * 256 + f[2] * 65536 + f[3] * 16777216; height = f[4] + f[5] * 256 + f[6] * 65536 + f[7] * 16777216; if (filesize >= 8 + width * height) { menuplyr_width = width; menuplyr_height = height; menuplyr_pixels = (unsigned char *)Mem_Alloc(cls.permanentmempool, width * height); menuplyr_translated = (unsigned int *)Mem_Alloc(cls.permanentmempool, width * height * 4); memcpy(menuplyr_pixels, f + 8, width * height); } } if (f) Mem_Free(f); } if (menuplyr_pixels) { if (menuplyr_top != setup_top || menuplyr_bottom != setup_bottom) { menuplyr_top = setup_top; menuplyr_bottom = setup_bottom; for (i = 0;i < menuplyr_width * menuplyr_height;i++) { j = menuplyr_pixels[i]; if (j >= TOP_RANGE && j < TOP_RANGE + 16) { if (menuplyr_top < 8 || menuplyr_top == 14) j = menuplyr_top * 16 + (j - TOP_RANGE); else j = menuplyr_top * 16 + 15-(j - TOP_RANGE); } else if (j >= BOTTOM_RANGE && j < BOTTOM_RANGE + 16) { if (menuplyr_bottom < 8 || menuplyr_bottom == 14) j = menuplyr_bottom * 16 + (j - BOTTOM_RANGE); else j = menuplyr_bottom * 16 + 15-(j - BOTTOM_RANGE); } menuplyr_translated[i] = palette_bgra_transparent[j]; } Draw_NewPic("gfx/menuplyr", menuplyr_width, menuplyr_height, true, (unsigned char *)menuplyr_translated); } M_DrawPic(160, 48, "gfx/bigbox"); M_DrawPic(172, 56, "gfx/menuplyr"); } if (setup_cursor == 0) M_DrawCharacter (168 + 8*strlen(setup_myname), setup_cursor_table [setup_cursor], 10+((int)(realtime*4)&1)); else M_DrawCharacter (56, setup_cursor_table [setup_cursor], 12+((int)(realtime*4)&1)); } static void M_Setup_Key (int k, int ascii) { int l; char vabuf[1024]; switch (k) { case K_ESCAPE: M_Menu_MultiPlayer_f (); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); setup_cursor--; if (setup_cursor < 0) setup_cursor = NUM_SETUP_CMDS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); setup_cursor++; if (setup_cursor >= NUM_SETUP_CMDS) setup_cursor = 0; break; case K_LEFTARROW: if (setup_cursor < 1) return; S_LocalSound ("sound/misc/menu3.wav"); if (setup_cursor == 1) setup_top = setup_top - 1; if (setup_cursor == 2) setup_bottom = setup_bottom - 1; if (setup_cursor == 3) { l = setup_rateindex(setup_rate) - 1; if (l < 0) l = RATES - 1; setup_rate = setup_ratetable[l].rate; } break; case K_RIGHTARROW: if (setup_cursor < 1) return; forward: S_LocalSound ("sound/misc/menu3.wav"); if (setup_cursor == 1) setup_top = setup_top + 1; if (setup_cursor == 2) setup_bottom = setup_bottom + 1; if (setup_cursor == 3) { l = setup_rateindex(setup_rate) + 1; if (l >= RATES) l = 0; setup_rate = setup_ratetable[l].rate; } break; case K_ENTER: if (setup_cursor == 0) return; if (setup_cursor == 1 || setup_cursor == 2 || setup_cursor == 3) goto forward; // setup_cursor == 4 (Accept changes) if (strcmp(cl_name.string, setup_myname) != 0) Cbuf_AddText(va(vabuf, sizeof(vabuf), "name \"%s\"\n", setup_myname) ); if (setup_top != setup_oldtop || setup_bottom != setup_oldbottom) Cbuf_AddText(va(vabuf, sizeof(vabuf), "color %i %i\n", setup_top, setup_bottom) ); if (setup_rate != setup_oldrate) Cbuf_AddText(va(vabuf, sizeof(vabuf), "rate %i\n", setup_rate)); m_entersound = true; M_Menu_MultiPlayer_f (); break; case K_BACKSPACE: if (setup_cursor == 0) { if (strlen(setup_myname)) setup_myname[strlen(setup_myname)-1] = 0; } break; default: if (ascii < 32) break; if (setup_cursor == 0) { l = (int)strlen(setup_myname); if (l < 15) { setup_myname[l+1] = 0; setup_myname[l] = ascii; } } } if (setup_top > 15) setup_top = 0; if (setup_top < 0) setup_top = 15; if (setup_bottom > 15) setup_bottom = 0; if (setup_bottom < 0) setup_bottom = 15; } //============================================================================= /* OPTIONS MENU */ #define SLIDER_RANGE 10 static void M_DrawSlider (int x, int y, float num, float rangemin, float rangemax) { char text[16]; int i; float range; range = bound(0, (num - rangemin) / (rangemax - rangemin), 1); M_DrawCharacter (x-8, y, 128); for (i = 0;i < SLIDER_RANGE;i++) M_DrawCharacter (x + i*8, y, 129); M_DrawCharacter (x+i*8, y, 130); M_DrawCharacter (x + (SLIDER_RANGE-1)*8 * range, y, 131); if (fabs((int)num - num) < 0.01) dpsnprintf(text, sizeof(text), "%i", (int)num); else dpsnprintf(text, sizeof(text), "%.3f", num); M_Print(x + (SLIDER_RANGE+2) * 8, y, text); } static void M_DrawCheckbox (int x, int y, int on) { if (on) M_Print(x, y, "on"); else M_Print(x, y, "off"); } //#define OPTIONS_ITEMS 25 aule was here #define OPTIONS_ITEMS 27 static int options_cursor; void M_Menu_Options_f (void) { key_dest = key_menu; m_state = m_options; m_entersound = true; } extern cvar_t slowmo; extern dllhandle_t jpeg_dll; extern cvar_t gl_texture_anisotropy; extern cvar_t r_textshadow; extern cvar_t r_hdr_scenebrightness; static void M_Menu_Options_AdjustSliders (int dir) { int optnum; double f; S_LocalSound ("sound/misc/menu3.wav"); optnum = 0; if (options_cursor == optnum++) ; else if (options_cursor == optnum++) ; else if (options_cursor == optnum++) ; else if (options_cursor == optnum++) ; else if (options_cursor == optnum++) Cvar_SetValueQuick(&crosshair, bound(0, crosshair.integer + dir, 7)); else if (options_cursor == optnum++) Cvar_SetValueQuick(&sensitivity, bound(1, sensitivity.value + dir * 0.5, 50)); else if (options_cursor == optnum++) Cvar_SetValueQuick(&m_pitch, -m_pitch.value); else if (options_cursor == optnum++) Cvar_SetValueQuick(&scr_fov, bound(1, scr_fov.integer + dir * 1, 170)); else if (options_cursor == optnum++) { if (cl_forwardspeed.value > 200) { Cvar_SetValueQuick (&cl_forwardspeed, 200); Cvar_SetValueQuick (&cl_backspeed, 200); } else { Cvar_SetValueQuick (&cl_forwardspeed, 400); Cvar_SetValueQuick (&cl_backspeed, 400); } } else if (options_cursor == optnum++) Cvar_SetValueQuick(&showfps, !showfps.integer); else if (options_cursor == optnum++) {f = !(showdate.integer && showtime.integer);Cvar_SetValueQuick(&showdate, f);Cvar_SetValueQuick(&showtime, f);} else if (options_cursor == optnum++) ; else if (options_cursor == optnum++) Cvar_SetValueQuick(&r_hdr_scenebrightness, bound(1, r_hdr_scenebrightness.value + dir * 0.0625, 4)); else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_contrast, bound(1, v_contrast.value + dir * 0.0625, 4)); else if (options_cursor == optnum++) Cvar_SetValueQuick(&v_gamma, bound(0.5, v_gamma.value + dir * 0.0625, 3)); else if (options_cursor == optnum++) Cvar_SetValueQuick(&volume, bound(0, volume.value + dir * 0.0625, 1)); #ifdef CONFIG_CD else if (options_cursor == optnum++) Cvar_SetValueQuick(&bgmvolume, bound(0, bgmvolume.value + dir * 0.0625, 1)); #endif } static int m_optnum; static int m_opty; static int m_optcursor; static void M_Options_PrintCommand(const char *s, int enabled) { if (m_opty >= 32) { if (m_optnum == m_optcursor) DrawQ_Fill(menu_x + 48, menu_y + m_opty, 320, 8, m_optnum == m_optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); M_ItemPrint(0 + 48, m_opty, s, enabled); } m_opty += 8; m_optnum++; } static void M_Options_PrintCheckbox(const char *s, int enabled, int yes) { if (m_opty >= 32) { if (m_optnum == m_optcursor) DrawQ_Fill(menu_x + 48, menu_y + m_opty, 320, 8, m_optnum == m_optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); M_ItemPrint(0 + 48, m_opty, s, enabled); M_DrawCheckbox(0 + 48 + (int)strlen(s) * 8 + 8, m_opty, yes); } m_opty += 8; m_optnum++; } static void M_Options_PrintSlider(const char *s, int enabled, float value, float minvalue, float maxvalue) { if (m_opty >= 32) { if (m_optnum == m_optcursor) DrawQ_Fill(menu_x + 48, menu_y + m_opty, 320, 8, m_optnum == m_optcursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); M_ItemPrint(0 + 48, m_opty, s, enabled); M_DrawSlider(0 + 48 + (int)strlen(s) * 8 + 8, m_opty, value, minvalue, maxvalue); } m_opty += 8; m_optnum++; } static void M_Options_Draw (void) { int visible; cachepic_t *p; M_Background(320, bound(200, 32 + OPTIONS_ITEMS * 8, vid_conheight.integer)); M_DrawPic(16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_option"); M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); m_optnum = 0; m_optcursor = options_cursor; visible = (int)((menu_height - 32) / 8); m_opty = 32 - bound(0, m_optcursor - (visible >> 1), max(0, OPTIONS_ITEMS - visible)) * 8; M_Options_PrintCommand( " Customize controls", true); M_Options_PrintCommand( " Go to console", true); M_Options_PrintCommand( " Reset to defaults", true); M_Options_PrintCommand( " Change Video Mode", true); M_Options_PrintSlider( " Crosshair", true, crosshair.value, 0, 7); M_Options_PrintSlider( " Mouse Speed", true, sensitivity.value, 1, 50); M_Options_PrintCheckbox(" Invert Mouse", true, m_pitch.value < 0); M_Options_PrintSlider( " Field of View", true, scr_fov.integer, 1, 170); M_Options_PrintCheckbox(" Always Run", true, cl_forwardspeed.value > 200); M_Options_PrintCheckbox(" Show Framerate", true, showfps.integer); M_Options_PrintCheckbox(" Show Date and Time", true, showdate.integer && showtime.integer); M_Options_PrintCommand( " Custom Brightness", true); M_Options_PrintSlider( " Game Brightness", true, r_hdr_scenebrightness.value, 1, 4); M_Options_PrintSlider( " Brightness", true, v_contrast.value, 1, 2); M_Options_PrintSlider( " Gamma", true, v_gamma.value, 0.5, 3); M_Options_PrintSlider( " Sound Volume", snd_initialized.integer, volume.value, 0, 1); #ifdef CONFIG_CD M_Options_PrintSlider( " Music Volume", cdaudioinitialized.integer, bgmvolume.value, 0, 1); #endif M_Options_PrintCommand( " Customize Effects", true); M_Options_PrintCommand( " Effects: Quake", true); M_Options_PrintCommand( " Effects: Normal", true); M_Options_PrintCommand( " Effects: High", true); M_Options_PrintCommand( " Customize Lighting", true); M_Options_PrintCommand( " Lighting: Flares", true); M_Options_PrintCommand( " Lighting: Normal", true); M_Options_PrintCommand( " Lighting: High", true); M_Options_PrintCommand( " Lighting: Full", true); M_Options_PrintCommand( " Browse Mods", true); } static void M_Options_Key (int k, int ascii) { switch (k) { case K_ESCAPE: M_Menu_Main_f (); break; case K_ENTER: m_entersound = true; switch (options_cursor) { case 0: M_Menu_Keys_f (); break; case 1: m_state = m_none; key_dest = key_game; Con_ToggleConsole_f (); break; case 2: M_Menu_Reset_f (); break; case 3: M_Menu_Video_f (); break; case 11: M_Menu_Options_ColorControl_f (); break; case 17: // Customize Effects M_Menu_Options_Effects_f (); break; case 18: // Effects: Quake Cbuf_AddText("cl_particles 1;cl_particles_quake 1;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_decals 0;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 0;cl_beams_polygons 0;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); break; case 19: // Effects: Normal Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 1;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 0;cl_stainmaps_clearonload 1;cl_decals 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); break; case 20: // Effects: High Cbuf_AddText("cl_particles 1;cl_particles_quake 0;cl_particles_quality 2;cl_particles_explosions_shell 0;r_explosionclip 1;cl_stainmaps 1;cl_stainmaps_clearonload 1;cl_decals 1;cl_particles_bulletimpacts 1;cl_particles_smoke 1;cl_particles_sparks 1;cl_particles_bubbles 1;cl_particles_blood 1;cl_particles_blood_alpha 1;cl_particles_blood_bloodhack 1;cl_beams_polygons 1;cl_beams_instantaimhack 0;cl_beams_quakepositionhack 1;cl_beams_lightatend 0;r_lerpmodels 1;r_lerpsprites 1;r_lerplightstyles 0;gl_polyblend 1;r_skyscroll1 1;r_skyscroll2 2;r_waterwarp 1;r_wateralpha 1;r_waterscroll 1\n"); break; case 21: M_Menu_Options_Graphics_f (); break; case 22: // Lighting: Flares Cbuf_AddText("r_coronas 1;gl_flashblend 1;r_shadow_gloss 0;r_shadow_realtime_dlight 0;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0"); break; case 23: // Lighting: Normal Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 0;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 0"); break; case 24: // Lighting: High Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 0;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1"); break; case 25: // Lighting: Full Cbuf_AddText("r_coronas 1;gl_flashblend 0;r_shadow_gloss 1;r_shadow_realtime_dlight 1;r_shadow_realtime_dlight_shadows 1;r_shadow_realtime_world 1;r_shadow_realtime_world_lightmaps 0;r_shadow_realtime_world_shadows 1;r_bloom 1"); break; case 26: M_Menu_ModList_f (); break; default: M_Menu_Options_AdjustSliders (1); break; } return; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); options_cursor--; if (options_cursor < 0) options_cursor = OPTIONS_ITEMS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); options_cursor++; if (options_cursor >= OPTIONS_ITEMS) options_cursor = 0; break; case K_LEFTARROW: M_Menu_Options_AdjustSliders (-1); break; case K_RIGHTARROW: M_Menu_Options_AdjustSliders (1); break; } } #define OPTIONS_EFFECTS_ITEMS 35 static int options_effects_cursor; void M_Menu_Options_Effects_f (void) { key_dest = key_menu; m_state = m_options_effects; m_entersound = true; } extern cvar_t cl_stainmaps; extern cvar_t cl_stainmaps_clearonload; extern cvar_t r_explosionclip; extern cvar_t r_coronas; extern cvar_t gl_flashblend; extern cvar_t cl_beams_polygons; extern cvar_t cl_beams_quakepositionhack; extern cvar_t cl_beams_instantaimhack; extern cvar_t cl_beams_lightatend; extern cvar_t r_lightningbeam_thickness; extern cvar_t r_lightningbeam_scroll; extern cvar_t r_lightningbeam_repeatdistance; extern cvar_t r_lightningbeam_color_red; extern cvar_t r_lightningbeam_color_green; extern cvar_t r_lightningbeam_color_blue; extern cvar_t r_lightningbeam_qmbtexture; static void M_Menu_Options_Effects_AdjustSliders (int dir) { int optnum; S_LocalSound ("sound/misc/menu3.wav"); optnum = 0; if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles, !cl_particles.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quake, !cl_particles_quake.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_quality, bound(1, cl_particles_quality.value + dir * 0.5, 4)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_explosions_shell, !cl_particles_explosions_shell.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_explosionclip, !r_explosionclip.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps, !cl_stainmaps.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_stainmaps_clearonload, !cl_stainmaps_clearonload.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_decals, !cl_decals.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bulletimpacts, !cl_particles_bulletimpacts.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_smoke, !cl_particles_smoke.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_sparks, !cl_particles_sparks.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_bubbles, !cl_particles_bubbles.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood, !cl_particles_blood.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_alpha, bound(0.2, cl_particles_blood_alpha.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_particles_blood_bloodhack, !cl_particles_blood_bloodhack.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_polygons, !cl_beams_polygons.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_instantaimhack, !cl_beams_instantaimhack.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_quakepositionhack, !cl_beams_quakepositionhack.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&cl_beams_lightatend, !cl_beams_lightatend.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_thickness, bound(1, r_lightningbeam_thickness.integer + dir, 10)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_scroll, bound(0, r_lightningbeam_scroll.integer + dir, 10)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_repeatdistance, bound(64, r_lightningbeam_repeatdistance.integer + dir * 64, 1024)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_red, bound(0, r_lightningbeam_color_red.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_green, bound(0, r_lightningbeam_color_green.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_color_blue, bound(0, r_lightningbeam_color_blue.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lightningbeam_qmbtexture, !r_lightningbeam_qmbtexture.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpmodels, !r_lerpmodels.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerpsprites, !r_lerpsprites.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_lerplightstyles, !r_lerplightstyles.integer); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&gl_polyblend, bound(0, gl_polyblend.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll1, bound(-8, r_skyscroll1.value + dir * 0.1, 8)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_skyscroll2, bound(-8, r_skyscroll2.value + dir * 0.1, 8)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterwarp, bound(0, r_waterwarp.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_wateralpha, bound(0, r_wateralpha.value + dir * 0.1, 1)); else if (options_effects_cursor == optnum++) Cvar_SetValueQuick (&r_waterscroll, bound(0, r_waterscroll.value + dir * 0.5, 10)); } static void M_Options_Effects_Draw (void) { int visible; cachepic_t *p; M_Background(320, bound(200, 32 + OPTIONS_EFFECTS_ITEMS * 8, vid_conheight.integer)); M_DrawPic(16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_option"); M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); m_optcursor = options_effects_cursor; m_optnum = 0; visible = (int)((menu_height - 32) / 8); m_opty = 32 - bound(0, m_optcursor - (visible >> 1), max(0, OPTIONS_EFFECTS_ITEMS - visible)) * 8; M_Options_PrintCheckbox(" Particles", true, cl_particles.integer); M_Options_PrintCheckbox(" Quake-style Particles", true, cl_particles_quake.integer); M_Options_PrintSlider( " Particles Quality", true, cl_particles_quality.value, 1, 4); M_Options_PrintCheckbox(" Explosion Shell", true, cl_particles_explosions_shell.integer); M_Options_PrintCheckbox(" Explosion Shell Clip", true, r_explosionclip.integer); M_Options_PrintCheckbox(" Stainmaps", true, cl_stainmaps.integer); M_Options_PrintCheckbox("Onload Clear Stainmaps", true, cl_stainmaps_clearonload.integer); M_Options_PrintCheckbox(" Decals", true, cl_decals.integer); M_Options_PrintCheckbox(" Bullet Impacts", true, cl_particles_bulletimpacts.integer); M_Options_PrintCheckbox(" Smoke", true, cl_particles_smoke.integer); M_Options_PrintCheckbox(" Sparks", true, cl_particles_sparks.integer); M_Options_PrintCheckbox(" Bubbles", true, cl_particles_bubbles.integer); M_Options_PrintCheckbox(" Blood", true, cl_particles_blood.integer); M_Options_PrintSlider( " Blood Opacity", true, cl_particles_blood_alpha.value, 0.2, 1); M_Options_PrintCheckbox("Force New Blood Effect", true, cl_particles_blood_bloodhack.integer); M_Options_PrintCheckbox(" Polygon Lightning", true, cl_beams_polygons.integer); M_Options_PrintCheckbox("Smooth Sweep Lightning", true, cl_beams_instantaimhack.integer); M_Options_PrintCheckbox(" Waist-level Lightning", true, cl_beams_quakepositionhack.integer); M_Options_PrintCheckbox(" Lightning End Light", true, cl_beams_lightatend.integer); M_Options_PrintSlider( " Lightning Thickness", cl_beams_polygons.integer, r_lightningbeam_thickness.integer, 1, 10); M_Options_PrintSlider( " Lightning Scroll", cl_beams_polygons.integer, r_lightningbeam_scroll.integer, 0, 10); M_Options_PrintSlider( " Lightning Repeat Dist", cl_beams_polygons.integer, r_lightningbeam_repeatdistance.integer, 64, 1024); M_Options_PrintSlider( " Lightning Color Red", cl_beams_polygons.integer, r_lightningbeam_color_red.value, 0, 1); M_Options_PrintSlider( " Lightning Color Green", cl_beams_polygons.integer, r_lightningbeam_color_green.value, 0, 1); M_Options_PrintSlider( " Lightning Color Blue", cl_beams_polygons.integer, r_lightningbeam_color_blue.value, 0, 1); M_Options_PrintCheckbox(" Lightning QMB Texture", cl_beams_polygons.integer, r_lightningbeam_qmbtexture.integer); M_Options_PrintCheckbox(" Model Interpolation", true, r_lerpmodels.integer); M_Options_PrintCheckbox(" Sprite Interpolation", true, r_lerpsprites.integer); M_Options_PrintCheckbox(" Flicker Interpolation", true, r_lerplightstyles.integer); M_Options_PrintSlider( " View Blend", true, gl_polyblend.value, 0, 1); M_Options_PrintSlider( "Upper Sky Scroll Speed", true, r_skyscroll1.value, -8, 8); M_Options_PrintSlider( "Lower Sky Scroll Speed", true, r_skyscroll2.value, -8, 8); M_Options_PrintSlider( " Underwater View Warp", true, r_waterwarp.value, 0, 1); M_Options_PrintSlider( " Water Alpha (opacity)", true, r_wateralpha.value, 0, 1); M_Options_PrintSlider( " Water Movement", true, r_waterscroll.value, 0, 10); } static void M_Options_Effects_Key (int k, int ascii) { switch (k) { case K_ESCAPE: M_Menu_Options_f (); break; case K_ENTER: M_Menu_Options_Effects_AdjustSliders (1); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); options_effects_cursor--; if (options_effects_cursor < 0) options_effects_cursor = OPTIONS_EFFECTS_ITEMS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); options_effects_cursor++; if (options_effects_cursor >= OPTIONS_EFFECTS_ITEMS) options_effects_cursor = 0; break; case K_LEFTARROW: M_Menu_Options_Effects_AdjustSliders (-1); break; case K_RIGHTARROW: M_Menu_Options_Effects_AdjustSliders (1); break; } } #define OPTIONS_GRAPHICS_ITEMS 20 static int options_graphics_cursor; void M_Menu_Options_Graphics_f (void) { key_dest = key_menu; m_state = m_options_graphics; m_entersound = true; } extern cvar_t r_shadow_gloss; extern cvar_t r_shadow_realtime_dlight; extern cvar_t r_shadow_realtime_dlight_shadows; extern cvar_t r_shadow_realtime_world; extern cvar_t r_shadow_realtime_world_lightmaps; extern cvar_t r_shadow_realtime_world_shadows; extern cvar_t r_bloom; extern cvar_t r_bloom_colorscale; extern cvar_t r_bloom_colorsubtract; extern cvar_t r_bloom_colorexponent; extern cvar_t r_bloom_blur; extern cvar_t r_bloom_brighten; extern cvar_t r_bloom_resolution; extern cvar_t r_hdr_scenebrightness; extern cvar_t r_hdr_glowintensity; extern cvar_t gl_picmip; static void M_Menu_Options_Graphics_AdjustSliders (int dir) { int optnum; S_LocalSound ("sound/misc/menu3.wav"); optnum = 0; if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_coronas, bound(0, r_coronas.value + dir * 0.125, 4)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&gl_flashblend, !gl_flashblend.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_gloss, bound(0, r_shadow_gloss.integer + dir, 2)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight, !r_shadow_realtime_dlight.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_dlight_shadows, !r_shadow_realtime_dlight_shadows.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world, !r_shadow_realtime_world.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_lightmaps, bound(0, r_shadow_realtime_world_lightmaps.value + dir * 0.1, 1)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_shadow_realtime_world_shadows, !r_shadow_realtime_world_shadows.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom, !r_bloom.integer); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_scenebrightness, bound(0.25, r_hdr_scenebrightness.value + dir * 0.125, 4)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_hdr_glowintensity, bound(0, r_hdr_glowintensity.value + dir * 0.25, 4)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorscale, bound(0.0625, r_bloom_colorscale.value + dir * 0.0625, 1)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorsubtract, bound(0, r_bloom_colorsubtract.value + dir * 0.0625, 1-0.0625)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_colorexponent, bound(1, r_bloom_colorexponent.value * (dir > 0 ? 2.0 : 0.5), 8)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_brighten, bound(1, r_bloom_brighten.value + dir * 0.0625, 4)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_blur, bound(1, r_bloom_blur.value + dir * 1, 16)); else if (options_graphics_cursor == optnum++) Cvar_SetValueQuick (&r_bloom_resolution, bound(64, r_bloom_resolution.value + dir * 64, 2048)); else if (options_graphics_cursor == optnum++) Cbuf_AddText ("r_restart\n"); } static void M_Options_Graphics_Draw (void) { int visible; cachepic_t *p; M_Background(320, bound(200, 32 + OPTIONS_GRAPHICS_ITEMS * 8, vid_conheight.integer)); M_DrawPic(16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_option"); M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); m_optcursor = options_graphics_cursor; m_optnum = 0; visible = (int)((menu_height - 32) / 8); m_opty = 32 - bound(0, m_optcursor - (visible >> 1), max(0, OPTIONS_GRAPHICS_ITEMS - visible)) * 8; M_Options_PrintSlider( " Corona Intensity", true, r_coronas.value, 0, 4); M_Options_PrintCheckbox(" Use Only Coronas", true, gl_flashblend.integer); M_Options_PrintSlider( " Gloss Mode", true, r_shadow_gloss.integer, 0, 2); M_Options_PrintCheckbox(" RT DLights", !gl_flashblend.integer, r_shadow_realtime_dlight.integer); M_Options_PrintCheckbox(" RT DLight Shadows", !gl_flashblend.integer, r_shadow_realtime_dlight_shadows.integer); M_Options_PrintCheckbox(" RT World", true, r_shadow_realtime_world.integer); M_Options_PrintSlider( " RT World Lightmaps", true, r_shadow_realtime_world_lightmaps.value, 0, 1); M_Options_PrintCheckbox(" RT World Shadow", true, r_shadow_realtime_world_shadows.integer); M_Options_PrintCheckbox(" Bloom Effect", true, r_bloom.integer); M_Options_PrintSlider( " Scene Brightness", true, r_hdr_scenebrightness.value, 0.25, 4); M_Options_PrintSlider( " Glow Brightness", true, r_hdr_glowintensity.value, 0, 4); M_Options_PrintSlider( " Bloom Color Scale", r_bloom.integer, r_bloom_colorscale.value, 0.0625, 1); M_Options_PrintSlider( " Bloom Color Subtract", r_bloom.integer, r_bloom_colorsubtract.value, 0, 1-0.0625); M_Options_PrintSlider( " Bloom Color Exponent", r_bloom.integer, r_bloom_colorexponent.value, 1, 8); M_Options_PrintSlider( " Bloom Intensity", r_bloom.integer, r_bloom_brighten.value, 1, 4); M_Options_PrintSlider( " Bloom Blur", r_bloom.integer, r_bloom_blur.value, 1, 16); M_Options_PrintSlider( " Bloom Resolution", r_bloom.integer, r_bloom_resolution.value, 64, 2048); M_Options_PrintCommand( " Restart Renderer", true); } static void M_Options_Graphics_Key (int k, int ascii) { switch (k) { case K_ESCAPE: M_Menu_Options_f (); break; case K_ENTER: M_Menu_Options_Graphics_AdjustSliders (1); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); options_graphics_cursor--; if (options_graphics_cursor < 0) options_graphics_cursor = OPTIONS_GRAPHICS_ITEMS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); options_graphics_cursor++; if (options_graphics_cursor >= OPTIONS_GRAPHICS_ITEMS) options_graphics_cursor = 0; break; case K_LEFTARROW: M_Menu_Options_Graphics_AdjustSliders (-1); break; case K_RIGHTARROW: M_Menu_Options_Graphics_AdjustSliders (1); break; } } #define OPTIONS_COLORCONTROL_ITEMS 18 static int options_colorcontrol_cursor; // intensity value to match up to 50% dither to 'correct' quake static cvar_t menu_options_colorcontrol_correctionvalue = {0, "menu_options_colorcontrol_correctionvalue", "0.5", "intensity value that matches up to white/black dither pattern, should be 0.5 for linear color"}; void M_Menu_Options_ColorControl_f (void) { key_dest = key_menu; m_state = m_options_colorcontrol; m_entersound = true; } static void M_Menu_Options_ColorControl_AdjustSliders (int dir) { int optnum; float f; S_LocalSound ("sound/misc/menu3.wav"); optnum = 1; if (options_colorcontrol_cursor == optnum++) Cvar_SetValueQuick (&v_hwgamma, !v_hwgamma.integer); else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 0); Cvar_SetValueQuick (&v_gamma, bound(1, v_gamma.value + dir * 0.125, 5)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 0); Cvar_SetValueQuick (&v_contrast, bound(1, v_contrast.value + dir * 0.125, 5)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 0); Cvar_SetValueQuick (&v_brightness, bound(0, v_brightness.value + dir * 0.05, 0.8)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, !v_color_enable.integer); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_black_r, bound(0, v_color_black_r.value + dir * 0.0125, 0.8)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_black_g, bound(0, v_color_black_g.value + dir * 0.0125, 0.8)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_black_b, bound(0, v_color_black_b.value + dir * 0.0125, 0.8)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); f = bound(0, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3 + dir * 0.0125, 0.8); Cvar_SetValueQuick (&v_color_black_r, f); Cvar_SetValueQuick (&v_color_black_g, f); Cvar_SetValueQuick (&v_color_black_b, f); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_grey_r, bound(0, v_color_grey_r.value + dir * 0.0125, 0.95)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_grey_g, bound(0, v_color_grey_g.value + dir * 0.0125, 0.95)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_grey_b, bound(0, v_color_grey_b.value + dir * 0.0125, 0.95)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); f = bound(0, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3 + dir * 0.0125, 0.95); Cvar_SetValueQuick (&v_color_grey_r, f); Cvar_SetValueQuick (&v_color_grey_g, f); Cvar_SetValueQuick (&v_color_grey_b, f); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_white_r, bound(1, v_color_white_r.value + dir * 0.125, 5)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_white_g, bound(1, v_color_white_g.value + dir * 0.125, 5)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); Cvar_SetValueQuick (&v_color_white_b, bound(1, v_color_white_b.value + dir * 0.125, 5)); } else if (options_colorcontrol_cursor == optnum++) { Cvar_SetValueQuick (&v_color_enable, 1); f = bound(1, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3 + dir * 0.125, 5); Cvar_SetValueQuick (&v_color_white_r, f); Cvar_SetValueQuick (&v_color_white_g, f); Cvar_SetValueQuick (&v_color_white_b, f); } } static void M_Options_ColorControl_Draw (void) { int visible; float x, c, s, t, u, v; cachepic_t *p, *dither; dither = Draw_CachePic_Flags ("gfx/colorcontrol/ditherpattern", CACHEPICFLAG_NOCLAMP); M_Background(320, 256); M_DrawPic(16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_option"); M_DrawPic((320-p->width)/2, 4, "gfx/p_option"); m_optcursor = options_colorcontrol_cursor; m_optnum = 0; visible = (int)((menu_height - 32) / 8); m_opty = 32 - bound(0, m_optcursor - (visible >> 1), max(0, OPTIONS_COLORCONTROL_ITEMS - visible)) * 8; M_Options_PrintCommand( " Reset to defaults", true); M_Options_PrintCheckbox("Hardware Gamma Control", vid_hardwaregammasupported.integer, v_hwgamma.integer); M_Options_PrintSlider( " Gamma", !v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_gamma.value, 1, 5); M_Options_PrintSlider( " Contrast", !v_color_enable.integer, v_contrast.value, 1, 5); M_Options_PrintSlider( " Brightness", !v_color_enable.integer, v_brightness.value, 0, 0.8); M_Options_PrintCheckbox(" Color Level Controls", true, v_color_enable.integer); M_Options_PrintSlider( " Black: Red ", v_color_enable.integer, v_color_black_r.value, 0, 0.8); M_Options_PrintSlider( " Black: Green", v_color_enable.integer, v_color_black_g.value, 0, 0.8); M_Options_PrintSlider( " Black: Blue ", v_color_enable.integer, v_color_black_b.value, 0, 0.8); M_Options_PrintSlider( " Black: Grey ", v_color_enable.integer, (v_color_black_r.value + v_color_black_g.value + v_color_black_b.value) / 3, 0, 0.8); M_Options_PrintSlider( " Grey: Red ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_r.value, 0, 0.95); M_Options_PrintSlider( " Grey: Green", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_g.value, 0, 0.95); M_Options_PrintSlider( " Grey: Blue ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, v_color_grey_b.value, 0, 0.95); M_Options_PrintSlider( " Grey: Grey ", v_color_enable.integer && vid_hardwaregammasupported.integer && v_hwgamma.integer, (v_color_grey_r.value + v_color_grey_g.value + v_color_grey_b.value) / 3, 0, 0.95); M_Options_PrintSlider( " White: Red ", v_color_enable.integer, v_color_white_r.value, 1, 5); M_Options_PrintSlider( " White: Green", v_color_enable.integer, v_color_white_g.value, 1, 5); M_Options_PrintSlider( " White: Blue ", v_color_enable.integer, v_color_white_b.value, 1, 5); M_Options_PrintSlider( " White: Grey ", v_color_enable.integer, (v_color_white_r.value + v_color_white_g.value + v_color_white_b.value) / 3, 1, 5); m_opty += 4; DrawQ_Fill(menu_x, menu_y + m_opty, 320, 4 + 64 + 8 + 64 + 4, 0, 0, 0, 1, 0);m_opty += 4; s = (float) 312 / 2 * vid.width / vid_conwidth.integer; t = (float) 4 / 2 * vid.height / vid_conheight.integer; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, dither, 312, 4, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,0,0,1, 0,1, 0,0,0,1, 1,1, 1,0,0,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, dither, 312, 4, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,1,0,1, 0,1, 0,0,0,1, 1,1, 0,1,0,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, dither, 312, 4, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 0,0,1,1, 0,1, 0,0,0,1, 1,1, 0,0,1,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, dither, 312, 4, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0);m_opty += 4; DrawQ_SuperPic(menu_x + 4, menu_y + m_opty, NULL , 312, 4, 0,0, 0,0,0,1, 1,0, 1,1,1,1, 0,1, 0,0,0,1, 1,1, 1,1,1,1, 0);m_opty += 4; c = menu_options_colorcontrol_correctionvalue.value; // intensity value that should be matched up to a 50% dither to 'correct' quake s = (float) 48 / 2 * vid.width / vid_conwidth.integer; t = (float) 48 / 2 * vid.height / vid_conheight.integer; u = s * 0.5; v = t * 0.5; m_opty += 8; x = 4; DrawQ_Fill(menu_x + x, menu_y + m_opty, 64, 48, c, 0, 0, 1, 0); DrawQ_SuperPic(menu_x + x + 16, menu_y + m_opty + 16, dither, 16, 16, 0,0, 1,0,0,1, s,0, 1,0,0,1, 0,t, 1,0,0,1, s,t, 1,0,0,1, 0); DrawQ_SuperPic(menu_x + x + 32, menu_y + m_opty + 16, dither, 16, 16, 0,0, 1,0,0,1, u,0, 1,0,0,1, 0,v, 1,0,0,1, u,v, 1,0,0,1, 0); x += 80; DrawQ_Fill(menu_x + x, menu_y + m_opty, 64, 48, 0, c, 0, 1, 0); DrawQ_SuperPic(menu_x + x + 16, menu_y + m_opty + 16, dither, 16, 16, 0,0, 0,1,0,1, s,0, 0,1,0,1, 0,t, 0,1,0,1, s,t, 0,1,0,1, 0); DrawQ_SuperPic(menu_x + x + 32, menu_y + m_opty + 16, dither, 16, 16, 0,0, 0,1,0,1, u,0, 0,1,0,1, 0,v, 0,1,0,1, u,v, 0,1,0,1, 0); x += 80; DrawQ_Fill(menu_x + x, menu_y + m_opty, 64, 48, 0, 0, c, 1, 0); DrawQ_SuperPic(menu_x + x + 16, menu_y + m_opty + 16, dither, 16, 16, 0,0, 0,0,1,1, s,0, 0,0,1,1, 0,t, 0,0,1,1, s,t, 0,0,1,1, 0); DrawQ_SuperPic(menu_x + x + 32, menu_y + m_opty + 16, dither, 16, 16, 0,0, 0,0,1,1, u,0, 0,0,1,1, 0,v, 0,0,1,1, u,v, 0,0,1,1, 0); x += 80; DrawQ_Fill(menu_x + x, menu_y + m_opty, 64, 48, c, c, c, 1, 0); DrawQ_SuperPic(menu_x + x + 16, menu_y + m_opty + 16, dither, 16, 16, 0,0, 1,1,1,1, s,0, 1,1,1,1, 0,t, 1,1,1,1, s,t, 1,1,1,1, 0); DrawQ_SuperPic(menu_x + x + 32, menu_y + m_opty + 16, dither, 16, 16, 0,0, 1,1,1,1, u,0, 1,1,1,1, 0,v, 1,1,1,1, u,v, 1,1,1,1, 0); } static void M_Options_ColorControl_Key (int k, int ascii) { switch (k) { case K_ESCAPE: M_Menu_Options_f (); break; case K_ENTER: m_entersound = true; switch (options_colorcontrol_cursor) { case 0: Cvar_SetValueQuick(&v_hwgamma, 1); Cvar_SetValueQuick(&v_gamma, 1); Cvar_SetValueQuick(&v_contrast, 1); Cvar_SetValueQuick(&v_brightness, 0); Cvar_SetValueQuick(&v_color_enable, 0); Cvar_SetValueQuick(&v_color_black_r, 0); Cvar_SetValueQuick(&v_color_black_g, 0); Cvar_SetValueQuick(&v_color_black_b, 0); Cvar_SetValueQuick(&v_color_grey_r, 0); Cvar_SetValueQuick(&v_color_grey_g, 0); Cvar_SetValueQuick(&v_color_grey_b, 0); Cvar_SetValueQuick(&v_color_white_r, 1); Cvar_SetValueQuick(&v_color_white_g, 1); Cvar_SetValueQuick(&v_color_white_b, 1); break; default: M_Menu_Options_ColorControl_AdjustSliders (1); break; } return; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); options_colorcontrol_cursor--; if (options_colorcontrol_cursor < 0) options_colorcontrol_cursor = OPTIONS_COLORCONTROL_ITEMS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); options_colorcontrol_cursor++; if (options_colorcontrol_cursor >= OPTIONS_COLORCONTROL_ITEMS) options_colorcontrol_cursor = 0; break; case K_LEFTARROW: M_Menu_Options_ColorControl_AdjustSliders (-1); break; case K_RIGHTARROW: M_Menu_Options_ColorControl_AdjustSliders (1); break; } } //============================================================================= /* KEYS MENU */ static const char *quakebindnames[][2] = { {"+attack", "attack"}, {"impulse 10", "next weapon"}, {"impulse 12", "previous weapon"}, {"+jump", "jump / swim up"}, {"+forward", "walk forward"}, {"+back", "backpedal"}, {"+left", "turn left"}, {"+right", "turn right"}, {"+speed", "run"}, {"+moveleft", "step left"}, {"+moveright", "step right"}, {"+strafe", "sidestep"}, {"+lookup", "look up"}, {"+lookdown", "look down"}, {"centerview", "center view"}, {"+mlook", "mouse look"}, {"+klook", "keyboard look"}, {"+moveup", "swim up"}, {"+movedown", "swim down"} }; static const char *transfusionbindnames[][2] = { {"", "Movement"}, // Movement commands {"+forward", "walk forward"}, {"+back", "backpedal"}, {"+left", "turn left"}, {"+right", "turn right"}, {"+moveleft", "step left"}, {"+moveright", "step right"}, {"+jump", "jump / swim up"}, {"+movedown", "swim down"}, {"", "Combat"}, // Combat commands {"impulse 1", "Pitch Fork"}, {"impulse 2", "Flare Gun"}, {"impulse 3", "Shotgun"}, {"impulse 4", "Machine Gun"}, {"impulse 5", "Incinerator"}, {"impulse 6", "Bombs (TNT)"}, {"impulse 35", "Proximity Bomb"}, {"impulse 36", "Remote Detonator"}, {"impulse 7", "Aerosol Can"}, {"impulse 8", "Tesla Cannon"}, {"impulse 9", "Life Leech"}, {"impulse 10", "Voodoo Doll"}, {"impulse 21", "next weapon"}, {"impulse 22", "previous weapon"}, {"+attack", "attack"}, {"+button3", "altfire"}, {"", "Inventory"}, // Inventory commands {"impulse 40", "Dr.'s Bag"}, {"impulse 41", "Crystal Ball"}, {"impulse 42", "Beast Vision"}, {"impulse 43", "Jump Boots"}, {"impulse 23", "next item"}, {"impulse 24", "previous item"}, {"impulse 25", "use item"}, {"", "Misc"}, // Misc commands {"+button4", "use"}, {"impulse 50", "add bot (red)"}, {"impulse 51", "add bot (blue)"}, {"impulse 52", "kick a bot"}, {"impulse 26", "next armor type"}, {"impulse 27", "identify player"}, {"impulse 55", "voting menu"}, {"impulse 56", "observer mode"}, {"", "Taunts"}, // Taunts {"impulse 70", "taunt 0"}, {"impulse 71", "taunt 1"}, {"impulse 72", "taunt 2"}, {"impulse 73", "taunt 3"}, {"impulse 74", "taunt 4"}, {"impulse 75", "taunt 5"}, {"impulse 76", "taunt 6"}, {"impulse 77", "taunt 7"}, {"impulse 78", "taunt 8"}, {"impulse 79", "taunt 9"} }; static const char *goodvsbad2bindnames[][2] = { {"impulse 69", "Power 1"}, {"impulse 70", "Power 2"}, {"impulse 71", "Power 3"}, {"+jump", "jump / swim up"}, {"+forward", "walk forward"}, {"+back", "backpedal"}, {"+left", "turn left"}, {"+right", "turn right"}, {"+speed", "run"}, {"+moveleft", "step left"}, {"+moveright", "step right"}, {"+strafe", "sidestep"}, {"+lookup", "look up"}, {"+lookdown", "look down"}, {"centerview", "center view"}, {"+mlook", "mouse look"}, {"kill", "kill yourself"}, {"+moveup", "swim up"}, {"+movedown", "swim down"} }; static int numcommands; static const char *(*bindnames)[2]; /* typedef struct binditem_s { char *command, *description; struct binditem_s *next; } binditem_t; typedef struct bindcategory_s { char *name; binditem_t *binds; struct bindcategory_s *next; } bindcategory_t; static bindcategory_t *bindcategories = NULL; static void M_ClearBinds (void) { for (c = bindcategories;c;c = cnext) { cnext = c->next; for (b = c->binds;b;b = bnext) { bnext = b->next; Z_Free(b); } Z_Free(c); } bindcategories = NULL; } static void M_AddBindToCategory(bindcategory_t *c, char *command, char *description) { for (b = &c->binds;*b;*b = &(*b)->next); *b = Z_Alloc(sizeof(binditem_t) + strlen(command) + 1 + strlen(description) + 1); *b->command = (char *)((*b) + 1); *b->description = *b->command + strlen(command) + 1; strlcpy(*b->command, command, strlen(command) + 1); strlcpy(*b->description, description, strlen(description) + 1); } static void M_AddBind (char *category, char *command, char *description) { for (c = &bindcategories;*c;c = &(*c)->next) { if (!strcmp(category, (*c)->name)) { M_AddBindToCategory(*c, command, description); return; } } *c = Z_Alloc(sizeof(bindcategory_t)); M_AddBindToCategory(*c, command, description); } static void M_DefaultBinds (void) { M_ClearBinds(); M_AddBind("movement", "+jump", "jump / swim up"); M_AddBind("movement", "+forward", "walk forward"); M_AddBind("movement", "+back", "backpedal"); M_AddBind("movement", "+left", "turn left"); M_AddBind("movement", "+right", "turn right"); M_AddBind("movement", "+speed", "run"); M_AddBind("movement", "+moveleft", "step left"); M_AddBind("movement", "+moveright", "step right"); M_AddBind("movement", "+strafe", "sidestep"); M_AddBind("movement", "+lookup", "look up"); M_AddBind("movement", "+lookdown", "look down"); M_AddBind("movement", "centerview", "center view"); M_AddBind("movement", "+mlook", "mouse look"); M_AddBind("movement", "+klook", "keyboard look"); M_AddBind("movement", "+moveup", "swim up"); M_AddBind("movement", "+movedown", "swim down"); M_AddBind("weapons", "+attack", "attack"); M_AddBind("weapons", "impulse 10", "next weapon"); M_AddBind("weapons", "impulse 12", "previous weapon"); M_AddBind("weapons", "impulse 1", "select weapon 1 (axe)"); M_AddBind("weapons", "impulse 2", "select weapon 2 (shotgun)"); M_AddBind("weapons", "impulse 3", "select weapon 3 (super )"); M_AddBind("weapons", "impulse 4", "select weapon 4 (nailgun)"); M_AddBind("weapons", "impulse 5", "select weapon 5 (super nailgun)"); M_AddBind("weapons", "impulse 6", "select weapon 6 (grenade launcher)"); M_AddBind("weapons", "impulse 7", "select weapon 7 (rocket launcher)"); M_AddBind("weapons", "impulse 8", "select weapon 8 (lightning gun)"); } */ static int keys_cursor; static int bind_grab; void M_Menu_Keys_f (void) { key_dest = key_menu_grabbed; m_state = m_keys; m_entersound = true; if (gamemode == GAME_TRANSFUSION) { numcommands = sizeof(transfusionbindnames) / sizeof(transfusionbindnames[0]); bindnames = transfusionbindnames; } else if (gamemode == GAME_GOODVSBAD2) { numcommands = sizeof(goodvsbad2bindnames) / sizeof(goodvsbad2bindnames[0]); bindnames = goodvsbad2bindnames; } else { numcommands = sizeof(quakebindnames) / sizeof(quakebindnames[0]); bindnames = quakebindnames; } // Make sure "keys_cursor" doesn't start on a section in the binding list keys_cursor = 0; while (bindnames[keys_cursor][0][0] == '\0') { keys_cursor++; // Only sections? There may be a problem somewhere... if (keys_cursor >= numcommands) Sys_Error ("M_Init: The key binding list only contains sections"); } } #define NUMKEYS 5 static void M_UnbindCommand (const char *command) { int j; const char *b; for (j = 0; j < (int)sizeof (keybindings[0]) / (int)sizeof (keybindings[0][0]); j++) { b = keybindings[0][j]; if (!b) continue; if (!strcmp (b, command)) Key_SetBinding (j, 0, ""); } } static void M_Keys_Draw (void) { int i, j; int keys[NUMKEYS]; int y; cachepic_t *p; char keystring[MAX_INPUTLINE]; M_Background(320, 48 + 8 * numcommands); p = Draw_CachePic ("gfx/ttl_cstm"); M_DrawPic ( (320-p->width)/2, 4, "gfx/ttl_cstm"); if (bind_grab) M_Print(12, 32, "Press a key or button for this action"); else M_Print(18, 32, "Enter to change, backspace to clear"); // search for known bindings for (i=0 ; i 0) strlcat(keystring, " or ", sizeof(keystring)); strlcat(keystring, Key_KeynumToString (keys[j], tinystr, sizeof(tinystr)), sizeof(keystring)); } } } M_Print(150, y, keystring); } if (bind_grab) M_DrawCharacter (140, 48 + keys_cursor*8, '='); else M_DrawCharacter (140, 48 + keys_cursor*8, 12+((int)(realtime*4)&1)); } static void M_Keys_Key (int k, int ascii) { char cmd[80]; int keys[NUMKEYS]; char tinystr[2]; if (bind_grab) { // defining a key S_LocalSound ("sound/misc/menu1.wav"); if (k == K_ESCAPE) { bind_grab = false; } else //if (k != '`') { dpsnprintf (cmd, sizeof(cmd), "bind \"%s\" \"%s\"\n", Key_KeynumToString (k, tinystr, sizeof(tinystr)), bindnames[keys_cursor][0]); Cbuf_InsertText (cmd); } bind_grab = false; return; } switch (k) { case K_ESCAPE: M_Menu_Options_f (); break; case K_LEFTARROW: case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); do { keys_cursor--; if (keys_cursor < 0) keys_cursor = numcommands-1; } while (bindnames[keys_cursor][0][0] == '\0'); // skip sections break; case K_DOWNARROW: case K_RIGHTARROW: S_LocalSound ("sound/misc/menu1.wav"); do { keys_cursor++; if (keys_cursor >= numcommands) keys_cursor = 0; } while (bindnames[keys_cursor][0][0] == '\0'); // skip sections break; case K_ENTER: // go into bind mode Key_FindKeysForCommand (bindnames[keys_cursor][0], keys, NUMKEYS, 0); S_LocalSound ("sound/misc/menu2.wav"); if (keys[NUMKEYS - 1] != -1) M_UnbindCommand (bindnames[keys_cursor][0]); bind_grab = true; break; case K_BACKSPACE: // delete bindings case K_DEL: // delete bindings S_LocalSound ("sound/misc/menu2.wav"); M_UnbindCommand (bindnames[keys_cursor][0]); break; } } void M_Menu_Reset_f (void) { key_dest = key_menu; m_state = m_reset; m_entersound = true; } static void M_Reset_Key (int key, int ascii) { switch (key) { case 'Y': case 'y': Cbuf_AddText ("cvar_resettodefaults_all;exec default.cfg\n"); // no break here since we also exit the menu case K_ESCAPE: case 'n': case 'N': m_state = m_options; m_entersound = true; break; default: break; } } static void M_Reset_Draw (void) { int lines = 2, linelength = 20; M_Background(linelength * 8 + 16, lines * 8 + 16); M_DrawTextBox(0, 0, linelength, lines); M_Print(8 + 4 * (linelength - 19), 8, "Really wanna reset?"); M_Print(8 + 4 * (linelength - 11), 16, "Press y / n"); } //============================================================================= /* VIDEO MENU */ video_resolution_t video_resolutions_hardcoded[] = { {"Standard 4x3" , 320, 240, 320, 240, 1 }, {"Standard 4x3" , 400, 300, 400, 300, 1 }, {"Standard 4x3" , 512, 384, 512, 384, 1 }, {"Standard 4x3" , 640, 480, 640, 480, 1 }, {"Standard 4x3" , 800, 600, 640, 480, 1 }, {"Standard 4x3" , 1024, 768, 640, 480, 1 }, {"Standard 4x3" , 1152, 864, 640, 480, 1 }, {"Standard 4x3" , 1280, 960, 640, 480, 1 }, {"Standard 4x3" , 1400,1050, 640, 480, 1 }, {"Standard 4x3" , 1600,1200, 640, 480, 1 }, {"Standard 4x3" , 1792,1344, 640, 480, 1 }, {"Standard 4x3" , 1856,1392, 640, 480, 1 }, {"Standard 4x3" , 1920,1440, 640, 480, 1 }, {"Standard 4x3" , 2048,1536, 640, 480, 1 }, {"Short Pixel (CRT) 5x4" , 320, 256, 320, 256, 0.9375}, {"Short Pixel (CRT) 5x4" , 640, 512, 640, 512, 0.9375}, {"Short Pixel (CRT) 5x4" , 1280,1024, 640, 512, 0.9375}, {"Tall Pixel (CRT) 8x5" , 320, 200, 320, 200, 1.2 }, {"Tall Pixel (CRT) 8x5" , 640, 400, 640, 400, 1.2 }, {"Tall Pixel (CRT) 8x5" , 840, 525, 640, 400, 1.2 }, {"Tall Pixel (CRT) 8x5" , 960, 600, 640, 400, 1.2 }, {"Tall Pixel (CRT) 8x5" , 1680,1050, 640, 400, 1.2 }, {"Tall Pixel (CRT) 8x5" , 1920,1200, 640, 400, 1.2 }, {"Square Pixel (LCD) 5x4" , 320, 256, 320, 256, 1 }, {"Square Pixel (LCD) 5x4" , 640, 512, 640, 512, 1 }, {"Square Pixel (LCD) 5x4" , 1280,1024, 640, 512, 1 }, {"WideScreen 5x3" , 640, 384, 640, 384, 1 }, {"WideScreen 5x3" , 1280, 768, 640, 384, 1 }, {"WideScreen 8x5" , 320, 200, 320, 200, 1 }, {"WideScreen 8x5" , 640, 400, 640, 400, 1 }, {"WideScreen 8x5" , 720, 450, 720, 450, 1 }, {"WideScreen 8x5" , 840, 525, 640, 400, 1 }, {"WideScreen 8x5" , 960, 600, 640, 400, 1 }, {"WideScreen 8x5" , 1280, 800, 640, 400, 1 }, {"WideScreen 8x5" , 1440, 900, 720, 450, 1 }, {"WideScreen 8x5" , 1680,1050, 640, 400, 1 }, {"WideScreen 8x5" , 1920,1200, 640, 400, 1 }, {"WideScreen 8x5" , 2560,1600, 640, 400, 1 }, {"WideScreen 8x5" , 3840,2400, 640, 400, 1 }, {"WideScreen 14x9" , 840, 540, 640, 400, 1 }, {"WideScreen 14x9" , 1680,1080, 640, 400, 1 }, {"WideScreen 16x9" , 640, 360, 640, 360, 1 }, {"WideScreen 16x9" , 683, 384, 683, 384, 1 }, {"WideScreen 16x9" , 960, 540, 640, 360, 1 }, {"WideScreen 16x9" , 1280, 720, 640, 360, 1 }, {"WideScreen 16x9" , 1360, 768, 680, 384, 1 }, {"WideScreen 16x9" , 1366, 768, 683, 384, 1 }, {"WideScreen 16x9" , 1920,1080, 640, 360, 1 }, {"WideScreen 16x9" , 2560,1440, 640, 360, 1 }, {"WideScreen 16x9" , 3840,2160, 640, 360, 1 }, {"NTSC 3x2" , 360, 240, 360, 240, 1.125 }, {"NTSC 3x2" , 720, 480, 720, 480, 1.125 }, {"PAL 14x11" , 360, 283, 360, 283, 0.9545}, {"PAL 14x11" , 720, 566, 720, 566, 0.9545}, {"NES 8x7" , 256, 224, 256, 224, 1.1667}, {"SNES 8x7" , 512, 448, 512, 448, 1.1667}, {NULL, 0, 0, 0, 0, 0} }; // this is the number of the default mode (640x480) in the list above int video_resolutions_hardcoded_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; #define VIDEO_ITEMS 11 static int video_cursor = 0; static int video_cursor_table[VIDEO_ITEMS] = {68, 88, 96, 104, 112, 120, 128, 136, 144, 152, 168}; static int menu_video_resolution; video_resolution_t *video_resolutions; int video_resolutions_count; static video_resolution_t *menu_video_resolutions; static int menu_video_resolutions_count; static qboolean menu_video_resolutions_forfullscreen; static void M_Menu_Video_FindResolution(int w, int h, float a) { int i; if(menu_video_resolutions_forfullscreen) { menu_video_resolutions = video_resolutions; menu_video_resolutions_count = video_resolutions_count; } else { menu_video_resolutions = video_resolutions_hardcoded; menu_video_resolutions_count = video_resolutions_hardcoded_count; } // Look for the closest match to the current resolution menu_video_resolution = 0; for (i = 1;i < menu_video_resolutions_count;i++) { // if the new mode would be a worse match in width, skip it if (abs(menu_video_resolutions[i].width - w) > abs(menu_video_resolutions[menu_video_resolution].width - w)) continue; // if it is equal in width, check height if (menu_video_resolutions[i].width == w && menu_video_resolutions[menu_video_resolution].width == w) { // if the new mode would be a worse match in height, skip it if (abs(menu_video_resolutions[i].height - h) > abs(menu_video_resolutions[menu_video_resolution].height - h)) continue; // if it is equal in width and height, check pixel aspect if (menu_video_resolutions[i].height == h && menu_video_resolutions[menu_video_resolution].height == h) { // if the new mode would be a worse match in pixel aspect, skip it if (fabs(menu_video_resolutions[i].pixelheight - a) > fabs(menu_video_resolutions[menu_video_resolution].pixelheight - a)) continue; // if it is equal in everything, skip it (prefer earlier modes) if (menu_video_resolutions[i].pixelheight == a && menu_video_resolutions[menu_video_resolution].pixelheight == a) continue; // better match for width, height, and pixel aspect menu_video_resolution = i; } else // better match for width and height menu_video_resolution = i; } else // better match for width menu_video_resolution = i; } } void M_Menu_Video_f (void) { key_dest = key_menu; m_state = m_video; m_entersound = true; M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); } static void M_Video_Draw (void) { int t; cachepic_t *p; char vabuf[1024]; if(!!vid_fullscreen.integer != menu_video_resolutions_forfullscreen) { video_resolution_t *res = &menu_video_resolutions[menu_video_resolution]; menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; M_Menu_Video_FindResolution(res->width, res->height, res->pixelheight); } M_Background(320, 200); M_DrawPic(16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/vidmodes"); M_DrawPic((320-p->width)/2, 4, "gfx/vidmodes"); t = 0; // Current and Proposed Resolution M_Print(16, video_cursor_table[t] - 12, " Current Resolution"); if (vid_supportrefreshrate && vid.userefreshrate && vid.fullscreen) M_Print(220, video_cursor_table[t] - 12, va(vabuf, sizeof(vabuf), "%dx%d %.2fhz", vid.width, vid.height, vid.refreshrate)); else M_Print(220, video_cursor_table[t] - 12, va(vabuf, sizeof(vabuf), "%dx%d", vid.width, vid.height)); M_Print(16, video_cursor_table[t], " New Resolution"); M_Print(220, video_cursor_table[t], va(vabuf, sizeof(vabuf), "%dx%d", menu_video_resolutions[menu_video_resolution].width, menu_video_resolutions[menu_video_resolution].height)); M_Print(96, video_cursor_table[t] + 8, va(vabuf, sizeof(vabuf), "Type: %s", menu_video_resolutions[menu_video_resolution].type)); t++; // Bits per pixel M_Print(16, video_cursor_table[t], " Bits per pixel"); M_Print(220, video_cursor_table[t], (vid_bitsperpixel.integer == 32) ? "32" : "16"); t++; // Bits per pixel M_Print(16, video_cursor_table[t], " Antialiasing"); M_DrawSlider(220, video_cursor_table[t], vid_samples.value, 1, 32); t++; // Refresh Rate M_ItemPrint(16, video_cursor_table[t], " Use Refresh Rate", vid_supportrefreshrate); M_DrawCheckbox(220, video_cursor_table[t], vid_userefreshrate.integer); t++; // Refresh Rate M_ItemPrint(16, video_cursor_table[t], " Refresh Rate", vid_supportrefreshrate && vid_userefreshrate.integer); M_DrawSlider(220, video_cursor_table[t], vid_refreshrate.value, 50, 150); t++; // Fullscreen M_Print(16, video_cursor_table[t], " Fullscreen"); M_DrawCheckbox(220, video_cursor_table[t], vid_fullscreen.integer); t++; // Vertical Sync M_ItemPrint(16, video_cursor_table[t], " Vertical Sync", true); M_DrawCheckbox(220, video_cursor_table[t], vid_vsync.integer); t++; M_ItemPrint(16, video_cursor_table[t], " Anisotropic Filter", vid.support.ext_texture_filter_anisotropic); M_DrawSlider(220, video_cursor_table[t], gl_texture_anisotropy.integer, 1, vid.max_anisotropy); t++; M_ItemPrint(16, video_cursor_table[t], " Texture Quality", true); M_DrawSlider(220, video_cursor_table[t], gl_picmip.value, 3, 0); t++; M_ItemPrint(16, video_cursor_table[t], " Texture Compression", vid.support.arb_texture_compression); M_DrawCheckbox(220, video_cursor_table[t], gl_texturecompression.integer); t++; // "Apply" button M_Print(220, video_cursor_table[t], "Apply"); t++; // Cursor M_DrawCharacter(200, video_cursor_table[video_cursor], 12+((int)(realtime*4)&1)); } static void M_Menu_Video_AdjustSliders (int dir) { int t; S_LocalSound ("sound/misc/menu3.wav"); t = 0; if (video_cursor == t++) { // Resolution int r; for(r = 0;r < menu_video_resolutions_count;r++) { menu_video_resolution += dir; if (menu_video_resolution >= menu_video_resolutions_count) menu_video_resolution = 0; if (menu_video_resolution < 0) menu_video_resolution = menu_video_resolutions_count - 1; if (menu_video_resolutions[menu_video_resolution].width >= vid_minwidth.integer && menu_video_resolutions[menu_video_resolution].height >= vid_minheight.integer) break; } } else if (video_cursor == t++) Cvar_SetValueQuick (&vid_bitsperpixel, (vid_bitsperpixel.integer == 32) ? 16 : 32); else if (video_cursor == t++) Cvar_SetValueQuick (&vid_samples, bound(1, vid_samples.value * (dir > 0 ? 2 : 0.5), 32)); else if (video_cursor == t++) Cvar_SetValueQuick (&vid_userefreshrate, !vid_userefreshrate.integer); else if (video_cursor == t++) Cvar_SetValueQuick (&vid_refreshrate, bound(50, vid_refreshrate.value + dir, 150)); else if (video_cursor == t++) Cvar_SetValueQuick (&vid_fullscreen, !vid_fullscreen.integer); else if (video_cursor == t++) Cvar_SetValueQuick (&vid_vsync, !vid_vsync.integer); else if (video_cursor == t++) Cvar_SetValueQuick (&gl_texture_anisotropy, bound(1, gl_texture_anisotropy.value * (dir < 0 ? 0.5 : 2.0), vid.max_anisotropy)); else if (video_cursor == t++) Cvar_SetValueQuick (&gl_picmip, bound(0, gl_picmip.value - dir, 3)); else if (video_cursor == t++) Cvar_SetValueQuick (&gl_texturecompression, !gl_texturecompression.integer); } static void M_Video_Key (int key, int ascii) { switch (key) { case K_ESCAPE: // vid_shared.c has a copy of the current video config. We restore it Cvar_SetValueQuick(&vid_fullscreen, vid.fullscreen); Cvar_SetValueQuick(&vid_bitsperpixel, vid.bitsperpixel); Cvar_SetValueQuick(&vid_samples, vid.samples); if (vid_supportrefreshrate) Cvar_SetValueQuick(&vid_refreshrate, vid.refreshrate); Cvar_SetValueQuick(&vid_userefreshrate, vid.userefreshrate); S_LocalSound ("sound/misc/menu1.wav"); M_Menu_Options_f (); break; case K_ENTER: m_entersound = true; switch (video_cursor) { case (VIDEO_ITEMS - 1): Cvar_SetValueQuick (&vid_width, menu_video_resolutions[menu_video_resolution].width); Cvar_SetValueQuick (&vid_height, menu_video_resolutions[menu_video_resolution].height); Cvar_SetValueQuick (&vid_conwidth, menu_video_resolutions[menu_video_resolution].conwidth); Cvar_SetValueQuick (&vid_conheight, menu_video_resolutions[menu_video_resolution].conheight); Cvar_SetValueQuick (&vid_pixelheight, menu_video_resolutions[menu_video_resolution].pixelheight); Cbuf_AddText ("vid_restart\n"); M_Menu_Options_f (); break; default: M_Menu_Video_AdjustSliders (1); } break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); video_cursor--; if (video_cursor < 0) video_cursor = VIDEO_ITEMS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); video_cursor++; if (video_cursor >= VIDEO_ITEMS) video_cursor = 0; break; case K_LEFTARROW: M_Menu_Video_AdjustSliders (-1); break; case K_RIGHTARROW: M_Menu_Video_AdjustSliders (1); break; } } //============================================================================= /* HELP MENU */ static int help_page; #define NUM_HELP_PAGES 6 void M_Menu_Help_f (void) { key_dest = key_menu; m_state = m_help; m_entersound = true; help_page = 0; } static void M_Help_Draw (void) { char vabuf[1024]; M_Background(320, 200); M_DrawPic (0, 0, va(vabuf, sizeof(vabuf), "gfx/help%i", help_page)); } static void M_Help_Key (int key, int ascii) { switch (key) { case K_ESCAPE: M_Menu_Main_f (); break; case K_UPARROW: case K_RIGHTARROW: m_entersound = true; if (++help_page >= NUM_HELP_PAGES) help_page = 0; break; case K_DOWNARROW: case K_LEFTARROW: m_entersound = true; if (--help_page < 0) help_page = NUM_HELP_PAGES-1; break; } } //============================================================================= /* CEDITS MENU */ void M_Menu_Credits_f (void) { key_dest = key_menu; m_state = m_credits; m_entersound = true; } static void M_Credits_Draw (void) { M_Background(640, 480); M_DrawPic (0, 0, "gfx/creditsmiddle"); M_Print (640/2 - 14/2*8, 236, "Coming soon..."); M_DrawPic (0, 0, "gfx/creditstop"); M_DrawPic (0, 433, "gfx/creditsbottom"); } static void M_Credits_Key (int key, int ascii) { M_Menu_Main_f (); } //============================================================================= /* QUIT MENU */ static const char *m_quit_message[9]; static int m_quit_prevstate; static qboolean wasInMenus; static int M_QuitMessage(const char *line1, const char *line2, const char *line3, const char *line4, const char *line5, const char *line6, const char *line7, const char *line8) { m_quit_message[0] = line1; m_quit_message[1] = line2; m_quit_message[2] = line3; m_quit_message[3] = line4; m_quit_message[4] = line5; m_quit_message[5] = line6; m_quit_message[6] = line7; m_quit_message[7] = line8; m_quit_message[8] = NULL; return 1; } static int M_ChooseQuitMessage(int request) { if (m_missingdata) { // frag related quit messages are pointless for a fallback menu, so use something generic if (request-- == 0) return M_QuitMessage("Are you sure you want to quit?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); return 0; } switch (gamemode) { case GAME_NORMAL: case GAME_HIPNOTIC: case GAME_ROGUE: case GAME_QUOTH: case GAME_NEHAHRA: case GAME_DEFEATINDETAIL2: if (request-- == 0) return M_QuitMessage("Are you gonna quit","this game just like","everything else?",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Milord, methinks that","thou art a lowly","quitter. Is this true?",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Do I need to bust your","face open for trying","to quit?",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Man, I oughta smack you","for trying to quit!","Press Y to get","smacked out.",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Press Y to quit like a","big loser in life.","Press N to stay proud","and successful!",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("If you press Y to","quit, I will summon","Satan all over your","hard drive!",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Um, Asmodeus dislikes","his children trying to","quit. Press Y to return","to your Tinkertoys.",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("If you quit now, I'll","throw a blanket-party","for you next time!",NULL,NULL,NULL,NULL,NULL); break; case GAME_GOODVSBAD2: if (request-- == 0) return M_QuitMessage("Press Yes To Quit","...","Yes",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Do you really want to","Quit?","Play Good vs bad 3!",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("All your quit are","belong to long duck","dong",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Press Y to quit","","But are you too legit?",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("This game was made by","e@chip-web.com","It is by far the best","game ever made.",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Even I really dont","know of a game better","Press Y to quit","like rougue chedder",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("After you stop playing","tell the guys who made","counterstrike to just","kill themselves now",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Press Y to exit to DOS","","SSH login as user Y","to exit to Linux",NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Press Y like you","were waanderers","from Ys'",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("This game was made in","Nippon like the SS","announcer's saying ipon",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("you","want to quit?",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Please stop playing","this stupid game",NULL,NULL,NULL,NULL,NULL,NULL); break; case GAME_BATTLEMECH: if (request-- == 0) return M_QuitMessage("? WHY ?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Leave now and your mech is scrap!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Accept Defeat?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Wait! There are more mechs to destroy!","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Where's your bloodlust?","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Your mech here is way more impressive","than your car out there...","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Quitting won't reduce your debt","Press Y to quit, N to keep fraggin'",NULL,NULL,NULL,NULL,NULL,NULL); break; case GAME_OPENQUARTZ: if (request-- == 0) return M_QuitMessage("There is nothing like free beer!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("GNU is not Unix!","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("You prefer free beer over free speech?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Is OpenQuartz Propaganda?","Press Y to quit, N to stay",NULL,NULL,NULL,NULL,NULL,NULL); break; default: if (request-- == 0) return M_QuitMessage("Tired of fragging already?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Quit now and forfeit your bodycount?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Are you sure you want to quit?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); if (request-- == 0) return M_QuitMessage("Off to do something constructive?",NULL,NULL,NULL,NULL,NULL,NULL,NULL); break; } return 0; } void M_Menu_Quit_f (void) { int n; if (m_state == m_quit) return; wasInMenus = (key_dest == key_menu || key_dest == key_menu_grabbed); key_dest = key_menu; m_quit_prevstate = m_state; m_state = m_quit; m_entersound = true; // count how many there are for (n = 1;M_ChooseQuitMessage(n);n++); // choose one M_ChooseQuitMessage(rand() % n); } static void M_Quit_Key (int key, int ascii) { switch (key) { case K_ESCAPE: case 'n': case 'N': if (wasInMenus) { m_state = (enum m_state_e)m_quit_prevstate; m_entersound = true; } else { key_dest = key_game; m_state = m_none; } break; case 'Y': case 'y': Host_Quit_f (); break; default: break; } } static void M_Quit_Draw (void) { int i, l, linelength, firstline, lastline, lines; for (i = 0, linelength = 0, firstline = 9999, lastline = -1;m_quit_message[i];i++) { if ((l = (int)strlen(m_quit_message[i]))) { if (firstline > i) firstline = i; if (lastline < i) lastline = i; if (linelength < l) linelength = l; } } lines = (lastline - firstline) + 1; M_Background(linelength * 8 + 16, lines * 8 + 16); if (!m_missingdata) //since this is a fallback menu for missing data, it is very hard to read with the box M_DrawTextBox(0, 0, linelength, lines); //this is less obtrusive than hacking up the M_DrawTextBox function for (i = 0, l = firstline;i < lines;i++, l++) M_Print(8 + 4 * (linelength - strlen(m_quit_message[l])), 8 + 8 * i, m_quit_message[l]); } //============================================================================= /* LAN CONFIG MENU */ static int lanConfig_cursor = -1; static int lanConfig_cursor_table [] = {56, 76, 84, 120}; #define NUM_LANCONFIG_CMDS 4 static int lanConfig_port; static char lanConfig_portname[6]; static char lanConfig_joinname[40]; void M_Menu_LanConfig_f (void) { key_dest = key_menu; m_state = m_lanconfig; m_entersound = true; if (lanConfig_cursor == -1) { if (JoiningGame) lanConfig_cursor = 1; } if (StartingGame) lanConfig_cursor = 1; lanConfig_port = 26000; dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); M_Update_Return_Reason(""); } static void M_LanConfig_Draw (void) { cachepic_t *p; int basex; const char *startJoin; const char *protocol; char vabuf[1024]; M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_multi"); basex = (320-p->width)/2; M_DrawPic (basex, 4, "gfx/p_multi"); if (StartingGame) startJoin = "New Game"; else startJoin = "Join Game"; protocol = "TCP/IP"; M_Print(basex, 32, va(vabuf, sizeof(vabuf), "%s - %s", startJoin, protocol)); basex += 8; M_Print(basex, lanConfig_cursor_table[0], "Port"); M_DrawTextBox (basex+8*8, lanConfig_cursor_table[0]-8, sizeof(lanConfig_portname), 1); M_Print(basex+9*8, lanConfig_cursor_table[0], lanConfig_portname); if (JoiningGame) { M_Print(basex, lanConfig_cursor_table[1], "Search for DarkPlaces games..."); M_Print(basex, lanConfig_cursor_table[2], "Search for QuakeWorld games..."); M_Print(basex, lanConfig_cursor_table[3]-16, "Join game at:"); M_DrawTextBox (basex+8, lanConfig_cursor_table[3]-8, sizeof(lanConfig_joinname), 1); M_Print(basex+16, lanConfig_cursor_table[3], lanConfig_joinname); } else { M_DrawTextBox (basex, lanConfig_cursor_table[1]-8, 2, 1); M_Print(basex+8, lanConfig_cursor_table[1], "OK"); } M_DrawCharacter (basex-8, lanConfig_cursor_table [lanConfig_cursor], 12+((int)(realtime*4)&1)); if (lanConfig_cursor == 0) M_DrawCharacter (basex+9*8 + 8*strlen(lanConfig_portname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); if (lanConfig_cursor == 3) M_DrawCharacter (basex+16 + 8*strlen(lanConfig_joinname), lanConfig_cursor_table [lanConfig_cursor], 10+((int)(realtime*4)&1)); if (*m_return_reason) M_Print(basex, 168, m_return_reason); } static void M_LanConfig_Key (int key, int ascii) { int l; char vabuf[1024]; switch (key) { case K_ESCAPE: M_Menu_MultiPlayer_f (); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); lanConfig_cursor--; if (lanConfig_cursor < 0) lanConfig_cursor = NUM_LANCONFIG_CMDS-1; // when in start game menu, skip the unused search qw servers item if (StartingGame && lanConfig_cursor == 2) lanConfig_cursor = 1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); lanConfig_cursor++; if (lanConfig_cursor >= NUM_LANCONFIG_CMDS) lanConfig_cursor = 0; // when in start game menu, skip the unused search qw servers item if (StartingGame && lanConfig_cursor == 1) lanConfig_cursor = 2; break; case K_ENTER: if (lanConfig_cursor == 0) break; m_entersound = true; Cbuf_AddText ("stopdemo\n"); Cvar_SetValue("port", lanConfig_port); if (lanConfig_cursor == 1 || lanConfig_cursor == 2) { if (StartingGame) { M_Menu_GameOptions_f (); break; } M_Menu_ServerList_f(); break; } if (lanConfig_cursor == 3) Cbuf_AddText(va(vabuf, sizeof(vabuf), "connect \"%s\"\n", lanConfig_joinname) ); break; case K_BACKSPACE: if (lanConfig_cursor == 0) { if (strlen(lanConfig_portname)) lanConfig_portname[strlen(lanConfig_portname)-1] = 0; } if (lanConfig_cursor == 3) { if (strlen(lanConfig_joinname)) lanConfig_joinname[strlen(lanConfig_joinname)-1] = 0; } break; default: if (ascii < 32) break; if (lanConfig_cursor == 3) { l = (int)strlen(lanConfig_joinname); if (l < (int)sizeof(lanConfig_joinname) - 1) { lanConfig_joinname[l+1] = 0; lanConfig_joinname[l] = ascii; } } if (ascii < '0' || ascii > '9') break; if (lanConfig_cursor == 0) { l = (int)strlen(lanConfig_portname); if (l < (int)sizeof(lanConfig_portname) - 1) { lanConfig_portname[l+1] = 0; lanConfig_portname[l] = ascii; } } } if (StartingGame && lanConfig_cursor == 3) { if (key == K_UPARROW) lanConfig_cursor = 1; else lanConfig_cursor = 0; } l = atoi(lanConfig_portname); if (l <= 65535) lanConfig_port = l; dpsnprintf(lanConfig_portname, sizeof(lanConfig_portname), "%u", (unsigned int) lanConfig_port); } //============================================================================= /* GAME OPTIONS MENU */ typedef struct level_s { const char *name; const char *description; } level_t; typedef struct episode_s { const char *description; int firstLevel; int levels; } episode_t; typedef struct gamelevels_s { const char *gamename; level_t *levels; episode_t *episodes; int numepisodes; } gamelevels_t; static level_t quakelevels[] = { {"start", "Entrance"}, // 0 {"e1m1", "Slipgate Complex"}, // 1 {"e1m2", "Castle of the Damned"}, {"e1m3", "The Necropolis"}, {"e1m4", "The Grisly Grotto"}, {"e1m5", "Gloom Keep"}, {"e1m6", "The Door To Chthon"}, {"e1m7", "The House of Chthon"}, {"e1m8", "Ziggurat Vertigo"}, {"e2m1", "The Installation"}, // 9 {"e2m2", "Ogre Citadel"}, {"e2m3", "Crypt of Decay"}, {"e2m4", "The Ebon Fortress"}, {"e2m5", "The Wizard's Manse"}, {"e2m6", "The Dismal Oubliette"}, {"e2m7", "Underearth"}, {"e3m1", "Termination Central"}, // 16 {"e3m2", "The Vaults of Zin"}, {"e3m3", "The Tomb of Terror"}, {"e3m4", "Satan's Dark Delight"}, {"e3m5", "Wind Tunnels"}, {"e3m6", "Chambers of Torment"}, {"e3m7", "The Haunted Halls"}, {"e4m1", "The Sewage System"}, // 23 {"e4m2", "The Tower of Despair"}, {"e4m3", "The Elder God Shrine"}, {"e4m4", "The Palace of Hate"}, {"e4m5", "Hell's Atrium"}, {"e4m6", "The Pain Maze"}, {"e4m7", "Azure Agony"}, {"e4m8", "The Nameless City"}, {"end", "Shub-Niggurath's Pit"}, // 31 {"dm1", "Place of Two Deaths"}, // 32 {"dm2", "Claustrophobopolis"}, {"dm3", "The Abandoned Base"}, {"dm4", "The Bad Place"}, {"dm5", "The Cistern"}, {"dm6", "The Dark Zone"} }; static episode_t quakeepisodes[] = { {"Welcome to Quake", 0, 1}, {"Doomed Dimension", 1, 8}, {"Realm of Black Magic", 9, 7}, {"Netherworld", 16, 7}, {"The Elder World", 23, 8}, {"Final Level", 31, 1}, {"Deathmatch Arena", 32, 6} }; //MED 01/06/97 added hipnotic levels static level_t hipnoticlevels[] = { {"start", "Command HQ"}, // 0 {"hip1m1", "The Pumping Station"}, // 1 {"hip1m2", "Storage Facility"}, {"hip1m3", "The Lost Mine"}, {"hip1m4", "Research Facility"}, {"hip1m5", "Military Complex"}, {"hip2m1", "Ancient Realms"}, // 6 {"hip2m2", "The Black Cathedral"}, {"hip2m3", "The Catacombs"}, {"hip2m4", "The Crypt"}, {"hip2m5", "Mortum's Keep"}, {"hip2m6", "The Gremlin's Domain"}, {"hip3m1", "Tur Torment"}, // 12 {"hip3m2", "Pandemonium"}, {"hip3m3", "Limbo"}, {"hip3m4", "The Gauntlet"}, {"hipend", "Armagon's Lair"}, // 16 {"hipdm1", "The Edge of Oblivion"} // 17 }; //MED 01/06/97 added hipnotic episodes static episode_t hipnoticepisodes[] = { {"Scourge of Armagon", 0, 1}, {"Fortress of the Dead", 1, 5}, {"Dominion of Darkness", 6, 6}, {"The Rift", 12, 4}, {"Final Level", 16, 1}, {"Deathmatch Arena", 17, 1} }; //PGM 01/07/97 added rogue levels //PGM 03/02/97 added dmatch level static level_t roguelevels[] = { {"start", "Split Decision"}, {"r1m1", "Deviant's Domain"}, {"r1m2", "Dread Portal"}, {"r1m3", "Judgement Call"}, {"r1m4", "Cave of Death"}, {"r1m5", "Towers of Wrath"}, {"r1m6", "Temple of Pain"}, {"r1m7", "Tomb of the Overlord"}, {"r2m1", "Tempus Fugit"}, {"r2m2", "Elemental Fury I"}, {"r2m3", "Elemental Fury II"}, {"r2m4", "Curse of Osiris"}, {"r2m5", "Wizard's Keep"}, {"r2m6", "Blood Sacrifice"}, {"r2m7", "Last Bastion"}, {"r2m8", "Source of Evil"}, {"ctf1", "Division of Change"} }; //PGM 01/07/97 added rogue episodes //PGM 03/02/97 added dmatch episode static episode_t rogueepisodes[] = { {"Introduction", 0, 1}, {"Hell's Fortress", 1, 7}, {"Corridors of Time", 8, 8}, {"Deathmatch Arena", 16, 1} }; static level_t nehahralevels[] = { {"nehstart", "Welcome to Nehahra"}, {"neh1m1", "Forge City1: Slipgates"}, {"neh1m2", "Forge City2: Boiler"}, {"neh1m3", "Forge City3: Escape"}, {"neh1m4", "Grind Core"}, {"neh1m5", "Industrial Silence"}, {"neh1m6", "Locked-Up Anger"}, {"neh1m7", "Wanderer of the Wastes"}, {"neh1m8", "Artemis System Net"}, {"neh1m9", "To the Death"}, {"neh2m1", "The Gates of Ghoro"}, {"neh2m2", "Sacred Trinity"}, {"neh2m3", "Realm of the Ancients"}, {"neh2m4", "Temple of the Ancients"}, {"neh2m5", "Dreams Made Flesh"}, {"neh2m6", "Your Last Cup of Sorrow"}, {"nehsec", "Ogre's Bane"}, {"nehahra", "Nehahra's Den"}, {"nehend", "Quintessence"} }; static episode_t nehahraepisodes[] = { {"Welcome to Nehahra", 0, 1}, {"The Fall of Forge", 1, 9}, {"The Outlands", 10, 7}, {"Dimension of the Lost", 17, 2} }; // Map list for Transfusion static level_t transfusionlevels[] = { {"e1m1", "Cradle to Grave"}, {"e1m2", "Wrong Side of the Tracks"}, {"e1m3", "Phantom Express"}, {"e1m4", "Dark Carnival"}, {"e1m5", "Hallowed Grounds"}, {"e1m6", "The Great Temple"}, {"e1m7", "Altar of Stone"}, {"e1m8", "House of Horrors"}, {"e2m1", "Shipwrecked"}, {"e2m2", "The Lumber Mill"}, {"e2m3", "Rest for the Wicked"}, {"e2m4", "The Overlooked Hotel"}, {"e2m5", "The Haunting"}, {"e2m6", "The Cold Rush"}, {"e2m7", "Bowels of the Earth"}, {"e2m8", "The Lair of Shial"}, {"e2m9", "Thin Ice"}, {"e3m1", "Ghost Town"}, {"e3m2", "The Siege"}, {"e3m3", "Raw Sewage"}, {"e3m4", "The Sick Ward"}, {"e3m5", "Spare Parts"}, {"e3m6", "Monster Bait"}, {"e3m7", "The Pit of Cerberus"}, {"e3m8", "Catacombs"}, {"e4m1", "Butchery Loves Company"}, {"e4m2", "Breeding Grounds"}, {"e4m3", "Charnel House"}, {"e4m4", "Crystal Lake"}, {"e4m5", "Fire and Brimstone"}, {"e4m6", "The Ganglion Depths"}, {"e4m7", "In the Flesh"}, {"e4m8", "The Hall of the Epiphany"}, {"e4m9", "Mall of the Dead"}, {"bb1", "The Stronghold"}, {"bb2", "Winter Wonderland"}, {"bb3", "Bodies"}, {"bb4", "The Tower"}, {"bb5", "Click!"}, {"bb6", "Twin Fortress"}, {"bb7", "Midgard"}, {"bb8", "Fun With Heads"}, {"dm1", "Monolith Building 11"}, {"dm2", "Power!"}, {"dm3", "Area 15"}, {"e6m1", "Welcome to Your Life"}, {"e6m2", "They Are Here"}, {"e6m3", "Public Storage"}, {"e6m4", "Aqueducts"}, {"e6m5", "The Ruined Temple"}, {"e6m6", "Forbidden Rituals"}, {"e6m7", "The Dungeon"}, {"e6m8", "Beauty and the Beast"}, {"e6m9", "Forgotten Catacombs"}, {"cp01", "Boat Docks"}, {"cp02", "Old Opera House"}, {"cp03", "Gothic Library"}, {"cp04", "Lost Monastery"}, {"cp05", "Steamboat"}, {"cp06", "Graveyard"}, {"cp07", "Mountain Pass"}, {"cp08", "Abysmal Mine"}, {"cp09", "Castle"}, {"cps1", "Boggy Creek"}, {"cpbb01", "Crypt of Despair"}, {"cpbb02", "Pits of Blood"}, {"cpbb03", "Unholy Cathedral"}, {"cpbb04", "Deadly Inspirations"}, {"b2a15", "Area 15 (B2)"}, {"b2bodies", "BB_Bodies (B2)"}, {"b2cabana", "BB_Cabana"}, {"b2power", "BB_Power"}, {"barena", "Blood Arena"}, {"bkeep", "Blood Keep"}, {"bstar", "Brown Star"}, {"crypt", "The Crypt"}, {"bb3_2k1", "Bodies Infusion"}, {"captasao", "Captasao"}, {"curandero", "Curandero"}, {"dcamp", "DeathCamp"}, {"highnoon", "HighNoon"}, {"qbb1", "The Confluence"}, {"qbb2", "KathartiK"}, {"qbb3", "Caleb's Woodland Retreat"}, {"zoo", "Zoo"}, {"dranzbb6", "Black Coffee"}, {"fragm", "Frag'M"}, {"maim", "Maim"}, {"qe1m7", "The House of Chthon"}, {"qdm1", "Place of Two Deaths"}, {"qdm4", "The Bad Place"}, {"qdm5", "The Cistern"}, {"qmorbias", "DM-Morbias"}, {"simple", "Dead Simple"} }; static episode_t transfusionepisodes[] = { {"The Way of All Flesh", 0, 8}, {"Even Death May Die", 8, 9}, {"Farewell to Arms", 17, 8}, {"Dead Reckoning", 25, 9}, {"BloodBath", 34, 11}, {"Post Mortem", 45, 9}, {"Cryptic Passage", 54, 10}, {"Cryptic BloodBath", 64, 4}, {"Blood 2", 68, 8}, {"Transfusion", 76, 9}, {"Conversions", 85, 9} }; static level_t goodvsbad2levels[] = { {"rts", "Many Paths"}, // 0 {"chess", "Chess, Scott Hess"}, // 1 {"dot", "Big Wall"}, {"city2", "The Big City"}, {"bwall", "0 G like Psychic TV"}, {"snow", "Wireframed"}, {"telep", "Infinite Falling"}, {"faces", "Facing Bases"}, {"island", "Adventure Islands"}, }; static episode_t goodvsbad2episodes[] = { {"Levels? Bevels!", 0, 8}, }; static level_t battlemechlevels[] = { {"start", "Parking Level"}, {"dm1", "Hot Dump"}, // 1 {"dm2", "The Pits"}, {"dm3", "Dimber Died"}, {"dm4", "Fire in the Hole"}, {"dm5", "Clubhouses"}, {"dm6", "Army go Underground"}, }; static episode_t battlemechepisodes[] = { {"Time for Battle", 0, 7}, }; static level_t openquartzlevels[] = { {"start", "Welcome to Openquartz"}, {"void1", "The center of nowhere"}, // 1 {"void2", "The place with no name"}, {"void3", "The lost supply base"}, {"void4", "Past the outer limits"}, {"void5", "Into the nonexistance"}, {"void6", "Void walk"}, {"vtest", "Warp Central"}, {"box", "The deathmatch box"}, {"bunkers", "Void command"}, {"house", "House of chaos"}, {"office", "Overnight office kill"}, {"am1", "The nameless chambers"}, }; static episode_t openquartzepisodes[] = { {"Single Player", 0, 1}, {"Void Deathmatch", 1, 6}, {"Contrib", 7, 6}, }; static level_t defeatindetail2levels[] = { {"atac3", "River Crossing"}, {"atac4", "Canyon Chaos"}, {"atac7", "Desert Stormer"}, }; static episode_t defeatindetail2episodes[] = { {"ATAC Campaign", 0, 3}, }; static level_t prydonlevels[] = { {"curig2", "Capel Curig"}, // 0 {"tdastart", "Gateway"}, // 1 }; static episode_t prydonepisodes[] = { {"Prydon Gate", 0, 1}, {"The Dark Age", 1, 1} }; static gamelevels_t sharewarequakegame = {"Shareware Quake", quakelevels, quakeepisodes, 2}; static gamelevels_t registeredquakegame = {"Quake", quakelevels, quakeepisodes, 7}; static gamelevels_t hipnoticgame = {"Scourge of Armagon", hipnoticlevels, hipnoticepisodes, 6}; static gamelevels_t roguegame = {"Dissolution of Eternity", roguelevels, rogueepisodes, 4}; static gamelevels_t nehahragame = {"Nehahra", nehahralevels, nehahraepisodes, 4}; static gamelevels_t transfusiongame = {"Transfusion", transfusionlevels, transfusionepisodes, 11}; static gamelevels_t goodvsbad2game = {"Good Vs. Bad 2", goodvsbad2levels, goodvsbad2episodes, 1}; static gamelevels_t battlemechgame = {"Battlemech", battlemechlevels, battlemechepisodes, 1}; static gamelevels_t openquartzgame = {"OpenQuartz", openquartzlevels, openquartzepisodes, 3}; static gamelevels_t defeatindetail2game = {"Defeat In Detail 2", defeatindetail2levels, defeatindetail2episodes, 1}; static gamelevels_t prydongame = {"Prydon Gate", prydonlevels, prydonepisodes, 2}; typedef struct gameinfo_s { gamemode_t gameid; gamelevels_t *notregistered; gamelevels_t *registered; } gameinfo_t; static gameinfo_t gamelist[] = { {GAME_NORMAL, &sharewarequakegame, ®isteredquakegame}, {GAME_HIPNOTIC, &hipnoticgame, &hipnoticgame}, {GAME_ROGUE, &roguegame, &roguegame}, {GAME_QUOTH, &sharewarequakegame, ®isteredquakegame}, {GAME_NEHAHRA, &nehahragame, &nehahragame}, {GAME_TRANSFUSION, &transfusiongame, &transfusiongame}, {GAME_GOODVSBAD2, &goodvsbad2game, &goodvsbad2game}, {GAME_BATTLEMECH, &battlemechgame, &battlemechgame}, {GAME_OPENQUARTZ, &openquartzgame, &openquartzgame}, {GAME_DEFEATINDETAIL2, &defeatindetail2game, &defeatindetail2game}, {GAME_PRYDON, &prydongame, &prydongame}, }; static gamelevels_t *gameoptions_levels = NULL; static int startepisode; static int startlevel; static int maxplayers; static qboolean m_serverInfoMessage = false; static double m_serverInfoMessageTime; void M_Menu_GameOptions_f (void) { int i; key_dest = key_menu; m_state = m_gameoptions; m_entersound = true; if (maxplayers == 0) maxplayers = svs.maxclients; if (maxplayers < 2) maxplayers = min(8, MAX_SCOREBOARD); // pick game level list based on gamemode (use GAME_NORMAL if no matches) gameoptions_levels = registered.integer ? gamelist[0].registered : gamelist[0].notregistered; for (i = 0;i < (int)(sizeof(gamelist)/sizeof(gamelist[0]));i++) if (gamelist[i].gameid == gamemode) gameoptions_levels = registered.integer ? gamelist[i].registered : gamelist[i].notregistered; } static int gameoptions_cursor_table[] = {40, 56, 64, 72, 80, 88, 96, 104, 112, 140, 160, 168}; #define NUM_GAMEOPTIONS 12 static int gameoptions_cursor; void M_GameOptions_Draw (void) { cachepic_t *p; int x; char vabuf[1024]; M_Background(320, 200); M_DrawPic (16, 4, "gfx/qplaque"); p = Draw_CachePic ("gfx/p_multi"); M_DrawPic ( (320-p->width)/2, 4, "gfx/p_multi"); M_DrawTextBox (152, 32, 10, 1); M_Print(160, 40, "begin game"); M_Print(0, 56, " Max players"); M_Print(160, 56, va(vabuf, sizeof(vabuf), "%i", maxplayers) ); if (gamemode != GAME_GOODVSBAD2) { M_Print(0, 64, " Game Type"); if (gamemode == GAME_TRANSFUSION) { if (!coop.integer && !deathmatch.integer) Cvar_SetValue("deathmatch", 1); if (deathmatch.integer == 0) M_Print(160, 64, "Cooperative"); else if (deathmatch.integer == 2) M_Print(160, 64, "Capture the Flag"); else M_Print(160, 64, "Blood Bath"); } else if (gamemode == GAME_BATTLEMECH) { if (!deathmatch.integer) Cvar_SetValue("deathmatch", 1); if (deathmatch.integer == 2) M_Print(160, 64, "Rambo Match"); else M_Print(160, 64, "Deathmatch"); } else { if (!coop.integer && !deathmatch.integer) Cvar_SetValue("deathmatch", 1); if (coop.integer) M_Print(160, 64, "Cooperative"); else M_Print(160, 64, "Deathmatch"); } M_Print(0, 72, " Teamplay"); if (gamemode == GAME_ROGUE) { const char *msg; switch((int)teamplay.integer) { case 1: msg = "No Friendly Fire"; break; case 2: msg = "Friendly Fire"; break; case 3: msg = "Tag"; break; case 4: msg = "Capture the Flag"; break; case 5: msg = "One Flag CTF"; break; case 6: msg = "Three Team CTF"; break; default: msg = "Off"; break; } M_Print(160, 72, msg); } else { const char *msg; switch (teamplay.integer) { case 0: msg = "Off"; break; case 2: msg = "Friendly Fire"; break; default: msg = "No Friendly Fire"; break; } M_Print(160, 72, msg); } M_Print(0, 80, " Skill"); if (gamemode == GAME_TRANSFUSION) { if (skill.integer == 1) M_Print(160, 80, "Still Kicking"); else if (skill.integer == 2) M_Print(160, 80, "Pink On The Inside"); else if (skill.integer == 3) M_Print(160, 80, "Lightly Broiled"); else if (skill.integer == 4) M_Print(160, 80, "Well Done"); else M_Print(160, 80, "Extra Crispy"); } else { if (skill.integer == 0) M_Print(160, 80, "Easy difficulty"); else if (skill.integer == 1) M_Print(160, 80, "Normal difficulty"); else if (skill.integer == 2) M_Print(160, 80, "Hard difficulty"); else M_Print(160, 80, "Nightmare difficulty"); } M_Print(0, 88, " Frag Limit"); if (fraglimit.integer == 0) M_Print(160, 88, "none"); else M_Print(160, 88, va(vabuf, sizeof(vabuf), "%i frags", fraglimit.integer)); M_Print(0, 96, " Time Limit"); if (timelimit.integer == 0) M_Print(160, 96, "none"); else M_Print(160, 96, va(vabuf, sizeof(vabuf), "%i minutes", timelimit.integer)); } M_Print(0, 104, " Public server"); M_Print(160, 104, (sv_public.integer == 0) ? "no" : "yes"); M_Print(0, 112, " Server maxrate"); M_Print(160, 112, va(vabuf, sizeof(vabuf), "%i", sv_maxrate.integer)); M_Print(0, 128, " Server name"); M_DrawTextBox (0, 132, 38, 1); M_Print(8, 140, hostname.string); if (gamemode != GAME_GOODVSBAD2) { M_Print(0, 160, " Episode"); M_Print(160, 160, gameoptions_levels->episodes[startepisode].description); } M_Print(0, 168, " Level"); M_Print(160, 168, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].description); M_Print(160, 176, gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name); // line cursor if (gameoptions_cursor == 9) M_DrawCharacter (8 + 8 * strlen(hostname.string), gameoptions_cursor_table[gameoptions_cursor], 10+((int)(realtime*4)&1)); else M_DrawCharacter (144, gameoptions_cursor_table[gameoptions_cursor], 12+((int)(realtime*4)&1)); if (m_serverInfoMessage) { if ((realtime - m_serverInfoMessageTime) < 5.0) { x = (320-26*8)/2; M_DrawTextBox (x, 138, 24, 4); x += 8; M_Print(x, 146, " More than 255 players??"); M_Print(x, 154, " First, question your "); M_Print(x, 162, " sanity, then email "); M_Print(x, 170, "darkplacesengine@gmail.com"); } else m_serverInfoMessage = false; } } static void M_NetStart_Change (int dir) { int count; switch (gameoptions_cursor) { case 1: maxplayers += dir; if (maxplayers > MAX_SCOREBOARD) { maxplayers = MAX_SCOREBOARD; m_serverInfoMessage = true; m_serverInfoMessageTime = realtime; } if (maxplayers < 2) maxplayers = 2; break; case 2: if (gamemode == GAME_GOODVSBAD2) break; if (gamemode == GAME_TRANSFUSION) { switch (deathmatch.integer) { // From Cooperative to BloodBath case 0: Cvar_SetValueQuick (&coop, 0); Cvar_SetValueQuick (&deathmatch, 1); break; // From BloodBath to CTF case 1: Cvar_SetValueQuick (&coop, 0); Cvar_SetValueQuick (&deathmatch, 2); break; // From CTF to Cooperative //case 2: default: Cvar_SetValueQuick (&coop, 1); Cvar_SetValueQuick (&deathmatch, 0); } } else if (gamemode == GAME_BATTLEMECH) { if (deathmatch.integer == 2) // changing from Rambo to Deathmatch Cvar_SetValueQuick (&deathmatch, 0); else // changing from Deathmatch to Rambo Cvar_SetValueQuick (&deathmatch, 2); } else { if (deathmatch.integer) // changing from deathmatch to coop { Cvar_SetValueQuick (&coop, 1); Cvar_SetValueQuick (&deathmatch, 0); } else // changing from coop to deathmatch { Cvar_SetValueQuick (&coop, 0); Cvar_SetValueQuick (&deathmatch, 1); } } break; case 3: if (gamemode == GAME_GOODVSBAD2) break; if (gamemode == GAME_ROGUE) count = 6; else count = 2; Cvar_SetValueQuick (&teamplay, teamplay.integer + dir); if (teamplay.integer > count) Cvar_SetValueQuick (&teamplay, 0); else if (teamplay.integer < 0) Cvar_SetValueQuick (&teamplay, count); break; case 4: if (gamemode == GAME_GOODVSBAD2) break; Cvar_SetValueQuick (&skill, skill.integer + dir); if (gamemode == GAME_TRANSFUSION) { if (skill.integer > 5) Cvar_SetValueQuick (&skill, 1); if (skill.integer < 1) Cvar_SetValueQuick (&skill, 5); } else { if (skill.integer > 3) Cvar_SetValueQuick (&skill, 0); if (skill.integer < 0) Cvar_SetValueQuick (&skill, 3); } break; case 5: if (gamemode == GAME_GOODVSBAD2) break; Cvar_SetValueQuick (&fraglimit, fraglimit.integer + dir*10); if (fraglimit.integer > 100) Cvar_SetValueQuick (&fraglimit, 0); if (fraglimit.integer < 0) Cvar_SetValueQuick (&fraglimit, 100); break; case 6: if (gamemode == GAME_GOODVSBAD2) break; Cvar_SetValueQuick (&timelimit, timelimit.value + dir*5); if (timelimit.value > 60) Cvar_SetValueQuick (&timelimit, 0); if (timelimit.value < 0) Cvar_SetValueQuick (&timelimit, 60); break; case 7: Cvar_SetValueQuick (&sv_public, !sv_public.integer); break; case 8: Cvar_SetValueQuick (&sv_maxrate, sv_maxrate.integer + dir*500); if (sv_maxrate.integer < NET_MINRATE) Cvar_SetValueQuick (&sv_maxrate, NET_MINRATE); break; case 9: break; case 10: if (gamemode == GAME_GOODVSBAD2) break; startepisode += dir; if (startepisode < 0) startepisode = gameoptions_levels->numepisodes - 1; if (startepisode >= gameoptions_levels->numepisodes) startepisode = 0; startlevel = 0; break; case 11: startlevel += dir; if (startlevel < 0) startlevel = gameoptions_levels->episodes[startepisode].levels - 1; if (startlevel >= gameoptions_levels->episodes[startepisode].levels) startlevel = 0; break; } } static void M_GameOptions_Key (int key, int ascii) { int l; char hostnamebuf[128]; char vabuf[1024]; switch (key) { case K_ESCAPE: M_Menu_MultiPlayer_f (); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); gameoptions_cursor--; if (gameoptions_cursor < 0) gameoptions_cursor = NUM_GAMEOPTIONS-1; break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); gameoptions_cursor++; if (gameoptions_cursor >= NUM_GAMEOPTIONS) gameoptions_cursor = 0; break; case K_LEFTARROW: if (gameoptions_cursor == 0) break; S_LocalSound ("sound/misc/menu3.wav"); M_NetStart_Change (-1); break; case K_RIGHTARROW: if (gameoptions_cursor == 0) break; S_LocalSound ("sound/misc/menu3.wav"); M_NetStart_Change (1); break; case K_ENTER: S_LocalSound ("sound/misc/menu2.wav"); if (gameoptions_cursor == 0) { if (sv.active) Cbuf_AddText("disconnect\n"); Cbuf_AddText(va(vabuf, sizeof(vabuf), "maxplayers %u\n", maxplayers) ); Cbuf_AddText(va(vabuf, sizeof(vabuf), "map %s\n", gameoptions_levels->levels[gameoptions_levels->episodes[startepisode].firstLevel + startlevel].name) ); return; } M_NetStart_Change (1); break; case K_BACKSPACE: if (gameoptions_cursor == 9) { l = (int)strlen(hostname.string); if (l) { l = min(l - 1, 37); memcpy(hostnamebuf, hostname.string, l); hostnamebuf[l] = 0; Cvar_Set("hostname", hostnamebuf); } } break; default: if (ascii < 32) break; if (gameoptions_cursor == 9) { l = (int)strlen(hostname.string); if (l < 37) { memcpy(hostnamebuf, hostname.string, l); hostnamebuf[l] = ascii; hostnamebuf[l+1] = 0; Cvar_Set("hostname", hostnamebuf); } } } } //============================================================================= /* SLIST MENU */ static int slist_cursor; void M_Menu_ServerList_f (void) { key_dest = key_menu; m_state = m_slist; m_entersound = true; slist_cursor = 0; M_Update_Return_Reason(""); if (lanConfig_cursor == 2) Net_SlistQW_f(); else Net_Slist_f(); } static void M_ServerList_Draw (void) { int n, y, visible, start, end, statnumplayers, statmaxplayers; cachepic_t *p; const char *s; char vabuf[1024]; // use as much vertical space as available if (gamemode == GAME_TRANSFUSION) M_Background(640, vid_conheight.integer - 80); else M_Background(640, vid_conheight.integer); // scroll the list as the cursor moves ServerList_GetPlayerStatistics(&statnumplayers, &statmaxplayers); s = va(vabuf, sizeof(vabuf), "%i/%i masters %i/%i servers %i/%i players", masterreplycount, masterquerycount, serverreplycount, serverquerycount, statnumplayers, statmaxplayers); M_PrintRed((640 - strlen(s) * 8) / 2, 32, s); if (*m_return_reason) M_Print(16, menu_height - 8, m_return_reason); y = 48; visible = (int)((menu_height - 16 - y) / 8 / 2); start = bound(0, slist_cursor - (visible >> 1), serverlist_viewcount - visible); end = min(start + visible, serverlist_viewcount); p = Draw_CachePic ("gfx/p_multi"); M_DrawPic((640 - p->width) / 2, 4, "gfx/p_multi"); if (end > start) { for (n = start;n < end;n++) { serverlist_entry_t *entry = ServerList_GetViewEntry(n); DrawQ_Fill(menu_x, menu_y + y, 640, 16, n == slist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); M_PrintColored(0, y, entry->line1);y += 8; M_PrintColored(0, y, entry->line2);y += 8; } } else if (realtime - masterquerytime > 10) { if (masterquerycount) M_Print(0, y, "No servers found"); else M_Print(0, y, "No master servers found (network problem?)"); } else { if (serverquerycount) M_Print(0, y, "Querying servers"); else M_Print(0, y, "Querying master servers"); } } static void M_ServerList_Key(int k, int ascii) { char vabuf[1024]; switch (k) { case K_ESCAPE: M_Menu_LanConfig_f(); break; case K_SPACE: if (lanConfig_cursor == 2) Net_SlistQW_f(); else Net_Slist_f(); break; case K_UPARROW: case K_LEFTARROW: S_LocalSound ("sound/misc/menu1.wav"); slist_cursor--; if (slist_cursor < 0) slist_cursor = serverlist_viewcount - 1; break; case K_DOWNARROW: case K_RIGHTARROW: S_LocalSound ("sound/misc/menu1.wav"); slist_cursor++; if (slist_cursor >= serverlist_viewcount) slist_cursor = 0; break; case K_ENTER: S_LocalSound ("sound/misc/menu2.wav"); if (serverlist_viewcount) Cbuf_AddText(va(vabuf, sizeof(vabuf), "connect \"%s\"\n", ServerList_GetViewEntry(slist_cursor)->info.cname)); break; default: break; } } //============================================================================= /* MODLIST MENU */ // same limit of mod dirs as in fs.c #define MODLIST_MAXDIRS 16 static int modlist_enabled [MODLIST_MAXDIRS]; //array of indexs to modlist static int modlist_numenabled; //number of enabled (or in process to be..) mods typedef struct modlist_entry_s { qboolean loaded; // used to determine whether this entry is loaded and running int enabled; // index to array of modlist_enabled // name of the modification, this is (will...be) displayed on the menu entry char name[128]; // directory where we will find it char dir[MAX_QPATH]; } modlist_entry_t; static int modlist_cursor; //static int modlist_viewcount; static int modlist_count = 0; static modlist_entry_t modlist[MODLIST_TOTALSIZE]; static void ModList_RebuildList(void) { int i,j; stringlist_t list; stringlistinit(&list); listdirectory(&list, fs_basedir, ""); stringlistsort(&list, true); modlist_count = 0; modlist_numenabled = fs_numgamedirs; for (i = 0;i < list.numstrings;i++) { if (modlist_count >= MODLIST_TOTALSIZE) break; // check all dirs to see if they "appear" to be mods // reject any dirs that are part of the base game if (gamedirname1 && !strcasecmp(gamedirname1, list.strings[i])) continue; //if (gamedirname2 && !strcasecmp(gamedirname2, list.strings[i])) continue; if (FS_CheckNastyPath (list.strings[i], true)) continue; if (!FS_CheckGameDir(list.strings[i])) continue; strlcpy (modlist[modlist_count].dir, list.strings[i], sizeof(modlist[modlist_count].dir)); //check currently loaded mods modlist[modlist_count].loaded = false; if (fs_numgamedirs) for (j = 0; j < fs_numgamedirs; j++) if (!strcasecmp(fs_gamedirs[j], modlist[modlist_count].dir)) { modlist[modlist_count].loaded = true; modlist[modlist_count].enabled = j; modlist_enabled[j] = modlist_count; break; } modlist_count ++; } stringlistfreecontents(&list); } static void ModList_Enable (void) { int i; int numgamedirs; char gamedirs[MODLIST_MAXDIRS][MAX_QPATH]; // copy our mod list into an array for FS_ChangeGameDirs numgamedirs = modlist_numenabled; for (i = 0; i < modlist_numenabled; i++) strlcpy (gamedirs[i], modlist[modlist_enabled[i]].dir,sizeof (gamedirs[i])); // this code snippet is from FS_ChangeGameDirs if (fs_numgamedirs == numgamedirs) { for (i = 0;i < numgamedirs;i++) if (strcasecmp(fs_gamedirs[i], gamedirs[i])) break; if (i == numgamedirs) return; // already using this set of gamedirs, do nothing } // this part is basically the same as the FS_GameDir_f function if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) { // actually, changing during game would work fine, but would be stupid Con_Printf("Can not change gamedir while client is connected or server is running!\n"); return; } FS_ChangeGameDirs (modlist_numenabled, gamedirs, true, true); } void M_Menu_ModList_f (void) { key_dest = key_menu; m_state = m_modlist; m_entersound = true; modlist_cursor = 0; M_Update_Return_Reason(""); ModList_RebuildList(); } static void M_Menu_ModList_AdjustSliders (int dir) { int i; S_LocalSound ("sound/misc/menu3.wav"); // stop adding mods, we reach the limit if (!modlist[modlist_cursor].loaded && (modlist_numenabled == MODLIST_MAXDIRS)) return; modlist[modlist_cursor].loaded = !modlist[modlist_cursor].loaded; if (modlist[modlist_cursor].loaded) { modlist[modlist_cursor].enabled = modlist_numenabled; //push the value on the enabled list modlist_enabled[modlist_numenabled++] = modlist_cursor; } else { //eliminate the value from the enabled list for (i = modlist[modlist_cursor].enabled; i < modlist_numenabled; i++) { modlist_enabled[i] = modlist_enabled[i+1]; modlist[modlist_enabled[i]].enabled--; } modlist_numenabled--; } } static void M_ModList_Draw (void) { int n, y, visible, start, end; cachepic_t *p; const char *s_available = "Available Mods"; const char *s_enabled = "Enabled Mods"; // use as much vertical space as available if (gamemode == GAME_TRANSFUSION) M_Background(640, vid_conheight.integer - 80); else M_Background(640, vid_conheight.integer); M_PrintRed(48 + 32, 32, s_available); M_PrintRed(432, 32, s_enabled); // Draw a list box with all enabled mods DrawQ_Pic(menu_x + 432, menu_y + 48, NULL, 172, 8 * modlist_numenabled, 0, 0, 0, 0.5, 0); for (y = 0; y < modlist_numenabled; y++) M_PrintRed(432, 48 + y * 8, modlist[modlist_enabled[y]].dir); if (*m_return_reason) M_Print(16, menu_height - 8, m_return_reason); // scroll the list as the cursor moves y = 48; visible = (int)((menu_height - 16 - y) / 8 / 2); start = bound(0, modlist_cursor - (visible >> 1), modlist_count - visible); end = min(start + visible, modlist_count); p = Draw_CachePic ("gfx/p_option"); M_DrawPic((640 - p->width) / 2, 4, "gfx/p_option"); if (end > start) { for (n = start;n < end;n++) { DrawQ_Pic(menu_x + 40, menu_y + y, NULL, 360, 8, n == modlist_cursor ? (0.5 + 0.2 * sin(realtime * M_PI)) : 0, 0, 0, 0.5, 0); M_ItemPrint(80, y, modlist[n].dir, true); M_DrawCheckbox(48, y, modlist[n].loaded); y +=8; } } else { M_Print(80, y, "No Mods found"); } } static void M_ModList_Key(int k, int ascii) { switch (k) { case K_ESCAPE: ModList_Enable (); M_Menu_Options_f(); break; case K_SPACE: S_LocalSound ("sound/misc/menu2.wav"); ModList_RebuildList(); break; case K_UPARROW: S_LocalSound ("sound/misc/menu1.wav"); modlist_cursor--; if (modlist_cursor < 0) modlist_cursor = modlist_count - 1; break; case K_LEFTARROW: M_Menu_ModList_AdjustSliders (-1); break; case K_DOWNARROW: S_LocalSound ("sound/misc/menu1.wav"); modlist_cursor++; if (modlist_cursor >= modlist_count) modlist_cursor = 0; break; case K_RIGHTARROW: M_Menu_ModList_AdjustSliders (1); break; case K_ENTER: S_LocalSound ("sound/misc/menu2.wav"); ModList_Enable (); break; default: break; } } //============================================================================= /* Menu Subsystem */ static void M_KeyEvent(int key, int ascii, qboolean downevent); static void M_Draw(void); void M_ToggleMenu(int mode); static void M_Shutdown(void); static void M_Init (void) { menuplyr_load = true; menuplyr_pixels = NULL; Cmd_AddCommand ("menu_main", M_Menu_Main_f, "open the main menu"); Cmd_AddCommand ("menu_singleplayer", M_Menu_SinglePlayer_f, "open the singleplayer menu"); Cmd_AddCommand ("menu_load", M_Menu_Load_f, "open the loadgame menu"); Cmd_AddCommand ("menu_save", M_Menu_Save_f, "open the savegame menu"); Cmd_AddCommand ("menu_multiplayer", M_Menu_MultiPlayer_f, "open the multiplayer menu"); Cmd_AddCommand ("menu_setup", M_Menu_Setup_f, "open the player setup menu"); Cmd_AddCommand ("menu_options", M_Menu_Options_f, "open the options menu"); Cmd_AddCommand ("menu_options_effects", M_Menu_Options_Effects_f, "open the effects options menu"); Cmd_AddCommand ("menu_options_graphics", M_Menu_Options_Graphics_f, "open the graphics options menu"); Cmd_AddCommand ("menu_options_colorcontrol", M_Menu_Options_ColorControl_f, "open the color control menu"); Cmd_AddCommand ("menu_keys", M_Menu_Keys_f, "open the key binding menu"); Cmd_AddCommand ("menu_video", M_Menu_Video_f, "open the video options menu"); Cmd_AddCommand ("menu_reset", M_Menu_Reset_f, "open the reset to defaults menu"); Cmd_AddCommand ("menu_mods", M_Menu_ModList_f, "open the mods browser menu"); Cmd_AddCommand ("help", M_Menu_Help_f, "open the help menu"); Cmd_AddCommand ("menu_quit", M_Menu_Quit_f, "open the quit menu"); Cmd_AddCommand ("menu_transfusion_episode", M_Menu_Transfusion_Episode_f, "open the transfusion episode select menu"); Cmd_AddCommand ("menu_transfusion_skill", M_Menu_Transfusion_Skill_f, "open the transfusion skill select menu"); Cmd_AddCommand ("menu_credits", M_Menu_Credits_f, "open the credits menu"); } void M_Draw (void) { char vabuf[1024]; if (key_dest != key_menu && key_dest != key_menu_grabbed) m_state = m_none; if (m_state == m_none) return; switch (m_state) { case m_none: break; case m_main: M_Main_Draw (); break; case m_demo: M_Demo_Draw (); break; case m_singleplayer: M_SinglePlayer_Draw (); break; case m_transfusion_episode: M_Transfusion_Episode_Draw (); break; case m_transfusion_skill: M_Transfusion_Skill_Draw (); break; case m_load: M_Load_Draw (); break; case m_save: M_Save_Draw (); break; case m_multiplayer: M_MultiPlayer_Draw (); break; case m_setup: M_Setup_Draw (); break; case m_options: M_Options_Draw (); break; case m_options_effects: M_Options_Effects_Draw (); break; case m_options_graphics: M_Options_Graphics_Draw (); break; case m_options_colorcontrol: M_Options_ColorControl_Draw (); break; case m_keys: M_Keys_Draw (); break; case m_reset: M_Reset_Draw (); break; case m_video: M_Video_Draw (); break; case m_help: M_Help_Draw (); break; case m_credits: M_Credits_Draw (); break; case m_quit: M_Quit_Draw (); break; case m_lanconfig: M_LanConfig_Draw (); break; case m_gameoptions: M_GameOptions_Draw (); break; case m_slist: M_ServerList_Draw (); break; case m_modlist: M_ModList_Draw (); break; } if (gamemode == GAME_TRANSFUSION && !m_missingdata) { if (m_state != m_credits) { cachepic_t *p, *drop1, *drop2, *drop3; int g, scale_x, scale_y, scale_y_repeat, top_offset; float scale_y_rate; scale_y_repeat = vid_conheight.integer * 2; g = (int)(realtime * 64)%96; scale_y_rate = (float)(g+1) / 96; top_offset = (g+12)/12; p = Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/menu/blooddrip%i", top_offset)); drop1 = Draw_CachePic ("gfx/menu/blooddrop1"); drop2 = Draw_CachePic ("gfx/menu/blooddrop2"); drop3 = Draw_CachePic ("gfx/menu/blooddrop3"); for (scale_x = 0; scale_x <= vid_conwidth.integer; scale_x += p->width) { for (scale_y = -scale_y_repeat; scale_y <= vid_conheight.integer; scale_y += scale_y_repeat) { DrawQ_Pic (scale_x + 21, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 116, scale_y_repeat + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 180, scale_y_repeat * .275 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 242, scale_y_repeat * .75 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 304, scale_y_repeat * .25 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 362, scale_y_repeat * .46125 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 402, scale_y_repeat * .1725 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 438, scale_y_repeat * .9 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 484, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop3, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 557, scale_y_repeat * .9425 + scale_y + scale_y_rate * scale_y_repeat, drop1, 0, 0, 1, 1, 1, 1, 0); DrawQ_Pic (scale_x + 606, scale_y_repeat * .5 + scale_y + scale_y_rate * scale_y_repeat, drop2, 0, 0, 1, 1, 1, 1, 0); } DrawQ_Pic (scale_x, -1, Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/menu/blooddrip%i", top_offset)), 0, 0, 1, 1, 1, 1, 0); } } } if (m_entersound) { S_LocalSound ("sound/misc/menu2.wav"); m_entersound = false; } S_ExtraUpdate (); } void M_KeyEvent (int key, int ascii, qboolean downevent) { if (!downevent) return; switch (m_state) { case m_none: return; case m_main: M_Main_Key (key, ascii); return; case m_demo: M_Demo_Key (key, ascii); return; case m_singleplayer: M_SinglePlayer_Key (key, ascii); return; case m_transfusion_episode: M_Transfusion_Episode_Key (key, ascii); return; case m_transfusion_skill: M_Transfusion_Skill_Key (key, ascii); return; case m_load: M_Load_Key (key, ascii); return; case m_save: M_Save_Key (key, ascii); return; case m_multiplayer: M_MultiPlayer_Key (key, ascii); return; case m_setup: M_Setup_Key (key, ascii); return; case m_options: M_Options_Key (key, ascii); return; case m_options_effects: M_Options_Effects_Key (key, ascii); return; case m_options_graphics: M_Options_Graphics_Key (key, ascii); return; case m_options_colorcontrol: M_Options_ColorControl_Key (key, ascii); return; case m_keys: M_Keys_Key (key, ascii); return; case m_reset: M_Reset_Key (key, ascii); return; case m_video: M_Video_Key (key, ascii); return; case m_help: M_Help_Key (key, ascii); return; case m_credits: M_Credits_Key (key, ascii); return; case m_quit: M_Quit_Key (key, ascii); return; case m_lanconfig: M_LanConfig_Key (key, ascii); return; case m_gameoptions: M_GameOptions_Key (key, ascii); return; case m_slist: M_ServerList_Key (key, ascii); return; case m_modlist: M_ModList_Key (key, ascii); return; } } static void M_NewMap(void) { } static int M_GetServerListEntryCategory(const serverlist_entry_t *entry) { return 0; } void M_Shutdown(void) { // reset key_dest key_dest = key_game; } //============================================================================ // Menu prog handling static const char *m_required_func[] = { "m_init", "m_keydown", "m_draw", "m_toggle", "m_shutdown", }; static int m_numrequiredfunc = sizeof(m_required_func) / sizeof(char*); static prvm_required_field_t m_required_fields[] = { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) {ev_float, #x}, #define PRVM_DECLARE_menufieldvector(x) {ev_vector, #x}, #define PRVM_DECLARE_menufieldstring(x) {ev_string, #x}, #define PRVM_DECLARE_menufieldedict(x) {ev_entity, #x}, #define PRVM_DECLARE_menufieldfunction(x) {ev_function, #x}, #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; static int m_numrequiredfields = sizeof(m_required_fields) / sizeof(m_required_fields[0]); static prvm_required_field_t m_required_globals[] = { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) {ev_float, #x}, #define PRVM_DECLARE_menuglobalvector(x) {ev_vector, #x}, #define PRVM_DECLARE_menuglobalstring(x) {ev_string, #x}, #define PRVM_DECLARE_menuglobaledict(x) {ev_entity, #x}, #define PRVM_DECLARE_menuglobalfunction(x) {ev_function, #x}, #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; static int m_numrequiredglobals = sizeof(m_required_globals) / sizeof(m_required_globals[0]); void MR_SetRouting (qboolean forceold); void MVM_error_cmd(const char *format, ...) DP_FUNC_PRINTF(1); void MVM_error_cmd(const char *format, ...) { prvm_prog_t *prog = MVM_prog; static qboolean processingError = false; char errorstring[MAX_INPUTLINE]; va_list argptr; va_start (argptr, format); dpvsnprintf (errorstring, sizeof(errorstring), format, argptr); va_end (argptr); Con_Printf( "Menu_Error: %s\n", errorstring ); if( !processingError ) { processingError = true; PRVM_Crash(prog); processingError = false; } else { Con_Printf( "Menu_Error: Recursive call to MVM_error_cmd (from PRVM_Crash)!\n" ); } // fall back to the normal menu // say it Con_Print("Falling back to normal menu\n"); key_dest = key_game; // init the normal menu now -> this will also correct the menu router pointers MR_SetRouting (TRUE); // reset the active scene, too (to be on the safe side ;)) R_SelectScene( RST_CLIENT ); Host_AbortCurrentFrame(); } static void MVM_begin_increase_edicts(prvm_prog_t *prog) { } static void MVM_end_increase_edicts(prvm_prog_t *prog) { } static void MVM_init_edict(prvm_prog_t *prog, prvm_edict_t *edict) { } static void MVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) { } static void MVM_count_edicts(prvm_prog_t *prog) { int i; prvm_edict_t *ent; int active; active = 0; for (i=0 ; inum_edicts ; i++) { ent = PRVM_EDICT_NUM(i); if (ent->priv.required->free) continue; active++; } Con_Printf("num_edicts:%3i\n", prog->num_edicts); Con_Printf("active :%3i\n", active); } static qboolean MVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) { return true; } static void MP_KeyEvent (int key, int ascii, qboolean downevent) { prvm_prog_t *prog = MVM_prog; // pass key prog->globals.fp[OFS_PARM0] = (prvm_vec_t) key; prog->globals.fp[OFS_PARM1] = (prvm_vec_t) ascii; if (downevent) prog->ExecuteProgram(prog, PRVM_menufunction(m_keydown),"m_keydown(float key, float ascii) required"); else if (PRVM_menufunction(m_keyup)) prog->ExecuteProgram(prog, PRVM_menufunction(m_keyup),"m_keyup(float key, float ascii) required"); } static void MP_Draw (void) { prvm_prog_t *prog = MVM_prog; // declarations that are needed right now float oldquality; R_SelectScene( RST_MENU ); // reset the temp entities each frame r_refdef.scene.numtempentities = 0; // menu scenes do not use reduced rendering quality oldquality = r_refdef.view.quality; r_refdef.view.quality = 1; // TODO: this needs to be exposed to R_SetView (or something similar) ASAP [2/5/2008 Andreas] r_refdef.scene.time = realtime; // FIXME: this really shouldnt error out lest we have a very broken refdef state...? // or does it kill the server too? PRVM_G_FLOAT(OFS_PARM0) = vid.width; PRVM_G_FLOAT(OFS_PARM1) = vid.height; prog->ExecuteProgram(prog, PRVM_menufunction(m_draw),"m_draw() required"); // TODO: imo this should be moved into scene, too [1/27/2008 Andreas] r_refdef.view.quality = oldquality; R_SelectScene( RST_CLIENT ); } static void MP_ToggleMenu(int mode) { prvm_prog_t *prog = MVM_prog; prog->globals.fp[OFS_PARM0] = (prvm_vec_t) mode; prog->ExecuteProgram(prog, PRVM_menufunction(m_toggle),"m_toggle(float mode) required"); } static void MP_NewMap(void) { prvm_prog_t *prog = MVM_prog; if (PRVM_menufunction(m_newmap)) prog->ExecuteProgram(prog, PRVM_menufunction(m_newmap),"m_newmap() required"); } const serverlist_entry_t *serverlist_callbackentry = NULL; static int MP_GetServerListEntryCategory(const serverlist_entry_t *entry) { prvm_prog_t *prog = MVM_prog; serverlist_callbackentry = entry; if (PRVM_menufunction(m_gethostcachecategory)) { prog->globals.fp[OFS_PARM0] = (prvm_vec_t) -1; prog->ExecuteProgram(prog, PRVM_menufunction(m_gethostcachecategory),"m_gethostcachecategory(float entry) required"); serverlist_callbackentry = NULL; return prog->globals.fp[OFS_RETURN]; } else { return 0; } } static void MP_Shutdown (void) { prvm_prog_t *prog = MVM_prog; if (prog->loaded) prog->ExecuteProgram(prog, PRVM_menufunction(m_shutdown),"m_shutdown() required"); // reset key_dest key_dest = key_game; // AK not using this cause Im not sure whether this is useful at all instead : PRVM_Prog_Reset(prog); } static void MP_Init (void) { prvm_prog_t *prog = MVM_prog; PRVM_Prog_Init(prog); prog->edictprivate_size = 0; // no private struct used prog->name = "menu"; prog->num_edicts = 1; prog->limit_edicts = M_MAX_EDICTS; prog->extensionstring = vm_m_extensions; prog->builtins = vm_m_builtins; prog->numbuiltins = vm_m_numbuiltins; // all callbacks must be defined (pointers are not checked before calling) prog->begin_increase_edicts = MVM_begin_increase_edicts; prog->end_increase_edicts = MVM_end_increase_edicts; prog->init_edict = MVM_init_edict; prog->free_edict = MVM_free_edict; prog->count_edicts = MVM_count_edicts; prog->load_edict = MVM_load_edict; prog->init_cmd = MVM_init_cmd; prog->reset_cmd = MVM_reset_cmd; prog->error_cmd = MVM_error_cmd; prog->ExecuteProgram = MVM_ExecuteProgram; // allocate the mempools prog->progs_mempool = Mem_AllocPool(menu_progs.string, 0, NULL); PRVM_Prog_Load(prog, menu_progs.string, NULL, 0, m_numrequiredfunc, m_required_func, m_numrequiredfields, m_required_fields, m_numrequiredglobals, m_required_globals); // note: OP_STATE is not supported by menu qc, we don't even try to detect // it here in_client_mouse = true; // call the prog init prog->ExecuteProgram(prog, PRVM_menufunction(m_init),"m_init() required"); // Once m_init was called, we consider menuqc code fully initialized. prog->inittime = realtime; } //============================================================================ // Menu router void (*MR_KeyEvent) (int key, int ascii, qboolean downevent); void (*MR_Draw) (void); void (*MR_ToggleMenu) (int mode); void (*MR_Shutdown) (void); void (*MR_NewMap) (void); int (*MR_GetServerListEntryCategory) (const serverlist_entry_t *entry); void MR_SetRouting(qboolean forceold) { // if the menu prog isnt available or forceqmenu ist set, use the old menu if(!FS_FileExists(menu_progs.string) || forceqmenu.integer || forceold) { // set menu router function pointers MR_KeyEvent = M_KeyEvent; MR_Draw = M_Draw; MR_ToggleMenu = M_ToggleMenu; MR_Shutdown = M_Shutdown; MR_NewMap = M_NewMap; MR_GetServerListEntryCategory = M_GetServerListEntryCategory; M_Init(); } else { // set menu router function pointers MR_KeyEvent = MP_KeyEvent; MR_Draw = MP_Draw; MR_ToggleMenu = MP_ToggleMenu; MR_Shutdown = MP_Shutdown; MR_NewMap = MP_NewMap; MR_GetServerListEntryCategory = MP_GetServerListEntryCategory; MP_Init(); } } void MR_Restart(void) { if(MR_Shutdown) MR_Shutdown (); MR_SetRouting (FALSE); } static void Call_MR_ToggleMenu_f(void) { int m; m = ((Cmd_Argc() < 2) ? -1 : atoi(Cmd_Argv(1))); Host_StartVideo(); if(MR_ToggleMenu) MR_ToggleMenu(m); } void MR_Init_Commands(void) { // set router console commands Cvar_RegisterVariable (&forceqmenu); Cvar_RegisterVariable (&menu_options_colorcontrol_correctionvalue); Cvar_RegisterVariable (&menu_progs); Cmd_AddCommand ("menu_restart",MR_Restart, "restart menu system (reloads menu.dat)"); Cmd_AddCommand ("togglemenu", Call_MR_ToggleMenu_f, "opens or closes menu"); } void MR_Init(void) { vid_mode_t res[1024]; size_t res_count, i; res_count = VID_ListModes(res, sizeof(res) / sizeof(*res)); res_count = VID_SortModes(res, res_count, false, false, true); if(res_count) { video_resolutions_count = (int)res_count; video_resolutions = (video_resolution_t *) Mem_Alloc(cls.permanentmempool, sizeof(*video_resolutions) * (video_resolutions_count + 1)); memset(&video_resolutions[video_resolutions_count], 0, sizeof(video_resolutions[video_resolutions_count])); for(i = 0; i < res_count; ++i) { int n, d, t; video_resolutions[i].type = "Detected mode"; // FIXME make this more dynamic video_resolutions[i].width = res[i].width; video_resolutions[i].height = res[i].height; video_resolutions[i].pixelheight = res[i].pixelheight_num / (double) res[i].pixelheight_denom; n = res[i].pixelheight_denom * video_resolutions[i].width; d = res[i].pixelheight_num * video_resolutions[i].height; while(d) { t = n; n = d; d = t % d; } d = (res[i].pixelheight_num * video_resolutions[i].height) / n; n = (res[i].pixelheight_denom * video_resolutions[i].width) / n; switch(n * 0x10000 | d) { case 0x00040003: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 480; video_resolutions[i].type = "Standard 4x3"; break; case 0x00050004: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 512; if(res[i].pixelheight_denom == res[i].pixelheight_num) video_resolutions[i].type = "Square Pixel (LCD) 5x4"; else video_resolutions[i].type = "Short Pixel (CRT) 5x4"; break; case 0x00080005: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 400; if(res[i].pixelheight_denom == res[i].pixelheight_num) video_resolutions[i].type = "Widescreen 8x5"; else video_resolutions[i].type = "Tall Pixel (CRT) 8x5"; break; case 0x00050003: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 384; video_resolutions[i].type = "Widescreen 5x3"; break; case 0x000D0009: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 400; video_resolutions[i].type = "Widescreen 14x9"; break; case 0x00100009: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 480; video_resolutions[i].type = "Widescreen 16x9"; break; case 0x00030002: video_resolutions[i].conwidth = 720; video_resolutions[i].conheight = 480; video_resolutions[i].type = "NTSC 3x2"; break; case 0x000D000B: video_resolutions[i].conwidth = 720; video_resolutions[i].conheight = 566; video_resolutions[i].type = "PAL 14x11"; break; case 0x00080007: if(video_resolutions[i].width >= 512) { video_resolutions[i].conwidth = 512; video_resolutions[i].conheight = 448; video_resolutions[i].type = "SNES 8x7"; } else { video_resolutions[i].conwidth = 256; video_resolutions[i].conheight = 224; video_resolutions[i].type = "NES 8x7"; } break; default: video_resolutions[i].conwidth = 640; video_resolutions[i].conheight = 640 * d / n; video_resolutions[i].type = "Detected mode"; break; } if(video_resolutions[i].conwidth > video_resolutions[i].width || video_resolutions[i].conheight > video_resolutions[i].height) { int f1, f2; f1 = video_resolutions[i].conwidth > video_resolutions[i].width; f2 = video_resolutions[i].conheight > video_resolutions[i].height; if(f1 > f2) { video_resolutions[i].conwidth = video_resolutions[i].width; video_resolutions[i].conheight = video_resolutions[i].conheight / f1; } else { video_resolutions[i].conwidth = video_resolutions[i].conwidth / f2; video_resolutions[i].conheight = video_resolutions[i].height; } } } } else { video_resolutions = video_resolutions_hardcoded; video_resolutions_count = sizeof(video_resolutions_hardcoded) / sizeof(*video_resolutions_hardcoded) - 1; } menu_video_resolutions_forfullscreen = !!vid_fullscreen.integer; M_Menu_Video_FindResolution(vid.width, vid.height, vid_pixelheight.value); // use -forceqmenu to use always the normal quake menu (it sets forceqmenu to 1) // COMMANDLINEOPTION: Client: -forceqmenu disables menu.dat (same as +forceqmenu 1) if(COM_CheckParm("-forceqmenu")) Cvar_SetValueQuick(&forceqmenu,1); // use -useqmenu for debugging proposes, cause it starts // the normal quake menu only the first time // COMMANDLINEOPTION: Client: -useqmenu causes the first time you open the menu to use the quake menu, then reverts to menu.dat (if forceqmenu is 0) if(COM_CheckParm("-useqmenu")) MR_SetRouting (TRUE); else MR_SetRouting (FALSE); } darkplaces/thread_null.c0000664000175000017500000000262313067716222014635 0ustar kalevkalev#include "quakedef.h" #include "thread.h" int Thread_Init(void) { return 0; } void Thread_Shutdown(void) { } qboolean Thread_HasThreads(void) { return false; } void *_Thread_CreateMutex(const char *filename, int fileline) { return NULL; } void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline) { } int _Thread_LockMutex(void *mutex, const char *filename, int fileline) { return -1; } int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline) { return -1; } void *_Thread_CreateCond(const char *filename, int fileline) { return NULL; } void _Thread_DestroyCond(void *cond, const char *filename, int fileline) { } int _Thread_CondSignal(void *cond, const char *filename, int fileline) { return -1; } int _Thread_CondBroadcast(void *cond, const char *filename, int fileline) { return -1; } int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline) { return -1; } void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline) { return NULL; } int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline) { return retval; } void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) { return NULL; } void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) { } void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) { } darkplaces/darkplaces-sdl2-vs2012.vcxproj0000664000175000017500000004310013067716220017504 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51} darkplacessdl2 Win32Proj darkplaces-sdl2-vs2012 Application v110 MultiByte true Application v110 MultiByte Application v110 MultiByte true Application v110 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 darkplaces/libcurl.h0000664000175000017500000000363413067716220013776 0ustar kalevkalevenum { CURLCBSTATUS_OK = 0, CURLCBSTATUS_FAILED = -1, // failed for generic reason (e.g. buffer too small) CURLCBSTATUS_ABORTED = -2, // aborted by curl --cancel CURLCBSTATUS_SERVERERROR = -3, // only used if no HTTP status code is available CURLCBSTATUS_UNKNOWN = -4 // should never happen }; typedef void (*curl_callback_t) (int status, size_t length_received, unsigned char *buffer, void *cbdata); // code is one of the CURLCBSTATUS constants, or the HTTP error code (when > 0). void Curl_Run(void); qboolean Curl_Running(void); qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qboolean forthismap); qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); void Curl_Cancel_ToMemory(curl_callback_t callback, void* cbdata); void Curl_Init(void); void Curl_Init_Commands(void); void Curl_Shutdown(void); void Curl_CancelAll(void); void Curl_Clear_forthismap(void); qboolean Curl_Have_forthismap(void); void Curl_Register_predownload(void); void Curl_ClearRequirements(void); void Curl_RequireFile(const char *filename); void Curl_SendRequirements(void); typedef struct Curl_downloadinfo_s { char filename[MAX_QPATH]; double progress; double speed; qboolean queued; } Curl_downloadinfo_t; Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength); // this may and should be Z_Free()ed // the result is actually an array // an additional info string may be returned in additional_info as a // pointer to a static string (but the argument may be NULL if the caller // does not care) darkplaces/image.c0000664000175000017500000013037413067716220013421 0ustar kalevkalev #include "quakedef.h" #include "image.h" #include "jpeg.h" #include "image_png.h" #include "r_shadow.h" int image_width; int image_height; static void Image_CopyAlphaFromBlueBGRA(unsigned char *outpixels, const unsigned char *inpixels, int w, int h) { int i, n; n = w * h; for(i = 0; i < n; ++i) outpixels[4*i+3] = inpixels[4*i]; // blue channel } #if 1 // written by LordHavoc in a readable way, optimized by Vic, further optimized by LordHavoc (the non-special index case), readable version preserved below this void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) { int index, c, x, y; const unsigned char *in, *line; int row_inc = (inputflipy ? -inputwidth : inputwidth) * numinputcomponents, col_inc = (inputflipx ? -1 : 1) * numinputcomponents; int row_ofs = (inputflipy ? (inputheight - 1) * inputwidth * numinputcomponents : 0), col_ofs = (inputflipx ? (inputwidth - 1) * numinputcomponents : 0); for (c = 0; c < numoutputcomponents; c++) if (outputinputcomponentindices[c] & 0x80000000) break; if (c < numoutputcomponents) { // special indices used if (inputflipdiagonal) { for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; } else { for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = ((index = outputinputcomponentindices[c]) & 0x80000000) ? index : in[index]; } } else { // special indices not used if (inputflipdiagonal) { for (x = 0, line = inpixels + col_ofs; x < inputwidth; x++, line += col_inc) for (y = 0, in = line + row_ofs; y < inputheight; y++, in += row_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = in[outputinputcomponentindices[c]]; } else { for (y = 0, line = inpixels + row_ofs; y < inputheight; y++, line += row_inc) for (x = 0, in = line + col_ofs; x < inputwidth; x++, in += col_inc, outpixels += numoutputcomponents) for (c = 0; c < numoutputcomponents; c++) outpixels[c] = in[outputinputcomponentindices[c]]; } } } #else // intentionally readable version void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices) { int index, c, x, y; const unsigned char *in, *inrow, *incolumn; if (inputflipdiagonal) { for (x = 0;x < inputwidth;x++) { for (y = 0;y < inputheight;y++) { in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; for (c = 0;c < numoutputcomponents;c++) { index = outputinputcomponentindices[c]; if (index & 0x80000000) *outpixels++ = index; else *outpixels++ = in[index]; } } } } else { for (y = 0;y < inputheight;y++) { for (x = 0;x < inputwidth;x++) { in = inpixels + ((inputflipy ? inputheight - 1 - y : y) * inputwidth + (inputflipx ? inputwidth - 1 - x : x)) * numinputcomponents; for (c = 0;c < numoutputcomponents;c++) { index = outputinputcomponentindices[c]; if (index & 0x80000000) *outpixels++ = index; else *outpixels++ = in[index]; } } } } } #endif void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab) { while (pixels--) { out[0] = gammar[in[0]]; out[1] = gammag[in[1]]; out[2] = gammab[in[2]]; in += 3; out += 3; } } // note: pal must be 32bit color void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal) { int *iout = (int *)out; while (pixels >= 8) { iout[0] = pal[in[0]]; iout[1] = pal[in[1]]; iout[2] = pal[in[2]]; iout[3] = pal[in[3]]; iout[4] = pal[in[4]]; iout[5] = pal[in[5]]; iout[6] = pal[in[6]]; iout[7] = pal[in[7]]; in += 8; iout += 8; pixels -= 8; } if (pixels & 4) { iout[0] = pal[in[0]]; iout[1] = pal[in[1]]; iout[2] = pal[in[2]]; iout[3] = pal[in[3]]; in += 4; iout += 4; } if (pixels & 2) { iout[0] = pal[in[0]]; iout[1] = pal[in[1]]; in += 2; iout += 2; } if (pixels & 1) iout[0] = pal[in[0]]; } /* ================================================================= PCX Loading ================================================================= */ typedef struct pcx_s { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin,ymin,xmax,ymax; unsigned short hres,vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; } pcx_t; /* ============ LoadPCX ============ */ static unsigned char* LoadPCX_BGRA (const unsigned char *f, int filesize, int *miplevel) { pcx_t pcx; unsigned char *a, *b, *image_buffer, *pbuf; const unsigned char *palette, *fin, *enddata; int x, y, x2, dataByte; if (filesize < (int)sizeof(pcx) + 768) { Con_Print("Bad pcx file\n"); return NULL; } fin = f; memcpy(&pcx, fin, sizeof(pcx)); fin += sizeof(pcx); // LordHavoc: big-endian support ported from QF newtree pcx.xmax = LittleShort (pcx.xmax); pcx.xmin = LittleShort (pcx.xmin); pcx.ymax = LittleShort (pcx.ymax); pcx.ymin = LittleShort (pcx.ymin); pcx.hres = LittleShort (pcx.hres); pcx.vres = LittleShort (pcx.vres); pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); pcx.palette_type = LittleShort (pcx.palette_type); image_width = pcx.xmax + 1 - pcx.xmin; image_height = pcx.ymax + 1 - pcx.ymin; if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) { Con_Print("Bad pcx file\n"); return NULL; } palette = f + filesize - 768; image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); if (!image_buffer) { Con_Printf("LoadPCX: not enough memory for %i by %i image\n", image_width, image_height); return NULL; } pbuf = image_buffer + image_width*image_height*3; enddata = palette; for (y = 0;y < image_height && fin < enddata;y++) { a = pbuf + y * image_width; for (x = 0;x < image_width && fin < enddata;) { dataByte = *fin++; if(dataByte >= 0xC0) { if (fin >= enddata) break; x2 = x + (dataByte & 0x3F); dataByte = *fin++; if (x2 > image_width) x2 = image_width; // technically an error while(x < x2) a[x++] = dataByte; } else a[x++] = dataByte; } while(x < image_width) a[x++] = 0; } a = image_buffer; b = pbuf; for(x = 0;x < image_width*image_height;x++) { y = *b++ * 3; *a++ = palette[y+2]; *a++ = palette[y+1]; *a++ = palette[y]; *a++ = 255; } return image_buffer; } /* ============ LoadPCX ============ */ qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight) { pcx_t pcx; unsigned char *a; const unsigned char *fin, *enddata; int x, y, x2, dataByte, pcxwidth, pcxheight; if (filesize < (int)sizeof(pcx) + 768) return false; image_width = outwidth; image_height = outheight; fin = f; memcpy(&pcx, fin, sizeof(pcx)); fin += sizeof(pcx); // LordHavoc: big-endian support ported from QF newtree pcx.xmax = LittleShort (pcx.xmax); pcx.xmin = LittleShort (pcx.xmin); pcx.ymax = LittleShort (pcx.ymax); pcx.ymin = LittleShort (pcx.ymin); pcx.hres = LittleShort (pcx.hres); pcx.vres = LittleShort (pcx.vres); pcx.bytes_per_line = LittleShort (pcx.bytes_per_line); pcx.palette_type = LittleShort (pcx.palette_type); pcxwidth = pcx.xmax + 1 - pcx.xmin; pcxheight = pcx.ymax + 1 - pcx.ymin; if (pcx.manufacturer != 0x0a || pcx.version != 5 || pcx.encoding != 1 || pcx.bits_per_pixel != 8 || pcxwidth > 4096 || pcxheight > 4096 || pcxwidth <= 0 || pcxheight <= 0) return false; enddata = f + filesize - 768; for (y = 0;y < outheight && fin < enddata;y++) { a = pixels + y * outwidth; // pad the output with blank lines if needed if (y >= pcxheight) { memset(a, 0, outwidth); continue; } for (x = 0;x < pcxwidth;) { if (fin >= enddata) return false; dataByte = *fin++; if(dataByte >= 0xC0) { x2 = x + (dataByte & 0x3F); if (fin >= enddata) return false; if (x2 > pcxwidth) return false; dataByte = *fin++; for (;x < x2;x++) if (x < outwidth) a[x] = dataByte; } else { if (x < outwidth) // truncate to destination width a[x] = dataByte; x++; } } while(x < outwidth) a[x++] = 0; } return true; } /* ============ LoadPCX ============ */ qboolean LoadPCX_PaletteOnly(const unsigned char *f, int filesize, unsigned char *palette768b) { if (filesize < 768) return false; memcpy(palette768b, f + filesize - 768, 768); return true; } /* ========================================================= TARGA LOADING ========================================================= */ typedef struct _TargaHeader { unsigned char id_length, colormap_type, image_type; unsigned short colormap_index, colormap_length; unsigned char colormap_size; unsigned short x_origin, y_origin, width, height; unsigned char pixel_size, attributes; } TargaHeader; static void PrintTargaHeader(TargaHeader *t) { Con_Printf("TargaHeader:\nuint8 id_length = %i;\nuint8 colormap_type = %i;\nuint8 image_type = %i;\nuint16 colormap_index = %i;\nuint16 colormap_length = %i;\nuint8 colormap_size = %i;\nuint16 x_origin = %i;\nuint16 y_origin = %i;\nuint16 width = %i;\nuint16 height = %i;\nuint8 pixel_size = %i;\nuint8 attributes = %i;\n", t->id_length, t->colormap_type, t->image_type, t->colormap_index, t->colormap_length, t->colormap_size, t->x_origin, t->y_origin, t->width, t->height, t->pixel_size, t->attributes); } /* ============= LoadTGA ============= */ unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel) { int x, y, pix_inc, row_inci, runlen, alphabits; unsigned char *image_buffer; unsigned int *pixbufi; const unsigned char *fin, *enddata; TargaHeader targa_header; unsigned int palettei[256]; union { unsigned int i; unsigned char b[4]; } bgra; if (filesize < 19) return NULL; enddata = f + filesize; targa_header.id_length = f[0]; targa_header.colormap_type = f[1]; targa_header.image_type = f[2]; targa_header.colormap_index = f[3] + f[4] * 256; targa_header.colormap_length = f[5] + f[6] * 256; targa_header.colormap_size = f[7]; targa_header.x_origin = f[8] + f[9] * 256; targa_header.y_origin = f[10] + f[11] * 256; targa_header.width = image_width = f[12] + f[13] * 256; targa_header.height = image_height = f[14] + f[15] * 256; targa_header.pixel_size = f[16]; targa_header.attributes = f[17]; if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) { Con_Print("LoadTGA: invalid size\n"); PrintTargaHeader(&targa_header); return NULL; } memset(palettei, 0, sizeof(palettei)); // advance to end of header fin = f + 18; // skip TARGA image comment (usually 0 bytes) fin += targa_header.id_length; // read/skip the colormap if present (note: according to the TARGA spec it // can be present even on truecolor or greyscale images, just not used by // the image data) if (targa_header.colormap_type) { if (targa_header.colormap_length > 256) { Con_Print("LoadTGA: only up to 256 colormap_length supported\n"); PrintTargaHeader(&targa_header); return NULL; } if (targa_header.colormap_index) { Con_Print("LoadTGA: colormap_index not supported\n"); PrintTargaHeader(&targa_header); return NULL; } if (targa_header.colormap_size == 24) { for (x = 0;x < targa_header.colormap_length;x++) { bgra.b[0] = *fin++; bgra.b[1] = *fin++; bgra.b[2] = *fin++; bgra.b[3] = 255; palettei[x] = bgra.i; } } else if (targa_header.colormap_size == 32) { memcpy(palettei, fin, targa_header.colormap_length*4); fin += targa_header.colormap_length * 4; } else { Con_Print("LoadTGA: Only 32 and 24 bit colormap_size supported\n"); PrintTargaHeader(&targa_header); return NULL; } } // check our pixel_size restrictions according to image_type switch (targa_header.image_type & ~8) { case 2: if (targa_header.pixel_size != 24 && targa_header.pixel_size != 32) { Con_Print("LoadTGA: only 24bit and 32bit pixel sizes supported for type 2 and type 10 images\n"); PrintTargaHeader(&targa_header); return NULL; } break; case 3: // set up a palette to make the loader easier for (x = 0;x < 256;x++) { bgra.b[0] = bgra.b[1] = bgra.b[2] = x; bgra.b[3] = 255; palettei[x] = bgra.i; } // fall through to colormap case case 1: if (targa_header.pixel_size != 8) { Con_Print("LoadTGA: only 8bit pixel size for type 1, 3, 9, and 11 images supported\n"); PrintTargaHeader(&targa_header); return NULL; } break; default: Con_Printf("LoadTGA: Only type 1, 2, 3, 9, 10, and 11 targa RGB images supported, image_type = %i\n", targa_header.image_type); PrintTargaHeader(&targa_header); return NULL; } if (targa_header.attributes & 0x10) { Con_Print("LoadTGA: origin must be in top left or bottom left, top right and bottom right are not supported\n"); return NULL; } // number of attribute bits per pixel, we only support 0 or 8 alphabits = targa_header.attributes & 0x0F; if (alphabits != 8 && alphabits != 0) { Con_Print("LoadTGA: only 0 or 8 attribute (alpha) bits supported\n"); return NULL; } image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); if (!image_buffer) { Con_Printf("LoadTGA: not enough memory for %i by %i image\n", image_width, image_height); return NULL; } // If bit 5 of attributes isn't set, the image has been stored from bottom to top if ((targa_header.attributes & 0x20) == 0) { pixbufi = (unsigned int*)image_buffer + (image_height - 1)*image_width; row_inci = -image_width*2; } else { pixbufi = (unsigned int*)image_buffer; row_inci = 0; } pix_inc = 1; if ((targa_header.image_type & ~8) == 2) pix_inc = (targa_header.pixel_size + 7) / 8; switch (targa_header.image_type) { case 1: // colormapped, uncompressed case 3: // greyscale, uncompressed if (fin + image_width * image_height * pix_inc > enddata) break; for (y = 0;y < image_height;y++, pixbufi += row_inci) for (x = 0;x < image_width;x++) *pixbufi++ = palettei[*fin++]; break; case 2: // BGR or BGRA, uncompressed if (fin + image_width * image_height * pix_inc > enddata) break; if (targa_header.pixel_size == 32 && alphabits) { for (y = 0;y < image_height;y++) memcpy(pixbufi + y * (image_width + row_inci), fin + y * image_width * pix_inc, image_width*4); } else { for (y = 0;y < image_height;y++, pixbufi += row_inci) { for (x = 0;x < image_width;x++, fin += pix_inc) { bgra.b[0] = fin[0]; bgra.b[1] = fin[1]; bgra.b[2] = fin[2]; bgra.b[3] = 255; *pixbufi++ = bgra.i; } } } break; case 9: // colormapped, RLE case 11: // greyscale, RLE for (y = 0;y < image_height;y++, pixbufi += row_inci) { for (x = 0;x < image_width;) { if (fin >= enddata) break; // error - truncated file runlen = *fin++; if (runlen & 0x80) { // RLE - all pixels the same color runlen += 1 - 0x80; if (fin + pix_inc > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width bgra.i = palettei[*fin++]; for (;runlen--;x++) *pixbufi++ = bgra.i; } else { // uncompressed - all pixels different color runlen++; if (fin + pix_inc * runlen > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width for (;runlen--;x++) *pixbufi++ = palettei[*fin++]; } } if (x != image_width) { // pixbufi is useless now Con_Printf("LoadTGA: corrupt file\n"); break; } } break; case 10: // BGR or BGRA, RLE if (targa_header.pixel_size == 32 && alphabits) { for (y = 0;y < image_height;y++, pixbufi += row_inci) { for (x = 0;x < image_width;) { if (fin >= enddata) break; // error - truncated file runlen = *fin++; if (runlen & 0x80) { // RLE - all pixels the same color runlen += 1 - 0x80; if (fin + pix_inc > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width bgra.b[0] = fin[0]; bgra.b[1] = fin[1]; bgra.b[2] = fin[2]; bgra.b[3] = fin[3]; fin += pix_inc; for (;runlen--;x++) *pixbufi++ = bgra.i; } else { // uncompressed - all pixels different color runlen++; if (fin + pix_inc * runlen > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width for (;runlen--;x++) { bgra.b[0] = fin[0]; bgra.b[1] = fin[1]; bgra.b[2] = fin[2]; bgra.b[3] = fin[3]; fin += pix_inc; *pixbufi++ = bgra.i; } } } if (x != image_width) { // pixbufi is useless now Con_Printf("LoadTGA: corrupt file\n"); break; } } } else { for (y = 0;y < image_height;y++, pixbufi += row_inci) { for (x = 0;x < image_width;) { if (fin >= enddata) break; // error - truncated file runlen = *fin++; if (runlen & 0x80) { // RLE - all pixels the same color runlen += 1 - 0x80; if (fin + pix_inc > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width bgra.b[0] = fin[0]; bgra.b[1] = fin[1]; bgra.b[2] = fin[2]; bgra.b[3] = 255; fin += pix_inc; for (;runlen--;x++) *pixbufi++ = bgra.i; } else { // uncompressed - all pixels different color runlen++; if (fin + pix_inc * runlen > enddata) break; // error - truncated file if (x + runlen > image_width) break; // error - line exceeds width for (;runlen--;x++) { bgra.b[0] = fin[0]; bgra.b[1] = fin[1]; bgra.b[2] = fin[2]; bgra.b[3] = 255; fin += pix_inc; *pixbufi++ = bgra.i; } } } if (x != image_width) { // pixbufi is useless now Con_Printf("LoadTGA: corrupt file\n"); break; } } } break; default: // unknown image_type break; } return image_buffer; } typedef struct q2wal_s { char name[32]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored char animname[32]; // next frame in animation chain int flags; int contents; int value; } q2wal_t; static unsigned char *LoadWAL_BGRA (const unsigned char *f, int filesize, int *miplevel) { unsigned char *image_buffer; const q2wal_t *inwal = (const q2wal_t *)f; if (filesize < (int) sizeof(q2wal_t)) { Con_Print("LoadWAL: invalid WAL file\n"); return NULL; } image_width = LittleLong(inwal->width); image_height = LittleLong(inwal->height); if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) { Con_Printf("LoadWAL: invalid size %ix%i\n", image_width, image_height); return NULL; } if (filesize < (int) LittleLong(inwal->offsets[0]) + image_width * image_height) { Con_Print("LoadWAL: invalid WAL file\n"); return NULL; } image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); if (!image_buffer) { Con_Printf("LoadWAL: not enough memory for %i by %i image\n", image_width, image_height); return NULL; } Image_Copy8bitBGRA(f + LittleLong(inwal->offsets[0]), image_buffer, image_width * image_height, q2palette_bgra_complete); return image_buffer; } qboolean LoadWAL_GetMetadata(const unsigned char *f, int filesize, int *retwidth, int *retheight, int *retflags, int *retvalue, int *retcontents, char *retanimname32c) { const q2wal_t *inwal = (const q2wal_t *)f; if (filesize < (int) sizeof(q2wal_t)) { Con_Print("LoadWAL: invalid WAL file\n"); if (retwidth) *retwidth = 16; if (retheight) *retheight = 16; if (retflags) *retflags = 0; if (retvalue) *retvalue = 0; if (retcontents) *retcontents = 0; if (retanimname32c) memset(retanimname32c, 0, 32); return false; } if (retwidth) *retwidth = LittleLong(inwal->width); if (retheight) *retheight = LittleLong(inwal->height); if (retflags) *retflags = LittleLong(inwal->flags); if (retvalue) *retvalue = LittleLong(inwal->value); if (retcontents) *retcontents = LittleLong(inwal->contents); if (retanimname32c) { memcpy(retanimname32c, inwal->animname, 32); retanimname32c[31] = 0; } return true; } void Image_StripImageExtension (const char *in, char *out, size_t size_out) { const char *ext; if (size_out == 0) return; ext = FS_FileExtension(in); if (ext && (!strcmp(ext, "tga") || !strcmp(ext, "pcx") || !strcmp(ext, "lmp") || !strcmp(ext, "png") || !strcmp(ext, "jpg") || !strcmp(ext, "wal"))) FS_StripExtension(in, out, size_out); else strlcpy(out, in, size_out); } static unsigned char image_linearfromsrgb[256]; static unsigned char image_srgbfromlinear_lightmap[256]; void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels) { int i; // this math from http://www.opengl.org/registry/specs/EXT/texture_sRGB.txt if (!image_linearfromsrgb[255]) for (i = 0;i < 256;i++) image_linearfromsrgb[i] = (unsigned char)floor(Image_LinearFloatFromsRGB(i) * 255.0f + 0.5f); for (i = 0;i < numpixels;i++) { pout[i*4+0] = image_linearfromsrgb[pin[i*4+0]]; pout[i*4+1] = image_linearfromsrgb[pin[i*4+1]]; pout[i*4+2] = image_linearfromsrgb[pin[i*4+2]]; pout[i*4+3] = pin[i*4+3]; } } void Image_MakesRGBColorsFromLinear_Lightmap(unsigned char *pout, const unsigned char *pin, int numpixels) { int i; // this math from http://www.opengl.org/registry/specs/EXT/texture_sRGB.txt if (!image_srgbfromlinear_lightmap[255]) for (i = 0;i < 256;i++) image_srgbfromlinear_lightmap[i] = (unsigned char)floor(bound(0.0f, Image_sRGBFloatFromLinear_Lightmap(i), 1.0f) * 255.0f + 0.5f); for (i = 0;i < numpixels;i++) { pout[i*4+0] = image_srgbfromlinear_lightmap[pin[i*4+0]]; pout[i*4+1] = image_srgbfromlinear_lightmap[pin[i*4+1]]; pout[i*4+2] = image_srgbfromlinear_lightmap[pin[i*4+2]]; pout[i*4+3] = pin[i*4+3]; } } typedef struct imageformat_s { const char *formatstring; unsigned char *(*loadfunc)(const unsigned char *f, int filesize, int *miplevel); } imageformat_t; // GAME_TENEBRAE only imageformat_t imageformats_tenebrae[] = { {"override/%s.tga", LoadTGA_BGRA}, {"override/%s.png", PNG_LoadImage_BGRA}, {"override/%s.jpg", JPEG_LoadImage_BGRA}, {"override/%s.pcx", LoadPCX_BGRA}, {"%s.tga", LoadTGA_BGRA}, {"%s.png", PNG_LoadImage_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"%s.pcx", LoadPCX_BGRA}, {NULL, NULL} }; imageformat_t imageformats_nopath[] = { {"override/%s.tga", LoadTGA_BGRA}, {"override/%s.png", PNG_LoadImage_BGRA}, {"override/%s.jpg", JPEG_LoadImage_BGRA}, {"textures/%s.tga", LoadTGA_BGRA}, {"textures/%s.png", PNG_LoadImage_BGRA}, {"textures/%s.jpg", JPEG_LoadImage_BGRA}, {"%s.tga", LoadTGA_BGRA}, {"%s.png", PNG_LoadImage_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"%s.pcx", LoadPCX_BGRA}, {NULL, NULL} }; // GAME_DELUXEQUAKE only // VorteX: the point why i use such messy texture paths is // that GtkRadiant can't detect normal/gloss textures // and exclude them from texture browser // so i just use additional folder to store this textures imageformat_t imageformats_dq[] = { {"%s.tga", LoadTGA_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"texturemaps/%s.tga", LoadTGA_BGRA}, {"texturemaps/%s.jpg", JPEG_LoadImage_BGRA}, {NULL, NULL} }; imageformat_t imageformats_textures[] = { {"%s.tga", LoadTGA_BGRA}, {"%s.png", PNG_LoadImage_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"%s.pcx", LoadPCX_BGRA}, {"%s.wal", LoadWAL_BGRA}, {NULL, NULL} }; imageformat_t imageformats_gfx[] = { {"%s.tga", LoadTGA_BGRA}, {"%s.png", PNG_LoadImage_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"%s.pcx", LoadPCX_BGRA}, {NULL, NULL} }; imageformat_t imageformats_other[] = { {"%s.tga", LoadTGA_BGRA}, {"%s.png", PNG_LoadImage_BGRA}, {"%s.jpg", JPEG_LoadImage_BGRA}, {"%s.pcx", LoadPCX_BGRA}, {NULL, NULL} }; int fixtransparentpixels(unsigned char *data, int w, int h); unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel) { fs_offset_t filesize; imageformat_t *firstformat, *format; unsigned char *f, *data = NULL, *data2 = NULL; char basename[MAX_QPATH], name[MAX_QPATH], name2[MAX_QPATH], *c; char vabuf[1024]; //if (developer_memorydebug.integer) // Mem_CheckSentinelsGlobal(); if (developer_texturelogging.integer) Log_Printf("textures.log", "%s\n", filename); Image_StripImageExtension(filename, basename, sizeof(basename)); // strip filename extensions to allow replacement by other types // replace *'s with #, so commandline utils don't get confused when dealing with the external files for (c = basename;*c;c++) if (*c == '*') *c = '#'; name[0] = 0; if (strchr(basename, '/')) { int i; for (i = 0;i < (int)sizeof(name)-1 && basename[i] != '/';i++) name[i] = basename[i]; name[i] = 0; } if (gamemode == GAME_TENEBRAE) firstformat = imageformats_tenebrae; else if (gamemode == GAME_DELUXEQUAKE) firstformat = imageformats_dq; else if (!strcasecmp(name, "textures")) firstformat = imageformats_textures; else if (!strcasecmp(name, "gfx")) firstformat = imageformats_gfx; else if (!strchr(basename, '/')) firstformat = imageformats_nopath; else firstformat = imageformats_other; // now try all the formats in the selected list for (format = firstformat;format->formatstring;format++) { dpsnprintf (name, sizeof(name), format->formatstring, basename); f = FS_LoadFile(name, tempmempool, true, &filesize); if (f) { int mymiplevel = miplevel ? *miplevel : 0; image_width = 0; image_height = 0; data = format->loadfunc(f, (int)filesize, &mymiplevel); Mem_Free(f); if (data) { if(format->loadfunc == JPEG_LoadImage_BGRA) // jpeg can't do alpha, so let's simulate it by loading another jpeg { dpsnprintf (name2, sizeof(name2), format->formatstring, va(vabuf, sizeof(vabuf), "%s_alpha", basename)); f = FS_LoadFile(name2, tempmempool, true, &filesize); if(f) { int mymiplevel2 = miplevel ? *miplevel : 0; int image_width_save = image_width; int image_height_save = image_height; data2 = format->loadfunc(f, (int)filesize, &mymiplevel2); if(data2 && mymiplevel == mymiplevel2 && image_width == image_width_save && image_height == image_height_save) Image_CopyAlphaFromBlueBGRA(data, data2, image_width, image_height); else Con_Printf("loadimagepixelsrgba: corrupt or invalid alpha image %s_alpha\n", basename); image_width = image_width_save; image_height = image_height_save; if(data2) Mem_Free(data2); Mem_Free(f); } } if (developer_loading.integer) Con_DPrintf("loaded image %s (%dx%d)\n", name, image_width, image_height); if(miplevel) *miplevel = mymiplevel; //if (developer_memorydebug.integer) // Mem_CheckSentinelsGlobal(); if(allowFixtrans && r_fixtrans_auto.integer) { int n = fixtransparentpixels(data, image_width, image_height); if(n) { Con_Printf("- had to fix %s (%d pixels changed)\n", name, n); if(r_fixtrans_auto.integer >= 2) { char outfilename[MAX_QPATH], buf[MAX_QPATH]; Image_StripImageExtension(name, buf, sizeof(buf)); dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); Image_WriteTGABGRA(outfilename, image_width, image_height, data); Con_Printf("- %s written.\n", outfilename); } } } if (convertsRGB) Image_MakeLinearColorsFromsRGB(data, data, image_width * image_height); return data; } else Con_DPrintf("Error loading image %s (file loaded but decode failed)\n", name); } } if (complain) { Con_Printf("Couldn't load %s using ", filename); for (format = firstformat;format->formatstring;format++) { dpsnprintf (name, sizeof(name), format->formatstring, basename); Con_Printf(format == firstformat ? "\"%s\"" : (format[1].formatstring ? ", \"%s\"" : " or \"%s\".\n"), format->formatstring); } } // texture loading can take a while, so make sure we're sending keepalives CL_KeepaliveMessage(false); //if (developer_memorydebug.integer) // Mem_CheckSentinelsGlobal(); return NULL; } extern cvar_t gl_picmip; rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB) { unsigned char *data; rtexture_t *rt; int miplevel = R_PicmipForFlags(flags); if (!(data = loadimagepixelsbgra (filename, complain, allowFixtrans, false, &miplevel))) return 0; rt = R_LoadTexture2D(pool, filename, image_width, image_height, data, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, flags, miplevel, NULL); Mem_Free(data); return rt; } int fixtransparentpixels(unsigned char *data, int w, int h) { int const FIXTRANS_NEEDED = 1; int const FIXTRANS_HAS_L = 2; int const FIXTRANS_HAS_R = 4; int const FIXTRANS_HAS_U = 8; int const FIXTRANS_HAS_D = 16; int const FIXTRANS_FIXED = 32; unsigned char *fixMask = (unsigned char *) Mem_Alloc(tempmempool, w * h); int fixPixels = 0; int changedPixels = 0; int x, y; #define FIXTRANS_PIXEL (y*w+x) #define FIXTRANS_PIXEL_U (((y+h-1)%h)*w+x) #define FIXTRANS_PIXEL_D (((y+1)%h)*w+x) #define FIXTRANS_PIXEL_L (y*w+((x+w-1)%w)) #define FIXTRANS_PIXEL_R (y*w+((x+1)%w)) memset(fixMask, 0, w * h); for(y = 0; y < h; ++y) for(x = 0; x < w; ++x) { if(data[FIXTRANS_PIXEL * 4 + 3] == 0) { fixMask[FIXTRANS_PIXEL] |= FIXTRANS_NEEDED; ++fixPixels; } else { fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; } } if(fixPixels == w * h) return 0; // sorry, can't do anything about this while(fixPixels) { for(y = 0; y < h; ++y) for(x = 0; x < w; ++x) if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_NEEDED) { unsigned int sumR = 0, sumG = 0, sumB = 0, sumA = 0, sumRA = 0, sumGA = 0, sumBA = 0, cnt = 0; unsigned char r, g, b, a, r0, g0, b0; if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_U) { r = data[FIXTRANS_PIXEL_U * 4 + 2]; g = data[FIXTRANS_PIXEL_U * 4 + 1]; b = data[FIXTRANS_PIXEL_U * 4 + 0]; a = data[FIXTRANS_PIXEL_U * 4 + 3]; sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; } if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_D) { r = data[FIXTRANS_PIXEL_D * 4 + 2]; g = data[FIXTRANS_PIXEL_D * 4 + 1]; b = data[FIXTRANS_PIXEL_D * 4 + 0]; a = data[FIXTRANS_PIXEL_D * 4 + 3]; sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; } if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_L) { r = data[FIXTRANS_PIXEL_L * 4 + 2]; g = data[FIXTRANS_PIXEL_L * 4 + 1]; b = data[FIXTRANS_PIXEL_L * 4 + 0]; a = data[FIXTRANS_PIXEL_L * 4 + 3]; sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; } if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_HAS_R) { r = data[FIXTRANS_PIXEL_R * 4 + 2]; g = data[FIXTRANS_PIXEL_R * 4 + 1]; b = data[FIXTRANS_PIXEL_R * 4 + 0]; a = data[FIXTRANS_PIXEL_R * 4 + 3]; sumR += r; sumG += g; sumB += b; sumA += a; sumRA += r*a; sumGA += g*a; sumBA += b*a; ++cnt; } if(!cnt) continue; r0 = data[FIXTRANS_PIXEL * 4 + 2]; g0 = data[FIXTRANS_PIXEL * 4 + 1]; b0 = data[FIXTRANS_PIXEL * 4 + 0]; if(sumA) { // there is a surrounding non-alpha pixel r = (sumRA + sumA / 2) / sumA; g = (sumGA + sumA / 2) / sumA; b = (sumBA + sumA / 2) / sumA; } else { // need to use a "regular" average r = (sumR + cnt / 2) / cnt; g = (sumG + cnt / 2) / cnt; b = (sumB + cnt / 2) / cnt; } if(r != r0 || g != g0 || b != b0) ++changedPixels; data[FIXTRANS_PIXEL * 4 + 2] = r; data[FIXTRANS_PIXEL * 4 + 1] = g; data[FIXTRANS_PIXEL * 4 + 0] = b; fixMask[FIXTRANS_PIXEL] |= FIXTRANS_FIXED; } for(y = 0; y < h; ++y) for(x = 0; x < w; ++x) if(fixMask[FIXTRANS_PIXEL] & FIXTRANS_FIXED) { fixMask[FIXTRANS_PIXEL] &= ~(FIXTRANS_NEEDED | FIXTRANS_FIXED); fixMask[FIXTRANS_PIXEL_D] |= FIXTRANS_HAS_U; fixMask[FIXTRANS_PIXEL_U] |= FIXTRANS_HAS_D; fixMask[FIXTRANS_PIXEL_R] |= FIXTRANS_HAS_L; fixMask[FIXTRANS_PIXEL_L] |= FIXTRANS_HAS_R; --fixPixels; } } return changedPixels; } void Image_FixTransparentPixels_f(void) { const char *filename, *filename_pattern; fssearch_t *search; int i, n; char outfilename[MAX_QPATH], buf[MAX_QPATH]; unsigned char *data; if(Cmd_Argc() != 2) { Con_Printf("Usage: %s imagefile\n", Cmd_Argv(0)); return; } filename_pattern = Cmd_Argv(1); search = FS_Search(filename_pattern, true, true); if(!search) return; for(i = 0; i < search->numfilenames; ++i) { filename = search->filenames[i]; Con_Printf("Processing %s... ", filename); Image_StripImageExtension(filename, buf, sizeof(buf)); dpsnprintf(outfilename, sizeof(outfilename), "fixtrans/%s.tga", buf); if(!(data = loadimagepixelsbgra(filename, true, false, false, NULL))) return; if((n = fixtransparentpixels(data, image_width, image_height))) { Image_WriteTGABGRA(outfilename, image_width, image_height, data); Con_Printf("%s written (%d pixels changed).\n", outfilename, n); } else Con_Printf("unchanged.\n"); Mem_Free(data); } FS_FreeSearch(search); } qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data) { qboolean ret; unsigned char buffer[18]; const void *buffers[2]; fs_offset_t sizes[2]; memset (buffer, 0, 18); buffer[2] = 2; // uncompressed type buffer[12] = (width >> 0) & 0xFF; buffer[13] = (width >> 8) & 0xFF; buffer[14] = (height >> 0) & 0xFF; buffer[15] = (height >> 8) & 0xFF; buffer[16] = 24; // pixel size buffers[0] = buffer; sizes[0] = 18; buffers[1] = data; sizes[1] = width*height*3; ret = FS_WriteFileInBlocks(filename, buffers, sizes, 2); return ret; } qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data) { int y; unsigned char *buffer, *out; const unsigned char *in, *end; qboolean ret; buffer = (unsigned char *)Mem_Alloc(tempmempool, width*height*4 + 18); memset (buffer, 0, 18); buffer[2] = 2; // uncompressed type buffer[12] = (width >> 0) & 0xFF; buffer[13] = (width >> 8) & 0xFF; buffer[14] = (height >> 0) & 0xFF; buffer[15] = (height >> 8) & 0xFF; for (y = 3;y < width*height*4;y += 4) if (data[y] < 255) break; if (y < width*height*4) { // save the alpha channel buffer[16] = 32; // pixel size buffer[17] = 8; // 8 bits of alpha // flip upside down out = buffer + 18; for (y = height - 1;y >= 0;y--) { memcpy(out, data + y * width * 4, width * 4); out += width*4; } } else { // save only the color channels buffer[16] = 24; // pixel size buffer[17] = 0; // 8 bits of alpha // truncate bgra to bgr and flip upside down out = buffer + 18; for (y = height - 1;y >= 0;y--) { in = data + y * width * 4; end = in + width * 4; for (;in < end;in += 4) { *out++ = in[0]; *out++ = in[1]; *out++ = in[2]; } } } ret = FS_WriteFile (filename, buffer, out - buffer); Mem_Free(buffer); return ret; } static void Image_Resample32LerpLine (const unsigned char *in, unsigned char *out, int inwidth, int outwidth) { int j, xi, oldx = 0, f, fstep, endx, lerp; fstep = (int) (inwidth*65536.0f/outwidth); endx = (inwidth-1); for (j = 0,f = 0;j < outwidth;j++, f += fstep) { xi = f >> 16; if (xi != oldx) { in += (xi - oldx) * 4; oldx = xi; } if (xi < endx) { lerp = f & 0xFFFF; *out++ = (unsigned char) ((((in[4] - in[0]) * lerp) >> 16) + in[0]); *out++ = (unsigned char) ((((in[5] - in[1]) * lerp) >> 16) + in[1]); *out++ = (unsigned char) ((((in[6] - in[2]) * lerp) >> 16) + in[2]); *out++ = (unsigned char) ((((in[7] - in[3]) * lerp) >> 16) + in[3]); } else // last pixel of the line has no pixel to lerp to { *out++ = in[0]; *out++ = in[1]; *out++ = in[2]; *out++ = in[3]; } } } #define LERPBYTE(i) r = resamplerow1[i];out[i] = (unsigned char) ((((resamplerow2[i] - r) * lerp) >> 16) + r) static void Image_Resample32Lerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) { int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight-1), inwidth4 = inwidth*4, outwidth4 = outwidth*4; unsigned char *out; const unsigned char *inrow; unsigned char *resamplerow1; unsigned char *resamplerow2; out = (unsigned char *)outdata; fstep = (int) (inheight*65536.0f/outheight); resamplerow1 = (unsigned char *)Mem_Alloc(tempmempool, outwidth*4*2); resamplerow2 = resamplerow1 + outwidth*4; inrow = (const unsigned char *)indata; oldy = 0; Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); for (i = 0, f = 0;i < outheight;i++,f += fstep) { yi = f >> 16; if (yi < endy) { lerp = f & 0xFFFF; if (yi != oldy) { inrow = (unsigned char *)indata + inwidth4*yi; if (yi == oldy+1) memcpy(resamplerow1, resamplerow2, outwidth4); else Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); oldy = yi; } j = outwidth - 4; while(j >= 0) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); LERPBYTE( 4); LERPBYTE( 5); LERPBYTE( 6); LERPBYTE( 7); LERPBYTE( 8); LERPBYTE( 9); LERPBYTE(10); LERPBYTE(11); LERPBYTE(12); LERPBYTE(13); LERPBYTE(14); LERPBYTE(15); out += 16; resamplerow1 += 16; resamplerow2 += 16; j -= 4; } if (j & 2) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); LERPBYTE( 4); LERPBYTE( 5); LERPBYTE( 6); LERPBYTE( 7); out += 8; resamplerow1 += 8; resamplerow2 += 8; } if (j & 1) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); out += 4; resamplerow1 += 4; resamplerow2 += 4; } resamplerow1 -= outwidth4; resamplerow2 -= outwidth4; } else { if (yi != oldy) { inrow = (unsigned char *)indata + inwidth4*yi; if (yi == oldy+1) memcpy(resamplerow1, resamplerow2, outwidth4); else Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); oldy = yi; } memcpy(out, resamplerow1, outwidth4); } } Mem_Free(resamplerow1); resamplerow1 = NULL; resamplerow2 = NULL; } static void Image_Resample32Nolerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) { int i, j; unsigned frac, fracstep; // relies on int being 4 bytes int *inrow, *out; out = (int *)outdata; fracstep = inwidth*0x10000/outwidth; for (i = 0;i < outheight;i++) { inrow = (int *)indata + inwidth*(i*inheight/outheight); frac = fracstep >> 1; j = outwidth - 4; while (j >= 0) { out[0] = inrow[frac >> 16];frac += fracstep; out[1] = inrow[frac >> 16];frac += fracstep; out[2] = inrow[frac >> 16];frac += fracstep; out[3] = inrow[frac >> 16];frac += fracstep; out += 4; j -= 4; } if (j & 2) { out[0] = inrow[frac >> 16];frac += fracstep; out[1] = inrow[frac >> 16];frac += fracstep; out += 2; } if (j & 1) { out[0] = inrow[frac >> 16];frac += fracstep; out += 1; } } } /* ================ Image_Resample ================ */ void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality) { if (indepth != 1 || outdepth != 1) { Con_Printf ("Image_Resample: 3D resampling not supported\n"); return; } if (quality) Image_Resample32Lerp(indata, inwidth, inheight, outdata, outwidth, outheight); else Image_Resample32Nolerp(indata, inwidth, inheight, outdata, outwidth, outheight); } // in can be the same as out void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth) { const unsigned char *inrow; int x, y, nextrow; if (*depth != 1 || destdepth != 1) { Con_Printf ("Image_Resample: 3D resampling not supported\n"); if (*width > destwidth) *width >>= 1; if (*height > destheight) *height >>= 1; if (*depth > destdepth) *depth >>= 1; return; } // note: if given odd width/height this discards the last row/column of // pixels, rather than doing a proper box-filter scale down inrow = in; nextrow = *width * 4; if (*width > destwidth) { *width >>= 1; if (*height > destheight) { // reduce both *height >>= 1; for (y = 0;y < *height;y++, inrow += nextrow * 2) { for (in = inrow, x = 0;x < *width;x++) { out[0] = (unsigned char) ((in[0] + in[4] + in[nextrow ] + in[nextrow+4]) >> 2); out[1] = (unsigned char) ((in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2); out[2] = (unsigned char) ((in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2); out[3] = (unsigned char) ((in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2); out += 4; in += 8; } } } else { // reduce width for (y = 0;y < *height;y++, inrow += nextrow) { for (in = inrow, x = 0;x < *width;x++) { out[0] = (unsigned char) ((in[0] + in[4]) >> 1); out[1] = (unsigned char) ((in[1] + in[5]) >> 1); out[2] = (unsigned char) ((in[2] + in[6]) >> 1); out[3] = (unsigned char) ((in[3] + in[7]) >> 1); out += 4; in += 8; } } } } else { if (*height > destheight) { // reduce height *height >>= 1; for (y = 0;y < *height;y++, inrow += nextrow * 2) { for (in = inrow, x = 0;x < *width;x++) { out[0] = (unsigned char) ((in[0] + in[nextrow ]) >> 1); out[1] = (unsigned char) ((in[1] + in[nextrow+1]) >> 1); out[2] = (unsigned char) ((in[2] + in[nextrow+2]) >> 1); out[3] = (unsigned char) ((in[3] + in[nextrow+3]) >> 1); out += 4; in += 4; } } } else Con_Printf ("Image_MipReduce: desired size already achieved\n"); } } void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale) { int x, y, x1, x2, y1, y2; const unsigned char *b, *row[3]; int p[5]; unsigned char *out; float ibumpscale, n[3]; ibumpscale = (255.0f * 6.0f) / bumpscale; out = outpixels; for (y = 0, y1 = height-1;y < height;y1 = y, y++) { y2 = y + 1;if (y2 >= height) y2 = 0; row[0] = inpixels + (y1 * width) * 4; row[1] = inpixels + (y * width) * 4; row[2] = inpixels + (y2 * width) * 4; for (x = 0, x1 = width-1;x < width;x1 = x, x++) { x2 = x + 1;if (x2 >= width) x2 = 0; // left, right b = row[1] + x1 * 4;p[0] = (b[0] + b[1] + b[2]); b = row[1] + x2 * 4;p[1] = (b[0] + b[1] + b[2]); // above, below b = row[0] + x * 4;p[2] = (b[0] + b[1] + b[2]); b = row[2] + x * 4;p[3] = (b[0] + b[1] + b[2]); // center b = row[1] + x * 4;p[4] = (b[0] + b[1] + b[2]); // calculate a normal from the slopes n[0] = p[0] - p[1]; n[1] = p[3] - p[2]; n[2] = ibumpscale; VectorNormalize(n); // turn it into a dot3 rgb vector texture out[2] = (int)(128.0f + n[0] * 127.0f); out[1] = (int)(128.0f + n[1] * 127.0f); out[0] = (int)(128.0f + n[2] * 127.0f); out[3] = (p[4]) / 3; out += 4; } } } darkplaces/README.iOS0000664000175000017500000000124113067716216013537 0ustar kalevkalevTo build DarkPlaces for iOS, you need to extract this zip into the source folder: http://ghdigital.com/~havoc/SDLiOS20110208.zip This is built from the in-development version of SDL 1.3, to make an updated include folder and libSDL*.a files, you need to get the SDL 1.3 source (from hg or a nightly build or whatever), then simply open Xcode-iPhoneOS/SDL/SDLiPhoneOS.xcodeproj and build libSDL for both simulator and device and then the SDL Application xcode template. Then copy the include folder and two libSDL*.a files from the Xcode-iPhoneOS/SDL/build/Debug-template (or Release-template) into the darkplaces source folder, and it will build with your updated files. darkplaces/.travis.yml0000664000175000017500000000221713067716216014303 0ustar kalevkalevlanguage: cpp matrix: include: - os: linux env: PROJECT=xonotic OS="linux32" compiler: gcc sudo: false addons: apt: packages: - libxpm-dev:i386 - libsdl1.2-dev:i386 - libxxf86vm-dev:i386 - gcc-multilib - g++-multilib # Workaround packages to install to fix dependency hell. - libglu1-mesa-dev:i386 - libcaca-dev:i386 - libxext-dev:i386 - libslang2-dev:i386 - libpng-dev:i386 - os: linux env: PROJECT=xonotic OS="linux64" compiler: gcc sudo: false addons: apt: packages: - libxpm-dev - libsdl1.2-dev - libxxf86vm-dev - os: linux env: PROJECT=xonotic OS="win32" compiler: gcc sudo: false - os: linux env: PROJECT=xonotic OS="win64" compiler: gcc sudo: false - os: osx env: PROJECT=xonotic OS="osx" compiler: gcc before_install: - "./.travis-before_install-${PROJECT}.sh $OS" install: - true before_script: - true script: - "./.travis-script-${PROJECT}.sh $OS" after_success: - true after_failure: - true after_script: - true darkplaces/cl_video.c0000664000175000017500000005254613067716216014134 0ustar kalevkalev #include "quakedef.h" #include "cl_dyntexture.h" #include "cl_video.h" // cvars cvar_t cl_video_subtitles = {CVAR_SAVE, "cl_video_subtitles", "0", "show subtitles for videos (if they are present)"}; cvar_t cl_video_subtitles_lines = {CVAR_SAVE, "cl_video_subtitles_lines", "4", "how many lines to occupy for subtitles"}; cvar_t cl_video_subtitles_textsize = {CVAR_SAVE, "cl_video_subtitles_textsize", "16", "textsize for subtitles"}; cvar_t cl_video_scale = {CVAR_SAVE, "cl_video_scale", "1", "scale of video, 1 = fullscreen, 0.75 - 3/4 of screen etc."}; cvar_t cl_video_scale_vpos = {CVAR_SAVE, "cl_video_scale_vpos", "0", "vertical align of scaled video, -1 is top, 1 is bottom"}; cvar_t cl_video_stipple = {CVAR_SAVE, "cl_video_stipple", "0", "draw interlacing-like effect on videos, similar to scr_stipple but static and used only with video playing."}; cvar_t cl_video_brightness = {CVAR_SAVE, "cl_video_brightness", "1", "brightness of video, 1 = fullbright, 0.75 - 3/4 etc."}; cvar_t cl_video_keepaspectratio = {CVAR_SAVE, "cl_video_keepaspectratio", "0", "keeps aspect ratio of fullscreen videos, leaving black color on unfilled areas, a value of 2 let video to be stretched horizontally with top & bottom being sliced out"}; cvar_t cl_video_fadein = {CVAR_SAVE, "cl_video_fadein", "0", "fading-from-black effect once video is started, in seconds"}; cvar_t cl_video_fadeout = {CVAR_SAVE, "cl_video_fadeout", "0", "fading-to-black effect once video is ended, in seconds"}; cvar_t v_glslgamma_video = {CVAR_SAVE, "v_glslgamma_video", "1", "applies GLSL gamma to played video, could be a fraction, requires r_glslgamma_2d 1."}; // DPV stream decoder #include "dpvsimpledecode.h" // VorteX: libavcodec implementation #include "cl_video_libavw.c" // JAM video decoder used by Blood Omnicide #ifdef JAMVIDEO #include "cl_video_jamdecode.c" #endif // constants (and semi-constants) static int cl_videormask; static int cl_videobmask; static int cl_videogmask; static int cl_videobytesperpixel; static int cl_num_videos; static clvideo_t cl_videos[ MAXCLVIDEOS ]; static rtexturepool_t *cl_videotexturepool; static clvideo_t *FindUnusedVid( void ) { int i; for( i = 1 ; i < MAXCLVIDEOS ; i++ ) if( cl_videos[ i ].state == CLVIDEO_UNUSED ) return &cl_videos[ i ]; return NULL; } static qboolean OpenStream( clvideo_t * video ) { const char *errorstring; video->stream = dpvsimpledecode_open( video, video->filename, &errorstring); if (video->stream) return true; #ifdef JAMVIDEO video->stream = jam_open( video, video->filename, &errorstring); if (video->stream) return true; #endif video->stream = LibAvW_OpenVideo( video, video->filename, &errorstring); if (video->stream) return true; Con_Printf("unable to open \"%s\", error: %s\n", video->filename, errorstring); return false; } static void VideoUpdateCallback(rtexture_t *rt, void *data) { clvideo_t *video = (clvideo_t *) data; R_UpdateTexture( video->cpif.tex, (unsigned char *)video->imagedata, 0, 0, 0, video->cpif.width, video->cpif.height, 1 ); } static void LinkVideoTexture( clvideo_t *video ) { video->cpif.tex = R_LoadTexture2D( cl_videotexturepool, video->cpif.name, video->cpif.width, video->cpif.height, NULL, TEXTYPE_BGRA, TEXF_PERSISTENT | TEXF_CLAMP, -1, NULL ); R_MakeTextureDynamic( video->cpif.tex, VideoUpdateCallback, video ); CL_LinkDynTexture( video->cpif.name, video->cpif.tex ); } static void UnlinkVideoTexture( clvideo_t *video ) { CL_UnlinkDynTexture( video->cpif.name ); // free the texture R_FreeTexture( video->cpif.tex ); video->cpif.tex = NULL; // free the image data Mem_Free( video->imagedata ); } static void SuspendVideo( clvideo_t * video ) { if (video->suspended) return; video->suspended = true; UnlinkVideoTexture(video); // if we are in firstframe mode, also close the stream if (video->state == CLVIDEO_FIRSTFRAME) { if (video->stream) video->close(video->stream); video->stream = NULL; } } static qboolean WakeVideo( clvideo_t * video ) { if( !video->suspended ) return true; video->suspended = false; if( video->state == CLVIDEO_FIRSTFRAME ) if( !OpenStream( video ) ) { video->state = CLVIDEO_UNUSED; return false; } video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); LinkVideoTexture( video ); // update starttime video->starttime += realtime - video->lasttime; return true; } static void LoadSubtitles( clvideo_t *video, const char *subtitlesfile ) { char *subtitle_text; const char *data; float subtime, sublen; int numsubs = 0; if (gamemode == GAME_BLOODOMNICIDE) { char overridename[MAX_QPATH]; cvar_t *langcvar; langcvar = Cvar_FindVar("language"); subtitle_text = NULL; if (langcvar) { dpsnprintf(overridename, sizeof(overridename), "locale/%s/%s", langcvar->string, subtitlesfile); subtitle_text = (char *)FS_LoadFile(overridename, cls.permanentmempool, false, NULL); } if (!subtitle_text) subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); } else { subtitle_text = (char *)FS_LoadFile(subtitlesfile, cls.permanentmempool, false, NULL); } if (!subtitle_text) { Con_DPrintf( "LoadSubtitles: can't open subtitle file '%s'!\n", subtitlesfile ); return; } // parse subtitle_text // line is: x y "text" where // x - start time // y - seconds last (if 0 - last thru next sub, if negative - last to next sub - this amount of seconds) data = subtitle_text; for (;;) { if (!COM_ParseToken_QuakeC(&data, false)) break; subtime = atof( com_token ); if (!COM_ParseToken_QuakeC(&data, false)) break; sublen = atof( com_token ); if (!COM_ParseToken_QuakeC(&data, false)) break; if (!com_token[0]) continue; // check limits if (video->subtitles == CLVIDEO_MAX_SUBTITLES) { Con_Printf("WARNING: CLVIDEO_MAX_SUBTITLES = %i reached when reading subtitles from '%s'\n", CLVIDEO_MAX_SUBTITLES, subtitlesfile); break; } // add a sub video->subtitle_text[numsubs] = (char *) Mem_Alloc(cls.permanentmempool, strlen(com_token) + 1); memcpy(video->subtitle_text[numsubs], com_token, strlen(com_token) + 1); video->subtitle_start[numsubs] = subtime; video->subtitle_end[numsubs] = sublen; if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles { if (video->subtitle_end[numsubs-1] <= 0) video->subtitle_end[numsubs-1] = max(video->subtitle_start[numsubs-1], video->subtitle_start[numsubs] + video->subtitle_end[numsubs-1]); else video->subtitle_end[numsubs-1] = min(video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1], video->subtitle_start[numsubs]); } numsubs++; // todo: check timing for consistency? } if (numsubs > 0) // make true len for prev sub, autofix overlapping subtitles { if (video->subtitle_end[numsubs-1] <= 0) video->subtitle_end[numsubs-1] = 99999999; // fixme: make it end when video ends? else video->subtitle_end[numsubs-1] = video->subtitle_start[numsubs-1] + video->subtitle_end[numsubs-1]; } Z_Free( subtitle_text ); video->subtitles = numsubs; /* Con_Printf( "video->subtitles: %i\n", video->subtitles ); for (numsubs = 0; numsubs < video->subtitles; numsubs++) Con_Printf( " %03.2f %03.2f : %s\n", video->subtitle_start[numsubs], video->subtitle_end[numsubs], video->subtitle_text[numsubs] ); */ } static clvideo_t* OpenVideo( clvideo_t *video, const char *filename, const char *name, int owner, const char *subtitlesfile ) { strlcpy( video->filename, filename, sizeof(video->filename) ); video->ownertag = owner; if( strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) ) return NULL; strlcpy( video->cpif.name, name, sizeof(video->cpif.name) ); if( !OpenStream( video ) ) return NULL; video->state = CLVIDEO_FIRSTFRAME; video->framenum = -1; video->framerate = video->getframerate( video->stream ); video->lasttime = realtime; video->subtitles = 0; video->cpif.width = video->getwidth( video->stream ); video->cpif.height = video->getheight( video->stream ); video->imagedata = Mem_Alloc( cls.permanentmempool, video->cpif.width * video->cpif.height * cl_videobytesperpixel ); LinkVideoTexture( video ); // VorteX: load simple subtitle_text file if (subtitlesfile[0]) LoadSubtitles( video, subtitlesfile ); return video; } clvideo_t* CL_OpenVideo( const char *filename, const char *name, int owner, const char *subtitlesfile ) { clvideo_t *video; // sanity check if( !name || !*name || strncmp( name, CLVIDEOPREFIX, sizeof( CLVIDEOPREFIX ) - 1 ) != 0 ) { Con_DPrintf( "CL_OpenVideo: Bad video texture name '%s'!\n", name ); return NULL; } video = FindUnusedVid(); if( !video ) { Con_Printf( "CL_OpenVideo: unable to open video \"%s\" - video limit reached\n", filename ); return NULL; } video = OpenVideo( video, filename, name, owner, subtitlesfile ); // expand the active range to include the new entry if (video) { cl_num_videos = max(cl_num_videos, (int)(video - cl_videos) + 1); } return video; } static clvideo_t* CL_GetVideoBySlot( int slot ) { clvideo_t *video = &cl_videos[ slot ]; if( video->suspended ) { if( !WakeVideo( video ) ) return NULL; else if( video->state == CLVIDEO_RESETONWAKEUP ) video->framenum = -1; } video->lasttime = realtime; return video; } clvideo_t *CL_GetVideoByName( const char *name ) { int i; for( i = 0 ; i < cl_num_videos ; i++ ) if( cl_videos[ i ].state != CLVIDEO_UNUSED && !strcmp( cl_videos[ i ].cpif.name , name ) ) break; if( i != cl_num_videos ) return CL_GetVideoBySlot( i ); else return NULL; } void CL_SetVideoState(clvideo_t *video, clvideostate_t state) { if (!video) return; video->lasttime = realtime; video->state = state; if (state == CLVIDEO_FIRSTFRAME) CL_RestartVideo(video); } void CL_RestartVideo(clvideo_t *video) { if (!video) return; // reset time video->starttime = video->lasttime = realtime; video->framenum = -1; // reopen stream if (video->stream) video->close(video->stream); video->stream = NULL; if (!OpenStream(video)) video->state = CLVIDEO_UNUSED; } // close video void CL_CloseVideo(clvideo_t * video) { int i; if (!video || video->state == CLVIDEO_UNUSED) return; // close stream if (!video->suspended || video->state != CLVIDEO_FIRSTFRAME) { if (video->stream) video->close(video->stream); video->stream = NULL; } // unlink texture if (!video->suspended) UnlinkVideoTexture(video); // purge subtitles if (video->subtitles) { for (i = 0; i < video->subtitles; i++) Z_Free( video->subtitle_text[i] ); video->subtitles = 0; } video->state = CLVIDEO_UNUSED; } // update all videos void CL_Video_Frame(void) { clvideo_t *video; int destframe; int i; if (!cl_num_videos) return; for (video = cl_videos, i = 0 ; i < cl_num_videos ; video++, i++) { if (video->state != CLVIDEO_UNUSED && !video->suspended) { if (realtime - video->lasttime > CLTHRESHOLD) { SuspendVideo(video); continue; } if (video->state == CLVIDEO_PAUSE) { video->starttime = realtime - video->framenum * video->framerate; continue; } // read video frame from stream if time has come if (video->state == CLVIDEO_FIRSTFRAME ) destframe = 0; else destframe = (int)((realtime - video->starttime) * video->framerate); if (destframe < 0) destframe = 0; if (video->framenum < destframe) { do { video->framenum++; if (video->decodeframe(video->stream, video->imagedata, cl_videormask, cl_videogmask, cl_videobmask, cl_videobytesperpixel, cl_videobytesperpixel * video->cpif.width)) { // finished? CL_RestartVideo(video); if (video->state == CLVIDEO_PLAY) video->state = CLVIDEO_FIRSTFRAME; return; } } while(video->framenum < destframe); R_MarkDirtyTexture(video->cpif.tex); } } } // stop main video if (cl_videos->state == CLVIDEO_FIRSTFRAME) CL_VideoStop(); // reduce range to exclude unnecessary entries while(cl_num_videos > 0 && cl_videos[cl_num_videos-1].state == CLVIDEO_UNUSED) cl_num_videos--; } void CL_PurgeOwner( int owner ) { int i; for (i = 0 ; i < cl_num_videos ; i++) if (cl_videos[i].ownertag == owner) CL_CloseVideo(&cl_videos[i]); } typedef struct { dp_font_t *font; float x; float y; float width; float height; float alignment; // 0 = left, 0.5 = center, 1 = right float fontsize; float textalpha; } cl_video_subtitle_info_t; static float CL_DrawVideo_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) { cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; if(w == NULL) return si->fontsize * si->font->maxwidth; if(maxWidth >= 0) return DrawQ_TextWidth_UntilWidth(w, length, si->fontsize, si->fontsize, false, si->font, -maxWidth); // -maxWidth: we want at least one char else if(maxWidth == -1) return DrawQ_TextWidth(w, *length, si->fontsize, si->fontsize, false, si->font); else return 0; } static int CL_DrawVideo_DisplaySubtitleLine(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) { cl_video_subtitle_info_t *si = (cl_video_subtitle_info_t *) passthrough; int x = (int) (si->x + (si->width - width) * si->alignment); if (length > 0) DrawQ_String(x, si->y, line, length, si->fontsize, si->fontsize, 1.0, 1.0, 1.0, si->textalpha, 0, NULL, false, si->font); si->y += si->fontsize; return 1; } int cl_videoplaying = false; // old, but still supported void CL_DrawVideo(void) { clvideo_t *video; float videotime, px, py, sx, sy, st[8], b; cl_video_subtitle_info_t si; int i; if (!cl_videoplaying) return; video = CL_GetVideoBySlot( 0 ); // fix cvars if (cl_video_scale.value <= 0 || cl_video_scale.value > 1) Cvar_SetValueQuick( &cl_video_scale, 1); if (cl_video_brightness.value <= 0 || cl_video_brightness.value > 10) Cvar_SetValueQuick( &cl_video_brightness, 1); // calc video proportions px = 0; py = 0; sx = vid_conwidth.integer; sy = vid_conheight.integer; st[0] = 0.0; st[1] = 0.0; st[2] = 1.0; st[3] = 0.0; st[4] = 0.0; st[5] = 1.0; st[6] = 1.0; st[7] = 1.0; if (cl_video_keepaspectratio.integer) { float a = video->getaspectratio(video->stream) / ((float)vid.width / (float)vid.height); if (cl_video_keepaspectratio.integer >= 2) { // clip instead of scale if (a < 1.0) // clip horizontally { st[1] = st[3] = (1 - a)*0.5; st[5] = st[7] = 1 - (1 - a)*0.5; } else if (a > 1.0) // clip vertically { st[0] = st[4] = (1 - 1/a)*0.5; st[2] = st[6] = (1/a)*0.5; } } else if (a < 1.0) // scale horizontally { px += sx * (1 - a) * 0.5; sx *= a; } else if (a > 1.0) // scale vertically { a = 1 / a; py += sy * (1 - a); sy *= a; } } if (cl_video_scale.value != 1) { px += sx * (1 - cl_video_scale.value) * 0.5; py += sy * (1 - cl_video_scale.value) * ((bound(-1, cl_video_scale_vpos.value, 1) + 1) / 2); sx *= cl_video_scale.value; sy *= cl_video_scale.value; } // calc brightness for fadein and fadeout effects b = cl_video_brightness.value; if (cl_video_fadein.value && (realtime - video->starttime) < cl_video_fadein.value) b = pow((realtime - video->starttime)/cl_video_fadein.value, 2); else if (cl_video_fadeout.value && ((video->starttime + video->framenum * video->framerate) - realtime) < cl_video_fadeout.value) b = pow(((video->starttime + video->framenum * video->framerate) - realtime)/cl_video_fadeout.value, 2); // draw black bg in case stipple is active or video is scaled if (cl_video_stipple.integer || px != 0 || py != 0 || sx != vid_conwidth.integer || sy != vid_conheight.integer) DrawQ_Fill(0, 0, vid_conwidth.integer, vid_conheight.integer, 0, 0, 0, 1, 0); #ifndef USE_GLES2 // enable video-only polygon stipple (of global stipple is not active) if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) { GLubyte stipple[128]; int s, width, parts; s = cl_video_stipple.integer; parts = (s & 007); width = (s & 070) >> 3; qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 for(i = 0; i < 128; ++i) { int line = i/4; stipple[i] = ((line >> width) & ((1 << parts) - 1)) ? 0x00 : 0xFF; } qglPolygonStipple(stipple);CHECKGLERROR } #endif // draw video if (v_glslgamma_video.value >= 1) DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, 0); else { DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, 1, st[2], st[3], b, b, b, 1, st[4], st[5], b, b, b, 1, st[6], st[7], b, b, b, 1, DRAWFLAG_NOGAMMA); if (v_glslgamma_video.value > 0.0) DrawQ_SuperPic(px, py, &video->cpif, sx, sy, st[0], st[1], b, b, b, v_glslgamma_video.value, st[2], st[3], b, b, b, v_glslgamma_video.value, st[4], st[5], b, b, b, v_glslgamma_video.value, st[6], st[7], b, b, b, v_glslgamma_video.value, 0); } #ifndef USE_GLES2 // disable video-only stipple if (qglPolygonStipple && !scr_stipple.integer && cl_video_stipple.integer) { qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR } #endif // VorteX: draw subtitle_text if (!video->subtitles || !cl_video_subtitles.integer) return; // find current subtitle videotime = realtime - video->starttime; for (i = 0; i < video->subtitles; i++) { if (videotime >= video->subtitle_start[i] && videotime <= video->subtitle_end[i]) { // found, draw it si.font = FONT_NOTIFY; si.x = vid_conwidth.integer * 0.1; si.y = vid_conheight.integer - (max(1, cl_video_subtitles_lines.value) * cl_video_subtitles_textsize.value); si.width = vid_conwidth.integer * 0.8; si.height = max(1, cl_video_subtitles_lines.integer) * cl_video_subtitles_textsize.value; si.alignment = 0.5; si.fontsize = cl_video_subtitles_textsize.value; si.textalpha = min(1, (videotime - video->subtitle_start[i])/0.5) * min(1, ((video->subtitle_end[i] - videotime)/0.3)); // fade in and fade out COM_Wordwrap(video->subtitle_text[i], strlen(video->subtitle_text[i]), 0, si.width, CL_DrawVideo_WordWidthFunc, &si, CL_DrawVideo_DisplaySubtitleLine, &si); break; } } } void CL_VideoStart(char *filename, const char *subtitlesfile) { char vabuf[1024]; Host_StartVideo(); if( cl_videos->state != CLVIDEO_UNUSED ) CL_CloseVideo( cl_videos ); // already contains video/ if( !OpenVideo( cl_videos, filename, va(vabuf, sizeof(vabuf), CLDYNTEXTUREPREFIX "%s", filename ), 0, subtitlesfile ) ) return; // expand the active range to include the new entry cl_num_videos = max(cl_num_videos, 1); cl_videoplaying = true; CL_SetVideoState( cl_videos, CLVIDEO_PLAY ); CL_RestartVideo( cl_videos ); } void CL_Video_KeyEvent( int key, int ascii, qboolean down ) { // only react to up events, to allow the user to delay the abortion point if it suddenly becomes interesting.. if( !down ) { if( key == K_ESCAPE || key == K_ENTER || key == K_SPACE ) { CL_VideoStop(); } } } void CL_VideoStop(void) { cl_videoplaying = false; CL_CloseVideo( cl_videos ); } static void CL_PlayVideo_f(void) { char name[MAX_QPATH], subtitlesfile[MAX_QPATH]; const char *extension; Host_StartVideo(); if (COM_CheckParm("-benchmark")) return; if (Cmd_Argc() < 2) { Con_Print("usage: playvideo [custom_subtitles_file]\nplays video named video/.dpv\nif custom subtitles file is not presented\nit tries video/.sub"); return; } extension = FS_FileExtension(Cmd_Argv(1)); if (extension[0]) dpsnprintf(name, sizeof(name), "video/%s", Cmd_Argv(1)); else dpsnprintf(name, sizeof(name), "video/%s.dpv", Cmd_Argv(1)); if ( Cmd_Argc() > 2) CL_VideoStart(name, Cmd_Argv(2)); else { dpsnprintf(subtitlesfile, sizeof(subtitlesfile), "video/%s.dpsubs", Cmd_Argv(1)); CL_VideoStart(name, subtitlesfile); } } static void CL_StopVideo_f(void) { CL_VideoStop(); } static void cl_video_start( void ) { int i; clvideo_t *video; cl_videotexturepool = R_AllocTexturePool(); for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) if( video->state != CLVIDEO_UNUSED && !video->suspended ) LinkVideoTexture( video ); } static void cl_video_shutdown( void ) { int i; clvideo_t *video; for( video = cl_videos, i = 0 ; i < cl_num_videos ; i++, video++ ) if( video->state != CLVIDEO_UNUSED && !video->suspended ) SuspendVideo( video ); R_FreeTexturePool( &cl_videotexturepool ); } static void cl_video_newmap( void ) { } void CL_Video_Init( void ) { union { unsigned char b[4]; unsigned int i; } bgra; cl_num_videos = 0; cl_videobytesperpixel = 4; // set masks in an endian-independent way (as they really represent bytes) bgra.i = 0;bgra.b[0] = 0xFF;cl_videobmask = bgra.i; bgra.i = 0;bgra.b[1] = 0xFF;cl_videogmask = bgra.i; bgra.i = 0;bgra.b[2] = 0xFF;cl_videormask = bgra.i; Cmd_AddCommand( "playvideo", CL_PlayVideo_f, "play a .dpv video file" ); Cmd_AddCommand( "stopvideo", CL_StopVideo_f, "stop playing a .dpv video file" ); Cvar_RegisterVariable(&cl_video_subtitles); Cvar_RegisterVariable(&cl_video_subtitles_lines); Cvar_RegisterVariable(&cl_video_subtitles_textsize); Cvar_RegisterVariable(&cl_video_scale); Cvar_RegisterVariable(&cl_video_scale_vpos); Cvar_RegisterVariable(&cl_video_brightness); Cvar_RegisterVariable(&cl_video_stipple); Cvar_RegisterVariable(&cl_video_keepaspectratio); Cvar_RegisterVariable(&cl_video_fadein); Cvar_RegisterVariable(&cl_video_fadeout); Cvar_RegisterVariable(&v_glslgamma_video); R_RegisterModule( "CL_Video", cl_video_start, cl_video_shutdown, cl_video_newmap, NULL, NULL ); LibAvW_OpenLibrary(); } void CL_Video_Shutdown( void ) { int i; for (i = 0 ; i < cl_num_videos ; i++) CL_CloseVideo(&cl_videos[ i ]); LibAvW_CloseLibrary(); } darkplaces/shader_glsl.h0000664000175000017500000024155513067716222014641 0ustar kalevkalev"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n", "// written by Forest 'LordHavoc' Hale\n", "// shadowmapping enhancements by Lee 'eihrul' Salzman\n", "\n", "#if defined(USESKELETAL) || defined(USEOCCLUDE)\n", "# ifdef GL_ARB_uniform_buffer_object\n", "# extension GL_ARB_uniform_buffer_object : enable\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAP2D\n", "# ifdef GL_EXT_gpu_shader4\n", "# extension GL_EXT_gpu_shader4 : enable\n", "# endif\n", "# ifdef GL_ARB_texture_gather\n", "# extension GL_ARB_texture_gather : enable\n", "# else\n", "# ifdef GL_AMD_texture_texture4\n", "# extension GL_AMD_texture_texture4 : enable\n", "# endif\n", "# endif\n", "#endif\n", "\n", "#ifdef USECELSHADING\n", "# define SHADEDIFFUSE myhalf diffuse = cast_myhalf(min(max(float(dot(surfacenormal, lightnormal)) * 2.0, 0.0), 1.0));\n", "# ifdef USEEXACTSPECULARMATH\n", "# define SHADESPECULAR(specpow) myhalf specular = pow(cast_myhalf(max(float(dot(reflect(lightnormal, surfacenormal), eyenormal))*-1.0, 0.0)), 1.0 + specpow);specular = max(0.0, specular * 10.0 - 9.0);\n", "# else\n", "# define SHADESPECULAR(specpow) myhalf3 specularnormal = normalize(lightnormal + eyenormal);myhalf specular = pow(cast_myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + specpow);specular = max(0.0, specular * 10.0 - 9.0);\n", "# endif\n", "#else\n", "# define SHADEDIFFUSE myhalf diffuse = cast_myhalf(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", "# ifdef USEEXACTSPECULARMATH\n", "# define SHADESPECULAR(specpow) myhalf specular = pow(cast_myhalf(max(float(dot(reflect(lightnormal, surfacenormal), eyenormal))*-1.0, 0.0)), 1.0 + specpow);\n", "# else\n", "# define SHADESPECULAR(specpow) myhalf3 specularnormal = normalize(lightnormal + eyenormal);myhalf specular = pow(cast_myhalf(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + specpow);\n", "# endif\n", "#endif\n", "\n", "#if defined(GLSL130) || defined(GLSL140)\n", "precision highp float;\n", "# ifdef VERTEX_SHADER\n", "# define dp_varying out\n", "# define dp_attribute in\n", "# endif\n", "# ifdef FRAGMENT_SHADER\n", "out vec4 dp_FragColor;\n", "# define dp_varying in\n", "# define dp_attribute in\n", "# endif\n", "# define dp_offsetmapping_dFdx dFdx\n", "# define dp_offsetmapping_dFdy dFdy\n", "# define dp_textureGrad textureGrad\n", "# define dp_textureOffset(a,b,c,d) textureOffset(a,b,ivec2(c,d))\n", "# define dp_texture2D texture\n", "# define dp_texture3D texture\n", "# define dp_textureCube texture\n", "# define dp_shadow2D(a,b) float(texture(a,b))\n", "#else\n", "# ifdef FRAGMENT_SHADER\n", "# define dp_FragColor gl_FragColor\n", "# endif\n", "# define dp_varying varying\n", "# define dp_attribute attribute\n", "# define dp_offsetmapping_dFdx(a) vec2(0.0, 0.0)\n", "# define dp_offsetmapping_dFdy(a) vec2(0.0, 0.0)\n", "# define dp_textureGrad(a,b,c,d) texture2D(a,b)\n", "# define dp_textureOffset(a,b,c,d) texture2DOffset(a,b,ivec2(c,d))\n", "# define dp_texture2D texture2D\n", "# define dp_texture3D texture3D\n", "# define dp_textureCube textureCube\n", "# define dp_shadow2D(a,b) float(shadow2D(a,b))\n", "#endif\n", "\n", "// GL ES and GLSL130 shaders use precision modifiers, standard GL does not\n", "// in GLSL130 we don't use them though because of syntax differences (can't use precision with inout)\n", "#ifndef GL_ES\n", "#define lowp\n", "#define mediump\n", "#define highp\n", "#endif\n", "\n", "#ifdef USEDEPTHRGB\n", " // for 565 RGB we'd need to use different multipliers\n", "#define decodedepthmacro(d) dot((d).rgb, vec3(1.0, 255.0 / 65536.0, 255.0 / 16777215.0))\n", "#define encodedepthmacro(d) (vec4(d, d*256.0, d*65536.0, 0.0) - floor(vec4(d, d*256.0, d*65536.0, 0.0)))\n", "#endif\n", "\n", "#ifdef VERTEX_SHADER\n", "dp_attribute vec4 Attrib_Position; // vertex\n", "dp_attribute vec4 Attrib_Color; // color\n", "dp_attribute vec4 Attrib_TexCoord0; // material texcoords\n", "dp_attribute vec3 Attrib_TexCoord1; // svector\n", "dp_attribute vec3 Attrib_TexCoord2; // tvector\n", "dp_attribute vec3 Attrib_TexCoord3; // normal\n", "dp_attribute vec4 Attrib_TexCoord4; // lightmap texcoords\n", "#ifdef USESKELETAL\n", "//uniform mat4 Skeletal_Transform[128];\n", "// this is used with glBindBufferRange to bind a uniform block to the name\n", "// Skeletal_Transform12_UniformBlock, the Skeletal_Transform12 variable is\n", "// directly accessible without a namespace.\n", "// explanation: http://www.opengl.org/wiki/Interface_Block_%28GLSL%29#Syntax\n", "uniform Skeletal_Transform12_UniformBlock\n", "{\n", " vec4 Skeletal_Transform12[768];\n", "};\n", "dp_attribute vec4 Attrib_SkeletalIndex;\n", "dp_attribute vec4 Attrib_SkeletalWeight;\n", "#endif\n", "#endif\n", "dp_varying mediump vec4 VertexColor;\n", "\n", "#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n", "# define USEFOG\n", "#endif\n", "#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP)\n", "# define USELIGHTMAP\n", "#endif\n", "#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT) || defined(USEFOG)\n", "# define USEEYEVECTOR\n", "#endif\n", "\n", "//#ifdef __GLSL_CG_DATA_TYPES\n", "//# define myhalf half\n", "//# define myhalf2 half2\n", "//# define myhalf3 half3\n", "//# define myhalf4 half4\n", "//# define cast_myhalf half\n", "//# define cast_myhalf2 half2\n", "//# define cast_myhalf3 half3\n", "//# define cast_myhalf4 half4\n", "//#else\n", "# define myhalf mediump float\n", "# define myhalf2 mediump vec2\n", "# define myhalf3 mediump vec3\n", "# define myhalf4 mediump vec4\n", "# define cast_myhalf float\n", "# define cast_myhalf2 vec2\n", "# define cast_myhalf3 vec3\n", "# define cast_myhalf4 vec4\n", "//#endif\n", "\n", "#ifdef VERTEX_SHADER\n", "uniform highp mat4 ModelViewProjectionMatrix;\n", "#endif\n", "\n", "#ifdef VERTEX_SHADER\n", "#ifdef USETRIPPY\n", "// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n", "// tweaked scale\n", "uniform highp float ClientTime;\n", "vec4 TrippyVertex(vec4 position)\n", "{\n", " float worldTime = ClientTime;\n", " // tweaked for Quake\n", " worldTime *= 10.0;\n", " position *= 0.125;\n", " //~tweaked for Quake\n", " float distanceSquared = (position.x * position.x + position.z * position.z);\n", " position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n", " float y = position.y;\n", " float x = position.x;\n", " float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n", " position.y = x*sin(om)+y*cos(om);\n", " position.x = x*cos(om)-y*sin(om);\n", " return position;\n", "}\n", "#endif\n", "#endif\n", "\n", "#ifdef MODE_DEPTH_OR_SHADOW\n", "dp_varying highp float Depth;\n", "#ifdef VERTEX_SHADER\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", "#define Attrib_Position SkeletalVertex\n", "#endif\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", " Depth = gl_Position.z;\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main(void)\n", "{\n", "#ifdef USEDEPTHRGB\n", " dp_FragColor = encodedepthmacro(Depth);\n", "#else\n", " dp_FragColor = vec4(1.0,1.0,1.0,1.0);\n", "#endif\n", "}\n", "#endif\n", "#else // !MODE_DEPTH_ORSHADOW\n", "\n", "\n", "\n", "\n", "#ifdef MODE_POSTPROCESS\n", "dp_varying mediump vec2 TexCoord1;\n", "dp_varying mediump vec2 TexCoord2;\n", "\n", "#ifdef VERTEX_SHADER\n", "void main(void)\n", "{\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", " TexCoord1 = Attrib_TexCoord0.xy;\n", "#ifdef USEBLOOM\n", " TexCoord2 = Attrib_TexCoord4.xy;\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "uniform sampler2D Texture_First;\n", "#ifdef USEBLOOM\n", "uniform sampler2D Texture_Second;\n", "uniform mediump vec4 BloomColorSubtract;\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", "uniform sampler2D Texture_GammaRamps;\n", "#endif\n", "#ifdef USESATURATION\n", "uniform mediump float Saturation;\n", "#endif\n", "#ifdef USEVIEWTINT\n", "uniform mediump vec4 ViewTintColor;\n", "#endif\n", "//uncomment these if you want to use them:\n", "uniform mediump vec4 UserVec1;\n", "uniform mediump vec4 UserVec2;\n", "// uniform mediump vec4 UserVec3;\n", "// uniform mediump vec4 UserVec4;\n", "// uniform highp float ClientTime;\n", "uniform mediump vec2 PixelSize;\n", "\n", "#ifdef USEFXAA\n", "// graphitemaster: based off the white paper by Timothy Lottes\n", "// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf\n", "vec4 fxaa(vec4 inColor, float maxspan)\n", "{\n", " vec4 ret = inColor; // preserve old\n", " float mulreduct = 1.0/maxspan;\n", " float minreduct = (1.0 / 128.0);\n", "\n", " // directions\n", " vec3 NW = dp_texture2D(Texture_First, TexCoord1 + (vec2(-1.0, -1.0) * PixelSize)).xyz;\n", " vec3 NE = dp_texture2D(Texture_First, TexCoord1 + (vec2(+1.0, -1.0) * PixelSize)).xyz;\n", " vec3 SW = dp_texture2D(Texture_First, TexCoord1 + (vec2(-1.0, +1.0) * PixelSize)).xyz;\n", " vec3 SE = dp_texture2D(Texture_First, TexCoord1 + (vec2(+1.0, +1.0) * PixelSize)).xyz;\n", " vec3 M = dp_texture2D(Texture_First, TexCoord1).xyz;\n", "\n", " // luminance directions\n", " vec3 luma = vec3(0.299, 0.587, 0.114);\n", " float lNW = dot(NW, luma);\n", " float lNE = dot(NE, luma);\n", " float lSW = dot(SW, luma);\n", " float lSE = dot(SE, luma);\n", " float lM = dot(M, luma);\n", " float lMin = min(lM, min(min(lNW, lNE), min(lSW, lSE)));\n", " float lMax = max(lM, max(max(lNW, lNE), max(lSW, lSE)));\n", "\n", " // direction and reciprocal\n", " vec2 dir = vec2(-((lNW + lNE) - (lSW + lSE)), ((lNW + lSW) - (lNE + lSE)));\n", " float rcp = 1.0/(min(abs(dir.x), abs(dir.y)) + max((lNW + lNE + lSW + lSE) * (0.25 * mulreduct), minreduct));\n", "\n", " // span\n", " dir = min(vec2(maxspan, maxspan), max(vec2(-maxspan, -maxspan), dir * rcp)) * PixelSize;\n", "\n", " vec3 rA = (1.0/2.0) * (\n", " dp_texture2D(Texture_First, TexCoord1 + dir * (1.0/3.0 - 0.5)).xyz +\n", " dp_texture2D(Texture_First, TexCoord1 + dir * (2.0/3.0 - 0.5)).xyz);\n", " vec3 rB = rA * (1.0/2.0) + (1.0/4.0) * (\n", " dp_texture2D(Texture_First, TexCoord1 + dir * (0.0/3.0 - 0.5)).xyz +\n", " dp_texture2D(Texture_First, TexCoord1 + dir * (3.0/3.0 - 0.5)).xyz);\n", " float lB = dot(rB, luma);\n", "\n", " ret.xyz = ((lB < lMin) || (lB > lMax)) ? rA : rB;\n", " ret.a = 1.0;\n", " return ret;\n", "}\n", "#endif\n", "\n", "void main(void)\n", "{\n", " dp_FragColor = dp_texture2D(Texture_First, TexCoord1);\n", "\n", "#ifdef USEFXAA\n", " dp_FragColor = fxaa(dp_FragColor, 8.0); // 8.0 can be changed for larger span\n", "#endif\n", "\n", "#ifdef USEPOSTPROCESSING\n", "// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n", "// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n", "#if defined(USERVEC1) || defined(USERVEC2)\n", " float sobel = 1.0;\n", " // vec2 ts = textureSize(Texture_First, 0);\n", " // vec2 px = vec2(1/ts.x, 1/ts.y);\n", " vec2 px = PixelSize;\n", " vec3 x1 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n", " vec3 x2 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, 0.0)).rgb;\n", " vec3 x3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n", " vec3 x4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n", " vec3 x5 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, 0.0)).rgb;\n", " vec3 x6 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n", " vec3 y1 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x,-px.y)).rgb;\n", " vec3 y2 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0,-px.y)).rgb;\n", " vec3 y3 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x,-px.y)).rgb;\n", " vec3 y4 = dp_texture2D(Texture_First, TexCoord1 + vec2( px.x, px.y)).rgb;\n", " vec3 y5 = dp_texture2D(Texture_First, TexCoord1 + vec2( 0.0, px.y)).rgb;\n", " vec3 y6 = dp_texture2D(Texture_First, TexCoord1 + vec2(-px.x, px.y)).rgb;\n", " float px1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x1);\n", " float px2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), x2);\n", " float px3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), x3);\n", " float px4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x4);\n", " float px5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), x5);\n", " float px6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), x6);\n", " float py1 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y1);\n", " float py2 = -2.0 * dot(vec3(0.3, 0.59, 0.11), y2);\n", " float py3 = -1.0 * dot(vec3(0.3, 0.59, 0.11), y3);\n", " float py4 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y4);\n", " float py5 = 2.0 * dot(vec3(0.3, 0.59, 0.11), y5);\n", " float py6 = 1.0 * dot(vec3(0.3, 0.59, 0.11), y6);\n", " sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n", " dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.987688, -0.156434)) * UserVec1.y;\n", " dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.156434, -0.891007)) * UserVec1.y;\n", " dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.891007, -0.453990)) * UserVec1.y;\n", " dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2( 0.707107, 0.707107)) * UserVec1.y;\n", " dp_FragColor += dp_texture2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*vec2(-0.453990, 0.891007)) * UserVec1.y;\n", " dp_FragColor /= (1.0 + 5.0 * UserVec1.y);\n", " dp_FragColor.rgb = dp_FragColor.rgb * (1.0 + UserVec2.x) + vec3(max(0.0, sobel - UserVec2.z))*UserVec2.y;\n", "#endif\n", "#endif\n", "\n", "#ifdef USEBLOOM\n", " dp_FragColor += max(vec4(0,0,0,0), dp_texture2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n", "#endif\n", "\n", "#ifdef USEVIEWTINT\n", " dp_FragColor = mix(dp_FragColor, ViewTintColor, ViewTintColor.a);\n", "#endif\n", "\n", "#ifdef USESATURATION\n", " //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n", " float y = dot(dp_FragColor.rgb, vec3(0.299, 0.587, 0.114));\n", " // 'vampire sight' effect, wheres red is compensated\n", " #ifdef SATURATION_REDCOMPENSATE\n", " float rboost = max(0.0, (dp_FragColor.r - max(dp_FragColor.g, dp_FragColor.b))*(1.0 - Saturation));\n", " dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n", " dp_FragColor.r += rboost;\n", " #else\n", " // normal desaturation\n", " //dp_FragColor = vec3(y) + (dp_FragColor.rgb - vec3(y)) * Saturation;\n", " dp_FragColor.rgb = mix(vec3(y), dp_FragColor.rgb, Saturation);\n", " #endif\n", "#endif\n", "\n", "#ifdef USEGAMMARAMPS\n", " dp_FragColor.r = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", " dp_FragColor.g = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", " dp_FragColor.b = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", "#endif\n", "}\n", "#endif\n", "#else // !MODE_POSTPROCESS\n", "\n", "\n", "\n", "\n", "#ifdef MODE_GENERIC\n", "#ifdef USEDIFFUSE\n", "dp_varying mediump vec2 TexCoord1;\n", "#endif\n", "#ifdef USESPECULAR\n", "dp_varying mediump vec2 TexCoord2;\n", "#endif\n", "uniform myhalf Alpha;\n", "#ifdef VERTEX_SHADER\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", "#define Attrib_Position SkeletalVertex\n", "#endif\n", " VertexColor = Attrib_Color;\n", "#ifdef USEDIFFUSE\n", " TexCoord1 = Attrib_TexCoord0.xy;\n", "#endif\n", "#ifdef USESPECULAR\n", " TexCoord2 = Attrib_TexCoord1.xy;\n", "#endif\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "#ifdef USEDIFFUSE\n", "uniform sampler2D Texture_First;\n", "#endif\n", "#ifdef USESPECULAR\n", "uniform sampler2D Texture_Second;\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", "uniform sampler2D Texture_GammaRamps;\n", "#endif\n", "\n", "void main(void)\n", "{\n", "#ifdef USEVIEWTINT\n", " dp_FragColor = VertexColor;\n", "#else\n", " dp_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n", "#endif\n", "#ifdef USEDIFFUSE\n", "# ifdef USEREFLECTCUBE\n", " // suppress texture alpha\n", " dp_FragColor.rgb *= dp_texture2D(Texture_First, TexCoord1).rgb;\n", "# else\n", " dp_FragColor *= dp_texture2D(Texture_First, TexCoord1);\n", "# endif\n", "#endif\n", "\n", "#ifdef USESPECULAR\n", " vec4 tex2 = dp_texture2D(Texture_Second, TexCoord2);\n", "# ifdef USECOLORMAPPING\n", " dp_FragColor *= tex2;\n", "# endif\n", "# ifdef USEGLOW\n", " dp_FragColor += tex2;\n", "# endif\n", "# ifdef USEVERTEXTEXTUREBLEND\n", " dp_FragColor = mix(dp_FragColor, tex2, tex2.a);\n", "# endif\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", " dp_FragColor.r = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", " dp_FragColor.g = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", " dp_FragColor.b = dp_texture2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", "#endif\n", "#ifdef USEALPHAKILL\n", " dp_FragColor.a *= Alpha;\n", "#endif\n", "}\n", "#endif\n", "#else // !MODE_GENERIC\n", "\n", "\n", "\n", "\n", "#ifdef MODE_BLOOMBLUR\n", "dp_varying mediump vec2 TexCoord;\n", "#ifdef VERTEX_SHADER\n", "void main(void)\n", "{\n", " VertexColor = Attrib_Color;\n", " TexCoord = Attrib_TexCoord0.xy;\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "uniform sampler2D Texture_First;\n", "uniform mediump vec4 BloomBlur_Parameters;\n", "\n", "void main(void)\n", "{\n", " int i;\n", " vec2 tc = TexCoord;\n", " vec3 color = dp_texture2D(Texture_First, tc).rgb;\n", " tc += BloomBlur_Parameters.xy;\n", " for (i = 1;i < SAMPLES;i++)\n", " {\n", " color += dp_texture2D(Texture_First, tc).rgb;\n", " tc += BloomBlur_Parameters.xy;\n", " }\n", " dp_FragColor = vec4(color * BloomBlur_Parameters.z + vec3(BloomBlur_Parameters.w), 1);\n", "}\n", "#endif\n", "#else // !MODE_BLOOMBLUR\n", "#ifdef MODE_REFRACTION\n", "dp_varying mediump vec2 TexCoord;\n", "dp_varying highp vec4 ModelViewProjectionPosition;\n", "uniform highp mat4 TexMatrix;\n", "#ifdef VERTEX_SHADER\n", "\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", "#define Attrib_Position SkeletalVertex\n", "#endif\n", "#ifdef USEALPHAGENVERTEX\n", " VertexColor = Attrib_Color;\n", "#endif\n", " TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", " ModelViewProjectionPosition = gl_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "uniform sampler2D Texture_Normal;\n", "uniform sampler2D Texture_Refraction;\n", "\n", "uniform mediump vec4 DistortScaleRefractReflect;\n", "uniform mediump vec4 ScreenScaleRefractReflect;\n", "uniform mediump vec4 ScreenCenterRefractReflect;\n", "uniform mediump vec4 RefractColor;\n", "uniform mediump vec4 ReflectColor;\n", "uniform highp float ClientTime;\n", "#ifdef USENORMALMAPSCROLLBLEND\n", "uniform highp vec2 NormalmapScrollBlend;\n", "#endif\n", "\n", "void main(void)\n", "{\n", " vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n", " //vec2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", " vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", "#ifdef USEALPHAGENVERTEX\n", " vec2 distort = DistortScaleRefractReflect.xy * VertexColor.a;\n", " vec4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), VertexColor.a);\n", "#else\n", " vec2 distort = DistortScaleRefractReflect.xy;\n", " vec4 refractcolor = RefractColor;\n", "#endif\n", " #ifdef USENORMALMAPSCROLLBLEND\n", " vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", " normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(normal))).xy * distort;\n", " #else\n", " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - cast_myhalf3(0.5))).xy * distort;\n", " #endif\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n", " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * refractcolor;\n", "}\n", "#endif\n", "#else // !MODE_REFRACTION\n", "\n", "\n", "\n", "\n", "#ifdef MODE_WATER\n", "dp_varying mediump vec2 TexCoord;\n", "dp_varying highp vec3 EyeVector;\n", "dp_varying highp vec4 ModelViewProjectionPosition;\n", "#ifdef VERTEX_SHADER\n", "uniform highp vec3 EyePosition;\n", "uniform highp mat4 TexMatrix;\n", "\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", " mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", " vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", "#define Attrib_Position SkeletalVertex\n", "#define Attrib_TexCoord1 SkeletalSVector\n", "#define Attrib_TexCoord2 SkeletalTVector\n", "#define Attrib_TexCoord3 SkeletalNormal\n", "#endif\n", "#ifdef USEALPHAGENVERTEX\n", " VertexColor = Attrib_Color;\n", "#endif\n", " TexCoord = vec2(TexMatrix * Attrib_TexCoord0);\n", " vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", " EyeVector.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", " EyeVector.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", " EyeVector.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", " ModelViewProjectionPosition = gl_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "uniform sampler2D Texture_Normal;\n", "uniform sampler2D Texture_Refraction;\n", "uniform sampler2D Texture_Reflection;\n", "\n", "uniform mediump vec4 DistortScaleRefractReflect;\n", "uniform mediump vec4 ScreenScaleRefractReflect;\n", "uniform mediump vec4 ScreenCenterRefractReflect;\n", "uniform mediump vec4 RefractColor;\n", "uniform mediump vec4 ReflectColor;\n", "uniform mediump float ReflectFactor;\n", "uniform mediump float ReflectOffset;\n", "uniform highp float ClientTime;\n", "#ifdef USENORMALMAPSCROLLBLEND\n", "uniform highp vec2 NormalmapScrollBlend;\n", "#endif\n", "\n", "void main(void)\n", "{\n", " vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", " //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " //SafeScreenTexCoord = gl_FragCoord.xyxy * vec4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n", " // slight water animation via 2 layer scrolling (todo: tweak)\n", "#ifdef USEALPHAGENVERTEX\n", " vec4 distort = DistortScaleRefractReflect * VertexColor.a;\n", " float reflectoffset = ReflectOffset * VertexColor.a;\n", " float reflectfactor = ReflectFactor * VertexColor.a;\n", " vec4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), VertexColor.a);\n", "#else\n", " vec4 distort = DistortScaleRefractReflect;\n", " float reflectoffset = ReflectOffset;\n", " float reflectfactor = ReflectFactor;\n", " vec4 refractcolor = RefractColor;\n", "#endif\n", " #ifdef USENORMALMAPSCROLLBLEND\n", " vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", " normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", " vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(normal) + vec3(0.15)).xyxy * distort;\n", " #else\n", " vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * distort;\n", " #endif\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, 0.01)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(0.005, -0.01)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, 0.01)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy + vec2(-0.005, -0.01)).rgb) / 0.002);\n", " ScreenTexCoord.xy = mix(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n", " f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, 0.005)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(0.005, -0.005)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, 0.005)).rgb) / 0.002);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw + vec2(-0.005, -0.005)).rgb) / 0.002);\n", " ScreenTexCoord.zw = mix(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n", " float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * reflectfactor + reflectoffset;\n", " dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * refractcolor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n", "}\n", "#endif\n", "#else // !MODE_WATER\n", "\n", "\n", "\n", "\n", "// common definitions between vertex shader and fragment shader:\n", "\n", "dp_varying mediump vec4 TexCoordSurfaceLightmap;\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "dp_varying mediump vec2 TexCoord2;\n", "#endif\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", "dp_varying mediump vec3 CubeVector;\n", "#endif\n", "\n", "#if (defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)) && defined(USEDIFFUSE)\n", "dp_varying mediump vec3 LightVector;\n", "#endif\n", "\n", "#ifdef USEEYEVECTOR\n", "dp_varying highp vec4 EyeVectorFogDepth;\n", "#endif\n", "\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", "dp_varying highp vec4 VectorS; // direction of S texcoord (sometimes crudely called tangent)\n", "dp_varying highp vec4 VectorT; // direction of T texcoord (sometimes crudely called binormal)\n", "dp_varying highp vec4 VectorR; // direction of R texcoord (surface normal)\n", "#else\n", "# ifdef USEFOG\n", "dp_varying highp vec3 EyeVectorModelSpace;\n", "# endif\n", "#endif\n", "\n", "#ifdef USEREFLECTION\n", "dp_varying highp vec4 ModelViewProjectionPosition;\n", "#endif\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "uniform highp vec3 LightPosition;\n", "dp_varying highp vec4 ModelViewPosition;\n", "#endif\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform highp vec3 LightPosition;\n", "#endif\n", "uniform highp vec3 EyePosition;\n", "#ifdef MODE_LIGHTDIRECTION\n", "uniform highp vec3 LightDir;\n", "#endif\n", "uniform highp vec4 FogPlane;\n", "\n", "#ifdef USESHADOWMAPORTHO\n", "dp_varying highp vec3 ShadowMapTC;\n", "#endif\n", "\n", "#ifdef USEBOUNCEGRID\n", "dp_varying highp vec3 BounceGridTexCoord;\n", "#endif\n", "\n", "#ifdef MODE_DEFERREDGEOMETRY\n", "dp_varying highp float Depth;\n", "#endif\n", "\n", "\n", "\n", "\n", "\n", "\n", "// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n", "\n", "// fragment shader specific:\n", "#ifdef FRAGMENT_SHADER\n", "\n", "uniform sampler2D Texture_Normal;\n", "uniform sampler2D Texture_Color;\n", "uniform sampler2D Texture_Gloss;\n", "#ifdef USEGLOW\n", "uniform sampler2D Texture_Glow;\n", "#endif\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform sampler2D Texture_SecondaryNormal;\n", "uniform sampler2D Texture_SecondaryColor;\n", "uniform sampler2D Texture_SecondaryGloss;\n", "#ifdef USEGLOW\n", "uniform sampler2D Texture_SecondaryGlow;\n", "#endif\n", "#endif\n", "#ifdef USECOLORMAPPING\n", "uniform sampler2D Texture_Pants;\n", "uniform sampler2D Texture_Shirt;\n", "#endif\n", "#ifdef USEFOG\n", "#ifdef USEFOGHEIGHTTEXTURE\n", "uniform sampler2D Texture_FogHeightTexture;\n", "#endif\n", "uniform sampler2D Texture_FogMask;\n", "#endif\n", "#ifdef USELIGHTMAP\n", "uniform sampler2D Texture_Lightmap;\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n", "uniform sampler2D Texture_Deluxemap;\n", "#endif\n", "#ifdef USEREFLECTION\n", "uniform sampler2D Texture_Reflection;\n", "#endif\n", "\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "uniform sampler2D Texture_ScreenNormalMap;\n", "#endif\n", "#ifdef USEDEFERREDLIGHTMAP\n", "#ifdef USECELOUTLINES\n", "uniform sampler2D Texture_ScreenNormalMap;\n", "#endif\n", "uniform sampler2D Texture_ScreenDiffuse;\n", "uniform sampler2D Texture_ScreenSpecular;\n", "#endif\n", "\n", "uniform mediump vec3 Color_Pants;\n", "uniform mediump vec3 Color_Shirt;\n", "uniform mediump vec3 FogColor;\n", "\n", "#ifdef USEFOG\n", "uniform highp float FogRangeRecip;\n", "uniform highp float FogPlaneViewDist;\n", "uniform highp float FogHeightFade;\n", "vec3 FogVertex(vec4 surfacecolor)\n", "{\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", " vec3 EyeVectorModelSpace = vec3(VectorS.w, VectorT.w, VectorR.w);\n", "#endif\n", " float FogPlaneVertexDist = EyeVectorFogDepth.w;\n", " float fogfrac;\n", " vec3 fc = FogColor;\n", "#ifdef USEFOGALPHAHACK\n", " fc *= surfacecolor.a;\n", "#endif\n", "#ifdef USEFOGHEIGHTTEXTURE\n", " vec4 fogheightpixel = dp_texture2D(Texture_FogHeightTexture, vec2(1,1) + vec2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n", " fogfrac = fogheightpixel.a;\n", " return mix(fogheightpixel.rgb * fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, cast_myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", "#else\n", "# ifdef USEFOGOUTSIDE\n", " fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n", "# else\n", " fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n", "# endif\n", " return mix(fc, surfacecolor.rgb, dp_texture2D(Texture_FogMask, cast_myhalf2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef USEOFFSETMAPPING\n", "uniform mediump vec4 OffsetMapping_ScaleSteps;\n", "uniform mediump float OffsetMapping_Bias;\n", "#ifdef USEOFFSETMAPPING_LOD\n", "uniform mediump float OffsetMapping_LodDistance;\n", "#endif\n", "vec2 OffsetMapping(vec2 TexCoord, vec2 dPdx, vec2 dPdy)\n", "{\n", " float i;\n", " // distance-based LOD\n", "#ifdef USEOFFSETMAPPING_LOD\n", " //mediump float LODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", " //mediump vec4 ScaleSteps = vec4(OffsetMapping_ScaleSteps.x, OffsetMapping_ScaleSteps.y * LODFactor, OffsetMapping_ScaleSteps.z / LODFactor, OffsetMapping_ScaleSteps.w * LODFactor);\n", " mediump float GuessLODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", "#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", " // stupid workaround because 1-step and 2-step reliefmapping is void\n", " mediump float LODSteps = max(3.0, ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y));\n", "#else\n", " mediump float LODSteps = ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y);\n", "#endif\n", " mediump float LODFactor = LODSteps / OffsetMapping_ScaleSteps.y;\n", " mediump vec4 ScaleSteps = vec4(OffsetMapping_ScaleSteps.x, LODSteps, 1.0 / LODSteps, OffsetMapping_ScaleSteps.w * LODFactor);\n", "#else\n", " #define ScaleSteps OffsetMapping_ScaleSteps\n", "#endif\n", "#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", " float f;\n", " // 14 sample relief mapping: linear search and then binary search\n", " // this basically steps forward a small amount repeatedly until it finds\n", " // itself inside solid, then jitters forward and back using decreasing\n", " // amounts to find the impact\n", " //vec3 OffsetVector = vec3(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * ScaleSteps.x) * vec2(-1, 1), -1);\n", " //vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xy) * ScaleSteps.x * vec2(-1, 1), -1);\n", " vec3 OffsetVector = vec3(normalize(EyeVectorFogDepth.xyz).xy * ScaleSteps.x * vec2(-1, 1), -1);\n", " vec3 RT = vec3(vec2(TexCoord.xy - OffsetVector.xy*OffsetMapping_Bias), 1);\n", " OffsetVector *= ScaleSteps.z;\n", " for(i = 1.0; i < ScaleSteps.y; ++i)\n", " RT += OffsetVector * step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n", " for(i = 0.0, f = 1.0; i < ScaleSteps.w; ++i, f *= 0.5)\n", " RT += OffsetVector * (step(dp_textureGrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n", " return RT.xy;\n", "#else\n", " // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n", " //vec2 OffsetVector = vec2(EyeVectorFogDepth.xy * ((1.0 / EyeVectorFogDepth.z) * ScaleSteps.x) * vec2(-1, 1));\n", " //vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xy) * ScaleSteps.x * vec2(-1, 1));\n", " vec2 OffsetVector = vec2(normalize(EyeVectorFogDepth.xyz).xy * ScaleSteps.x * vec2(-1, 1));\n", " OffsetVector *= ScaleSteps.z;\n", " for(i = 0.0; i < ScaleSteps.y; ++i)\n", " TexCoord += OffsetVector * ((1.0 - OffsetMapping_Bias) - dp_textureGrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n", " return TexCoord;\n", "#endif\n", "}\n", "#endif // USEOFFSETMAPPING\n", "\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n", "uniform sampler2D Texture_Attenuation;\n", "uniform samplerCube Texture_Cube;\n", "#endif\n", "\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", "\n", "#ifdef USESHADOWMAP2D\n", "# ifdef USESHADOWSAMPLER\n", "uniform sampler2DShadow Texture_ShadowMap2D;\n", "# else\n", "uniform sampler2D Texture_ShadowMap2D;\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAPVSDCT\n", "uniform samplerCube Texture_CubeProjection;\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", "uniform mediump vec2 ShadowMap_TextureScale;\n", "uniform mediump vec4 ShadowMap_Parameters;\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", "# ifdef USESHADOWMAPORTHO\n", "# define GetShadowMapTC2D(dir) (min(dir, ShadowMap_Parameters.xyz))\n", "# else\n", "# ifdef USESHADOWMAPVSDCT\n", "vec3 GetShadowMapTC2D(vec3 dir)\n", "{\n", " vec3 adir = abs(dir);\n", " float m = max(max(adir.x, adir.y), adir.z);\n", " vec4 proj = dp_textureCube(Texture_CubeProjection, dir);\n", "#ifdef USEDEPTHRGB\n", " return vec3(mix(dir.xy, dir.zz, proj.xy) * (ShadowMap_Parameters.x / m) + proj.zw * ShadowMap_Parameters.z, m + 64.0 * ShadowMap_Parameters.w);\n", "#else\n", " vec2 mparams = ShadowMap_Parameters.xy / m;\n", " return vec3(mix(dir.xy, dir.zz, proj.xy) * mparams.x + proj.zw * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", "#endif\n", "}\n", "# else\n", "vec3 GetShadowMapTC2D(vec3 dir)\n", "{\n", " vec3 adir = abs(dir);\n", " float m; vec4 proj;\n", " if (adir.x > adir.y) { m = adir.x; proj = vec4(dir.zyx, 0.5); } else { m = adir.y; proj = vec4(dir.xzy, 1.5); }\n", " if (adir.z > m) { m = adir.z; proj = vec4(dir, 2.5); }\n", "#ifdef USEDEPTHRGB\n", " return vec3(proj.xy * (ShadowMap_Parameters.x / m) + vec2(0.5,0.5) + vec2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, m + 64.0 * ShadowMap_Parameters.w);\n", "#else\n", " vec2 mparams = ShadowMap_Parameters.xy / m;\n", " return vec3(proj.xy * mparams.x + vec2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", "#endif\n", "}\n", "# endif\n", "# endif\n", "#endif // defined(USESHADOWMAP2D)\n", "\n", "# ifdef USESHADOWMAP2D\n", "float ShadowMapCompare(vec3 dir)\n", "{\n", " vec3 shadowmaptc = GetShadowMapTC2D(dir);\n", " float f;\n", "\n", "# ifdef USEDEPTHRGB\n", "# ifdef USESHADOWMAPPCF\n", "# define texval(x, y) decodedepthmacro(dp_texture2D(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale))\n", "# if USESHADOWMAPPCF > 1\n", " vec2 center = shadowmaptc.xy - 0.5, offset = fract(center);\n", " center *= ShadowMap_TextureScale;\n", " vec4 row1 = step(shadowmaptc.z, vec4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", " vec4 row2 = step(shadowmaptc.z, vec4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", " vec4 row3 = step(shadowmaptc.z, vec4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", " vec4 row4 = step(shadowmaptc.z, vec4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", " vec4 cols = row2 + row3 + mix(row1, row4, offset.y);\n", " f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", "# else\n", " vec2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = fract(shadowmaptc.xy);\n", " vec3 row1 = step(shadowmaptc.z, vec3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", " vec3 row2 = step(shadowmaptc.z, vec3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", " vec3 row3 = step(shadowmaptc.z, vec3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", " vec3 cols = row2 + mix(row1, row3, offset.y);\n", " f = dot(mix(cols.xy, cols.yz, offset.x), vec2(0.25));\n", "# endif\n", "# else\n", " f = step(shadowmaptc.z, decodedepthmacro(dp_texture2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale)));\n", "# endif\n", "# else\n", "# ifdef USESHADOWSAMPLER\n", "# ifdef USESHADOWMAPPCF\n", "# define texval(off) dp_shadow2D(Texture_ShadowMap2D, vec3(off, shadowmaptc.z)) \n", " vec2 offset = fract(shadowmaptc.xy - 0.5);\n", " vec4 size = vec4(offset + 1.0, 2.0 - offset);\n", "# if USESHADOWMAPPCF > 1\n", " vec2 center = (shadowmaptc.xy - offset + 0.5)*ShadowMap_TextureScale;\n", " vec4 weight = (vec4(-1.5, -1.5, 2.0, 2.0) + (shadowmaptc.xy - 0.5*offset).xyxy)*ShadowMap_TextureScale.xyxy;\n", " f = (1.0/25.0)*dot(size.zxzx*size.wwyy, vec4(texval(weight.xy), texval(weight.zy), texval(weight.xw), texval(weight.zw))) +\n", " (2.0/25.0)*dot(size, vec4(texval(vec2(weight.z, center.y)), texval(vec2(center.x, weight.w)), texval(vec2(weight.x, center.y)), texval(vec2(center.x, weight.y)))) +\n", " (4.0/25.0)*texval(center);\n", "# else\n", " vec4 weight = (vec4(1.0, 1.0, -0.5, -0.5) + (shadowmaptc.xy - 0.5*offset).xyxy)*ShadowMap_TextureScale.xyxy;\n", " f = (1.0/9.0)*dot(size.zxzx*size.wwyy, vec4(texval(weight.zw), texval(weight.xw), texval(weight.zy), texval(weight.xy)));\n", "# endif \n", "# else\n", " f = dp_shadow2D(Texture_ShadowMap2D, vec3(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z));\n", "# endif\n", "# else\n", "# ifdef USESHADOWMAPPCF\n", "# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n", "# ifdef GL_ARB_texture_gather\n", "# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, ivec2(x, y))\n", "# else\n", "# define texval(x, y) texture4(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale)\n", "# endif\n", " vec2 offset = fract(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n", "# if USESHADOWMAPPCF > 1\n", " vec4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n", " vec4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n", " vec4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n", " vec4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n", " vec4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n", " vec4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n", " vec4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n", " vec4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n", " vec4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n", " vec4 locols = vec4(group1.ab, group3.ab);\n", " vec4 hicols = vec4(group7.rg, group9.rg);\n", " locols.yz += group2.ab;\n", " hicols.yz += group8.rg;\n", " vec4 midcols = vec4(group1.rg, group3.rg) + vec4(group7.ab, group9.ab) +\n", " vec4(group4.rg, group6.rg) + vec4(group4.ab, group6.ab) +\n", " mix(locols, hicols, offset.y);\n", " vec4 cols = group5 + vec4(group2.rg, group8.ab);\n", " cols.xyz += mix(midcols.xyz, midcols.yzw, offset.x);\n", " f = dot(cols, vec4(1.0/25.0));\n", "# else\n", " vec4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n", " vec4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n", " vec4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n", " vec4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n", " vec4 cols = vec4(group1.rg, group2.rg) + vec4(group3.ab, group4.ab) +\n", " mix(vec4(group1.ab, group2.ab), vec4(group3.rg, group4.rg), offset.y);\n", " f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", "# endif\n", "# else\n", "# ifdef GL_EXT_gpu_shader4\n", "# define texval(x, y) dp_textureOffset(Texture_ShadowMap2D, center, x, y).r\n", "# else\n", "# define texval(x, y) dp_texture2D(Texture_ShadowMap2D, center + vec2(x, y)*ShadowMap_TextureScale).r \n", "# endif\n", "# if USESHADOWMAPPCF > 1\n", " vec2 center = shadowmaptc.xy - 0.5, offset = fract(center);\n", " center *= ShadowMap_TextureScale;\n", " vec4 row1 = step(shadowmaptc.z, vec4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", " vec4 row2 = step(shadowmaptc.z, vec4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", " vec4 row3 = step(shadowmaptc.z, vec4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", " vec4 row4 = step(shadowmaptc.z, vec4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", " vec4 cols = row2 + row3 + mix(row1, row4, offset.y);\n", " f = dot(mix(cols.xyz, cols.yzw, offset.x), vec3(1.0/9.0));\n", "# else\n", " vec2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = fract(shadowmaptc.xy);\n", " vec3 row1 = step(shadowmaptc.z, vec3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", " vec3 row2 = step(shadowmaptc.z, vec3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", " vec3 row3 = step(shadowmaptc.z, vec3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", " vec3 cols = row2 + mix(row1, row3, offset.y);\n", " f = dot(mix(cols.xy, cols.yz, offset.x), vec2(0.25));\n", "# endif\n", "# endif\n", "# else\n", " f = step(shadowmaptc.z, dp_texture2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n", "# endif\n", "# endif\n", "# endif\n", "# ifdef USESHADOWMAPORTHO\n", " return mix(ShadowMap_Parameters.w, 1.0, f);\n", "# else\n", " return f;\n", "# endif\n", "}\n", "# endif\n", "#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", "#endif // FRAGMENT_SHADER\n", "\n", "\n", "\n", "\n", "#ifdef MODE_DEFERREDGEOMETRY\n", "#ifdef VERTEX_SHADER\n", "uniform highp mat4 TexMatrix;\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform highp mat4 BackgroundTexMatrix;\n", "#endif\n", "uniform highp mat4 ModelViewMatrix;\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", " mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", " vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", "#define Attrib_Position SkeletalVertex\n", "#define Attrib_TexCoord1 SkeletalSVector\n", "#define Attrib_TexCoord2 SkeletalTVector\n", "#define Attrib_TexCoord3 SkeletalNormal\n", "#endif\n", " TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " VertexColor = Attrib_Color;\n", " TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n", "#endif\n", "\n", " // transform unnormalized eye direction into tangent space\n", "#ifdef USEOFFSETMAPPING\n", " vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", " EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", " EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", " EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", " EyeVectorFogDepth.w = 0.0;\n", "#endif\n", "\n", " VectorS = (ModelViewMatrix * vec4(Attrib_TexCoord1.xyz, 0));\n", " VectorT = (ModelViewMatrix * vec4(Attrib_TexCoord2.xyz, 0));\n", " VectorR = (ModelViewMatrix * vec4(Attrib_TexCoord3.xyz, 0));\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", " Depth = (ModelViewMatrix * Attrib_Position).z;\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main(void)\n", "{\n", "#ifdef USEOFFSETMAPPING\n", " // apply offsetmapping\n", " vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n", " vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n", " vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n", "# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n", "#else\n", "# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n", "#endif\n", "\n", "#ifdef USEALPHAKILL\n", " if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n", " discard;\n", "#endif\n", "\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " float alpha = offsetMappedTexture2D(Texture_Color).a;\n", " float terrainblend = clamp(float(VertexColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n", " //float terrainblend = min(float(VertexColor.a) * alpha * 2.0, float(1.0));\n", " //float terrainblend = float(VertexColor.a) * alpha > 0.5;\n", "#endif\n", "\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " vec3 surfacenormal = mix(vec3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), vec3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - vec3(0.5, 0.5, 0.5);\n", " float a = mix(dp_texture2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n", "#else\n", " vec3 surfacenormal = vec3(offsetMappedTexture2D(Texture_Normal)) - vec3(0.5, 0.5, 0.5);\n", " float a = offsetMappedTexture2D(Texture_Gloss).a;\n", "#endif\n", "\n", " vec3 pixelnormal = normalize(surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz);\n", " dp_FragColor = vec4(pixelnormal.x, pixelnormal.y, Depth, a);\n", "}\n", "#endif // FRAGMENT_SHADER\n", "#else // !MODE_DEFERREDGEOMETRY\n", "\n", "\n", "\n", "\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "#ifdef VERTEX_SHADER\n", "uniform highp mat4 ModelViewMatrix;\n", "void main(void)\n", "{\n", " ModelViewPosition = ModelViewMatrix * Attrib_Position;\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "#ifdef FRAGMENT_SHADER\n", "uniform highp mat4 ViewToLight;\n", "// ScreenToDepth = vec2(Far / (Far - Near), Far * Near / (Near - Far));\n", "uniform highp vec2 ScreenToDepth;\n", "uniform myhalf3 DeferredColor_Ambient;\n", "uniform myhalf3 DeferredColor_Diffuse;\n", "#ifdef USESPECULAR\n", "uniform myhalf3 DeferredColor_Specular;\n", "uniform myhalf SpecularPower;\n", "#endif\n", "uniform myhalf2 PixelToScreenTexCoord;\n", "void main(void)\n", "{\n", " // calculate viewspace pixel position\n", " vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", " vec3 position;\n", " // get the geometry information (depth, normal, specular exponent)\n", " myhalf4 normalmap = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord);\n", " // decode viewspace pixel normal\n", "// myhalf3 surfacenormal = normalize(normalmap.rgb - cast_myhalf3(0.5,0.5,0.5));\n", " myhalf3 surfacenormal = myhalf3(normalmap.rg, sqrt(1.0-dot(normalmap.rg, normalmap.rg)));\n", " // decode viewspace pixel position\n", "// position.z = decodedepthmacro(dp_texture2D(Texture_ScreenDepth, ScreenTexCoord));\n", " position.z = normalmap.b;\n", "// position.z = ScreenToDepth.y / (dp_texture2D(Texture_ScreenDepth, ScreenTexCoord).r + ScreenToDepth.x);\n", " position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n", "\n", " // now do the actual shading\n", " // surfacenormal = pixel normal in viewspace\n", " // LightVector = pixel to light in viewspace\n", " // CubeVector = pixel in lightspace\n", " // eyenormal = pixel to view direction in viewspace\n", " vec3 CubeVector = vec3(ViewToLight * vec4(position,1));\n", " myhalf fade = cast_myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n", "#ifdef USEDIFFUSE\n", " // calculate diffuse shading\n", " myhalf3 lightnormal = cast_myhalf3(normalize(LightPosition - position));\n", "SHADEDIFFUSE\n", "#endif\n", "#ifdef USESPECULAR\n", " // calculate directional shading\n", " myhalf3 eyenormal = -normalize(cast_myhalf3(position));\n", "SHADESPECULAR(SpecularPower * normalmap.a)\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", " fade *= ShadowMapCompare(CubeVector);\n", "#endif\n", "\n", "#ifdef USESPECULAR\n", " gl_FragData[0] = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", " gl_FragData[1] = vec4(DeferredColor_Specular * (specular * fade), 1.0);\n", "# ifdef USECUBEFILTER\n", " vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n", " gl_FragData[0].rgb *= cubecolor;\n", " gl_FragData[1].rgb *= cubecolor;\n", "# endif\n", "#else\n", "# ifdef USEDIFFUSE\n", " gl_FragColor = vec4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", "# else\n", " gl_FragColor = vec4(DeferredColor_Ambient * fade, 1.0);\n", "# endif\n", "# ifdef USECUBEFILTER\n", " vec3 cubecolor = dp_textureCube(Texture_Cube, CubeVector).rgb;\n", " gl_FragColor.rgb *= cubecolor;\n", "# endif\n", "#endif\n", "}\n", "#endif // FRAGMENT_SHADER\n", "#else // !MODE_DEFERREDLIGHTSOURCE\n", "\n", "\n", "\n", "\n", "#ifdef VERTEX_SHADER\n", "uniform highp mat4 TexMatrix;\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform highp mat4 BackgroundTexMatrix;\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform highp mat4 ModelToLight;\n", "#endif\n", "#ifdef USESHADOWMAPORTHO\n", "uniform highp mat4 ShadowMapMatrix;\n", "#endif\n", "#ifdef USEBOUNCEGRID\n", "uniform highp mat4 BounceGridMatrix;\n", "#endif\n", "void main(void)\n", "{\n", "#ifdef USESKELETAL\n", " ivec4 si0 = ivec4(Attrib_SkeletalIndex * 3.0);\n", " ivec4 si1 = si0 + ivec4(1, 1, 1, 1);\n", " ivec4 si2 = si0 + ivec4(2, 2, 2, 2);\n", " vec4 sw = Attrib_SkeletalWeight;\n", " vec4 SkeletalMatrix1 = Skeletal_Transform12[si0.x] * sw.x + Skeletal_Transform12[si0.y] * sw.y + Skeletal_Transform12[si0.z] * sw.z + Skeletal_Transform12[si0.w] * sw.w;\n", " vec4 SkeletalMatrix2 = Skeletal_Transform12[si1.x] * sw.x + Skeletal_Transform12[si1.y] * sw.y + Skeletal_Transform12[si1.z] * sw.z + Skeletal_Transform12[si1.w] * sw.w;\n", " vec4 SkeletalMatrix3 = Skeletal_Transform12[si2.x] * sw.x + Skeletal_Transform12[si2.y] * sw.y + Skeletal_Transform12[si2.z] * sw.z + Skeletal_Transform12[si2.w] * sw.w;\n", " mat4 SkeletalMatrix = mat4(SkeletalMatrix1, SkeletalMatrix2, SkeletalMatrix3, vec4(0.0, 0.0, 0.0, 1.0));\n", "// ivec4 si = ivec4(Attrib_SkeletalIndex);\n", "// mat4 SkeletalMatrix = Skeletal_Transform[si.x] * Attrib_SkeletalWeight.x + Skeletal_Transform[si.y] * Attrib_SkeletalWeight.y + Skeletal_Transform[si.z] * Attrib_SkeletalWeight.z + Skeletal_Transform[si.w] * Attrib_SkeletalWeight.w;\n", " mat3 SkeletalNormalMatrix = mat3(cross(SkeletalMatrix[1].xyz, SkeletalMatrix[2].xyz), cross(SkeletalMatrix[2].xyz, SkeletalMatrix[0].xyz), cross(SkeletalMatrix[0].xyz, SkeletalMatrix[1].xyz)); // is actually transpose(inverse(mat3(SkeletalMatrix))) * det(mat3(SkeletalMatrix))\n", " vec4 SkeletalVertex = Attrib_Position * SkeletalMatrix;\n", " SkeletalVertex.w = 1.0;\n", " vec3 SkeletalSVector = normalize(Attrib_TexCoord1.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalTVector = normalize(Attrib_TexCoord2.xyz * SkeletalNormalMatrix);\n", " vec3 SkeletalNormal = normalize(Attrib_TexCoord3.xyz * SkeletalNormalMatrix);\n", "#define Attrib_Position SkeletalVertex\n", "#define Attrib_TexCoord1 SkeletalSVector\n", "#define Attrib_TexCoord2 SkeletalTVector\n", "#define Attrib_TexCoord3 SkeletalNormal\n", "#endif\n", "\n", "#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", " VertexColor = Attrib_Color;\n", "#endif\n", " // copy the surface texcoord\n", "#ifdef USELIGHTMAP\n", " TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, Attrib_TexCoord4.xy);\n", "#else\n", " TexCoordSurfaceLightmap = vec4((TexMatrix * Attrib_TexCoord0).xy, 0.0, 0.0);\n", "#endif\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " TexCoord2 = vec2(BackgroundTexMatrix * Attrib_TexCoord0);\n", "#endif\n", "\n", "#ifdef USEBOUNCEGRID\n", " BounceGridTexCoord = vec3(BounceGridMatrix * Attrib_Position);\n", "#ifdef USEBOUNCEGRIDDIRECTIONAL\n", " BounceGridTexCoord.z *= 0.125;\n", "#endif\n", "#endif\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", " // transform vertex position into light attenuation/cubemap space\n", " // (-1 to +1 across the light box)\n", " CubeVector = vec3(ModelToLight * Attrib_Position);\n", "\n", "# ifdef USEDIFFUSE\n", " // transform unnormalized light direction into tangent space\n", " // (we use unnormalized to ensure that it interpolates correctly and then\n", " // normalize it per pixel)\n", " vec3 lightminusvertex = LightPosition - Attrib_Position.xyz;\n", " LightVector.x = dot(lightminusvertex, Attrib_TexCoord1.xyz);\n", " LightVector.y = dot(lightminusvertex, Attrib_TexCoord2.xyz);\n", " LightVector.z = dot(lightminusvertex, Attrib_TexCoord3.xyz);\n", "# endif\n", "#endif\n", "\n", "#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n", " LightVector.x = dot(LightDir, Attrib_TexCoord1.xyz);\n", " LightVector.y = dot(LightDir, Attrib_TexCoord2.xyz);\n", " LightVector.z = dot(LightDir, Attrib_TexCoord3.xyz);\n", "#endif\n", "\n", " // transform unnormalized eye direction into tangent space\n", "#ifdef USEEYEVECTOR\n", " vec3 EyeRelative = EyePosition - Attrib_Position.xyz;\n", " EyeVectorFogDepth.x = dot(EyeRelative, Attrib_TexCoord1.xyz);\n", " EyeVectorFogDepth.y = dot(EyeRelative, Attrib_TexCoord2.xyz);\n", " EyeVectorFogDepth.z = dot(EyeRelative, Attrib_TexCoord3.xyz);\n", "#ifdef USEFOG\n", " EyeVectorFogDepth.w = dot(FogPlane, Attrib_Position);\n", "#else\n", " EyeVectorFogDepth.w = 0.0;\n", "#endif\n", "#endif\n", "\n", "\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(USEREFLECTCUBE) || defined(USEBOUNCEGRIDDIRECTIONAL)\n", "# ifdef USEFOG\n", " VectorS = vec4(Attrib_TexCoord1.xyz, EyePosition.x - Attrib_Position.x);\n", " VectorT = vec4(Attrib_TexCoord2.xyz, EyePosition.y - Attrib_Position.y);\n", " VectorR = vec4(Attrib_TexCoord3.xyz, EyePosition.z - Attrib_Position.z);\n", "# else\n", " VectorS = vec4(Attrib_TexCoord1, 0);\n", " VectorT = vec4(Attrib_TexCoord2, 0);\n", " VectorR = vec4(Attrib_TexCoord3, 0);\n", "# endif\n", "#else\n", "# ifdef USEFOG\n", " EyeVectorModelSpace = EyePosition - Attrib_Position.xyz;\n", "# endif\n", "#endif\n", "\n", " // transform vertex to clipspace (post-projection, but before perspective divide by W occurs)\n", " gl_Position = ModelViewProjectionMatrix * Attrib_Position;\n", "\n", "#ifdef USESHADOWMAPORTHO\n", " ShadowMapTC = vec3(ShadowMapMatrix * gl_Position);\n", "#endif\n", "\n", "#ifdef USEREFLECTION\n", " ModelViewProjectionPosition = gl_Position;\n", "#endif\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position);\n", "#endif\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "\n", "\n", "\n", "#ifdef FRAGMENT_SHADER\n", "#ifdef USEDEFERREDLIGHTMAP\n", "uniform myhalf2 PixelToScreenTexCoord;\n", "uniform myhalf3 DeferredMod_Diffuse;\n", "uniform myhalf3 DeferredMod_Specular;\n", "#endif\n", "uniform myhalf3 Color_Ambient;\n", "uniform myhalf3 Color_Diffuse;\n", "uniform myhalf3 Color_Specular;\n", "uniform myhalf SpecularPower;\n", "#ifdef USEGLOW\n", "uniform myhalf3 Color_Glow;\n", "#endif\n", "uniform myhalf Alpha;\n", "#ifdef USEREFLECTION\n", "uniform mediump vec4 DistortScaleRefractReflect;\n", "uniform mediump vec4 ScreenScaleRefractReflect;\n", "uniform mediump vec4 ScreenCenterRefractReflect;\n", "uniform mediump vec4 ReflectColor;\n", "#endif\n", "#ifdef USEREFLECTCUBE\n", "uniform highp mat4 ModelToReflectCube;\n", "uniform sampler2D Texture_ReflectMask;\n", "uniform samplerCube Texture_ReflectCube;\n", "#endif\n", "#ifdef MODE_LIGHTDIRECTION\n", "uniform myhalf3 LightColor;\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform myhalf3 LightColor;\n", "#endif\n", "#ifdef USEBOUNCEGRID\n", "uniform sampler3D Texture_BounceGrid;\n", "uniform float BounceGridIntensity;\n", "uniform highp mat4 BounceGridMatrix;\n", "#endif\n", "uniform highp float ClientTime;\n", "#ifdef USENORMALMAPSCROLLBLEND\n", "uniform highp vec2 NormalmapScrollBlend;\n", "#endif\n", "#ifdef USEOCCLUDE\n", "uniform occludeQuery {\n", " uint visiblepixels;\n", " uint allpixels;\n", "};\n", "#endif\n", "void main(void)\n", "{\n", "#ifdef USEOFFSETMAPPING\n", " // apply offsetmapping\n", " vec2 dPdx = dp_offsetmapping_dFdx(TexCoordSurfaceLightmap.xy);\n", " vec2 dPdy = dp_offsetmapping_dFdy(TexCoordSurfaceLightmap.xy);\n", " vec2 TexCoordOffset = OffsetMapping(TexCoordSurfaceLightmap.xy, dPdx, dPdy);\n", "# define offsetMappedTexture2D(t) dp_textureGrad(t, TexCoordOffset, dPdx, dPdy)\n", "# define TexCoord TexCoordOffset\n", "#else\n", "# define offsetMappedTexture2D(t) dp_texture2D(t, TexCoordSurfaceLightmap.xy)\n", "# define TexCoord TexCoordSurfaceLightmap.xy\n", "#endif\n", "\n", " // combine the diffuse textures (base, pants, shirt)\n", " myhalf4 color = cast_myhalf4(offsetMappedTexture2D(Texture_Color));\n", "#ifdef USEALPHAKILL\n", " if (color.a < 0.5)\n", " discard;\n", "#endif\n", " color.a *= Alpha;\n", "#ifdef USECOLORMAPPING\n", " color.rgb += cast_myhalf3(offsetMappedTexture2D(Texture_Pants)) * Color_Pants + cast_myhalf3(offsetMappedTexture2D(Texture_Shirt)) * Color_Shirt;\n", "#endif\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "#ifdef USEBOTHALPHAS\n", " myhalf4 color2 = cast_myhalf4(dp_texture2D(Texture_SecondaryColor, TexCoord2));\n", " myhalf terrainblend = clamp(cast_myhalf(VertexColor.a) * color.a, cast_myhalf(1.0 - color2.a), cast_myhalf(1.0));\n", " color.rgb = mix(color2.rgb, color.rgb, terrainblend);\n", "#else\n", " myhalf terrainblend = clamp(cast_myhalf(VertexColor.a) * color.a * 2.0 - 0.5, cast_myhalf(0.0), cast_myhalf(1.0));\n", " //myhalf terrainblend = min(cast_myhalf(VertexColor.a) * color.a * 2.0, cast_myhalf(1.0));\n", " //myhalf terrainblend = cast_myhalf(VertexColor.a) * color.a > 0.5;\n", " color.rgb = mix(cast_myhalf3(dp_texture2D(Texture_SecondaryColor, TexCoord2)), color.rgb, terrainblend);\n", "#endif\n", " color.a = 1.0;\n", " //color = mix(cast_myhalf4(1, 0, 0, 1), color, terrainblend);\n", "#endif\n", "#ifdef USEALPHAGENVERTEX\n", " color.a *= VertexColor.a;\n", "#endif\n", "\n", " // get the surface normal\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " myhalf3 surfacenormal = normalize(mix(cast_myhalf3(dp_texture2D(Texture_SecondaryNormal, TexCoord2)), cast_myhalf3(offsetMappedTexture2D(Texture_Normal)), terrainblend) - cast_myhalf3(0.5, 0.5, 0.5));\n", "#else\n", " myhalf3 surfacenormal = normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5, 0.5, 0.5));\n", "#endif\n", "\n", " // get the material colors\n", " myhalf3 diffusetex = color.rgb;\n", "#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", "# ifdef USEVERTEXTEXTUREBLEND\n", " myhalf4 glosstex = mix(cast_myhalf4(dp_texture2D(Texture_SecondaryGloss, TexCoord2)), cast_myhalf4(offsetMappedTexture2D(Texture_Gloss)), terrainblend);\n", "# else\n", " myhalf4 glosstex = cast_myhalf4(offsetMappedTexture2D(Texture_Gloss));\n", "# endif\n", "#endif\n", "\n", "#ifdef USEREFLECTCUBE\n", " vec3 TangentReflectVector = reflect(-EyeVectorFogDepth.xyz, surfacenormal);\n", " vec3 ModelReflectVector = TangentReflectVector.x * VectorS.xyz + TangentReflectVector.y * VectorT.xyz + TangentReflectVector.z * VectorR.xyz;\n", " vec3 ReflectCubeTexCoord = vec3(ModelToReflectCube * vec4(ModelReflectVector, 0));\n", " diffusetex += cast_myhalf3(offsetMappedTexture2D(Texture_ReflectMask)) * cast_myhalf3(dp_textureCube(Texture_ReflectCube, ReflectCubeTexCoord));\n", "#endif\n", "\n", "#ifdef USESPECULAR\n", " myhalf3 eyenormal = normalize(cast_myhalf3(EyeVectorFogDepth.xyz));\n", "#endif\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", " // light source\n", "#ifdef USEDIFFUSE\n", " myhalf3 lightnormal = cast_myhalf3(normalize(LightVector));\n", "SHADEDIFFUSE\n", " color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n", "#ifdef USESPECULAR\n", "SHADESPECULAR(SpecularPower * glosstex.a)\n", " color.rgb += glosstex.rgb * (specular * Color_Specular);\n", "#endif\n", "#else\n", " color.rgb = diffusetex * Color_Ambient;\n", "#endif\n", " color.rgb *= LightColor;\n", " color.rgb *= cast_myhalf(dp_texture2D(Texture_Attenuation, vec2(length(CubeVector), 0.0)));\n", "#if defined(USESHADOWMAP2D)\n", " color.rgb *= ShadowMapCompare(CubeVector);\n", "#endif\n", "# ifdef USECUBEFILTER\n", " color.rgb *= cast_myhalf3(dp_textureCube(Texture_Cube, CubeVector));\n", "# endif\n", "#endif // MODE_LIGHTSOURCE\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTDIRECTION\n", " #define SHADING\n", " #ifdef USEDIFFUSE\n", " myhalf3 lightnormal = cast_myhalf3(normalize(LightVector));\n", " #endif\n", " #define lightcolor LightColor\n", "#endif // MODE_LIGHTDIRECTION\n", "#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " #define SHADING\n", " // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n", " myhalf3 lightnormal_modelspace = cast_myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + cast_myhalf3(-1.0, -1.0, -1.0);\n", " myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", " // convert modelspace light vector to tangentspace\n", " myhalf3 lightnormal;\n", " lightnormal.x = dot(lightnormal_modelspace, cast_myhalf3(VectorS));\n", " lightnormal.y = dot(lightnormal_modelspace, cast_myhalf3(VectorT));\n", " lightnormal.z = dot(lightnormal_modelspace, cast_myhalf3(VectorR));\n", " lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n", " // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n", " // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n", " // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n", " // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n", " // to map the luxels to coordinates on the draw surfaces), which also causes\n", " // deluxemaps to be wrong because light contributions from the wrong side of the surface\n", " // are added up. To prevent divisions by zero or strong exaggerations, a max()\n", " // nudge is done here at expense of some additional fps. This is ONLY needed for\n", " // deluxemaps, tangentspace deluxemap avoid this problem by design.\n", " lightcolor *= 1.0 / max(0.25, lightnormal.z);\n", "#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", "#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " #define SHADING\n", " // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n", " myhalf3 lightnormal = cast_myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + cast_myhalf3(-1.0, -1.0, -1.0);\n", " myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", " #define SHADING\n", " // forced deluxemap on lightmapped/vertexlit surfaces\n", " myhalf3 lightnormal = cast_myhalf3(0.0, 0.0, 1.0);\n", " #ifdef USELIGHTMAP\n", " myhalf3 lightcolor = cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n", " #else\n", " myhalf3 lightcolor = cast_myhalf3(VertexColor.rgb);\n", " #endif\n", "#endif\n", "#ifdef MODE_FAKELIGHT\n", " #define SHADING\n", " myhalf3 lightnormal = cast_myhalf3(normalize(EyeVectorFogDepth.xyz));\n", " myhalf3 lightcolor = cast_myhalf3(1.0);\n", "#endif // MODE_FAKELIGHT\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTMAP\n", " color.rgb = diffusetex * (Color_Ambient + cast_myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw)) * Color_Diffuse);\n", "#endif // MODE_LIGHTMAP\n", "#ifdef MODE_VERTEXCOLOR\n", " color.rgb = diffusetex * (Color_Ambient + cast_myhalf3(VertexColor.rgb) * Color_Diffuse);\n", "#endif // MODE_VERTEXCOLOR\n", "#ifdef MODE_FLATCOLOR\n", " color.rgb = diffusetex * Color_Ambient;\n", "#endif // MODE_FLATCOLOR\n", "\n", "\n", "\n", "\n", "#ifdef SHADING\n", "# ifdef USEDIFFUSE\n", "SHADEDIFFUSE\n", "# ifdef USESPECULAR\n", "SHADESPECULAR(SpecularPower * glosstex.a)\n", " color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n", "# else\n", " color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n", "# endif\n", "# else\n", " color.rgb = diffusetex * Color_Ambient;\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAPORTHO\n", " color.rgb *= ShadowMapCompare(ShadowMapTC);\n", "#endif\n", "\n", "#ifdef USEDEFERREDLIGHTMAP\n", " vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", " color.rgb += diffusetex * cast_myhalf3(dp_texture2D(Texture_ScreenDiffuse, ScreenTexCoord)) * DeferredMod_Diffuse;\n", " color.rgb += glosstex.rgb * cast_myhalf3(dp_texture2D(Texture_ScreenSpecular, ScreenTexCoord)) * DeferredMod_Specular;\n", "// color.rgb = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord).rgb * vec3(1.0, 1.0, 0.001);\n", "#endif\n", "\n", "#ifdef USEBOUNCEGRID\n", "#ifdef USEBOUNCEGRIDDIRECTIONAL\n", "// myhalf4 bouncegrid_coeff1 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord ));\n", "// myhalf4 bouncegrid_coeff2 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.125))) * 2.0 + cast_myhalf4(-1.0, -1.0, -1.0, -1.0);\n", " myhalf4 bouncegrid_coeff3 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.250)));\n", " myhalf4 bouncegrid_coeff4 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.375)));\n", " myhalf4 bouncegrid_coeff5 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.500)));\n", " myhalf4 bouncegrid_coeff6 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.625)));\n", " myhalf4 bouncegrid_coeff7 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.750)));\n", " myhalf4 bouncegrid_coeff8 = cast_myhalf4(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord + vec3(0.0, 0.0, 0.875)));\n", " myhalf3 bouncegrid_dir = normalize(mat3(BounceGridMatrix) * (surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz));\n", " myhalf3 bouncegrid_dirp = max(cast_myhalf3(0.0, 0.0, 0.0), bouncegrid_dir);\n", " myhalf3 bouncegrid_dirn = max(cast_myhalf3(0.0, 0.0, 0.0), -bouncegrid_dir);\n", "// bouncegrid_dirp = bouncegrid_dirn = cast_myhalf3(1.0,1.0,1.0);\n", " myhalf3 bouncegrid_light = cast_myhalf3(\n", " dot(bouncegrid_coeff3.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff6.xyz, bouncegrid_dirn),\n", " dot(bouncegrid_coeff4.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff7.xyz, bouncegrid_dirn),\n", " dot(bouncegrid_coeff5.xyz, bouncegrid_dirp) + dot(bouncegrid_coeff8.xyz, bouncegrid_dirn));\n", " color.rgb += diffusetex * bouncegrid_light * BounceGridIntensity;\n", "// color.rgb = bouncegrid_dir.rgb * 0.5 + vec3(0.5, 0.5, 0.5);\n", "#else\n", " color.rgb += diffusetex * cast_myhalf3(dp_texture3D(Texture_BounceGrid, BounceGridTexCoord)) * BounceGridIntensity;\n", "#endif\n", "#endif\n", "\n", "#ifdef USEGLOW\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " color.rgb += mix(cast_myhalf3(dp_texture2D(Texture_SecondaryGlow, TexCoord2)), cast_myhalf3(offsetMappedTexture2D(Texture_Glow)), terrainblend) * Color_Glow;\n", "#else\n", " color.rgb += cast_myhalf3(offsetMappedTexture2D(Texture_Glow)) * Color_Glow;\n", "#endif\n", "#endif\n", "\n", "#ifdef USECELOUTLINES\n", "# ifdef USEDEFERREDLIGHTMAP\n", "// vec2 ScreenTexCoord = gl_FragCoord.xy * PixelToScreenTexCoord;\n", " vec4 ScreenTexCoordStep = vec4(PixelToScreenTexCoord.x, 0.0, 0.0, PixelToScreenTexCoord.y);\n", " vec4 DepthNeighbors;\n", "\n", " // enable to test ink on white geometry\n", "// color.rgb = vec3(1.0, 1.0, 1.0);\n", "\n", " // note: this seems to be negative\n", " float DepthCenter = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord).b;\n", "\n", " // edge detect method\n", "// DepthNeighbors.x = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord - ScreenTexCoordStep.xy).b;\n", "// DepthNeighbors.y = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + ScreenTexCoordStep.xy).b;\n", "// DepthNeighbors.z = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + ScreenTexCoordStep.zw).b;\n", "// DepthNeighbors.w = dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord - ScreenTexCoordStep.zw).b;\n", "// float DepthAverage = dot(DepthNeighbors, vec4(0.25, 0.25, 0.25, 0.25));\n", "// float DepthDelta = abs(dot(DepthNeighbors.xy, vec2(-1.0, 1.0))) + abs(dot(DepthNeighbors.zw, vec2(-1.0, 1.0)));\n", "// color.rgb *= max(0.5, 1.0 - max(0.0, abs(DepthCenter - DepthAverage) - 0.2 * DepthDelta) / (0.01 + 0.2 * DepthDelta));\n", "// color.rgb *= step(abs(DepthCenter - DepthAverage), 0.2 * DepthDelta); \n", "\n", " // shadow method\n", " float DepthScale1 = 4.0 / DepthCenter; // inner ink (shadow on object)\n", "// float DepthScale1 = -4.0 / DepthCenter; // outer ink (shadow around object)\n", "// float DepthScale1 = 0.003;\n", " float DepthScale2 = DepthScale1 / 2.0;\n", "// float DepthScale3 = DepthScale1 / 4.0;\n", " float DepthBias1 = -DepthCenter * DepthScale1;\n", " float DepthBias2 = -DepthCenter * DepthScale2;\n", "// float DepthBias3 = -DepthCenter * DepthScale3;\n", " float DepthShadow = max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-1.0, 0.0)).b * DepthScale1 + DepthBias1)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 1.0, 0.0)).b * DepthScale1 + DepthBias1)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -1.0)).b * DepthScale1 + DepthBias1)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 1.0)).b * DepthScale1 + DepthBias1)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-2.0, 0.0)).b * DepthScale2 + DepthBias2)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 2.0, 0.0)).b * DepthScale2 + DepthBias2)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -2.0)).b * DepthScale2 + DepthBias2)\n", " + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 2.0)).b * DepthScale2 + DepthBias2)\n", "// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2(-3.0, 0.0)).b * DepthScale3 + DepthBias3)\n", "// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 3.0, 0.0)).b * DepthScale3 + DepthBias3)\n", "// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, -3.0)).b * DepthScale3 + DepthBias3)\n", "// + max(0.0, dp_texture2D(Texture_ScreenNormalMap, ScreenTexCoord + PixelToScreenTexCoord * vec2( 0.0, 3.0)).b * DepthScale3 + DepthBias3)\n", " - 0.0;\n", " color.rgb *= 1.0 - max(0.0, min(DepthShadow, 1.0));\n", "// color.r = DepthCenter / -1024.0;\n", "# endif\n", "#endif\n", "\n", "#ifdef USEFOG\n", " color.rgb = FogVertex(color);\n", "#endif\n", "\n", " // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n", "#ifdef USEREFLECTION\n", " vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", " //vec4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n", " #ifdef USENORMALMAPSCROLLBLEND\n", "# ifdef USEOFFSETMAPPING\n", " vec3 normal = dp_textureGrad(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y, dPdx*NormalmapScrollBlend.y, dPdy*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", "# else\n", " vec3 normal = dp_texture2D(Texture_Normal, (TexCoord + vec2(0.08, 0.08)*ClientTime*NormalmapScrollBlend.x*0.5)*NormalmapScrollBlend.y).rgb - vec3(1.0);\n", "# endif\n", " normal += dp_texture2D(Texture_Normal, (TexCoord + vec2(-0.06, -0.09)*ClientTime*NormalmapScrollBlend.x)*NormalmapScrollBlend.y*0.75).rgb;\n", " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(normal))).xy * DistortScaleRefractReflect.zw;\n", " #else\n", " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(cast_myhalf3(offsetMappedTexture2D(Texture_Normal)) - cast_myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n", " #endif\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(dp_texture2D(Texture_Reflection, ScreenTexCoord + vec2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord = mix(SafeScreenTexCoord, ScreenTexCoord, f);\n", " color.rgb = mix(color.rgb, cast_myhalf3(dp_texture2D(Texture_Reflection, ScreenTexCoord)) * ReflectColor.rgb, ReflectColor.a);\n", "#endif\n", "#ifdef USEOCCLUDE\n", " color.rgb *= clamp(float(visiblepixels) / float(allpixels), 0.0, 1.0);\n", "#endif\n", "\n", " dp_FragColor = vec4(color);\n", "}\n", "#endif // FRAGMENT_SHADER\n", "\n", "#endif // !MODE_DEFERREDLIGHTSOURCE\n", "#endif // !MODE_DEFERREDGEOMETRY\n", "#endif // !MODE_WATER\n", "#endif // !MODE_REFRACTION\n", "#endif // !MODE_BLOOMBLUR\n", "#endif // !MODE_GENERIC\n", "#endif // !MODE_POSTPROCESS\n", "#endif // !MODE_DEPTH_OR_SHADOW\n", darkplaces/mathlib.h0000664000175000017500000004310613067716220013760 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // mathlib.h #ifndef MATHLIB_H #define MATHLIB_H #include "qtypes.h" #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif struct mplane_s; extern vec3_t vec3_origin; #define float_nanmask (0x7F800000) #define double_nanmask (0x7FF8000000000000) #define FLOAT_IS_NAN(x) (((*(int *)&x)&float_nanmask)==float_nanmask) #define DOUBLE_IS_NAN(x) (((*(long long *)&x)&double_nanmask)==double_nanmask) #ifdef VEC_64 #define VEC_IS_NAN(x) DOUBLE_IS_NAN(x) #else #define VEC_IS_NAN(x) FLOAT_IS_NAN(x) #endif #ifdef PRVM_64 #define PRVM_IS_NAN(x) DOUBLE_IS_NAN(x) #else #define PRVM_IS_NAN(x) FLOAT_IS_NAN(x) #endif #define bound(min,num,max) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) #ifndef min #define min(A,B) ((A) < (B) ? (A) : (B)) #define max(A,B) ((A) > (B) ? (A) : (B)) #endif /// LordHavoc: this function never returns exactly MIN or exactly MAX, because /// of a QuakeC bug in id1 where the line /// self.nextthink = self.nexthink + random() * 0.5; /// can result in 0 (self.nextthink is 0 at this point in the code to begin /// with), causing "stone monsters" that never spawned properly, also MAX is /// avoided because some people use random() as an index into arrays or for /// loop conditions, where hitting exactly MAX may be a fatal error #define lhrandom(MIN,MAX) (((double)(rand() + 0.5) / ((double)RAND_MAX + 1)) * ((MAX)-(MIN)) + (MIN)) #define invpow(base,number) (log(number) / log(base)) /// returns log base 2 of "n" /// \WARNING: "n" MUST be a power of 2! #define log2i(n) ((((n) & 0xAAAAAAAA) != 0 ? 1 : 0) | (((n) & 0xCCCCCCCC) != 0 ? 2 : 0) | (((n) & 0xF0F0F0F0) != 0 ? 4 : 0) | (((n) & 0xFF00FF00) != 0 ? 8 : 0) | (((n) & 0xFFFF0000) != 0 ? 16 : 0)) /// \TODO: what is this function supposed to do? #define bit2i(n) log2i((n) << 1) /// boolean XOR (why doesn't C have the ^^ operator for this purpose?) #define boolxor(a,b) (!(a) != !(b)) /// returns the smallest integer greater than or equal to "value", or 0 if "value" is too big unsigned int CeilPowerOf2(unsigned int value); #define DEG2RAD(a) ((a) * ((float) M_PI / 180.0f)) #define RAD2DEG(a) ((a) * (180.0f / (float) M_PI)) #define ANGLEMOD(a) ((a) - 360.0 * floor((a) / 360.0)) #define DotProduct2(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]) #define Vector2Clear(a) ((a)[0]=(a)[1]=0) #define Vector2Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])) #define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) #define Vector2Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1])) #define Vector2Set(a,b,c) ((a)[0]=(b),(a)[1]=(c)) #define Vector2Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale)) #define Vector2Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct2((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;} #define DotProduct4(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]+(a)[3]*(b)[3]) #define Vector4Clear(a) ((a)[0]=(a)[1]=(a)[2]=(a)[3]=0) #define Vector4Compare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])&&((a)[3]==(b)[3])) #define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) #define Vector4Negate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2]),(b)[3]=-((a)[3])) #define Vector4Set(a,b,c,d,e) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d),(a)[3]=(e)) #define Vector4Normalize2(v,dest) {float ilength = (float) sqrt(DotProduct4((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;dest[3] = (v)[3] * ilength;} #define Vector4Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2],(c)[3]=(a)[3]-(b)[3]) #define Vector4Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2],(c)[3]=(a)[3]+(b)[3]) #define Vector4Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale),(out)[3] = (in)[3] * (scale)) #define Vector4Multiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2],(c)[3]=(a)[3]*(b)[3]) #define Vector4MA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2],(c)[3] = (a)[3] + (scale) * (b)[3]) #define Vector4Lerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2]), (c)[3] = (v1)[3] + (lerp) * ((v2)[3] - (v1)[3])) #define VectorNegate(a,b) ((b)[0]=-((a)[0]),(b)[1]=-((a)[1]),(b)[2]=-((a)[2])) #define VectorSet(a,b,c,d) ((a)[0]=(b),(a)[1]=(c),(a)[2]=(d)) #define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) #define DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) #define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) #define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) #define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) #define VectorMultiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2]) #define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) #define VectorNormalize(v) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} #define VectorNormalize2(v,dest) {float ilength = (float) sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0f / ilength;dest[0] = (v)[0] * ilength;dest[1] = (v)[1] * ilength;dest[2] = (v)[2] * ilength;} #define VectorNormalizeDouble(v) {double ilength = sqrt(DotProduct((v),(v)));if (ilength) ilength = 1.0 / ilength;(v)[0] *= ilength;(v)[1] *= ilength;(v)[2] *= ilength;} #define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) #define VectorDistance(a, b) (sqrt(VectorDistance2(a,b))) #define VectorLength(a) (sqrt((double)DotProduct(a, a))) #define VectorLength2(a) (DotProduct(a, a)) #define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) #define VectorScaleCast(in, scale, outtype, out) ((out)[0] = (outtype) ((in)[0] * (scale)),(out)[1] = (outtype) ((in)[1] * (scale)),(out)[2] = (outtype) ((in)[2] * (scale))) #define VectorCompare(a,b) (((a)[0]==(b)[0])&&((a)[1]==(b)[1])&&((a)[2]==(b)[2])) #define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2]) #define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2]) #define VectorMAM(scale1, b1, scale2, b2, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2]) #define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2]) #define VectorMAMAMAM(scale1, b1, scale2, b2, scale3, b3, scale4, b4, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0] + (scale4) * (b4)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1] + (scale4) * (b4)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2] + (scale4) * (b4)[2]) #define VectorRandom(v) do{(v)[0] = lhrandom(-1, 1);(v)[1] = lhrandom(-1, 1);(v)[2] = lhrandom(-1, 1);}while(DotProduct(v, v) > 1) #define VectorLerp(v1,lerp,v2,c) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2])) #define VectorReflect(a,r,b,c) do{double d;d = DotProduct((a), (b)) * -(1.0 + (r));VectorMA((a), (d), (b), (c));}while(0) #define BoxesOverlap(a,b,c,d) ((a)[0] <= (d)[0] && (b)[0] >= (c)[0] && (a)[1] <= (d)[1] && (b)[1] >= (c)[1] && (a)[2] <= (d)[2] && (b)[2] >= (c)[2]) #define BoxInsideBox(a,b,c,d) ((a)[0] >= (c)[0] && (b)[0] <= (d)[0] && (a)[1] >= (c)[1] && (b)[1] <= (d)[1] && (a)[2] >= (c)[2] && (b)[2] <= (d)[2]) #define TriangleBBoxOverlapsBox(a,b,c,d,e) (min((a)[0], min((b)[0], (c)[0])) < (e)[0] && max((a)[0], max((b)[0], (c)[0])) > (d)[0] && min((a)[1], min((b)[1], (c)[1])) < (e)[1] && max((a)[1], max((b)[1], (c)[1])) > (d)[1] && min((a)[2], min((b)[2], (c)[2])) < (e)[2] && max((a)[2], max((b)[2], (c)[2])) > (d)[2]) #define TriangleNormal(a,b,c,n) ( \ (n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \ (n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \ (n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \ ) /*! Fast PointInfrontOfTriangle. * subtracts v1 from v0 and v2, combined into a crossproduct, combined with a * dotproduct of the light location relative to the first point of the * triangle (any point works, since any triangle is obviously flat), and * finally a comparison to determine if the light is infront of the triangle * (the goal of this statement) we do not need to normalize the surface * normal because both sides of the comparison use it, therefore they are * both multiplied the same amount... furthermore a subtract can be done on * the point to eliminate one dotproduct * this is ((p - a) * cross(a-b,c-b)) */ #define PointInfrontOfTriangle(p,a,b,c) \ ( ((p)[0] - (a)[0]) * (((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1])) \ + ((p)[1] - (a)[1]) * (((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2])) \ + ((p)[2] - (a)[2]) * (((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0])) > 0) #if 0 // readable version, kept only for explanatory reasons int PointInfrontOfTriangle(const float *p, const float *a, const float *b, const float *c) { float dir0[3], dir1[3], normal[3]; // calculate two mostly perpendicular edge directions VectorSubtract(a, b, dir0); VectorSubtract(c, b, dir1); // we have two edge directions, we can calculate a third vector from // them, which is the direction of the surface normal (its magnitude // is not 1 however) CrossProduct(dir0, dir1, normal); // compare distance of light along normal, with distance of any point // of the triangle along the same normal (the triangle is planar, // I.E. flat, so all points give the same answer) return DotProduct(p, normal) > DotProduct(a, normal); } #endif #define lhcheeserand(seed) ((seed) = ((seed) * 987211u) ^ ((seed) >> 13u) ^ 914867) #define lhcheeserandom(seed,MIN,MAX) ((double)(lhcheeserand(seed) + 0.5) / ((double)4096.0*1024.0*1024.0) * ((MAX)-(MIN)) + (MIN)) #define VectorCheeseRandom(seed,v) do{(v)[0] = lhcheeserandom(seed,-1, 1);(v)[1] = lhcheeserandom(seed,-1, 1);(v)[2] = lhcheeserandom(seed,-1, 1);}while(DotProduct(v, v) > 1) #define VectorLehmerRandom(seed,v) do{(v)[0] = Math_crandomf(seed);(v)[1] = Math_crandomf(seed);(v)[2] = Math_crandomf(seed);}while(DotProduct(v, v) > 1) /* // LordHavoc: quaternion math, untested, don't know if these are correct, // need to add conversion to/from matrices // LordHavoc: later note: the matrix faq is useful: http://skal.planet-d.net/demo/matrixfaq.htm // LordHavoc: these are probably very wrong and I'm not sure I care, not used by anything // returns length of quaternion #define qlen(a) ((float) sqrt((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3])) // returns squared length of quaternion #define qlen2(a) ((a)[0]*(a)[0]+(a)[1]*(a)[1]+(a)[2]*(a)[2]+(a)[3]*(a)[3]) // makes a quaternion from x, y, z, and a rotation angle (in degrees) #define QuatMake(x,y,z,r,c)\ {\ if (r == 0)\ {\ (c)[0]=(float) ((x) * (1.0f / 0.0f));\ (c)[1]=(float) ((y) * (1.0f / 0.0f));\ (c)[2]=(float) ((z) * (1.0f / 0.0f));\ (c)[3]=(float) 1.0f;\ }\ else\ {\ float r2 = (r) * 0.5 * (M_PI / 180);\ float r2is = 1.0f / sin(r2);\ (c)[0]=(float) ((x)/r2is);\ (c)[1]=(float) ((y)/r2is);\ (c)[2]=(float) ((z)/r2is);\ (c)[3]=(float) (cos(r2));\ }\ } // makes a quaternion from a vector and a rotation angle (in degrees) #define QuatFromVec(a,r,c) QuatMake((a)[0],(a)[1],(a)[2],(r)) // copies a quaternion #define QuatCopy(a,c) {(c)[0]=(a)[0];(c)[1]=(a)[1];(c)[2]=(a)[2];(c)[3]=(a)[3];} #define QuatSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];(c)[3]=(a)[3]-(b)[3];} #define QuatAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];(c)[3]=(a)[3]+(b)[3];} #define QuatScale(a,b,c) {(c)[0]=(a)[0]*b;(c)[1]=(a)[1]*b;(c)[2]=(a)[2]*b;(c)[3]=(a)[3]*b;} // FIXME: this is wrong, do some more research on quaternions //#define QuatMultiply(a,b,c) {(c)[0]=(a)[0]*(b)[0];(c)[1]=(a)[1]*(b)[1];(c)[2]=(a)[2]*(b)[2];(c)[3]=(a)[3]*(b)[3];} // FIXME: this is wrong, do some more research on quaternions //#define QuatMultiplyAdd(a,b,d,c) {(c)[0]=(a)[0]*(b)[0]+d[0];(c)[1]=(a)[1]*(b)[1]+d[1];(c)[2]=(a)[2]*(b)[2]+d[2];(c)[3]=(a)[3]*(b)[3]+d[3];} #define qdist(a,b) ((float) sqrt(((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3]))) #define qdist2(a,b) (((b)[0]-(a)[0])*((b)[0]-(a)[0])+((b)[1]-(a)[1])*((b)[1]-(a)[1])+((b)[2]-(a)[2])*((b)[2]-(a)[2])+((b)[3]-(a)[3])*((b)[3]-(a)[3])) */ #define VectorCopy4(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];(b)[3]=(a)[3];} vec_t Length (vec3_t v); /// returns vector length float VectorNormalizeLength (vec3_t v); /// returns vector length float VectorNormalizeLength2 (vec3_t v, vec3_t dest); #define NUMVERTEXNORMALS 162 extern float m_bytenormals[NUMVERTEXNORMALS][3]; unsigned char NormalToByte(const vec3_t n); void ByteToNormal(unsigned char num, vec3_t n); void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]); void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]); void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); /// LordHavoc: proper matrix version of AngleVectors void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up); /// divVerent: improper matrix version of AngleVectors void AngleVectorsDuke3DFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up, double maxShearAngle); /// LordHavoc: builds a [3][4] matrix void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]); /// LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch); /// LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up); void VectorVectorsDouble(const double *forward, double *right, double *up); void PlaneClassify(struct mplane_s *p); int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p); int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist); void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec3_t outnear, vec3_t outfar); void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar); void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const struct mplane_s *p, vec_t *outnear, vec_t *outfar); void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outnear, vec_t *outfar); #define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) #define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) /// LordHavoc: minimal plane structure typedef struct tinyplane_s { float normal[3], dist; } tinyplane_t; typedef struct tinydoubleplane_s { double normal[3], dist; } tinydoubleplane_t; void RotatePointAroundVector(vec3_t dst, const vec3_t dir, const vec3_t point, float degrees); float RadiusFromBounds (const vec3_t mins, const vec3_t maxs); float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin); struct matrix4x4_s; /// print a matrix to the console void Matrix4x4_Print(const struct matrix4x4_s *in); int Math_atov(const char *s, prvm_vec3_t out); void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f); int LoopingFrameNumberFromDouble(double t, int loopframes); // implementation of 128bit Lehmer Random Number Generator with 2^126 period // https://en.wikipedia.org/Lehmer_random_number_generator typedef struct randomseed_s { unsigned int s[4]; } randomseed_t; void Math_RandomSeed_Reset(randomseed_t *r); void Math_RandomSeed_FromInt(randomseed_t *r, unsigned int n); unsigned long long Math_rand64(randomseed_t *r); float Math_randomf(randomseed_t *r); float Math_crandomf(randomseed_t *r); float Math_randomrangef(randomseed_t *r, float minf, float maxf); int Math_randomrangei(randomseed_t *r, int mini, int maxi); void Mathlib_Init(void); #endif darkplaces/r_sprites.c0000664000175000017500000003700013067716222014343 0ustar kalevkalev #include "quakedef.h" #include "r_shadow.h" extern cvar_t r_labelsprites_scale; extern cvar_t r_labelsprites_roundtopixels; extern cvar_t r_track_sprites; extern cvar_t r_track_sprites_flags; extern cvar_t r_track_sprites_scalew; extern cvar_t r_track_sprites_scaleh; extern cvar_t r_overheadsprites_perspective; extern cvar_t r_overheadsprites_pushback; extern cvar_t r_overheadsprites_scalex; extern cvar_t r_overheadsprites_scaley; #define TSF_ROTATE 1 #define TSF_ROTATE_CONTINOUSLY 2 // use same epsilon as in sv_phys.c, it's not in any header, that's why i redefine it // MIN_EPSILON is for accurateness' sake :) #ifndef EPSILON # define EPSILON (1.0f / 32.0f) # define MIN_EPSILON 0.0001f #endif /* R_Track_Sprite If the sprite is out of view, track it. `origin`, `left` and `up` are changed by this function to achive a rotation around the hotspot. --blub */ #define SIDE_TOP 1 #define SIDE_LEFT 2 #define SIDE_BOTTOM 3 #define SIDE_RIGHT 4 static void R_TrackSprite(const entity_render_t *ent, vec3_t origin, vec3_t left, vec3_t up, int *edge, float *dir_angle) { float distance; vec3_t bCoord; // body coordinates of object unsigned int i; // temporarily abuse bCoord as the vector player->sprite-origin VectorSubtract(origin, r_refdef.view.origin, bCoord); distance = VectorLength(bCoord); // Now get the bCoords :) Matrix4x4_Transform(&r_refdef.view.inverse_matrix, origin, bCoord); *edge = 0; // FIXME::should assume edge == 0, which is correct currently for(i = 0; i < 4; ++i) { if(PlaneDiff(origin, &r_refdef.view.frustum[i]) < -EPSILON) break; } // If it wasn't outside a plane, no tracking needed if(i < 4) { float x, y; // screen X and Y coordinates float ax, ay; // absolute coords, used for division // I divide x and y by the greater absolute value to get ranges -1.0 to +1.0 bCoord[2] *= r_refdef.view.frustum_x; bCoord[1] *= r_refdef.view.frustum_y; //Con_Printf("%f %f %f\n", bCoord[0], bCoord[1], bCoord[2]); ax = fabs(bCoord[1]); ay = fabs(bCoord[2]); // get the greater value and determine the screen edge it's on if(ax < ay) { ax = ay; // 180 or 0 degrees if(bCoord[2] < 0.0f) *edge = SIDE_BOTTOM; else *edge = SIDE_TOP; } else { if(bCoord[1] < 0.0f) *edge = SIDE_RIGHT; else *edge = SIDE_LEFT; } // umm... if(ax < MIN_EPSILON) // this was == 0.0f before --blub ax = MIN_EPSILON; // get the -1 to +1 range x = bCoord[1] / ax; y = bCoord[2] / ax; ax = (1.0f / VectorLength(left)); ay = (1.0f / VectorLength(up)); // Using the placement below the distance of a sprite is // real dist = sqrt(d*d + dfxa*dfxa + dgyb*dgyb) // d is the distance we use // f is frustum X // x is x // a is ax // g is frustum Y // y is y // b is ay // real dist (r) shall be d, so // r*r = d*d + dfxa*dfxa + dgyb*dgyb // r*r = d*d * (1 + fxa*fxa + gyb*gyb) // d*d = r*r / (1 + fxa*fxa + gyb*gyb) // d = sqrt(r*r / (1 + fxa*fxa + gyb*gyb)) // thus: distance = sqrt((distance*distance) / (1.0 + r_refdef.view.frustum_x*r_refdef.view.frustum_x * x*x * ax*ax + r_refdef.view.frustum_y*r_refdef.view.frustum_y * y*y * ay*ay)); // ^ the one we want ^ the one we have ^ our factors // Place the sprite a few units ahead of the player VectorCopy(r_refdef.view.origin, origin); VectorMA(origin, distance, r_refdef.view.forward, origin); // Move the sprite left / up the screeen height VectorMA(origin, distance * r_refdef.view.frustum_x * x * ax, left, origin); VectorMA(origin, distance * r_refdef.view.frustum_y * y * ay, up, origin); if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) { // compute the rotation, negate y axis, we're pointing outwards *dir_angle = atan(-y / x) * 180.0f/M_PI; // we need the real, full angle if(x < 0.0f) *dir_angle += 180.0f; } left[0] *= r_track_sprites_scalew.value; left[1] *= r_track_sprites_scalew.value; left[2] *= r_track_sprites_scalew.value; up[0] *= r_track_sprites_scaleh.value; up[1] *= r_track_sprites_scaleh.value; up[2] *= r_track_sprites_scaleh.value; } } static void R_RotateSprite(const mspriteframe_t *frame, vec3_t origin, vec3_t left, vec3_t up, int edge, float dir_angle) { if(!(r_track_sprites_flags.integer & TSF_ROTATE)) { // move down by its size if on top, otherwise it's invisible if(edge == SIDE_TOP) VectorMA(origin, -(fabs(frame->up)+fabs(frame->down)), up, origin); } else { static float rotation_angles[5] = { 0, // no edge -90.0f, //top 0.0f, // left 90.0f, // bottom 180.0f, // right }; // rotate around the hotspot according to which edge it's on // since the hotspot == the origin, only rotate the vectors matrix4x4_t rotm; vec3_t axis; vec3_t temp; vec2_t dir; float angle; if(edge < 1 || edge > 4) return; // this usually means something went wrong somewhere, there's no way to get a wrong edge value currently dir[0] = frame->right + frame->left; dir[1] = frame->down + frame->up; // only rotate when the hotspot isn't the center though. if(dir[0] < MIN_EPSILON && dir[1] < MIN_EPSILON) { return; } // Now that we've kicked center-hotspotted sprites, rotate using the appropriate matrix :) // determine the angle of a sprite, we could only do that once though and // add a `qboolean initialized' to the mspriteframe_t struct... let's get the direction vector of it :) angle = atan(dir[1] / dir[0]) * 180.0f/M_PI; // we need the real, full angle if(dir[0] < 0.0f) angle += 180.0f; // Rotate around rotation_angle - frame_angle // The axis SHOULD equal r_refdef.view.forward, but let's generalize this: CrossProduct(up, left, axis); if(r_track_sprites_flags.integer & TSF_ROTATE_CONTINOUSLY) Matrix4x4_CreateRotate(&rotm, dir_angle - angle, axis[0], axis[1], axis[2]); else Matrix4x4_CreateRotate(&rotm, rotation_angles[edge] - angle, axis[0], axis[1], axis[2]); Matrix4x4_Transform(&rotm, up, temp); VectorCopy(temp, up); Matrix4x4_Transform(&rotm, left, temp); VectorCopy(temp, left); } } static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; static void R_Model_Sprite_Draw_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i; dp_model_t *model = ent->model; vec3_t left, up, org, mforward, mleft, mup, middle; float scale, dx, dy, hud_vs_screen; int edge = 0; float dir_angle = 0.0f; float vertex3f[12]; // nudge it toward the view to make sure it isn't in a wall Matrix4x4_ToVectors(&ent->matrix, mforward, mleft, mup, org); VectorSubtract(org, r_refdef.view.forward, org); switch(model->sprite.sprnum_type) { case SPR_VP_PARALLEL_UPRIGHT: // flames and such // vertical beam sprite, faces view plane scale = ent->scale / sqrt(r_refdef.view.forward[0]*r_refdef.view.forward[0]+r_refdef.view.forward[1]*r_refdef.view.forward[1]); left[0] = -r_refdef.view.forward[1] * scale; left[1] = r_refdef.view.forward[0] * scale; left[2] = 0; up[0] = 0; up[1] = 0; up[2] = ent->scale; break; case SPR_FACING_UPRIGHT: // flames and such // vertical beam sprite, faces viewer's origin (not the view plane) scale = ent->scale / sqrt((org[0] - r_refdef.view.origin[0])*(org[0] - r_refdef.view.origin[0])+(org[1] - r_refdef.view.origin[1])*(org[1] - r_refdef.view.origin[1])); left[0] = (org[1] - r_refdef.view.origin[1]) * scale; left[1] = -(org[0] - r_refdef.view.origin[0]) * scale; left[2] = 0; up[0] = 0; up[1] = 0; up[2] = ent->scale; break; default: Con_Printf("R_SpriteSetup: unknown sprite type %i\n", model->sprite.sprnum_type); // fall through to normal sprite case SPR_VP_PARALLEL: // normal sprite // faces view plane VectorScale(r_refdef.view.left, ent->scale, left); VectorScale(r_refdef.view.up, ent->scale, up); break; case SPR_LABEL_SCALE: // normal sprite // faces view plane // fixed HUD pixel size specified in sprite // honors scale // honors a global label scaling cvar if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections return; // See the R_TrackSprite definition for a reason for this copying VectorCopy(r_refdef.view.left, left); VectorCopy(r_refdef.view.up, up); // It has to be done before the calculations, because it moves the origin. if(r_track_sprites.integer) R_TrackSprite(ent, org, left, up, &edge, &dir_angle); scale = 2 * ent->scale * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)) * r_labelsprites_scale.value; VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer, up); // 1px break; case SPR_LABEL: // normal sprite // faces view plane // fixed pixel size specified in sprite // tries to get the right size in HUD units, if possible // ignores scale // honors a global label scaling cvar before the rounding // FIXME assumes that 1qu is 1 pixel in the sprite like in SPR32 format. Should not do that, but instead query the source image! This bug only applies to the roundtopixels case, though. if(r_fb.water.renderingscene) // labels are considered HUD items, and don't appear in reflections return; // See the R_TrackSprite definition for a reason for this copying VectorCopy(r_refdef.view.left, left); VectorCopy(r_refdef.view.up, up); // It has to be done before the calculations, because it moves the origin. if(r_track_sprites.integer) R_TrackSprite(ent, org, left, up, &edge, &dir_angle); scale = 2 * (DotProduct(r_refdef.view.forward, org) - DotProduct(r_refdef.view.forward, r_refdef.view.origin)); if(r_labelsprites_roundtopixels.integer) { hud_vs_screen = max( vid_conwidth.integer / (float) r_refdef.view.width, vid_conheight.integer / (float) r_refdef.view.height ) / max(0.125, r_labelsprites_scale.value); // snap to "good sizes" // 1 for (0.6, 1.41] // 2 for (1.8, 3.33] if(hud_vs_screen <= 0.6) hud_vs_screen = 0; // don't, use real HUD pixels else if(hud_vs_screen <= 1.41) hud_vs_screen = 1; else if(hud_vs_screen <= 3.33) hud_vs_screen = 2; else hud_vs_screen = 0; // don't, use real HUD pixels if(hud_vs_screen) { // use screen pixels VectorScale(left, scale * r_refdef.view.frustum_x / (r_refdef.view.width * hud_vs_screen), left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / (r_refdef.view.height * hud_vs_screen), up); // 1px } else { // use HUD pixels VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px } if(hud_vs_screen == 1) { VectorMA(r_refdef.view.origin, scale, r_refdef.view.forward, middle); // center of screen in distance scale dx = 0.5 - fmod(r_refdef.view.width * 0.5 + (DotProduct(org, left) - DotProduct(middle, left)) / DotProduct(left, left) + 0.5, 1.0); dy = 0.5 - fmod(r_refdef.view.height * 0.5 + (DotProduct(org, up) - DotProduct(middle, up)) / DotProduct(up, up) + 0.5, 1.0); VectorMAMAM(1, org, dx, left, dy, up, org); } } else { // use HUD pixels VectorScale(left, scale * r_refdef.view.frustum_x / vid_conwidth.integer * r_labelsprites_scale.value, left); // 1px VectorScale(up, scale * r_refdef.view.frustum_y / vid_conheight.integer * r_labelsprites_scale.value, up); // 1px } break; case SPR_ORIENTED: // bullet marks on walls // ignores viewer entirely VectorCopy(mleft, left); VectorCopy(mup, up); break; case SPR_VP_PARALLEL_ORIENTED: // I have no idea what people would use this for... // oriented relative to view space // FIXME: test this and make sure it mimicks software left[0] = mleft[0] * r_refdef.view.forward[0] + mleft[1] * r_refdef.view.left[0] + mleft[2] * r_refdef.view.up[0]; left[1] = mleft[0] * r_refdef.view.forward[1] + mleft[1] * r_refdef.view.left[1] + mleft[2] * r_refdef.view.up[1]; left[2] = mleft[0] * r_refdef.view.forward[2] + mleft[1] * r_refdef.view.left[2] + mleft[2] * r_refdef.view.up[2]; up[0] = mup[0] * r_refdef.view.forward[0] + mup[1] * r_refdef.view.left[0] + mup[2] * r_refdef.view.up[0]; up[1] = mup[0] * r_refdef.view.forward[1] + mup[1] * r_refdef.view.left[1] + mup[2] * r_refdef.view.up[1]; up[2] = mup[0] * r_refdef.view.forward[2] + mup[1] * r_refdef.view.left[2] + mup[2] * r_refdef.view.up[2]; break; case SPR_OVERHEAD: // Overhead games sprites, have some special hacks to look good VectorScale(r_refdef.view.left, ent->scale * r_overheadsprites_scalex.value, left); VectorScale(r_refdef.view.up, ent->scale * r_overheadsprites_scaley.value, up); VectorSubtract(org, r_refdef.view.origin, middle); VectorNormalize(middle); // offset and rotate dir_angle = r_overheadsprites_perspective.value * (1 - fabs(DotProduct(middle, r_refdef.view.forward))); up[2] = up[2] + dir_angle; VectorNormalize(up); VectorScale(up, ent->scale * r_overheadsprites_scaley.value, up); // offset (move nearer to player, yz is camera plane) org[0] = org[0] - middle[0]*r_overheadsprites_pushback.value; org[1] = org[1] - middle[1]*r_overheadsprites_pushback.value; org[2] = org[2] - middle[2]*r_overheadsprites_pushback.value; // little perspective effect up[2] = up[2] + dir_angle * 0.3; // a bit of counter-camera rotation up[0] = up[0] + r_refdef.view.forward[0] * 0.07; up[1] = up[1] + r_refdef.view.forward[1] * 0.07; up[2] = up[2] + r_refdef.view.forward[2] * 0.07; break; } // LordHavoc: interpolated sprite rendering for (i = 0;i < MAX_FRAMEBLENDS;i++) { if (ent->frameblend[i].lerp >= 0.01f) { mspriteframe_t *frame; texture_t *texture; RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, ent->flags, 0, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha * ent->frameblend[i].lerp, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); frame = model->sprite.sprdata_frames + ent->frameblend[i].subframe; texture = R_GetCurrentTexture(model->data_textures + ent->frameblend[i].subframe); // lit sprite by lightgrid if it is not fullbright, lit only ambient if (!(texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) VectorAdd(ent->modellight_ambient, ent->modellight_diffuse, rsurface.modellight_ambient); // sprites dont use lightdirection // SPR_LABEL should not use depth test AT ALL if(model->sprite.sprnum_type == SPR_LABEL || model->sprite.sprnum_type == SPR_LABEL_SCALE) if(texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) texture->currentmaterialflags = (texture->currentmaterialflags & ~MATERIALFLAG_SHORTDEPTHRANGE) | MATERIALFLAG_NODEPTHTEST; if(edge) { // FIXME:: save vectors/origin and re-rotate? necessary if the hotspot can change per frame R_RotateSprite(frame, org, left, up, edge, dir_angle); edge = 0; } R_CalcSprite_Vertex3f(vertex3f, org, left, up, frame->left, frame->right, frame->down, frame->up); R_DrawCustomSurface_Texture(texture, &identitymatrix, texture->currentmaterialflags, 0, 4, 0, 2, false, false); } } rsurface.entity = NULL; } void R_Model_Sprite_Draw(entity_render_t *ent) { vec3_t org; if (ent->frameblend[0].subframe < 0) return; Matrix4x4_OriginFromMatrix(&ent->matrix, org); R_MeshQueue_AddTransparent((ent->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : (ent->flags & RENDER_NODEPTHTEST) ? TRANSPARENTSORT_HUD : TRANSPARENTSORT_DISTANCE, org, R_Model_Sprite_Draw_TransparentCallback, ent, 0, rsurface.rtlight); } darkplaces/mathlib.c0000664000175000017500000010516113067716220013753 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // mathlib.c -- math primitives #include "quakedef.h" #include vec3_t vec3_origin = {0,0,0}; float ixtable[4096]; /*-----------------------------------------------------------------*/ float m_bytenormals[NUMVERTEXNORMALS][3] = { {-0.525731f, 0.000000f, 0.850651f}, {-0.442863f, 0.238856f, 0.864188f}, {-0.295242f, 0.000000f, 0.955423f}, {-0.309017f, 0.500000f, 0.809017f}, {-0.162460f, 0.262866f, 0.951056f}, {0.000000f, 0.000000f, 1.000000f}, {0.000000f, 0.850651f, 0.525731f}, {-0.147621f, 0.716567f, 0.681718f}, {0.147621f, 0.716567f, 0.681718f}, {0.000000f, 0.525731f, 0.850651f}, {0.309017f, 0.500000f, 0.809017f}, {0.525731f, 0.000000f, 0.850651f}, {0.295242f, 0.000000f, 0.955423f}, {0.442863f, 0.238856f, 0.864188f}, {0.162460f, 0.262866f, 0.951056f}, {-0.681718f, 0.147621f, 0.716567f}, {-0.809017f, 0.309017f, 0.500000f}, {-0.587785f, 0.425325f, 0.688191f}, {-0.850651f, 0.525731f, 0.000000f}, {-0.864188f, 0.442863f, 0.238856f}, {-0.716567f, 0.681718f, 0.147621f}, {-0.688191f, 0.587785f, 0.425325f}, {-0.500000f, 0.809017f, 0.309017f}, {-0.238856f, 0.864188f, 0.442863f}, {-0.425325f, 0.688191f, 0.587785f}, {-0.716567f, 0.681718f, -0.147621f}, {-0.500000f, 0.809017f, -0.309017f}, {-0.525731f, 0.850651f, 0.000000f}, {0.000000f, 0.850651f, -0.525731f}, {-0.238856f, 0.864188f, -0.442863f}, {0.000000f, 0.955423f, -0.295242f}, {-0.262866f, 0.951056f, -0.162460f}, {0.000000f, 1.000000f, 0.000000f}, {0.000000f, 0.955423f, 0.295242f}, {-0.262866f, 0.951056f, 0.162460f}, {0.238856f, 0.864188f, 0.442863f}, {0.262866f, 0.951056f, 0.162460f}, {0.500000f, 0.809017f, 0.309017f}, {0.238856f, 0.864188f, -0.442863f}, {0.262866f, 0.951056f, -0.162460f}, {0.500000f, 0.809017f, -0.309017f}, {0.850651f, 0.525731f, 0.000000f}, {0.716567f, 0.681718f, 0.147621f}, {0.716567f, 0.681718f, -0.147621f}, {0.525731f, 0.850651f, 0.000000f}, {0.425325f, 0.688191f, 0.587785f}, {0.864188f, 0.442863f, 0.238856f}, {0.688191f, 0.587785f, 0.425325f}, {0.809017f, 0.309017f, 0.500000f}, {0.681718f, 0.147621f, 0.716567f}, {0.587785f, 0.425325f, 0.688191f}, {0.955423f, 0.295242f, 0.000000f}, {1.000000f, 0.000000f, 0.000000f}, {0.951056f, 0.162460f, 0.262866f}, {0.850651f, -0.525731f, 0.000000f}, {0.955423f, -0.295242f, 0.000000f}, {0.864188f, -0.442863f, 0.238856f}, {0.951056f, -0.162460f, 0.262866f}, {0.809017f, -0.309017f, 0.500000f}, {0.681718f, -0.147621f, 0.716567f}, {0.850651f, 0.000000f, 0.525731f}, {0.864188f, 0.442863f, -0.238856f}, {0.809017f, 0.309017f, -0.500000f}, {0.951056f, 0.162460f, -0.262866f}, {0.525731f, 0.000000f, -0.850651f}, {0.681718f, 0.147621f, -0.716567f}, {0.681718f, -0.147621f, -0.716567f}, {0.850651f, 0.000000f, -0.525731f}, {0.809017f, -0.309017f, -0.500000f}, {0.864188f, -0.442863f, -0.238856f}, {0.951056f, -0.162460f, -0.262866f}, {0.147621f, 0.716567f, -0.681718f}, {0.309017f, 0.500000f, -0.809017f}, {0.425325f, 0.688191f, -0.587785f}, {0.442863f, 0.238856f, -0.864188f}, {0.587785f, 0.425325f, -0.688191f}, {0.688191f, 0.587785f, -0.425325f}, {-0.147621f, 0.716567f, -0.681718f}, {-0.309017f, 0.500000f, -0.809017f}, {0.000000f, 0.525731f, -0.850651f}, {-0.525731f, 0.000000f, -0.850651f}, {-0.442863f, 0.238856f, -0.864188f}, {-0.295242f, 0.000000f, -0.955423f}, {-0.162460f, 0.262866f, -0.951056f}, {0.000000f, 0.000000f, -1.000000f}, {0.295242f, 0.000000f, -0.955423f}, {0.162460f, 0.262866f, -0.951056f}, {-0.442863f, -0.238856f, -0.864188f}, {-0.309017f, -0.500000f, -0.809017f}, {-0.162460f, -0.262866f, -0.951056f}, {0.000000f, -0.850651f, -0.525731f}, {-0.147621f, -0.716567f, -0.681718f}, {0.147621f, -0.716567f, -0.681718f}, {0.000000f, -0.525731f, -0.850651f}, {0.309017f, -0.500000f, -0.809017f}, {0.442863f, -0.238856f, -0.864188f}, {0.162460f, -0.262866f, -0.951056f}, {0.238856f, -0.864188f, -0.442863f}, {0.500000f, -0.809017f, -0.309017f}, {0.425325f, -0.688191f, -0.587785f}, {0.716567f, -0.681718f, -0.147621f}, {0.688191f, -0.587785f, -0.425325f}, {0.587785f, -0.425325f, -0.688191f}, {0.000000f, -0.955423f, -0.295242f}, {0.000000f, -1.000000f, 0.000000f}, {0.262866f, -0.951056f, -0.162460f}, {0.000000f, -0.850651f, 0.525731f}, {0.000000f, -0.955423f, 0.295242f}, {0.238856f, -0.864188f, 0.442863f}, {0.262866f, -0.951056f, 0.162460f}, {0.500000f, -0.809017f, 0.309017f}, {0.716567f, -0.681718f, 0.147621f}, {0.525731f, -0.850651f, 0.000000f}, {-0.238856f, -0.864188f, -0.442863f}, {-0.500000f, -0.809017f, -0.309017f}, {-0.262866f, -0.951056f, -0.162460f}, {-0.850651f, -0.525731f, 0.000000f}, {-0.716567f, -0.681718f, -0.147621f}, {-0.716567f, -0.681718f, 0.147621f}, {-0.525731f, -0.850651f, 0.000000f}, {-0.500000f, -0.809017f, 0.309017f}, {-0.238856f, -0.864188f, 0.442863f}, {-0.262866f, -0.951056f, 0.162460f}, {-0.864188f, -0.442863f, 0.238856f}, {-0.809017f, -0.309017f, 0.500000f}, {-0.688191f, -0.587785f, 0.425325f}, {-0.681718f, -0.147621f, 0.716567f}, {-0.442863f, -0.238856f, 0.864188f}, {-0.587785f, -0.425325f, 0.688191f}, {-0.309017f, -0.500000f, 0.809017f}, {-0.147621f, -0.716567f, 0.681718f}, {-0.425325f, -0.688191f, 0.587785f}, {-0.162460f, -0.262866f, 0.951056f}, {0.442863f, -0.238856f, 0.864188f}, {0.162460f, -0.262866f, 0.951056f}, {0.309017f, -0.500000f, 0.809017f}, {0.147621f, -0.716567f, 0.681718f}, {0.000000f, -0.525731f, 0.850651f}, {0.425325f, -0.688191f, 0.587785f}, {0.587785f, -0.425325f, 0.688191f}, {0.688191f, -0.587785f, 0.425325f}, {-0.955423f, 0.295242f, 0.000000f}, {-0.951056f, 0.162460f, 0.262866f}, {-1.000000f, 0.000000f, 0.000000f}, {-0.850651f, 0.000000f, 0.525731f}, {-0.955423f, -0.295242f, 0.000000f}, {-0.951056f, -0.162460f, 0.262866f}, {-0.864188f, 0.442863f, -0.238856f}, {-0.951056f, 0.162460f, -0.262866f}, {-0.809017f, 0.309017f, -0.500000f}, {-0.864188f, -0.442863f, -0.238856f}, {-0.951056f, -0.162460f, -0.262866f}, {-0.809017f, -0.309017f, -0.500000f}, {-0.681718f, 0.147621f, -0.716567f}, {-0.681718f, -0.147621f, -0.716567f}, {-0.850651f, 0.000000f, -0.525731f}, {-0.688191f, 0.587785f, -0.425325f}, {-0.587785f, 0.425325f, -0.688191f}, {-0.425325f, 0.688191f, -0.587785f}, {-0.425325f, -0.688191f, -0.587785f}, {-0.587785f, -0.425325f, -0.688191f}, {-0.688191f, -0.587785f, -0.425325f}, }; #if 0 unsigned char NormalToByte(const vec3_t n) { int i, best; float bestdistance, distance; best = 0; bestdistance = DotProduct (n, m_bytenormals[0]); for (i = 1;i < NUMVERTEXNORMALS;i++) { distance = DotProduct (n, m_bytenormals[i]); if (distance > bestdistance) { bestdistance = distance; best = i; } } return best; } // note: uses byte partly to force unsigned for the validity check void ByteToNormal(unsigned char num, vec3_t n) { if (num < NUMVERTEXNORMALS) VectorCopy(m_bytenormals[num], n); else VectorClear(n); // FIXME: complain? } // assumes "src" is normalized void PerpendicularVector( vec3_t dst, const vec3_t src ) { // LordHavoc: optimized to death and beyond int pos; float minelem; if (src[0]) { dst[0] = 0; if (src[1]) { dst[1] = 0; if (src[2]) { dst[2] = 0; pos = 0; minelem = fabs(src[0]); if (fabs(src[1]) < minelem) { pos = 1; minelem = fabs(src[1]); } if (fabs(src[2]) < minelem) pos = 2; dst[pos] = 1; dst[0] -= src[pos] * src[0]; dst[1] -= src[pos] * src[1]; dst[2] -= src[pos] * src[2]; // normalize the result VectorNormalize(dst); } else dst[2] = 1; } else { dst[1] = 1; dst[2] = 0; } } else { dst[0] = 1; dst[1] = 0; dst[2] = 0; } } #endif // LordHavoc: like AngleVectors, but taking a forward vector instead of angles, useful! void VectorVectors(const vec3_t forward, vec3_t right, vec3_t up) { // NOTE: this is consistent to AngleVectors applied to AnglesFromVectors if (forward[0] == 0 && forward[1] == 0) { if(forward[2] > 0) { VectorSet(right, 0, -1, 0); VectorSet(up, -1, 0, 0); } else { VectorSet(right, 0, -1, 0); VectorSet(up, 1, 0, 0); } } else { right[0] = forward[1]; right[1] = -forward[0]; right[2] = 0; VectorNormalize(right); up[0] = (-forward[2]*forward[0]); up[1] = (-forward[2]*forward[1]); up[2] = (forward[0]*forward[0] + forward[1]*forward[1]); VectorNormalize(up); } } void VectorVectorsDouble(const double *forward, double *right, double *up) { if (forward[0] == 0 && forward[1] == 0) { if(forward[2] > 0) { VectorSet(right, 0, -1, 0); VectorSet(up, -1, 0, 0); } else { VectorSet(right, 0, -1, 0); VectorSet(up, 1, 0, 0); } } else { right[0] = forward[1]; right[1] = -forward[0]; right[2] = 0; VectorNormalize(right); up[0] = (-forward[2]*forward[0]); up[1] = (-forward[2]*forward[1]); up[2] = (forward[0]*forward[0] + forward[1]*forward[1]); VectorNormalize(up); } } void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ) { float t0, t1; float angle, c, s; vec3_t vr, vu, vf; angle = DEG2RAD(degrees); c = cos(angle); s = sin(angle); VectorCopy(dir, vf); VectorVectors(vf, vr, vu); t0 = vr[0] * c + vu[0] * -s; t1 = vr[0] * s + vu[0] * c; dst[0] = (t0 * vr[0] + t1 * vu[0] + vf[0] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[0] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[0] * vf[2]) * point[2]; t0 = vr[1] * c + vu[1] * -s; t1 = vr[1] * s + vu[1] * c; dst[1] = (t0 * vr[0] + t1 * vu[0] + vf[1] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[1] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[1] * vf[2]) * point[2]; t0 = vr[2] * c + vu[2] * -s; t1 = vr[2] * s + vu[2] * c; dst[2] = (t0 * vr[0] + t1 * vu[0] + vf[2] * vf[0]) * point[0] + (t0 * vr[1] + t1 * vu[1] + vf[2] * vf[1]) * point[1] + (t0 * vr[2] + t1 * vu[2] + vf[2] * vf[2]) * point[2]; } /*-----------------------------------------------------------------*/ // returns the smallest integer greater than or equal to "value", or 0 if "value" is too big unsigned int CeilPowerOf2(unsigned int value) { unsigned int ceilvalue; if (value > (1U << (sizeof(int) * 8 - 1))) return 0; ceilvalue = 1; while (ceilvalue < value) ceilvalue <<= 1; return ceilvalue; } /*-----------------------------------------------------------------*/ void PlaneClassify(mplane_t *p) { // for optimized plane comparisons if (p->normal[0] == 1) p->type = 0; else if (p->normal[1] == 1) p->type = 1; else if (p->normal[2] == 1) p->type = 2; else p->type = 3; // for BoxOnPlaneSide p->signbits = 0; if (p->normal[0] < 0) // 1 p->signbits |= 1; if (p->normal[1] < 0) // 2 p->signbits |= 2; if (p->normal[2] < 0) // 4 p->signbits |= 4; } int BoxOnPlaneSide(const vec3_t emins, const vec3_t emaxs, const mplane_t *p) { if (p->type < 3) return ((emaxs[p->type] >= p->dist) | ((emins[p->type] < p->dist) << 1)); switch(p->signbits) { default: case 0: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); case 1: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) < p->dist) << 1)); case 2: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); case 3: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) < p->dist) << 1)); case 4: return (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); case 5: return (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); case 6: return (((p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); case 7: return (((p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2]) >= p->dist) | (((p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2]) < p->dist) << 1)); } } #if 0 int BoxOnPlaneSide_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, const vec_t dist) { switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) { default: case 0: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); case 1: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) < dist) << 1)); case 2: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); case 3: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) < dist) << 1)); case 4: return (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); case 5: return (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2]) < dist) << 1)); case 6: return (((normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); case 7: return (((normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2]) >= dist) | (((normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2]) < dist) << 1)); } } #endif void BoxPlaneCorners(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec3_t outnear, vec3_t outfar) { if (p->type < 3) { outnear[0] = outnear[1] = outnear[2] = outfar[0] = outfar[1] = outfar[2] = 0; outnear[p->type] = emins[p->type]; outfar[p->type] = emaxs[p->type]; return; } switch(p->signbits) { default: case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; } } void BoxPlaneCorners_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec3_t outnear, vec3_t outfar) { switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) { default: case 0: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emins[2];break; case 1: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emins[2];break; case 2: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; case 3: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emaxs[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emins[2];break; case 4: outnear[0] = emaxs[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; case 5: outnear[0] = emins[0];outnear[1] = emaxs[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emins[1];outfar[2] = emaxs[2];break; case 6: outnear[0] = emaxs[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emins[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; case 7: outnear[0] = emins[0];outnear[1] = emins[1];outnear[2] = emins[2];outfar[0] = emaxs[0];outfar[1] = emaxs[1];outfar[2] = emaxs[2];break; } } void BoxPlaneCornerDistances(const vec3_t emins, const vec3_t emaxs, const mplane_t *p, vec_t *outneardist, vec_t *outfardist) { if (p->type < 3) { *outneardist = emins[p->type] - p->dist; *outfardist = emaxs[p->type] - p->dist; return; } switch(p->signbits) { default: case 0: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; case 1: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;break; case 2: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; case 3: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;break; case 4: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; case 5: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emaxs[2] - p->dist;break; case 6: *outneardist = p->normal[0] * emaxs[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emins[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; case 7: *outneardist = p->normal[0] * emins[0] + p->normal[1] * emins[1] + p->normal[2] * emins[2] - p->dist;*outfardist = p->normal[0] * emaxs[0] + p->normal[1] * emaxs[1] + p->normal[2] * emaxs[2] - p->dist;break; } } void BoxPlaneCornerDistances_Separate(const vec3_t emins, const vec3_t emaxs, const vec3_t normal, vec_t *outneardist, vec_t *outfardist) { switch((normal[0] < 0) | ((normal[1] < 0) << 1) | ((normal[2] < 0) << 2)) { default: case 0: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];break; case 1: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];break; case 2: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; case 3: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];break; case 4: *outneardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; case 5: *outneardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emaxs[2];break; case 6: *outneardist = normal[0] * emaxs[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emins[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; case 7: *outneardist = normal[0] * emins[0] + normal[1] * emins[1] + normal[2] * emins[2];*outfardist = normal[0] * emaxs[0] + normal[1] * emaxs[1] + normal[2] * emaxs[2];break; } } void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) { double angle, sr, sp, sy, cr, cp, cy; angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); if (forward) { forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; } if (right || up) { if (angles[ROLL]) { angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (right) { right[0] = -1*(sr*sp*cy+cr*-sy); right[1] = -1*(sr*sp*sy+cr*cy); right[2] = -1*(sr*cp); } if (up) { up[0] = (cr*sp*cy+-sr*-sy); up[1] = (cr*sp*sy+-sr*cy); up[2] = cr*cp; } } else { if (right) { right[0] = sy; right[1] = -cy; right[2] = 0; } if (up) { up[0] = (sp*cy); up[1] = (sp*sy); up[2] = cp; } } } } void AngleVectorsFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up) { double angle, sr, sp, sy, cr, cp, cy; angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); if (forward) { forward[0] = cp*cy; forward[1] = cp*sy; forward[2] = -sp; } if (left || up) { if (angles[ROLL]) { angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (left) { left[0] = sr*sp*cy+cr*-sy; left[1] = sr*sp*sy+cr*cy; left[2] = sr*cp; } if (up) { up[0] = cr*sp*cy+-sr*-sy; up[1] = cr*sp*sy+-sr*cy; up[2] = cr*cp; } } else { if (left) { left[0] = -sy; left[1] = cy; left[2] = 0; } if (up) { up[0] = sp*cy; up[1] = sp*sy; up[2] = cp; } } } } void AngleVectorsDuke3DFLU (const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up, double maxShearAngle) { double angle, sr, sy, cr, cy; double sxx, sxz, szx, szz; double cosMaxShearAngle = cos(maxShearAngle * (M_PI*2 / 360)); double tanMaxShearAngle = tan(maxShearAngle * (M_PI*2 / 360)); angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); // We will calculate a shear matrix pitch = [[sxx sxz][szx szz]]. if (fabs(cos(angle)) > cosMaxShearAngle) { // Pure shear. Keep the original sign of the coefficients. sxx = 1; sxz = 0; szx = -tan(angle); szz = 1; // Covering angle per screen coordinate: // d/dt arctan((sxz + t*szz) / (sxx + t*szx)) @ t=0 // d_angle = det(S) / (sxx*sxx + szx*szx) // = 1 / (1 + tan^2 angle) // = cos^2 angle. } else { // A mix of shear and rotation. Implementation-wise, we're // looking at a capsule, and making the screen surface // tangential to it... and if we get here, we're looking at the // two half-spheres of the capsule (and the cylinder part is // handled above). double x, y, h, t, d, f; h = tanMaxShearAngle; x = cos(angle); y = sin(angle); t = h * fabs(y) + sqrt(1 - (h * x) * (h * x)); sxx = x * t; sxz = y * t - h * (y > 0 ? 1.0 : -1.0); szx = -y * t; szz = x * t; // BUT: keep the amount of a sphere we see in pitch direction // invariant. // Covering angle per screen coordinate: // d_angle = det(S) / (sxx*sxx + szx*szx) d = (sxx * szz - sxz * szx) / (sxx * sxx + szx * szx); f = cosMaxShearAngle * cosMaxShearAngle / d; sxz *= f; szz *= f; } if (forward) { forward[0] = sxx*cy; forward[1] = sxx*sy; forward[2] = szx; } if (left || up) { if (angles[ROLL]) { angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); if (left) { left[0] = sr*sxz*cy+cr*-sy; left[1] = sr*sxz*sy+cr*cy; left[2] = sr*szz; } if (up) { up[0] = cr*sxz*cy+-sr*-sy; up[1] = cr*sxz*sy+-sr*cy; up[2] = cr*szz; } } else { if (left) { left[0] = -sy; left[1] = cy; left[2] = 0; } if (up) { up[0] = sxz*cy; up[1] = sxz*sy; up[2] = szz; } } } } // LordHavoc: calculates pitch/yaw/roll angles from forward and up vectors void AnglesFromVectors (vec3_t angles, const vec3_t forward, const vec3_t up, qboolean flippitch) { if (forward[0] == 0 && forward[1] == 0) { if(forward[2] > 0) { angles[PITCH] = -M_PI * 0.5; angles[YAW] = up ? atan2(-up[1], -up[0]) : 0; } else { angles[PITCH] = M_PI * 0.5; angles[YAW] = up ? atan2(up[1], up[0]) : 0; } angles[ROLL] = 0; } else { angles[YAW] = atan2(forward[1], forward[0]); angles[PITCH] = -atan2(forward[2], sqrt(forward[0]*forward[0] + forward[1]*forward[1])); // note: we know that angles[PITCH] is in ]-pi/2..pi/2[ due to atan2(anything, positive) if (up) { vec_t cp = cos(angles[PITCH]), sp = sin(angles[PITCH]); // note: we know cp > 0, due to the range angles[pitch] is in vec_t cy = cos(angles[YAW]), sy = sin(angles[YAW]); vec3_t tleft, tup; tleft[0] = -sy; tleft[1] = cy; tleft[2] = 0; tup[0] = sp*cy; tup[1] = sp*sy; tup[2] = cp; angles[ROLL] = -atan2(DotProduct(up, tleft), DotProduct(up, tup)); // for up == '0 0 1', this is // angles[ROLL] = -atan2(0, cp); // which is 0 } else angles[ROLL] = 0; // so no up vector is equivalent to '1 0 0'! } // now convert radians to degrees, and make all values positive VectorScale(angles, 180.0 / M_PI, angles); if (flippitch) angles[PITCH] *= -1; if (angles[PITCH] < 0) angles[PITCH] += 360; if (angles[YAW] < 0) angles[YAW] += 360; if (angles[ROLL] < 0) angles[ROLL] += 360; #if 0 { // debugging code vec3_t tforward, tleft, tup, nforward, nup; VectorCopy(forward, nforward); VectorNormalize(nforward); if (up) { VectorCopy(up, nup); VectorNormalize(nup); AngleVectors(angles, tforward, tleft, tup); if (VectorDistance(tforward, nforward) > 0.01 || VectorDistance(tup, nup) > 0.01) { Con_Printf("vectoangles('%f %f %f', '%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], nup[0], nup[1], nup[2], angles[0], angles[1], angles[2]); Con_Printf("^3But that is '%f %f %f', '%f %f %f'\n", tforward[0], tforward[1], tforward[2], tup[0], tup[1], tup[2]); } } else { AngleVectors(angles, tforward, tleft, tup); if (VectorDistance(tforward, nforward) > 0.01) { Con_Printf("vectoangles('%f %f %f') = %f %f %f\n", nforward[0], nforward[1], nforward[2], angles[0], angles[1], angles[2]); Con_Printf("^3But that is '%f %f %f'\n", tforward[0], tforward[1], tforward[2]); } } } #endif } #if 0 void AngleMatrix (const vec3_t angles, const vec3_t translate, vec_t matrix[][4]) { double angle, sr, sp, sy, cr, cp, cy; angle = angles[YAW] * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = angles[PITCH] * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); angle = angles[ROLL] * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); matrix[0][0] = cp*cy; matrix[0][1] = sr*sp*cy+cr*-sy; matrix[0][2] = cr*sp*cy+-sr*-sy; matrix[0][3] = translate[0]; matrix[1][0] = cp*sy; matrix[1][1] = sr*sp*sy+cr*cy; matrix[1][2] = cr*sp*sy+-sr*cy; matrix[1][3] = translate[1]; matrix[2][0] = -sp; matrix[2][1] = sr*cp; matrix[2][2] = cr*cp; matrix[2][3] = translate[2]; } #endif // LordHavoc: renamed this to Length, and made the normal one a #define float VectorNormalizeLength (vec3_t v) { float length, ilength; length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; length = sqrt (length); if (length) { ilength = 1/length; v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } return length; } /* ================ R_ConcatRotations ================ */ void R_ConcatRotations (const float in1[3*3], const float in2[3*3], float out[3*3]) { out[0*3+0] = in1[0*3+0] * in2[0*3+0] + in1[0*3+1] * in2[1*3+0] + in1[0*3+2] * in2[2*3+0]; out[0*3+1] = in1[0*3+0] * in2[0*3+1] + in1[0*3+1] * in2[1*3+1] + in1[0*3+2] * in2[2*3+1]; out[0*3+2] = in1[0*3+0] * in2[0*3+2] + in1[0*3+1] * in2[1*3+2] + in1[0*3+2] * in2[2*3+2]; out[1*3+0] = in1[1*3+0] * in2[0*3+0] + in1[1*3+1] * in2[1*3+0] + in1[1*3+2] * in2[2*3+0]; out[1*3+1] = in1[1*3+0] * in2[0*3+1] + in1[1*3+1] * in2[1*3+1] + in1[1*3+2] * in2[2*3+1]; out[1*3+2] = in1[1*3+0] * in2[0*3+2] + in1[1*3+1] * in2[1*3+2] + in1[1*3+2] * in2[2*3+2]; out[2*3+0] = in1[2*3+0] * in2[0*3+0] + in1[2*3+1] * in2[1*3+0] + in1[2*3+2] * in2[2*3+0]; out[2*3+1] = in1[2*3+0] * in2[0*3+1] + in1[2*3+1] * in2[1*3+1] + in1[2*3+2] * in2[2*3+1]; out[2*3+2] = in1[2*3+0] * in2[0*3+2] + in1[2*3+1] * in2[1*3+2] + in1[2*3+2] * in2[2*3+2]; } /* ================ R_ConcatTransforms ================ */ void R_ConcatTransforms (const float in1[3*4], const float in2[3*4], float out[3*4]) { out[0*4+0] = in1[0*4+0] * in2[0*4+0] + in1[0*4+1] * in2[1*4+0] + in1[0*4+2] * in2[2*4+0]; out[0*4+1] = in1[0*4+0] * in2[0*4+1] + in1[0*4+1] * in2[1*4+1] + in1[0*4+2] * in2[2*4+1]; out[0*4+2] = in1[0*4+0] * in2[0*4+2] + in1[0*4+1] * in2[1*4+2] + in1[0*4+2] * in2[2*4+2]; out[0*4+3] = in1[0*4+0] * in2[0*4+3] + in1[0*4+1] * in2[1*4+3] + in1[0*4+2] * in2[2*4+3] + in1[0*4+3]; out[1*4+0] = in1[1*4+0] * in2[0*4+0] + in1[1*4+1] * in2[1*4+0] + in1[1*4+2] * in2[2*4+0]; out[1*4+1] = in1[1*4+0] * in2[0*4+1] + in1[1*4+1] * in2[1*4+1] + in1[1*4+2] * in2[2*4+1]; out[1*4+2] = in1[1*4+0] * in2[0*4+2] + in1[1*4+1] * in2[1*4+2] + in1[1*4+2] * in2[2*4+2]; out[1*4+3] = in1[1*4+0] * in2[0*4+3] + in1[1*4+1] * in2[1*4+3] + in1[1*4+2] * in2[2*4+3] + in1[1*4+3]; out[2*4+0] = in1[2*4+0] * in2[0*4+0] + in1[2*4+1] * in2[1*4+0] + in1[2*4+2] * in2[2*4+0]; out[2*4+1] = in1[2*4+0] * in2[0*4+1] + in1[2*4+1] * in2[1*4+1] + in1[2*4+2] * in2[2*4+1]; out[2*4+2] = in1[2*4+0] * in2[0*4+2] + in1[2*4+1] * in2[1*4+2] + in1[2*4+2] * in2[2*4+2]; out[2*4+3] = in1[2*4+0] * in2[0*4+3] + in1[2*4+1] * in2[1*4+3] + in1[2*4+2] * in2[2*4+3] + in1[2*4+3]; } float RadiusFromBounds (const vec3_t mins, const vec3_t maxs) { vec3_t m1, m2; VectorMultiply(mins, mins, m1); VectorMultiply(maxs, maxs, m2); return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); } float RadiusFromBoundsAndOrigin (const vec3_t mins, const vec3_t maxs, const vec3_t origin) { vec3_t m1, m2; VectorSubtract(mins, origin, m1);VectorMultiply(m1, m1, m1); VectorSubtract(maxs, origin, m2);VectorMultiply(m2, m2, m2); return sqrt(max(m1[0], m2[0]) + max(m1[1], m2[1]) + max(m1[2], m2[2])); } void Mathlib_Init(void) { int a; // LordHavoc: setup 1.0f / N table for quick recipricols of integers ixtable[0] = 0; for (a = 1;a < 4096;a++) ixtable[a] = 1.0f / a; } #include "matrixlib.h" void Matrix4x4_Print(const matrix4x4_t *in) { Con_Printf("%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n%f %f %f %f\n" , in->m[0][0], in->m[0][1], in->m[0][2], in->m[0][3] , in->m[1][0], in->m[1][1], in->m[1][2], in->m[1][3] , in->m[2][0], in->m[2][1], in->m[2][2], in->m[2][3] , in->m[3][0], in->m[3][1], in->m[3][2], in->m[3][3]); } int Math_atov(const char *s, prvm_vec3_t out) { int i; VectorClear(out); if (*s == '\'') s++; for (i = 0;i < 3;i++) { while (*s == ' ' || *s == '\t') s++; out[i] = atof (s); if (out[i] == 0 && *s != '-' && *s != '+' && (*s < '0' || *s > '9')) break; // not a number while (*s && *s != ' ' && *s !='\t' && *s != '\'') s++; if (*s == '\'') break; } return i; } void BoxFromPoints(vec3_t mins, vec3_t maxs, int numpoints, vec_t *point3f) { int i; VectorCopy(point3f, mins); VectorCopy(point3f, maxs); for (i = 1, point3f += 3;i < numpoints;i++, point3f += 3) { mins[0] = min(mins[0], point3f[0]);maxs[0] = max(maxs[0], point3f[0]); mins[1] = min(mins[1], point3f[1]);maxs[1] = max(maxs[1], point3f[1]); mins[2] = min(mins[2], point3f[2]);maxs[2] = max(maxs[2], point3f[2]); } } // LordHavoc: this has to be done right or you get severe precision breakdown int LoopingFrameNumberFromDouble(double t, int loopframes) { if (loopframes) return (int)(t - floor(t/loopframes)*loopframes); else return (int)t; } static unsigned int mul_Lecuyer[4] = { 0x12e15e35, 0xb500f16e, 0x2e714eb2, 0xb37916a5 }; static void mul128(unsigned int a[], unsigned int b[], unsigned int dest[4]) { unsigned long long t[4]; t[0] = a[0] * b[0]; t[1] = a[1] * b[1]; t[2] = a[2] * b[2]; t[3] = a[3] * b[3]; // this is complicated because C doesn't have a way to make use of the // cpu status carry flag, so we do it all in reverse order from what // would otherwise make sense, and have to make multiple passes... t[3] += t[2] >> 32; t[2] &= 0xffffffff; t[2] += t[1] >> 32; t[1] &= 0xffffffff; t[1] += t[0] >> 32; t[0] &= 0xffffffff; t[3] += t[2] >> 32; t[2] &= 0xffffffff; t[2] += t[1] >> 32; t[1] &= 0xffffffff; t[3] += t[2] >> 32; t[2] &= 0xffffffff; dest[0] = t[0] & 0xffffffff; dest[1] = t[1] & 0xffffffff; dest[2] = t[2] & 0xffffffff; dest[3] = t[3] & 0xffffffff; } void Math_RandomSeed_Reset(randomseed_t *r) { r->s[0] = 1; r->s[1] = 0; r->s[2] = 0; r->s[3] = 0; } void Math_RandomSeed_FromInt(randomseed_t *r, unsigned int n) { // if the entire s[] is zero the algorithm would break completely, so make sure it isn't zero by putting a 1 here r->s[0] = 1; r->s[1] = 0; r->s[2] = 0; r->s[3] = n; } unsigned long long Math_rand64(randomseed_t *r) { unsigned int o[4]; mul128(r->s, mul_Lecuyer, o); r->s[0] = o[0]; r->s[1] = o[1]; r->s[2] = o[2]; r->s[3] = o[3]; return ((unsigned long long)o[3] << 32) + o[2]; } float Math_randomf(randomseed_t *r) { unsigned long long n = Math_rand64(r); return n * (0.25f / 0x80000000 / 0x80000000); } float Math_crandomf(randomseed_t *r) { // do this with a signed number and double the result, so we make use of all parts of the cow long long n = (long long)Math_rand64(r); return n * (0.5f / 0x80000000 / 0x80000000); } float Math_randomrangef(randomseed_t *r, float minf, float maxf) { return Math_randomf(r) * (maxf - minf) + minf; } int Math_randomrangei(randomseed_t *r, int mini, int maxi) { unsigned long long n = Math_rand64(r); return (int)(((n >> 33) * (maxi - mini) + mini) >> 31); } darkplaces/r_textures.h0000664000175000017500000002255113067716222014547 0ustar kalevkalev #ifndef R_TEXTURES_H #define R_TEXTURES_H // transparent #define TEXF_ALPHA 0x00000001 // mipmapped #define TEXF_MIPMAP 0x00000002 // multiply RGB by A channel before uploading #define TEXF_RGBMULTIPLYBYALPHA 0x00000004 // indicates texture coordinates should be clamped rather than wrapping #define TEXF_CLAMP 0x00000020 // indicates texture should be uploaded using GL_NEAREST or GL_NEAREST_MIPMAP_NEAREST mode #define TEXF_FORCENEAREST 0x00000040 // indicates texture should be uploaded using GL_LINEAR or GL_LINEAR_MIPMAP_NEAREST or GL_LINEAR_MIPMAP_LINEAR mode #define TEXF_FORCELINEAR 0x00000080 // indicates texture should be affected by gl_picmip and gl_max_size cvar #define TEXF_PICMIP 0x00000100 // indicates texture should be compressed if possible #define TEXF_COMPRESS 0x00000200 // use this flag to block R_PurgeTexture from freeing a texture (only used by r_texture_white and similar which may be used in skinframe_t) #define TEXF_PERSISTENT 0x00000400 // indicates texture should use GL_COMPARE_R_TO_TEXTURE mode #define TEXF_COMPARE 0x00000800 // indicates texture should use lower precision where supported #define TEXF_LOWPRECISION 0x00001000 // indicates texture should support R_UpdateTexture on small regions, actual uploads may be delayed until R_Mesh_TexBind if gl_nopartialtextureupdates is on #define TEXF_ALLOWUPDATES 0x00002000 // indicates texture should be affected by gl_picmip_world and r_picmipworld (maybe others in the future) instead of gl_picmip_other #define TEXF_ISWORLD 0x00004000 // indicates texture should be affected by gl_picmip_sprites and r_picmipsprites (maybe others in the future) instead of gl_picmip_other #define TEXF_ISSPRITE 0x00008000 // indicates the texture will be used as a render target (D3D hint) #define TEXF_RENDERTARGET 0x0010000 // used for checking if textures mismatch #define TEXF_IMPORTANTBITS (TEXF_ALPHA | TEXF_MIPMAP | TEXF_RGBMULTIPLYBYALPHA | TEXF_CLAMP | TEXF_FORCENEAREST | TEXF_FORCELINEAR | TEXF_PICMIP | TEXF_COMPRESS | TEXF_COMPARE | TEXF_LOWPRECISION | TEXF_RENDERTARGET) // set as a flag to force the texture to be reloaded #define TEXF_FORCE_RELOAD 0x80000000 typedef enum textype_e { // 8bit paletted TEXTYPE_PALETTE, // 32bit RGBA TEXTYPE_RGBA, // 32bit BGRA (preferred format due to faster uploads on most hardware) TEXTYPE_BGRA, // 8bit ALPHA (used for freetype fonts) TEXTYPE_ALPHA, // 4x4 block compressed 15bit color (4 bits per pixel) TEXTYPE_DXT1, // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) TEXTYPE_DXT1A, // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) TEXTYPE_DXT3, // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) TEXTYPE_DXT5, // default compressed type for GLES2 TEXTYPE_ETC1, // 8bit paletted in sRGB colorspace TEXTYPE_SRGB_PALETTE, // 32bit RGBA in sRGB colorspace TEXTYPE_SRGB_RGBA, // 32bit BGRA (preferred format due to faster uploads on most hardware) in sRGB colorspace TEXTYPE_SRGB_BGRA, // 4x4 block compressed 15bit color (4 bits per pixel) in sRGB colorspace TEXTYPE_SRGB_DXT1, // 4x4 block compressed 15bit color plus 1bit alpha (4 bits per pixel) in sRGB colorspace TEXTYPE_SRGB_DXT1A, // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace TEXTYPE_SRGB_DXT3, // 4x4 block compressed 15bit color plus 8bit alpha (8 bits per pixel) in sRGB colorspace TEXTYPE_SRGB_DXT5, // this represents the same format as the framebuffer, for fast copies TEXTYPE_COLORBUFFER, // this represents an RGBA half_float texture (4 16bit floats) TEXTYPE_COLORBUFFER16F, // this represents an RGBA float texture (4 32bit floats) TEXTYPE_COLORBUFFER32F, // depth-stencil buffer (or texture) TEXTYPE_DEPTHBUFFER16, // depth-stencil buffer (or texture) TEXTYPE_DEPTHBUFFER24, // 32bit D24S8 buffer (24bit depth, 8bit stencil), not supported on OpenGL ES TEXTYPE_DEPTHBUFFER24STENCIL8, // shadowmap-friendly format with depth comparison (not supported on some hardware) TEXTYPE_SHADOWMAP16_COMP, // shadowmap-friendly format with raw reading (not supported on some hardware) TEXTYPE_SHADOWMAP16_RAW, // shadowmap-friendly format with depth comparison (not supported on some hardware) TEXTYPE_SHADOWMAP24_COMP, // shadowmap-friendly format with raw reading (not supported on some hardware) TEXTYPE_SHADOWMAP24_RAW, } textype_t; /* #ifdef WIN32 #define SUPPORTD3D #define SUPPORTDIRECTX #ifdef SUPPORTD3D #include #endif #endif */ // contents of this structure are mostly private to gl_textures.c typedef struct rtexture_s { // this is exposed (rather than private) for speed reasons only int texnum; // GL texture slot number int renderbuffernum; // GL renderbuffer slot number qboolean dirty; // indicates that R_RealGetTexture should be called qboolean glisdepthstencil; // indicates that FBO attachment has to be GL_DEPTH_STENCIL_ATTACHMENT int gltexturetypeenum; // used by R_Mesh_TexBind // d3d stuff the backend needs void *d3dtexture; void *d3dsurface; #ifdef SUPPORTD3D qboolean d3disrendertargetsurface; qboolean d3disdepthstencilsurface; int d3dformat; int d3dusage; int d3dpool; int d3daddressu; int d3daddressv; int d3daddressw; int d3dmagfilter; int d3dminfilter; int d3dmipfilter; int d3dmaxmiplevelfilter; int d3dmipmaplodbias; int d3dmaxmiplevel; #endif } rtexture_t; // contents of this structure are private to gl_textures.c typedef struct rtexturepool_s { int useless; } rtexturepool_t; typedef void (*updatecallback_t)(rtexture_t *rt, void *data); // allocate a texture pool, to be used with R_LoadTexture rtexturepool_t *R_AllocTexturePool(void); // free a texture pool (textures can not be freed individually) void R_FreeTexturePool(rtexturepool_t **rtexturepool); // the color/normal/etc cvars should be checked by callers of R_LoadTexture* functions to decide whether to add TEXF_COMPRESS to the flags extern cvar_t gl_texturecompression; extern cvar_t gl_texturecompression_color; extern cvar_t gl_texturecompression_normal; extern cvar_t gl_texturecompression_gloss; extern cvar_t gl_texturecompression_glow; extern cvar_t gl_texturecompression_2d; extern cvar_t gl_texturecompression_q3bsplightmaps; extern cvar_t gl_texturecompression_q3bspdeluxemaps; extern cvar_t gl_texturecompression_sky; extern cvar_t gl_texturecompression_lightcubemaps; extern cvar_t gl_texturecompression_reflectmask; extern cvar_t r_texture_dds_load; extern cvar_t r_texture_dds_save; // add a texture to a pool and optionally precache (upload) it // (note: data == NULL is perfectly acceptable) // (note: palette must not be NULL if using TEXTYPE_PALETTE) rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette); rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype, qboolean filter); rtexture_t *R_LoadTextureRenderBuffer(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype); rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, qboolean srgb, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel, qboolean optionaltexture); // saves a texture to a DDS file int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha); // free a texture void R_FreeTexture(rtexture_t *rt); // update a portion of the image data of a texture, used by lightmap updates // and procedural textures such as video playback, actual uploads may be // delayed by gl_nopartialtextureupdates cvar until R_Mesh_TexBind uses it void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth); // returns the renderer dependent texture slot number (call this before each // use, as a texture might not have been precached) #define R_GetTexture(rt) ((rt) ? ((rt)->dirty ? R_RealGetTexture(rt) : (rt)->texnum) : r_texture_white->texnum) int R_RealGetTexture (rtexture_t *rt); // returns width of texture, as was specified when it was uploaded int R_TextureWidth(rtexture_t *rt); // returns height of texture, as was specified when it was uploaded int R_TextureHeight(rtexture_t *rt); // returns flags of texture, as was specified when it was uploaded int R_TextureFlags(rtexture_t *rt); // only frees the texture if TEXF_PERSISTENT is not set // also resets the variable void R_PurgeTexture(rtexture_t *prt); // frees processing buffers each frame, and may someday animate procedural textures void R_Textures_Frame(void); // maybe rename this - sounds awful? [11/21/2007 Black] void R_MarkDirtyTexture(rtexture_t *rt); void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data); // Clear the texture's contents void R_ClearTexture (rtexture_t *rt); // returns the desired picmip level for given TEXF_ flags int R_PicmipForFlags(int flags); void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal); #endif darkplaces/bspfile.h0000664000175000017500000002311213067716216013764 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define MAX_MAP_HULLS 16 // Q1BSP has 4, Hexen2 Q1BSP has 8, MCBSP has 16 //============================================================================= #define BSPVERSION 29 typedef struct lump_s { int fileofs, filelen; } lump_t; #define LUMP_ENTITIES 0 #define LUMP_PLANES 1 #define LUMP_TEXTURES 2 #define LUMP_VERTEXES 3 #define LUMP_VISIBILITY 4 #define LUMP_NODES 5 #define LUMP_TEXINFO 6 #define LUMP_FACES 7 #define LUMP_LIGHTING 8 #define LUMP_CLIPNODES 9 #define LUMP_LEAFS 10 #define LUMP_MARKSURFACES 11 #define LUMP_EDGES 12 #define LUMP_SURFEDGES 13 #define LUMP_MODELS 14 #define HEADER_LUMPS 15 typedef struct hullinfo_s { int filehulls; float hullsizes[MAX_MAP_HULLS][2][3]; } hullinfo_t; typedef struct mmodel_s { float mins[3], maxs[3]; float origin[3]; int headnode[MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; } mmodel_t; /* // WARNING: this struct does NOT match q1bsp's disk format because MAX_MAP_HULLS has been changed by Sajt's MCBSP code, this struct is only being used in memory as a result typedef struct dmodel_s { float mins[3], maxs[3]; float origin[3]; int headnode[MAX_MAP_HULLS]; int visleafs; // not including the solid leaf 0 int firstface, numfaces; } dmodel_t; typedef struct dheader_s { int version; lump_t lumps[HEADER_LUMPS]; } dheader_t; typedef struct dmiptexlump_s { int nummiptex; int dataofs[4]; // [nummiptex] } dmiptexlump_t; */ #define MIPLEVELS 4 /* typedef struct miptex_s { char name[16]; unsigned width, height; unsigned offsets[MIPLEVELS]; // four mip maps stored } miptex_t; typedef struct dvertex_s { float point[3]; } dvertex_t; */ // 0-2 are axial planes #define PLANE_X 0 #define PLANE_Y 1 #define PLANE_Z 2 // 3-5 are non-axial planes snapped to the nearest #define PLANE_ANYX 3 #define PLANE_ANYY 4 #define PLANE_ANYZ 5 /* typedef struct dplane_s { float normal[3]; float dist; int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate } dplane_t; */ // contents values in Q1 maps #define CONTENTS_EMPTY -1 #define CONTENTS_SOLID -2 #define CONTENTS_WATER -3 #define CONTENTS_SLIME -4 #define CONTENTS_LAVA -5 #define CONTENTS_SKY -6 // these were #ifdef QUAKE2 in the quake source #define CONTENTS_ORIGIN -7 // removed at csg time #define CONTENTS_CLIP -8 // changed to contents_solid #define CONTENTS_CURRENT_0 -9 #define CONTENTS_CURRENT_90 -10 #define CONTENTS_CURRENT_180 -11 #define CONTENTS_CURRENT_270 -12 #define CONTENTS_CURRENT_UP -13 #define CONTENTS_CURRENT_DOWN -14 //contents flags in Q2 maps #define CONTENTSQ2_SOLID 0x00000001 // an eye is never valid in a solid #define CONTENTSQ2_WINDOW 0x00000002 // translucent, but not watery #define CONTENTSQ2_AUX 0x00000004 #define CONTENTSQ2_LAVA 0x00000008 #define CONTENTSQ2_SLIME 0x00000010 #define CONTENTSQ2_WATER 0x00000020 #define CONTENTSQ2_MIST 0x00000040 #define CONTENTSQ2_AREAPORTAL 0x00008000 #define CONTENTSQ2_PLAYERCLIP 0x00010000 #define CONTENTSQ2_MONSTERCLIP 0x00020000 #define CONTENTSQ2_CURRENT_0 0x00040000 #define CONTENTSQ2_CURRENT_90 0x00080000 #define CONTENTSQ2_CURRENT_180 0x00100000 #define CONTENTSQ2_CURRENT_270 0x00200000 #define CONTENTSQ2_CURRENT_UP 0x00400000 #define CONTENTSQ2_CURRENT_DOWN 0x00800000 #define CONTENTSQ2_ORIGIN 0x01000000 // removed before bsping an entity #define CONTENTSQ2_MONSTER 0x02000000 // should never be on a brush, only in game #define CONTENTSQ2_DEADMONSTER 0x04000000 #define CONTENTSQ2_DETAIL 0x08000000 // brushes to be added after vis leafs #define CONTENTSQ2_TRANSLUCENT 0x10000000 // auto set if any surface has trans #define CONTENTSQ2_LADDER 0x20000000 //contents flags in Q3 maps #define CONTENTSQ3_SOLID 0x00000001 // solid (opaque and transparent) #define CONTENTSQ3_LAVA 0x00000008 // lava #define CONTENTSQ3_SLIME 0x00000010 // slime #define CONTENTSQ3_WATER 0x00000020 // water #define CONTENTSQ3_FOG 0x00000040 // unused? #define CONTENTSQ3_AREAPORTAL 0x00008000 // areaportal (separates areas) #define CONTENTSQ3_PLAYERCLIP 0x00010000 // block players #define CONTENTSQ3_MONSTERCLIP 0x00020000 // block monsters #define CONTENTSQ3_TELEPORTER 0x00040000 // hint for Q3's bots #define CONTENTSQ3_JUMPPAD 0x00080000 // hint for Q3's bots #define CONTENTSQ3_CLUSTERPORTAL 0x00100000 // hint for Q3's bots #define CONTENTSQ3_DONOTENTER 0x00200000 // hint for Q3's bots #define CONTENTSQ3_BOTCLIP 0x00400000 // hint for Q3's bots #define CONTENTSQ3_ORIGIN 0x01000000 // used by origin brushes to indicate origin of bmodel (removed by map compiler) #define CONTENTSQ3_BODY 0x02000000 // used by bbox entities (should never be on a brush) #define CONTENTSQ3_CORPSE 0x04000000 // used by dead bodies (SOLID_CORPSE in darkplaces) #define CONTENTSQ3_DETAIL 0x08000000 // brushes that do not split the bsp tree (decorations) #define CONTENTSQ3_STRUCTURAL 0x10000000 // brushes that split the bsp tree #define CONTENTSQ3_TRANSLUCENT 0x20000000 // leaves surfaces that are inside for rendering #define CONTENTSQ3_TRIGGER 0x40000000 // used by trigger entities #define CONTENTSQ3_NODROP 0x80000000 // remove items that fall into this brush #define SUPERCONTENTS_SOLID 0x00000001 #define SUPERCONTENTS_WATER 0x00000002 #define SUPERCONTENTS_SLIME 0x00000004 #define SUPERCONTENTS_LAVA 0x00000008 #define SUPERCONTENTS_SKY 0x00000010 #define SUPERCONTENTS_BODY 0x00000020 #define SUPERCONTENTS_CORPSE 0x00000040 #define SUPERCONTENTS_NODROP 0x00000080 #define SUPERCONTENTS_PLAYERCLIP 0x00000100 #define SUPERCONTENTS_MONSTERCLIP 0x00000200 #define SUPERCONTENTS_DONOTENTER 0x00000400 #define SUPERCONTENTS_BOTCLIP 0x00000800 #define SUPERCONTENTS_OPAQUE 0x00001000 // TODO: is there any reason to define: // fog? // areaportal? // teleporter? // jumppad? // clusterportal? // detail? (div0) no, game code should not be allowed to differentiate between structural and detail // structural? (div0) no, game code should not be allowed to differentiate between structural and detail // trigger? (div0) no, as these are always solid anyway, and that's all that matters for trigger brushes #define SUPERCONTENTS_LIQUIDSMASK (SUPERCONTENTS_LAVA | SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER) #define SUPERCONTENTS_VISBLOCKERMASK SUPERCONTENTS_OPAQUE /* #define SUPERCONTENTS_DEADMONSTER 0x00000000 #define SUPERCONTENTS_CURRENT_0 0x00000000 #define SUPERCONTENTS_CURRENT_90 0x00000000 #define SUPERCONTENTS_CURRENT_180 0x00000000 #define SUPERCONTENTS_CURRENT_270 0x00000000 #define SUPERCONTENTS_CURRENT_DOWN 0x00000000 #define SUPERCONTENTS_CURRENT_UP 0x00000000 #define SUPERCONTENTS_AREAPORTAL 0x00000000 #define SUPERCONTENTS_AUX 0x00000000 #define SUPERCONTENTS_CLUSTERPORTAL 0x00000000 #define SUPERCONTENTS_DETAIL 0x00000000 #define SUPERCONTENTS_STRUCTURAL 0x00000000 #define SUPERCONTENTS_DONOTENTER 0x00000000 #define SUPERCONTENTS_JUMPPAD 0x00000000 #define SUPERCONTENTS_LADDER 0x00000000 #define SUPERCONTENTS_MONSTER 0x00000000 #define SUPERCONTENTS_MONSTERCLIP 0x00000000 #define SUPERCONTENTS_PLAYERCLIP 0x00000000 #define SUPERCONTENTS_TELEPORTER 0x00000000 #define SUPERCONTENTS_TRANSLUCENT 0x00000000 #define SUPERCONTENTS_TRIGGER 0x00000000 #define SUPERCONTENTS_WINDOW 0x00000000 */ /* typedef struct dnode_s { int planenum; short children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for sphere culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } dnode_t; typedef struct dclipnode_s { int planenum; short children[2]; // negative numbers are contents } dclipnode_t; typedef struct texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int miptex; int flags; } texinfo_t; */ #define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision // note that edge 0 is never used, because negative edge nums are used for // counterclockwise use of the edge in a face /* typedef struct dedge_s { unsigned short v[2]; // vertex numbers } dedge_t; */ #define MAXLIGHTMAPS 4 /* typedef struct dface_s { // LordHavoc: changed from short to unsigned short for q2 support unsigned short planenum; short side; int firstedge; // we must support > 64k edges short numedges; short texinfo; // lighting info unsigned char styles[MAXLIGHTMAPS]; int lightofs; // start of [numstyles*surfsize] samples } dface_t; */ #define AMBIENT_WATER 0 #define AMBIENT_SKY 1 #define AMBIENT_SLIME 2 #define AMBIENT_LAVA 3 #define NUM_AMBIENTS 4 // automatic ambient sounds // leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas // all other leafs need visibility info /* typedef struct dleaf_s { int contents; int visofs; // -1 = no visibility info short mins[3]; // for frustum culling short maxs[3]; unsigned short firstmarksurface; unsigned short nummarksurfaces; unsigned char ambient_level[NUM_AMBIENTS]; } dleaf_t; */ darkplaces/.gitignore0000664000175000017500000000031713067716216014161 0ustar kalevkalevobj/ *.d *.o *.i *.s ChangeLog darkplaces-agl darkplaces-glx darkplaces-sdl darkplaces-dedicated gmon.out *.ncb *.opt *.plg *.exe darkplaces_private.h darkplaces_private.rc Makefile.win *.dll *.dylib *.dSYM darkplaces/ft2_fontdefs.h0000664000175000017500000000342613067716220014724 0ustar kalevkalev#ifndef FT2_PRIVATE_H__ #define FT2_PRIVATE_H__ // anything should work, but I recommend multiples of 8 // since the texture size should be a power of 2 #define FONT_CHARS_PER_LINE 16 #define FONT_CHAR_LINES 16 #define FONT_CHARS_PER_MAP (FONT_CHARS_PER_LINE * FONT_CHAR_LINES) typedef struct glyph_slot_s { qboolean image; // we keep the quad coords here only currently // if you need other info, make Font_LoadMapForIndex fill it into this slot float txmin; // texture coordinate in [0,1] float txmax; float tymin; float tymax; float vxmin; float vxmax; float vymin; float vymax; float advance_x; float advance_y; } glyph_slot_t; struct ft2_font_map_s { Uchar start; struct ft2_font_map_s *next; float size; // the actual size used in the freetype code // by convention, the requested size is the height of the font's bounding box. float intSize; int glyphSize; cachepic_t *pic; qboolean static_tex; glyph_slot_t glyphs[FONT_CHARS_PER_MAP]; // contains the kerning information for the first 256 characters // for the other characters, we will lookup the kerning information ft2_kerning_t kerning; // safes us the trouble of calculating these over and over again double sfx, sfy; // the width_of for the image-font, pixel-snapped for this size float width_of[256]; }; struct ft2_attachment_s { const unsigned char *data; fs_offset_t size; }; //qboolean Font_LoadMapForIndex(ft2_font_t *font, Uchar _ch, ft2_font_map_t **outmap); qboolean Font_LoadMapForIndex(ft2_font_t *font, int map_index, Uchar _ch, ft2_font_map_t **outmap); void font_start(void); void font_shutdown(void); void font_newmap(void); #endif // FT2_PRIVATE_H__ darkplaces/vs2012_sdl2_win32.props0000664000175000017500000000130413067716222016153 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL2-2.0\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;C:\dev\SDL2-2.0\lib\x86;$(LibraryPath) <_PropertySheetDisplayName>vs2012_win32 darkplaces/timing.h0000664000175000017500000000303313067716222013624 0ustar kalevkalev/* Simple helper macros to time blocks or statements. Copyright (C) 2007 Frank Richter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef __TIMING_H__ #define __TIMING_H__ #if defined(DO_TIMING) #define TIMING_BEGIN double _timing_end_, _timing_start_ = Sys_DirtyTime(); #define TIMING_END_STR(S) \ _timing_end_ = Sys_DirtyTime(); \ Con_Printf ("%s: %.3g s\n", S, _timing_end_ - _timing_start_); #define TIMING_END TIMING_END_STR(__FUNCTION__) #define TIMING_INTERMEDIATE(S) \ { \ double currentTime = Sys_DirtyTime(); \ Con_Printf ("%s: %.3g s\n", S, currentTime - _timing_start_); \ } #define TIMING_TIMESTATEMENT(Stmt) \ { \ TIMING_BEGIN \ Stmt; \ TIMING_END_STR(#Stmt); \ } #else #define TIMING_BEGIN #define TIMING_END_STR(S) #define TIMING_END #define TIMING_INTERMEDIATE(S) #define TIMING_TIMESTATEMENT(Stmt) Stmt #endif #endif // __TIMING_H__ darkplaces/vid_shared.c0000664000175000017500000031676613067716222014464 0ustar kalevkalev #include "quakedef.h" #ifdef CONFIG_CD #include "cdaudio.h" #endif #include "image.h" #ifdef SUPPORTD3D #include #ifdef _MSC_VER #pragma comment(lib, "d3d9.lib") #endif LPDIRECT3DDEVICE9 vid_d3d9dev; #endif #ifdef WIN32 //#include #define XINPUT_GAMEPAD_DPAD_UP 0x0001 #define XINPUT_GAMEPAD_DPAD_DOWN 0x0002 #define XINPUT_GAMEPAD_DPAD_LEFT 0x0004 #define XINPUT_GAMEPAD_DPAD_RIGHT 0x0008 #define XINPUT_GAMEPAD_START 0x0010 #define XINPUT_GAMEPAD_BACK 0x0020 #define XINPUT_GAMEPAD_LEFT_THUMB 0x0040 #define XINPUT_GAMEPAD_RIGHT_THUMB 0x0080 #define XINPUT_GAMEPAD_LEFT_SHOULDER 0x0100 #define XINPUT_GAMEPAD_RIGHT_SHOULDER 0x0200 #define XINPUT_GAMEPAD_A 0x1000 #define XINPUT_GAMEPAD_B 0x2000 #define XINPUT_GAMEPAD_X 0x4000 #define XINPUT_GAMEPAD_Y 0x8000 #define XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE 7849 #define XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE 8689 #define XINPUT_GAMEPAD_TRIGGER_THRESHOLD 30 #define XUSER_INDEX_ANY 0x000000FF typedef struct xinput_gamepad_s { WORD wButtons; BYTE bLeftTrigger; BYTE bRightTrigger; SHORT sThumbLX; SHORT sThumbLY; SHORT sThumbRX; SHORT sThumbRY; } xinput_gamepad_t; typedef struct xinput_state_s { DWORD dwPacketNumber; xinput_gamepad_t Gamepad; } xinput_state_t; typedef struct xinput_keystroke_s { WORD VirtualKey; WCHAR Unicode; WORD Flags; BYTE UserIndex; BYTE HidCode; } xinput_keystroke_t; DWORD (WINAPI *qXInputGetState)(DWORD index, xinput_state_t *state); DWORD (WINAPI *qXInputGetKeystroke)(DWORD index, DWORD reserved, xinput_keystroke_t *keystroke); qboolean vid_xinputinitialized = false; int vid_xinputindex = -1; #endif // global video state viddef_t vid; // AK FIXME -> input_dest qboolean in_client_mouse = true; // AK where should it be placed ? float in_mouse_x, in_mouse_y; float in_windowmouse_x, in_windowmouse_y; // LordHavoc: if window is hidden, don't update screen qboolean vid_hidden = true; // LordHavoc: if window is not the active window, don't hog as much CPU time, // let go of the mouse, turn off sound, and restore system gamma ramps... qboolean vid_activewindow = true; vid_joystate_t vid_joystate; #ifdef WIN32 cvar_t joy_xinputavailable = {CVAR_READONLY, "joy_xinputavailable", "0", "indicates which devices are being reported by the Windows XInput API (first controller = 1, second = 2, third = 4, fourth = 8, added together)"}; #endif cvar_t joy_active = {CVAR_READONLY, "joy_active", "0", "indicates that a joystick is active (detected and enabled)"}; cvar_t joy_detected = {CVAR_READONLY, "joy_detected", "0", "number of joysticks detected by engine"}; cvar_t joy_enable = {CVAR_SAVE, "joy_enable", "0", "enables joystick support"}; cvar_t joy_index = {0, "joy_index", "0", "selects which joystick to use if you have multiple (0 uses the first controller, 1 uses the second, ...)"}; cvar_t joy_axisforward = {0, "joy_axisforward", "1", "which joystick axis to query for forward/backward movement"}; cvar_t joy_axisside = {0, "joy_axisside", "0", "which joystick axis to query for right/left movement"}; cvar_t joy_axisup = {0, "joy_axisup", "-1", "which joystick axis to query for up/down movement"}; cvar_t joy_axispitch = {0, "joy_axispitch", "3", "which joystick axis to query for looking up/down"}; cvar_t joy_axisyaw = {0, "joy_axisyaw", "2", "which joystick axis to query for looking right/left"}; cvar_t joy_axisroll = {0, "joy_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; cvar_t joy_deadzoneforward = {0, "joy_deadzoneforward", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_deadzoneside = {0, "joy_deadzoneside", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_deadzoneup = {0, "joy_deadzoneup", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_deadzonepitch = {0, "joy_deadzonepitch", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_deadzoneyaw = {0, "joy_deadzoneyaw", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_deadzoneroll = {0, "joy_deadzoneroll", "0", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_sensitivityforward = {0, "joy_sensitivityforward", "-1", "movement multiplier"}; cvar_t joy_sensitivityside = {0, "joy_sensitivityside", "1", "movement multiplier"}; cvar_t joy_sensitivityup = {0, "joy_sensitivityup", "1", "movement multiplier"}; cvar_t joy_sensitivitypitch = {0, "joy_sensitivitypitch", "1", "movement multiplier"}; cvar_t joy_sensitivityyaw = {0, "joy_sensitivityyaw", "-1", "movement multiplier"}; cvar_t joy_sensitivityroll = {0, "joy_sensitivityroll", "1", "movement multiplier"}; cvar_t joy_axiskeyevents = {CVAR_SAVE, "joy_axiskeyevents", "0", "generate uparrow/leftarrow etc. keyevents for joystick axes, use if your joystick driver is not generating them"}; cvar_t joy_axiskeyevents_deadzone = {CVAR_SAVE, "joy_axiskeyevents_deadzone", "0.5", "deadzone value for axes"}; cvar_t joy_x360_axisforward = {0, "joy_x360_axisforward", "1", "which joystick axis to query for forward/backward movement"}; cvar_t joy_x360_axisside = {0, "joy_x360_axisside", "0", "which joystick axis to query for right/left movement"}; cvar_t joy_x360_axisup = {0, "joy_x360_axisup", "-1", "which joystick axis to query for up/down movement"}; cvar_t joy_x360_axispitch = {0, "joy_x360_axispitch", "3", "which joystick axis to query for looking up/down"}; cvar_t joy_x360_axisyaw = {0, "joy_x360_axisyaw", "2", "which joystick axis to query for looking right/left"}; cvar_t joy_x360_axisroll = {0, "joy_x360_axisroll", "-1", "which joystick axis to query for tilting head right/left"}; cvar_t joy_x360_deadzoneforward = {0, "joy_x360_deadzoneforward", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_deadzoneside = {0, "joy_x360_deadzoneside", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_deadzoneup = {0, "joy_x360_deadzoneup", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_deadzonepitch = {0, "joy_x360_deadzonepitch", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_deadzoneyaw = {0, "joy_x360_deadzoneyaw", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_deadzoneroll = {0, "joy_x360_deadzoneroll", "0.266", "deadzone tolerance, suggested values are in the range 0 to 0.01"}; cvar_t joy_x360_sensitivityforward = {0, "joy_x360_sensitivityforward", "1", "movement multiplier"}; cvar_t joy_x360_sensitivityside = {0, "joy_x360_sensitivityside", "1", "movement multiplier"}; cvar_t joy_x360_sensitivityup = {0, "joy_x360_sensitivityup", "1", "movement multiplier"}; cvar_t joy_x360_sensitivitypitch = {0, "joy_x360_sensitivitypitch", "-1", "movement multiplier"}; cvar_t joy_x360_sensitivityyaw = {0, "joy_x360_sensitivityyaw", "-1", "movement multiplier"}; cvar_t joy_x360_sensitivityroll = {0, "joy_x360_sensitivityroll", "1", "movement multiplier"}; // cvars for DPSOFTRAST cvar_t vid_soft = {CVAR_SAVE, "vid_soft", "0", "enables use of the DarkPlaces Software Rasterizer rather than OpenGL or Direct3D"}; cvar_t vid_soft_threads = {CVAR_SAVE, "vid_soft_threads", "8", "the number of threads the DarkPlaces Software Rasterizer should use"}; cvar_t vid_soft_interlace = {CVAR_SAVE, "vid_soft_interlace", "1", "whether the DarkPlaces Software Rasterizer should interlace the screen bands occupied by each thread"}; // we don't know until we try it! cvar_t vid_hardwaregammasupported = {CVAR_READONLY,"vid_hardwaregammasupported","1", "indicates whether hardware gamma is supported (updated by attempts to set hardware gamma ramps)"}; // VorteX: more info cvars, mostly set in VID_CheckExtensions cvar_t gl_info_vendor = {CVAR_READONLY, "gl_info_vendor", "", "indicates brand of graphics chip"}; cvar_t gl_info_renderer = {CVAR_READONLY, "gl_info_renderer", "", "indicates graphics chip model and other information"}; cvar_t gl_info_version = {CVAR_READONLY, "gl_info_version", "", "indicates version of current renderer. begins with 1.0.0, 1.1.0, 1.2.0, 1.3.1 etc."}; cvar_t gl_info_extensions = {CVAR_READONLY, "gl_info_extensions", "", "indicates extension list found by engine, space separated."}; cvar_t gl_info_platform = {CVAR_READONLY, "gl_info_platform", "", "indicates GL platform: WGL, GLX, or AGL."}; cvar_t gl_info_driver = {CVAR_READONLY, "gl_info_driver", "", "name of driver library (opengl32.dll, libGL.so.1, or whatever)."}; // whether hardware gamma ramps are currently in effect qboolean vid_usinghwgamma = false; int vid_gammarampsize = 0; unsigned short *vid_gammaramps = NULL; unsigned short *vid_systemgammaramps = NULL; cvar_t vid_fullscreen = {CVAR_SAVE, "vid_fullscreen", "1", "use fullscreen (1) or windowed (0)"}; cvar_t vid_width = {CVAR_SAVE, "vid_width", "640", "resolution"}; cvar_t vid_height = {CVAR_SAVE, "vid_height", "480", "resolution"}; cvar_t vid_bitsperpixel = {CVAR_SAVE, "vid_bitsperpixel", "32", "how many bits per pixel to render at (32 or 16, 32 is recommended)"}; cvar_t vid_samples = {CVAR_SAVE, "vid_samples", "1", "how many anti-aliasing samples per pixel to request from the graphics driver (4 is recommended, 1 is faster)"}; cvar_t vid_refreshrate = {CVAR_SAVE, "vid_refreshrate", "60", "refresh rate to use, in hz (higher values flicker less, if supported by your monitor)"}; cvar_t vid_userefreshrate = {CVAR_SAVE, "vid_userefreshrate", "0", "set this to 1 to make vid_refreshrate used, or to 0 to let the engine choose a sane default"}; cvar_t vid_stereobuffer = {CVAR_SAVE, "vid_stereobuffer", "0", "enables 'quad-buffered' stereo rendering for stereo shutterglasses, HMD (head mounted display) devices, or polarized stereo LCDs, if supported by your drivers"}; // the density cvars are completely optional, set and use when something needs to have a density-independent size. // TODO: set them when changing resolution, setting them from the commandline will be independent from the resolution - use only if you have a native fixed resolution. // values for the Samsung Galaxy SIII, Snapdragon version: 2.000000 density, 304.799988 xdpi, 303.850464 ydpi cvar_t vid_touchscreen_density = {0, "vid_touchscreen_density", "2.0", "Standard quantized screen density multiplier (see Android documentation for DisplayMetrics), similar values are given on iPhoneOS"}; cvar_t vid_touchscreen_xdpi = {0, "vid_touchscreen_xdpi", "300", "Horizontal DPI of the screen (only valid on Android currently)"}; cvar_t vid_touchscreen_ydpi = {0, "vid_touchscreen_ydpi", "300", "Vertical DPI of the screen (only valid on Android currently)"}; cvar_t vid_vsync = {CVAR_SAVE, "vid_vsync", "0", "sync to vertical blank, prevents 'tearing' (seeing part of one frame and part of another on the screen at the same time), automatically disabled when doing timedemo benchmarks"}; cvar_t vid_mouse = {CVAR_SAVE, "vid_mouse", "1", "whether to use the mouse in windowed mode (fullscreen always does)"}; cvar_t vid_grabkeyboard = {CVAR_SAVE, "vid_grabkeyboard", "0", "whether to grab the keyboard when mouse is active (prevents use of volume control keys, music player keys, etc on some keyboards)"}; cvar_t vid_minwidth = {0, "vid_minwidth", "0", "minimum vid_width that is acceptable (to be set in default.cfg in mods)"}; cvar_t vid_minheight = {0, "vid_minheight", "0", "minimum vid_height that is acceptable (to be set in default.cfg in mods)"}; cvar_t vid_gl13 = {0, "vid_gl13", "1", "enables faster rendering using OpenGL 1.3 features (such as GL_ARB_texture_env_combine extension)"}; cvar_t vid_gl20 = {0, "vid_gl20", "1", "enables faster rendering using OpenGL 2.0 features (such as GL_ARB_fragment_shader extension)"}; cvar_t gl_finish = {0, "gl_finish", "0", "make the cpu wait for the graphics processor at the end of each rendered frame (can help with strange input or video lag problems on some machines)"}; cvar_t vid_sRGB = {CVAR_SAVE, "vid_sRGB", "0", "if hardware is capable, modify rendering to be gamma corrected for the sRGB color standard (computer monitors, TVs), recommended"}; cvar_t vid_sRGB_fallback = {CVAR_SAVE, "vid_sRGB_fallback", "0", "do an approximate sRGB fallback if not properly supported by hardware (2: also use the fallback if framebuffer is 8bit, 3: always use the fallback even if sRGB is supported)"}; cvar_t vid_touchscreen = {0, "vid_touchscreen", "0", "Use touchscreen-style input (no mouse grab, track mouse motion only while button is down, screen areas for mimicing joystick axes and buttons"}; cvar_t vid_touchscreen_showkeyboard = {0, "vid_touchscreen_showkeyboard", "0", "shows the platform's screen keyboard for text entry, can be set by csqc or menu qc if it wants to receive text input, does nothing if the platform has no screen keyboard"}; cvar_t vid_touchscreen_supportshowkeyboard = {CVAR_READONLY, "vid_touchscreen_supportshowkeyboard", "0", "indicates if the platform supports a virtual keyboard"}; cvar_t vid_stick_mouse = {CVAR_SAVE, "vid_stick_mouse", "0", "have the mouse stuck in the center of the screen" }; cvar_t vid_resizable = {CVAR_SAVE, "vid_resizable", "0", "0: window not resizable, 1: resizable, 2: window can be resized but the framebuffer isn't adjusted" }; cvar_t vid_desktopfullscreen = {CVAR_SAVE, "vid_desktopfullscreen", "0", "force desktop resolution for fullscreen; also use some OS dependent tricks for better fullscreen integration"}; cvar_t v_gamma = {CVAR_SAVE, "v_gamma", "1", "inverse gamma correction value, a brightness effect that does not affect white or black, and tends to make the image grey and dull"}; cvar_t v_contrast = {CVAR_SAVE, "v_contrast", "1", "brightness of white (values above 1 give a brighter image with increased color saturation, unlike v_gamma)"}; cvar_t v_brightness = {CVAR_SAVE, "v_brightness", "0", "brightness of black, useful for monitors that are too dark"}; cvar_t v_contrastboost = {CVAR_SAVE, "v_contrastboost", "1", "by how much to multiply the contrast in dark areas (1 is no change)"}; cvar_t v_color_enable = {CVAR_SAVE, "v_color_enable", "0", "enables black-grey-white color correction curve controls"}; cvar_t v_color_black_r = {CVAR_SAVE, "v_color_black_r", "0", "desired color of black"}; cvar_t v_color_black_g = {CVAR_SAVE, "v_color_black_g", "0", "desired color of black"}; cvar_t v_color_black_b = {CVAR_SAVE, "v_color_black_b", "0", "desired color of black"}; cvar_t v_color_grey_r = {CVAR_SAVE, "v_color_grey_r", "0.5", "desired color of grey"}; cvar_t v_color_grey_g = {CVAR_SAVE, "v_color_grey_g", "0.5", "desired color of grey"}; cvar_t v_color_grey_b = {CVAR_SAVE, "v_color_grey_b", "0.5", "desired color of grey"}; cvar_t v_color_white_r = {CVAR_SAVE, "v_color_white_r", "1", "desired color of white"}; cvar_t v_color_white_g = {CVAR_SAVE, "v_color_white_g", "1", "desired color of white"}; cvar_t v_color_white_b = {CVAR_SAVE, "v_color_white_b", "1", "desired color of white"}; cvar_t v_hwgamma = {CVAR_SAVE, "v_hwgamma", "0", "enables use of hardware gamma correction ramps if available (note: does not work very well on Windows2000 and above), values are 0 = off, 1 = attempt to use hardware gamma, 2 = use hardware gamma whether it works or not"}; cvar_t v_glslgamma = {CVAR_SAVE, "v_glslgamma", "1", "enables use of GLSL to apply gamma correction ramps if available (note: overrides v_hwgamma)"}; cvar_t v_glslgamma_2d = {CVAR_SAVE, "v_glslgamma_2d", "0", "applies GLSL gamma to 2d pictures (HUD, fonts)"}; cvar_t v_psycho = {0, "v_psycho", "0", "easter egg"}; // brand of graphics chip const char *gl_vendor; // graphics chip model and other information const char *gl_renderer; // begins with 1.0.0, 1.1.0, 1.2.0, 1.2.1, 1.3.0, 1.3.1, or 1.4.0 const char *gl_version; // extensions list, space separated const char *gl_extensions; // WGL, GLX, or AGL const char *gl_platform; // another extensions list, containing platform-specific extensions that are // not in the main list const char *gl_platformextensions; // name of driver library (opengl32.dll, libGL.so.1, or whatever) char gl_driver[256]; #ifndef USE_GLES2 // GL_ARB_multitexture void (GLAPIENTRY *qglMultiTexCoord1f) (GLenum, GLfloat); void (GLAPIENTRY *qglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); void (GLAPIENTRY *qglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); void (GLAPIENTRY *qglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); void (GLAPIENTRY *qglActiveTexture) (GLenum); void (GLAPIENTRY *qglClientActiveTexture) (GLenum); // general GL functions void (GLAPIENTRY *qglClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); void (GLAPIENTRY *qglClear)(GLbitfield mask); void (GLAPIENTRY *qglAlphaFunc)(GLenum func, GLclampf ref); void (GLAPIENTRY *qglBlendFunc)(GLenum sfactor, GLenum dfactor); void (GLAPIENTRY *qglCullFace)(GLenum mode); void (GLAPIENTRY *qglDrawBuffer)(GLenum mode); void (GLAPIENTRY *qglReadBuffer)(GLenum mode); void (GLAPIENTRY *qglEnable)(GLenum cap); void (GLAPIENTRY *qglDisable)(GLenum cap); GLboolean (GLAPIENTRY *qglIsEnabled)(GLenum cap); void (GLAPIENTRY *qglEnableClientState)(GLenum cap); void (GLAPIENTRY *qglDisableClientState)(GLenum cap); void (GLAPIENTRY *qglGetBooleanv)(GLenum pname, GLboolean *params); void (GLAPIENTRY *qglGetDoublev)(GLenum pname, GLdouble *params); void (GLAPIENTRY *qglGetFloatv)(GLenum pname, GLfloat *params); void (GLAPIENTRY *qglGetIntegerv)(GLenum pname, GLint *params); GLenum (GLAPIENTRY *qglGetError)(void); const GLubyte* (GLAPIENTRY *qglGetString)(GLenum name); void (GLAPIENTRY *qglFinish)(void); void (GLAPIENTRY *qglFlush)(void); void (GLAPIENTRY *qglClearDepth)(GLclampd depth); void (GLAPIENTRY *qglDepthFunc)(GLenum func); void (GLAPIENTRY *qglDepthMask)(GLboolean flag); void (GLAPIENTRY *qglDepthRange)(GLclampd near_val, GLclampd far_val); void (GLAPIENTRY *qglDepthRangef)(GLclampf near_val, GLclampf far_val); void (GLAPIENTRY *qglColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); void (GLAPIENTRY *qglDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); void (GLAPIENTRY *qglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); void (GLAPIENTRY *qglDrawArrays)(GLenum mode, GLint first, GLsizei count); void (GLAPIENTRY *qglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); void (GLAPIENTRY *qglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); void (GLAPIENTRY *qglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); void (GLAPIENTRY *qglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); void (GLAPIENTRY *qglArrayElement)(GLint i); void (GLAPIENTRY *qglColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); void (GLAPIENTRY *qglColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); void (GLAPIENTRY *qglTexCoord1f)(GLfloat s); void (GLAPIENTRY *qglTexCoord2f)(GLfloat s, GLfloat t); void (GLAPIENTRY *qglTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); void (GLAPIENTRY *qglTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); void (GLAPIENTRY *qglVertex2f)(GLfloat x, GLfloat y); void (GLAPIENTRY *qglVertex3f)(GLfloat x, GLfloat y, GLfloat z); void (GLAPIENTRY *qglVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); void (GLAPIENTRY *qglBegin)(GLenum mode); void (GLAPIENTRY *qglEnd)(void); void (GLAPIENTRY *qglMatrixMode)(GLenum mode); //void (GLAPIENTRY *qglOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); //void (GLAPIENTRY *qglFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); void (GLAPIENTRY *qglViewport)(GLint x, GLint y, GLsizei width, GLsizei height); //void (GLAPIENTRY *qglPushMatrix)(void); //void (GLAPIENTRY *qglPopMatrix)(void); void (GLAPIENTRY *qglLoadIdentity)(void); //void (GLAPIENTRY *qglLoadMatrixd)(const GLdouble *m); void (GLAPIENTRY *qglLoadMatrixf)(const GLfloat *m); //void (GLAPIENTRY *qglMultMatrixd)(const GLdouble *m); //void (GLAPIENTRY *qglMultMatrixf)(const GLfloat *m); //void (GLAPIENTRY *qglRotated)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); //void (GLAPIENTRY *qglRotatef)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); //void (GLAPIENTRY *qglScaled)(GLdouble x, GLdouble y, GLdouble z); //void (GLAPIENTRY *qglScalef)(GLfloat x, GLfloat y, GLfloat z); //void (GLAPIENTRY *qglTranslated)(GLdouble x, GLdouble y, GLdouble z); //void (GLAPIENTRY *qglTranslatef)(GLfloat x, GLfloat y, GLfloat z); void (GLAPIENTRY *qglReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); void (GLAPIENTRY *qglStencilFunc)(GLenum func, GLint ref, GLuint mask); void (GLAPIENTRY *qglStencilMask)(GLuint mask); void (GLAPIENTRY *qglStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); void (GLAPIENTRY *qglClearStencil)(GLint s); void (GLAPIENTRY *qglTexEnvf)(GLenum target, GLenum pname, GLfloat param); void (GLAPIENTRY *qglTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); void (GLAPIENTRY *qglTexEnvi)(GLenum target, GLenum pname, GLint param); void (GLAPIENTRY *qglTexParameterf)(GLenum target, GLenum pname, GLfloat param); void (GLAPIENTRY *qglTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); void (GLAPIENTRY *qglTexParameteri)(GLenum target, GLenum pname, GLint param); void (GLAPIENTRY *qglGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); void (GLAPIENTRY *qglGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); void (GLAPIENTRY *qglGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); void (GLAPIENTRY *qglHint)(GLenum target, GLenum mode); void (GLAPIENTRY *qglGenTextures)(GLsizei n, GLuint *textures); void (GLAPIENTRY *qglDeleteTextures)(GLsizei n, const GLuint *textures); void (GLAPIENTRY *qglBindTexture)(GLenum target, GLuint texture); //void (GLAPIENTRY *qglPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); //GLboolean (GLAPIENTRY *qglAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); //GLboolean (GLAPIENTRY *qglIsTexture)(GLuint texture); //void (GLAPIENTRY *qglPixelStoref)(GLenum pname, GLfloat param); void (GLAPIENTRY *qglPixelStorei)(GLenum pname, GLint param); //void (GLAPIENTRY *qglTexImage1D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); void (GLAPIENTRY *qglTexImage2D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); //void (GLAPIENTRY *qglTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); void (GLAPIENTRY *qglTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); //void (GLAPIENTRY *qglCopyTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); void (GLAPIENTRY *qglCopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); //void (GLAPIENTRY *qglCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); void (GLAPIENTRY *qglCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); void (GLAPIENTRY *qglDrawRangeElementsEXT)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); //void (GLAPIENTRY *qglColorTableEXT)(int, int, int, int, int, const void *); void (GLAPIENTRY *qglTexImage3D)(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); void (GLAPIENTRY *qglTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); void (GLAPIENTRY *qglCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); void (GLAPIENTRY *qglScissor)(GLint x, GLint y, GLsizei width, GLsizei height); void (GLAPIENTRY *qglPolygonOffset)(GLfloat factor, GLfloat units); void (GLAPIENTRY *qglPolygonMode)(GLenum face, GLenum mode); void (GLAPIENTRY *qglPolygonStipple)(const GLubyte *mask); //void (GLAPIENTRY *qglClipPlane)(GLenum plane, const GLdouble *equation); //void (GLAPIENTRY *qglGetClipPlane)(GLenum plane, GLdouble *equation); //[515]: added on 29.07.2005 void (GLAPIENTRY *qglLineWidth)(GLfloat width); void (GLAPIENTRY *qglPointSize)(GLfloat size); void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); void (GLAPIENTRY *qglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); void (GLAPIENTRY *qglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); void (GLAPIENTRY *qglActiveStencilFaceEXT)(GLenum); void (GLAPIENTRY *qglDeleteShader)(GLuint obj); void (GLAPIENTRY *qglDeleteProgram)(GLuint obj); //GLuint (GLAPIENTRY *qglGetHandle)(GLenum pname); void (GLAPIENTRY *qglDetachShader)(GLuint containerObj, GLuint attachedObj); GLuint (GLAPIENTRY *qglCreateShader)(GLenum shaderType); void (GLAPIENTRY *qglShaderSource)(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); void (GLAPIENTRY *qglCompileShader)(GLuint shaderObj); GLuint (GLAPIENTRY *qglCreateProgram)(void); void (GLAPIENTRY *qglAttachShader)(GLuint containerObj, GLuint obj); void (GLAPIENTRY *qglLinkProgram)(GLuint programObj); void (GLAPIENTRY *qglUseProgram)(GLuint programObj); void (GLAPIENTRY *qglValidateProgram)(GLuint programObj); void (GLAPIENTRY *qglUniform1f)(GLint location, GLfloat v0); void (GLAPIENTRY *qglUniform2f)(GLint location, GLfloat v0, GLfloat v1); void (GLAPIENTRY *qglUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); void (GLAPIENTRY *qglUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); void (GLAPIENTRY *qglUniform1i)(GLint location, GLint v0); void (GLAPIENTRY *qglUniform2i)(GLint location, GLint v0, GLint v1); void (GLAPIENTRY *qglUniform3i)(GLint location, GLint v0, GLint v1, GLint v2); void (GLAPIENTRY *qglUniform4i)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); void (GLAPIENTRY *qglUniform1fv)(GLint location, GLsizei count, const GLfloat *value); void (GLAPIENTRY *qglUniform2fv)(GLint location, GLsizei count, const GLfloat *value); void (GLAPIENTRY *qglUniform3fv)(GLint location, GLsizei count, const GLfloat *value); void (GLAPIENTRY *qglUniform4fv)(GLint location, GLsizei count, const GLfloat *value); void (GLAPIENTRY *qglUniform1iv)(GLint location, GLsizei count, const GLint *value); void (GLAPIENTRY *qglUniform2iv)(GLint location, GLsizei count, const GLint *value); void (GLAPIENTRY *qglUniform3iv)(GLint location, GLsizei count, const GLint *value); void (GLAPIENTRY *qglUniform4iv)(GLint location, GLsizei count, const GLint *value); void (GLAPIENTRY *qglUniformMatrix2fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); void (GLAPIENTRY *qglUniformMatrix3fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); void (GLAPIENTRY *qglUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); void (GLAPIENTRY *qglGetShaderiv)(GLuint obj, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetProgramiv)(GLuint obj, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetShaderInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); void (GLAPIENTRY *qglGetProgramInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); void (GLAPIENTRY *qglGetAttachedShaders)(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); GLint (GLAPIENTRY *qglGetUniformLocation)(GLuint programObj, const GLchar *name); void (GLAPIENTRY *qglGetActiveUniform)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); void (GLAPIENTRY *qglGetUniformfv)(GLuint programObj, GLint location, GLfloat *params); void (GLAPIENTRY *qglGetUniformiv)(GLuint programObj, GLint location, GLint *params); void (GLAPIENTRY *qglGetShaderSource)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); void (GLAPIENTRY *qglVertexAttrib1f)(GLuint index, GLfloat v0); void (GLAPIENTRY *qglVertexAttrib1s)(GLuint index, GLshort v0); void (GLAPIENTRY *qglVertexAttrib1d)(GLuint index, GLdouble v0); void (GLAPIENTRY *qglVertexAttrib2f)(GLuint index, GLfloat v0, GLfloat v1); void (GLAPIENTRY *qglVertexAttrib2s)(GLuint index, GLshort v0, GLshort v1); void (GLAPIENTRY *qglVertexAttrib2d)(GLuint index, GLdouble v0, GLdouble v1); void (GLAPIENTRY *qglVertexAttrib3f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); void (GLAPIENTRY *qglVertexAttrib3s)(GLuint index, GLshort v0, GLshort v1, GLshort v2); void (GLAPIENTRY *qglVertexAttrib3d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); void (GLAPIENTRY *qglVertexAttrib4f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); void (GLAPIENTRY *qglVertexAttrib4s)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); void (GLAPIENTRY *qglVertexAttrib4d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); void (GLAPIENTRY *qglVertexAttrib4Nub)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); void (GLAPIENTRY *qglVertexAttrib1fv)(GLuint index, const GLfloat *v); void (GLAPIENTRY *qglVertexAttrib1sv)(GLuint index, const GLshort *v); void (GLAPIENTRY *qglVertexAttrib1dv)(GLuint index, const GLdouble *v); void (GLAPIENTRY *qglVertexAttrib2fv)(GLuint index, const GLfloat *v); void (GLAPIENTRY *qglVertexAttrib2sv)(GLuint index, const GLshort *v); void (GLAPIENTRY *qglVertexAttrib2dv)(GLuint index, const GLdouble *v); void (GLAPIENTRY *qglVertexAttrib3fv)(GLuint index, const GLfloat *v); void (GLAPIENTRY *qglVertexAttrib3sv)(GLuint index, const GLshort *v); void (GLAPIENTRY *qglVertexAttrib3dv)(GLuint index, const GLdouble *v); void (GLAPIENTRY *qglVertexAttrib4fv)(GLuint index, const GLfloat *v); void (GLAPIENTRY *qglVertexAttrib4sv)(GLuint index, const GLshort *v); void (GLAPIENTRY *qglVertexAttrib4dv)(GLuint index, const GLdouble *v); void (GLAPIENTRY *qglVertexAttrib4iv)(GLuint index, const GLint *v); void (GLAPIENTRY *qglVertexAttrib4bv)(GLuint index, const GLbyte *v); void (GLAPIENTRY *qglVertexAttrib4ubv)(GLuint index, const GLubyte *v); void (GLAPIENTRY *qglVertexAttrib4usv)(GLuint index, const GLushort *v); void (GLAPIENTRY *qglVertexAttrib4uiv)(GLuint index, const GLuint *v); void (GLAPIENTRY *qglVertexAttrib4Nbv)(GLuint index, const GLbyte *v); void (GLAPIENTRY *qglVertexAttrib4Nsv)(GLuint index, const GLshort *v); void (GLAPIENTRY *qglVertexAttrib4Niv)(GLuint index, const GLint *v); void (GLAPIENTRY *qglVertexAttrib4Nubv)(GLuint index, const GLubyte *v); void (GLAPIENTRY *qglVertexAttrib4Nusv)(GLuint index, const GLushort *v); void (GLAPIENTRY *qglVertexAttrib4Nuiv)(GLuint index, const GLuint *v); void (GLAPIENTRY *qglVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); void (GLAPIENTRY *qglEnableVertexAttribArray)(GLuint index); void (GLAPIENTRY *qglDisableVertexAttribArray)(GLuint index); void (GLAPIENTRY *qglBindAttribLocation)(GLuint programObj, GLuint index, const GLchar *name); void (GLAPIENTRY *qglBindFragDataLocation)(GLuint programObj, GLuint index, const GLchar *name); void (GLAPIENTRY *qglGetActiveAttrib)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); GLint (GLAPIENTRY *qglGetAttribLocation)(GLuint programObj, const GLchar *name); void (GLAPIENTRY *qglGetVertexAttribdv)(GLuint index, GLenum pname, GLdouble *params); void (GLAPIENTRY *qglGetVertexAttribfv)(GLuint index, GLenum pname, GLfloat *params); void (GLAPIENTRY *qglGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetVertexAttribPointerv)(GLuint index, GLenum pname, GLvoid **pointer); //GL_ARB_vertex_buffer_object void (GLAPIENTRY *qglBindBufferARB) (GLenum target, GLuint buffer); void (GLAPIENTRY *qglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); void (GLAPIENTRY *qglGenBuffersARB) (GLsizei n, GLuint *buffers); GLboolean (GLAPIENTRY *qglIsBufferARB) (GLuint buffer); GLvoid* (GLAPIENTRY *qglMapBufferARB) (GLenum target, GLenum access); GLboolean (GLAPIENTRY *qglUnmapBufferARB) (GLenum target); void (GLAPIENTRY *qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); void (GLAPIENTRY *qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); //GL_ARB_framebuffer_object GLboolean (GLAPIENTRY *qglIsRenderbuffer)(GLuint renderbuffer); GLvoid (GLAPIENTRY *qglBindRenderbuffer)(GLenum target, GLuint renderbuffer); GLvoid (GLAPIENTRY *qglDeleteRenderbuffers)(GLsizei n, const GLuint *renderbuffers); GLvoid (GLAPIENTRY *qglGenRenderbuffers)(GLsizei n, GLuint *renderbuffers); GLvoid (GLAPIENTRY *qglRenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); GLvoid (GLAPIENTRY *qglRenderbufferStorageMultisample)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); GLvoid (GLAPIENTRY *qglGetRenderbufferParameteriv)(GLenum target, GLenum pname, GLint *params); GLboolean (GLAPIENTRY *qglIsFramebuffer)(GLuint framebuffer); GLvoid (GLAPIENTRY *qglBindFramebuffer)(GLenum target, GLuint framebuffer); GLvoid (GLAPIENTRY *qglDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers); GLvoid (GLAPIENTRY *qglGenFramebuffers)(GLsizei n, GLuint *framebuffers); GLenum (GLAPIENTRY *qglCheckFramebufferStatus)(GLenum target); GLvoid (GLAPIENTRY *qglFramebufferTexture1D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLvoid (GLAPIENTRY *qglFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); GLvoid (GLAPIENTRY *qglFramebufferTexture3D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); GLvoid (GLAPIENTRY *qglFramebufferTextureLayer)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); GLvoid (GLAPIENTRY *qglFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); GLvoid (GLAPIENTRY *qglGetFramebufferAttachmentParameteriv)(GLenum target, GLenum attachment, GLenum pname, GLint *params); GLvoid (GLAPIENTRY *qglBlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); GLvoid (GLAPIENTRY *qglGenerateMipmap)(GLenum target); void (GLAPIENTRY *qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); void (GLAPIENTRY *qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); void (GLAPIENTRY *qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); //void (GLAPIENTRY *qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); void (GLAPIENTRY *qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); void (GLAPIENTRY *qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); //void (GLAPIENTRY *qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); void (GLAPIENTRY *qglGetCompressedTexImageARB)(GLenum target, GLint lod, void *img); void (GLAPIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); void (GLAPIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); GLboolean (GLAPIENTRY *qglIsQueryARB)(GLuint qid); void (GLAPIENTRY *qglBeginQueryARB)(GLenum target, GLuint qid); void (GLAPIENTRY *qglEndQueryARB)(GLenum target); void (GLAPIENTRY *qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetQueryObjectivARB)(GLuint qid, GLenum pname, GLint *params); void (GLAPIENTRY *qglGetQueryObjectuivARB)(GLuint qid, GLenum pname, GLuint *params); void (GLAPIENTRY *qglSampleCoverageARB)(GLclampf value, GLboolean invert); void (GLAPIENTRY *qglGetUniformIndices)(GLuint program, GLsizei uniformCount, const GLchar** uniformNames, GLuint* uniformIndices); void (GLAPIENTRY *qglGetActiveUniformsiv)(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params); void (GLAPIENTRY *qglGetActiveUniformName)(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformName); GLuint (GLAPIENTRY *qglGetUniformBlockIndex)(GLuint program, const GLchar* uniformBlockName); void (GLAPIENTRY *qglGetActiveUniformBlockiv)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); void (GLAPIENTRY *qglGetActiveUniformBlockName)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, GLchar* uniformBlockName); void (GLAPIENTRY *qglBindBufferRange)(GLenum target, GLuint index, GLuint buffer, GLintptrARB offset, GLsizeiptrARB size); void (GLAPIENTRY *qglBindBufferBase)(GLenum target, GLuint index, GLuint buffer); void (GLAPIENTRY *qglGetIntegeri_v)(GLenum target, GLuint index, GLint* data); void (GLAPIENTRY *qglUniformBlockBinding)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); void (GLAPIENTRY *qglBlendFuncSeparate)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #endif #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif qboolean GL_CheckExtension(const char *minglver_or_ext, const dllfunction_t *funcs, const char *disableparm, int silent) { int failed = false; const dllfunction_t *func; struct { int major, minor; } min_version, curr_version; char extstr[MAX_INPUTLINE]; int ext; if(sscanf(minglver_or_ext, "%d.%d", &min_version.major, &min_version.minor) == 2) ext = 0; // opengl version else if(minglver_or_ext[0] != toupper(minglver_or_ext[0])) ext = -1; // pseudo name else ext = 1; // extension name if (ext) Con_DPrintf("checking for %s... ", minglver_or_ext); else Con_DPrintf("checking for OpenGL %s core features... ", minglver_or_ext); for (func = funcs;func && func->name;func++) *func->funcvariable = NULL; if (disableparm && (COM_CheckParm(disableparm) || COM_CheckParm("-safe"))) { Con_DPrint("disabled by commandline\n"); return false; } if (ext == 1) // opengl extension { if (!strstr(gl_extensions ? gl_extensions : "", minglver_or_ext) && !strstr(gl_platformextensions ? gl_platformextensions : "", minglver_or_ext)) { Con_DPrint("not detected\n"); return false; } } if(ext == 0) // opengl version { if (sscanf(gl_version, "%d.%d", &curr_version.major, &curr_version.minor) < 2) curr_version.major = curr_version.minor = 1; if (curr_version.major < min_version.major || (curr_version.major == min_version.major && curr_version.minor < min_version.minor)) { Con_DPrintf("not detected (OpenGL %d.%d loaded)\n", curr_version.major, curr_version.minor); return false; } } for (func = funcs;func && func->name != NULL;func++) { // Con_DPrintf("\n %s... ", func->name); // functions are cleared before all the extensions are evaluated if (!(*func->funcvariable = (void *) GL_GetProcAddress(func->name))) { if (ext && !silent) Con_DPrintf("%s is missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); if (!ext) Con_Printf("OpenGL %s core features are missing function \"%s\" - broken driver!\n", minglver_or_ext, func->name); failed = true; } } // delay the return so it prints all missing functions if (failed) return false; // VorteX: add to found extension list dpsnprintf(extstr, sizeof(extstr), "%s %s ", gl_info_extensions.string, minglver_or_ext); Cvar_SetQuick(&gl_info_extensions, extstr); Con_DPrint("enabled\n"); return true; } #ifndef USE_GLES2 static dllfunction_t opengl110funcs[] = { {"glClearColor", (void **) &qglClearColor}, {"glClear", (void **) &qglClear}, {"glAlphaFunc", (void **) &qglAlphaFunc}, {"glBlendFunc", (void **) &qglBlendFunc}, {"glCullFace", (void **) &qglCullFace}, {"glDrawBuffer", (void **) &qglDrawBuffer}, {"glReadBuffer", (void **) &qglReadBuffer}, {"glEnable", (void **) &qglEnable}, {"glDisable", (void **) &qglDisable}, {"glIsEnabled", (void **) &qglIsEnabled}, {"glEnableClientState", (void **) &qglEnableClientState}, {"glDisableClientState", (void **) &qglDisableClientState}, {"glGetBooleanv", (void **) &qglGetBooleanv}, {"glGetDoublev", (void **) &qglGetDoublev}, {"glGetFloatv", (void **) &qglGetFloatv}, {"glGetIntegerv", (void **) &qglGetIntegerv}, {"glGetError", (void **) &qglGetError}, {"glGetString", (void **) &qglGetString}, {"glFinish", (void **) &qglFinish}, {"glFlush", (void **) &qglFlush}, {"glClearDepth", (void **) &qglClearDepth}, {"glDepthFunc", (void **) &qglDepthFunc}, {"glDepthMask", (void **) &qglDepthMask}, {"glDepthRange", (void **) &qglDepthRange}, {"glDrawElements", (void **) &qglDrawElements}, {"glDrawArrays", (void **) &qglDrawArrays}, {"glColorMask", (void **) &qglColorMask}, {"glVertexPointer", (void **) &qglVertexPointer}, {"glNormalPointer", (void **) &qglNormalPointer}, {"glColorPointer", (void **) &qglColorPointer}, {"glTexCoordPointer", (void **) &qglTexCoordPointer}, {"glArrayElement", (void **) &qglArrayElement}, {"glColor4ub", (void **) &qglColor4ub}, {"glColor4f", (void **) &qglColor4f}, {"glTexCoord1f", (void **) &qglTexCoord1f}, {"glTexCoord2f", (void **) &qglTexCoord2f}, {"glTexCoord3f", (void **) &qglTexCoord3f}, {"glTexCoord4f", (void **) &qglTexCoord4f}, {"glVertex2f", (void **) &qglVertex2f}, {"glVertex3f", (void **) &qglVertex3f}, {"glVertex4f", (void **) &qglVertex4f}, {"glBegin", (void **) &qglBegin}, {"glEnd", (void **) &qglEnd}, //[515]: added on 29.07.2005 {"glLineWidth", (void**) &qglLineWidth}, {"glPointSize", (void**) &qglPointSize}, // {"glMatrixMode", (void **) &qglMatrixMode}, // {"glOrtho", (void **) &qglOrtho}, // {"glFrustum", (void **) &qglFrustum}, {"glViewport", (void **) &qglViewport}, // {"glPushMatrix", (void **) &qglPushMatrix}, // {"glPopMatrix", (void **) &qglPopMatrix}, {"glLoadIdentity", (void **) &qglLoadIdentity}, // {"glLoadMatrixd", (void **) &qglLoadMatrixd}, {"glLoadMatrixf", (void **) &qglLoadMatrixf}, // {"glMultMatrixd", (void **) &qglMultMatrixd}, // {"glMultMatrixf", (void **) &qglMultMatrixf}, // {"glRotated", (void **) &qglRotated}, // {"glRotatef", (void **) &qglRotatef}, // {"glScaled", (void **) &qglScaled}, // {"glScalef", (void **) &qglScalef}, // {"glTranslated", (void **) &qglTranslated}, // {"glTranslatef", (void **) &qglTranslatef}, {"glReadPixels", (void **) &qglReadPixels}, {"glStencilFunc", (void **) &qglStencilFunc}, {"glStencilMask", (void **) &qglStencilMask}, {"glStencilOp", (void **) &qglStencilOp}, {"glClearStencil", (void **) &qglClearStencil}, {"glTexEnvf", (void **) &qglTexEnvf}, {"glTexEnvfv", (void **) &qglTexEnvfv}, {"glTexEnvi", (void **) &qglTexEnvi}, {"glTexParameterf", (void **) &qglTexParameterf}, {"glTexParameterfv", (void **) &qglTexParameterfv}, {"glTexParameteri", (void **) &qglTexParameteri}, {"glGetTexImage", (void **) &qglGetTexImage}, {"glGetTexParameterfv", (void **) &qglGetTexParameterfv}, {"glGetTexParameteriv", (void **) &qglGetTexParameteriv}, {"glGetTexLevelParameterfv", (void **) &qglGetTexLevelParameterfv}, {"glGetTexLevelParameteriv", (void **) &qglGetTexLevelParameteriv}, {"glHint", (void **) &qglHint}, // {"glPixelStoref", (void **) &qglPixelStoref}, {"glPixelStorei", (void **) &qglPixelStorei}, {"glGenTextures", (void **) &qglGenTextures}, {"glDeleteTextures", (void **) &qglDeleteTextures}, {"glBindTexture", (void **) &qglBindTexture}, // {"glPrioritizeTextures", (void **) &qglPrioritizeTextures}, // {"glAreTexturesResident", (void **) &qglAreTexturesResident}, // {"glIsTexture", (void **) &qglIsTexture}, // {"glTexImage1D", (void **) &qglTexImage1D}, {"glTexImage2D", (void **) &qglTexImage2D}, // {"glTexSubImage1D", (void **) &qglTexSubImage1D}, {"glTexSubImage2D", (void **) &qglTexSubImage2D}, // {"glCopyTexImage1D", (void **) &qglCopyTexImage1D}, {"glCopyTexImage2D", (void **) &qglCopyTexImage2D}, // {"glCopyTexSubImage1D", (void **) &qglCopyTexSubImage1D}, {"glCopyTexSubImage2D", (void **) &qglCopyTexSubImage2D}, {"glScissor", (void **) &qglScissor}, {"glPolygonOffset", (void **) &qglPolygonOffset}, {"glPolygonMode", (void **) &qglPolygonMode}, {"glPolygonStipple", (void **) &qglPolygonStipple}, // {"glClipPlane", (void **) &qglClipPlane}, // {"glGetClipPlane", (void **) &qglGetClipPlane}, {NULL, NULL} }; static dllfunction_t drawrangeelementsfuncs[] = { {"glDrawRangeElements", (void **) &qglDrawRangeElements}, {NULL, NULL} }; static dllfunction_t drawrangeelementsextfuncs[] = { {"glDrawRangeElementsEXT", (void **) &qglDrawRangeElementsEXT}, {NULL, NULL} }; static dllfunction_t multitexturefuncs[] = { {"glMultiTexCoord1fARB", (void **) &qglMultiTexCoord1f}, {"glMultiTexCoord2fARB", (void **) &qglMultiTexCoord2f}, {"glMultiTexCoord3fARB", (void **) &qglMultiTexCoord3f}, {"glMultiTexCoord4fARB", (void **) &qglMultiTexCoord4f}, {"glActiveTextureARB", (void **) &qglActiveTexture}, {"glClientActiveTextureARB", (void **) &qglClientActiveTexture}, {NULL, NULL} }; static dllfunction_t texture3dextfuncs[] = { {"glTexImage3DEXT", (void **) &qglTexImage3D}, {"glTexSubImage3DEXT", (void **) &qglTexSubImage3D}, {"glCopyTexSubImage3DEXT", (void **) &qglCopyTexSubImage3D}, {NULL, NULL} }; static dllfunction_t atiseparatestencilfuncs[] = { {"glStencilOpSeparateATI", (void **) &qglStencilOpSeparate}, {"glStencilFuncSeparateATI", (void **) &qglStencilFuncSeparate}, {NULL, NULL} }; static dllfunction_t gl2separatestencilfuncs[] = { {"glStencilOpSeparate", (void **) &qglStencilOpSeparate}, {"glStencilFuncSeparate", (void **) &qglStencilFuncSeparate}, {NULL, NULL} }; static dllfunction_t stenciltwosidefuncs[] = { {"glActiveStencilFaceEXT", (void **) &qglActiveStencilFaceEXT}, {NULL, NULL} }; static dllfunction_t blendequationfuncs[] = { {"glBlendEquationEXT", (void **) &qglBlendEquationEXT}, {NULL, NULL} }; static dllfunction_t gl20shaderfuncs[] = { {"glDeleteShader", (void **) &qglDeleteShader}, {"glDeleteProgram", (void **) &qglDeleteProgram}, // {"glGetHandle", (void **) &qglGetHandle}, {"glDetachShader", (void **) &qglDetachShader}, {"glCreateShader", (void **) &qglCreateShader}, {"glShaderSource", (void **) &qglShaderSource}, {"glCompileShader", (void **) &qglCompileShader}, {"glCreateProgram", (void **) &qglCreateProgram}, {"glAttachShader", (void **) &qglAttachShader}, {"glLinkProgram", (void **) &qglLinkProgram}, {"glUseProgram", (void **) &qglUseProgram}, {"glValidateProgram", (void **) &qglValidateProgram}, {"glUniform1f", (void **) &qglUniform1f}, {"glUniform2f", (void **) &qglUniform2f}, {"glUniform3f", (void **) &qglUniform3f}, {"glUniform4f", (void **) &qglUniform4f}, {"glUniform1i", (void **) &qglUniform1i}, {"glUniform2i", (void **) &qglUniform2i}, {"glUniform3i", (void **) &qglUniform3i}, {"glUniform4i", (void **) &qglUniform4i}, {"glUniform1fv", (void **) &qglUniform1fv}, {"glUniform2fv", (void **) &qglUniform2fv}, {"glUniform3fv", (void **) &qglUniform3fv}, {"glUniform4fv", (void **) &qglUniform4fv}, {"glUniform1iv", (void **) &qglUniform1iv}, {"glUniform2iv", (void **) &qglUniform2iv}, {"glUniform3iv", (void **) &qglUniform3iv}, {"glUniform4iv", (void **) &qglUniform4iv}, {"glUniformMatrix2fv", (void **) &qglUniformMatrix2fv}, {"glUniformMatrix3fv", (void **) &qglUniformMatrix3fv}, {"glUniformMatrix4fv", (void **) &qglUniformMatrix4fv}, {"glGetShaderiv", (void **) &qglGetShaderiv}, {"glGetProgramiv", (void **) &qglGetProgramiv}, {"glGetShaderInfoLog", (void **) &qglGetShaderInfoLog}, {"glGetProgramInfoLog", (void **) &qglGetProgramInfoLog}, {"glGetAttachedShaders", (void **) &qglGetAttachedShaders}, {"glGetUniformLocation", (void **) &qglGetUniformLocation}, {"glGetActiveUniform", (void **) &qglGetActiveUniform}, {"glGetUniformfv", (void **) &qglGetUniformfv}, {"glGetUniformiv", (void **) &qglGetUniformiv}, {"glGetShaderSource", (void **) &qglGetShaderSource}, {"glVertexAttrib1f", (void **) &qglVertexAttrib1f}, {"glVertexAttrib1s", (void **) &qglVertexAttrib1s}, {"glVertexAttrib1d", (void **) &qglVertexAttrib1d}, {"glVertexAttrib2f", (void **) &qglVertexAttrib2f}, {"glVertexAttrib2s", (void **) &qglVertexAttrib2s}, {"glVertexAttrib2d", (void **) &qglVertexAttrib2d}, {"glVertexAttrib3f", (void **) &qglVertexAttrib3f}, {"glVertexAttrib3s", (void **) &qglVertexAttrib3s}, {"glVertexAttrib3d", (void **) &qglVertexAttrib3d}, {"glVertexAttrib4f", (void **) &qglVertexAttrib4f}, {"glVertexAttrib4s", (void **) &qglVertexAttrib4s}, {"glVertexAttrib4d", (void **) &qglVertexAttrib4d}, {"glVertexAttrib4Nub", (void **) &qglVertexAttrib4Nub}, {"glVertexAttrib1fv", (void **) &qglVertexAttrib1fv}, {"glVertexAttrib1sv", (void **) &qglVertexAttrib1sv}, {"glVertexAttrib1dv", (void **) &qglVertexAttrib1dv}, {"glVertexAttrib2fv", (void **) &qglVertexAttrib1fv}, {"glVertexAttrib2sv", (void **) &qglVertexAttrib1sv}, {"glVertexAttrib2dv", (void **) &qglVertexAttrib1dv}, {"glVertexAttrib3fv", (void **) &qglVertexAttrib1fv}, {"glVertexAttrib3sv", (void **) &qglVertexAttrib1sv}, {"glVertexAttrib3dv", (void **) &qglVertexAttrib1dv}, {"glVertexAttrib4fv", (void **) &qglVertexAttrib1fv}, {"glVertexAttrib4sv", (void **) &qglVertexAttrib1sv}, {"glVertexAttrib4dv", (void **) &qglVertexAttrib1dv}, // {"glVertexAttrib4iv", (void **) &qglVertexAttrib1iv}, // {"glVertexAttrib4bv", (void **) &qglVertexAttrib1bv}, // {"glVertexAttrib4ubv", (void **) &qglVertexAttrib1ubv}, // {"glVertexAttrib4usv", (void **) &qglVertexAttrib1usv}, // {"glVertexAttrib4uiv", (void **) &qglVertexAttrib1uiv}, // {"glVertexAttrib4Nbv", (void **) &qglVertexAttrib1Nbv}, // {"glVertexAttrib4Nsv", (void **) &qglVertexAttrib1Nsv}, // {"glVertexAttrib4Niv", (void **) &qglVertexAttrib1Niv}, // {"glVertexAttrib4Nubv", (void **) &qglVertexAttrib1Nubv}, // {"glVertexAttrib4Nusv", (void **) &qglVertexAttrib1Nusv}, // {"glVertexAttrib4Nuiv", (void **) &qglVertexAttrib1Nuiv}, {"glVertexAttribPointer", (void **) &qglVertexAttribPointer}, {"glEnableVertexAttribArray", (void **) &qglEnableVertexAttribArray}, {"glDisableVertexAttribArray", (void **) &qglDisableVertexAttribArray}, {"glBindAttribLocation", (void **) &qglBindAttribLocation}, {"glGetActiveAttrib", (void **) &qglGetActiveAttrib}, {"glGetAttribLocation", (void **) &qglGetAttribLocation}, {"glGetVertexAttribdv", (void **) &qglGetVertexAttribdv}, {"glGetVertexAttribfv", (void **) &qglGetVertexAttribfv}, {"glGetVertexAttribiv", (void **) &qglGetVertexAttribiv}, {"glGetVertexAttribPointerv", (void **) &qglGetVertexAttribPointerv}, {NULL, NULL} }; static dllfunction_t glsl130funcs[] = { {"glBindFragDataLocation", (void **) &qglBindFragDataLocation}, {NULL, NULL} }; static dllfunction_t vbofuncs[] = { {"glBindBufferARB" , (void **) &qglBindBufferARB}, {"glDeleteBuffersARB" , (void **) &qglDeleteBuffersARB}, {"glGenBuffersARB" , (void **) &qglGenBuffersARB}, {"glIsBufferARB" , (void **) &qglIsBufferARB}, {"glMapBufferARB" , (void **) &qglMapBufferARB}, {"glUnmapBufferARB" , (void **) &qglUnmapBufferARB}, {"glBufferDataARB" , (void **) &qglBufferDataARB}, {"glBufferSubDataARB" , (void **) &qglBufferSubDataARB}, {NULL, NULL} }; static dllfunction_t ubofuncs[] = { {"glGetUniformIndices" , (void **) &qglGetUniformIndices}, {"glGetActiveUniformsiv" , (void **) &qglGetActiveUniformsiv}, {"glGetActiveUniformName" , (void **) &qglGetActiveUniformName}, {"glGetUniformBlockIndex" , (void **) &qglGetUniformBlockIndex}, {"glGetActiveUniformBlockiv" , (void **) &qglGetActiveUniformBlockiv}, {"glGetActiveUniformBlockName", (void **) &qglGetActiveUniformBlockName}, {"glBindBufferRange" , (void **) &qglBindBufferRange}, {"glBindBufferBase" , (void **) &qglBindBufferBase}, {"glGetIntegeri_v" , (void **) &qglGetIntegeri_v}, {"glUniformBlockBinding" , (void **) &qglUniformBlockBinding}, {NULL, NULL} }; static dllfunction_t arbfbofuncs[] = { {"glIsRenderbuffer" , (void **) &qglIsRenderbuffer}, {"glBindRenderbuffer" , (void **) &qglBindRenderbuffer}, {"glDeleteRenderbuffers" , (void **) &qglDeleteRenderbuffers}, {"glGenRenderbuffers" , (void **) &qglGenRenderbuffers}, {"glRenderbufferStorage" , (void **) &qglRenderbufferStorage}, {"glRenderbufferStorageMultisample" , (void **) &qglRenderbufferStorageMultisample}, // not in GL_EXT_framebuffer_object {"glGetRenderbufferParameteriv" , (void **) &qglGetRenderbufferParameteriv}, {"glIsFramebuffer" , (void **) &qglIsFramebuffer}, {"glBindFramebuffer" , (void **) &qglBindFramebuffer}, {"glDeleteFramebuffers" , (void **) &qglDeleteFramebuffers}, {"glGenFramebuffers" , (void **) &qglGenFramebuffers}, {"glCheckFramebufferStatus" , (void **) &qglCheckFramebufferStatus}, {"glFramebufferTexture1D" , (void **) &qglFramebufferTexture1D}, {"glFramebufferTexture2D" , (void **) &qglFramebufferTexture2D}, {"glFramebufferTexture3D" , (void **) &qglFramebufferTexture3D}, {"glFramebufferTextureLayer" , (void **) &qglFramebufferTextureLayer}, // not in GL_EXT_framebuffer_object {"glFramebufferRenderbuffer" , (void **) &qglFramebufferRenderbuffer}, {"glGetFramebufferAttachmentParameteriv" , (void **) &qglGetFramebufferAttachmentParameteriv}, {"glBlitFramebuffer" , (void **) &qglBlitFramebuffer}, // not in GL_EXT_framebuffer_object {"glGenerateMipmap" , (void **) &qglGenerateMipmap}, {NULL, NULL} }; static dllfunction_t extfbofuncs[] = { {"glIsRenderbufferEXT" , (void **) &qglIsRenderbuffer}, {"glBindRenderbufferEXT" , (void **) &qglBindRenderbuffer}, {"glDeleteRenderbuffersEXT" , (void **) &qglDeleteRenderbuffers}, {"glGenRenderbuffersEXT" , (void **) &qglGenRenderbuffers}, {"glRenderbufferStorageEXT" , (void **) &qglRenderbufferStorage}, {"glGetRenderbufferParameterivEXT" , (void **) &qglGetRenderbufferParameteriv}, {"glIsFramebufferEXT" , (void **) &qglIsFramebuffer}, {"glBindFramebufferEXT" , (void **) &qglBindFramebuffer}, {"glDeleteFramebuffersEXT" , (void **) &qglDeleteFramebuffers}, {"glGenFramebuffersEXT" , (void **) &qglGenFramebuffers}, {"glCheckFramebufferStatusEXT" , (void **) &qglCheckFramebufferStatus}, {"glFramebufferTexture1DEXT" , (void **) &qglFramebufferTexture1D}, {"glFramebufferTexture2DEXT" , (void **) &qglFramebufferTexture2D}, {"glFramebufferTexture3DEXT" , (void **) &qglFramebufferTexture3D}, {"glFramebufferRenderbufferEXT" , (void **) &qglFramebufferRenderbuffer}, {"glGetFramebufferAttachmentParameterivEXT" , (void **) &qglGetFramebufferAttachmentParameteriv}, {"glGenerateMipmapEXT" , (void **) &qglGenerateMipmap}, {NULL, NULL} }; static dllfunction_t texturecompressionfuncs[] = { {"glCompressedTexImage3DARB", (void **) &qglCompressedTexImage3DARB}, {"glCompressedTexImage2DARB", (void **) &qglCompressedTexImage2DARB}, // {"glCompressedTexImage1DARB", (void **) &qglCompressedTexImage1DARB}, {"glCompressedTexSubImage3DARB", (void **) &qglCompressedTexSubImage3DARB}, {"glCompressedTexSubImage2DARB", (void **) &qglCompressedTexSubImage2DARB}, // {"glCompressedTexSubImage1DARB", (void **) &qglCompressedTexSubImage1DARB}, {"glGetCompressedTexImageARB", (void **) &qglGetCompressedTexImageARB}, {NULL, NULL} }; static dllfunction_t occlusionqueryfuncs[] = { {"glGenQueriesARB", (void **) &qglGenQueriesARB}, {"glDeleteQueriesARB", (void **) &qglDeleteQueriesARB}, {"glIsQueryARB", (void **) &qglIsQueryARB}, {"glBeginQueryARB", (void **) &qglBeginQueryARB}, {"glEndQueryARB", (void **) &qglEndQueryARB}, {"glGetQueryivARB", (void **) &qglGetQueryivARB}, {"glGetQueryObjectivARB", (void **) &qglGetQueryObjectivARB}, {"glGetQueryObjectuivARB", (void **) &qglGetQueryObjectuivARB}, {NULL, NULL} }; static dllfunction_t drawbuffersfuncs[] = { {"glDrawBuffersARB", (void **) &qglDrawBuffersARB}, {NULL, NULL} }; static dllfunction_t multisamplefuncs[] = { {"glSampleCoverageARB", (void **) &qglSampleCoverageARB}, {NULL, NULL} }; static dllfunction_t blendfuncseparatefuncs[] = { {"glBlendFuncSeparateEXT", (void **) &qglBlendFuncSeparate}, {NULL, NULL} }; #endif void VID_ClearExtensions(void) { // VorteX: reset extensions info cvar, it got filled by GL_CheckExtension Cvar_SetQuick(&gl_info_extensions, ""); // clear the extension flags memset(&vid.support, 0, sizeof(vid.support)); vid.renderpath = RENDERPATH_GL11; vid.sRGBcapable2D = false; vid.sRGBcapable3D = false; vid.useinterleavedarrays = false; vid.forcevbo = false; vid.maxtexturesize_2d = 0; vid.maxtexturesize_3d = 0; vid.maxtexturesize_cubemap = 0; vid.texunits = 1; vid.teximageunits = 1; vid.texarrayunits = 1; vid.max_anisotropy = 1; vid.maxdrawbuffers = 1; #ifndef USE_GLES2 // this is a complete list of all functions that are directly checked in the renderer qglDrawRangeElements = NULL; qglDrawBuffer = NULL; qglPolygonStipple = NULL; qglFlush = NULL; qglActiveTexture = NULL; qglGetCompressedTexImageARB = NULL; qglFramebufferTexture2D = NULL; qglDrawBuffersARB = NULL; #endif } #ifndef USE_GLES2 void VID_CheckExtensions(void) { if (!GL_CheckExtension("glbase", opengl110funcs, NULL, false)) Sys_Error("OpenGL 1.1.0 functions not found"); vid.support.gl20shaders = GL_CheckExtension("2.0", gl20shaderfuncs, "-noshaders", true); CHECKGLERROR Con_DPrint("Checking OpenGL extensions...\n"); if (vid.support.gl20shaders) { char *s; // detect what GLSL version is available, to enable features like r_glsl_skeletal and higher quality reliefmapping vid.support.glshaderversion = 100; s = (char *) qglGetString(GL_SHADING_LANGUAGE_VERSION); if (s) vid.support.glshaderversion = (int)(atof(s) * 100.0f + 0.5f); if (vid.support.glshaderversion < 100) vid.support.glshaderversion = 100; Con_DPrintf("Detected GLSL #version %i\n", vid.support.glshaderversion); // get the glBindFragDataLocation function if (vid.support.glshaderversion >= 130) vid.support.gl20shaders130 = GL_CheckExtension("glshaders130", glsl130funcs, "-noglsl130", true); } // GL drivers generally prefer GL_BGRA vid.forcetextype = GL_BGRA; vid.support.amd_texture_texture4 = GL_CheckExtension("GL_AMD_texture_texture4", NULL, "-notexture4", false); vid.support.arb_depth_texture = GL_CheckExtension("GL_ARB_depth_texture", NULL, "-nodepthtexture", false); vid.support.arb_draw_buffers = GL_CheckExtension("GL_ARB_draw_buffers", drawbuffersfuncs, "-nodrawbuffers", false); vid.support.arb_multitexture = GL_CheckExtension("GL_ARB_multitexture", multitexturefuncs, "-nomtex", false); vid.support.arb_occlusion_query = GL_CheckExtension("GL_ARB_occlusion_query", occlusionqueryfuncs, "-noocclusionquery", false); vid.support.arb_query_buffer_object = GL_CheckExtension("GL_ARB_query_buffer_object", NULL, "-noquerybuffer", true); vid.support.arb_shadow = GL_CheckExtension("GL_ARB_shadow", NULL, "-noshadow", false); vid.support.arb_texture_compression = GL_CheckExtension("GL_ARB_texture_compression", texturecompressionfuncs, "-notexturecompression", false); vid.support.arb_texture_cube_map = GL_CheckExtension("GL_ARB_texture_cube_map", NULL, "-nocubemap", false); vid.support.arb_texture_env_combine = GL_CheckExtension("GL_ARB_texture_env_combine", NULL, "-nocombine", false) || GL_CheckExtension("GL_EXT_texture_env_combine", NULL, "-nocombine", false); vid.support.arb_texture_gather = GL_CheckExtension("GL_ARB_texture_gather", NULL, "-notexturegather", false); #ifndef __APPLE__ // LordHavoc: too many bugs on OSX! vid.support.arb_texture_non_power_of_two = GL_CheckExtension("GL_ARB_texture_non_power_of_two", NULL, "-notexturenonpoweroftwo", false); #endif vid.support.arb_vertex_buffer_object = GL_CheckExtension("GL_ARB_vertex_buffer_object", vbofuncs, "-novbo", false); vid.support.arb_uniform_buffer_object = GL_CheckExtension("GL_ARB_uniform_buffer_object", ubofuncs, "-noubo", false); vid.support.ati_separate_stencil = GL_CheckExtension("separatestencil", gl2separatestencilfuncs, "-noseparatestencil", true) || GL_CheckExtension("GL_ATI_separate_stencil", atiseparatestencilfuncs, "-noseparatestencil", false); vid.support.ext_blend_minmax = GL_CheckExtension("GL_EXT_blend_minmax", blendequationfuncs, "-noblendminmax", false); vid.support.ext_blend_subtract = GL_CheckExtension("GL_EXT_blend_subtract", blendequationfuncs, "-noblendsubtract", false); vid.support.ext_blend_func_separate = GL_CheckExtension("GL_EXT_blend_func_separate", blendfuncseparatefuncs, "-noblendfuncseparate", false); vid.support.ext_draw_range_elements = GL_CheckExtension("drawrangeelements", drawrangeelementsfuncs, "-nodrawrangeelements", true) || GL_CheckExtension("GL_EXT_draw_range_elements", drawrangeelementsextfuncs, "-nodrawrangeelements", false); vid.support.arb_framebuffer_object = GL_CheckExtension("GL_ARB_framebuffer_object", arbfbofuncs, "-nofbo", false); if (vid.support.arb_framebuffer_object) vid.support.ext_framebuffer_object = true; else vid.support.ext_framebuffer_object = GL_CheckExtension("GL_EXT_framebuffer_object", extfbofuncs, "-nofbo", false); vid.support.ext_packed_depth_stencil = GL_CheckExtension("GL_EXT_packed_depth_stencil", NULL, "-nopackeddepthstencil", false); vid.support.ext_stencil_two_side = GL_CheckExtension("GL_EXT_stencil_two_side", stenciltwosidefuncs, "-nostenciltwoside", false); vid.support.ext_texture_3d = GL_CheckExtension("GL_EXT_texture3D", texture3dextfuncs, "-notexture3d", false); vid.support.ext_texture_compression_s3tc = GL_CheckExtension("GL_EXT_texture_compression_s3tc", NULL, "-nos3tc", false); vid.support.ext_texture_edge_clamp = GL_CheckExtension("GL_EXT_texture_edge_clamp", NULL, "-noedgeclamp", false) || GL_CheckExtension("GL_SGIS_texture_edge_clamp", NULL, "-noedgeclamp", false); vid.support.ext_texture_filter_anisotropic = GL_CheckExtension("GL_EXT_texture_filter_anisotropic", NULL, "-noanisotropy", false); vid.support.ext_texture_srgb = GL_CheckExtension("GL_EXT_texture_sRGB", NULL, "-nosrgb", false); vid.support.arb_texture_float = GL_CheckExtension("GL_ARB_texture_float", NULL, "-notexturefloat", false); vid.support.arb_half_float_pixel = GL_CheckExtension("GL_ARB_half_float_pixel", NULL, "-nohalffloatpixel", false); vid.support.arb_half_float_vertex = GL_CheckExtension("GL_ARB_half_float_vertex", NULL, "-nohalffloatvertex", false); vid.support.arb_multisample = GL_CheckExtension("GL_ARB_multisample", multisamplefuncs, "-nomultisample", false); vid.allowalphatocoverage = false; // COMMANDLINEOPTION: GL: -noshaders disables use of OpenGL 2.0 shaders (which allow pixel shader effects, can improve per pixel lighting performance and capabilities) // COMMANDLINEOPTION: GL: -noanisotropy disables GL_EXT_texture_filter_anisotropic (allows higher quality texturing) // COMMANDLINEOPTION: GL: -noblendminmax disables GL_EXT_blend_minmax // COMMANDLINEOPTION: GL: -noblendsubtract disables GL_EXT_blend_subtract // COMMANDLINEOPTION: GL: -nocombine disables GL_ARB_texture_env_combine or GL_EXT_texture_env_combine (required for bumpmapping and faster map rendering) // COMMANDLINEOPTION: GL: -nocubemap disables GL_ARB_texture_cube_map (required for bumpmapping) // COMMANDLINEOPTION: GL: -nodepthtexture disables use of GL_ARB_depth_texture (required for shadowmapping) // COMMANDLINEOPTION: GL: -nodrawbuffers disables use of GL_ARB_draw_buffers (required for r_shadow_deferredprepass) // COMMANDLINEOPTION: GL: -nodrawrangeelements disables GL_EXT_draw_range_elements (renders faster) // COMMANDLINEOPTION: GL: -noedgeclamp disables GL_EXT_texture_edge_clamp or GL_SGIS_texture_edge_clamp (recommended, some cards do not support the other texture clamp method) // COMMANDLINEOPTION: GL: -nofbo disables GL_EXT_framebuffer_object (which accelerates rendering), only used if GL_ARB_fragment_shader is also available // COMMANDLINEOPTION: GL: -nomtex disables GL_ARB_multitexture (required for faster map rendering) // COMMANDLINEOPTION: GL: -noocclusionquery disables GL_ARB_occlusion_query (which allows coronas to fade according to visibility, and potentially used for rendering optimizations) // COMMANDLINEOPTION: GL: -noquerybuffer disables GL_ARB_query_buffer_object (which allows corona fading without synchronous rendering) // COMMANDLINEOPTION: GL: -nos3tc disables GL_EXT_texture_compression_s3tc (which allows use of .dds texture caching) // COMMANDLINEOPTION: GL: -noseparatestencil disables use of OpenGL2.0 glStencilOpSeparate and GL_ATI_separate_stencil extensions (which accelerate shadow rendering) // COMMANDLINEOPTION: GL: -noshadow disables use of GL_ARB_shadow (required for hardware shadowmap filtering) // COMMANDLINEOPTION: GL: -nostenciltwoside disables GL_EXT_stencil_two_side (which accelerate shadow rendering) // COMMANDLINEOPTION: GL: -notexture3d disables GL_EXT_texture3D (required for spherical lights, otherwise they render as a column) // COMMANDLINEOPTION: GL: -notexture4 disables GL_AMD_texture_texture4 (which provides fetch4 sampling) // COMMANDLINEOPTION: GL: -notexturecompression disables GL_ARB_texture_compression (which saves video memory if it is supported, but can also degrade image quality, see gl_texturecompression cvar documentation for more information) // COMMANDLINEOPTION: GL: -notexturegather disables GL_ARB_texture_gather (which provides fetch4 sampling) // COMMANDLINEOPTION: GL: -notexturenonpoweroftwo disables GL_ARB_texture_non_power_of_two (which saves video memory if it is supported, but crashes on some buggy drivers) // COMMANDLINEOPTION: GL: -novbo disables GL_ARB_vertex_buffer_object (which accelerates rendering) // COMMANDLINEOPTION: GL: -nosrgb disables GL_EXT_texture_sRGB (which is used for higher quality non-linear texture gamma) // COMMANDLINEOPTION: GL: -nomultisample disables GL_ARB_multisample if (vid.support.arb_draw_buffers) qglGetIntegerv(GL_MAX_DRAW_BUFFERS_ARB, (GLint*)&vid.maxdrawbuffers); // disable non-power-of-two textures on Radeon X1600 and other cards that do not accelerate it with some filtering modes / repeat modes that we use // we detect these cards by checking if the hardware supports vertex texture fetch (Geforce6 does, Radeon X1600 does not, all GL3-class hardware does) if(vid.support.arb_texture_non_power_of_two && vid.support.gl20shaders) { int val = 0; qglGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &val);CHECKGLERROR if (val < 1) vid.support.arb_texture_non_power_of_two = false; } // we don't care if it's an extension or not, they are identical functions, so keep it simple in the rendering code if (qglDrawRangeElements == NULL) qglDrawRangeElements = qglDrawRangeElementsEXT; qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); if (vid.support.ext_texture_filter_anisotropic) qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); if (vid.support.arb_texture_cube_map) qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_cubemap); if (vid.support.ext_texture_3d) qglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_3d); // verify that 3d textures are really supported if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) { vid.support.ext_texture_3d = false; Con_Printf("GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); } vid.texunits = vid.teximageunits = vid.texarrayunits = 1; if (vid.support.arb_multitexture) qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); if (vid_gl20.integer && vid.support.gl20shaders) { qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (int *)&vid.teximageunits);CHECKGLERROR qglGetIntegerv(GL_MAX_TEXTURE_COORDS, (int *)&vid.texarrayunits);CHECKGLERROR vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); Con_DPrintf("Using GL2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); vid.renderpath = RENDERPATH_GL20; vid.sRGBcapable2D = false; vid.sRGBcapable3D = true; vid.useinterleavedarrays = false; Con_Printf("vid.support.arb_multisample %i\n", vid.support.arb_multisample); Con_Printf("vid.support.gl20shaders %i\n", vid.support.gl20shaders); vid.allowalphatocoverage = true; // but see below, it may get turned to false again if GL_SAMPLES_ARB is <= 1 } else if (vid.support.arb_texture_env_combine && vid.texunits >= 2 && vid_gl13.integer) { qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = vid.texunits; vid.texarrayunits = vid.texunits; Con_DPrintf("Using GL1.3 rendering path - %i texture units, single pass rendering\n", vid.texunits); vid.renderpath = RENDERPATH_GL13; vid.sRGBcapable2D = false; vid.sRGBcapable3D = false; vid.useinterleavedarrays = false; } else { vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = vid.texunits; vid.texarrayunits = vid.texunits; Con_DPrintf("Using GL1.1 rendering path - %i texture units, two pass rendering\n", vid.texunits); vid.renderpath = RENDERPATH_GL11; vid.sRGBcapable2D = false; vid.sRGBcapable3D = false; vid.useinterleavedarrays = false; } // enable multisample antialiasing if possible if(vid.support.arb_multisample) { int samples = 0; qglGetIntegerv(GL_SAMPLES_ARB, &samples); vid.samples = samples; if (samples > 1) qglEnable(GL_MULTISAMPLE_ARB); else vid.allowalphatocoverage = false; } else { vid.allowalphatocoverage = false; vid.samples = 1; } // VorteX: set other info (maybe place them in VID_InitMode?) Cvar_SetQuick(&gl_info_vendor, gl_vendor); Cvar_SetQuick(&gl_info_renderer, gl_renderer); Cvar_SetQuick(&gl_info_version, gl_version); Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); Cvar_SetQuick(&gl_info_driver, gl_driver); } #endif float VID_JoyState_GetAxis(const vid_joystate_t *joystate, int axis, float fsensitivity, float deadzone) { float value; value = (axis >= 0 && axis < MAXJOYAXIS) ? joystate->axis[axis] : 0.0f; value = value > deadzone ? (value - deadzone) : (value < -deadzone ? (value + deadzone) : 0.0f); value *= deadzone > 0 ? (1.0f / (1.0f - deadzone)) : 1.0f; value = bound(-1, value, 1); return value * fsensitivity; } qboolean VID_JoyBlockEmulatedKeys(int keycode) { int j; vid_joystate_t joystate; if (!joy_axiskeyevents.integer) return false; if (vid_joystate.is360) return false; if (keycode != K_UPARROW && keycode != K_DOWNARROW && keycode != K_RIGHTARROW && keycode != K_LEFTARROW) return false; // block system-generated key events for arrow keys if we're emulating the arrow keys ourselves VID_BuildJoyState(&joystate); for (j = 32;j < 36;j++) if (vid_joystate.button[j] || joystate.button[j]) return true; return false; } void VID_Shared_BuildJoyState_Begin(vid_joystate_t *joystate) { #ifdef WIN32 xinput_state_t xinputstate; #endif memset(joystate, 0, sizeof(*joystate)); #ifdef WIN32 if (vid_xinputindex >= 0 && qXInputGetState && qXInputGetState(vid_xinputindex, &xinputstate) == S_OK) { joystate->is360 = true; joystate->button[ 0] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) != 0; joystate->button[ 1] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) != 0; joystate->button[ 2] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) != 0; joystate->button[ 3] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0; joystate->button[ 4] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_START) != 0; joystate->button[ 5] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) != 0; joystate->button[ 6] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) != 0; joystate->button[ 7] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) != 0; joystate->button[ 8] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0; joystate->button[ 9] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0; joystate->button[10] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_A) != 0; joystate->button[11] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_B) != 0; joystate->button[12] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_X) != 0; joystate->button[13] = (xinputstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y) != 0; joystate->button[14] = xinputstate.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; joystate->button[15] = xinputstate.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD; joystate->button[16] = xinputstate.Gamepad.sThumbLY < -16384; joystate->button[17] = xinputstate.Gamepad.sThumbLY > 16384; joystate->button[18] = xinputstate.Gamepad.sThumbLX < -16384; joystate->button[19] = xinputstate.Gamepad.sThumbLX > 16384; joystate->button[20] = xinputstate.Gamepad.sThumbRY < -16384; joystate->button[21] = xinputstate.Gamepad.sThumbRY > 16384; joystate->button[22] = xinputstate.Gamepad.sThumbRX < -16384; joystate->button[23] = xinputstate.Gamepad.sThumbRX > 16384; joystate->axis[ 4] = xinputstate.Gamepad.bLeftTrigger * (1.0f / 255.0f); joystate->axis[ 5] = xinputstate.Gamepad.bRightTrigger * (1.0f / 255.0f); joystate->axis[ 0] = xinputstate.Gamepad.sThumbLX * (1.0f / 32767.0f); joystate->axis[ 1] = xinputstate.Gamepad.sThumbLY * (1.0f / 32767.0f); joystate->axis[ 2] = xinputstate.Gamepad.sThumbRX * (1.0f / 32767.0f); joystate->axis[ 3] = xinputstate.Gamepad.sThumbRY * (1.0f / 32767.0f); } #endif } void VID_Shared_BuildJoyState_Finish(vid_joystate_t *joystate) { float f, r; if (joystate->is360) return; // emulate key events for thumbstick f = VID_JoyState_GetAxis(joystate, joy_axisforward.integer, 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityforward.value; r = VID_JoyState_GetAxis(joystate, joy_axisside.integer , 1, joy_axiskeyevents_deadzone.value) * joy_sensitivityside.value; #if MAXJOYBUTTON != 36 #error this code must be updated if MAXJOYBUTTON changes! #endif joystate->button[32] = f > 0.0f; joystate->button[33] = f < 0.0f; joystate->button[34] = r > 0.0f; joystate->button[35] = r < 0.0f; } static void VID_KeyEventForButton(qboolean oldbutton, qboolean newbutton, int key, double *timer) { if (oldbutton) { if (newbutton) { if (realtime >= *timer) { Key_Event(key, 0, true); *timer = realtime + 0.1; } } else { Key_Event(key, 0, false); *timer = 0; } } else { if (newbutton) { Key_Event(key, 0, true); *timer = realtime + 0.5; } } } #if MAXJOYBUTTON != 36 #error this code must be updated if MAXJOYBUTTON changes! #endif static int joybuttonkey[MAXJOYBUTTON][2] = { {K_JOY1, K_ENTER}, {K_JOY2, K_ESCAPE}, {K_JOY3, 0}, {K_JOY4, 0}, {K_JOY5, 0}, {K_JOY6, 0}, {K_JOY7, 0}, {K_JOY8, 0}, {K_JOY9, 0}, {K_JOY10, 0}, {K_JOY11, 0}, {K_JOY12, 0}, {K_JOY13, 0}, {K_JOY14, 0}, {K_JOY15, 0}, {K_JOY16, 0}, {K_AUX1, 0}, {K_AUX2, 0}, {K_AUX3, 0}, {K_AUX4, 0}, {K_AUX5, 0}, {K_AUX6, 0}, {K_AUX7, 0}, {K_AUX8, 0}, {K_AUX9, 0}, {K_AUX10, 0}, {K_AUX11, 0}, {K_AUX12, 0}, {K_AUX13, 0}, {K_AUX14, 0}, {K_AUX15, 0}, {K_AUX16, 0}, {K_JOY_UP, K_UPARROW}, {K_JOY_DOWN, K_DOWNARROW}, {K_JOY_RIGHT, K_RIGHTARROW}, {K_JOY_LEFT, K_LEFTARROW}, }; static int joybuttonkey360[][2] = { {K_X360_DPAD_UP, K_UPARROW}, {K_X360_DPAD_DOWN, K_DOWNARROW}, {K_X360_DPAD_LEFT, K_LEFTARROW}, {K_X360_DPAD_RIGHT, K_RIGHTARROW}, {K_X360_START, K_ESCAPE}, {K_X360_BACK, K_ESCAPE}, {K_X360_LEFT_THUMB, 0}, {K_X360_RIGHT_THUMB, 0}, {K_X360_LEFT_SHOULDER, 0}, {K_X360_RIGHT_SHOULDER, 0}, {K_X360_A, K_ENTER}, {K_X360_B, K_ESCAPE}, {K_X360_X, 0}, {K_X360_Y, 0}, {K_X360_LEFT_TRIGGER, 0}, {K_X360_RIGHT_TRIGGER, 0}, {K_X360_LEFT_THUMB_DOWN, K_DOWNARROW}, {K_X360_LEFT_THUMB_UP, K_UPARROW}, {K_X360_LEFT_THUMB_LEFT, K_LEFTARROW}, {K_X360_LEFT_THUMB_RIGHT, K_RIGHTARROW}, {K_X360_RIGHT_THUMB_DOWN, 0}, {K_X360_RIGHT_THUMB_UP, 0}, {K_X360_RIGHT_THUMB_LEFT, 0}, {K_X360_RIGHT_THUMB_RIGHT, 0}, }; double vid_joybuttontimer[MAXJOYBUTTON]; void VID_ApplyJoyState(vid_joystate_t *joystate) { int j; int c = joy_axiskeyevents.integer != 0; if (joystate->is360) { #if 0 // keystrokes (chatpad) // DOES NOT WORK - no driver support in xinput1_3.dll :( xinput_keystroke_t keystroke; while (qXInputGetKeystroke && qXInputGetKeystroke(XUSER_INDEX_ANY, 0, &keystroke) == S_OK) Con_Printf("XInput KeyStroke: VirtualKey %i, Unicode %i, Flags %x, UserIndex %i, HidCode %i\n", keystroke.VirtualKey, keystroke.Unicode, keystroke.Flags, keystroke.UserIndex, keystroke.HidCode); #endif // emit key events for buttons for (j = 0;j < (int)(sizeof(joybuttonkey360)/sizeof(joybuttonkey360[0]));j++) VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey360[j][c], &vid_joybuttontimer[j]); // axes cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_x360_axisforward.integer, joy_x360_sensitivityforward.value, joy_x360_deadzoneforward.value) * cl_forwardspeed.value; cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_x360_axisside.integer, joy_x360_sensitivityside.value, joy_x360_deadzoneside.value) * cl_sidespeed.value; cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_x360_axisup.integer, joy_x360_sensitivityup.value, joy_x360_deadzoneup.value) * cl_upspeed.value; cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_x360_axispitch.integer, joy_x360_sensitivitypitch.value, joy_x360_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_x360_axisyaw.integer, joy_x360_sensitivityyaw.value, joy_x360_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_x360_axisroll.integer, joy_x360_sensitivityroll.value, joy_x360_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; } else { // emit key events for buttons for (j = 0;j < MAXJOYBUTTON;j++) VID_KeyEventForButton(vid_joystate.button[j] != 0, joystate->button[j] != 0, joybuttonkey[j][c], &vid_joybuttontimer[j]); // axes cl.cmd.forwardmove += VID_JoyState_GetAxis(joystate, joy_axisforward.integer, joy_sensitivityforward.value, joy_deadzoneforward.value) * cl_forwardspeed.value; cl.cmd.sidemove += VID_JoyState_GetAxis(joystate, joy_axisside.integer, joy_sensitivityside.value, joy_deadzoneside.value) * cl_sidespeed.value; cl.cmd.upmove += VID_JoyState_GetAxis(joystate, joy_axisup.integer, joy_sensitivityup.value, joy_deadzoneup.value) * cl_upspeed.value; cl.viewangles[0] += VID_JoyState_GetAxis(joystate, joy_axispitch.integer, joy_sensitivitypitch.value, joy_deadzonepitch.value) * cl.realframetime * cl_pitchspeed.value; cl.viewangles[1] += VID_JoyState_GetAxis(joystate, joy_axisyaw.integer, joy_sensitivityyaw.value, joy_deadzoneyaw.value) * cl.realframetime * cl_yawspeed.value; //cl.viewangles[2] += VID_JoyState_GetAxis(joystate, joy_axisroll.integer, joy_sensitivityroll.value, joy_deadzoneroll.value) * cl.realframetime * cl_rollspeed.value; } vid_joystate = *joystate; } int VID_Shared_SetJoystick(int index) { #ifdef WIN32 int i; int xinputcount = 0; int xinputindex = -1; int xinputavailable = 0; xinput_state_t state; // detect available XInput controllers for (i = 0;i < 4;i++) { if (qXInputGetState && qXInputGetState(i, &state) == S_OK) { xinputavailable |= 1<= 0) Con_Printf("Joystick %i opened (XInput Device %i)\n", index, xinputindex); } return xinputcount; #else return 0; #endif } static void Force_CenterView_f (void) { cl.viewangles[PITCH] = 0; } static int gamma_forcenextframe = false; static float cachegamma, cachebrightness, cachecontrast, cacheblack[3], cachegrey[3], cachewhite[3], cachecontrastboost; static int cachecolorenable, cachehwgamma; unsigned int vid_gammatables_serial = 0; // so other subsystems can poll if gamma parameters have changed qboolean vid_gammatables_trivial = true; void VID_BuildGammaTables(unsigned short *ramps, int rampsize) { if (cachecolorenable) { BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[0]), cachewhite[0], cacheblack[0], cachecontrastboost, ramps, rampsize); BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[1]), cachewhite[1], cacheblack[1], cachecontrastboost, ramps + rampsize, rampsize); BuildGammaTable16(1.0f, invpow(0.5, 1 - cachegrey[2]), cachewhite[2], cacheblack[2], cachecontrastboost, ramps + rampsize*2, rampsize); } else { BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps, rampsize); BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize, rampsize); BuildGammaTable16(1.0f, cachegamma, cachecontrast, cachebrightness, cachecontrastboost, ramps + rampsize*2, rampsize); } if(vid.sRGB2D || vid.sRGB3D) { int i; for(i = 0; i < 3*rampsize; ++i) ramps[i] = (int)floor(bound(0.0f, Image_sRGBFloatFromLinearFloat(ramps[i] / 65535.0f), 1.0f) * 65535.0f + 0.5f); } // LordHavoc: this code came from Ben Winslow and Zinx Verituse, I have // immensely butchered it to work with variable framerates and fit in with // the rest of darkplaces. if (v_psycho.integer) { int x, y; float t; static float n[3], nd[3], nt[3]; static int init = true; unsigned short *ramp; gamma_forcenextframe = true; if (init) { init = false; for (x = 0;x < 3;x++) { n[x] = lhrandom(0, 1); nd[x] = (rand()&1)?-0.25:0.25; nt[x] = lhrandom(1, 8.2); } } for (x = 0;x < 3;x++) { nt[x] -= cl.realframetime; if (nt[x] < 0) { nd[x] = -nd[x]; nt[x] += lhrandom(1, 8.2); } n[x] += nd[x] * cl.realframetime; n[x] -= floor(n[x]); } for (x = 0, ramp = ramps;x < 3;x++) for (y = 0, t = n[x] - 0.75f;y < rampsize;y++, t += 0.75f * (2.0f / rampsize)) *ramp++ = (unsigned short)(cos(t*(M_PI*2.0)) * 32767.0f + 32767.0f); } } void VID_UpdateGamma(qboolean force, int rampsize) { cvar_t *c; float f; int wantgamma; qboolean gamma_changed = false; // LordHavoc: don't mess with gamma tables if running dedicated if (cls.state == ca_dedicated) return; wantgamma = v_hwgamma.integer; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: if (v_glslgamma.integer) wantgamma = 0; break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: break; } if(!vid_activewindow) wantgamma = 0; #define BOUNDCVAR(cvar, m1, m2) c = &(cvar);f = bound(m1, c->value, m2);if (c->value != f) Cvar_SetValueQuick(c, f); BOUNDCVAR(v_gamma, 0.1, 5); BOUNDCVAR(v_contrast, 0.2, 5); BOUNDCVAR(v_brightness, -v_contrast.value * 0.8, 0.8); //BOUNDCVAR(v_contrastboost, 0.0625, 16); BOUNDCVAR(v_color_black_r, 0, 0.8); BOUNDCVAR(v_color_black_g, 0, 0.8); BOUNDCVAR(v_color_black_b, 0, 0.8); BOUNDCVAR(v_color_grey_r, 0, 0.95); BOUNDCVAR(v_color_grey_g, 0, 0.95); BOUNDCVAR(v_color_grey_b, 0, 0.95); BOUNDCVAR(v_color_white_r, 1, 5); BOUNDCVAR(v_color_white_g, 1, 5); BOUNDCVAR(v_color_white_b, 1, 5); #undef BOUNDCVAR // set vid_gammatables_trivial to true if the current settings would generate the identity gamma table vid_gammatables_trivial = false; if(v_psycho.integer == 0) if(v_contrastboost.value == 1) if(!vid.sRGB2D) if(!vid.sRGB3D) { if(v_color_enable.integer) { if(v_color_black_r.value == 0) if(v_color_black_g.value == 0) if(v_color_black_b.value == 0) if(fabs(v_color_grey_r.value - 0.5) < 1e-6) if(fabs(v_color_grey_g.value - 0.5) < 1e-6) if(fabs(v_color_grey_b.value - 0.5) < 1e-6) if(v_color_white_r.value == 1) if(v_color_white_g.value == 1) if(v_color_white_b.value == 1) vid_gammatables_trivial = true; } else { if(v_gamma.value == 1) if(v_contrast.value == 1) if(v_brightness.value == 0) vid_gammatables_trivial = true; } } #define GAMMACHECK(cache, value) if (cache != (value)) gamma_changed = true;cache = (value) if(v_psycho.integer) gamma_changed = true; GAMMACHECK(cachegamma , v_gamma.value); GAMMACHECK(cachecontrast , v_contrast.value); GAMMACHECK(cachebrightness , v_brightness.value); GAMMACHECK(cachecontrastboost, v_contrastboost.value); GAMMACHECK(cachecolorenable, v_color_enable.integer); GAMMACHECK(cacheblack[0] , v_color_black_r.value); GAMMACHECK(cacheblack[1] , v_color_black_g.value); GAMMACHECK(cacheblack[2] , v_color_black_b.value); GAMMACHECK(cachegrey[0] , v_color_grey_r.value); GAMMACHECK(cachegrey[1] , v_color_grey_g.value); GAMMACHECK(cachegrey[2] , v_color_grey_b.value); GAMMACHECK(cachewhite[0] , v_color_white_r.value); GAMMACHECK(cachewhite[1] , v_color_white_g.value); GAMMACHECK(cachewhite[2] , v_color_white_b.value); if(gamma_changed) ++vid_gammatables_serial; GAMMACHECK(cachehwgamma , wantgamma); #undef GAMMACHECK if (!force && !gamma_forcenextframe && !gamma_changed) return; gamma_forcenextframe = false; if (cachehwgamma) { if (!vid_usinghwgamma) { vid_usinghwgamma = true; if (vid_gammarampsize != rampsize || !vid_gammaramps) { vid_gammarampsize = rampsize; if (vid_gammaramps) Z_Free(vid_gammaramps); vid_gammaramps = (unsigned short *)Z_Malloc(6 * vid_gammarampsize * sizeof(unsigned short)); vid_systemgammaramps = vid_gammaramps + 3 * vid_gammarampsize; } VID_GetGamma(vid_systemgammaramps, vid_gammarampsize); } VID_BuildGammaTables(vid_gammaramps, vid_gammarampsize); // set vid_hardwaregammasupported to true if VID_SetGamma succeeds, OR if vid_hwgamma is >= 2 (forced gamma - ignores driver return value) Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_gammaramps, vid_gammarampsize) || cachehwgamma >= 2); // if custom gamma ramps failed (Windows stupidity), restore to system gamma if(!vid_hardwaregammasupported.integer) { if (vid_usinghwgamma) { vid_usinghwgamma = false; VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); } } } else { if (vid_usinghwgamma) { vid_usinghwgamma = false; VID_SetGamma(vid_systemgammaramps, vid_gammarampsize); } } } void VID_RestoreSystemGamma(void) { if (vid_usinghwgamma) { vid_usinghwgamma = false; Cvar_SetValueQuick(&vid_hardwaregammasupported, VID_SetGamma(vid_systemgammaramps, vid_gammarampsize)); // force gamma situation to be reexamined next frame gamma_forcenextframe = true; } } #ifdef WIN32 static dllfunction_t xinputdllfuncs[] = { {"XInputGetState", (void **) &qXInputGetState}, {"XInputGetKeystroke", (void **) &qXInputGetKeystroke}, {NULL, NULL} }; static const char* xinputdllnames [] = { "xinput1_3.dll", "xinput1_2.dll", "xinput1_1.dll", NULL }; static dllhandle_t xinputdll_dll = NULL; #endif void VID_Shared_Init(void) { #ifdef SSE_POSSIBLE if (Sys_HaveSSE2()) { Con_Printf("DPSOFTRAST available (SSE2 instructions detected)\n"); Cvar_RegisterVariable(&vid_soft); Cvar_RegisterVariable(&vid_soft_threads); Cvar_RegisterVariable(&vid_soft_interlace); } else Con_Printf("DPSOFTRAST not available (SSE2 disabled or not detected)\n"); #else Con_Printf("DPSOFTRAST not available (SSE2 not compiled in)\n"); #endif Cvar_RegisterVariable(&vid_hardwaregammasupported); Cvar_RegisterVariable(&gl_info_vendor); Cvar_RegisterVariable(&gl_info_renderer); Cvar_RegisterVariable(&gl_info_version); Cvar_RegisterVariable(&gl_info_extensions); Cvar_RegisterVariable(&gl_info_platform); Cvar_RegisterVariable(&gl_info_driver); Cvar_RegisterVariable(&v_gamma); Cvar_RegisterVariable(&v_brightness); Cvar_RegisterVariable(&v_contrastboost); Cvar_RegisterVariable(&v_contrast); Cvar_RegisterVariable(&v_color_enable); Cvar_RegisterVariable(&v_color_black_r); Cvar_RegisterVariable(&v_color_black_g); Cvar_RegisterVariable(&v_color_black_b); Cvar_RegisterVariable(&v_color_grey_r); Cvar_RegisterVariable(&v_color_grey_g); Cvar_RegisterVariable(&v_color_grey_b); Cvar_RegisterVariable(&v_color_white_r); Cvar_RegisterVariable(&v_color_white_g); Cvar_RegisterVariable(&v_color_white_b); Cvar_RegisterVariable(&v_hwgamma); Cvar_RegisterVariable(&v_glslgamma); Cvar_RegisterVariable(&v_glslgamma_2d); Cvar_RegisterVariable(&v_psycho); Cvar_RegisterVariable(&vid_fullscreen); Cvar_RegisterVariable(&vid_width); Cvar_RegisterVariable(&vid_height); Cvar_RegisterVariable(&vid_bitsperpixel); Cvar_RegisterVariable(&vid_samples); Cvar_RegisterVariable(&vid_refreshrate); Cvar_RegisterVariable(&vid_userefreshrate); Cvar_RegisterVariable(&vid_stereobuffer); Cvar_RegisterVariable(&vid_touchscreen_density); Cvar_RegisterVariable(&vid_touchscreen_xdpi); Cvar_RegisterVariable(&vid_touchscreen_ydpi); Cvar_RegisterVariable(&vid_vsync); Cvar_RegisterVariable(&vid_mouse); Cvar_RegisterVariable(&vid_grabkeyboard); Cvar_RegisterVariable(&vid_touchscreen); Cvar_RegisterVariable(&vid_touchscreen_showkeyboard); Cvar_RegisterVariable(&vid_touchscreen_supportshowkeyboard); Cvar_RegisterVariable(&vid_stick_mouse); Cvar_RegisterVariable(&vid_resizable); Cvar_RegisterVariable(&vid_desktopfullscreen); Cvar_RegisterVariable(&vid_minwidth); Cvar_RegisterVariable(&vid_minheight); Cvar_RegisterVariable(&vid_gl13); Cvar_RegisterVariable(&vid_gl20); Cvar_RegisterVariable(&gl_finish); Cvar_RegisterVariable(&vid_sRGB); Cvar_RegisterVariable(&vid_sRGB_fallback); Cvar_RegisterVariable(&joy_active); #ifdef WIN32 Cvar_RegisterVariable(&joy_xinputavailable); #endif Cvar_RegisterVariable(&joy_detected); Cvar_RegisterVariable(&joy_enable); Cvar_RegisterVariable(&joy_index); Cvar_RegisterVariable(&joy_axisforward); Cvar_RegisterVariable(&joy_axisside); Cvar_RegisterVariable(&joy_axisup); Cvar_RegisterVariable(&joy_axispitch); Cvar_RegisterVariable(&joy_axisyaw); //Cvar_RegisterVariable(&joy_axisroll); Cvar_RegisterVariable(&joy_deadzoneforward); Cvar_RegisterVariable(&joy_deadzoneside); Cvar_RegisterVariable(&joy_deadzoneup); Cvar_RegisterVariable(&joy_deadzonepitch); Cvar_RegisterVariable(&joy_deadzoneyaw); //Cvar_RegisterVariable(&joy_deadzoneroll); Cvar_RegisterVariable(&joy_sensitivityforward); Cvar_RegisterVariable(&joy_sensitivityside); Cvar_RegisterVariable(&joy_sensitivityup); Cvar_RegisterVariable(&joy_sensitivitypitch); Cvar_RegisterVariable(&joy_sensitivityyaw); //Cvar_RegisterVariable(&joy_sensitivityroll); Cvar_RegisterVariable(&joy_axiskeyevents); Cvar_RegisterVariable(&joy_axiskeyevents_deadzone); Cvar_RegisterVariable(&joy_x360_axisforward); Cvar_RegisterVariable(&joy_x360_axisside); Cvar_RegisterVariable(&joy_x360_axisup); Cvar_RegisterVariable(&joy_x360_axispitch); Cvar_RegisterVariable(&joy_x360_axisyaw); //Cvar_RegisterVariable(&joy_x360_axisroll); Cvar_RegisterVariable(&joy_x360_deadzoneforward); Cvar_RegisterVariable(&joy_x360_deadzoneside); Cvar_RegisterVariable(&joy_x360_deadzoneup); Cvar_RegisterVariable(&joy_x360_deadzonepitch); Cvar_RegisterVariable(&joy_x360_deadzoneyaw); //Cvar_RegisterVariable(&joy_x360_deadzoneroll); Cvar_RegisterVariable(&joy_x360_sensitivityforward); Cvar_RegisterVariable(&joy_x360_sensitivityside); Cvar_RegisterVariable(&joy_x360_sensitivityup); Cvar_RegisterVariable(&joy_x360_sensitivitypitch); Cvar_RegisterVariable(&joy_x360_sensitivityyaw); //Cvar_RegisterVariable(&joy_x360_sensitivityroll); #ifdef WIN32 Sys_LoadLibrary(xinputdllnames, &xinputdll_dll, xinputdllfuncs); #endif Cmd_AddCommand("force_centerview", Force_CenterView_f, "recenters view (stops looking up/down)"); Cmd_AddCommand("vid_restart", VID_Restart_f, "restarts video system (closes and reopens the window, restarts renderer)"); } static int VID_Mode(int fullscreen, int width, int height, int bpp, float refreshrate, int stereobuffer, int samples) { viddef_mode_t mode; char vabuf[1024]; memset(&mode, 0, sizeof(mode)); mode.fullscreen = fullscreen != 0; mode.width = width; mode.height = height; mode.bitsperpixel = bpp; mode.refreshrate = vid_userefreshrate.integer ? max(1, refreshrate) : 0; mode.userefreshrate = vid_userefreshrate.integer != 0; mode.stereobuffer = stereobuffer != 0; mode.samples = samples; cl_ignoremousemoves = 2; VID_ClearExtensions(); vid.samples = vid.mode.samples; if (VID_InitMode(&mode)) { // accept the (possibly modified) mode vid.mode = mode; vid.fullscreen = vid.mode.fullscreen; vid.width = vid.mode.width; vid.height = vid.mode.height; vid.bitsperpixel = vid.mode.bitsperpixel; vid.refreshrate = vid.mode.refreshrate; vid.userefreshrate = vid.mode.userefreshrate; vid.stereobuffer = vid.mode.stereobuffer; vid.stencil = vid.mode.bitsperpixel > 16; vid.sRGB2D = vid_sRGB.integer >= 1 && vid.sRGBcapable2D; vid.sRGB3D = vid_sRGB.integer >= 1 && vid.sRGBcapable3D; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: #ifdef GL_STEREO { GLboolean stereo; qglGetBooleanv(GL_STEREO, &stereo); vid.stereobuffer = stereo != 0; } #endif break; default: vid.stereobuffer = false; break; } if( (vid_sRGB_fallback.integer >= 3) // force fallback || (vid_sRGB_fallback.integer >= 2 && // fallback if framebuffer is 8bit !(r_viewfbo.integer >= 2 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2)) ) vid.sRGB2D = vid.sRGB3D = false; if(vid.samples != vid.mode.samples) Con_Printf("NOTE: requested %dx AA, got %dx AA\n", vid.mode.samples, vid.samples); Con_Printf("Video Mode: %s %dx%dx%dx%.2fhz%s%s\n", mode.fullscreen ? "fullscreen" : "window", mode.width, mode.height, mode.bitsperpixel, mode.refreshrate, mode.stereobuffer ? " stereo" : "", mode.samples > 1 ? va(vabuf, sizeof(vabuf), " (%ix AA)", mode.samples) : ""); Cvar_SetValueQuick(&vid_fullscreen, vid.mode.fullscreen); Cvar_SetValueQuick(&vid_width, vid.mode.width); Cvar_SetValueQuick(&vid_height, vid.mode.height); Cvar_SetValueQuick(&vid_bitsperpixel, vid.mode.bitsperpixel); Cvar_SetValueQuick(&vid_samples, vid.mode.samples); if(vid_userefreshrate.integer) Cvar_SetValueQuick(&vid_refreshrate, vid.mode.refreshrate); Cvar_SetValueQuick(&vid_stereobuffer, vid.stereobuffer ? 1 : 0); if (vid_touchscreen.integer) { in_windowmouse_x = vid_width.value / 2.f; in_windowmouse_y = vid_height.value / 2.f; } return true; } else return false; } static void VID_OpenSystems(void) { R_Modules_Start(); S_Startup(); } static void VID_CloseSystems(void) { S_Shutdown(); R_Modules_Shutdown(); } qboolean vid_commandlinecheck = true; extern qboolean vid_opened; void VID_Restart_f(void) { char vabuf[1024]; char vabuf2[1024]; // don't crash if video hasn't started yet if (vid_commandlinecheck) return; if (!vid_opened) { SCR_BeginLoadingPlaque(false); return; } Con_Printf("VID_Restart: changing from %s %dx%dx%dbpp%s%s, to %s %dx%dx%dbpp%s%s.\n", vid.mode.fullscreen ? "fullscreen" : "window", vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.fullscreen && vid.mode.userefreshrate ? va(vabuf, sizeof(vabuf), "x%.2fhz", vid.mode.refreshrate) : "", vid.mode.samples > 1 ? va(vabuf2, sizeof(vabuf2), " (%ix AA)", vid.mode.samples) : "", vid_fullscreen.integer ? "fullscreen" : "window", vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_fullscreen.integer && vid_userefreshrate.integer ? va(vabuf, sizeof(vabuf), "x%.2fhz", vid_refreshrate.value) : "", vid_samples.integer > 1 ? va(vabuf2, sizeof(vabuf2), " (%ix AA)", vid_samples.integer) : ""); VID_CloseSystems(); VID_Shutdown(); if (!VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer)) { Con_Print("Video mode change failed\n"); if (!VID_Mode(vid.mode.fullscreen, vid.mode.width, vid.mode.height, vid.mode.bitsperpixel, vid.mode.refreshrate, vid.mode.stereobuffer, vid.mode.samples)) Sys_Error("Unable to restore to last working video mode"); } VID_OpenSystems(); } const char *vidfallbacks[][2] = { {"vid_stereobuffer", "0"}, {"vid_samples", "1"}, {"vid_userefreshrate", "0"}, {"vid_width", "640"}, {"vid_height", "480"}, {"vid_bitsperpixel", "16"}, {NULL, NULL} }; // this is only called once by Host_StartVideo and again on each FS_GameDir_f void VID_Start(void) { int i, width, height, success; if (vid_commandlinecheck) { // interpret command-line parameters vid_commandlinecheck = false; // COMMANDLINEOPTION: Video: -window performs +vid_fullscreen 0 if (COM_CheckParm("-window") || COM_CheckParm("-safe")) Cvar_SetValueQuick(&vid_fullscreen, false); // COMMANDLINEOPTION: Video: -fullscreen performs +vid_fullscreen 1 if (COM_CheckParm("-fullscreen")) Cvar_SetValueQuick(&vid_fullscreen, true); width = 0; height = 0; // COMMANDLINEOPTION: Video: -width performs +vid_width and also +vid_height if only -width is specified (example: -width 1024 sets 1024x768 mode) if ((i = COM_CheckParm("-width")) != 0) width = atoi(com_argv[i+1]); // COMMANDLINEOPTION: Video: -height performs +vid_height and also +vid_width if only -height is specified (example: -height 768 sets 1024x768 mode) if ((i = COM_CheckParm("-height")) != 0) height = atoi(com_argv[i+1]); if (width == 0) width = height * 4 / 3; if (height == 0) height = width * 3 / 4; if (width) Cvar_SetValueQuick(&vid_width, width); if (height) Cvar_SetValueQuick(&vid_height, height); // COMMANDLINEOPTION: Video: -bpp performs +vid_bitsperpixel (example -bpp 32 or -bpp 16) if ((i = COM_CheckParm("-bpp")) != 0) Cvar_SetQuick(&vid_bitsperpixel, com_argv[i+1]); // COMMANDLINEOPTION: Video: -density performs +vid_touchscreen_density (example -density 1 or -density 1.5) if ((i = COM_CheckParm("-density")) != 0) Cvar_SetQuick(&vid_touchscreen_density, com_argv[i+1]); // COMMANDLINEOPTION: Video: -xdpi performs +vid_touchscreen_xdpi (example -xdpi 160 or -xdpi 320) if ((i = COM_CheckParm("-touchscreen_xdpi")) != 0) Cvar_SetQuick(&vid_touchscreen_xdpi, com_argv[i+1]); // COMMANDLINEOPTION: Video: -ydpi performs +vid_touchscreen_ydpi (example -ydpi 160 or -ydpi 320) if ((i = COM_CheckParm("-touchscreen_ydpi")) != 0) Cvar_SetQuick(&vid_touchscreen_ydpi, com_argv[i+1]); } success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); if (!success) { Con_Print("Desired video mode fail, trying fallbacks...\n"); for (i = 0;!success && vidfallbacks[i][0] != NULL;i++) { Cvar_Set(vidfallbacks[i][0], vidfallbacks[i][1]); success = VID_Mode(vid_fullscreen.integer, vid_width.integer, vid_height.integer, vid_bitsperpixel.integer, vid_refreshrate.value, vid_stereobuffer.integer, vid_samples.integer); } if (!success) Sys_Error("Video modes failed"); } VID_OpenSystems(); } void VID_Stop(void) { VID_CloseSystems(); VID_Shutdown(); } static int VID_SortModes_Compare(const void *a_, const void *b_) { vid_mode_t *a = (vid_mode_t *) a_; vid_mode_t *b = (vid_mode_t *) b_; if(a->width > b->width) return +1; if(a->width < b->width) return -1; if(a->height > b->height) return +1; if(a->height < b->height) return -1; if(a->refreshrate > b->refreshrate) return +1; if(a->refreshrate < b->refreshrate) return -1; if(a->bpp > b->bpp) return +1; if(a->bpp < b->bpp) return -1; if(a->pixelheight_num * b->pixelheight_denom > a->pixelheight_denom * b->pixelheight_num) return +1; if(a->pixelheight_num * b->pixelheight_denom < a->pixelheight_denom * b->pixelheight_num) return -1; return 0; } size_t VID_SortModes(vid_mode_t *modes, size_t count, qboolean usebpp, qboolean userefreshrate, qboolean useaspect) { size_t i; if(count == 0) return 0; // 1. sort them qsort(modes, count, sizeof(*modes), VID_SortModes_Compare); // 2. remove duplicates for(i = 0; i < count; ++i) { if(modes[i].width && modes[i].height) { if(i == 0) continue; if(modes[i].width != modes[i-1].width) continue; if(modes[i].height != modes[i-1].height) continue; if(userefreshrate) if(modes[i].refreshrate != modes[i-1].refreshrate) continue; if(usebpp) if(modes[i].bpp != modes[i-1].bpp) continue; if(useaspect) if(modes[i].pixelheight_num * modes[i-1].pixelheight_denom != modes[i].pixelheight_denom * modes[i-1].pixelheight_num) continue; } // a dupe, or a bogus mode! if(i < count-1) memmove(&modes[i], &modes[i+1], sizeof(*modes) * (count-1 - i)); --i; // check this index again, as mode i+1 is now here --count; } return count; } void VID_Soft_SharedSetup(void) { gl_platform = "DPSOFTRAST"; gl_platformextensions = ""; gl_renderer = "DarkPlaces-Soft"; gl_vendor = "Forest Hale"; gl_version = "0.0"; gl_extensions = ""; // clear the extension flags memset(&vid.support, 0, sizeof(vid.support)); Cvar_SetQuick(&gl_info_extensions, ""); // DPSOFTRAST requires BGRA vid.forcetextype = TEXTYPE_BGRA; vid.forcevbo = false; vid.support.arb_depth_texture = true; vid.support.arb_draw_buffers = true; vid.support.arb_occlusion_query = true; vid.support.arb_query_buffer_object = false; vid.support.arb_shadow = true; //vid.support.arb_texture_compression = true; vid.support.arb_texture_cube_map = true; vid.support.arb_texture_non_power_of_two = false; vid.support.arb_vertex_buffer_object = true; vid.support.ext_blend_subtract = true; vid.support.ext_draw_range_elements = true; vid.support.ext_framebuffer_object = true; vid.support.ext_texture_3d = true; //vid.support.ext_texture_compression_s3tc = true; vid.support.ext_texture_filter_anisotropic = true; vid.support.ati_separate_stencil = true; vid.support.ext_texture_srgb = false; vid.maxtexturesize_2d = 16384; vid.maxtexturesize_3d = 512; vid.maxtexturesize_cubemap = 16384; vid.texunits = 4; vid.teximageunits = 32; vid.texarrayunits = 8; vid.max_anisotropy = 1; vid.maxdrawbuffers = 4; vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); Con_DPrintf("Using DarkPlaces Software Rasterizer rendering path\n"); vid.renderpath = RENDERPATH_SOFT; vid.sRGBcapable2D = false; vid.sRGBcapable3D = false; vid.useinterleavedarrays = false; Cvar_SetQuick(&gl_info_vendor, gl_vendor); Cvar_SetQuick(&gl_info_renderer, gl_renderer); Cvar_SetQuick(&gl_info_version, gl_version); Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); Cvar_SetQuick(&gl_info_driver, gl_driver); // LordHavoc: report supported extensions #ifdef CONFIG_MENU Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); #else Con_DPrintf("\nQuakeC extensions for server and client: %s\n", vm_sv_extensions ); #endif // clear to black (loading plaque will be seen over this) GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); } darkplaces/model_shared.h0000664000175000017500000013335613067716222014777 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MODEL_SHARED_H #define MODEL_SHARED_H typedef enum synctype_e {ST_SYNC=0, ST_RAND } synctype_t; /* d*_t structures are on-disk representations m*_t structures are in-memory */ typedef enum modtype_e {mod_invalid, mod_brushq1, mod_sprite, mod_alias, mod_brushq2, mod_brushq3, mod_obj, mod_null} modtype_t; typedef struct animscene_s { char name[32]; // for viewthing support int firstframe; int framecount; int loop; // true or false float framerate; } animscene_t; typedef struct skinframe_s { rtexture_t *stain; // inverse modulate with background (used for decals and such) rtexture_t *merged; // original texture without glow rtexture_t *base; // original texture without pants/shirt/glow rtexture_t *pants; // pants only (in greyscale) rtexture_t *shirt; // shirt only (in greyscale) rtexture_t *nmap; // normalmap (bumpmap for dot3) rtexture_t *gloss; // glossmap (for dot3) rtexture_t *glow; // glow only (fullbrights) rtexture_t *fog; // alpha of the base texture (if not opaque) rtexture_t *reflect; // colored mask for cubemap reflections // accounting data for hash searches: // the compare variables are used to identify internal skins from certain // model formats // (so that two q1bsp maps with the same texture name for different // textures do not have any conflicts) struct skinframe_s *next; // next on hash chain char basename[MAX_QPATH]; // name of this int textureflags; // texture flags to use int comparewidth; int compareheight; int comparecrc; // mark and sweep garbage collection, this value is updated to a new value // on each level change for the used skinframes, if some are not used they // are freed unsigned int loadsequence; // indicates whether this texture has transparent pixels qboolean hasalpha; // average texture color, if applicable float avgcolor[4]; // for mdl skins, we actually only upload on first use (many are never used, and they are almost never used in both base+pants+shirt and merged modes) unsigned char *qpixels; int qwidth; int qheight; qboolean qhascolormapping; qboolean qgeneratebase; qboolean qgeneratemerged; qboolean qgeneratenmap; qboolean qgenerateglow; } skinframe_t; struct md3vertex_s; struct trivertx_s; typedef struct texvecvertex_s { signed char svec[3]; signed char tvec[3]; } texvecvertex_t; typedef struct blendweights_s { unsigned char index[4]; unsigned char influence[4]; } blendweights_t; typedef struct r_vertexgeneric_s { // 36 bytes float vertex3f[3]; float color4f[4]; float texcoord2f[2]; } r_vertexgeneric_t; typedef struct r_vertexmesh_s { // 88 bytes float vertex3f[3]; float color4f[4]; float texcoordtexture2f[2]; float texcoordlightmap2f[2]; float svector3f[3]; float tvector3f[3]; float normal3f[3]; unsigned char skeletalindex4ub[4]; unsigned char skeletalweight4ub[4]; } r_vertexmesh_t; typedef struct r_meshbuffer_s { int bufferobject; // OpenGL void *devicebuffer; // Direct3D size_t size; qboolean isindexbuffer; qboolean isuniformbuffer; qboolean isdynamic; qboolean isindex16; char name[MAX_QPATH]; } r_meshbuffer_t; // used for mesh lists in q1bsp/q3bsp map models // (the surfaces reference portions of these meshes) typedef struct surfmesh_s { // triangle data in system memory int num_triangles; // number of triangles in the mesh int *data_element3i; // int[tris*3] triangles of the mesh, 3 indices into vertex arrays for each r_meshbuffer_t *data_element3i_indexbuffer; int data_element3i_bufferoffset; unsigned short *data_element3s; // unsigned short[tris*3] triangles of the mesh in unsigned short format (NULL if num_vertices > 65536) r_meshbuffer_t *data_element3s_indexbuffer; int data_element3s_bufferoffset; int *data_neighbor3i; // int[tris*3] neighboring triangle on each edge (-1 if none) // vertex data in system memory int num_vertices; // number of vertices in the mesh float *data_vertex3f; // float[verts*3] vertex locations float *data_svector3f; // float[verts*3] direction of 'S' (right) texture axis for each vertex float *data_tvector3f; // float[verts*3] direction of 'T' (down) texture axis for each vertex float *data_normal3f; // float[verts*3] direction of 'R' (out) texture axis for each vertex float *data_texcoordtexture2f; // float[verts*2] texcoords for surface texture float *data_texcoordlightmap2f; // float[verts*2] texcoords for lightmap texture float *data_lightmapcolor4f; unsigned char *data_skeletalindex4ub; unsigned char *data_skeletalweight4ub; int *data_lightmapoffsets; // index into surface's lightmap samples for vertex lighting r_vertexmesh_t *data_vertexmesh; // interleaved arrays for D3D // vertex buffer object (stores geometry in video memory) r_meshbuffer_t *vbo_vertexbuffer; int vbooffset_vertex3f; int vbooffset_svector3f; int vbooffset_tvector3f; int vbooffset_normal3f; int vbooffset_texcoordtexture2f; int vbooffset_texcoordlightmap2f; int vbooffset_lightmapcolor4f; int vbooffset_skeletalindex4ub; int vbooffset_skeletalweight4ub; int vbooffset_vertexmesh; // morph blending, these are zero if model is skeletal or static int num_morphframes; struct md3vertex_s *data_morphmd3vertex; struct trivertx_s *data_morphmdlvertex; struct texvecvertex_s *data_morphtexvecvertex; float *data_morphmd2framesize6f; float num_morphmdlframescale[3]; float num_morphmdlframetranslate[3]; // skeletal blending, these are NULL if model is morph or static struct blendweights_s *data_blendweights; int num_blends; unsigned short *blends; // set if there is some kind of animation on this model qboolean isanimated; // vertex and index buffers for rendering r_meshbuffer_t *vertexmesh_vertexbuffer; } surfmesh_t; #define SHADOWMESHVERTEXHASH 1024 typedef struct shadowmeshvertexhash_s { struct shadowmeshvertexhash_s *next; } shadowmeshvertexhash_t; typedef struct shadowmesh_s { // next mesh in chain struct shadowmesh_s *next; // used for light mesh (NULL on shadow mesh) rtexture_t *map_diffuse; rtexture_t *map_specular; rtexture_t *map_normal; // buffer sizes int numverts, maxverts; int numtriangles, maxtriangles; // used always float *vertex3f; // used for light mesh (NULL on shadow mesh) float *svector3f; float *tvector3f; float *normal3f; float *texcoord2f; // used always int *element3i; r_meshbuffer_t *element3i_indexbuffer; int element3i_bufferoffset; unsigned short *element3s; r_meshbuffer_t *element3s_indexbuffer; int element3s_bufferoffset; // vertex/index buffers for rendering // (created by Mod_ShadowMesh_Finish if possible) r_vertexmesh_t *vertexmesh; // usually NULL // used for shadow mapping cubemap side partitioning int sideoffsets[6], sidetotals[6]; // used for shadow mesh (NULL on light mesh) int *neighbor3i; // these are NULL after Mod_ShadowMesh_Finish is performed, only used // while building meshes shadowmeshvertexhash_t **vertexhashtable, *vertexhashentries; r_meshbuffer_t *vbo_vertexbuffer; int vbooffset_vertex3f; int vbooffset_svector3f; int vbooffset_tvector3f; int vbooffset_normal3f; int vbooffset_texcoord2f; int vbooffset_vertexmesh; } shadowmesh_t; // various flags from shaders, used for special effects not otherwise classified // TODO: support these features more directly #define Q3TEXTUREFLAG_TWOSIDED 1 #define Q3TEXTUREFLAG_NOPICMIP 16 #define Q3TEXTUREFLAG_POLYGONOFFSET 32 #define Q3TEXTUREFLAG_REFRACTION 256 #define Q3TEXTUREFLAG_REFLECTION 512 #define Q3TEXTUREFLAG_WATERSHADER 1024 #define Q3TEXTUREFLAG_CAMERA 2048 #define Q3TEXTUREFLAG_TRANSPARENTSORT 4096 #define Q3PATHLENGTH 64 #define TEXTURE_MAXFRAMES 64 #define Q3WAVEPARMS 4 #define Q3DEFORM_MAXPARMS 3 #define Q3SHADER_MAXLAYERS 2 // FIXME support more than that (currently only two are used, so why keep more in RAM?) #define Q3RGBGEN_MAXPARMS 3 #define Q3ALPHAGEN_MAXPARMS 1 #define Q3TCGEN_MAXPARMS 6 #define Q3TCMOD_MAXPARMS 6 #define Q3MAXTCMODS 8 #define Q3MAXDEFORMS 4 typedef enum q3wavefunc_e { Q3WAVEFUNC_NONE, Q3WAVEFUNC_INVERSESAWTOOTH, Q3WAVEFUNC_NOISE, Q3WAVEFUNC_SAWTOOTH, Q3WAVEFUNC_SIN, Q3WAVEFUNC_SQUARE, Q3WAVEFUNC_TRIANGLE, Q3WAVEFUNC_COUNT } q3wavefunc_e; typedef int q3wavefunc_t; #define Q3WAVEFUNC_USER_COUNT 4 #define Q3WAVEFUNC_USER_SHIFT 8 // use 8 bits for wave func type typedef enum q3deform_e { Q3DEFORM_NONE, Q3DEFORM_PROJECTIONSHADOW, Q3DEFORM_AUTOSPRITE, Q3DEFORM_AUTOSPRITE2, Q3DEFORM_TEXT0, Q3DEFORM_TEXT1, Q3DEFORM_TEXT2, Q3DEFORM_TEXT3, Q3DEFORM_TEXT4, Q3DEFORM_TEXT5, Q3DEFORM_TEXT6, Q3DEFORM_TEXT7, Q3DEFORM_BULGE, Q3DEFORM_WAVE, Q3DEFORM_NORMAL, Q3DEFORM_MOVE, Q3DEFORM_COUNT } q3deform_t; typedef enum q3rgbgen_e { Q3RGBGEN_IDENTITY, Q3RGBGEN_CONST, Q3RGBGEN_ENTITY, Q3RGBGEN_EXACTVERTEX, Q3RGBGEN_IDENTITYLIGHTING, Q3RGBGEN_LIGHTINGDIFFUSE, Q3RGBGEN_ONEMINUSENTITY, Q3RGBGEN_ONEMINUSVERTEX, Q3RGBGEN_VERTEX, Q3RGBGEN_WAVE, Q3RGBGEN_COUNT } q3rgbgen_t; typedef enum q3alphagen_e { Q3ALPHAGEN_IDENTITY, Q3ALPHAGEN_CONST, Q3ALPHAGEN_ENTITY, Q3ALPHAGEN_LIGHTINGSPECULAR, Q3ALPHAGEN_ONEMINUSENTITY, Q3ALPHAGEN_ONEMINUSVERTEX, Q3ALPHAGEN_PORTAL, Q3ALPHAGEN_VERTEX, Q3ALPHAGEN_WAVE, Q3ALPHAGEN_COUNT } q3alphagen_t; typedef enum q3tcgen_e { Q3TCGEN_NONE, Q3TCGEN_TEXTURE, // very common Q3TCGEN_ENVIRONMENT, // common Q3TCGEN_LIGHTMAP, Q3TCGEN_VECTOR, Q3TCGEN_COUNT } q3tcgen_t; typedef enum q3tcmod_e { Q3TCMOD_NONE, Q3TCMOD_ENTITYTRANSLATE, Q3TCMOD_ROTATE, Q3TCMOD_SCALE, Q3TCMOD_SCROLL, Q3TCMOD_STRETCH, Q3TCMOD_TRANSFORM, Q3TCMOD_TURBULENT, Q3TCMOD_PAGE, Q3TCMOD_COUNT } q3tcmod_t; typedef struct q3shaderinfo_layer_rgbgen_s { q3rgbgen_t rgbgen; float parms[Q3RGBGEN_MAXPARMS]; q3wavefunc_t wavefunc; float waveparms[Q3WAVEPARMS]; } q3shaderinfo_layer_rgbgen_t; typedef struct q3shaderinfo_layer_alphagen_s { q3alphagen_t alphagen; float parms[Q3ALPHAGEN_MAXPARMS]; q3wavefunc_t wavefunc; float waveparms[Q3WAVEPARMS]; } q3shaderinfo_layer_alphagen_t; typedef struct q3shaderinfo_layer_tcgen_s { q3tcgen_t tcgen; float parms[Q3TCGEN_MAXPARMS]; } q3shaderinfo_layer_tcgen_t; typedef struct q3shaderinfo_layer_tcmod_s { q3tcmod_t tcmod; float parms[Q3TCMOD_MAXPARMS]; q3wavefunc_t wavefunc; float waveparms[Q3WAVEPARMS]; } q3shaderinfo_layer_tcmod_t; typedef struct q3shaderinfo_layer_s { int alphatest; int clampmap; float framerate; int numframes; int texflags; char** texturename; int blendfunc[2]; q3shaderinfo_layer_rgbgen_t rgbgen; q3shaderinfo_layer_alphagen_t alphagen; q3shaderinfo_layer_tcgen_t tcgen; q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; } q3shaderinfo_layer_t; typedef struct q3shaderinfo_deform_s { q3deform_t deform; float parms[Q3DEFORM_MAXPARMS]; q3wavefunc_t wavefunc; float waveparms[Q3WAVEPARMS]; } q3shaderinfo_deform_t; typedef enum dpoffsetmapping_technique_s { OFFSETMAPPING_OFF, // none OFFSETMAPPING_DEFAULT, // cvar-set OFFSETMAPPING_LINEAR, // linear OFFSETMAPPING_RELIEF // relief }dpoffsetmapping_technique_t; typedef enum dptransparentsort_category_e { TRANSPARENTSORT_SKY, TRANSPARENTSORT_DISTANCE, TRANSPARENTSORT_HUD, }dptransparentsortcategory_t; typedef struct q3shaderinfo_s { char name[Q3PATHLENGTH]; #define Q3SHADERINFO_COMPARE_START surfaceparms int surfaceparms; int surfaceflags; int textureflags; int numlayers; qboolean lighting; qboolean vertexalpha; qboolean textureblendalpha; int primarylayer, backgroundlayer; q3shaderinfo_layer_t layers[Q3SHADER_MAXLAYERS]; char skyboxname[Q3PATHLENGTH]; q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; // dp-specific additions: // shadow control qboolean dpnortlight; qboolean dpshadow; qboolean dpnoshadow; // add collisions to all triangles of the surface qboolean dpmeshcollisions; // kill shader based on cvar checks qboolean dpshaderkill; // fake reflection char dpreflectcube[Q3PATHLENGTH]; // reflection float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) vec4_t refractcolor4f; // color tint of refraction (including alpha factor) float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) float r_water_wateralpha; // additional wateralpha to apply when r_water is active float r_water_waterscroll[2]; // water normalmapscrollblend - scale and speed // offsetmapping dpoffsetmapping_technique_t offsetmapping; float offsetscale; float offsetbias; // 0 is normal, 1 leads to alpha 0 being neutral and alpha 1 pushing "out" // polygonoffset (only used if Q3TEXTUREFLAG_POLYGONOFFSET) float biaspolygonoffset, biaspolygonfactor; // transparent sort category dptransparentsortcategory_t transparentsort; // gloss float specularscalemod; float specularpowermod; // rtlighting ambient addition float rtlightambient; #define Q3SHADERINFO_COMPARE_END rtlightambient } q3shaderinfo_t; typedef enum texturelayertype_e { TEXTURELAYERTYPE_INVALID, TEXTURELAYERTYPE_LITTEXTURE, TEXTURELAYERTYPE_TEXTURE, TEXTURELAYERTYPE_FOG } texturelayertype_t; typedef struct texturelayer_s { texturelayertype_t type; qboolean depthmask; int blendfunc1; int blendfunc2; rtexture_t *texture; matrix4x4_t texmatrix; vec4_t color; } texturelayer_t; typedef struct texture_s { // q1bsp // name //char name[16]; // size unsigned int width, height; // SURF_ flags //unsigned int flags; // base material flags int basematerialflags; // current material flags (updated each bmodel render) int currentmaterialflags; // base material alpha (used for Q2 materials) float basealpha; // PolygonOffset values for rendering this material // (these are added to the r_refdef values and submodel values) float biaspolygonfactor; float biaspolygonoffset; // textures to use when rendering this material skinframe_t *currentskinframe; int numskinframes; float skinframerate; skinframe_t *skinframes[TEXTURE_MAXFRAMES]; // background layer (for terrain texture blending) skinframe_t *backgroundcurrentskinframe; int backgroundnumskinframes; float backgroundskinframerate; skinframe_t *backgroundskinframes[TEXTURE_MAXFRAMES]; // total frames in sequence and alternate sequence int anim_total[2]; // direct pointers to each of the frames in the sequences // (indexed as [alternate][frame]) struct texture_s *anim_frames[2][10]; // 1 = q1bsp animation with anim_total[0] >= 2 (animated) or anim_total[1] >= 1 (alternate frame set) // 2 = q2bsp animation with anim_total[0] >= 2 (uses self.frame) int animated; // renderer checks if this texture needs updating... int update_lastrenderframe; void *update_lastrenderentity; // the current alpha of this texture (may be affected by r_wateralpha, also basealpha, and ent->alpha) float currentalpha; // the current texture frame in animation struct texture_s *currentframe; // current texture transform matrix (used for water scrolling) matrix4x4_t currenttexmatrix; matrix4x4_t currentbackgroundtexmatrix; // various q3 shader features q3shaderinfo_layer_rgbgen_t rgbgen; q3shaderinfo_layer_alphagen_t alphagen; q3shaderinfo_layer_tcgen_t tcgen; q3shaderinfo_layer_tcmod_t tcmods[Q3MAXTCMODS]; q3shaderinfo_layer_tcmod_t backgroundtcmods[Q3MAXTCMODS]; q3shaderinfo_deform_t deforms[Q3MAXDEFORMS]; qboolean colormapping; rtexture_t *basetexture; // original texture without pants/shirt/glow rtexture_t *pantstexture; // pants only (in greyscale) rtexture_t *shirttexture; // shirt only (in greyscale) rtexture_t *nmaptexture; // normalmap (bumpmap for dot3) rtexture_t *glosstexture; // glossmap (for dot3) rtexture_t *glowtexture; // glow only (fullbrights) rtexture_t *fogtexture; // alpha of the base texture (if not opaque) rtexture_t *reflectmasktexture; // mask for fake reflections rtexture_t *reflectcubetexture; // fake reflections cubemap rtexture_t *backgroundbasetexture; // original texture without pants/shirt/glow rtexture_t *backgroundnmaptexture; // normalmap (bumpmap for dot3) rtexture_t *backgroundglosstexture; // glossmap (for dot3) rtexture_t *backgroundglowtexture; // glow only (fullbrights) float specularscale; float specularpower; // color tint (colormod * currentalpha) used for rtlighting this material float dlightcolor[3]; // color tint (colormod * 2) used for lightmapped lighting on this material // includes alpha as 4th component // replaces role of gl_Color in GLSL shader float lightmapcolor[4]; // from q3 shaders int customblendfunc[2]; int currentnumlayers; texturelayer_t currentlayers[16]; // q3bsp char name[64]; int surfaceflags; int supercontents; int textureflags; // q2bsp // we have to load the texture multiple times when Q2SURF_ flags differ, // though it still shares the skinframe int q2flags; int q2value; int q2contents; // reflection float reflectmin; // when refraction is used, minimum amount of reflection (when looking straight down) float reflectmax; // when refraction is used, maximum amount of reflection (when looking parallel to water) float refractfactor; // amount of refraction distort (1.0 = like the cvar specifies) vec4_t refractcolor4f; // color tint of refraction (including alpha factor) float reflectfactor; // amount of reflection distort (1.0 = like the cvar specifies) vec4_t reflectcolor4f; // color tint of reflection (including alpha factor) float r_water_wateralpha; // additional wateralpha to apply when r_water is active float r_water_waterscroll[2]; // scale and speed int camera_entity; // entity number for use by cameras // offsetmapping dpoffsetmapping_technique_t offsetmapping; float offsetscale; float offsetbias; // transparent sort category dptransparentsortcategory_t transparentsort; // gloss float specularscalemod; float specularpowermod; // diffuse and ambient float rtlightambient; } texture_t; typedef struct mtexinfo_s { float vecs[2][4]; // [s/t][xyz offset] int textureindex; int q1flags; int q2flags; // miptex flags + overrides int q2value; // light emission, etc char q2texture[32]; // texture name (textures/*.wal) int q2nexttexinfo; // for animations, -1 = end of chain } mtexinfo_t; typedef struct msurface_lightmapinfo_s { // texture mapping properties used by this surface mtexinfo_t *texinfo; // q1bsp // index into r_refdef.scene.lightstylevalue array, 255 means not used (black) unsigned char styles[MAXLIGHTMAPS]; // q1bsp // RGB lighting data [numstyles][height][width][3] unsigned char *samples; // q1bsp // RGB normalmap data [numstyles][height][width][3] unsigned char *nmapsamples; // q1bsp // stain to apply on lightmap (soot/dirt/blood/whatever) unsigned char *stainsamples; // q1bsp int texturemins[2]; // q1bsp int extents[2]; // q1bsp int lightmaporigin[2]; // q1bsp } msurface_lightmapinfo_t; struct q3deffect_s; typedef struct msurface_s { // bounding box for onscreen checks vec3_t mins; vec3_t maxs; // the texture to use on the surface texture_t *texture; // the lightmap texture fragment to use on the rendering mesh rtexture_t *lightmaptexture; // the lighting direction texture fragment to use on the rendering mesh rtexture_t *deluxemaptexture; // lightmaptexture rebuild information not used in q3bsp msurface_lightmapinfo_t *lightmapinfo; // q1bsp // fog volume info in q3bsp struct q3deffect_s *effect; // q3bsp // mesh information for collisions (only used by q3bsp curves) int num_firstcollisiontriangle; int *deprecatedq3data_collisionelement3i; // q3bsp float *deprecatedq3data_collisionvertex3f; // q3bsp float *deprecatedq3data_collisionbbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles float *deprecatedq3data_bbox6f; // collision optimization - contains combined bboxes of every data_collisionstride triangles // surfaces own ranges of vertices and triangles in the model->surfmesh int num_triangles; // number of triangles int num_firsttriangle; // first triangle int num_vertices; // number of vertices int num_firstvertex; // first vertex // shadow volume building information int num_firstshadowmeshtriangle; // index into model->brush.shadowmesh // mesh information for collisions (only used by q3bsp curves) int num_collisiontriangles; // q3bsp int num_collisionvertices; // q3bsp int deprecatedq3num_collisionbboxstride; int deprecatedq3num_bboxstride; // FIXME: collisionmarkframe should be kept in a separate array int deprecatedq3collisionmarkframe; // q3bsp // don't collide twice in one trace } msurface_t; #include "matrixlib.h" #include "bih.h" #include "model_brush.h" #include "model_sprite.h" #include "model_alias.h" typedef struct model_sprite_s { int sprnum_type; mspriteframe_t *sprdata_frames; } model_sprite_t; struct trace_s; typedef struct model_brush_lightstyleinfo_s { int style; int value; int numsurfaces; int *surfacelist; } model_brush_lightstyleinfo_t; typedef struct model_brush_s { // true if this model is a HalfLife .bsp file qboolean ishlbsp; // true if this model is a BSP2rmqe .bsp file (expanded 32bit bsp format for rmqe) qboolean isbsp2rmqe; // true if this model is a BSP2 .bsp file (expanded 32bit bsp format for DarkPlaces, others?) qboolean isbsp2; // true if this model is a Quake2 .bsp file (IBSP38) qboolean isq2bsp; // true if this model is a Quake3 .bsp file (IBSP46) qboolean isq3bsp; // true if this model is a Quake1/Quake2 .bsp file where skymasking capability exists qboolean skymasking; // string of entity definitions (.map format) char *entities; // if not NULL this is a submodel struct model_s *parentmodel; // (this is the number of the submodel, an index into submodels) int submodel; // number of submodels in this map (just used by server to know how many // submodels to load) int numsubmodels; // pointers to each of the submodels struct model_s **submodels; int num_planes; mplane_t *data_planes; int num_nodes; mnode_t *data_nodes; // visible leafs, not counting 0 (solid) int num_visleafs; // number of actual leafs (including 0 which is solid) int num_leafs; mleaf_t *data_leafs; int num_leafbrushes; int *data_leafbrushes; int num_leafsurfaces; int *data_leafsurfaces; int num_portals; mportal_t *data_portals; int num_portalpoints; mvertex_t *data_portalpoints; int num_brushes; q3mbrush_t *data_brushes; int num_brushsides; q3mbrushside_t *data_brushsides; // pvs int num_pvsclusters; int num_pvsclusterbytes; unsigned char *data_pvsclusters; // example //pvschain = model->brush.data_pvsclusters + mycluster * model->brush.num_pvsclusterbytes; //if (pvschain[thatcluster >> 3] & (1 << (thatcluster & 7))) // collision geometry for q3 curves int num_collisionvertices; int num_collisiontriangles; float *data_collisionvertex3f; int *data_collisionelement3i; // a mesh containing all shadow casting geometry for the whole model (including submodels), portions of this are referenced by each surface's num_firstshadowmeshtriangle shadowmesh_t *shadowmesh; // a mesh containing all SUPERCONTENTS_SOLID surfaces for this model or submodel, for physics engines to use shadowmesh_t *collisionmesh; // common functions int (*SuperContentsFromNativeContents)(struct model_s *model, int nativecontents); int (*NativeContentsFromSuperContents)(struct model_s *model, int supercontents); unsigned char *(*GetPVS)(struct model_s *model, const vec3_t p); int (*FatPVS)(struct model_s *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge); int (*BoxTouchingPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); int (*BoxTouchingLeafPVS)(struct model_s *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs); int (*BoxTouchingVisibleLeafs)(struct model_s *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs); int (*FindBoxClusters)(struct model_s *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist); void (*LightPoint)(struct model_s *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal); void (*FindNonSolidLocation)(struct model_s *model, const vec3_t in, vec3_t out, vec_t radius); mleaf_t *(*PointInLeaf)(struct model_s *model, const vec3_t p); // these are actually only found on brushq1, but NULL is handled gracefully void (*AmbientSoundLevelsForPoint)(struct model_s *model, const vec3_t p, unsigned char *out, int outsize); void (*RoundUpToHullSize)(struct model_s *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs); // trace a line of sight through this model (returns false if the line if sight is definitely blocked) qboolean (*TraceLineOfSight)(struct model_s *model, const vec3_t start, const vec3_t end); char skybox[MAX_QPATH]; skinframe_t *solidskyskinframe; skinframe_t *alphaskyskinframe; qboolean supportwateralpha; // QuakeWorld int qw_md4sum; int qw_md4sum2; } model_brush_t; typedef struct model_brushq1_s { mmodel_t *submodels; int numvertexes; mvertex_t *vertexes; int numedges; medge_t *edges; int numtexinfo; mtexinfo_t *texinfo; int numsurfedges; int *surfedges; int numclipnodes; mclipnode_t *clipnodes; hull_t hulls[MAX_MAP_HULLS]; int num_compressedpvs; unsigned char *data_compressedpvs; int num_lightdata; unsigned char *lightdata; unsigned char *nmaplightdata; // deluxemap file // lightmap update chains for light styles int num_lightstyles; model_brush_lightstyleinfo_t *data_lightstyleinfo; // this contains bytes that are 1 if a surface needs its lightmap rebuilt unsigned char *lightmapupdateflags; qboolean firstrender; // causes all surface lightmaps to be loaded in first frame } model_brushq1_t; typedef struct model_brushq2_s { int dummy; // MSVC can't handle an empty struct } model_brushq2_t; typedef struct model_brushq3_s { int num_models; q3dmodel_t *data_models; // used only during loading - freed after loading! int num_vertices; float *data_vertex3f; float *data_normal3f; float *data_texcoordtexture2f; float *data_texcoordlightmap2f; float *data_color4f; // freed after loading! int num_triangles; int *data_element3i; int num_effects; q3deffect_t *data_effects; // lightmap textures int num_originallightmaps; int num_mergedlightmaps; int num_lightmapmergedwidthpower; int num_lightmapmergedheightpower; int num_lightmapmergedwidthheightdeluxepower; int num_lightmapmerge; rtexture_t **data_lightmaps; rtexture_t **data_deluxemaps; // voxel light data with directional shading int num_lightgrid; q3dlightgrid_t *data_lightgrid; // size of each cell (may vary by map, typically 64 64 128) float num_lightgrid_cellsize[3]; // 1.0 / num_lightgrid_cellsize float num_lightgrid_scale[3]; // dimensions of the world model in lightgrid cells int num_lightgrid_imins[3]; int num_lightgrid_imaxs[3]; int num_lightgrid_isize[3]; // transform modelspace coordinates to lightgrid index matrix4x4_t num_lightgrid_indexfromworld; // true if this q3bsp file has been detected as using deluxemapping // (lightmap texture pairs, every odd one is never directly refernced, // and contains lighting normals, not colors) qboolean deluxemapping; // true if the detected deluxemaps are the modelspace kind, rather than // the faster tangentspace kind qboolean deluxemapping_modelspace; // size of lightmaps (128 by default, but may be another poweroftwo if // external lightmaps are used (q3map2 -lightmapsize) int lightmapsize; } model_brushq3_t; struct frameblend_s; struct skeleton_s; typedef struct model_s { // name and path of model, for example "progs/player.mdl" char name[MAX_QPATH]; // model needs to be loaded if this is false qboolean loaded; // set if the model is used in current map, models which are not, are purged qboolean used; // CRC of the file this model was loaded from, to reload if changed unsigned int crc; // mod_brush, mod_alias, mod_sprite modtype_t type; // memory pool for allocations mempool_t *mempool; // all models use textures... rtexturepool_t *texturepool; // EF_* flags (translated from the model file's different flags layout) int effects; // number of QC accessible frame(group)s in the model int numframes; // number of QC accessible skin(group)s in the model int numskins; // whether to randomize animated framegroups synctype_t synctype; // bounding box at angles '0 0 0' vec3_t normalmins, normalmaxs; // bounding box if yaw angle is not 0, but pitch and roll are vec3_t yawmins, yawmaxs; // bounding box if pitch or roll are used vec3_t rotatedmins, rotatedmaxs; // sphere radius, usable at any angles float radius; // squared sphere radius for easier comparisons float radius2; // skin animation info animscene_t *skinscenes; // [numskins] // skin animation info animscene_t *animscenes; // [numframes] // range of surface numbers in this (sub)model int firstmodelsurface; int nummodelsurfaces; int *sortedmodelsurfaces; // range of collision brush numbers in this (sub)model int firstmodelbrush; int nummodelbrushes; // BIH (Bounding Interval Hierarchy) for this (sub)model bih_t collision_bih; bih_t render_bih; // if not set, use collision_bih instead for rendering purposes too // for md3 models int num_tags; int num_tagframes; aliastag_t *data_tags; // for skeletal models int num_bones; aliasbone_t *data_bones; float num_posescale; // scaling factor from origin in poses7s format (includes divide by 32767) float num_poseinvscale; // scaling factor to origin in poses7s format (includes multiply by 32767) int num_poses; short *data_poses7s; // origin xyz, quat xyzw, unit length, values normalized to +/-32767 range float *data_baseboneposeinverse; // textures of this model int num_textures; int num_texturesperskin; texture_t *data_textures; qboolean wantnormals; qboolean wanttangents; // surfaces of this model int num_surfaces; msurface_t *data_surfaces; // optional lightmapinfo data for surface lightmap updates msurface_lightmapinfo_t *data_surfaces_lightmapinfo; // all surfaces belong to this mesh surfmesh_t surfmesh; // data type of model const char *modeldatatypestring; // generates vertex data for a given frameblend void(*AnimateVertices)(const struct model_s * RESTRICT model, const struct frameblend_s * RESTRICT frameblend, const struct skeleton_s *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); // draw the model's sky polygons (only used by brush models) void(*DrawSky)(struct entity_render_s *ent); // draw refraction/reflection textures for the model's water polygons (only used by brush models) void(*DrawAddWaterPlanes)(struct entity_render_s *ent); // draw the model using lightmap/dlight shading void(*Draw)(struct entity_render_s *ent); // draw the model to the depth buffer (no color rendering at all) void(*DrawDepth)(struct entity_render_s *ent); // draw any enabled debugging effects on this model (such as showing triangles, normals, collision brushes...) void(*DrawDebug)(struct entity_render_s *ent); // draw geometry textures for deferred rendering void(*DrawPrepass)(struct entity_render_s *ent); // compile an optimized shadowmap mesh for the model based on light source void(*CompileShadowMap)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); // draw depth into a shadowmap void(*DrawShadowMap)(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); // gathers info on which clusters and surfaces are lit by light, as well as calculating a bounding box void(*GetLightInfo)(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); // compile a shadow volume for the model based on light source void(*CompileShadowVolume)(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); // draw a shadow volume for the model based on light source void(*DrawShadowVolume)(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); // draw the lighting on a model (through stencil) void(*DrawLight)(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); // trace a box against this model void (*TraceBox)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); void (*TraceBrush)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask, int skipsupercontentsmask); // trace a box against this model void (*TraceLine)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); // trace a point against this model (like PointSuperContents) void (*TracePoint)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask); // find the supercontents value at a point in this model int (*PointSuperContents)(struct model_s *model, int frame, const vec3_t point); // trace a line against geometry in this model and report correct texture (used by r_shadow_bouncegrid) void (*TraceLineAgainstSurfaces)(struct model_s *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); // fields belonging to some types of model model_sprite_t sprite; model_brush_t brush; model_brushq1_t brushq1; model_brushq2_t brushq2; model_brushq3_t brushq3; // flags this model for offseting sounds to the model center (used by brush models) int soundfromcenter; // if set, the model contains light information (lightmap, or vertexlight) qboolean lit; float lightmapscale; } dp_model_t; //============================================================================ // model loading extern dp_model_t *loadmodel; extern unsigned char *mod_base; // sky/water subdivision //extern cvar_t gl_subdivide_size; // texture fullbrights extern cvar_t r_fullbrights; extern cvar_t r_enableshadowvolumes; void Mod_Init (void); void Mod_Reload (void); dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk); dp_model_t *Mod_FindName (const char *name, const char *parentname); dp_model_t *Mod_ForName (const char *name, qboolean crash, qboolean checkdisk, const char *parentname); void Mod_UnloadModel (dp_model_t *mod); void Mod_ClearUsed(void); void Mod_PurgeUnused(void); void Mod_RemoveStaleWorldModels(dp_model_t *skip); // only used during loading! extern dp_model_t *loadmodel; extern char loadname[32]; // for hunk tags int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices); void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles); void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline); void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting); void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting); void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors); void Mod_MakeSortedSurfaces(dp_model_t *mod); // called specially by brush model loaders before generating submodels // automatically called after model loader returns void Mod_BuildVBOs(void); shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors); int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f); void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f); void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i); shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable); shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo); void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius); void Mod_ShadowMesh_Free(shadowmesh_t *mesh); void Mod_CreateCollisionMesh(dp_model_t *mod); void Mod_FreeQ3Shaders(void); void Mod_LoadQ3Shaders(void); q3shaderinfo_t *Mod_LookupQ3Shader(const char *name); qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags); extern cvar_t r_mipskins; extern cvar_t r_mipnormalmaps; typedef struct skinfileitem_s { struct skinfileitem_s *next; char name[MAX_QPATH]; char replacement[MAX_QPATH]; } skinfileitem_t; typedef struct skinfile_s { struct skinfile_s *next; skinfileitem_t *items; } skinfile_t; skinfile_t *Mod_LoadSkinFiles(void); void Mod_FreeSkinFiles(skinfile_t *skinfile); int Mod_CountSkinFiles(skinfile_t *skinfile); void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername); void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap); int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f); void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer); typedef struct mod_alloclightmap_row_s { int rowY; int currentX; } mod_alloclightmap_row_t; typedef struct mod_alloclightmap_state_s { int width; int height; int currentY; mod_alloclightmap_row_t *rows; } mod_alloclightmap_state_t; void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height); void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state); void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state); qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy); // bsp models void Mod_BrushInit(void); // used for talking to the QuakeC mainly int Mod_Q1BSP_NativeContentsFromSuperContents(struct model_s *model, int supercontents); int Mod_Q1BSP_SuperContentsFromNativeContents(struct model_s *model, int nativecontents); // used for loading wal files in Mod_LoadTextureFromQ3Shader int Mod_Q2BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents); int Mod_Q2BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents); // a lot of model formats use the Q1BSP code, so here are the prototypes... struct entity_render_s; void R_Q1BSP_DrawAddWaterPlanes(struct entity_render_s *ent); void R_Q1BSP_DrawSky(struct entity_render_s *ent); void R_Q1BSP_Draw(struct entity_render_s *ent); void R_Q1BSP_DrawDepth(struct entity_render_s *ent); void R_Q1BSP_DrawDebug(struct entity_render_s *ent); void R_Q1BSP_DrawPrepass(struct entity_render_s *ent); void R_Q1BSP_GetLightInfo(struct entity_render_s *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes); void R_Q1BSP_CompileShadowMap(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); void R_Q1BSP_DrawShadowMap(int side, struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs); void R_Q1BSP_CompileShadowVolume(struct entity_render_s *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist); void R_Q1BSP_DrawShadowVolume(struct entity_render_s *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist, const vec3_t lightmins, const vec3_t lightmaxs); void R_Q1BSP_DrawLight(struct entity_render_s *ent, int numsurfaces, const int *surfacelist, const unsigned char *trispvs); // Collision optimization using Bounding Interval Hierarchy void Mod_CollisionBIH_TracePoint(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask); void Mod_CollisionBIH_TraceLine(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); void Mod_CollisionBIH_TraceBox(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, struct colbrushf_s *start, struct colbrushf_s *end, int hitsupercontentsmask, int skipsupercontentsmask); void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const struct frameblend_s *frameblend, const skeleton_t *skeleton, struct trace_s *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask); qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end); int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const vec3_t point); int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t point); bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out); // alias models struct frameblend_s; struct skeleton_s; void Mod_AliasInit(void); int Mod_Alias_GetTagMatrix(const dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, matrix4x4_t *outmatrix); int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname); int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix); void Mod_Skeletal_FreeBuffers(void); // sprite models void Mod_SpriteInit(void); // loaders void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend); void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend); #endif // MODEL_SHARED_H darkplaces/r_lightning.c0000664000175000017500000003015713067716222014643 0ustar kalevkalev #include "quakedef.h" #include "image.h" cvar_t r_lightningbeam_thickness = {CVAR_SAVE, "r_lightningbeam_thickness", "4", "thickness of the lightning beam effect"}; cvar_t r_lightningbeam_scroll = {CVAR_SAVE, "r_lightningbeam_scroll", "5", "speed of texture scrolling on the lightning beam effect"}; cvar_t r_lightningbeam_repeatdistance = {CVAR_SAVE, "r_lightningbeam_repeatdistance", "128", "how far to stretch the texture along the lightning beam effect"}; cvar_t r_lightningbeam_color_red = {CVAR_SAVE, "r_lightningbeam_color_red", "1", "color of the lightning beam effect"}; cvar_t r_lightningbeam_color_green = {CVAR_SAVE, "r_lightningbeam_color_green", "1", "color of the lightning beam effect"}; cvar_t r_lightningbeam_color_blue = {CVAR_SAVE, "r_lightningbeam_color_blue", "1", "color of the lightning beam effect"}; cvar_t r_lightningbeam_qmbtexture = {CVAR_SAVE, "r_lightningbeam_qmbtexture", "0", "load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better"}; skinframe_t *r_lightningbeamtexture; skinframe_t *r_lightningbeamqmbtexture; int r_lightningbeamelement3i[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; unsigned short r_lightningbeamelement3s[18] = {0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11}; static void r_lightningbeams_start(void) { r_lightningbeamtexture = NULL; r_lightningbeamqmbtexture = NULL; } static void r_lightningbeams_setupqmbtexture(void) { r_lightningbeamqmbtexture = R_SkinFrame_LoadExternal("textures/particles/lightning.pcx", TEXF_ALPHA | TEXF_FORCELINEAR, false); if (r_lightningbeamqmbtexture == NULL) Cvar_SetValueQuick(&r_lightningbeam_qmbtexture, false); } static void r_lightningbeams_setuptexture(void) { #if 0 #define BEAMWIDTH 128 #define BEAMHEIGHT 64 #define PATHPOINTS 8 int i, j, px, py, nearestpathindex, imagenumber; float particlex, particley, particlexv, particleyv, dx, dy, s, maxpathstrength; unsigned char *pixels; int *image; struct lightningpathnode_s { float x, y, strength; } path[PATHPOINTS], temppath; image = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); pixels = Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * sizeof(unsigned char[4])); for (imagenumber = 0, maxpathstrength = 0.0339476;maxpathstrength < 0.5;imagenumber++, maxpathstrength += 0.01) { for (i = 0;i < PATHPOINTS;i++) { path[i].x = lhrandom(0, 1); path[i].y = lhrandom(0.2, 0.8); path[i].strength = lhrandom(0, 1); } for (i = 0;i < PATHPOINTS;i++) { for (j = i + 1;j < PATHPOINTS;j++) { if (path[j].x < path[i].x) { temppath = path[j]; path[j] = path[i]; path[i] = temppath; } } } particlex = path[0].x; particley = path[0].y; particlexv = lhrandom(0, 0.02); particlexv = lhrandom(-0.02, 0.02); memset(image, 0, BEAMWIDTH * BEAMHEIGHT * sizeof(int)); for (i = 0;i < 65536;i++) { for (nearestpathindex = 0;nearestpathindex < PATHPOINTS;nearestpathindex++) if (path[nearestpathindex].x > particlex) break; nearestpathindex %= PATHPOINTS; dx = path[nearestpathindex].x + lhrandom(-0.01, 0.01);dx = bound(0, dx, 1) - particlex;if (dx < 0) dx += 1; dy = path[nearestpathindex].y + lhrandom(-0.01, 0.01);dy = bound(0, dy, 1) - particley; s = path[nearestpathindex].strength / sqrt(dx*dx+dy*dy); particlexv = particlexv /* (1 - lhrandom(0.08, 0.12))*/ + dx * s; particleyv = particleyv /* (1 - lhrandom(0.08, 0.12))*/ + dy * s; particlex += particlexv * maxpathstrength;particlex -= (int) particlex; particley += particleyv * maxpathstrength;particley = bound(0, particley, 1); px = particlex * BEAMWIDTH; py = particley * BEAMHEIGHT; if (px >= 0 && py >= 0 && px < BEAMWIDTH && py < BEAMHEIGHT) image[py*BEAMWIDTH+px] += 16; } for (py = 0;py < BEAMHEIGHT;py++) { for (px = 0;px < BEAMWIDTH;px++) { pixels[(py*BEAMWIDTH+px)*4+2] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); pixels[(py*BEAMWIDTH+px)*4+1] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); pixels[(py*BEAMWIDTH+px)*4+0] = bound(0, image[py*BEAMWIDTH+px] * 1.0f, 255.0f); pixels[(py*BEAMWIDTH+px)*4+3] = 255; } } Image_WriteTGABGRA(va(vabuf, sizeof(vabuf), "lightningbeam%i.tga", imagenumber), BEAMWIDTH, BEAMHEIGHT, pixels); } r_lightningbeamtexture = R_LoadTexture2D(r_lightningbeamtexturepool, "lightningbeam", BEAMWIDTH, BEAMHEIGHT, pixels, TEXTYPE_BGRA, TEXF_FORCELINEAR, NULL); Mem_Free(pixels); Mem_Free(image); #else #define BEAMWIDTH 64 #define BEAMHEIGHT 128 float r, g, b, intensity, fx, width, center; int x, y; unsigned char *data, *noise1, *noise2; data = (unsigned char *)Mem_Alloc(tempmempool, BEAMWIDTH * BEAMHEIGHT * 4); noise1 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); noise2 = (unsigned char *)Mem_Alloc(tempmempool, BEAMHEIGHT * BEAMHEIGHT); fractalnoise(noise1, BEAMHEIGHT, BEAMHEIGHT / 8); fractalnoise(noise2, BEAMHEIGHT, BEAMHEIGHT / 16); for (y = 0;y < BEAMHEIGHT;y++) { width = 0.15;//((noise1[y * BEAMHEIGHT] * (1.0f / 256.0f)) * 0.1f + 0.1f); center = (noise1[y * BEAMHEIGHT + (BEAMHEIGHT / 2)] / 256.0f) * (1.0f - width * 2.0f) + width; for (x = 0;x < BEAMWIDTH;x++, fx++) { fx = (((float) x / BEAMWIDTH) - center) / width; intensity = 1.0f - sqrt(fx * fx); if (intensity > 0) intensity = pow(intensity, 2) * ((noise2[y * BEAMHEIGHT + x] * (1.0f / 256.0f)) * 0.33f + 0.66f); intensity = bound(0, intensity, 1); r = intensity * 1.0f; g = intensity * 1.0f; b = intensity * 1.0f; data[(y * BEAMWIDTH + x) * 4 + 2] = (unsigned char)(bound(0, r, 1) * 255.0f); data[(y * BEAMWIDTH + x) * 4 + 1] = (unsigned char)(bound(0, g, 1) * 255.0f); data[(y * BEAMWIDTH + x) * 4 + 0] = (unsigned char)(bound(0, b, 1) * 255.0f); data[(y * BEAMWIDTH + x) * 4 + 3] = (unsigned char)255; } } r_lightningbeamtexture = R_SkinFrame_LoadInternalBGRA("lightningbeam", TEXF_FORCELINEAR, data, BEAMWIDTH, BEAMHEIGHT, false); Mem_Free(noise1); Mem_Free(noise2); Mem_Free(data); #endif } static void r_lightningbeams_shutdown(void) { r_lightningbeamtexture = NULL; r_lightningbeamqmbtexture = NULL; } static void r_lightningbeams_newmap(void) { if (r_lightningbeamtexture) R_SkinFrame_MarkUsed(r_lightningbeamtexture); if (r_lightningbeamqmbtexture) R_SkinFrame_MarkUsed(r_lightningbeamqmbtexture); } void R_LightningBeams_Init(void) { Cvar_RegisterVariable(&r_lightningbeam_thickness); Cvar_RegisterVariable(&r_lightningbeam_scroll); Cvar_RegisterVariable(&r_lightningbeam_repeatdistance); Cvar_RegisterVariable(&r_lightningbeam_color_red); Cvar_RegisterVariable(&r_lightningbeam_color_green); Cvar_RegisterVariable(&r_lightningbeam_color_blue); Cvar_RegisterVariable(&r_lightningbeam_qmbtexture); R_RegisterModule("R_LightningBeams", r_lightningbeams_start, r_lightningbeams_shutdown, r_lightningbeams_newmap, NULL, NULL); } static void R_CalcLightningBeamPolygonVertex3f(float *v, const float *start, const float *end, const float *offset) { // near right corner VectorAdd (start, offset, (v + 0)); // near left corner VectorSubtract(start, offset, (v + 3)); // far left corner VectorSubtract(end , offset, (v + 6)); // far right corner VectorAdd (end , offset, (v + 9)); } static void R_CalcLightningBeamPolygonTexCoord2f(float *tc, float t1, float t2) { if (r_lightningbeam_qmbtexture.integer) { // near right corner tc[0] = t1;tc[1] = 0; // near left corner tc[2] = t1;tc[3] = 1; // far left corner tc[4] = t2;tc[5] = 1; // far right corner tc[6] = t2;tc[7] = 0; } else { // near right corner tc[0] = 0;tc[1] = t1; // near left corner tc[2] = 1;tc[3] = t1; // far left corner tc[4] = 1;tc[5] = t2; // far right corner tc[6] = 0;tc[7] = t2; } } float beamrepeatscale; static void R_DrawLightningBeam_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int surfacelistindex; float vertex3f[12*3]; float texcoord2f[12*2]; RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, r_lightningbeam_color_red.value, r_lightningbeam_color_green.value, r_lightningbeam_color_blue.value, 1, 12, vertex3f, texcoord2f, NULL, NULL, NULL, NULL, 6, r_lightningbeamelement3i, r_lightningbeamelement3s, false, false); if (r_lightningbeam_qmbtexture.integer && r_lightningbeamqmbtexture == NULL) r_lightningbeams_setupqmbtexture(); if (!r_lightningbeam_qmbtexture.integer && r_lightningbeamtexture == NULL) r_lightningbeams_setuptexture(); for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { const beam_t *b = cl.beams + surfacelist[surfacelistindex]; vec3_t beamdir, right, up, offset, start, end; float length, t1, t2; CL_Beam_CalculatePositions(b, start, end); // calculate beam direction (beamdir) vector and beam length // get difference vector VectorSubtract(end, start, beamdir); // find length of difference vector length = sqrt(DotProduct(beamdir, beamdir)); // calculate scale to make beamdir a unit vector (normalized) t1 = 1.0f / length; // scale beamdir so it is now normalized VectorScale(beamdir, t1, beamdir); // calculate up vector such that it points toward viewer, and rotates around the beamdir // get direction from start of beam to viewer VectorSubtract(r_refdef.view.origin, start, up); // remove the portion of the vector that moves along the beam // (this leaves only a vector pointing directly away from the beam) t1 = -DotProduct(up, beamdir); VectorMA(up, t1, beamdir, up); // generate right vector from forward and up, the result is unnormalized CrossProduct(beamdir, up, right); // now normalize the right vector and up vector VectorNormalize(right); VectorNormalize(up); // calculate T coordinate scrolling (start and end texcoord along the beam) t1 = r_refdef.scene.time * -r_lightningbeam_scroll.value;// + beamrepeatscale * DotProduct(start, beamdir); t1 = t1 - (int) t1; t2 = t1 + beamrepeatscale * length; // the beam is 3 polygons in this configuration: // * 2 // * * // 1****** // * * // * 3 // they are showing different portions of the beam texture, creating an // illusion of a beam that appears to curl around in 3D space // (and realize that the whole polygon assembly orients itself to face // the viewer) // polygon 1, verts 0-3 VectorScale(right, r_lightningbeam_thickness.value, offset); R_CalcLightningBeamPolygonVertex3f(vertex3f + 0, start, end, offset); // polygon 2, verts 4-7 VectorAdd(right, up, offset); VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); R_CalcLightningBeamPolygonVertex3f(vertex3f + 12, start, end, offset); // polygon 3, verts 8-11 VectorSubtract(right, up, offset); VectorScale(offset, r_lightningbeam_thickness.value * 0.70710681f, offset); R_CalcLightningBeamPolygonVertex3f(vertex3f + 24, start, end, offset); R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 0, t1, t2); R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 8, t1 + 0.33, t2 + 0.33); R_CalcLightningBeamPolygonTexCoord2f(texcoord2f + 16, t1 + 0.66, t2 + 0.66); // draw the 3 polygons as one batch of 6 triangles using the 12 vertices R_DrawCustomSurface(r_lightningbeam_qmbtexture.integer ? r_lightningbeamqmbtexture : r_lightningbeamtexture, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 12, 0, 6, false, false); } } extern cvar_t cl_beams_polygons; void R_DrawLightningBeams(void) { int i; beam_t *b; if (!cl_beams_polygons.integer) return; beamrepeatscale = 1.0f / r_lightningbeam_repeatdistance.value; for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) { if (b->model && b->lightning) { vec3_t org, start, end, dir; vec_t dist; CL_Beam_CalculatePositions(b, start, end); // calculate the nearest point on the line (beam) for depth sorting VectorSubtract(end, start, dir); dist = (DotProduct(r_refdef.view.origin, dir) - DotProduct(start, dir)) / (DotProduct(end, dir) - DotProduct(start, dir)); dist = bound(0, dist, 1); VectorLerp(start, dist, end, org); // now we have the nearest point on the line, so sort with it R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, org, R_DrawLightningBeam_TransparentCallback, NULL, i, NULL); } } } darkplaces/snd_ogg.h0000664000175000017500000000161513067716222013761 0ustar kalevkalev/* Copyright (C) 2003 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef SND_OGG_H #define SND_OGG_H qboolean OGG_OpenLibrary (void); void OGG_CloseLibrary (void); qboolean OGG_LoadVorbisFile (const char *filename, sfx_t *sfx); #endif darkplaces/sv_user.c0000664000175000017500000010540613067716222014025 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_user.c -- server code for moving users #include "quakedef.h" #include "sv_demo.h" #define DEBUGMOVES 0 static usercmd_t cmd; extern cvar_t sv_autodemo_perclient; /* =============== SV_SetIdealPitch =============== */ #define MAX_FORWARD 6 void SV_SetIdealPitch (void) { prvm_prog_t *prog = SVVM_prog; float angleval, sinval, cosval, step, dir; trace_t tr; vec3_t top, bottom; float z[MAX_FORWARD]; int i, j; int steps; if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND)) return; angleval = PRVM_serveredictvector(host_client->edict, angles)[YAW] * M_PI*2 / 360; sinval = sin(angleval); cosval = cos(angleval); for (i=0 ; iedict, origin)[0] + cosval*(i+3)*12; top[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + sinval*(i+3)*12; top[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, view_ofs)[2]; bottom[0] = top[0]; bottom[1] = top[1]; bottom[2] = top[2] - 160; tr = SV_TraceLine(top, bottom, MOVE_NOMONSTERS, host_client->edict, SUPERCONTENTS_SOLID, 0, collision_extendmovelength.value); // if looking at a wall, leave ideal the way is was if (tr.startsolid) return; // near a dropoff if (tr.fraction == 1) return; z[i] = top[2] + tr.fraction*(bottom[2]-top[2]); } dir = 0; steps = 0; for (j=1 ; j -ON_EPSILON && step < ON_EPSILON) continue; // mixed changes if (dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON ) ) return; steps++; dir = step; } if (!dir) { PRVM_serveredictfloat(host_client->edict, idealpitch) = 0; return; } if (steps < 2) return; PRVM_serveredictfloat(host_client->edict, idealpitch) = -dir * sv_idealpitchscale.value; } static vec3_t wishdir, forward, right, up; static float wishspeed; static qboolean onground; /* ================== SV_UserFriction ================== */ static void SV_UserFriction (void) { prvm_prog_t *prog = SVVM_prog; float speed, newspeed, control, friction; vec3_t start, stop; trace_t trace; speed = sqrt(PRVM_serveredictvector(host_client->edict, velocity)[0]*PRVM_serveredictvector(host_client->edict, velocity)[0]+PRVM_serveredictvector(host_client->edict, velocity)[1]*PRVM_serveredictvector(host_client->edict, velocity)[1]); if (!speed) return; // if the leading edge is over a dropoff, increase friction start[0] = stop[0] = PRVM_serveredictvector(host_client->edict, origin)[0] + PRVM_serveredictvector(host_client->edict, velocity)[0]/speed*16; start[1] = stop[1] = PRVM_serveredictvector(host_client->edict, origin)[1] + PRVM_serveredictvector(host_client->edict, velocity)[1]/speed*16; start[2] = PRVM_serveredictvector(host_client->edict, origin)[2] + PRVM_serveredictvector(host_client->edict, mins)[2]; stop[2] = start[2] - 34; trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, host_client->edict, SV_GenericHitSuperContentsMask(host_client->edict), 0, collision_extendmovelength.value); if (trace.fraction == 1.0) friction = sv_friction.value*sv_edgefriction.value; else friction = sv_friction.value; // apply friction control = speed < sv_stopspeed.value ? sv_stopspeed.value : speed; newspeed = speed - sv.frametime*control*friction; if (newspeed < 0) newspeed = 0; else newspeed /= speed; VectorScale(PRVM_serveredictvector(host_client->edict, velocity), newspeed, PRVM_serveredictvector(host_client->edict, velocity)); } /* ============== SV_Accelerate ============== */ static void SV_Accelerate (void) { prvm_prog_t *prog = SVVM_prog; int i; float addspeed, accelspeed, currentspeed; currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishdir); addspeed = wishspeed - currentspeed; if (addspeed <= 0) return; accelspeed = sv_accelerate.value*sv.frametime*wishspeed; if (accelspeed > addspeed) accelspeed = addspeed; for (i=0 ; i<3 ; i++) PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishdir[i]; } extern cvar_t sv_gameplayfix_q2airaccelerate; static void SV_AirAccelerate (vec3_t wishveloc) { prvm_prog_t *prog = SVVM_prog; int i; float addspeed, wishspd, accelspeed, currentspeed; wishspd = VectorNormalizeLength (wishveloc); if (wishspd > sv_maxairspeed.value) wishspd = sv_maxairspeed.value; currentspeed = DotProduct (PRVM_serveredictvector(host_client->edict, velocity), wishveloc); addspeed = wishspd - currentspeed; if (addspeed <= 0) return; accelspeed = (sv_airaccelerate.value < 0 ? sv_accelerate.value : sv_airaccelerate.value)*(sv_gameplayfix_q2airaccelerate.integer ? wishspd : wishspeed) * sv.frametime; if (accelspeed > addspeed) accelspeed = addspeed; for (i=0 ; i<3 ; i++) PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed*wishveloc[i]; } static void DropPunchAngle (void) { prvm_prog_t *prog = SVVM_prog; vec_t len; vec3_t punchangle, punchvector; VectorCopy(PRVM_serveredictvector(host_client->edict, punchangle), punchangle); VectorCopy(PRVM_serveredictvector(host_client->edict, punchvector), punchvector); len = VectorNormalizeLength(punchangle); if (len > 0) { len -= 10*sv.frametime; if (len < 0) len = 0; VectorScale(punchangle, len, punchangle); } len = VectorNormalizeLength(punchvector); if (len > 0) { len -= 20*sv.frametime; if (len < 0) len = 0; VectorScale(punchvector, len, punchvector); } VectorCopy(punchangle, PRVM_serveredictvector(host_client->edict, punchangle)); VectorCopy(punchvector, PRVM_serveredictvector(host_client->edict, punchvector)); } /* =================== SV_WaterMove =================== */ static void SV_WaterMove (void) { prvm_prog_t *prog = SVVM_prog; int i; vec3_t wishvel, v_angle; vec_t speed, newspeed, fwishspeed, addspeed, accelspeed, temp; // user intentions VectorCopy(PRVM_serveredictvector(host_client->edict, v_angle), v_angle); AngleVectors(v_angle, forward, right, up); for (i=0 ; i<3 ; i++) wishvel[i] = forward[i]*cmd.forwardmove + right[i]*cmd.sidemove; if (!cmd.forwardmove && !cmd.sidemove && !cmd.upmove) wishvel[2] -= 60; // drift towards bottom else wishvel[2] += cmd.upmove; fwishspeed = VectorLength(wishvel); if (fwishspeed > sv_maxspeed.value) { temp = sv_maxspeed.value/fwishspeed; VectorScale (wishvel, temp, wishvel); fwishspeed = sv_maxspeed.value; } fwishspeed *= 0.7; // water friction speed = VectorLength(PRVM_serveredictvector(host_client->edict, velocity)); if (speed) { newspeed = speed - sv.frametime * speed * (sv_waterfriction.value < 0 ? sv_friction.value : sv_waterfriction.value); if (newspeed < 0) newspeed = 0; temp = newspeed/speed; VectorScale(PRVM_serveredictvector(host_client->edict, velocity), temp, PRVM_serveredictvector(host_client->edict, velocity)); } else newspeed = 0; // water acceleration if (!fwishspeed) return; addspeed = fwishspeed - newspeed; if (addspeed <= 0) return; VectorNormalize (wishvel); accelspeed = (sv_wateraccelerate.value < 0 ? sv_accelerate.value : sv_wateraccelerate.value) * fwishspeed * sv.frametime; if (accelspeed > addspeed) accelspeed = addspeed; for (i=0 ; i<3 ; i++) PRVM_serveredictvector(host_client->edict, velocity)[i] += accelspeed * wishvel[i]; } static void SV_WaterJump (void) { prvm_prog_t *prog = SVVM_prog; if (sv.time > PRVM_serveredictfloat(host_client->edict, teleport_time) || !PRVM_serveredictfloat(host_client->edict, waterlevel)) { PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) & ~FL_WATERJUMP; PRVM_serveredictfloat(host_client->edict, teleport_time) = 0; } PRVM_serveredictvector(host_client->edict, velocity)[0] = PRVM_serveredictvector(host_client->edict, movedir)[0]; PRVM_serveredictvector(host_client->edict, velocity)[1] = PRVM_serveredictvector(host_client->edict, movedir)[1]; } /* =================== SV_AirMove =================== */ static void SV_AirMove (void) { prvm_prog_t *prog = SVVM_prog; int i; vec3_t wishvel; float fmove, smove, temp; // LordHavoc: correct quake movement speed bug when looking up/down wishvel[0] = wishvel[2] = 0; wishvel[1] = PRVM_serveredictvector(host_client->edict, angles)[1]; AngleVectors (wishvel, forward, right, up); fmove = cmd.forwardmove; smove = cmd.sidemove; // hack to not let you back into teleporter if (sv.time < PRVM_serveredictfloat(host_client->edict, teleport_time) && fmove < 0) fmove = 0; for (i=0 ; i<3 ; i++) wishvel[i] = forward[i]*fmove + right[i]*smove; if ((int)PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_WALK) wishvel[2] += cmd.upmove; VectorCopy (wishvel, wishdir); wishspeed = VectorNormalizeLength(wishdir); if (wishspeed > sv_maxspeed.value) { temp = sv_maxspeed.value/wishspeed; VectorScale (wishvel, temp, wishvel); wishspeed = sv_maxspeed.value; } if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP) { // noclip VectorCopy (wishvel, PRVM_serveredictvector(host_client->edict, velocity)); } else if (onground) { SV_UserFriction (); SV_Accelerate (); } else { // not on ground, so little effect on velocity SV_AirAccelerate (wishvel); } } /* =================== SV_ClientThink the move fields specify an intended velocity in pix/sec the angle fields specify an exact angular motion in degrees =================== */ void SV_ClientThink (void) { prvm_prog_t *prog = SVVM_prog; vec3_t v_angle, angles, velocity; //Con_Printf("clientthink for %ims\n", (int) (sv.frametime * 1000)); SV_ApplyClientMove(); // make sure the velocity is sane (not a NaN) SV_CheckVelocity(host_client->edict); // LordHavoc: QuakeC replacement for SV_ClientThink (player movement) if (PRVM_serverfunction(SV_PlayerPhysics) && sv_playerphysicsqc.integer) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PlayerPhysics), "QC function SV_PlayerPhysics is missing"); SV_CheckVelocity(host_client->edict); return; } if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NONE) return; onground = ((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_ONGROUND) != 0; DropPunchAngle (); // if dead, behave differently if (PRVM_serveredictfloat(host_client->edict, health) <= 0) return; cmd = host_client->cmd; // angles // show 1/3 the pitch angle and all the roll angle VectorAdd (PRVM_serveredictvector(host_client->edict, v_angle), PRVM_serveredictvector(host_client->edict, punchangle), v_angle); VectorCopy(PRVM_serveredictvector(host_client->edict, angles), angles); VectorCopy(PRVM_serveredictvector(host_client->edict, velocity), velocity); PRVM_serveredictvector(host_client->edict, angles)[ROLL] = V_CalcRoll (angles, velocity)*4; if (!PRVM_serveredictfloat(host_client->edict, fixangle)) { PRVM_serveredictvector(host_client->edict, angles)[PITCH] = -v_angle[PITCH]/3; PRVM_serveredictvector(host_client->edict, angles)[YAW] = v_angle[YAW]; } if ( (int)PRVM_serveredictfloat(host_client->edict, flags) & FL_WATERJUMP ) { SV_WaterJump (); SV_CheckVelocity(host_client->edict); return; } /* // Player is (somehow) outside of the map, or flying, or noclipping if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP && (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict))) //if (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_NOCLIP || PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_FLY || SV_TestEntityPosition (host_client->edict)) { SV_FreeMove (); return; } */ // walk if ((PRVM_serveredictfloat(host_client->edict, waterlevel) >= 2) && (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP)) { SV_WaterMove (); SV_CheckVelocity(host_client->edict); return; } SV_AirMove (); SV_CheckVelocity(host_client->edict); } /* =================== SV_ReadClientMove =================== */ int sv_numreadmoves = 0; usercmd_t sv_readmoves[CL_MAX_USERCMDS]; static void SV_ReadClientMove (void) { prvm_prog_t *prog = SVVM_prog; int i; usercmd_t newmove; usercmd_t *move = &newmove; memset(move, 0, sizeof(*move)); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); // read ping time if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) move->sequence = MSG_ReadLong(&sv_message); move->time = move->clienttime = MSG_ReadFloat(&sv_message); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); move->receivetime = (float)sv.time; #if DEBUGMOVES Con_Printf("%s move%i #%u %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", move->time > move->receivetime ? "^3read future" : "^4read normal", sv_numreadmoves + 1, move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); #endif // limit reported time to current time // (incase the client is trying to cheat) move->time = min(move->time, move->receivetime + sv.frametime); // read current angles for (i = 0;i < 3;i++) { if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) move->viewangles[i] = MSG_ReadAngle8i(&sv_message); else if (sv.protocol == PROTOCOL_DARKPLACES1) move->viewangles[i] = MSG_ReadAngle16i(&sv_message); else if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) move->viewangles[i] = MSG_ReadAngle32f(&sv_message); else move->viewangles[i] = MSG_ReadAngle16i(&sv_message); } if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); // read movement move->forwardmove = MSG_ReadCoord16i(&sv_message); move->sidemove = MSG_ReadCoord16i(&sv_message); move->upmove = MSG_ReadCoord16i(&sv_message); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); // read buttons // be sure to bitwise OR them into the move->buttons because we want to // accumulate button presses from multiple packets per actual move if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) move->buttons = MSG_ReadByte(&sv_message); else move->buttons = MSG_ReadLong(&sv_message); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); // read impulse move->impulse = MSG_ReadByte(&sv_message); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); // PRYDON_CLIENTCURSOR if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) { // 30 bytes move->cursor_screen[0] = MSG_ReadShort(&sv_message) * (1.0f / 32767.0f); move->cursor_screen[1] = MSG_ReadShort(&sv_message) * (1.0f / 32767.0f); move->cursor_start[0] = MSG_ReadFloat(&sv_message); move->cursor_start[1] = MSG_ReadFloat(&sv_message); move->cursor_start[2] = MSG_ReadFloat(&sv_message); move->cursor_impact[0] = MSG_ReadFloat(&sv_message); move->cursor_impact[1] = MSG_ReadFloat(&sv_message); move->cursor_impact[2] = MSG_ReadFloat(&sv_message); move->cursor_entitynumber = (unsigned short)MSG_ReadShort(&sv_message); if (move->cursor_entitynumber >= prog->max_edicts) { Con_DPrintf("SV_ReadClientMessage: client send bad cursor_entitynumber\n"); move->cursor_entitynumber = 0; } // as requested by FrikaC, cursor_trace_ent is reset to world if the // entity is free at time of receipt if (PRVM_EDICT_NUM(move->cursor_entitynumber)->priv.server->free) move->cursor_entitynumber = 0; if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); } // if the previous move has not been applied yet, we need to accumulate // the impulse/buttons from it if (!host_client->cmd.applied) { if (!move->impulse) move->impulse = host_client->cmd.impulse; move->buttons |= host_client->cmd.buttons; } // now store this move for later execution // (we have to buffer the moves because of old ones being repeated) if (sv_numreadmoves < CL_MAX_USERCMDS) sv_readmoves[sv_numreadmoves++] = *move; // movement packet loss tracking if(move->sequence) { if(move->sequence > host_client->movement_highestsequence_seen) { if(host_client->movement_highestsequence_seen) { // mark moves in between as lost unsigned int delta = move->sequence - host_client->movement_highestsequence_seen - 1; if(delta < NETGRAPH_PACKETS) { unsigned int u; for(u = 0; u < delta; ++u) host_client->movement_count[(host_client->movement_highestsequence_seen + 1 + u) % NETGRAPH_PACKETS] = -1; } else memset(host_client->movement_count, -1, sizeof(host_client->movement_count)); } // mark THIS move as seen for the first time host_client->movement_count[move->sequence % NETGRAPH_PACKETS] = 1; // update highest sequence seen host_client->movement_highestsequence_seen = move->sequence; } else if(host_client->movement_count[move->sequence % NETGRAPH_PACKETS] >= 0) ++host_client->movement_count[move->sequence % NETGRAPH_PACKETS]; } else { host_client->movement_highestsequence_seen = 0; memset(host_client->movement_count, 0, sizeof(host_client->movement_count)); } } static void SV_ExecuteClientMoves(void) { prvm_prog_t *prog = SVVM_prog; int moveindex; float moveframetime; double oldframetime; double oldframetime2; #ifdef NUM_PING_TIMES double total; #endif if (sv_numreadmoves < 1) return; // only start accepting input once the player is spawned if (!host_client->begun) return; #if DEBUGMOVES Con_Printf("SV_ExecuteClientMoves: read %i moves at sv.time %f\n", sv_numreadmoves, (float)sv.time); #endif // disable clientside movement prediction in some cases if (ceil(max(sv_readmoves[sv_numreadmoves-1].receivetime - sv_readmoves[sv_numreadmoves-1].time, 0) * 1000.0) < sv_clmovement_minping.integer) host_client->clmovement_disabletimeout = realtime + sv_clmovement_minping_disabletime.value / 1000.0; // several conditions govern whether clientside movement prediction is allowed if (sv_readmoves[sv_numreadmoves-1].sequence && sv_clmovement_enable.integer && sv_clmovement_inputtimeout.value > 0 && host_client->clmovement_disabletimeout <= realtime && (PRVM_serveredictfloat(host_client->edict, disableclientprediction) == -1 || (PRVM_serveredictfloat(host_client->edict, movetype) == MOVETYPE_WALK && (!PRVM_serveredictfloat(host_client->edict, disableclientprediction))))) { // process the moves in order and ignore old ones // but always trust the latest move // (this deals with bogus initial move sequences after level change, // where the client will eventually catch up with the level change // and reset its move sequence) for (moveindex = 0;moveindex < sv_numreadmoves;moveindex++) { usercmd_t *move = sv_readmoves + moveindex; if (host_client->movesequence < move->sequence || moveindex == sv_numreadmoves - 1) { #if DEBUGMOVES Con_Printf("%smove #%u %ims (%ims) %i %i '%i %i %i' '%i %i %i'\n", (move->time - host_client->cmd.time) > sv.frametime * 1.01 ? "^1" : "^2", move->sequence, (int)floor((move->time - host_client->cmd.time) * 1000.0 + 0.5), (int)floor(move->time * 1000.0 + 0.5), move->impulse, move->buttons, (int)move->viewangles[0], (int)move->viewangles[1], (int)move->viewangles[2], (int)move->forwardmove, (int)move->sidemove, (int)move->upmove); #endif // this is a new move move->time = bound(sv.time - 1, move->time, sv.time); // prevent slowhack/speedhack combos move->time = max(move->time, host_client->cmd.time); // prevent backstepping of time moveframetime = bound(0, move->time - host_client->cmd.time, min(0.1, sv_clmovement_inputtimeout.value)); // discard (treat like lost) moves with too low distance from // the previous one to prevent hacks using float inaccuracy // clients will see this as packet loss in the netgraph // this should also apply if a move cannot get // executed because it came too late and // already was performed serverside if(moveframetime < 0.0005) { // count the move as LOST if we don't // execute it but it has higher // sequence count if(host_client->movesequence) if(move->sequence > host_client->movesequence) host_client->movement_count[(move->sequence) % NETGRAPH_PACKETS] = -1; continue; } //Con_Printf("movesequence = %i (%i lost), moveframetime = %f\n", move->sequence, move->sequence ? move->sequence - host_client->movesequence - 1 : 0, moveframetime); host_client->cmd = *move; host_client->movesequence = move->sequence; // if using prediction, we need to perform moves when packets are // received, even if multiple occur in one frame // (they can't go beyond the current time so there is no cheat issue // with this approach, and if they don't send input for a while they // start moving anyway, so the longest 'lagaport' possible is // determined by the sv_clmovement_inputtimeout cvar) if (moveframetime <= 0) continue; oldframetime = PRVM_serverglobalfloat(frametime); oldframetime2 = sv.frametime; // update ping time for qc to see while executing this move host_client->ping = host_client->cmd.receivetime - host_client->cmd.time; // the server and qc frametime values must be changed temporarily PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime; // if move is more than 50ms, split it into two moves (this matches QWSV behavior and the client prediction) if (sv.frametime > 0.05) { PRVM_serverglobalfloat(frametime) = sv.frametime = moveframetime * 0.5f; SV_Physics_ClientMove(); } SV_Physics_ClientMove(); sv.frametime = oldframetime2; PRVM_serverglobalfloat(frametime) = oldframetime; host_client->clmovement_inputtimeout = sv_clmovement_inputtimeout.value; } } } else { // try to gather button bits from old moves, but only if their time is // advancing (ones with the same timestamp can't be trusted) for (moveindex = 0;moveindex < sv_numreadmoves-1;moveindex++) { usercmd_t *move = sv_readmoves + moveindex; if (host_client->cmd.time < move->time) { sv_readmoves[sv_numreadmoves-1].buttons |= move->buttons; if (move->impulse) sv_readmoves[sv_numreadmoves-1].impulse = move->impulse; } } // now copy the new move host_client->cmd = sv_readmoves[sv_numreadmoves-1]; host_client->cmd.time = max(host_client->cmd.time, sv.time); // physics will run up to sv.time, so allow no predicted moves // before that otherwise, there is a speedhack by turning // prediction on and off repeatedly on client side because the // engine would run BOTH client and server physics for the same // time host_client->movesequence = 0; // make sure that normal physics takes over immediately host_client->clmovement_inputtimeout = 0; } // calculate average ping time host_client->ping = host_client->cmd.receivetime - host_client->cmd.clienttime; #ifdef NUM_PING_TIMES host_client->ping_times[host_client->num_pings % NUM_PING_TIMES] = host_client->cmd.receivetime - host_client->cmd.clienttime; host_client->num_pings++; for (i=0, total = 0;i < NUM_PING_TIMES;i++) total += host_client->ping_times[i]; host_client->ping = total / NUM_PING_TIMES; #endif } void SV_ApplyClientMove (void) { prvm_prog_t *prog = SVVM_prog; usercmd_t *move = &host_client->cmd; int j, movementloss, packetloss; if (!move->receivetime) return; // note: a move can be applied multiple times if the client packets are // not coming as often as the physics is executed, and the move must be // applied before running qc each time because the id1 qc had a bug where // it clears self.button2 in PlayerJump, causing pogostick behavior if // moves are not applied every time before calling qc move->applied = true; // set the edict fields PRVM_serveredictfloat(host_client->edict, button0) = move->buttons & 1; PRVM_serveredictfloat(host_client->edict, button2) = (move->buttons & 2)>>1; if (move->impulse) PRVM_serveredictfloat(host_client->edict, impulse) = move->impulse; // only send the impulse to qc once move->impulse = 0; movementloss = packetloss = 0; if(host_client->netconnection) { for (j = 0;j < NETGRAPH_PACKETS;j++) if (host_client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) packetloss++; for (j = 0;j < NETGRAPH_PACKETS;j++) if (host_client->movement_count[j] < 0) movementloss++; } VectorCopy(move->viewangles, PRVM_serveredictvector(host_client->edict, v_angle)); PRVM_serveredictfloat(host_client->edict, button3) = ((move->buttons >> 2) & 1); PRVM_serveredictfloat(host_client->edict, button4) = ((move->buttons >> 3) & 1); PRVM_serveredictfloat(host_client->edict, button5) = ((move->buttons >> 4) & 1); PRVM_serveredictfloat(host_client->edict, button6) = ((move->buttons >> 5) & 1); PRVM_serveredictfloat(host_client->edict, button7) = ((move->buttons >> 6) & 1); PRVM_serveredictfloat(host_client->edict, button8) = ((move->buttons >> 7) & 1); PRVM_serveredictfloat(host_client->edict, button9) = ((move->buttons >> 11) & 1); PRVM_serveredictfloat(host_client->edict, button10) = ((move->buttons >> 12) & 1); PRVM_serveredictfloat(host_client->edict, button11) = ((move->buttons >> 13) & 1); PRVM_serveredictfloat(host_client->edict, button12) = ((move->buttons >> 14) & 1); PRVM_serveredictfloat(host_client->edict, button13) = ((move->buttons >> 15) & 1); PRVM_serveredictfloat(host_client->edict, button14) = ((move->buttons >> 16) & 1); PRVM_serveredictfloat(host_client->edict, button15) = ((move->buttons >> 17) & 1); PRVM_serveredictfloat(host_client->edict, button16) = ((move->buttons >> 18) & 1); PRVM_serveredictfloat(host_client->edict, buttonuse) = ((move->buttons >> 8) & 1); PRVM_serveredictfloat(host_client->edict, buttonchat) = ((move->buttons >> 9) & 1); PRVM_serveredictfloat(host_client->edict, cursor_active) = ((move->buttons >> 10) & 1); VectorSet(PRVM_serveredictvector(host_client->edict, movement), move->forwardmove, move->sidemove, move->upmove); VectorCopy(move->cursor_screen, PRVM_serveredictvector(host_client->edict, cursor_screen)); VectorCopy(move->cursor_start, PRVM_serveredictvector(host_client->edict, cursor_trace_start)); VectorCopy(move->cursor_impact, PRVM_serveredictvector(host_client->edict, cursor_trace_endpos)); PRVM_serveredictedict(host_client->edict, cursor_trace_ent) = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM(move->cursor_entitynumber)); PRVM_serveredictfloat(host_client->edict, ping) = host_client->ping * 1000.0; PRVM_serveredictfloat(host_client->edict, ping_packetloss) = packetloss / (float) NETGRAPH_PACKETS; PRVM_serveredictfloat(host_client->edict, ping_movementloss) = movementloss / (float) NETGRAPH_PACKETS; } static qboolean SV_FrameLost(int framenum) { if (host_client->entitydatabase5) { if (framenum <= host_client->entitydatabase5->latestframenum) { EntityFrame5_LostFrame(host_client->entitydatabase5, framenum); EntityFrameCSQC_LostFrame(host_client, framenum); return true; } } return false; } static void SV_FrameAck(int framenum) { if (host_client->entitydatabase) EntityFrame_AckFrame(host_client->entitydatabase, framenum); else if (host_client->entitydatabase4) EntityFrame4_AckFrame(host_client->entitydatabase4, framenum, true); else if (host_client->entitydatabase5) EntityFrame5_AckFrame(host_client->entitydatabase5, framenum); } /* =================== SV_ReadClientMessage =================== */ void SV_ReadClientMessage(void) { prvm_prog_t *prog = SVVM_prog; int netcmd, num, start; char *s, *p, *q; if(sv_autodemo_perclient.integer >= 2) SV_WriteDemoMessage(host_client, &(host_client->netconnection->message), true); //MSG_BeginReading (); sv_numreadmoves = 0; for(;;) { if (!host_client->active) { // a command caused an error SV_DropClient (false); return; } if (sv_message.badread) { Con_Print("SV_ReadClientMessage: badread\n"); SV_DropClient (false); return; } netcmd = MSG_ReadByte(&sv_message); if (netcmd == -1) { // end of message // apply the moves that were read this frame SV_ExecuteClientMoves(); break; } switch (netcmd) { default: Con_Printf("SV_ReadClientMessage: unknown command char %i (at offset 0x%x)\n", netcmd, sv_message.readcount); if (developer_networking.integer) Com_HexDumpToConsole(sv_message.data, sv_message.cursize); SV_DropClient (false); return; case clc_nop: break; case clc_stringcmd: // allow reliable messages now as the client is done with initial loading if (host_client->sendsignon == 2) host_client->sendsignon = 0; s = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); q = NULL; for(p = s; *p; ++p) switch(*p) { case 10: case 13: if(!q) q = p; break; default: if(q) goto clc_stringcmd_invalid; // newline seen, THEN something else -> possible exploit break; } if(q) *q = 0; if (strncasecmp(s, "spawn", 5) == 0 || strncasecmp(s, "begin", 5) == 0 || strncasecmp(s, "prespawn", 8) == 0) Cmd_ExecuteString (s, src_client, true); else if (PRVM_serverfunction(SV_ParseClientCommand)) { int restorevm_tempstringsbuf_cursize; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ParseClientCommand), "QC function SV_ParseClientCommand is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } else Cmd_ExecuteString (s, src_client, true); break; clc_stringcmd_invalid: Con_Printf("Received invalid stringcmd from %s\n", host_client->name); if(developer.integer > 0) Com_HexDumpToConsole((unsigned char *) s, (int)strlen(s)); break; case clc_disconnect: SV_DropClient (false); // client wants to disconnect return; case clc_move: SV_ReadClientMove(); break; case clc_ackdownloaddata: start = MSG_ReadLong(&sv_message); num = MSG_ReadShort(&sv_message); if (host_client->download_file && host_client->download_started) { if (host_client->download_expectedposition == start) { int size = (int)FS_FileSize(host_client->download_file); // a data block was successfully received by the client, // update the expected position on the next data block host_client->download_expectedposition = start + num; // if this was the last data block of the file, it's done if (host_client->download_expectedposition >= FS_FileSize(host_client->download_file)) { // tell the client that the download finished // we need to calculate the crc now // // note: at this point the OS probably has the file // entirely in memory, so this is a faster operation // now than it was when the download started. // // it is also preferable to do this at the end of the // download rather than the start because it reduces // potential for Denial Of Service attacks against the // server. int crc; unsigned char *temp; FS_Seek(host_client->download_file, 0, SEEK_SET); temp = (unsigned char *) Mem_Alloc(tempmempool, size); FS_Read(host_client->download_file, temp, size); crc = CRC_Block(temp, size); Mem_Free(temp); // calculated crc, send the file info to the client // (so that it can verify the data) Host_ClientCommands("\ncl_downloadfinished %i %i %s\n", size, crc, host_client->download_name); Con_DPrintf("Download of %s by %s has finished\n", host_client->download_name, host_client->name); FS_Close(host_client->download_file); host_client->download_file = NULL; host_client->download_name[0] = 0; host_client->download_expectedposition = 0; host_client->download_started = false; } } else { // a data block was lost, reset to the expected position // and resume sending from there FS_Seek(host_client->download_file, host_client->download_expectedposition, SEEK_SET); } } break; case clc_ackframe: if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); num = MSG_ReadLong(&sv_message); if (sv_message.badread) Con_Printf("SV_ReadClientMessage: badread at %s:%i\n", __FILE__, __LINE__); if (developer_networkentities.integer >= 10) Con_Printf("recv clc_ackframe %i\n", num); // if the client hasn't progressed through signons yet, // ignore any clc_ackframes we get (they're probably from the // previous level) if (host_client->begun && host_client->latestframenum < num) { int i; for (i = host_client->latestframenum + 1;i < num;i++) if (!SV_FrameLost(i)) break; SV_FrameAck(num); host_client->latestframenum = num; } break; } } } darkplaces/cd_linux.c0000664000175000017500000001134413067716216014144 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All // rights reserved. // suggested by Zero_Dogg to fix a compile problem on Mandriva Linux #include "quakedef.h" #include #include #include #include #include #include #include "cdaudio.h" static int cdfile = -1; static char cd_dev[64] = "/dev/cdrom"; void CDAudio_SysEject (void) { if (cdfile == -1) return; if (ioctl(cdfile, CDROMEJECT) == -1) Con_Print("ioctl CDROMEJECT failed\n"); } void CDAudio_SysCloseDoor (void) { if (cdfile == -1) return; if (ioctl(cdfile, CDROMCLOSETRAY) == -1) Con_Print("ioctl CDROMCLOSETRAY failed\n"); } int CDAudio_SysGetAudioDiskInfo (void) { struct cdrom_tochdr tochdr; if (cdfile == -1) return -1; if (ioctl(cdfile, CDROMREADTOCHDR, &tochdr) == -1) { Con_Print("ioctl CDROMREADTOCHDR failed\n"); return -1; } if (tochdr.cdth_trk0 < 1) { Con_Print("CDAudio: no music tracks\n"); return -1; } return tochdr.cdth_trk1; } float CDAudio_SysGetVolume (void) { struct cdrom_volctrl vol; if (cdfile == -1) return -1.0f; if (ioctl (cdfile, CDROMVOLREAD, &vol) == -1) { Con_Print("ioctl CDROMVOLREAD failed\n"); return -1.0f; } return (vol.channel0 + vol.channel1) / 2.0f / 255.0f; } void CDAudio_SysSetVolume (float volume) { struct cdrom_volctrl vol; if (cdfile == -1) return; vol.channel0 = vol.channel1 = (__u8)(volume * 255); vol.channel2 = vol.channel3 = 0; if (ioctl (cdfile, CDROMVOLCTRL, &vol) == -1) Con_Print("ioctl CDROMVOLCTRL failed\n"); } int CDAudio_SysPlay (int track) { struct cdrom_tocentry entry; struct cdrom_ti ti; if (cdfile == -1) return -1; // don't try to play a non-audio track entry.cdte_track = track; entry.cdte_format = CDROM_MSF; if (ioctl(cdfile, CDROMREADTOCENTRY, &entry) == -1) { Con_Print("ioctl CDROMREADTOCENTRY failed\n"); return -1; } if (entry.cdte_ctrl == CDROM_DATA_TRACK) { Con_Printf("CDAudio: track %i is not audio\n", track); return -1; } if (cdPlaying) CDAudio_Stop(); ti.cdti_trk0 = track; ti.cdti_trk1 = track; ti.cdti_ind0 = 1; ti.cdti_ind1 = 99; if (ioctl(cdfile, CDROMPLAYTRKIND, &ti) == -1) { Con_Print("ioctl CDROMPLAYTRKIND failed\n"); return -1; } if (ioctl(cdfile, CDROMRESUME) == -1) { Con_Print("ioctl CDROMRESUME failed\n"); return -1; } return 0; } int CDAudio_SysStop (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDROMSTOP) == -1) { Con_Printf("ioctl CDROMSTOP failed (%d)\n", errno); return -1; } return 0; } int CDAudio_SysPause (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDROMPAUSE) == -1) { Con_Print("ioctl CDROMPAUSE failed\n"); return -1; } return 0; } int CDAudio_SysResume (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDROMRESUME) == -1) Con_Print("ioctl CDROMRESUME failed\n"); return 0; } int CDAudio_SysUpdate (void) { struct cdrom_subchnl subchnl; static time_t lastchk = 0; if (cdPlaying && lastchk < time(NULL) && cdfile != -1) { lastchk = time(NULL) + 2; //two seconds between chks subchnl.cdsc_format = CDROM_MSF; if (ioctl(cdfile, CDROMSUBCHNL, &subchnl) == -1) { Con_Print("ioctl CDROMSUBCHNL failed\n"); cdPlaying = false; return -1; } if (subchnl.cdsc_audiostatus != CDROM_AUDIO_PLAY && subchnl.cdsc_audiostatus != CDROM_AUDIO_PAUSED) { cdPlaying = false; if (cdPlayLooping) CDAudio_Play(cdPlayTrack, true); } else cdPlayTrack = subchnl.cdsc_trk; } return 0; } void CDAudio_SysInit (void) { int i; // COMMANDLINEOPTION: Linux Sound: -cddev chooses which CD drive to use if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) strlcpy(cd_dev, com_argv[i + 1], sizeof(cd_dev)); } int CDAudio_SysStartup (void) { if ((cdfile = open(cd_dev, O_RDONLY | O_NONBLOCK)) == -1) { Con_Printf("CDAudio_SysStartup: open of \"%s\" failed (%i)\n", cd_dev, errno); cdfile = -1; return -1; } return 0; } void CDAudio_SysShutdown (void) { close(cdfile); cdfile = -1; } darkplaces/vs2010_win32.props0000664000175000017500000000130213067716222015223 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL-1.2\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;C:\dev\SDL-1.2\lib\x86;$(LibraryPath) <_PropertySheetDisplayName>vs2010_win32 darkplaces/vs2012_win64.props0000664000175000017500000000130213067716222015232 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL-1.2\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x64;C:\dev\SDL-1.2\lib\x64;$(LibraryPath) <_PropertySheetDisplayName>vs2012_win64 darkplaces/curves.c0000664000175000017500000003414713067716216013654 0ustar kalevkalev /* this code written by Forest Hale, on 2004-10-17, and placed into public domain this implements Quadratic BSpline surfaces as seen in Quake3 by id Software a small rant on misuse of the name 'bezier': many people seem to think that bezier is a generic term for splines, but it is not, it is a term for a specific type of bspline (4 control points, cubic bspline), bsplines are the generalization of the bezier spline to support dimensions other than cubic. example equations for 1-5 control point bsplines being sampled as t=0...1 1: flat (0th dimension) o = a 2: linear (1st dimension) o = a * (1 - t) + b * t 3: quadratic bspline (2nd dimension) o = a * (1 - t) * (1 - t) + 2 * b * (1 - t) * t + c * t * t 4: cubic (bezier) bspline (3rd dimension) o = a * (1 - t) * (1 - t) * (1 - t) + 3 * b * (1 - t) * (1 - t) * t + 3 * c * (1 - t) * t * t + d * t * t * t 5: quartic bspline (4th dimension) o = a * (1 - t) * (1 - t) * (1 - t) * (1 - t) + 4 * b * (1 - t) * (1 - t) * (1 - t) * t + 6 * c * (1 - t) * (1 - t) * t * t + 4 * d * (1 - t) * t * t * t + e * t * t * t * t arbitrary dimension bspline double factorial(int n) { int i; double f; f = 1; for (i = 1;i < n;i++) f = f * i; return f; } double bsplinesample(int dimensions, double t, double *param) { double o = 0; for (i = 0;i < dimensions + 1;i++) o += param[i] * factorial(dimensions)/(factorial(i)*factorial(dimensions-i)) * pow(t, i) * pow(1 - t, dimensions - i); return o; } */ #include "quakedef.h" #include "mathlib.h" #include #include "curves.h" // Calculate number of resulting vertex rows/columns by given patch size and tesselation factor // tess=0 means that we reduce detalization of base 3x3 patches by removing middle row and column of vertices // "DimForTess" is "DIMension FOR TESSelation factor" // NB: tess=0 actually means that tess must be 0.5, but obviously it can't because it is of int type. (so "a*tess"-like code is replaced by "a/2" if tess=0) int Q3PatchDimForTess(int size, int tess) { if (tess > 0) return (size - 1) * tess + 1; else if (tess == 0) return (size - 1) / 2 + 1; else return 0; // Maybe warn about wrong tess here? } // usage: // to expand a 5x5 patch to 21x21 vertices (4x4 tesselation), one might use this call: // Q3PatchSubdivideFloat(3, sizeof(float[3]), outvertices, 5, 5, sizeof(float[3]), patchvertices, 4, 4); void Q3PatchTesselateFloat(int numcomponents, int outputstride, float *outputvertices, int patchwidth, int patchheight, int inputstride, float *patchvertices, int tesselationwidth, int tesselationheight) { int k, l, x, y, component, outputwidth = Q3PatchDimForTess(patchwidth, tesselationwidth); float px, py, *v, a, b, c, *cp[3][3], temp[3][64]; int xmax = max(1, 2*tesselationwidth); int ymax = max(1, 2*tesselationheight); // iterate over the individual 3x3 quadratic spline surfaces one at a time // expanding them to fill the output array (with some overlap to ensure // the edges are filled) for (k = 0;k < patchheight-1;k += 2) { for (l = 0;l < patchwidth-1;l += 2) { // set up control point pointers for quicker lookup later for (y = 0;y < 3;y++) for (x = 0;x < 3;x++) cp[y][x] = (float *)((unsigned char *)patchvertices + ((k+y)*patchwidth+(l+x)) * inputstride); // for each row... for (y = 0;y <= ymax;y++) { // calculate control points for this row by collapsing the 3 // rows of control points to one row using py py = (float)y / (float)ymax; // calculate quadratic spline weights for py a = ((1.0f - py) * (1.0f - py)); b = ((1.0f - py) * (2.0f * py)); c = (( py) * ( py)); for (component = 0;component < numcomponents;component++) { temp[0][component] = cp[0][0][component] * a + cp[1][0][component] * b + cp[2][0][component] * c; temp[1][component] = cp[0][1][component] * a + cp[1][1][component] * b + cp[2][1][component] * c; temp[2][component] = cp[0][2][component] * a + cp[1][2][component] * b + cp[2][2][component] * c; } // fetch a pointer to the beginning of the output vertex row v = (float *)((unsigned char *)outputvertices + ((k * ymax / 2 + y) * outputwidth + l * xmax / 2) * outputstride); // for each column of the row... for (x = 0;x <= xmax;x++) { // calculate point based on the row control points px = (float)x / (float)xmax; // calculate quadratic spline weights for px // (could be precalculated) a = ((1.0f - px) * (1.0f - px)); b = ((1.0f - px) * (2.0f * px)); c = (( px) * ( px)); for (component = 0;component < numcomponents;component++) v[component] = temp[0][component] * a + temp[1][component] * b + temp[2][component] * c; // advance to next output vertex using outputstride // (the next vertex may not be directly following this // one, as this may be part of a larger structure) v = (float *)((unsigned char *)v + outputstride); } } } } #if 0 // enable this if you want results printed out printf("vertices[%i][%i] =\n{\n", (patchheight-1)*tesselationheight+1, (patchwidth-1)*tesselationwidth+1); for (y = 0;y < (patchheight-1)*tesselationheight+1;y++) { for (x = 0;x < (patchwidth-1)*tesselationwidth+1;x++) { printf("("); for (component = 0;component < numcomponents;component++) printf("%f ", outputvertices[(y*((patchwidth-1)*tesselationwidth+1)+x)*numcomponents+component]); printf(") "); } printf("\n"); } printf("}\n"); #endif } static int Q3PatchTesselation(float largestsquared3xcurvearea, float tolerance) { float f; // f is actually a squared 2x curve area... so the formula had to be adjusted to give roughly the same subdivisions f = pow(largestsquared3xcurvearea / 64.0f, 0.25f) / tolerance; //if(f < 0.25) // VERY flat patches if(f < 0.0001) // TOTALLY flat patches return 0; else if(f < 2) return 1; else return (int) floor(log(f) / log(2.0f)) + 1; // this is always at least 2 // maps [0.25..0.5[ to -1 (actually, 1 is returned) // maps [0.5..1[ to 0 (actually, 1 is returned) // maps [1..2[ to 1 // maps [2..4[ to 2 // maps [4..8[ to 4 } static float Squared3xCurveArea(const float *a, const float *control, const float *b, int components) { #if 0 // mimicing the old behaviour with the new code... float deviation; float quartercurvearea = 0; int c; for (c = 0;c < components;c++) { deviation = control[c] * 0.5f - a[c] * 0.25f - b[c] * 0.25f; quartercurvearea += deviation*deviation; } // But as the new code now works on the squared 2x curve area, let's scale the value return quartercurvearea * quartercurvearea * 64.0; #else // ideally, we'd like the area between the spline a->control->b and the line a->b. // but as this is hard to calculate, let's calculate an upper bound of it: // the area of the triangle a->control->b->a. // // one can prove that the area of a quadratic spline = 2/3 * the area of // the triangle of its control points! // to do it, first prove it for the spline through (0,0), (1,1), (2,0) // (which is a parabola) and then note that moving the control point // left/right is just shearing and keeps the area of both the spline and // the triangle invariant. // // why are we going for the spline area anyway? // we know that: // // the area between the spline and the line a->b is a measure of the // error of approximation of the spline by the line. // // also, on circle-like or parabola-like curves, you easily get that the // double amount of line approximation segments reduces the error to its quarter // (also, easy to prove for splines by doing it for one specific one, and using // affine transforms to get all other splines) // // so... // // let's calculate the area! but we have to avoid the cross product, as // components is not necessarily 3 // // the area of a triangle spanned by vectors a and b is // // 0.5 * |a| |b| sin gamma // // now, cos gamma is // // a.b / (|a| |b|) // // so the area is // // 0.5 * sqrt(|a|^2 |b|^2 - (a.b)^2) int c; float aa = 0, bb = 0, ab = 0; for (c = 0;c < components;c++) { float xa = a[c] - control[c]; float xb = b[c] - control[c]; aa += xa * xa; ab += xa * xb; bb += xb * xb; } // area is 0.5 * sqrt(aa*bb - ab*ab) // 2x TRIANGLE area is sqrt(aa*bb - ab*ab) // 3x CURVE area is sqrt(aa*bb - ab*ab) return aa * bb - ab * ab; #endif } // returns how much tesselation of each segment is needed to remain under tolerance int Q3PatchTesselationOnX(int patchwidth, int patchheight, int components, const float *in, float tolerance) { int x, y; const float *patch; float squared3xcurvearea, largestsquared3xcurvearea; largestsquared3xcurvearea = 0; for (y = 0;y < patchheight;y++) { for (x = 0;x < patchwidth-1;x += 2) { patch = in + ((y * patchwidth) + x) * components; squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[components], &patch[2*components], components); if (largestsquared3xcurvearea < squared3xcurvearea) largestsquared3xcurvearea = squared3xcurvearea; } } return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); } // returns how much tesselation of each segment is needed to remain under tolerance int Q3PatchTesselationOnY(int patchwidth, int patchheight, int components, const float *in, float tolerance) { int x, y; const float *patch; float squared3xcurvearea, largestsquared3xcurvearea; largestsquared3xcurvearea = 0; for (y = 0;y < patchheight-1;y += 2) { for (x = 0;x < patchwidth;x++) { patch = in + ((y * patchwidth) + x) * components; squared3xcurvearea = Squared3xCurveArea(&patch[0], &patch[patchwidth*components], &patch[2*patchwidth*components], components); if (largestsquared3xcurvearea < squared3xcurvearea) largestsquared3xcurvearea = squared3xcurvearea; } } return Q3PatchTesselation(largestsquared3xcurvearea, tolerance); } // Find an equal vertex in array. Check only vertices with odd X and Y static int FindEqualOddVertexInArray(int numcomponents, float *vertex, float *vertices, int width, int height) { int x, y, j; for (y=0; y 0.05) // div0: this is notably smaller than the smallest radiant grid // but large enough so we don't need to get scared of roundoff // errors { found = false; break; } if(found) return y*width+x; vertices += numcomponents*2; } vertices += numcomponents*(width-1); } return -1; } #define SIDE_INVALID -1 #define SIDE_X 0 #define SIDE_Y 1 static int GetSide(int p1, int p2, int width, int height, int *pointdist) { int x1 = p1 % width, y1 = p1 / width; int x2 = p2 % width, y2 = p2 / width; if (p1 < 0 || p2 < 0) return SIDE_INVALID; if (x1 == x2) { if (y1 != y2) { *pointdist = abs(y2 - y1); return SIDE_Y; } else return SIDE_INVALID; } else if (y1 == y2) { *pointdist = abs(x2 - x1); return SIDE_X; } else return SIDE_INVALID; } // Increase tesselation of one of two touching patches to make a seamless connection between them // Returns 0 in case if patches were not modified, otherwise 1 int Q3PatchAdjustTesselation(int numcomponents, patchinfo_t *patch1, float *patchvertices1, patchinfo_t *patch2, float *patchvertices2) { // what we are doing here is: // we take for each corner of one patch // and check if the other patch contains that corner // once we have a pair of such matches struct {int id1,id2;} commonverts[8]; int i, j, k, side1, side2, *tess1, *tess2; int dist1 = 0, dist2 = 0; qboolean modified = false; // Potential paired vertices (corners of the first patch) commonverts[0].id1 = 0; commonverts[1].id1 = patch1->xsize-1; commonverts[2].id1 = patch1->xsize*(patch1->ysize-1); commonverts[3].id1 = patch1->xsize*patch1->ysize-1; for (i=0;i<4;++i) commonverts[i].id2 = FindEqualOddVertexInArray(numcomponents, patchvertices1+numcomponents*commonverts[i].id1, patchvertices2, patch2->xsize, patch2->ysize); // Corners of the second patch commonverts[4].id2 = 0; commonverts[5].id2 = patch2->xsize-1; commonverts[6].id2 = patch2->xsize*(patch2->ysize-1); commonverts[7].id2 = patch2->xsize*patch2->ysize-1; for (i=4;i<8;++i) commonverts[i].id1 = FindEqualOddVertexInArray(numcomponents, patchvertices2+numcomponents*commonverts[i].id2, patchvertices1, patch1->xsize, patch1->ysize); for (i=0;i<8;++i) for (j=i+1;j<8;++j) { side1 = GetSide(commonverts[i].id1,commonverts[j].id1,patch1->xsize,patch1->ysize,&dist1); side2 = GetSide(commonverts[i].id2,commonverts[j].id2,patch2->xsize,patch2->ysize,&dist2); if (side1 == SIDE_INVALID || side2 == SIDE_INVALID) continue; if(dist1 != dist2) { // no patch welding if the resolutions mismatch continue; } // Update every lod level for (k=0;klods[k].xtess : &patch1->lods[k].ytess; tess2 = side2 == SIDE_X ? &patch2->lods[k].xtess : &patch2->lods[k].ytess; if (*tess1 != *tess2) { if (*tess1 < *tess2) *tess1 = *tess2; else *tess2 = *tess1; modified = true; } } } return modified; } #undef SIDE_INVALID #undef SIDE_X #undef SIDE_Y // calculates elements for a grid of vertices // (such as those produced by Q3PatchTesselate) // (note: width and height are the actual vertex size, this produces // (width-1)*(height-1)*2 triangles, 3 elements each) void Q3PatchTriangleElements(int *elements, int width, int height, int firstvertex) { int x, y, row0, row1; for (y = 0;y < height - 1;y++) { if(y % 2) { // swap the triangle order in odd rows as optimization for collision stride row0 = firstvertex + (y + 0) * width + width - 2; row1 = firstvertex + (y + 1) * width + width - 2; for (x = 0;x < width - 1;x++) { *elements++ = row1; *elements++ = row1 + 1; *elements++ = row0 + 1; *elements++ = row0; *elements++ = row1; *elements++ = row0 + 1; row0--; row1--; } } else { row0 = firstvertex + (y + 0) * width; row1 = firstvertex + (y + 1) * width; for (x = 0;x < width - 1;x++) { *elements++ = row0; *elements++ = row1; *elements++ = row0 + 1; *elements++ = row1; *elements++ = row1 + 1; *elements++ = row0 + 1; row0++; row1++; } } } } darkplaces/gl_textures.c0000664000175000017500000035406613067716220014712 0ustar kalevkalev #include "quakedef.h" #ifdef SUPPORTD3D #include extern LPDIRECT3DDEVICE9 vid_d3d9dev; #endif #include "image.h" #include "jpeg.h" #include "image_png.h" #include "intoverflow.h" #include "dpsoftrast.h" #ifndef GL_TEXTURE_3D #define GL_TEXTURE_3D 0x806F #endif cvar_t gl_max_size = {CVAR_SAVE, "gl_max_size", "2048", "maximum allowed texture size, can be used to reduce video memory usage, limited by hardware capabilities (typically 2048, 4096, or 8192)"}; cvar_t gl_max_lightmapsize = {CVAR_SAVE, "gl_max_lightmapsize", "1024", "maximum allowed texture size for lightmap textures, use larger values to improve rendering speed, as long as there is enough video memory available (setting it too high for the hardware will cause very bad performance)"}; cvar_t gl_picmip = {CVAR_SAVE, "gl_picmip", "0", "reduces resolution of textures by powers of 2, for example 1 will halve width/height, reducing texture memory usage by 75%"}; cvar_t gl_picmip_world = {CVAR_SAVE, "gl_picmip_world", "0", "extra picmip level for world textures (may be negative, which will then reduce gl_picmip for these)"}; cvar_t r_picmipworld = {CVAR_SAVE, "r_picmipworld", "1", "whether gl_picmip shall apply to world textures too (setting this to 0 is a shorthand for gl_picmip_world -9999999)"}; cvar_t gl_picmip_sprites = {CVAR_SAVE, "gl_picmip_sprites", "0", "extra picmip level for sprite textures (may be negative, which will then reduce gl_picmip for these)"}; cvar_t r_picmipsprites = {CVAR_SAVE, "r_picmipsprites", "1", "make gl_picmip affect sprites too (saves some graphics memory in sprite heavy games) (setting this to 0 is a shorthand for gl_picmip_sprites -9999999)"}; cvar_t gl_picmip_other = {CVAR_SAVE, "gl_picmip_other", "0", "extra picmip level for other textures (may be negative, which will then reduce gl_picmip for these)"}; cvar_t r_lerpimages = {CVAR_SAVE, "r_lerpimages", "1", "bilinear filters images when scaling them up to power of 2 size (mode 1), looks better than glquake (mode 0)"}; cvar_t gl_texture_anisotropy = {CVAR_SAVE, "gl_texture_anisotropy", "1", "anisotropic filtering quality (if supported by hardware), 1 sample (no anisotropy) and 8 sample (8 tap anisotropy) are recommended values"}; cvar_t gl_texturecompression = {CVAR_SAVE, "gl_texturecompression", "0", "whether to compress textures, a value of 0 disables compression (even if the individual cvars are 1), 1 enables fast (low quality) compression at startup, 2 enables slow (high quality) compression at startup"}; cvar_t gl_texturecompression_color = {CVAR_SAVE, "gl_texturecompression_color", "1", "whether to compress colormap (diffuse) textures"}; cvar_t gl_texturecompression_normal = {CVAR_SAVE, "gl_texturecompression_normal", "0", "whether to compress normalmap (normalmap) textures"}; cvar_t gl_texturecompression_gloss = {CVAR_SAVE, "gl_texturecompression_gloss", "1", "whether to compress glossmap (specular) textures"}; cvar_t gl_texturecompression_glow = {CVAR_SAVE, "gl_texturecompression_glow", "1", "whether to compress glowmap (luma) textures"}; cvar_t gl_texturecompression_2d = {CVAR_SAVE, "gl_texturecompression_2d", "0", "whether to compress 2d (hud/menu) textures other than the font"}; cvar_t gl_texturecompression_q3bsplightmaps = {CVAR_SAVE, "gl_texturecompression_q3bsplightmaps", "0", "whether to compress lightmaps in q3bsp format levels"}; cvar_t gl_texturecompression_q3bspdeluxemaps = {CVAR_SAVE, "gl_texturecompression_q3bspdeluxemaps", "0", "whether to compress deluxemaps in q3bsp format levels (only levels compiled with q3map2 -deluxe have these)"}; cvar_t gl_texturecompression_sky = {CVAR_SAVE, "gl_texturecompression_sky", "0", "whether to compress sky textures"}; cvar_t gl_texturecompression_lightcubemaps = {CVAR_SAVE, "gl_texturecompression_lightcubemaps", "1", "whether to compress light cubemaps (spotlights and other light projection images)"}; cvar_t gl_texturecompression_reflectmask = {CVAR_SAVE, "gl_texturecompression_reflectmask", "1", "whether to compress reflection cubemap masks (mask of which areas of the texture should reflect the generic shiny cubemap)"}; cvar_t gl_texturecompression_sprites = {CVAR_SAVE, "gl_texturecompression_sprites", "1", "whether to compress sprites"}; cvar_t gl_nopartialtextureupdates = {CVAR_SAVE, "gl_nopartialtextureupdates", "0", "use alternate path for dynamic lightmap updates that avoids a possibly slow code path in the driver"}; cvar_t r_texture_dds_load_alphamode = {0, "r_texture_dds_load_alphamode", "1", "0: trust DDPF_ALPHAPIXELS flag, 1: texture format and brute force search if ambiguous, 2: texture format only"}; cvar_t r_texture_dds_load_logfailure = {0, "r_texture_dds_load_logfailure", "0", "log missing DDS textures to ddstexturefailures.log, 0: done log, 1: log with no optional textures (_norm, glow etc.). 2: log all"}; cvar_t r_texture_dds_swdecode = {0, "r_texture_dds_swdecode", "0", "0: don't software decode DDS, 1: software decode DDS if unsupported, 2: always software decode DDS"}; qboolean gl_filter_force = false; int gl_filter_min = GL_LINEAR_MIPMAP_LINEAR; int gl_filter_mag = GL_LINEAR; DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_mipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE; DPSOFTRAST_TEXTURE_FILTER dpsoftrast_filter_nomipmap = DPSOFTRAST_TEXTURE_FILTER_LINEAR; #ifdef SUPPORTD3D int d3d_filter_flatmin = D3DTEXF_LINEAR; int d3d_filter_flatmag = D3DTEXF_LINEAR; int d3d_filter_flatmix = D3DTEXF_POINT; int d3d_filter_mipmin = D3DTEXF_LINEAR; int d3d_filter_mipmag = D3DTEXF_LINEAR; int d3d_filter_mipmix = D3DTEXF_LINEAR; int d3d_filter_nomip = false; #endif static mempool_t *texturemempool; static memexpandablearray_t texturearray; // note: this must not conflict with TEXF_ flags in r_textures.h // bitmask for mismatch checking #define GLTEXF_IMPORTANTBITS (0) // dynamic texture (treat texnum == 0 differently) #define GLTEXF_DYNAMIC 0x00080000 typedef struct textypeinfo_s { const char *name; textype_t textype; int inputbytesperpixel; int internalbytesperpixel; float glinternalbytesperpixel; int glinternalformat; int glformat; int gltype; } textypeinfo_t; #ifdef USE_GLES2 // we use these internally even if we never deliver such data to the driver #define GL_BGR 0x80E0 #define GL_BGRA 0x80E1 // framebuffer texture formats // GLES2 devices rarely support depth textures, so we actually use a renderbuffer there static textypeinfo_t textype_shadowmap16_comp = {"shadowmap16_comp", TEXTYPE_SHADOWMAP16_COMP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap16_raw = {"shadowmap16_raw", TEXTYPE_SHADOWMAP16_RAW , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap24_comp = {"shadowmap24_comp", TEXTYPE_SHADOWMAP24_COMP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap24_raw = {"shadowmap24_raw", TEXTYPE_SHADOWMAP24_RAW , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_depth16 = {"depth16", TEXTYPE_DEPTHBUFFER16 , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_depth24 = {"depth24", TEXTYPE_DEPTHBUFFER24 , 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_depth24stencil8 = {"depth24stencil8", TEXTYPE_DEPTHBUFFER24STENCIL8, 2, 2, 2.0f, GL_DEPTH_COMPONENT16 , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_colorbuffer = {"colorbuffer", TEXTYPE_COLORBUFFER , 2, 2, 2.0f, GL_RGB565 , GL_RGBA , GL_UNSIGNED_SHORT_5_6_5}; static textypeinfo_t textype_colorbuffer16f = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F , 2, 2, 2.0f, GL_RGBA16F , GL_RGBA , GL_HALF_FLOAT_ARB}; static textypeinfo_t textype_colorbuffer32f = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F , 2, 2, 2.0f, GL_RGBA32F , GL_RGBA , GL_FLOAT}; // image formats: static textypeinfo_t textype_alpha = {"alpha", TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , GL_ALPHA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_palette = {"palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_palette_alpha = {"palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba = {"rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba_alpha = {"rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra = {"bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra_alpha = {"bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; #ifdef __ANDROID__ static textypeinfo_t textype_etc1 = {"etc1", TEXTYPE_ETC1 , 1, 3, 0.5f, GL_ETC1_RGB8_OES , 0 , 0 }; #endif #else // framebuffer texture formats static textypeinfo_t textype_shadowmap16_comp = {"shadowmap16_comp", TEXTYPE_SHADOWMAP16_COMP , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap16_raw = {"shadowmap16_raw", TEXTYPE_SHADOWMAP16_RAW , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap24_comp = {"shadowmap24_comp", TEXTYPE_SHADOWMAP24_COMP , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; static textypeinfo_t textype_shadowmap24_raw = {"shadowmap24_raw", TEXTYPE_SHADOWMAP24_RAW , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; static textypeinfo_t textype_depth16 = {"depth16", TEXTYPE_DEPTHBUFFER16 , 2, 2, 2.0f, GL_DEPTH_COMPONENT16_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_depth24 = {"depth24", TEXTYPE_DEPTHBUFFER24 , 4, 4, 4.0f, GL_DEPTH_COMPONENT24_ARB , GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }; static textypeinfo_t textype_depth24stencil8 = {"depth24stencil8", TEXTYPE_DEPTHBUFFER24STENCIL8, 4, 4, 4.0f, GL_DEPTH24_STENCIL8_EXT , GL_DEPTH_STENCIL_EXT, GL_UNSIGNED_INT_24_8_EXT}; static textypeinfo_t textype_colorbuffer = {"colorbuffer", TEXTYPE_COLORBUFFER , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_colorbuffer16f = {"colorbuffer16f", TEXTYPE_COLORBUFFER16F , 8, 8, 8.0f, GL_RGBA16F_ARB , GL_RGBA , GL_HALF_FLOAT_ARB}; static textypeinfo_t textype_colorbuffer32f = {"colorbuffer32f", TEXTYPE_COLORBUFFER32F , 16, 16, 16.0f, GL_RGBA32F_ARB , GL_RGBA , GL_FLOAT }; // image formats: static textypeinfo_t textype_alpha = {"alpha", TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , GL_ALPHA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_palette = {"palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_palette_alpha = {"palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba = {"rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGB , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba_alpha = {"rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_RGBA , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba_compress = {"rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_rgba_alpha_compress = {"rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra = {"bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGB , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra_alpha = {"bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_RGBA , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra_compress = {"bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_bgra_alpha_compress = {"bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_dxt1 = {"dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_RGB_S3TC_DXT1_EXT , 0 , 0 }; static textypeinfo_t textype_dxt1a = {"dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT , 0 , 0 }; static textypeinfo_t textype_dxt3 = {"dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT , 0 , 0 }; static textypeinfo_t textype_dxt5 = {"dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT , 0 , 0 }; static textypeinfo_t textype_sRGB_palette = {"sRGB_palette", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_palette_alpha = {"sRGB_palette_alpha", TEXTYPE_PALETTE , 1, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_rgba = {"sRGB_rgba", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_rgba_alpha = {"sRGB_rgba_alpha", TEXTYPE_RGBA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_rgba_compress = {"sRGB_rgba_compress", TEXTYPE_RGBA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_rgba_alpha_compress = {"sRGB_rgba_alpha_compress", TEXTYPE_RGBA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_bgra = {"sRGB_bgra", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_bgra_alpha = {"sRGB_bgra_alpha", TEXTYPE_BGRA , 4, 4, 4.0f, GL_SRGB_ALPHA_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_bgra_compress = {"sRGB_bgra_compress", TEXTYPE_BGRA , 4, 4, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_bgra_alpha_compress = {"sRGB_bgra_alpha_compress", TEXTYPE_BGRA , 4, 4, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_BGRA , GL_UNSIGNED_BYTE }; static textypeinfo_t textype_sRGB_dxt1 = {"sRGB_dxt1", TEXTYPE_DXT1 , 4, 0, 0.5f, GL_COMPRESSED_SRGB_S3TC_DXT1_EXT , 0 , 0 }; static textypeinfo_t textype_sRGB_dxt1a = {"sRGB_dxt1a", TEXTYPE_DXT1A , 4, 0, 0.5f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, 0 , 0 }; static textypeinfo_t textype_sRGB_dxt3 = {"sRGB_dxt3", TEXTYPE_DXT3 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, 0 , 0 }; static textypeinfo_t textype_sRGB_dxt5 = {"sRGB_dxt5", TEXTYPE_DXT5 , 4, 0, 1.0f, GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, 0 , 0 }; #endif typedef enum gltexturetype_e { GLTEXTURETYPE_2D, GLTEXTURETYPE_3D, GLTEXTURETYPE_CUBEMAP, GLTEXTURETYPE_TOTAL } gltexturetype_t; static int gltexturetypeenums[GLTEXTURETYPE_TOTAL] = {GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_CUBE_MAP}; #ifdef GL_TEXTURE_WRAP_R static int gltexturetypedimensions[GLTEXTURETYPE_TOTAL] = {2, 3, 2}; #endif static int cubemapside[6] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; typedef struct gltexture_s { // this portion of the struct is exposed to the R_GetTexture macro for // speed reasons, must be identical in rtexture_t! int texnum; // GL texture slot number int renderbuffernum; // GL renderbuffer slot number qboolean dirty; // indicates that R_RealGetTexture should be called qboolean glisdepthstencil; // indicates that FBO attachment has to be GL_DEPTH_STENCIL_ATTACHMENT int gltexturetypeenum; // used by R_Mesh_TexBind // d3d stuff the backend needs void *d3dtexture; void *d3dsurface; #ifdef SUPPORTD3D qboolean d3disrendertargetsurface; qboolean d3disdepthstencilsurface; int d3dformat; int d3dusage; int d3dpool; int d3daddressu; int d3daddressv; int d3daddressw; int d3dmagfilter; int d3dminfilter; int d3dmipfilter; int d3dmaxmiplevelfilter; int d3dmipmaplodbias; int d3dmaxmiplevel; #endif // dynamic texture stuff [11/22/2007 Black] updatecallback_t updatecallback; void *updatecallback_data; // --- [11/22/2007 Black] // stores backup copy of texture for deferred texture updates (gl_nopartialtextureupdates cvar) unsigned char *bufferpixels; qboolean buffermodified; // pointer to texturepool (check this to see if the texture is allocated) struct gltexturepool_s *pool; // pointer to next texture in texturepool chain struct gltexture_s *chain; // name of the texture (this might be removed someday), no duplicates char identifier[MAX_QPATH + 32]; // original data size in *inputtexels int inputwidth, inputheight, inputdepth; // copy of the original texture(s) supplied to the upload function, for // delayed uploads (non-precached) unsigned char *inputtexels; // original data size in *inputtexels int inputdatasize; // flags supplied to the LoadTexture function // (might be altered to remove TEXF_ALPHA), and GLTEXF_ private flags int flags; // picmip level int miplevel; // pointer to one of the textype_ structs textypeinfo_t *textype; // one of the GLTEXTURETYPE_ values int texturetype; // palette if the texture is TEXTYPE_PALETTE const unsigned int *palette; // actual stored texture size after gl_picmip and gl_max_size are applied // (power of 2 if vid.support.arb_texture_non_power_of_two is not supported) int tilewidth, tileheight, tiledepth; // 1 or 6 depending on texturetype int sides; // how many mipmap levels in this texture int miplevels; // bytes per pixel int bytesperpixel; // GL_RGB or GL_RGBA or GL_DEPTH_COMPONENT int glformat; // 3 or 4 int glinternalformat; // GL_UNSIGNED_BYTE or GL_UNSIGNED_INT or GL_UNSIGNED_SHORT or GL_FLOAT int gltype; } gltexture_t; #define TEXTUREPOOL_SENTINEL 0xC0DEDBAD typedef struct gltexturepool_s { unsigned int sentinel; struct gltexture_s *gltchain; struct gltexturepool_s *next; } gltexturepool_t; static gltexturepool_t *gltexturepoolchain = NULL; static unsigned char *resizebuffer = NULL, *colorconvertbuffer; static int resizebuffersize = 0; static const unsigned char *texturebuffer; static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) { switch(textype) { #ifdef USE_GLES2 case TEXTYPE_PALETTE: return (flags & TEXF_ALPHA) ? &textype_palette_alpha : &textype_palette; case TEXTYPE_RGBA: return ((flags & TEXF_ALPHA) ? &textype_rgba_alpha : &textype_rgba); case TEXTYPE_BGRA: return ((flags & TEXF_ALPHA) ? &textype_bgra_alpha : &textype_bgra); #ifdef __ANDROID__ case TEXTYPE_ETC1: return &textype_etc1; #endif case TEXTYPE_ALPHA: return &textype_alpha; case TEXTYPE_COLORBUFFER: return &textype_colorbuffer; case TEXTYPE_COLORBUFFER16F: return &textype_colorbuffer16f; case TEXTYPE_COLORBUFFER32F: return &textype_colorbuffer32f; case TEXTYPE_DEPTHBUFFER16: return &textype_depth16; case TEXTYPE_DEPTHBUFFER24: return &textype_depth24; case TEXTYPE_DEPTHBUFFER24STENCIL8: return &textype_depth24stencil8; case TEXTYPE_SHADOWMAP16_COMP: return &textype_shadowmap16_comp; case TEXTYPE_SHADOWMAP16_RAW: return &textype_shadowmap16_raw; case TEXTYPE_SHADOWMAP24_COMP: return &textype_shadowmap24_comp; case TEXTYPE_SHADOWMAP24_RAW: return &textype_shadowmap24_raw; #else case TEXTYPE_DXT1: return &textype_dxt1; case TEXTYPE_DXT1A: return &textype_dxt1a; case TEXTYPE_DXT3: return &textype_dxt3; case TEXTYPE_DXT5: return &textype_dxt5; case TEXTYPE_PALETTE: return (flags & TEXF_ALPHA) ? &textype_palette_alpha : &textype_palette; case TEXTYPE_RGBA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_rgba_alpha_compress : &textype_rgba_compress) : ((flags & TEXF_ALPHA) ? &textype_rgba_alpha : &textype_rgba); case TEXTYPE_BGRA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_bgra_alpha_compress : &textype_bgra_compress) : ((flags & TEXF_ALPHA) ? &textype_bgra_alpha : &textype_bgra); case TEXTYPE_ALPHA: return &textype_alpha; case TEXTYPE_COLORBUFFER: return &textype_colorbuffer; case TEXTYPE_COLORBUFFER16F: return &textype_colorbuffer16f; case TEXTYPE_COLORBUFFER32F: return &textype_colorbuffer32f; case TEXTYPE_DEPTHBUFFER16: return &textype_depth16; case TEXTYPE_DEPTHBUFFER24: return &textype_depth24; case TEXTYPE_DEPTHBUFFER24STENCIL8: return &textype_depth24stencil8; case TEXTYPE_SHADOWMAP16_COMP: return &textype_shadowmap16_comp; case TEXTYPE_SHADOWMAP16_RAW: return &textype_shadowmap16_raw; case TEXTYPE_SHADOWMAP24_COMP: return &textype_shadowmap24_comp; case TEXTYPE_SHADOWMAP24_RAW: return &textype_shadowmap24_raw; case TEXTYPE_SRGB_DXT1: return &textype_sRGB_dxt1; case TEXTYPE_SRGB_DXT1A: return &textype_sRGB_dxt1a; case TEXTYPE_SRGB_DXT3: return &textype_sRGB_dxt3; case TEXTYPE_SRGB_DXT5: return &textype_sRGB_dxt5; case TEXTYPE_SRGB_PALETTE: return (flags & TEXF_ALPHA) ? &textype_sRGB_palette_alpha : &textype_sRGB_palette; case TEXTYPE_SRGB_RGBA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha_compress : &textype_sRGB_rgba_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_rgba_alpha : &textype_sRGB_rgba); case TEXTYPE_SRGB_BGRA: return ((flags & TEXF_COMPRESS) && vid.support.ext_texture_compression_s3tc) ? ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha_compress : &textype_sRGB_bgra_compress) : ((flags & TEXF_ALPHA) ? &textype_sRGB_bgra_alpha : &textype_sRGB_bgra); #endif default: Host_Error("R_GetTexTypeInfo: unknown texture format"); break; } return NULL; } // dynamic texture code [11/22/2007 Black] void R_MarkDirtyTexture(rtexture_t *rt) { gltexture_t *glt = (gltexture_t*) rt; if( !glt ) { return; } // dont do anything if the texture is already dirty (and make sure this *is* a dynamic texture after all!) if (glt->flags & GLTEXF_DYNAMIC) { // mark it as dirty, so R_RealGetTexture gets called glt->dirty = true; } } void R_MakeTextureDynamic(rtexture_t *rt, updatecallback_t updatecallback, void *data) { gltexture_t *glt = (gltexture_t*) rt; if( !glt ) { return; } glt->flags |= GLTEXF_DYNAMIC; glt->updatecallback = updatecallback; glt->updatecallback_data = data; } static void R_UpdateDynamicTexture(gltexture_t *glt) { glt->dirty = false; if( glt->updatecallback ) { glt->updatecallback( (rtexture_t*) glt, glt->updatecallback_data ); } } void R_PurgeTexture(rtexture_t *rt) { if(rt && !(((gltexture_t*) rt)->flags & TEXF_PERSISTENT)) { R_FreeTexture(rt); } } void R_FreeTexture(rtexture_t *rt) { gltexture_t *glt, **gltpointer; glt = (gltexture_t *)rt; if (glt == NULL) Host_Error("R_FreeTexture: texture == NULL"); for (gltpointer = &glt->pool->gltchain;*gltpointer && *gltpointer != glt;gltpointer = &(*gltpointer)->chain); if (*gltpointer == glt) *gltpointer = glt->chain; else Host_Error("R_FreeTexture: texture \"%s\" not linked in pool", glt->identifier); R_Mesh_ClearBindingsForTexture(glt->texnum); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (glt->texnum) { CHECKGLERROR qglDeleteTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR } if (glt->renderbuffernum) { CHECKGLERROR qglDeleteRenderbuffers(1, (GLuint *)&glt->renderbuffernum);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (glt->d3dsurface) IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dsurface); else if (glt->tiledepth > 1) IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); else if (glt->sides == 6) IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); else IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); glt->d3dtexture = NULL; glt->d3dsurface = NULL; #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (glt->texnum) DPSOFTRAST_Texture_Free(glt->texnum); break; } if (glt->inputtexels) Mem_Free(glt->inputtexels); Mem_ExpandableArray_FreeRecord(&texturearray, glt); } rtexturepool_t *R_AllocTexturePool(void) { gltexturepool_t *pool; if (texturemempool == NULL) return NULL; pool = (gltexturepool_t *)Mem_Alloc(texturemempool, sizeof(gltexturepool_t)); if (pool == NULL) return NULL; pool->next = gltexturepoolchain; gltexturepoolchain = pool; pool->sentinel = TEXTUREPOOL_SENTINEL; return (rtexturepool_t *)pool; } void R_FreeTexturePool(rtexturepool_t **rtexturepool) { gltexturepool_t *pool, **poolpointer; if (rtexturepool == NULL) return; if (*rtexturepool == NULL) return; pool = (gltexturepool_t *)(*rtexturepool); *rtexturepool = NULL; if (pool->sentinel != TEXTUREPOOL_SENTINEL) Host_Error("R_FreeTexturePool: pool already freed"); for (poolpointer = &gltexturepoolchain;*poolpointer && *poolpointer != pool;poolpointer = &(*poolpointer)->next); if (*poolpointer == pool) *poolpointer = pool->next; else Host_Error("R_FreeTexturePool: pool not linked"); while (pool->gltchain) R_FreeTexture((rtexture_t *)pool->gltchain); Mem_Free(pool); } typedef struct glmode_s { const char *name; int minification, magnification; DPSOFTRAST_TEXTURE_FILTER dpsoftrastfilter_mipmap, dpsoftrastfilter_nomipmap; } glmode_t; static glmode_t modes[6] = { {"GL_NEAREST", GL_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, {"GL_LINEAR", GL_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, {"GL_NEAREST_MIPMAP_NEAREST", GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, {"GL_LINEAR_MIPMAP_NEAREST", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR}, {"GL_NEAREST_MIPMAP_LINEAR", GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_NEAREST}, {"GL_LINEAR_MIPMAP_LINEAR", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE, DPSOFTRAST_TEXTURE_FILTER_LINEAR} }; #ifdef SUPPORTD3D typedef struct d3dmode_s { const char *name; int m1, m2; } d3dmode_t; static d3dmode_t d3dmodes[6] = { {"GL_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, {"GL_LINEAR", D3DTEXF_LINEAR, D3DTEXF_POINT}, {"GL_NEAREST_MIPMAP_NEAREST", D3DTEXF_POINT, D3DTEXF_POINT}, {"GL_LINEAR_MIPMAP_NEAREST", D3DTEXF_LINEAR, D3DTEXF_POINT}, {"GL_NEAREST_MIPMAP_LINEAR", D3DTEXF_POINT, D3DTEXF_LINEAR}, {"GL_LINEAR_MIPMAP_LINEAR", D3DTEXF_LINEAR, D3DTEXF_LINEAR} }; #endif static void GL_TextureMode_f (void) { int i; GLint oldbindtexnum; gltexture_t *glt; gltexturepool_t *pool; if (Cmd_Argc() == 1) { Con_Printf("Texture mode is %sforced\n", gl_filter_force ? "" : "not "); for (i = 0;i < 6;i++) { if (gl_filter_min == modes[i].minification) { Con_Printf("%s\n", modes[i].name); return; } } Con_Print("current filter is unknown???\n"); return; } for (i = 0;i < (int)(sizeof(modes)/sizeof(*modes));i++) if (!strcasecmp (modes[i].name, Cmd_Argv(1) ) ) break; if (i == 6) { Con_Print("bad filter name\n"); return; } gl_filter_min = modes[i].minification; gl_filter_mag = modes[i].magnification; gl_filter_force = ((Cmd_Argc() > 2) && !strcasecmp(Cmd_Argv(2), "force")); dpsoftrast_filter_mipmap = modes[i].dpsoftrastfilter_mipmap; dpsoftrast_filter_nomipmap = modes[i].dpsoftrastfilter_nomipmap; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // change all the existing mipmap texture objects // FIXME: force renderer(/client/something?) restart instead? CHECKGLERROR GL_ActiveTexture(0); for (pool = gltexturepoolchain;pool;pool = pool->next) { for (glt = pool->gltchain;glt;glt = glt->chain) { // only update already uploaded images if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) { oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR if (glt->flags & TEXF_MIPMAP) { qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR } else { qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR } qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR } } } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D d3d_filter_flatmin = d3dmodes[i].m1; d3d_filter_flatmag = d3dmodes[i].m1; d3d_filter_flatmix = D3DTEXF_POINT; d3d_filter_mipmin = d3dmodes[i].m1; d3d_filter_mipmag = d3dmodes[i].m1; d3d_filter_mipmix = d3dmodes[i].m2; d3d_filter_nomip = i < 2; if (gl_texture_anisotropy.integer > 1 && i == 5) d3d_filter_mipmin = d3d_filter_mipmag = D3DTEXF_ANISOTROPIC; for (pool = gltexturepoolchain;pool;pool = pool->next) { for (glt = pool->gltchain;glt;glt = glt->chain) { // only update already uploaded images if (glt->d3dtexture && !glt->d3dsurface && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) { if (glt->flags & TEXF_MIPMAP) { glt->d3dminfilter = d3d_filter_mipmin; glt->d3dmagfilter = d3d_filter_mipmag; glt->d3dmipfilter = d3d_filter_mipmix; glt->d3dmaxmiplevelfilter = 0; } else { glt->d3dminfilter = d3d_filter_flatmin; glt->d3dmagfilter = d3d_filter_flatmag; glt->d3dmipfilter = d3d_filter_flatmix; glt->d3dmaxmiplevelfilter = 0; } } } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: // change all the existing texture objects for (pool = gltexturepoolchain;pool;pool = pool->next) for (glt = pool->gltchain;glt;glt = glt->chain) if (glt->texnum && (gl_filter_force || !(glt->flags & (TEXF_FORCENEAREST | TEXF_FORCELINEAR)))) DPSOFTRAST_Texture_Filter(glt->texnum, (glt->flags & TEXF_MIPMAP) ? dpsoftrast_filter_mipmap : dpsoftrast_filter_nomipmap); break; } } static void GL_Texture_CalcImageSize(int texturetype, int flags, int miplevel, int inwidth, int inheight, int indepth, int *outwidth, int *outheight, int *outdepth, int *outmiplevels) { int picmip = 0, maxsize = 0, width2 = 1, height2 = 1, depth2 = 1, miplevels = 1; switch (texturetype) { default: case GLTEXTURETYPE_2D: maxsize = vid.maxtexturesize_2d; if (flags & TEXF_PICMIP) { maxsize = bound(1, gl_max_size.integer, maxsize); picmip = miplevel; } break; case GLTEXTURETYPE_3D: maxsize = vid.maxtexturesize_3d; break; case GLTEXTURETYPE_CUBEMAP: maxsize = vid.maxtexturesize_cubemap; break; } if (vid.support.arb_texture_non_power_of_two) { width2 = min(inwidth >> picmip, maxsize); height2 = min(inheight >> picmip, maxsize); depth2 = min(indepth >> picmip, maxsize); } else { for (width2 = 1;width2 < inwidth;width2 <<= 1); for (width2 >>= picmip;width2 > maxsize;width2 >>= 1); for (height2 = 1;height2 < inheight;height2 <<= 1); for (height2 >>= picmip;height2 > maxsize;height2 >>= 1); for (depth2 = 1;depth2 < indepth;depth2 <<= 1); for (depth2 >>= picmip;depth2 > maxsize;depth2 >>= 1); } switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #if 0 // for some reason the REF rasterizer (and hence the PIX debugger) does not like small textures... if (texturetype == GLTEXTURETYPE_2D) { width2 = max(width2, 2); height2 = max(height2, 2); } #endif break; } miplevels = 1; if (flags & TEXF_MIPMAP) { int extent = max(width2, max(height2, depth2)); while(extent >>= 1) miplevels++; } if (outwidth) *outwidth = max(1, width2); if (outheight) *outheight = max(1, height2); if (outdepth) *outdepth = max(1, depth2); if (outmiplevels) *outmiplevels = miplevels; } static int R_CalcTexelDataSize (gltexture_t *glt) { int width2, height2, depth2, size; GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &width2, &height2, &depth2, NULL); size = width2 * height2 * depth2; if (glt->flags & TEXF_MIPMAP) { while (width2 > 1 || height2 > 1 || depth2 > 1) { if (width2 > 1) width2 >>= 1; if (height2 > 1) height2 >>= 1; if (depth2 > 1) depth2 >>= 1; size += width2 * height2 * depth2; } } return (int)(size * glt->textype->glinternalbytesperpixel) * glt->sides; } void R_TextureStats_Print(qboolean printeach, qboolean printpool, qboolean printtotal) { int glsize; int isloaded; int pooltotal = 0, pooltotalt = 0, pooltotalp = 0, poolloaded = 0, poolloadedt = 0, poolloadedp = 0; int sumtotal = 0, sumtotalt = 0, sumtotalp = 0, sumloaded = 0, sumloadedt = 0, sumloadedp = 0; gltexture_t *glt; gltexturepool_t *pool; if (printeach) Con_Print("glsize input loaded mip alpha name\n"); for (pool = gltexturepoolchain;pool;pool = pool->next) { pooltotal = 0; pooltotalt = 0; pooltotalp = 0; poolloaded = 0; poolloadedt = 0; poolloadedp = 0; for (glt = pool->gltchain;glt;glt = glt->chain) { glsize = R_CalcTexelDataSize(glt); isloaded = glt->texnum != 0 || glt->renderbuffernum != 0 || glt->d3dtexture || glt->d3dsurface; pooltotal++; pooltotalt += glsize; pooltotalp += glt->inputdatasize; if (isloaded) { poolloaded++; poolloadedt += glsize; poolloadedp += glt->inputdatasize; } if (printeach) Con_Printf("%c%4i%c%c%4i%c %-24s %s %s %s %s\n", isloaded ? '[' : ' ', (glsize + 1023) / 1024, isloaded ? ']' : ' ', glt->inputtexels ? '[' : ' ', (glt->inputdatasize + 1023) / 1024, glt->inputtexels ? ']' : ' ', glt->textype->name, isloaded ? "loaded" : " ", (glt->flags & TEXF_MIPMAP) ? "mip" : " ", (glt->flags & TEXF_ALPHA) ? "alpha" : " ", glt->identifier); } if (printpool) Con_Printf("texturepool %10p total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", (void *)pool, pooltotal, pooltotalt / 1048576.0, pooltotalp / 1048576.0, poolloaded, poolloadedt / 1048576.0, poolloadedp / 1048576.0, pooltotal - poolloaded, (pooltotalt - poolloadedt) / 1048576.0, (pooltotalp - poolloadedp) / 1048576.0); sumtotal += pooltotal; sumtotalt += pooltotalt; sumtotalp += pooltotalp; sumloaded += poolloaded; sumloadedt += poolloadedt; sumloadedp += poolloadedp; } if (printtotal) Con_Printf("textures total: %i (%.3fMB, %.3fMB original), uploaded %i (%.3fMB, %.3fMB original), upload on demand %i (%.3fMB, %.3fMB original)\n", sumtotal, sumtotalt / 1048576.0, sumtotalp / 1048576.0, sumloaded, sumloadedt / 1048576.0, sumloadedp / 1048576.0, sumtotal - sumloaded, (sumtotalt - sumloadedt) / 1048576.0, (sumtotalp - sumloadedp) / 1048576.0); } static void R_TextureStats_f(void) { R_TextureStats_Print(true, true, true); } static void r_textures_start(void) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // LordHavoc: allow any alignment CHECKGLERROR qglPixelStorei(GL_UNPACK_ALIGNMENT, 1);CHECKGLERROR qglPixelStorei(GL_PACK_ALIGNMENT, 1);CHECKGLERROR break; case RENDERPATH_D3D9: break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } texturemempool = Mem_AllocPool("texture management", 0, NULL); Mem_ExpandableArray_NewArray(&texturearray, texturemempool, sizeof(gltexture_t), 512); // Disable JPEG screenshots if the DLL isn't loaded if (! JPEG_OpenLibrary ()) Cvar_SetValueQuick (&scr_screenshot_jpeg, 0); if (! PNG_OpenLibrary ()) Cvar_SetValueQuick (&scr_screenshot_png, 0); } static void r_textures_shutdown(void) { rtexturepool_t *temp; JPEG_CloseLibrary (); while(gltexturepoolchain) { temp = (rtexturepool_t *) gltexturepoolchain; R_FreeTexturePool(&temp); } resizebuffersize = 0; resizebuffer = NULL; colorconvertbuffer = NULL; texturebuffer = NULL; Mem_ExpandableArray_FreeArray(&texturearray); Mem_FreePool(&texturemempool); } static void r_textures_newmap(void) { } static void r_textures_devicelost(void) { int i, endindex; gltexture_t *glt; endindex = (int)Mem_ExpandableArray_IndexRange(&texturearray); for (i = 0;i < endindex;i++) { glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); if (!glt || !(glt->flags & TEXF_RENDERTARGET)) continue; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (glt->d3dsurface) IDirect3DSurface9_Release((IDirect3DSurface9 *)glt->d3dsurface); else if (glt->tiledepth > 1) IDirect3DVolumeTexture9_Release((IDirect3DVolumeTexture9 *)glt->d3dtexture); else if (glt->sides == 6) IDirect3DCubeTexture9_Release((IDirect3DCubeTexture9 *)glt->d3dtexture); else IDirect3DTexture9_Release((IDirect3DTexture9 *)glt->d3dtexture); glt->d3dtexture = NULL; glt->d3dsurface = NULL; #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } } } static void r_textures_devicerestored(void) { int i, endindex; gltexture_t *glt; endindex = (int)Mem_ExpandableArray_IndexRange(&texturearray); for (i = 0;i < endindex;i++) { glt = (gltexture_t *) Mem_ExpandableArray_RecordAtIndex(&texturearray, i); if (!glt || !(glt->flags & TEXF_RENDERTARGET)) continue; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { HRESULT d3dresult; if (glt->d3disrendertargetsurface) { if (FAILED(d3dresult = IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) Sys_Error("IDirect3DDevice9_CreateRenderTarget failed!"); } else if (glt->d3disdepthstencilsurface) { if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); } else if (glt->tiledepth > 1) { if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); } else if (glt->sides == 6) { if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); } else { if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateTexture failed!"); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } } } void R_Textures_Init (void) { Cmd_AddCommand("gl_texturemode", &GL_TextureMode_f, "set texture filtering mode (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, etc); an additional argument 'force' forces the texture mode even in cases where it may not be appropriate"); Cmd_AddCommand("r_texturestats", R_TextureStats_f, "print information about all loaded textures and some statistics"); Cvar_RegisterVariable (&gl_max_size); Cvar_RegisterVariable (&gl_picmip); Cvar_RegisterVariable (&gl_picmip_world); Cvar_RegisterVariable (&r_picmipworld); Cvar_RegisterVariable (&gl_picmip_sprites); Cvar_RegisterVariable (&r_picmipsprites); Cvar_RegisterVariable (&gl_picmip_other); Cvar_RegisterVariable (&gl_max_lightmapsize); Cvar_RegisterVariable (&r_lerpimages); Cvar_RegisterVariable (&gl_texture_anisotropy); Cvar_RegisterVariable (&gl_texturecompression); Cvar_RegisterVariable (&gl_texturecompression_color); Cvar_RegisterVariable (&gl_texturecompression_normal); Cvar_RegisterVariable (&gl_texturecompression_gloss); Cvar_RegisterVariable (&gl_texturecompression_glow); Cvar_RegisterVariable (&gl_texturecompression_2d); Cvar_RegisterVariable (&gl_texturecompression_q3bsplightmaps); Cvar_RegisterVariable (&gl_texturecompression_q3bspdeluxemaps); Cvar_RegisterVariable (&gl_texturecompression_sky); Cvar_RegisterVariable (&gl_texturecompression_lightcubemaps); Cvar_RegisterVariable (&gl_texturecompression_reflectmask); Cvar_RegisterVariable (&gl_texturecompression_sprites); Cvar_RegisterVariable (&gl_nopartialtextureupdates); Cvar_RegisterVariable (&r_texture_dds_load_alphamode); Cvar_RegisterVariable (&r_texture_dds_load_logfailure); Cvar_RegisterVariable (&r_texture_dds_swdecode); R_RegisterModule("R_Textures", r_textures_start, r_textures_shutdown, r_textures_newmap, r_textures_devicelost, r_textures_devicerestored); } void R_Textures_Frame (void) { #ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT static int old_aniso = 0; #endif // could do procedural texture animation here, if we keep track of which // textures were accessed this frame... // free the resize buffers resizebuffersize = 0; if (resizebuffer) { Mem_Free(resizebuffer); resizebuffer = NULL; } if (colorconvertbuffer) { Mem_Free(colorconvertbuffer); colorconvertbuffer = NULL; } #ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT if (old_aniso != gl_texture_anisotropy.integer) { gltexture_t *glt; gltexturepool_t *pool; GLint oldbindtexnum; old_aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); Cvar_SetValueQuick(&gl_texture_anisotropy, old_aniso); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR GL_ActiveTexture(0); for (pool = gltexturepoolchain;pool;pool = pool->next) { for (glt = pool->gltchain;glt;glt = glt->chain) { // only update already uploaded images if (glt->texnum && (glt->flags & TEXF_MIPMAP) == TEXF_MIPMAP) { oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_ANISOTROPY_EXT, old_aniso);CHECKGLERROR qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR } } } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } } #endif } static void R_MakeResizeBufferBigger(int size) { if (resizebuffersize < size) { resizebuffersize = size; if (resizebuffer) Mem_Free(resizebuffer); if (colorconvertbuffer) Mem_Free(colorconvertbuffer); resizebuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); colorconvertbuffer = (unsigned char *)Mem_Alloc(texturemempool, resizebuffersize); if (!resizebuffer || !colorconvertbuffer) Host_Error("R_Upload: out of memory"); } } static void GL_SetupTextureParameters(int flags, textype_t textype, int texturetype) { int textureenum = gltexturetypeenums[texturetype]; int wrapmode = (flags & TEXF_CLAMP) ? GL_CLAMP_TO_EDGE : GL_REPEAT; CHECKGLERROR #ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT if (vid.support.ext_texture_filter_anisotropic && (flags & TEXF_MIPMAP)) { int aniso = bound(1, gl_texture_anisotropy.integer, (int)vid.max_anisotropy); if (gl_texture_anisotropy.integer != aniso) Cvar_SetValueQuick(&gl_texture_anisotropy, aniso); qglTexParameteri(textureenum, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);CHECKGLERROR } #endif qglTexParameteri(textureenum, GL_TEXTURE_WRAP_S, wrapmode);CHECKGLERROR qglTexParameteri(textureenum, GL_TEXTURE_WRAP_T, wrapmode);CHECKGLERROR #ifdef GL_TEXTURE_WRAP_R if (gltexturetypedimensions[texturetype] >= 3) { qglTexParameteri(textureenum, GL_TEXTURE_WRAP_R, wrapmode);CHECKGLERROR } #endif CHECKGLERROR if (!gl_filter_force && flags & TEXF_FORCENEAREST) { if (flags & TEXF_MIPMAP) { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);CHECKGLERROR } else { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_NEAREST);CHECKGLERROR } qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_NEAREST);CHECKGLERROR } else if (!gl_filter_force && flags & TEXF_FORCELINEAR) { if (flags & TEXF_MIPMAP) { if (gl_filter_min == GL_NEAREST_MIPMAP_LINEAR || gl_filter_min == GL_LINEAR_MIPMAP_LINEAR) { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);CHECKGLERROR } else { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);CHECKGLERROR } } else { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, GL_LINEAR);CHECKGLERROR } qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, GL_LINEAR);CHECKGLERROR } else { if (flags & TEXF_MIPMAP) { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_min);CHECKGLERROR } else { qglTexParameteri(textureenum, GL_TEXTURE_MIN_FILTER, gl_filter_mag);CHECKGLERROR } qglTexParameteri(textureenum, GL_TEXTURE_MAG_FILTER, gl_filter_mag);CHECKGLERROR } #ifdef GL_TEXTURE_COMPARE_MODE_ARB switch(textype) { case TEXTYPE_SHADOWMAP16_COMP: case TEXTYPE_SHADOWMAP24_COMP: qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB);CHECKGLERROR qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);CHECKGLERROR qglTexParameteri(textureenum, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);CHECKGLERROR break; case TEXTYPE_SHADOWMAP16_RAW: case TEXTYPE_SHADOWMAP24_RAW: qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);CHECKGLERROR qglTexParameteri(textureenum, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);CHECKGLERROR qglTexParameteri(textureenum, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);CHECKGLERROR break; default: break; } #endif CHECKGLERROR } static void R_UploadPartialTexture(gltexture_t *glt, const unsigned char *data, int fragx, int fragy, int fragz, int fragwidth, int fragheight, int fragdepth) { if (data == NULL) Sys_Error("R_UploadPartialTexture \"%s\": partial update with NULL pixels", glt->identifier); if (glt->texturetype != GLTEXTURETYPE_2D) Sys_Error("R_UploadPartialTexture \"%s\": partial update of type other than 2D", glt->identifier); if (glt->textype->textype == TEXTYPE_PALETTE) Sys_Error("R_UploadPartialTexture \"%s\": partial update of paletted texture", glt->identifier); if (glt->flags & (TEXF_MIPMAP | TEXF_PICMIP)) Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with MIPMAP or PICMIP flags", glt->identifier); if (glt->inputwidth != glt->tilewidth || glt->inputheight != glt->tileheight || glt->tiledepth != 1) Sys_Error("R_UploadPartialTexture \"%s\": partial update not supported with stretched or special textures", glt->identifier); // update a portion of the image switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: { int oldbindtexnum; CHECKGLERROR // we need to restore the texture binding after finishing the upload GL_ActiveTexture(0); oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR qglTexSubImage2D(GL_TEXTURE_2D, 0, fragx, fragy, fragwidth, fragheight, glt->glformat, glt->gltype, data);CHECKGLERROR qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { RECT d3drect; D3DLOCKED_RECT d3dlockedrect; int y; memset(&d3drect, 0, sizeof(d3drect)); d3drect.left = fragx; d3drect.top = fragy; d3drect.right = fragx+fragwidth; d3drect.bottom = fragy+fragheight; if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, 0, &d3dlockedrect, &d3drect, 0) == D3D_OK && d3dlockedrect.pBits) { for (y = 0;y < fragheight;y++) memcpy((unsigned char *)d3dlockedrect.pBits + d3dlockedrect.Pitch * y, data + fragwidth*glt->bytesperpixel * y, fragwidth*glt->bytesperpixel); IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, 0); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_Texture_UpdatePartial(glt->texnum, 0, data, fragx, fragy, fragwidth, fragheight); break; } } static void R_UploadFullTexture(gltexture_t *glt, const unsigned char *data) { int i, mip = 0, width, height, depth; GLint oldbindtexnum = 0; const unsigned char *prevbuffer; prevbuffer = data; // error out if a stretch is needed on special texture types if (glt->texturetype != GLTEXTURETYPE_2D && (glt->tilewidth != glt->inputwidth || glt->tileheight != glt->inputheight || glt->tiledepth != glt->inputdepth)) Sys_Error("R_UploadFullTexture \"%s\": stretch uploads allowed only on 2D textures\n", glt->identifier); // when picmip or maxsize is applied, we scale up to a power of 2 multiple // of the target size and then use the mipmap reduction function to get // high quality supersampled results for (width = glt->tilewidth;width < glt->inputwidth ;width <<= 1); for (height = glt->tileheight;height < glt->inputheight;height <<= 1); for (depth = glt->tiledepth;depth < glt->inputdepth ;depth <<= 1); R_MakeResizeBufferBigger(width * height * depth * glt->sides * glt->bytesperpixel); if (prevbuffer == NULL) { width = glt->tilewidth; height = glt->tileheight; depth = glt->tiledepth; // memset(resizebuffer, 0, width * height * depth * glt->sides * glt->bytesperpixel); // prevbuffer = resizebuffer; } else { if (glt->textype->textype == TEXTYPE_PALETTE) { // promote paletted to BGRA, so we only have to worry about BGRA in the rest of this code Image_Copy8bitBGRA(prevbuffer, colorconvertbuffer, glt->inputwidth * glt->inputheight * glt->inputdepth * glt->sides, glt->palette); prevbuffer = colorconvertbuffer; } if (glt->flags & TEXF_RGBMULTIPLYBYALPHA) { // multiply RGB channels by A channel before uploading int alpha; for (i = 0;i < glt->inputwidth*glt->inputheight*glt->inputdepth*4;i += 4) { alpha = prevbuffer[i+3]; colorconvertbuffer[i] = (prevbuffer[i] * alpha) >> 8; colorconvertbuffer[i+1] = (prevbuffer[i+1] * alpha) >> 8; colorconvertbuffer[i+2] = (prevbuffer[i+2] * alpha) >> 8; colorconvertbuffer[i+3] = alpha; } prevbuffer = colorconvertbuffer; } // scale up to a power of 2 size (if appropriate) if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) { Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); prevbuffer = resizebuffer; } // apply mipmap reduction algorithm to get down to picmip/max_size while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); prevbuffer = resizebuffer; } } // do the appropriate upload type... switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (glt->texnum) // not renderbuffers { CHECKGLERROR // we need to restore the texture binding after finishing the upload GL_ActiveTexture(0); oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR #ifndef USE_GLES2 #ifdef GL_TEXTURE_COMPRESSION_HINT_ARB if (qglGetCompressedTexImageARB) { if (gl_texturecompression.integer >= 2) qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_NICEST); else qglHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_FASTEST); CHECKGLERROR } #endif #endif switch(glt->texturetype) { case GLTEXTURETYPE_2D: qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR if (glt->flags & TEXF_MIPMAP) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; qglTexImage2D(GL_TEXTURE_2D, mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR } } break; case GLTEXTURETYPE_3D: #ifndef USE_GLES2 qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR if (glt->flags & TEXF_MIPMAP) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; qglTexImage3D(GL_TEXTURE_3D, mip++, glt->glinternalformat, width, height, depth, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR } } #endif break; case GLTEXTURETYPE_CUBEMAP: // convert and upload each side in turn, // from a continuous block of input texels texturebuffer = (unsigned char *)prevbuffer; for (i = 0;i < 6;i++) { prevbuffer = texturebuffer; texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) { Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); prevbuffer = resizebuffer; } // picmip/max_size while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); prevbuffer = resizebuffer; } mip = 0; qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR if (glt->flags & TEXF_MIPMAP) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; qglTexImage2D(cubemapside[i], mip++, glt->glinternalformat, width, height, 0, glt->glformat, glt->gltype, prevbuffer);CHECKGLERROR } } } break; } GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (!(glt->flags & TEXF_RENDERTARGET) && glt->d3dtexture && !glt->d3dsurface) { D3DLOCKED_RECT d3dlockedrect; D3DLOCKED_BOX d3dlockedbox; switch(glt->texturetype) { case GLTEXTURETYPE_2D: if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) { if (prevbuffer) memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); else memset(d3dlockedrect.pBits, 255, width*height*glt->bytesperpixel); IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); } mip++; if ((glt->flags & TEXF_MIPMAP) && prevbuffer) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) { memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); } mip++; } } break; case GLTEXTURETYPE_3D: if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) { // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); } mip++; if (glt->flags & TEXF_MIPMAP) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; if (IDirect3DVolumeTexture9_LockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip, &d3dlockedbox, NULL, 0) == D3D_OK && d3dlockedbox.pBits) { // we are not honoring the RowPitch or SlicePitch, hopefully this works with all sizes memcpy(d3dlockedbox.pBits, prevbuffer, width*height*depth*glt->bytesperpixel); IDirect3DVolumeTexture9_UnlockBox((IDirect3DVolumeTexture9*)glt->d3dtexture, mip); } mip++; } } break; case GLTEXTURETYPE_CUBEMAP: // convert and upload each side in turn, // from a continuous block of input texels texturebuffer = (unsigned char *)prevbuffer; for (i = 0;i < 6;i++) { prevbuffer = texturebuffer; texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) { Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); prevbuffer = resizebuffer; } // picmip/max_size while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); prevbuffer = resizebuffer; } mip = 0; if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) { memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); } mip++; if (glt->flags & TEXF_MIPMAP) { while (width > 1 || height > 1 || depth > 1) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, 1, 1, 1); prevbuffer = resizebuffer; if (IDirect3DCubeTexture9_LockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) { memcpy(d3dlockedrect.pBits, prevbuffer, width*height*glt->bytesperpixel); IDirect3DCubeTexture9_UnlockRect((IDirect3DCubeTexture9*)glt->d3dtexture, (D3DCUBEMAP_FACES)i, mip); } mip++; } } } break; } } glt->d3daddressw = 0; if (glt->flags & TEXF_CLAMP) { glt->d3daddressu = D3DTADDRESS_CLAMP; glt->d3daddressv = D3DTADDRESS_CLAMP; if (glt->tiledepth > 1) glt->d3daddressw = D3DTADDRESS_CLAMP; } else { glt->d3daddressu = D3DTADDRESS_WRAP; glt->d3daddressv = D3DTADDRESS_WRAP; if (glt->tiledepth > 1) glt->d3daddressw = D3DTADDRESS_WRAP; } glt->d3dmipmaplodbias = 0; glt->d3dmaxmiplevel = 0; glt->d3dmaxmiplevelfilter = d3d_filter_nomip ? 0 : glt->d3dmaxmiplevel; if (glt->flags & TEXF_FORCELINEAR) { glt->d3dminfilter = D3DTEXF_LINEAR; glt->d3dmagfilter = D3DTEXF_LINEAR; glt->d3dmipfilter = D3DTEXF_POINT; } else if (glt->flags & TEXF_FORCENEAREST) { glt->d3dminfilter = D3DTEXF_POINT; glt->d3dmagfilter = D3DTEXF_POINT; glt->d3dmipfilter = D3DTEXF_POINT; } else if (glt->flags & TEXF_MIPMAP) { glt->d3dminfilter = d3d_filter_mipmin; glt->d3dmagfilter = d3d_filter_mipmag; glt->d3dmipfilter = d3d_filter_mipmix; } else { glt->d3dminfilter = d3d_filter_flatmin; glt->d3dmagfilter = d3d_filter_flatmag; glt->d3dmipfilter = d3d_filter_flatmix; } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: switch(glt->texturetype) { case GLTEXTURETYPE_2D: DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); break; case GLTEXTURETYPE_3D: DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); break; case GLTEXTURETYPE_CUBEMAP: if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) { unsigned char *combinedbuffer = (unsigned char *)Mem_Alloc(tempmempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); // convert and upload each side in turn, // from a continuous block of input texels // copy the results into combinedbuffer texturebuffer = (unsigned char *)prevbuffer; for (i = 0;i < 6;i++) { prevbuffer = texturebuffer; texturebuffer += glt->inputwidth * glt->inputheight * glt->inputdepth * glt->textype->inputbytesperpixel; if (glt->inputwidth != width || glt->inputheight != height || glt->inputdepth != depth) { Image_Resample32(prevbuffer, glt->inputwidth, glt->inputheight, glt->inputdepth, resizebuffer, width, height, depth, r_lerpimages.integer); prevbuffer = resizebuffer; } // picmip/max_size while (width > glt->tilewidth || height > glt->tileheight || depth > glt->tiledepth) { Image_MipReduce32(prevbuffer, resizebuffer, &width, &height, &depth, glt->tilewidth, glt->tileheight, glt->tiledepth); prevbuffer = resizebuffer; } memcpy(combinedbuffer + i*glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel, prevbuffer, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->bytesperpixel); } DPSOFTRAST_Texture_UpdateFull(glt->texnum, combinedbuffer); Mem_Free(combinedbuffer); } else DPSOFTRAST_Texture_UpdateFull(glt->texnum, prevbuffer); break; } if (glt->flags & TEXF_FORCELINEAR) DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); else if (glt->flags & TEXF_FORCENEAREST) DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); else if (glt->flags & TEXF_MIPMAP) DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); else DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); break; } } static rtexture_t *R_SetupTexture(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, int sides, int flags, int miplevel, textype_t textype, int texturetype, const unsigned char *data, const unsigned int *palette) { int i, size; gltexture_t *glt; gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; textypeinfo_t *texinfo, *texinfo2; unsigned char *temppixels = NULL; qboolean swaprb; if (cls.state == ca_dedicated) return NULL; // see if we need to swap red and blue (BGRA <-> RGBA conversion) if (textype == TEXTYPE_PALETTE && vid.forcetextype == TEXTYPE_RGBA) { int numpixels = width * height * depth * sides; size = numpixels * 4; temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); if (data) { const unsigned char *p; unsigned char *o = temppixels; for (i = 0;i < numpixels;i++, o += 4) { p = (const unsigned char *)palette + 4*data[i]; o[0] = p[2]; o[1] = p[1]; o[2] = p[0]; o[3] = p[3]; } } data = temppixels; textype = TEXTYPE_RGBA; } swaprb = false; switch(textype) { case TEXTYPE_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_BGRA;} break; case TEXTYPE_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_RGBA;} break; case TEXTYPE_SRGB_RGBA: if (vid.forcetextype == TEXTYPE_BGRA) {swaprb = true;textype = TEXTYPE_SRGB_BGRA;} break; case TEXTYPE_SRGB_BGRA: if (vid.forcetextype == TEXTYPE_RGBA) {swaprb = true;textype = TEXTYPE_SRGB_RGBA;} break; default: break; } if (swaprb) { // swap bytes static int rgbaswapindices[4] = {2, 1, 0, 3}; size = width * height * depth * sides * 4; temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); if (data) Image_CopyMux(temppixels, data, width, height*depth*sides, false, false, false, 4, 4, rgbaswapindices); data = temppixels; } // if sRGB texture formats are not supported, convert input to linear and upload as normal types if (!vid.support.ext_texture_srgb) { qboolean convertsRGB = false; switch(textype) { case TEXTYPE_SRGB_DXT1: textype = TEXTYPE_DXT1 ;convertsRGB = true;break; case TEXTYPE_SRGB_DXT1A: textype = TEXTYPE_DXT1A ;convertsRGB = true;break; case TEXTYPE_SRGB_DXT3: textype = TEXTYPE_DXT3 ;convertsRGB = true;break; case TEXTYPE_SRGB_DXT5: textype = TEXTYPE_DXT5 ;convertsRGB = true;break; case TEXTYPE_SRGB_PALETTE: textype = TEXTYPE_PALETTE;/*convertsRGB = true;*/break; case TEXTYPE_SRGB_RGBA: textype = TEXTYPE_RGBA ;convertsRGB = true;break; case TEXTYPE_SRGB_BGRA: textype = TEXTYPE_BGRA ;convertsRGB = true;break; default: break; } if (convertsRGB && data) { size = width * height * depth * sides * 4; if (!temppixels) { temppixels = (unsigned char *)Mem_Alloc(tempmempool, size); memcpy(temppixels, data, size); data = temppixels; } Image_MakeLinearColorsFromsRGB(temppixels, temppixels, width*height*depth*sides); } } if (texturetype == GLTEXTURETYPE_CUBEMAP && !vid.support.arb_texture_cube_map) { Con_Printf ("R_LoadTexture: cubemap texture not supported by driver\n"); return NULL; } if (texturetype == GLTEXTURETYPE_3D && !vid.support.ext_texture_3d) { Con_Printf ("R_LoadTexture: 3d texture not supported by driver\n"); return NULL; } texinfo = R_GetTexTypeInfo(textype, flags); size = width * height * depth * sides * texinfo->inputbytesperpixel; if (size < 1) { Con_Printf ("R_LoadTexture: bogus texture size (%dx%dx%dx%dbppx%dsides = %d bytes)\n", width, height, depth, texinfo->inputbytesperpixel * 8, sides, size); return NULL; } // clear the alpha flag if the texture has no transparent pixels switch(textype) { case TEXTYPE_PALETTE: case TEXTYPE_SRGB_PALETTE: if (flags & TEXF_ALPHA) { flags &= ~TEXF_ALPHA; if (data) { for (i = 0;i < size;i++) { if (((unsigned char *)&palette[data[i]])[3] < 255) { flags |= TEXF_ALPHA; break; } } } } break; case TEXTYPE_RGBA: case TEXTYPE_BGRA: case TEXTYPE_SRGB_RGBA: case TEXTYPE_SRGB_BGRA: if (flags & TEXF_ALPHA) { flags &= ~TEXF_ALPHA; if (data) { for (i = 3;i < size;i += 4) { if (data[i] < 255) { flags |= TEXF_ALPHA; break; } } } } break; case TEXTYPE_SHADOWMAP16_COMP: case TEXTYPE_SHADOWMAP16_RAW: case TEXTYPE_SHADOWMAP24_COMP: case TEXTYPE_SHADOWMAP24_RAW: break; case TEXTYPE_DXT1: case TEXTYPE_SRGB_DXT1: break; case TEXTYPE_DXT1A: case TEXTYPE_SRGB_DXT1A: case TEXTYPE_DXT3: case TEXTYPE_SRGB_DXT3: case TEXTYPE_DXT5: case TEXTYPE_SRGB_DXT5: flags |= TEXF_ALPHA; break; case TEXTYPE_ALPHA: flags |= TEXF_ALPHA; break; case TEXTYPE_COLORBUFFER: case TEXTYPE_COLORBUFFER16F: case TEXTYPE_COLORBUFFER32F: flags |= TEXF_ALPHA; break; default: Sys_Error("R_LoadTexture: unknown texture type"); } texinfo2 = R_GetTexTypeInfo(textype, flags); if(size == width * height * depth * sides * texinfo->inputbytesperpixel) texinfo = texinfo2; else Con_Printf ("R_LoadTexture: input size changed after alpha fallback\n"); glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); if (identifier) strlcpy (glt->identifier, identifier, sizeof(glt->identifier)); glt->pool = pool; glt->chain = pool->gltchain; pool->gltchain = glt; glt->inputwidth = width; glt->inputheight = height; glt->inputdepth = depth; glt->flags = flags; glt->miplevel = (miplevel < 0) ? R_PicmipForFlags(flags) : miplevel; // note: if miplevel is -1, we know the texture is in original size and we can picmip it normally glt->textype = texinfo; glt->texturetype = texturetype; glt->inputdatasize = size; glt->palette = palette; glt->glinternalformat = texinfo->glinternalformat; glt->glformat = texinfo->glformat; glt->gltype = texinfo->gltype; glt->bytesperpixel = texinfo->internalbytesperpixel; glt->sides = glt->texturetype == GLTEXTURETYPE_CUBEMAP ? 6 : 1; glt->texnum = 0; glt->dirty = false; glt->glisdepthstencil = false; glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; // init the dynamic texture attributes, too [11/22/2007 Black] glt->updatecallback = NULL; glt->updatecallback_data = NULL; GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &glt->tilewidth, &glt->tileheight, &glt->tiledepth, &glt->miplevels); // upload the texture // data may be NULL (blank texture for dynamic rendering) switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DFORMAT d3dformat; D3DPOOL d3dpool; DWORD d3dusage; HRESULT d3dresult; d3dusage = 0; d3dpool = D3DPOOL_MANAGED; if (flags & TEXF_RENDERTARGET) { d3dusage |= D3DUSAGE_RENDERTARGET; d3dpool = D3DPOOL_DEFAULT; } switch(textype) { case TEXTYPE_PALETTE: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; case TEXTYPE_RGBA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8B8G8R8 : D3DFMT_X8B8G8R8;break; case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; case TEXTYPE_COLORBUFFER: d3dformat = D3DFMT_A8R8G8B8;break; case TEXTYPE_COLORBUFFER16F: d3dformat = D3DFMT_A16B16G16R16F;break; case TEXTYPE_COLORBUFFER32F: d3dformat = D3DFMT_A32B32G32R32F;break; case TEXTYPE_ALPHA: d3dformat = D3DFMT_A8;break; default: d3dformat = D3DFMT_A8R8G8B8;Sys_Error("R_LoadTexture: unsupported texture type %i when picking D3DFMT", (int)textype);break; } glt->d3dformat = d3dformat; glt->d3dusage = d3dusage; glt->d3dpool = d3dpool; glt->d3disrendertargetsurface = false; glt->d3disdepthstencilsurface = false; if (glt->tiledepth > 1) { if (FAILED(d3dresult = IDirect3DDevice9_CreateVolumeTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->tiledepth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DVolumeTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateVolumeTexture failed!"); } else if (glt->sides == 6) { if (FAILED(d3dresult = IDirect3DDevice9_CreateCubeTexture(vid_d3d9dev, glt->tilewidth, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DCubeTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateCubeTexture failed!"); } else { if (FAILED(d3dresult = IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, glt->d3dusage, (D3DFORMAT)glt->d3dformat, (D3DPOOL)glt->d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL))) Sys_Error("IDirect3DDevice9_CreateTexture failed!"); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: { int tflags = 0; switch(textype) { case TEXTYPE_PALETTE: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; case TEXTYPE_RGBA: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA8;break; case TEXTYPE_BGRA: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; case TEXTYPE_COLORBUFFER: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8;break; case TEXTYPE_COLORBUFFER16F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA16F;break; case TEXTYPE_COLORBUFFER32F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA32F;break; case TEXTYPE_SHADOWMAP16_COMP: case TEXTYPE_SHADOWMAP16_RAW: case TEXTYPE_SHADOWMAP24_COMP: case TEXTYPE_SHADOWMAP24_RAW: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH;break; case TEXTYPE_DEPTHBUFFER16: case TEXTYPE_DEPTHBUFFER24: case TEXTYPE_DEPTHBUFFER24STENCIL8: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH;break; case TEXTYPE_ALPHA: tflags = DPSOFTRAST_TEXTURE_FORMAT_ALPHA8;break; default: Sys_Error("R_LoadTexture: unsupported texture type %i when picking DPSOFTRAST_TEXTURE_FLAGS", (int)textype); } if (glt->miplevels > 1) tflags |= DPSOFTRAST_TEXTURE_FLAG_MIPMAP; if (flags & TEXF_ALPHA) tflags |= DPSOFTRAST_TEXTURE_FLAG_USEALPHA; if (glt->sides == 6) tflags |= DPSOFTRAST_TEXTURE_FLAG_CUBEMAP; if (glt->flags & TEXF_CLAMP) tflags |= DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE; glt->texnum = DPSOFTRAST_Texture_New(tflags, glt->tilewidth, glt->tileheight, glt->tiledepth); } break; } R_UploadFullTexture(glt, data); if ((glt->flags & TEXF_ALLOWUPDATES) && gl_nopartialtextureupdates.integer) glt->bufferpixels = (unsigned char *)Mem_Alloc(texturemempool, glt->tilewidth*glt->tileheight*glt->tiledepth*glt->sides*glt->bytesperpixel); // free any temporary processing buffer we allocated... if (temppixels) Mem_Free(temppixels); // texture converting and uploading can take a while, so make sure we're sending keepalives // FIXME: this causes rendering during R_Shadow_DrawLights // CL_KeepaliveMessage(false); return (rtexture_t *)glt; } rtexture_t *R_LoadTexture2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) { return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, flags, miplevel, textype, GLTEXTURETYPE_2D, data, palette); } rtexture_t *R_LoadTexture3D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, int depth, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) { return R_SetupTexture(rtexturepool, identifier, width, height, depth, 1, flags, miplevel, textype, GLTEXTURETYPE_3D, data, palette); } rtexture_t *R_LoadTextureCubeMap(rtexturepool_t *rtexturepool, const char *identifier, int width, const unsigned char *data, textype_t textype, int flags, int miplevel, const unsigned int *palette) { return R_SetupTexture(rtexturepool, identifier, width, width, 1, 6, flags, miplevel, textype, GLTEXTURETYPE_CUBEMAP, data, palette); } rtexture_t *R_LoadTextureShadowMap2D(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype, qboolean filter) { return R_SetupTexture(rtexturepool, identifier, width, height, 1, 1, TEXF_RENDERTARGET | TEXF_CLAMP | (filter ? TEXF_FORCELINEAR : TEXF_FORCENEAREST), -1, textype, GLTEXTURETYPE_2D, NULL, NULL); } rtexture_t *R_LoadTextureRenderBuffer(rtexturepool_t *rtexturepool, const char *identifier, int width, int height, textype_t textype) { gltexture_t *glt; gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; textypeinfo_t *texinfo; if (cls.state == ca_dedicated) return NULL; texinfo = R_GetTexTypeInfo(textype, TEXF_RENDERTARGET | TEXF_CLAMP); glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); if (identifier) strlcpy (glt->identifier, identifier, sizeof(glt->identifier)); glt->pool = pool; glt->chain = pool->gltchain; pool->gltchain = glt; glt->inputwidth = width; glt->inputheight = height; glt->inputdepth = 1; glt->flags = TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_FORCENEAREST; glt->miplevel = 0; glt->textype = texinfo; glt->texturetype = textype; glt->inputdatasize = width*height*texinfo->internalbytesperpixel; glt->palette = NULL; glt->glinternalformat = texinfo->glinternalformat; glt->glformat = texinfo->glformat; glt->gltype = texinfo->gltype; glt->bytesperpixel = texinfo->internalbytesperpixel; glt->sides = glt->texturetype == GLTEXTURETYPE_CUBEMAP ? 6 : 1; glt->texnum = 0; glt->dirty = false; glt->glisdepthstencil = textype == TEXTYPE_DEPTHBUFFER24STENCIL8; glt->gltexturetypeenum = GL_TEXTURE_2D; // init the dynamic texture attributes, too [11/22/2007 Black] glt->updatecallback = NULL; glt->updatecallback_data = NULL; GL_Texture_CalcImageSize(glt->texturetype, glt->flags, glt->miplevel, glt->inputwidth, glt->inputheight, glt->inputdepth, &glt->tilewidth, &glt->tileheight, &glt->tiledepth, &glt->miplevels); // upload the texture // data may be NULL (blank texture for dynamic rendering) switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglGenRenderbuffers(1, (GLuint *)&glt->renderbuffernum);CHECKGLERROR qglBindRenderbuffer(GL_RENDERBUFFER, glt->renderbuffernum);CHECKGLERROR qglRenderbufferStorage(GL_RENDERBUFFER, glt->glinternalformat, glt->tilewidth, glt->tileheight);CHECKGLERROR // note we can query the renderbuffer for info with glGetRenderbufferParameteriv for GL_WIDTH, GL_HEIGHt, GL_RED_SIZE, GL_GREEN_SIZE, GL_BLUE_SIZE, GL_GL_ALPHA_SIZE, GL_DEPTH_SIZE, GL_STENCIL_SIZE, GL_INTERNAL_FORMAT qglBindRenderbuffer(GL_RENDERBUFFER, 0);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DFORMAT d3dformat; HRESULT d3dresult; glt->d3disrendertargetsurface = false; glt->d3disdepthstencilsurface = false; switch(textype) { case TEXTYPE_COLORBUFFER: d3dformat = D3DFMT_A8R8G8B8;glt->d3disrendertargetsurface = true;break; case TEXTYPE_COLORBUFFER16F: d3dformat = D3DFMT_A16B16G16R16F;glt->d3disrendertargetsurface = true;break; case TEXTYPE_COLORBUFFER32F: d3dformat = D3DFMT_A32B32G32R32F;glt->d3disrendertargetsurface = true;break; case TEXTYPE_DEPTHBUFFER16: d3dformat = D3DFMT_D16;glt->d3disdepthstencilsurface = true;break; case TEXTYPE_DEPTHBUFFER24: d3dformat = D3DFMT_D24X8;glt->d3disdepthstencilsurface = true;break; case TEXTYPE_DEPTHBUFFER24STENCIL8: d3dformat = D3DFMT_D24S8;glt->d3disdepthstencilsurface = true;break; default: d3dformat = D3DFMT_A8R8G8B8;Sys_Error("R_LoadTextureRenderbuffer: unsupported texture type %i when picking D3DFMT", (int)textype);break; } glt->d3dformat = d3dformat; glt->d3dusage = 0; glt->d3dpool = 0; if (glt->d3disrendertargetsurface) { if (FAILED(d3dresult = IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) Sys_Error("IDirect3DDevice9_CreateRenderTarget failed!"); } else if (glt->d3disdepthstencilsurface) { if (FAILED(d3dresult = IDirect3DDevice9_CreateDepthStencilSurface(vid_d3d9dev, glt->tilewidth, glt->tileheight, (D3DFORMAT)glt->d3dformat, D3DMULTISAMPLE_NONE, 0, false, (IDirect3DSurface9 **)&glt->d3dsurface, NULL))) Sys_Error("IDirect3DDevice9_CreateDepthStencilSurface failed!"); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: { int tflags = 0; switch(textype) { case TEXTYPE_COLORBUFFER: tflags = DPSOFTRAST_TEXTURE_FORMAT_BGRA8 | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; case TEXTYPE_COLORBUFFER16F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA16F | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; case TEXTYPE_COLORBUFFER32F: tflags = DPSOFTRAST_TEXTURE_FORMAT_RGBA32F | DPSOFTRAST_TEXTURE_FLAG_USEALPHA | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; case TEXTYPE_DEPTHBUFFER16: case TEXTYPE_DEPTHBUFFER24: case TEXTYPE_DEPTHBUFFER24STENCIL8: tflags = DPSOFTRAST_TEXTURE_FORMAT_DEPTH | DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE;break; default: Sys_Error("R_LoadTextureRenderbuffer: unsupported texture type %i when picking DPSOFTRAST_TEXTURE_FLAGS", (int)textype); } glt->texnum = DPSOFTRAST_Texture_New(tflags, glt->tilewidth, glt->tileheight, glt->tiledepth); } break; } return (rtexture_t *)glt; } int R_SaveTextureDDSFile(rtexture_t *rt, const char *filename, qboolean skipuncompressed, qboolean hasalpha) { #ifdef USE_GLES2 return -1; // unsupported on this platform #else gltexture_t *glt = (gltexture_t *)rt; unsigned char *dds; int oldbindtexnum; int bytesperpixel = 0; int bytesperblock = 0; int dds_flags; int dds_format_flags; int dds_caps1; int dds_caps2; int ret; int mip; int mipmaps; int mipinfo[16][4]; int ddssize = 128; GLint internalformat; const char *ddsfourcc; if (!rt) return -1; // NULL pointer if (!strcmp(gl_version, "2.0.5885 WinXP Release")) return -2; // broken driver - crashes on reading internal format if (!qglGetTexLevelParameteriv) return -2; GL_ActiveTexture(0); oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR qglGetTexLevelParameteriv(gltexturetypeenums[glt->texturetype], 0, GL_TEXTURE_INTERNAL_FORMAT, &internalformat); switch(internalformat) { default: ddsfourcc = NULL;bytesperpixel = 4;break; case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: ddsfourcc = "DXT1";bytesperblock = 8;break; case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT3";bytesperblock = 16;break; case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT5";bytesperblock = 16;break; } // if premultiplied alpha, say so in the DDS file if(glt->flags & TEXF_RGBMULTIPLYBYALPHA) { switch(internalformat) { case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: ddsfourcc = "DXT2";break; case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: ddsfourcc = "DXT4";break; } } if (!bytesperblock && skipuncompressed) return -3; // skipped memset(mipinfo, 0, sizeof(mipinfo)); mipinfo[0][0] = glt->tilewidth; mipinfo[0][1] = glt->tileheight; mipmaps = 1; if ((glt->flags & TEXF_MIPMAP) && !(glt->tilewidth == 1 && glt->tileheight == 1)) { for (mip = 1;mip < 16;mip++) { mipinfo[mip][0] = mipinfo[mip-1][0] > 1 ? mipinfo[mip-1][0] >> 1 : 1; mipinfo[mip][1] = mipinfo[mip-1][1] > 1 ? mipinfo[mip-1][1] >> 1 : 1; if (mipinfo[mip][0] == 1 && mipinfo[mip][1] == 1) { mip++; break; } } mipmaps = mip; } for (mip = 0;mip < mipmaps;mip++) { mipinfo[mip][2] = bytesperblock ? ((mipinfo[mip][0]+3)/4)*((mipinfo[mip][1]+3)/4)*bytesperblock : mipinfo[mip][0]*mipinfo[mip][1]*bytesperpixel; mipinfo[mip][3] = ddssize; ddssize += mipinfo[mip][2]; } dds = (unsigned char *)Mem_Alloc(tempmempool, ddssize); if (!dds) return -4; dds_caps1 = 0x1000; // DDSCAPS_TEXTURE dds_caps2 = 0; if (bytesperblock) { dds_flags = 0x81007; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_LINEARSIZE dds_format_flags = 0x4; // DDPF_FOURCC } else { dds_flags = 0x100F; // DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_PITCH dds_format_flags = 0x40; // DDPF_RGB } if (mipmaps) { dds_flags |= 0x20000; // DDSD_MIPMAPCOUNT dds_caps1 |= 0x400008; // DDSCAPS_MIPMAP | DDSCAPS_COMPLEX } if(hasalpha) dds_format_flags |= 0x1; // DDPF_ALPHAPIXELS memcpy(dds, "DDS ", 4); StoreLittleLong(dds+4, 124); // http://msdn.microsoft.com/en-us/library/bb943982%28v=vs.85%29.aspx says so StoreLittleLong(dds+8, dds_flags); StoreLittleLong(dds+12, mipinfo[0][1]); // height StoreLittleLong(dds+16, mipinfo[0][0]); // width StoreLittleLong(dds+24, 0); // depth StoreLittleLong(dds+28, mipmaps); // mipmaps StoreLittleLong(dds+76, 32); // format size StoreLittleLong(dds+80, dds_format_flags); StoreLittleLong(dds+108, dds_caps1); StoreLittleLong(dds+112, dds_caps2); if (bytesperblock) { StoreLittleLong(dds+20, mipinfo[0][2]); // linear size memcpy(dds+84, ddsfourcc, 4); for (mip = 0;mip < mipmaps;mip++) { qglGetCompressedTexImageARB(gltexturetypeenums[glt->texturetype], mip, dds + mipinfo[mip][3]);CHECKGLERROR } } else { StoreLittleLong(dds+20, mipinfo[0][0]*bytesperpixel); // pitch StoreLittleLong(dds+88, bytesperpixel*8); // bits per pixel dds[94] = dds[97] = dds[100] = dds[107] = 255; // bgra byte order masks for (mip = 0;mip < mipmaps;mip++) { qglGetTexImage(gltexturetypeenums[glt->texturetype], mip, GL_BGRA, GL_UNSIGNED_BYTE, dds + mipinfo[mip][3]);CHECKGLERROR } } qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR ret = FS_WriteFile(filename, dds, ddssize); Mem_Free(dds); return ret ? ddssize : -5; #endif } #ifdef __ANDROID__ // ELUAN: FIXME: separate this code #include "ktx10/include/ktx.h" #endif rtexture_t *R_LoadTextureDDSFile(rtexturepool_t *rtexturepool, const char *filename, qboolean srgb, int flags, qboolean *hasalphaflag, float *avgcolor, int miplevel, qboolean optionaltexture) // DDS textures are opaque, so miplevel isn't a pointer but just seen as a hint { int i, size, dds_format_flags, dds_miplevels, dds_width, dds_height; //int dds_flags; textype_t textype; int bytesperblock, bytesperpixel; int mipcomplete; gltexture_t *glt; gltexturepool_t *pool = (gltexturepool_t *)rtexturepool; textypeinfo_t *texinfo; int mip, mipwidth, mipheight, mipsize, mipsize_total; unsigned int c, r, g, b; GLint oldbindtexnum = 0; unsigned char *mippixels; unsigned char *mippixels_start; unsigned char *ddspixels; unsigned char *dds; fs_offset_t ddsfilesize; unsigned int ddssize; qboolean force_swdecode, npothack; #ifdef __ANDROID__ // ELUAN: FIXME: separate this code char vabuf[1024]; char vabuf2[1024]; int strsize; KTX_dimensions sizes; #endif if (cls.state == ca_dedicated) return NULL; #ifdef __ANDROID__ // ELUAN: FIXME: separate this code if (vid.renderpath != RENDERPATH_GLES2) { Con_DPrintf("KTX texture format is only supported on the GLES2 renderpath\n"); return NULL; } // some textures are specified with extensions, so it becomes .tga.dds FS_StripExtension (filename, vabuf2, sizeof(vabuf2)); FS_StripExtension (vabuf2, vabuf, sizeof(vabuf)); FS_DefaultExtension (vabuf, ".ktx", sizeof(vabuf)); strsize = strlen(vabuf); if (strsize > 5) for (i = 0; i <= strsize - 4; i++) // copy null termination vabuf[i] = vabuf[i + 4]; Con_DPrintf("Loading %s...\n", vabuf); dds = FS_LoadFile(vabuf, tempmempool, true, &ddsfilesize); ddssize = ddsfilesize; if (!dds) { Con_DPrintf("Not found!\n"); return NULL; // not found } Con_DPrintf("Found!\n"); if (flags & TEXF_ALPHA) { Con_DPrintf("KTX texture with alpha not supported yet, disabling\n"); flags &= ~TEXF_ALPHA; } { GLenum target; GLenum glerror; GLboolean isMipmapped; KTX_error_code ktxerror; glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); // texture uploading can take a while, so make sure we're sending keepalives CL_KeepaliveMessage(false); // create the texture object CHECKGLERROR GL_ActiveTexture(0); oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[GLTEXTURETYPE_2D]); qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR qglBindTexture(gltexturetypeenums[GLTEXTURETYPE_2D], glt->texnum);CHECKGLERROR // upload the texture // we need to restore the texture binding after finishing the upload // NOTE: some drivers fail with ETC1 NPOT (only PowerVR?). This may make the driver crash later. ktxerror = ktxLoadTextureM(dds, ddssize, &glt->texnum, &target, &sizes, &isMipmapped, &glerror, 0, NULL);// can't CHECKGLERROR, the lib catches it // FIXME: delete texture if we fail here if (target != GL_TEXTURE_2D) { qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR Mem_Free(dds); Con_DPrintf("%s target != GL_TEXTURE_2D, target == %x\n", vabuf, target); return NULL; // FIXME: delete the texture from memory } if (KTX_SUCCESS == ktxerror) { textype = TEXTYPE_ETC1; flags &= ~TEXF_COMPRESS; // don't let the textype be wrong // return whether this texture is transparent if (hasalphaflag) *hasalphaflag = (flags & TEXF_ALPHA) != 0; // TODO: apply gl_picmip // TODO: avgcolor // TODO: srgb // TODO: only load mipmaps if requested if (isMipmapped) flags |= TEXF_MIPMAP; else flags &= ~TEXF_MIPMAP; texinfo = R_GetTexTypeInfo(textype, flags); strlcpy (glt->identifier, vabuf, sizeof(glt->identifier)); glt->pool = pool; glt->chain = pool->gltchain; pool->gltchain = glt; glt->inputwidth = sizes.width; glt->inputheight = sizes.height; glt->inputdepth = 1; glt->flags = flags; glt->textype = texinfo; glt->texturetype = GLTEXTURETYPE_2D; glt->inputdatasize = ddssize; glt->glinternalformat = texinfo->glinternalformat; glt->glformat = texinfo->glformat; glt->gltype = texinfo->gltype; glt->bytesperpixel = texinfo->internalbytesperpixel; glt->sides = 1; glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; glt->tilewidth = sizes.width; glt->tileheight = sizes.height; glt->tiledepth = 1; glt->miplevels = isMipmapped ? 1 : 0; // FIXME // after upload we have to set some parameters... #ifdef GL_TEXTURE_MAX_LEVEL /* FIXME if (dds_miplevels >= 1 && !mipcomplete) { // need to set GL_TEXTURE_MAX_LEVEL qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_LEVEL, dds_miplevels - 1);CHECKGLERROR } */ #endif GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR Mem_Free(dds); return (rtexture_t *)glt; } else { qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR Mem_Free(dds); Con_DPrintf("KTX texture %s failed to load: %x\n", vabuf, ktxerror); return NULL; } } #endif // __ANDROID__ dds = FS_LoadFile(filename, tempmempool, true, &ddsfilesize); ddssize = ddsfilesize; if (!dds) { if (r_texture_dds_load_logfailure.integer && (r_texture_dds_load_logfailure.integer >= 2 || !optionaltexture)) Log_Printf("ddstexturefailures.log", "%s\n", filename); return NULL; // not found } if (ddsfilesize <= 128 || memcmp(dds, "DDS ", 4) || ddssize < (unsigned int)BuffLittleLong(dds+4) || BuffLittleLong(dds+76) != 32) { Mem_Free(dds); Con_Printf("^1%s: not a DDS image\n", filename); return NULL; } //dds_flags = BuffLittleLong(dds+8); dds_format_flags = BuffLittleLong(dds+80); dds_miplevels = (BuffLittleLong(dds+108) & 0x400000) ? BuffLittleLong(dds+28) : 1; dds_width = BuffLittleLong(dds+16); dds_height = BuffLittleLong(dds+12); ddspixels = dds + 128; if(r_texture_dds_load_alphamode.integer == 0) if(!(dds_format_flags & 0x1)) // DDPF_ALPHAPIXELS flags &= ~TEXF_ALPHA; //flags &= ~TEXF_ALPHA; // disabled, as we DISABLE TEXF_ALPHA in the alpha detection, not enable it! if ((dds_format_flags & 0x40) && BuffLittleLong(dds+88) == 32) { // very sloppy BGRA 32bit identification textype = TEXTYPE_BGRA; flags &= ~TEXF_COMPRESS; // don't let the textype be wrong bytesperblock = 0; bytesperpixel = 4; size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(dds_width, dds_height), bytesperpixel); if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) { Mem_Free(dds); Con_Printf("^1%s: invalid BGRA DDS image\n", filename); return NULL; } if((r_texture_dds_load_alphamode.integer == 1) && (flags & TEXF_ALPHA)) { // check alpha for (i = 3;i < size;i += 4) if (ddspixels[i] < 255) break; if (i >= size) flags &= ~TEXF_ALPHA; } } else if (!memcmp(dds+84, "DXT1", 4)) { // we need to find out if this is DXT1 (opaque) or DXT1A (transparent) // LordHavoc: it is my belief that this does not infringe on the // patent because it is not decoding pixels... textype = TEXTYPE_DXT1; bytesperblock = 8; bytesperpixel = 0; //size = ((dds_width+3)/4)*((dds_height+3)/4)*bytesperblock; size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) { Mem_Free(dds); Con_Printf("^1%s: invalid DXT1 DDS image\n", filename); return NULL; } if (flags & TEXF_ALPHA) { if (r_texture_dds_load_alphamode.integer == 1) { // check alpha for (i = 0;i < size;i += bytesperblock) if (ddspixels[i+0] + ddspixels[i+1] * 256 <= ddspixels[i+2] + ddspixels[i+3] * 256) { // NOTE: this assumes sizeof(unsigned int) == 4 unsigned int data = * (unsigned int *) &(ddspixels[i+4]); // check if data, in base 4, contains a digit 3 (DXT1: transparent pixel) if(data & (data<<1) & 0xAAAAAAAA)//rgh break; } if (i < size) textype = TEXTYPE_DXT1A; else flags &= ~TEXF_ALPHA; } else if (r_texture_dds_load_alphamode.integer == 0) textype = TEXTYPE_DXT1A; else { flags &= ~TEXF_ALPHA; } } } else if (!memcmp(dds+84, "DXT3", 4) || !memcmp(dds+84, "DXT2", 4)) { if(!memcmp(dds+84, "DXT2", 4)) { if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) { Con_Printf("^1%s: expecting DXT3 image without premultiplied alpha, got DXT2 image with premultiplied alpha\n", filename); } } else { if(flags & TEXF_RGBMULTIPLYBYALPHA) { Con_Printf("^1%s: expecting DXT2 image without premultiplied alpha, got DXT3 image without premultiplied alpha\n", filename); } } textype = TEXTYPE_DXT3; bytesperblock = 16; bytesperpixel = 0; size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) { Mem_Free(dds); Con_Printf("^1%s: invalid DXT3 DDS image\n", filename); return NULL; } // we currently always assume alpha } else if (!memcmp(dds+84, "DXT5", 4) || !memcmp(dds+84, "DXT4", 4)) { if(!memcmp(dds+84, "DXT4", 4)) { if(!(flags & TEXF_RGBMULTIPLYBYALPHA)) { Con_Printf("^1%s: expecting DXT5 image without premultiplied alpha, got DXT4 image with premultiplied alpha\n", filename); } } else { if(flags & TEXF_RGBMULTIPLYBYALPHA) { Con_Printf("^1%s: expecting DXT4 image without premultiplied alpha, got DXT5 image without premultiplied alpha\n", filename); } } textype = TEXTYPE_DXT5; bytesperblock = 16; bytesperpixel = 0; size = INTOVERFLOW_MUL(INTOVERFLOW_MUL(INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_width, 3), 4), INTOVERFLOW_DIV(INTOVERFLOW_ADD(dds_height, 3), 4)), bytesperblock); if(INTOVERFLOW_ADD(128, size) > INTOVERFLOW_NORMALIZE(ddsfilesize)) { Mem_Free(dds); Con_Printf("^1%s: invalid DXT5 DDS image\n", filename); return NULL; } // we currently always assume alpha } else { Mem_Free(dds); Con_Printf("^1%s: unrecognized/unsupported DDS format\n", filename); return NULL; } // when requesting a non-alpha texture and we have DXT3/5, convert to DXT1 if(!(flags & TEXF_ALPHA) && (textype == TEXTYPE_DXT3 || textype == TEXTYPE_DXT5)) { textype = TEXTYPE_DXT1; bytesperblock = 8; ddssize -= 128; ddssize /= 2; for (i = 0;i < (int)ddssize;i += bytesperblock) memcpy(&ddspixels[i], &ddspixels[(i<<1)+8], 8); ddssize += 128; } force_swdecode = false; npothack = (!vid.support.arb_texture_non_power_of_two && ( (dds_width & (dds_width - 1)) || (dds_height & (dds_height - 1)) ) ); if(bytesperblock) { if(vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && !npothack) { if(r_texture_dds_swdecode.integer > 1) force_swdecode = true; } else { if(r_texture_dds_swdecode.integer < 1) { // unsupported Mem_Free(dds); return NULL; } force_swdecode = true; } } // return whether this texture is transparent if (hasalphaflag) *hasalphaflag = (flags & TEXF_ALPHA) != 0; // if we SW decode, choose 2 sizes bigger if(force_swdecode) { // this is quarter res, so do not scale down more than we have to miplevel -= 2; if(miplevel < 0) Con_DPrintf("WARNING: fake software decoding of compressed texture %s degraded quality\n", filename); } // this is where we apply gl_picmip mippixels_start = ddspixels; mipwidth = dds_width; mipheight = dds_height; while(miplevel >= 1 && dds_miplevels >= 1) { if (mipwidth <= 1 && mipheight <= 1) break; mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; mippixels_start += mipsize; // just skip --dds_miplevels; --miplevel; if (mipwidth > 1) mipwidth >>= 1; if (mipheight > 1) mipheight >>= 1; } mipsize_total = ddssize - 128 - (mippixels_start - ddspixels); mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; // from here on, we do not need the ddspixels and ddssize any more (apart from the statistics entry in glt) // fake decode S3TC if needed if(force_swdecode) { int mipsize_new = mipsize_total / bytesperblock * 4; unsigned char *mipnewpixels = (unsigned char *) Mem_Alloc(tempmempool, mipsize_new); unsigned char *p = mipnewpixels; for (i = bytesperblock == 16 ? 8 : 0;i < (int)mipsize_total;i += bytesperblock, p += 4) { c = mippixels_start[i] + 256*mippixels_start[i+1] + 65536*mippixels_start[i+2] + 16777216*mippixels_start[i+3]; p[2] = (((c >> 11) & 0x1F) + ((c >> 27) & 0x1F)) * (0.5f / 31.0f * 255.0f); p[1] = (((c >> 5) & 0x3F) + ((c >> 21) & 0x3F)) * (0.5f / 63.0f * 255.0f); p[0] = (((c ) & 0x1F) + ((c >> 16) & 0x1F)) * (0.5f / 31.0f * 255.0f); if(textype == TEXTYPE_DXT5) p[3] = (0.5 * mippixels_start[i-8] + 0.5 * mippixels_start[i-7]); else if(textype == TEXTYPE_DXT3) p[3] = ( (mippixels_start[i-8] & 0x0F) + (mippixels_start[i-8] >> 4) + (mippixels_start[i-7] & 0x0F) + (mippixels_start[i-7] >> 4) + (mippixels_start[i-6] & 0x0F) + (mippixels_start[i-6] >> 4) + (mippixels_start[i-5] & 0x0F) + (mippixels_start[i-5] >> 4) ) * (0.125f / 15.0f * 255.0f); else p[3] = 255; } textype = TEXTYPE_BGRA; bytesperblock = 0; bytesperpixel = 4; // as each block becomes a pixel, we must use pixel count for this mipwidth = (mipwidth + 3) / 4; mipheight = (mipheight + 3) / 4; mipsize = bytesperpixel * mipwidth * mipheight; mippixels_start = mipnewpixels; mipsize_total = mipsize_new; } // start mip counting mippixels = mippixels_start; // calculate average color if requested if (avgcolor) { float f; Vector4Clear(avgcolor); if (bytesperblock) { for (i = bytesperblock == 16 ? 8 : 0;i < mipsize;i += bytesperblock) { c = mippixels[i] + 256*mippixels[i+1] + 65536*mippixels[i+2] + 16777216*mippixels[i+3]; avgcolor[0] += ((c >> 11) & 0x1F) + ((c >> 27) & 0x1F); avgcolor[1] += ((c >> 5) & 0x3F) + ((c >> 21) & 0x3F); avgcolor[2] += ((c ) & 0x1F) + ((c >> 16) & 0x1F); if(textype == TEXTYPE_DXT5) avgcolor[3] += (mippixels[i-8] + (int) mippixels[i-7]) * (0.5f / 255.0f); else if(textype == TEXTYPE_DXT3) avgcolor[3] += ( (mippixels_start[i-8] & 0x0F) + (mippixels_start[i-8] >> 4) + (mippixels_start[i-7] & 0x0F) + (mippixels_start[i-7] >> 4) + (mippixels_start[i-6] & 0x0F) + (mippixels_start[i-6] >> 4) + (mippixels_start[i-5] & 0x0F) + (mippixels_start[i-5] >> 4) ) * (0.125f / 15.0f); else avgcolor[3] += 1.0f; } f = (float)bytesperblock / mipsize; avgcolor[0] *= (0.5f / 31.0f) * f; avgcolor[1] *= (0.5f / 63.0f) * f; avgcolor[2] *= (0.5f / 31.0f) * f; avgcolor[3] *= f; } else { for (i = 0;i < mipsize;i += 4) { avgcolor[0] += mippixels[i+2]; avgcolor[1] += mippixels[i+1]; avgcolor[2] += mippixels[i]; avgcolor[3] += mippixels[i+3]; } f = (1.0f / 255.0f) * bytesperpixel / mipsize; avgcolor[0] *= f; avgcolor[1] *= f; avgcolor[2] *= f; avgcolor[3] *= f; } } // if we want sRGB, convert now if(srgb) { if (vid.support.ext_texture_srgb) { switch(textype) { case TEXTYPE_DXT1: textype = TEXTYPE_SRGB_DXT1 ;break; case TEXTYPE_DXT1A: textype = TEXTYPE_SRGB_DXT1A ;break; case TEXTYPE_DXT3: textype = TEXTYPE_SRGB_DXT3 ;break; case TEXTYPE_DXT5: textype = TEXTYPE_SRGB_DXT5 ;break; case TEXTYPE_RGBA: textype = TEXTYPE_SRGB_RGBA ;break; default: break; } } else { switch(textype) { case TEXTYPE_DXT1: case TEXTYPE_DXT1A: case TEXTYPE_DXT3: case TEXTYPE_DXT5: { for (i = bytesperblock == 16 ? 8 : 0;i < mipsize_total;i += bytesperblock) { int c0, c1, c0new, c1new; c0 = mippixels_start[i] + 256*mippixels_start[i+1]; r = ((c0 >> 11) & 0x1F); g = ((c0 >> 5) & 0x3F); b = ((c0 ) & 0x1F); r = floor(Image_LinearFloatFromsRGB(r * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB g = floor(Image_LinearFloatFromsRGB(g * (255.0f / 63.0f)) * 63.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB b = floor(Image_LinearFloatFromsRGB(b * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB c0new = (r << 11) | (g << 5) | b; c1 = mippixels_start[i+2] + 256*mippixels_start[i+3]; r = ((c1 >> 11) & 0x1F); g = ((c1 >> 5) & 0x3F); b = ((c1 ) & 0x1F); r = floor(Image_LinearFloatFromsRGB(r * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB g = floor(Image_LinearFloatFromsRGB(g * (255.0f / 63.0f)) * 63.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB b = floor(Image_LinearFloatFromsRGB(b * (255.0f / 31.0f)) * 31.0f + 0.5f); // these multiplications here get combined with multiplications in Image_LinearFloatFromsRGB c1new = (r << 11) | (g << 5) | b; // swap the colors if needed to fix order if(c0 > c1) // thirds { if(c0new < c1new) { c = c0new; c0new = c1new; c1new = c; if(c0new == c1new) mippixels_start[i+4] ^= 0x55; mippixels_start[i+5] ^= 0x55; mippixels_start[i+6] ^= 0x55; mippixels_start[i+7] ^= 0x55; } else if(c0new == c1new) { mippixels_start[i+4] = 0x00; mippixels_start[i+5] = 0x00; mippixels_start[i+6] = 0x00; mippixels_start[i+7] = 0x00; } } else // half + transparent { if(c0new > c1new) { c = c0new; c0new = c1new; c1new = c; mippixels_start[i+4] ^= (~mippixels_start[i+4] >> 1) & 0x55; mippixels_start[i+5] ^= (~mippixels_start[i+5] >> 1) & 0x55; mippixels_start[i+6] ^= (~mippixels_start[i+6] >> 1) & 0x55; mippixels_start[i+7] ^= (~mippixels_start[i+7] >> 1) & 0x55; } } mippixels_start[i] = c0new & 255; mippixels_start[i+1] = c0new >> 8; mippixels_start[i+2] = c1new & 255; mippixels_start[i+3] = c1new >> 8; } } break; case TEXTYPE_RGBA: Image_MakeLinearColorsFromsRGB(mippixels, mippixels, mipsize_total / bytesperblock); break; default: break; } } } // when not requesting mipmaps, do not load them if(!(flags & TEXF_MIPMAP)) dds_miplevels = 0; if (dds_miplevels >= 1) flags |= TEXF_MIPMAP; else flags &= ~TEXF_MIPMAP; texinfo = R_GetTexTypeInfo(textype, flags); glt = (gltexture_t *)Mem_ExpandableArray_AllocRecord(&texturearray); strlcpy (glt->identifier, filename, sizeof(glt->identifier)); glt->pool = pool; glt->chain = pool->gltchain; pool->gltchain = glt; glt->inputwidth = mipwidth; glt->inputheight = mipheight; glt->inputdepth = 1; glt->flags = flags; glt->textype = texinfo; glt->texturetype = GLTEXTURETYPE_2D; glt->inputdatasize = ddssize; glt->glinternalformat = texinfo->glinternalformat; glt->glformat = texinfo->glformat; glt->gltype = texinfo->gltype; glt->bytesperpixel = texinfo->internalbytesperpixel; glt->sides = 1; glt->gltexturetypeenum = gltexturetypeenums[glt->texturetype]; glt->tilewidth = mipwidth; glt->tileheight = mipheight; glt->tiledepth = 1; glt->miplevels = dds_miplevels; if(npothack) { for (glt->tilewidth = 1;glt->tilewidth < mipwidth;glt->tilewidth <<= 1); for (glt->tileheight = 1;glt->tileheight < mipheight;glt->tileheight <<= 1); } // texture uploading can take a while, so make sure we're sending keepalives CL_KeepaliveMessage(false); // create the texture object switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR GL_ActiveTexture(0); oldbindtexnum = R_Mesh_TexBound(0, gltexturetypeenums[glt->texturetype]); qglGenTextures(1, (GLuint *)&glt->texnum);CHECKGLERROR qglBindTexture(gltexturetypeenums[glt->texturetype], glt->texnum);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DFORMAT d3dformat; D3DPOOL d3dpool; DWORD d3dusage; switch(textype) { case TEXTYPE_BGRA: d3dformat = (flags & TEXF_ALPHA) ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8;break; case TEXTYPE_DXT1: case TEXTYPE_DXT1A: d3dformat = D3DFMT_DXT1;break; case TEXTYPE_DXT3: d3dformat = D3DFMT_DXT3;break; case TEXTYPE_DXT5: d3dformat = D3DFMT_DXT5;break; default: d3dformat = D3DFMT_A8R8G8B8;Host_Error("R_LoadTextureDDSFile: unsupported texture type %i when picking D3DFMT", (int)textype);break; } d3dusage = 0; d3dpool = D3DPOOL_MANAGED; IDirect3DDevice9_CreateTexture(vid_d3d9dev, glt->tilewidth, glt->tileheight, glt->miplevels, d3dusage, d3dformat, d3dpool, (IDirect3DTexture9 **)&glt->d3dtexture, NULL); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: glt->texnum = DPSOFTRAST_Texture_New(((glt->flags & TEXF_CLAMP) ? DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE : 0) | (dds_miplevels > 1 ? DPSOFTRAST_TEXTURE_FLAG_MIPMAP : 0), glt->tilewidth, glt->tileheight, glt->tiledepth); break; } // upload the texture // we need to restore the texture binding after finishing the upload mipcomplete = false; for (mip = 0;mip <= dds_miplevels;mip++) // <= to include the not-counted "largest" miplevel { unsigned char *upload_mippixels = mippixels; int upload_mipwidth = mipwidth; int upload_mipheight = mipheight; mipsize = bytesperblock ? ((mipwidth+3)/4)*((mipheight+3)/4)*bytesperblock : mipwidth*mipheight*bytesperpixel; if (mippixels + mipsize > mippixels_start + mipsize_total) break; if(npothack) { upload_mipwidth = (glt->tilewidth >> mip); upload_mipheight = (glt->tileheight >> mip); if(upload_mipwidth != mipwidth || upload_mipheight != mipheight) // I _think_ they always mismatch, but I was too lazy // to properly check, and this test here is really // harmless { upload_mippixels = (unsigned char *) Mem_Alloc(tempmempool, 4 * upload_mipwidth * upload_mipheight); Image_Resample32(mippixels, mipwidth, mipheight, 1, upload_mippixels, upload_mipwidth, upload_mipheight, 1, r_lerpimages.integer); } } switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (bytesperblock) { qglCompressedTexImage2DARB(GL_TEXTURE_2D, mip, glt->glinternalformat, upload_mipwidth, upload_mipheight, 0, mipsize, upload_mippixels);CHECKGLERROR } else { qglTexImage2D(GL_TEXTURE_2D, mip, glt->glinternalformat, upload_mipwidth, upload_mipheight, 0, glt->glformat, glt->gltype, upload_mippixels);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DLOCKED_RECT d3dlockedrect; if (IDirect3DTexture9_LockRect((IDirect3DTexture9*)glt->d3dtexture, mip, &d3dlockedrect, NULL, 0) == D3D_OK && d3dlockedrect.pBits) { memcpy(d3dlockedrect.pBits, upload_mippixels, mipsize); IDirect3DTexture9_UnlockRect((IDirect3DTexture9*)glt->d3dtexture, mip); } break; } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (bytesperblock) Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); else DPSOFTRAST_Texture_UpdateFull(glt->texnum, upload_mippixels); // DPSOFTRAST calculates its own mipmaps mip = dds_miplevels; break; } if(upload_mippixels != mippixels) Mem_Free(upload_mippixels); mippixels += mipsize; if (mipwidth <= 1 && mipheight <= 1) { mipcomplete = true; break; } if (mipwidth > 1) mipwidth >>= 1; if (mipheight > 1) mipheight >>= 1; } // after upload we have to set some parameters... switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #ifdef GL_TEXTURE_MAX_LEVEL if (dds_miplevels >= 1 && !mipcomplete) { // need to set GL_TEXTURE_MAX_LEVEL qglTexParameteri(gltexturetypeenums[glt->texturetype], GL_TEXTURE_MAX_LEVEL, dds_miplevels - 1);CHECKGLERROR } #endif GL_SetupTextureParameters(glt->flags, glt->textype->textype, glt->texturetype); qglBindTexture(gltexturetypeenums[glt->texturetype], oldbindtexnum);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D glt->d3daddressw = 0; if (glt->flags & TEXF_CLAMP) { glt->d3daddressu = D3DTADDRESS_CLAMP; glt->d3daddressv = D3DTADDRESS_CLAMP; if (glt->tiledepth > 1) glt->d3daddressw = D3DTADDRESS_CLAMP; } else { glt->d3daddressu = D3DTADDRESS_WRAP; glt->d3daddressv = D3DTADDRESS_WRAP; if (glt->tiledepth > 1) glt->d3daddressw = D3DTADDRESS_WRAP; } glt->d3dmipmaplodbias = 0; glt->d3dmaxmiplevel = 0; glt->d3dmaxmiplevelfilter = 0; if (glt->flags & TEXF_MIPMAP) { glt->d3dminfilter = d3d_filter_mipmin; glt->d3dmagfilter = d3d_filter_mipmag; glt->d3dmipfilter = d3d_filter_mipmix; } else { glt->d3dminfilter = d3d_filter_flatmin; glt->d3dmagfilter = d3d_filter_flatmag; glt->d3dmipfilter = d3d_filter_flatmix; } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (glt->flags & TEXF_FORCELINEAR) DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_LINEAR); else if (glt->flags & TEXF_FORCENEAREST) DPSOFTRAST_Texture_Filter(glt->texnum, DPSOFTRAST_TEXTURE_FILTER_NEAREST); else if (glt->flags & TEXF_MIPMAP) DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_mipmap); else DPSOFTRAST_Texture_Filter(glt->texnum, dpsoftrast_filter_nomipmap); break; } Mem_Free(dds); if(force_swdecode) Mem_Free((unsigned char *) mippixels_start); return (rtexture_t *)glt; } int R_TextureWidth(rtexture_t *rt) { return rt ? ((gltexture_t *)rt)->inputwidth : 0; } int R_TextureHeight(rtexture_t *rt) { return rt ? ((gltexture_t *)rt)->inputheight : 0; } int R_TextureFlags(rtexture_t *rt) { return rt ? ((gltexture_t *)rt)->flags : 0; } void R_UpdateTexture(rtexture_t *rt, const unsigned char *data, int x, int y, int z, int width, int height, int depth) { gltexture_t *glt = (gltexture_t *)rt; if (data == NULL) Host_Error("R_UpdateTexture: no data supplied"); if (glt == NULL) Host_Error("R_UpdateTexture: no texture supplied"); if (!glt->texnum && !glt->d3dtexture) { Con_DPrintf("R_UpdateTexture: texture %p \"%s\" in pool %p has not been uploaded yet\n", (void *)glt, glt->identifier, (void *)glt->pool); return; } // update part of the texture if (glt->bufferpixels) { int j; int bpp = glt->bytesperpixel; int inputskip = width*bpp; int outputskip = glt->tilewidth*bpp; const unsigned char *input = data; unsigned char *output = glt->bufferpixels; if (glt->inputdepth != 1 || glt->sides != 1) Sys_Error("R_UpdateTexture on buffered texture that is not 2D\n"); if (x < 0) { width += x; input -= x*bpp; x = 0; } if (y < 0) { height += y; input -= y*inputskip; y = 0; } if (width > glt->tilewidth - x) width = glt->tilewidth - x; if (height > glt->tileheight - y) height = glt->tileheight - y; if (width < 1 || height < 1) return; glt->dirty = true; glt->buffermodified = true; output += y*outputskip + x*bpp; for (j = 0;j < height;j++, output += outputskip, input += inputskip) memcpy(output, input, width*bpp); } else if (x || y || z || width != glt->inputwidth || height != glt->inputheight || depth != glt->inputdepth) R_UploadPartialTexture(glt, data, x, y, z, width, height, depth); else R_UploadFullTexture(glt, data); } int R_RealGetTexture(rtexture_t *rt) { if (rt) { gltexture_t *glt; glt = (gltexture_t *)rt; if (glt->flags & GLTEXF_DYNAMIC) R_UpdateDynamicTexture(glt); if (glt->buffermodified && glt->bufferpixels) { glt->buffermodified = false; R_UploadFullTexture(glt, glt->bufferpixels); } glt->dirty = false; return glt->texnum; } else return 0; } void R_ClearTexture (rtexture_t *rt) { gltexture_t *glt = (gltexture_t *)rt; R_UploadFullTexture(glt, NULL); } int R_PicmipForFlags(int flags) { int miplevel = 0; if(flags & TEXF_PICMIP) { miplevel += gl_picmip.integer; if (flags & TEXF_ISWORLD) { if (r_picmipworld.integer) miplevel += gl_picmip_world.integer; else miplevel = 0; } else if (flags & TEXF_ISSPRITE) { if (r_picmipsprites.integer) miplevel += gl_picmip_sprites.integer; else miplevel = 0; } else miplevel += gl_picmip_other.integer; } return max(0, miplevel); } darkplaces/cl_video_libavw.c0000664000175000017500000002713313067716216015472 0ustar kalevkalev/* Libavcodec integration for Darkplaces by Timofeyev Pavel This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ // LordHavoc: for some reason this is being #include'd rather than treated as its own file... // LordHavoc: adapted to not require stdint.h as this is not available on MSVC++, using unsigned char instead of uint8_t and fs_offset_t instead of int64_t. // scaler type #define LIBAVW_SCALER_BILINEAR 0 #define LIBAVW_SCALER_BICUBIC 1 #define LIBAVW_SCALER_X 2 #define LIBAVW_SCALER_POINT 3 #define LIBAVW_SCALER_AREA 4 #define LIBAVW_SCALER_BICUBLIN 5 #define LIBAVW_SCALER_GAUSS 6 #define LIBAVW_SCALER_SINC 7 #define LIBAVW_SCALER_LANCZOS 8 #define LIBAVW_SCALER_SPLINE 9 // output format #define LIBAVW_PIXEL_FORMAT_BGR 0 #define LIBAVW_PIXEL_FORMAT_BGRA 1 // print levels #define LIBAVW_PRINT_WARNING 1 #define LIBAVW_PRINT_ERROR 2 #define LIBAVW_PRINT_FATAL 3 #define LIBAVW_PRINT_PANIC 4 // exported callback functions: typedef void avwCallbackPrint(int, const char *); typedef int avwCallbackIoRead(void *, unsigned char *, int); typedef fs_offset_t avwCallbackIoSeek(void *, fs_offset_t, int); typedef fs_offset_t avwCallbackIoSeekSize(void *); // exported functions: int (*qLibAvW_Init)(avwCallbackPrint *printfunction); // init library, returns error code const char *(*qLibAvW_ErrorString)(int errorcode); // get string for error code const char *(*qLibAvW_AvcVersion)(void); // get a string containing libavcodec version wrapper was built for float (*qLibAvW_Version)(void); // get wrapper version int (*qLibAvW_CreateStream)(void **stream); // create stream, returns error code void (*qLibAvW_RemoveStream)(void *stream); // flush and remove stream int (*qLibAvW_StreamGetVideoWidth)(void *stream); // get video parameters of stream int (*qLibAvW_StreamGetVideoHeight)(void *stream); double (*qLibAvW_StreamGetFramerate)(void *stream); int (*qLibAvW_StreamGetError)(void *stream); // get last function errorcode from stream // simple API to play video int (*qLibAvW_PlayVideo)(void *stream, void *file, avwCallbackIoRead *IoRead, avwCallbackIoSeek *IoSeek, avwCallbackIoSeekSize *IoSeekSize); int (*qLibAvW_PlaySeekNextFrame)(void *stream); int (*qLibAvW_PlayGetFrameImage)(void *stream, int pixel_format, void *imagedata, int imagewidth, int imageheight, int scaler); static dllfunction_t libavwfuncs[] = { {"LibAvW_Init", (void **) &qLibAvW_Init }, {"LibAvW_ErrorString", (void **) &qLibAvW_ErrorString }, {"LibAvW_AvcVersion", (void **) &qLibAvW_AvcVersion }, {"LibAvW_Version", (void **) &qLibAvW_Version }, {"LibAvW_CreateStream", (void **) &qLibAvW_CreateStream }, {"LibAvW_RemoveStream", (void **) &qLibAvW_RemoveStream }, {"LibAvW_StreamGetVideoWidth", (void **) &qLibAvW_StreamGetVideoWidth }, {"LibAvW_StreamGetVideoHeight",(void **) &qLibAvW_StreamGetVideoHeight }, {"LibAvW_StreamGetFramerate", (void **) &qLibAvW_StreamGetFramerate }, {"LibAvW_StreamGetError", (void **) &qLibAvW_StreamGetError }, {"LibAvW_PlayVideo", (void **) &qLibAvW_PlayVideo }, {"LibAvW_PlaySeekNextFrame", (void **) &qLibAvW_PlaySeekNextFrame }, {"LibAvW_PlayGetFrameImage", (void **) &qLibAvW_PlayGetFrameImage }, {NULL, NULL} }; const char* dllnames_libavw[] = { #if defined(WIN32) "libavw.dll", #elif defined(MACOSX) "libavw.dylib", #else "libavw.so.1", "libavw.so", #endif NULL }; static dllhandle_t libavw_dll = NULL; // DP videostream typedef struct libavwstream_s { qfile_t *file; double info_framerate; unsigned int info_imagewidth; unsigned int info_imageheight; double info_aspectratio; void *stream; // channel the sound file is being played on sfx_t *sfx; int sndchan; int sndstarted; } libavwstream_t; cvar_t cl_video_libavw_minwidth = {CVAR_SAVE, "cl_video_libavw_minwidth", "0", "if videos width is lesser than minimal, thay will be upscaled"}; cvar_t cl_video_libavw_minheight = {CVAR_SAVE, "cl_video_libavw_minheight", "0", "if videos height is lesser than minimal, thay will be upscaled"}; cvar_t cl_video_libavw_scaler = {CVAR_SAVE, "cl_video_libavw_scaler", "1", "selects a scaler for libavcode played videos. Scalers are: 0 - bilinear, 1 - bicubic, 2 - x, 3 - point, 4 - area, 5 - bicublin, 6 - gauss, 7 - sinc, 8 - lanczos, 9 - spline."}; // video extensions const char* libavw_extensions[] = { "ogv", "avi", "mpg", "mp4", "mkv", "webm", "bik", "roq", "flv", "wmv", "mpeg", "mjpeg", "mpeg4", NULL }; /* ================================================================= Video decoding a features that is not supported yet and likely to be done - streaming audio from videofiles - streaming subtitles ================================================================= */ unsigned int libavw_getwidth(void *stream); unsigned int libavw_getheight(void *stream); double libavw_getframerate(void *stream); double libavw_getaspectratio(void *stream); void libavw_close(void *stream); static int libavw_decodeframe(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) { int pixel_format = LIBAVW_PIXEL_FORMAT_BGR; int errorcode; libavwstream_t *s = (libavwstream_t *)stream; // start sound if (!s->sndstarted) { if (s->sfx != NULL) s->sndchan = S_StartSound(-1, 0, s->sfx, vec3_origin, 1.0f, 0); s->sndstarted = 1; } // read frame if (!qLibAvW_PlaySeekNextFrame(s->stream)) { // got error or file end errorcode = qLibAvW_StreamGetError(s->stream); if (errorcode) Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(errorcode)); return 1; } // decode into bgr texture if (bytesperpixel == 4) pixel_format = LIBAVW_PIXEL_FORMAT_BGRA; else if (bytesperpixel == 3) pixel_format = LIBAVW_PIXEL_FORMAT_BGR; else { Con_Printf("LibAvW: cannot determine pixel format for bpp %i\n", bytesperpixel); return 1; } if (!qLibAvW_PlayGetFrameImage(s->stream, pixel_format, imagedata, s->info_imagewidth, s->info_imageheight, min(9, max(0, cl_video_libavw_scaler.integer)))) Con_Printf("LibAvW: %s\n", qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream))); return 0; } // get stream info unsigned int libavw_getwidth(void *stream) { return ((libavwstream_t *)stream)->info_imagewidth; } unsigned int libavw_getheight(void *stream) { return ((libavwstream_t *)stream)->info_imageheight; } double libavw_getframerate(void *stream) { return ((libavwstream_t *)stream)->info_framerate; } double libavw_getaspectratio(void *stream) { return ((libavwstream_t *)stream)->info_aspectratio; } // close stream void libavw_close(void *stream) { libavwstream_t *s = (libavwstream_t *)stream; if (s->stream) qLibAvW_RemoveStream(s->stream); s->stream = NULL; if (s->file) FS_Close(s->file); s->file = NULL; if (s->sndchan >= 0) S_StopChannel(s->sndchan, true, true); s->sndchan = -1; } // IO wrapper static int LibAvW_FS_Read(void *opaque, unsigned char *buf, int buf_size) { return FS_Read((qfile_t *)opaque, buf, buf_size); } static fs_offset_t LibAvW_FS_Seek(void *opaque, fs_offset_t pos, int whence) { return (fs_offset_t)FS_Seek((qfile_t *)opaque, pos, whence); } static fs_offset_t LibAvW_FS_SeekSize(void *opaque) { return (fs_offset_t)FS_FileSize((qfile_t *)opaque); } // open as DP video stream static void *LibAvW_OpenVideo(clvideo_t *video, char *filename, const char **errorstring) { libavwstream_t *s; char filebase[MAX_OSPATH], check[MAX_OSPATH]; unsigned int i; int errorcode; char *wavename; size_t len; if (!libavw_dll) return NULL; // allocate stream s = (libavwstream_t *)Z_Malloc(sizeof(libavwstream_t)); if (s == NULL) { *errorstring = "unable to allocate memory for stream info structure"; return NULL; } memset(s, 0, sizeof(libavwstream_t)); s->sndchan = -1; // open file s->file = FS_OpenVirtualFile(filename, true); if (!s->file) { FS_StripExtension(filename, filebase, sizeof(filebase)); // we tried .dpv, try another extensions for (i = 0; libavw_extensions[i] != NULL; i++) { dpsnprintf(check, sizeof(check), "%s.%s", filebase, libavw_extensions[i]); s->file = FS_OpenVirtualFile(check, true); if (s->file) break; } if (!s->file) { *errorstring = "unable to open videofile"; libavw_close(s); Z_Free(s); return NULL; } } // allocate libavw stream if ((errorcode = qLibAvW_CreateStream(&s->stream))) { *errorstring = qLibAvW_ErrorString(errorcode); libavw_close(s); Z_Free(s); return NULL; } // open video for playing if (!qLibAvW_PlayVideo(s->stream, s->file, &LibAvW_FS_Read, &LibAvW_FS_Seek, &LibAvW_FS_SeekSize)) { *errorstring = qLibAvW_ErrorString(qLibAvW_StreamGetError(s->stream)); libavw_close(s); Z_Free(s); return NULL; } // all right, start codec s->info_imagewidth = qLibAvW_StreamGetVideoWidth(s->stream); s->info_imageheight = qLibAvW_StreamGetVideoHeight(s->stream); s->info_framerate = qLibAvW_StreamGetFramerate(s->stream); s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; video->close = libavw_close; video->getwidth = libavw_getwidth; video->getheight = libavw_getheight; video->getframerate = libavw_getframerate; video->decodeframe = libavw_decodeframe; video->getaspectratio = libavw_getaspectratio; // apply min-width, min-height, keep aspect rate if (cl_video_libavw_minwidth.integer > 0) s->info_imagewidth = max(s->info_imagewidth, (unsigned int)cl_video_libavw_minwidth.integer); if (cl_video_libavw_minheight.integer > 0) s->info_imageheight = max(s->info_imageheight, (unsigned int)cl_video_libavw_minheight.integer); // provide sound in separate .wav len = strlen(filename) + 10; wavename = (char *)Z_Malloc(len); if (wavename) { FS_StripExtension(filename, wavename, len-1); strlcat(wavename, ".wav", len); s->sfx = S_PrecacheSound(wavename, false, false); s->sndchan = -1; Z_Free(wavename); } return s; } static void libavw_message(int level, const char *message) { if (level == LIBAVW_PRINT_WARNING) Con_Printf("LibAvcodec warning: %s\n", message); else if (level == LIBAVW_PRINT_ERROR) Con_Printf("LibAvcodec error: %s\n", message); else if (level == LIBAVW_PRINT_FATAL) Con_Printf("LibAvcodec fatal error: %s\n", message); else Con_Printf("LibAvcodec panic: %s\n", message); } static qboolean LibAvW_OpenLibrary(void) { int errorcode; // COMMANDLINEOPTION: Video: -nolibavw disables libavcodec wrapper support if (COM_CheckParm("-nolibavw")) return false; // load DLL's Sys_LoadLibrary(dllnames_libavw, &libavw_dll, libavwfuncs); if (!libavw_dll) return false; // initialize libav wrapper if ((errorcode = qLibAvW_Init(&libavw_message))) { Con_Printf("LibAvW failed to initialize: %s\n", qLibAvW_ErrorString(errorcode)); Sys_UnloadLibrary(&libavw_dll); } Cvar_RegisterVariable(&cl_video_libavw_minwidth); Cvar_RegisterVariable(&cl_video_libavw_minheight); Cvar_RegisterVariable(&cl_video_libavw_scaler); return true; } static void LibAvW_CloseLibrary(void) { Sys_UnloadLibrary(&libavw_dll); } darkplaces/nexuiz.xpm0000664000175000017500000000751213067716222014242 0ustar kalevkalev/* XPM */ static char * nexuiz_xpm[] = { "48 48 91 1", " c None", ". c #0E100D", "+ c #131412", "@ c #1D1E1C", "# c #1F211E", "$ c #212320", "% c #232522", "& c #252724", "* c #272826", "= c #2A2C29", "- c #2D2E2C", "; c #2F312E", "> c #323331", ", c #343633", "' c #363735", ") c #383937", "! c #393B38", "~ c #3C3D3B", "{ c #3D3F3C", "] c #3F403E", "^ c #40413F", "/ c #414340", "( c #444643", "_ c #454744", ": c #464845", "< c #474946", "[ c #484947", "} c #494B48", "| c #4A4C4A", "1 c #4C4E4B", "2 c #4D4F4C", "3 c #4F514E", "4 c #50524F", "5 c #525351", "6 c #545653", "7 c #575956", "8 c #5A5C59", "9 c #5C5E5B", "0 c #5F615E", "a c #616360", "b c #636562", "c c #656764", "d c #686A67", "e c #6B6D6A", "f c #6E6F6D", "g c #70726F", "h c #727471", "i c #757774", "j c #787977", "k c #7B7D7A", "l c #7E807D", "m c #818380", "n c #838582", "o c #858784", "p c #878986", "q c #898B88", "r c #8C8E8B", "s c #8E908D", "t c #90928F", "u c #939592", "v c #959794", "w c #979996", "x c #9A9C99", "y c #9FA19E", "z c #A5A7A4", "A c #AAACA9", "B c #B0B2AF", "C c #B5B7B4", "D c #B9BBB8", "E c #BEC0BD", "F c #C3C5C2", "G c #C7CAC6", "H c #CDCFCB", "I c #D2D4D1", "J c #D5D8D4", "K c #D8DAD7", "L c #DBDDDA", "M c #DEE0DD", "N c #E1E3E0", "O c #E4E6E2", "P c #E7E9E6", "Q c #EAECE8", "R c #EDEFEC", "S c #F1F3EF", "T c #F3F5F2", "U c #F5F7F4", "V c #F7F9F6", "W c #F8FBF7", "X c #FAFCF9", "Y c #FBFDFA", "Z c #FDFFFC", " #%=--=&# ", " #'3ekoqrrrokd2,# ", " *3ke60kuxwuuvwxwtk6= ", " &7pxs[Cxb[6juuttttuvxr0= ", " @_nwutu2AZZLx8}quttttttuxq2# ", " %cuusssum}QZZZWw_stsssssssuwg= ", " =kvssssssv3yZYYZZgcussstuutssvn' ", " >murrrrrrrucjZXXYZy4vutqkc0iptuup! ", " -mtrrrrrrrrt0oZWWXZu}i81}8lpb11alur' ", " %isqqqqqqqqqs2AZWWYSe(drDMWZZYOCk4 ", " !a000000aa94({^]zWSSRi!80aa00048SSSSSPb}a000b^ ", " /97777777788887'DWRSJ228777778:eVRRRSI[487778[ ", "#:65555555555553(LSRSB!45555556{mWRRRSB!455555}$", "$_1}}}}}}}}}}}1]0SQRQk)2}}}}}}2'wVQQRRp)1}}}}1[&", "%/_(((((((((((_>pUQRJ4{(((((((_>AUQQQOa!_((((((&", "%{^]]]]]]]]]]]]>CSPRB){]]]]]]]{'ESPPRG_~]]]]]]]&", "$=============*/LQPPm&========*!JRPPRA;========%", "#..............eSPQJ6+.........(MPPPPo@........@", "$&***********&&zSOQB'&********%8PPOPL0%&*******$", "$=-----------=(LPPMi=---------&gROOPF^=-------=$", " =>>>>>>>>>>>*oSNPC{;>>>>>>>>>=rRNOPy;>>>>>>>>-$", " =!)))))))))'{IPOKe;!)))))))));BQNOMg;)))))))!- ", " &{{{{{{{{{{-oRNNv,{{{{{{{{{{~{HOMOG<){{{{{{{{= ", " $~(///////{(JOOB(!//////////!7OMMOw,///////({$ ", " >[::::::<;xQNE4)<:::::::::<,nQMMJ0!<::::::[' ", " &<}}}}}})0ONE8'}}}}}}}}}}}}'COLNA!:}}}}}}}}* ", " !2}}}2(^HOC6'11}}}}}}}}}1/3LMMJd!2}}}}}}2{# ", " &}222}>APz}!2222222222223,rPLMA~}22222221= ", " >322>rLn{{3222222222223_(IMLIa!22222224) ", " #]3!iE0'(21111111112:~);xOJLv)}1111112($ ", " &{}l/)[}}}}}}}}}}}})qrlMJLE2{}}}}}}}<* ", " &!)^<____________:)hQMJJIi,:_____:_= ", " =^(//////////////{!DLJJx){//////^= ", " &){~!!!!!!!!!!!!~;hMKC},~!!!!~!* ", " %;'',,,,,,,,,,,,>)DG0-',,,''>% ", " #*-;;;;;;;;;;;;;=ek--;;;;-*$ ", " $&=============;>=====*% ", " $$%%%%%%%%%%%%%%%%%$# ", " $##############$ ", " $######$$ "}; darkplaces/model_brush.h0000664000175000017500000004720213067716222014646 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MODEL_BRUSH_H #define MODEL_BRUSH_H /* ============================================================================== BRUSH MODELS ============================================================================== */ // // in memory representation // typedef struct mvertex_s { vec3_t position; } mvertex_t; #define SIDE_FRONT 0 #define SIDE_BACK 1 #define SIDE_ON 2 // plane_t structure typedef struct mplane_s { union { struct { vec3_t normal; vec_t dist; }; vec4_t normal_and_dist; }; // for texture axis selection and fast side tests int type; // set by PlaneClassify() int signbits; // set by PlaneClassify() } mplane_t; #define SHADERSTAGE_SKY 0 #define SHADERSTAGE_NORMAL 1 #define SHADERSTAGE_COUNT 2 //#define SURF_PLANEBACK 2 // indicates that all triangles of the surface should be added to the BIH collision system #define MATERIALFLAG_MESHCOLLISIONS 1 // use alpha blend on this material #define MATERIALFLAG_ALPHA 2 // use additive blend on this material #define MATERIALFLAG_ADD 4 // turn off depth test on this material #define MATERIALFLAG_NODEPTHTEST 8 // multiply alpha by r_wateralpha cvar #define MATERIALFLAG_WATERALPHA 16 // draw with no lighting #define MATERIALFLAG_FULLBRIGHT 32 // drawn as a normal surface (alternative to SKY) #define MATERIALFLAG_WALL 64 // this surface shows the sky in its place, alternative to WALL // skipped if transparent #define MATERIALFLAG_SKY 128 // swirling water effect (used with MATERIALFLAG_WALL) #define MATERIALFLAG_WATERSCROLL 256 // skips drawing the surface #define MATERIALFLAG_NODRAW 512 // probably used only on q1bsp water #define MATERIALFLAG_LIGHTBOTHSIDES 1024 // use alpha test on this material #define MATERIALFLAG_ALPHATEST 2048 // treat this material as a blended transparency (as opposed to an alpha test // transparency), this causes special fog behavior, and disables glDepthMask #define MATERIALFLAG_BLENDED 4096 // render using a custom blendfunc #define MATERIALFLAG_CUSTOMBLEND 8192 // do not cast shadows from this material #define MATERIALFLAG_NOSHADOW 16384 // render using vertex alpha (q3bsp) as texture blend parameter between foreground (normal) skinframe and background skinframe #define MATERIALFLAG_VERTEXTEXTUREBLEND 32768 // disables GL_CULL_FACE on this texture (making it double sided) #define MATERIALFLAG_NOCULLFACE 65536 // render with a very short depth range (like 10% of normal), this causes entities to appear infront of most of the scene #define MATERIALFLAG_SHORTDEPTHRANGE 131072 // render water, comprising refraction and reflection (note: this is always opaque, the shader does the alpha effect) #define MATERIALFLAG_WATERSHADER 262144 // render refraction (note: this is just a way to distort the background, otherwise useless) #define MATERIALFLAG_REFRACTION 524288 // render reflection #define MATERIALFLAG_REFLECTION 1048576 // use model lighting on this material (q1bsp lightmap sampling or q3bsp lightgrid, implies FULLBRIGHT is false) #define MATERIALFLAG_MODELLIGHT 4194304 // add directional model lighting to this material (q3bsp lightgrid only) #define MATERIALFLAG_MODELLIGHT_DIRECTIONAL 8388608 // causes RSurf_GetCurrentTexture to leave alone certain fields #define MATERIALFLAG_CUSTOMSURFACE 16777216 // causes MATERIALFLAG_BLENDED to render a depth pass before rendering, hiding backfaces and other hidden geometry #define MATERIALFLAG_TRANSDEPTH 33554432 // like refraction, but doesn't distort etc. #define MATERIALFLAG_CAMERA 67108864 // disable rtlight on surface, use R_LightPoint instead #define MATERIALFLAG_NORTLIGHT 134217728 // alphagen vertex #define MATERIALFLAG_ALPHAGEN_VERTEX 268435456 // use occlusion buffer for corona #define MATERIALFLAG_OCCLUDE 536870912 // combined mask of all attributes that require depth sorted rendering #define MATERIALFLAGMASK_DEPTHSORTED (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST) // combined mask of all attributes that cause some sort of transparency #define MATERIALFLAGMASK_TRANSLUCENT (MATERIALFLAG_WATERALPHA | MATERIALFLAG_SKY | MATERIALFLAG_NODRAW | MATERIALFLAG_ALPHATEST | MATERIALFLAG_BLENDED | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION) typedef struct medge_s { unsigned int v[2]; } medge_t; struct entity_render_s; struct texture_s; struct msurface_s; typedef struct mnode_s { //this part shared between node and leaf mplane_t *plane; // != NULL struct mnode_s *parent; struct mportal_s *portals; // for bounding box culling vec3_t mins; vec3_t maxs; // supercontents from all brushes inside this node or leaf int combinedsupercontents; // this part unique to node struct mnode_s *children[2]; // q1bsp specific unsigned int firstsurface; unsigned int numsurfaces; } mnode_t; typedef struct mleaf_s { //this part shared between node and leaf mplane_t *plane; // == NULL struct mnode_s *parent; struct mportal_s *portals; // for bounding box culling vec3_t mins; vec3_t maxs; // supercontents from all brushes inside this node or leaf int combinedsupercontents; // this part unique to leaf // common int clusterindex; // -1 is not in pvs, >= 0 is pvs bit number int areaindex; // q3bsp int containscollisionsurfaces; // indicates whether the leafsurfaces contains q3 patches int numleafsurfaces; int *firstleafsurface; int numleafbrushes; // q3bsp int *firstleafbrush; // q3bsp unsigned char ambient_sound_level[NUM_AMBIENTS]; // q1bsp int contents; // q1bsp: // TODO: remove (only used temporarily during loading when making collision hull 0) int portalmarkid; // q1bsp // used by see-polygon-through-portals visibility checker } mleaf_t; typedef struct mclipnode_s { int planenum; int children[2]; // negative numbers are contents } mclipnode_t; typedef struct hull_s { mclipnode_t *clipnodes; mplane_t *planes; int firstclipnode; int lastclipnode; vec3_t clip_mins; vec3_t clip_maxs; vec3_t clip_size; } hull_t; typedef struct mportal_s { struct mportal_s *next; // the next portal on this leaf mleaf_t *here; // the leaf this portal is on mleaf_t *past; // the leaf through this portal (infront) int numpoints; mvertex_t *points; vec3_t mins, maxs; // culling mplane_t plane; } mportal_t; typedef struct svbspmesh_s { struct svbspmesh_s *next; int numverts, maxverts; int numtriangles, maxtriangles; float *verts; int *elements; } svbspmesh_t; // Q2 bsp stuff #define Q2BSPMAGIC ('I' + 'B' * 256 + 'S' * 65536 + 'P' * 16777216) #define Q2BSPVERSION 38 // leaffaces, leafbrushes, planes, and verts are still bounded by // 16 bit short limits //============================================================================= #define Q2LUMP_ENTITIES 0 #define Q2LUMP_PLANES 1 #define Q2LUMP_VERTEXES 2 #define Q2LUMP_VISIBILITY 3 #define Q2LUMP_NODES 4 #define Q2LUMP_TEXINFO 5 #define Q2LUMP_FACES 6 #define Q2LUMP_LIGHTING 7 #define Q2LUMP_LEAFS 8 #define Q2LUMP_LEAFFACES 9 #define Q2LUMP_LEAFBRUSHES 10 #define Q2LUMP_EDGES 11 #define Q2LUMP_SURFEDGES 12 #define Q2LUMP_MODELS 13 #define Q2LUMP_BRUSHES 14 #define Q2LUMP_BRUSHSIDES 15 #define Q2LUMP_POP 16 #define Q2LUMP_AREAS 17 #define Q2LUMP_AREAPORTALS 18 #define Q2HEADER_LUMPS 19 typedef struct q2dheader_s { int ident; int version; lump_t lumps[Q2HEADER_LUMPS]; } q2dheader_t; typedef struct q2dmodel_s { float mins[3], maxs[3]; float origin[3]; // for sounds or lights int headnode; int firstface, numfaces; // submodels just draw faces // without walking the bsp tree } q2dmodel_t; // planes (x&~1) and (x&~1)+1 are always opposites // contents flags are seperate bits // a given brush can contribute multiple content bits // multiple brushes can be in a single leaf // these definitions also need to be in q_shared.h! // lower bits are stronger, and will eat weaker brushes completely #define Q2CONTENTS_SOLID 1 // an eye is never valid in a solid #define Q2CONTENTS_WINDOW 2 // translucent, but not watery #define Q2CONTENTS_AUX 4 #define Q2CONTENTS_LAVA 8 #define Q2CONTENTS_SLIME 16 #define Q2CONTENTS_WATER 32 #define Q2CONTENTS_MIST 64 #define Q2LAST_VISIBLE_CONTENTS 64 // remaining contents are non-visible, and don't eat brushes #define Q2CONTENTS_AREAPORTAL 0x8000 #define Q2CONTENTS_PLAYERCLIP 0x10000 #define Q2CONTENTS_MONSTERCLIP 0x20000 // currents can be added to any other contents, and may be mixed #define Q2CONTENTS_CURRENT_0 0x40000 #define Q2CONTENTS_CURRENT_90 0x80000 #define Q2CONTENTS_CURRENT_180 0x100000 #define Q2CONTENTS_CURRENT_270 0x200000 #define Q2CONTENTS_CURRENT_UP 0x400000 #define Q2CONTENTS_CURRENT_DOWN 0x800000 #define Q2CONTENTS_ORIGIN 0x1000000 // removed before bsping an entity #define Q2CONTENTS_MONSTER 0x2000000 // should never be on a brush, only in game #define Q2CONTENTS_DEADMONSTER 0x4000000 #define Q2CONTENTS_DETAIL 0x8000000 // brushes to be added after vis leafs #define Q2CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans #define Q2CONTENTS_LADDER 0x20000000 #define Q2SURF_LIGHT 0x1 // value will hold the light strength #define Q2SURF_SLICK 0x2 // effects game physics #define Q2SURF_SKY 0x4 // don't draw, but add to skybox #define Q2SURF_WARP 0x8 // turbulent water warp #define Q2SURF_TRANS33 0x10 #define Q2SURF_TRANS66 0x20 #define Q2SURF_FLOWING 0x40 // scroll towards angle #define Q2SURF_NODRAW 0x80 // don't bother referencing the texture #define Q2SURF_HINT 0x100 // make a primary bsp splitter #define Q2SURF_SKIP 0x200 // completely ignore, allowing non-closed brushes #define Q2SURF_ALPHATEST 0x02000000 // alpha test masking of color 255 in wal textures (supported by modded engines) /* typedef struct q2dnode_s { int planenum; int children[2]; // negative numbers are -(leafs+1), not nodes short mins[3]; // for frustom culling short maxs[3]; unsigned short firstface; unsigned short numfaces; // counting both sides } q2dnode_t; typedef struct q2texinfo_s { float vecs[2][4]; // [s/t][xyz offset] int flags; // miptex flags + overrides int value; // light emission, etc char texture[32]; // texture name (textures/something.wal) int nexttexinfo; // for animations, -1 = end of chain } q2texinfo_t; typedef struct q2dleaf_s { int contents; // OR of all brushes (not needed?) short cluster; short area; short mins[3]; // for frustum culling short maxs[3]; unsigned short firstleafface; unsigned short numleaffaces; unsigned short firstleafbrush; unsigned short numleafbrushes; } q2dleaf_t; typedef struct q2dbrushside_s { unsigned short planenum; // facing out of the leaf short texinfo; } q2dbrushside_t; typedef struct q2dbrush_s { int firstside; int numsides; int contents; } q2dbrush_t; // the visibility lump consists of a header with a count, then // byte offsets for the PVS and PHS of each cluster, then the raw // compressed bit vectors #define Q2DVIS_PVS 0 #define Q2DVIS_PHS 1 typedef struct q2dvis_s { int numclusters; int bitofs[8][2]; // bitofs[numclusters][2] } q2dvis_t; // each area has a list of portals that lead into other areas // when portals are closed, other areas may not be visible or // hearable even if the vis info says that it should be typedef struct q2dareaportal_s { int portalnum; int otherarea; } q2dareaportal_t; typedef struct q2darea_s { int numareaportals; int firstareaportal; } q2darea_t; */ //Q3 bsp stuff #define Q3BSPVERSION 46 #define Q3BSPVERSION_LIVE 47 #define Q3BSPVERSION_IG 48 #define Q3LUMP_ENTITIES 0 // entities to spawn (used by server and client) #define Q3LUMP_TEXTURES 1 // textures used (used by faces) #define Q3LUMP_PLANES 2 // planes used (used by bsp nodes) #define Q3LUMP_NODES 3 // bsp nodes (used by bsp nodes, bsp leafs, rendering, collisions) #define Q3LUMP_LEAFS 4 // bsp leafs (used by bsp nodes) #define Q3LUMP_LEAFFACES 5 // array of ints indexing faces (used by leafs) #define Q3LUMP_LEAFBRUSHES 6 // array of ints indexing brushes (used by leafs) #define Q3LUMP_MODELS 7 // models (used by rendering, collisions) #define Q3LUMP_BRUSHES 8 // brushes (used by effects, collisions) #define Q3LUMP_BRUSHSIDES 9 // brush faces (used by brushes) #define Q3LUMP_VERTICES 10 // mesh vertices (used by faces) #define Q3LUMP_TRIANGLES 11 // mesh triangles (used by faces) #define Q3LUMP_EFFECTS 12 // fog (used by faces) #define Q3LUMP_FACES 13 // surfaces (used by leafs) #define Q3LUMP_LIGHTMAPS 14 // lightmap textures (used by faces) #define Q3LUMP_LIGHTGRID 15 // lighting as a voxel grid (used by rendering) #define Q3LUMP_PVS 16 // potentially visible set; bit[clusters][clusters] (used by rendering) #define Q3HEADER_LUMPS 17 #define Q3LUMP_ADVERTISEMENTS 17 // quake live stuff written by zeroradiant's q3map2 (ignored by DP) #define Q3HEADER_LUMPS_LIVE 18 #define Q3HEADER_LUMPS_MAX 18 typedef struct q3dheader_s { int ident; int version; lump_t lumps[Q3HEADER_LUMPS_MAX]; } q3dheader_t; typedef struct q3dtexture_s { char name[Q3PATHLENGTH]; int surfaceflags; int contents; } q3dtexture_t; // note: planes are paired, the pair of planes with i and i ^ 1 are opposites. typedef struct q3dplane_s { float normal[3]; float dist; } q3dplane_t; typedef struct q3dnode_s { int planeindex; int childrenindex[2]; int mins[3]; int maxs[3]; } q3dnode_t; typedef struct q3dleaf_s { int clusterindex; // pvs index int areaindex; // area index int mins[3]; int maxs[3]; int firstleafface; int numleaffaces; int firstleafbrush; int numleafbrushes; } q3dleaf_t; typedef struct q3dmodel_s { float mins[3]; float maxs[3]; int firstface; int numfaces; int firstbrush; int numbrushes; } q3dmodel_t; typedef struct q3dbrush_s { int firstbrushside; int numbrushsides; int textureindex; } q3dbrush_t; typedef struct q3dbrushside_s { int planeindex; int textureindex; } q3dbrushside_t; typedef struct q3dbrushside_ig_s { int planeindex; int textureindex; int surfaceflags; } q3dbrushside_ig_t; typedef struct q3dvertex_s { float origin3f[3]; float texcoord2f[2]; float lightmap2f[2]; float normal3f[3]; unsigned char color4ub[4]; } q3dvertex_t; typedef struct q3dmeshvertex_s { int offset; // first vertex index of mesh } q3dmeshvertex_t; typedef struct q3deffect_s { char shadername[Q3PATHLENGTH]; int brushindex; int unknown; // I read this is always 5 except in q3dm8 which has one effect with -1 } q3deffect_t; #define Q3FACETYPE_FLAT 1 // common #define Q3FACETYPE_PATCH 2 // common #define Q3FACETYPE_MESH 3 // common #define Q3FACETYPE_FLARE 4 // rare (is this ever used?) typedef struct q3dface_s { int textureindex; int effectindex; // -1 if none int type; // Q3FACETYPE int firstvertex; int numvertices; int firstelement; int numelements; int lightmapindex; // -1 if none int lightmap_base[2]; int lightmap_size[2]; union { struct { // corrupt or don't care int blah[14]; } unknown; struct { // Q3FACETYPE_FLAT // mesh is a collection of triangles on a plane, renderable as a mesh (NOT a polygon) float lightmap_origin[3]; float lightmap_vectors[2][3]; float normal[3]; int unused1[2]; } flat; struct { // Q3FACETYPE_PATCH // patch renders as a bezier mesh, with adjustable tesselation // level (optionally based on LOD using the bbox and polygon // count to choose a tesselation level) // note: multiple patches may have the same bbox to cause them to // be LOD adjusted together as a group int unused1[3]; float mins[3]; // LOD bbox float maxs[3]; // LOD bbox int unused2[3]; int patchsize[2]; // dimensions of vertex grid } patch; struct { // Q3FACETYPE_MESH // mesh renders as simply a triangle mesh int unused1[3]; float mins[3]; float maxs[3]; int unused2[5]; } mesh; struct { // Q3FACETYPE_FLARE // flare renders as a simple sprite at origin, no geometry // exists, nor does it have a radius, a cvar controls the radius // and another cvar controls distance fade // (they were not used in Q3 I'm told) float origin[3]; int unused1[11]; } flare; } specific; } q3dface_t; typedef struct q3dlightmap_s { unsigned char rgb[128*128*3]; } q3dlightmap_t; typedef struct q3dlightgrid_s { unsigned char ambientrgb[3]; unsigned char diffusergb[3]; unsigned char diffusepitch; unsigned char diffuseyaw; } q3dlightgrid_t; typedef struct q3dpvs_s { int numclusters; int chainlength; // unsigned char chains[]; // containing bits in 0-7 order (not 7-0 order), // pvschains[mycluster * chainlength + (thatcluster >> 3)] & (1 << (thatcluster & 7)) } q3dpvs_t; // surfaceflags from bsp #define Q3SURFACEFLAG_NODAMAGE 1 #define Q3SURFACEFLAG_SLICK 2 #define Q3SURFACEFLAG_SKY 4 #define Q3SURFACEFLAG_LADDER 8 // has no surfaceparm #define Q3SURFACEFLAG_NOIMPACT 16 #define Q3SURFACEFLAG_NOMARKS 32 #define Q3SURFACEFLAG_FLESH 64 // has no surfaceparm #define Q3SURFACEFLAG_NODRAW 128 #define Q3SURFACEFLAG_HINT 256 #define Q3SURFACEFLAG_SKIP 512 // has no surfaceparm #define Q3SURFACEFLAG_NOLIGHTMAP 1024 #define Q3SURFACEFLAG_POINTLIGHT 2048 #define Q3SURFACEFLAG_METALSTEPS 4096 #define Q3SURFACEFLAG_NOSTEPS 8192 // has no surfaceparm #define Q3SURFACEFLAG_NONSOLID 16384 #define Q3SURFACEFLAG_LIGHTFILTER 32768 #define Q3SURFACEFLAG_ALPHASHADOW 65536 #define Q3SURFACEFLAG_NODLIGHT 131072 #define Q3SURFACEFLAG_DUST 262144 // surfaceparms from shaders #define Q3SURFACEPARM_ALPHASHADOW 1 #define Q3SURFACEPARM_AREAPORTAL 2 #define Q3SURFACEPARM_CLUSTERPORTAL 4 #define Q3SURFACEPARM_DETAIL 8 #define Q3SURFACEPARM_DONOTENTER 16 #define Q3SURFACEPARM_FOG 32 #define Q3SURFACEPARM_LAVA 64 #define Q3SURFACEPARM_LIGHTFILTER 128 #define Q3SURFACEPARM_METALSTEPS 256 #define Q3SURFACEPARM_NODAMAGE 512 #define Q3SURFACEPARM_NODLIGHT 1024 #define Q3SURFACEPARM_NODRAW 2048 #define Q3SURFACEPARM_NODROP 4096 #define Q3SURFACEPARM_NOIMPACT 8192 #define Q3SURFACEPARM_NOLIGHTMAP 16384 #define Q3SURFACEPARM_NOMARKS 32768 #define Q3SURFACEPARM_NOMIPMAPS 65536 #define Q3SURFACEPARM_NONSOLID 131072 #define Q3SURFACEPARM_ORIGIN 262144 #define Q3SURFACEPARM_PLAYERCLIP 524288 #define Q3SURFACEPARM_SKY 1048576 #define Q3SURFACEPARM_SLICK 2097152 #define Q3SURFACEPARM_SLIME 4194304 #define Q3SURFACEPARM_STRUCTURAL 8388608 #define Q3SURFACEPARM_TRANS 16777216 #define Q3SURFACEPARM_WATER 33554432 #define Q3SURFACEPARM_POINTLIGHT 67108864 #define Q3SURFACEPARM_HINT 134217728 #define Q3SURFACEPARM_DUST 268435456 #define Q3SURFACEPARM_BOTCLIP 536870912 #define Q3SURFACEPARM_LIGHTGRID 1073741824 #define Q3SURFACEPARM_ANTIPORTAL 2147483648u typedef struct q3mbrush_s { struct colbrushf_s *colbrushf; int numbrushsides; struct q3mbrushside_s *firstbrushside; struct texture_s *texture; } q3mbrush_t; typedef struct q3mbrushside_s { struct mplane_s *plane; struct texture_s *texture; } q3mbrushside_t; // the first cast is to shut up a stupid warning by clang, the second cast is to make both sides have the same type #define CHECKPVSBIT(pvs,b) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] & (1 << ((b) & 7))) : (unsigned char) false) #define SETPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] |= (1 << ((b) & 7))) : (unsigned char) false) #define CLEARPVSBIT(pvs,b) (void) ((b) >= 0 ? (unsigned char) ((pvs)[(b) >> 3] &= ~(1 << ((b) & 7))) : (unsigned char) false) #endif darkplaces/world.h0000664000175000017500000000761113067716222013472 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // world.h #ifndef WORLD_H #define WORLD_H #include "collision.h" #define MOVE_NORMAL 0 #define MOVE_NOMONSTERS 1 #define MOVE_MISSILE 2 #define MOVE_WORLDONLY 3 #define MOVE_HITMODEL 4 #define AREA_GRID 128 #define AREA_GRIDNODES (AREA_GRID * AREA_GRID) typedef struct link_s { int entitynumber; struct link_s *prev, *next; } link_t; typedef struct world_physics_s { // for ODE physics engine qboolean ode; // if true then ode is activated void *ode_world; void *ode_space; void *ode_contactgroup; // number of constraint solver iterations to use (for dWorldQuickStep) int ode_iterations; // actual step (server frametime / ode_iterations) vec_t ode_step; // time we need to simulate, for constantstep vec_t ode_time; // stats int ode_numobjects; // total objects cound int ode_activeovjects; // active objects count // max velocity for a 1-unit radius object at current step to prevent // missed collisions vec_t ode_movelimit; } world_physics_t; struct prvm_prog_s; typedef struct world_s { // convenient fields char filename[MAX_QPATH]; vec3_t mins; vec3_t maxs; struct prvm_prog_s *prog; int areagrid_stats_calls; int areagrid_stats_nodechecks; int areagrid_stats_entitychecks; link_t areagrid[AREA_GRIDNODES]; link_t areagrid_outside; vec3_t areagrid_bias; vec3_t areagrid_scale; vec3_t areagrid_mins; vec3_t areagrid_maxs; vec3_t areagrid_size; int areagrid_marknumber; // if the QC uses a physics engine, the data for it is here world_physics_t physics; } world_t; struct prvm_edict_s; // cyclic doubly-linked list functions void World_ClearLink(link_t *l); void World_RemoveLink(link_t *l); void World_InsertLinkBefore(link_t *l, link_t *before, int entitynumber); void World_Init(void); void World_Shutdown(void); /// called after the world model has been loaded, before linking any entities void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs, struct prvm_prog_s *prog); /// unlinks all entities (used before reallocation of edicts) void World_UnlinkAll(world_t *world); void World_PrintAreaStats(world_t *world, const char *worldname); /// call before removing an entity, and before trying to move one, /// so it doesn't clip against itself void World_UnlinkEdict(struct prvm_edict_s *ent); /// Needs to be called any time an entity changes origin, mins, maxs void World_LinkEdict(world_t *world, struct prvm_edict_s *ent, const vec3_t mins, const vec3_t maxs); /// \returns list of entities touching a box int World_EntitiesInBox(world_t *world, const vec3_t mins, const vec3_t maxs, int maxlist, struct prvm_edict_s **list); void World_Start(world_t *world); void World_End(world_t *world); // update physics // this is called by SV_Physics void World_Physics_Frame(world_t *world, double frametime, double gravity); // change physics properties of entity struct prvm_edict_s; struct edict_odefunc_s; void World_Physics_ApplyCmd(struct prvm_edict_s *ed, struct edict_odefunc_s *f); // remove physics data from entity // this is called by entity removal void World_Physics_RemoveFromEntity(world_t *world, struct prvm_edict_s *ed); void World_Physics_RemoveJointFromEntity(world_t *world, struct prvm_edict_s *ed); #endif darkplaces/sys.h0000664000175000017500000000665713067716222013172 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sys.h -- non-portable functions #ifndef SYS_H #define SYS_H extern cvar_t sys_usenoclockbutbenchmark; // // DLL management // // Win32 specific #ifdef WIN32 # include typedef HMODULE dllhandle_t; // Other platforms #else typedef void* dllhandle_t; #endif typedef struct dllfunction_s { const char *name; void **funcvariable; } dllfunction_t; /*! Loads a library. * \param dllnames a NULL terminated array of possible names for the DLL you want to load. * \param handle * \param fcts */ qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts); void Sys_UnloadLibrary (dllhandle_t* handle); void* Sys_GetProcAddress (dllhandle_t handle, const char* name); /// called early in Host_Init void Sys_InitConsole (void); /// called after command system is initialized but before first Con_Print void Sys_Init_Commands (void); /// \returns current timestamp char *Sys_TimeString(const char *timeformat); // // system IO interface (these are the sys functions that need to be implemented in a new driver atm) // /// an error will cause the entire program to exit void Sys_Error (const char *error, ...) DP_FUNC_PRINTF(1) DP_FUNC_NORETURN; /// (may) output text to terminal which launched program void Sys_PrintToTerminal(const char *text); void Sys_PrintfToTerminal(const char *fmt, ...); /// INFO: This is only called by Host_Shutdown so we dont need testing for recursion void Sys_Shutdown (void); void Sys_Quit (int returnvalue); /*! on some build/platform combinations (such as Linux gcc with the -pg * profiling option) this can turn on/off profiling, used primarily to limit * profiling to certain areas of the code, such as ingame performance without * regard for loading/shutdown performance (-profilegameonly on commandline) */ #ifdef __cplusplus extern "C" #endif void Sys_AllowProfiling (qboolean enable); typedef struct sys_cleantime_s { double dirtytime; // last value gotten from Sys_DirtyTime() double cleantime; // sanitized linearly increasing time since app start } sys_cleantime_t; double Sys_DirtyTime(void); void Sys_ProvideSelfFD (void); char *Sys_ConsoleInput (void); /// called to yield for a little bit so as not to hog cpu when paused or debugging void Sys_Sleep(int microseconds); /// Perform Key_Event () callbacks until the input que is empty void Sys_SendKeyEvents (void); char *Sys_GetClipboardData (void); extern qboolean sys_supportsdlgetticks; unsigned int Sys_SDL_GetTicks (void); // wrapper to call SDL_GetTicks void Sys_SDL_Delay (unsigned int milliseconds); // wrapper to call SDL_Delay /// called to set process priority for dedicated servers void Sys_InitProcessNice (void); void Sys_MakeProcessNice (void); void Sys_MakeProcessMean (void); #endif darkplaces/model_dpmodel.h0000664000175000017500000001375213067716222015152 0ustar kalevkalev #ifndef MODEL_DPMODEL_H #define MODEL_DPMODEL_H /* type 2 model (hierarchical skeletal pose) within this specification, int is assumed to be 32bit, float is assumed to be 32bit, char is assumed to be 8bit, text is assumed to be an array of chars with NULL termination all values are big endian (also known as network byte ordering), NOT x86 little endian general notes: a pose is a 3x4 matrix (rotation matrix, and translate vector) parent bones must always be lower in number than their children, models will be rejected if this is not obeyed (can be fixed by modelling utilities) utility notes: if a hard edge is desired (faceted lighting, or a jump to another set of skin coordinates), vertices must be duplicated ability to visually edit groupids of triangles is highly recommended bones should be markable as 'attach' somehow (up to the utility) and thus protected from culling of unused resources frame 0 is always the base pose (the one the skeleton was built for) game notes: the loader should be very thorough about error checking, all vertex and bone indices should be validated, etc the gamecode can look up bone numbers by name using a builtin function, for use in attachment situations (the client should have the same model as the host of the gamecode in question - that is to say if the server gamecode is setting the bone number, the client and server must have vaguely compatible models so the client understands, and if the client gamecode is setting the bone number, the server could have a completely different model with no harm done) the triangle groupid values are up to the gamecode, it is recommended that gamecode process this in an object-oriented fashion (I.E. bullet hits entity, call that entity's function for getting properties of that groupid) frame 0 should be usable, not skipped speed optimizations for the saver to do: remove all unused data (unused bones, vertices, etc, be sure to check if bones are used for attachments however) sort triangles into strips sort vertices according to first use in a triangle (caching benefits) after sorting triangles speed optimizations for the loader to do: if the model only has one frame, process it at load time to create a simple static vertex mesh to render (this is a hassle, but it is rewarding to optimize all such models) rendering process: 1*. one or two poses are looked up by number 2*. boneposes (matrices) are interpolated, building bone matrix array 3. bones are parsed sequentially, each bone's matrix is transformed by it's parent bone (which can be -1; the model to world matrix) 4. meshs are parsed sequentially, as follows: 1. vertices are parsed sequentially and may be influenced by more than one bone (the results of the 3x4 matrix transform will be added together - weighting is already built into these) 2. shader is looked up and called, passing vertex buffer (temporary) and triangle indices (which are stored in the mesh) 5. rendering is complete * - these stages can be replaced with completely dynamic animation instead of pose animations. */ // header for the entire file typedef struct dpmheader_s { char id[16]; // "DARKPLACESMODEL\0", length 16 unsigned int type; // 2 (hierarchical skeletal pose) unsigned int filesize; // size of entire model file float mins[3], maxs[3], yawradius, allradius; // for clipping uses // these offsets are relative to the file unsigned int num_bones; unsigned int num_meshs; unsigned int num_frames; unsigned int ofs_bones; // dpmbone_t bone[num_bones]; unsigned int ofs_meshs; // dpmmesh_t mesh[num_meshs]; unsigned int ofs_frames; // dpmframe_t frame[num_frames]; } dpmheader_t; // there may be more than one of these typedef struct dpmmesh_s { // these offsets are relative to the file char shadername[32]; // name of the shader to use unsigned int num_verts; unsigned int num_tris; unsigned int ofs_verts; // dpmvertex_t vert[numvertices]; // see vertex struct unsigned int ofs_texcoords; // float texcoords[numvertices][2]; unsigned int ofs_indices; // unsigned int indices[numtris*3]; // designed for glDrawElements (each triangle is 3 unsigned int indices) unsigned int ofs_groupids; // unsigned int groupids[numtris]; // the meaning of these values is entirely up to the gamecode and modeler } dpmmesh_t; // if set on a bone, it must be protected from removal #define DPMBONEFLAG_ATTACHMENT 1 // one per bone typedef struct dpmbone_s { // name examples: upperleftarm leftfinger1 leftfinger2 hand, etc char name[32]; // parent bone number signed int parent; // flags for the bone unsigned int flags; } dpmbone_t; // a bonepose matrix is intended to be used like this: // (n = output vertex, v = input vertex, m = matrix, f = influence) // n[0] = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2] + f * m[0][3]; // n[1] = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2] + f * m[1][3]; // n[2] = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2] + f * m[2][3]; typedef struct dpmbonepose_s { float matrix[3][4]; } dpmbonepose_t; // immediately followed by bone positions for the frame typedef struct dpmframe_s { // name examples: idle_1 idle_2 idle_3 shoot_1 shoot_2 shoot_3, etc char name[32]; float mins[3], maxs[3], yawradius, allradius; int ofs_bonepositions; // dpmbonepose_t bonepositions[bones]; } dpmframe_t; // one or more of these per vertex typedef struct dpmbonevert_s { // this pairing of origin and influence is intentional // (in SSE or 3DNow! assembly it can be done as a quad vector op // (or two dual vector ops) very easily) float origin[3]; // vertex location (these blend) float influence; // influence fraction (these must add up to 1) // this pairing of normal and bonenum is intentional // (in SSE or 3DNow! assembly it can be done as a quad vector op // (or two dual vector ops) very easily, the bonenum is ignored) float normal[3]; // surface normal (these blend) unsigned int bonenum; // number of the bone } dpmbonevert_t; // variable size, parsed sequentially typedef struct dpmvertex_s { unsigned int numbones; // immediately followed by 1 or more dpmbonevert_t structures } dpmvertex_t; #endif darkplaces/cvar.c0000664000175000017500000006541413067716216013301 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cvar.c -- dynamic variable tracking #include "quakedef.h" const char *cvar_dummy_description = "custom cvar"; cvar_t *cvar_vars = NULL; cvar_t *cvar_hashtable[CVAR_HASHSIZE]; const char *cvar_null_string = ""; /* ============ Cvar_FindVar ============ */ cvar_t *Cvar_FindVar (const char *var_name) { int hashindex; cvar_t *var; // use hash lookup to minimize search time hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)) % CVAR_HASHSIZE; for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) if (!strcmp (var_name, var->name)) return var; return NULL; } cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags) { cvar_t *var; if (*prev_var_name) { var = Cvar_FindVar (prev_var_name); if (!var) return NULL; var = var->next; } else var = cvar_vars; // search for the next cvar matching the needed flags while (var) { if ((var->flags & neededflags) || !neededflags) break; var = var->next; } return var; } static cvar_t *Cvar_FindVarLink (const char *var_name, cvar_t **parent, cvar_t ***link, cvar_t **prev_alpha) { int hashindex; cvar_t *var; // use hash lookup to minimize search time hashindex = CRC_Block((const unsigned char *)var_name, strlen(var_name)); if(parent) *parent = NULL; if(prev_alpha) *prev_alpha = NULL; if(link) *link = &cvar_hashtable[hashindex]; for (var = cvar_hashtable[hashindex];var;var = var->nextonhashchain) { if (!strcmp (var_name, var->name)) { if(!prev_alpha || var == cvar_vars) return var; *prev_alpha = cvar_vars; // if prev_alpha happens to become NULL then there has been some inconsistency elsewhere // already - should I still insert '*prev_alpha &&' in the loop? while((*prev_alpha)->next != var) *prev_alpha = (*prev_alpha)->next; return var; } if(parent) *parent = var; } return NULL; } /* ============ Cvar_VariableValue ============ */ float Cvar_VariableValueOr (const char *var_name, float def) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return def; return atof (var->string); } float Cvar_VariableValue (const char *var_name) { return Cvar_VariableValueOr(var_name, 0); } /* ============ Cvar_VariableString ============ */ const char *Cvar_VariableStringOr (const char *var_name, const char *def) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return def; return var->string; } const char *Cvar_VariableString (const char *var_name) { return Cvar_VariableStringOr(var_name, cvar_null_string); } /* ============ Cvar_VariableDefString ============ */ const char *Cvar_VariableDefString (const char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return cvar_null_string; return var->defstring; } /* ============ Cvar_VariableDescription ============ */ const char *Cvar_VariableDescription (const char *var_name) { cvar_t *var; var = Cvar_FindVar (var_name); if (!var) return cvar_null_string; return var->description; } /* ============ Cvar_CompleteVariable ============ */ const char *Cvar_CompleteVariable (const char *partial) { cvar_t *cvar; size_t len; len = strlen(partial); if (!len) return NULL; // check functions for (cvar=cvar_vars ; cvar ; cvar=cvar->next) if (!strncasecmp (partial,cvar->name, len)) return cvar->name; return NULL; } /* CVar_CompleteCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com */ int Cvar_CompleteCountPossible (const char *partial) { cvar_t *cvar; size_t len; int h; h = 0; len = strlen(partial); if (!len) return 0; // Loop through the cvars and count all possible matches for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strncasecmp(partial, cvar->name, len)) h++; return h; } /* CVar_CompleteBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char **Cvar_CompleteBuildList (const char *partial) { const cvar_t *cvar; size_t len = 0; size_t bpos = 0; size_t sizeofbuf = (Cvar_CompleteCountPossible (partial) + 1) * sizeof (const char *); const char **buf; len = strlen(partial); buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); // Loop through the alias list and print all matches for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strncasecmp(partial, cvar->name, len)) buf[bpos++] = cvar->name; buf[bpos] = NULL; return buf; } // written by LordHavoc void Cvar_CompleteCvarPrint (const char *partial) { cvar_t *cvar; size_t len = strlen(partial); // Loop through the command list and print all matches for (cvar = cvar_vars; cvar; cvar = cvar->next) if (!strncasecmp(partial, cvar->name, len)) Con_Printf ("^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); } // check if a cvar is held by some progs static qboolean Cvar_IsAutoCvar(cvar_t *var) { int i; prvm_prog_t *prog; for (i = 0;i < PRVM_PROG_MAX;i++) { prog = &prvm_prog_list[i]; if (prog->loaded && var->globaldefindex[i] >= 0) return true; } return false; } // we assume that prog is already set to the target progs static void Cvar_UpdateAutoCvar(cvar_t *var) { int i; int j; const char *s; vec3_t v; prvm_prog_t *prog; for (i = 0;i < PRVM_PROG_MAX;i++) { prog = &prvm_prog_list[i]; if (prog->loaded && var->globaldefindex[i] >= 0) { // MUST BE SYNCED WITH prvm_edict.c PRVM_LoadProgs switch(prog->globaldefs[var->globaldefindex[i]].type & ~DEF_SAVEGLOBAL) { case ev_float: PRVM_GLOBALFIELDFLOAT(prog->globaldefs[var->globaldefindex[i]].ofs) = var->value; break; case ev_vector: s = var->string; VectorClear(v); for (j = 0;j < 3;j++) { while (*s && ISWHITESPACE(*s)) s++; if (!*s) break; v[j] = atof(s); while (!ISWHITESPACE(*s)) s++; if (!*s) break; } VectorCopy(v, PRVM_GLOBALFIELDVECTOR(prog->globaldefs[var->globaldefindex[i]].ofs)); break; case ev_string: PRVM_ChangeEngineString(prog, var->globaldefindex_stringno[i], var->string); PRVM_GLOBALFIELDSTRING(prog->globaldefs[var->globaldefindex[i]].ofs) = var->globaldefindex_stringno[i]; break; } } } } // called after loading a savegame void Cvar_UpdateAllAutoCvars(void) { cvar_t *var; for (var = cvar_vars ; var ; var = var->next) Cvar_UpdateAutoCvar(var); } /* ============ Cvar_Set ============ */ extern cvar_t sv_disablenotify; static void Cvar_SetQuick_Internal (cvar_t *var, const char *value) { qboolean changed; size_t valuelen; char vabuf[1024]; changed = strcmp(var->string, value) != 0; // LordHavoc: don't reallocate when there is no change if (!changed) return; // LordHavoc: don't reallocate when the buffer is the same size valuelen = strlen(value); if (!var->string || strlen(var->string) != valuelen) { Z_Free ((char *)var->string); // free the old value string var->string = (char *)Z_Malloc (valuelen + 1); } memcpy ((char *)var->string, value, valuelen + 1); var->value = atof (var->string); var->integer = (int) var->value; if ((var->flags & CVAR_NOTIFY) && changed && sv.active && !sv_disablenotify.integer) SV_BroadcastPrintf("\"%s\" changed to \"%s\"\n", var->name, var->string); #if 0 // TODO: add infostring support to the server? if ((var->flags & CVAR_SERVERINFO) && changed && sv.active) { InfoString_SetValue(svs.serverinfo, sizeof(svs.serverinfo), var->name, var->string); if (sv.active) { MSG_WriteByte (&sv.reliable_datagram, svc_serverinfostring); MSG_WriteString (&sv.reliable_datagram, var->name); MSG_WriteString (&sv.reliable_datagram, var->string); } } #endif if ((var->flags & CVAR_USERINFO) && cls.state != ca_dedicated) CL_SetInfo(var->name, var->string, true, false, false, false); else if ((var->flags & CVAR_NQUSERINFOHACK) && cls.state != ca_dedicated) { // update the cls.userinfo to have proper values for the // silly nq config variables. // // this is done when these variables are changed rather than at // connect time because if the user or code checks the userinfo and it // holds weird values it may cause confusion... if (!strcmp(var->name, "_cl_color")) { int top = (var->integer >> 4) & 15, bottom = var->integer & 15; CL_SetInfo("topcolor", va(vabuf, sizeof(vabuf), "%i", top), true, false, false, false); CL_SetInfo("bottomcolor", va(vabuf, sizeof(vabuf), "%i", bottom), true, false, false, false); if (cls.protocol != PROTOCOL_QUAKEWORLD && cls.netcon) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "color %i %i", top, bottom)); } } else if (!strcmp(var->name, "_cl_rate")) CL_SetInfo("rate", va(vabuf, sizeof(vabuf), "%i", var->integer), true, false, false, false); else if (!strcmp(var->name, "_cl_rate_burstsize")) CL_SetInfo("rate_burstsize", va(vabuf, sizeof(vabuf), "%i", var->integer), true, false, false, false); else if (!strcmp(var->name, "_cl_playerskin")) CL_SetInfo("playerskin", var->string, true, false, false, false); else if (!strcmp(var->name, "_cl_playermodel")) CL_SetInfo("playermodel", var->string, true, false, false, false); else if (!strcmp(var->name, "_cl_name")) CL_SetInfo("name", var->string, true, false, false, false); else if (!strcmp(var->name, "rcon_secure")) { // whenever rcon_secure is changed to 0, clear rcon_password for // security reasons (prevents a send-rcon-password-as-plaintext // attack based on NQ protocol session takeover and svc_stufftext) if(var->integer <= 0) Cvar_Set("rcon_password", ""); } #ifdef CONFIG_MENU else if (!strcmp(var->name, "net_slist_favorites")) NetConn_UpdateFavorites(); #endif } Cvar_UpdateAutoCvar(var); } void Cvar_SetQuick (cvar_t *var, const char *value) { if (var == NULL) { Con_Print("Cvar_SetQuick: var == NULL\n"); return; } if (developer_extra.integer) Con_DPrintf("Cvar_SetQuick({\"%s\", \"%s\", %i, \"%s\"}, \"%s\");\n", var->name, var->string, var->flags, var->defstring, value); Cvar_SetQuick_Internal(var, value); } void Cvar_Set (const char *var_name, const char *value) { cvar_t *var; var = Cvar_FindVar (var_name); if (var == NULL) { Con_Printf("Cvar_Set: variable %s not found\n", var_name); return; } Cvar_SetQuick(var, value); } /* ============ Cvar_SetValue ============ */ void Cvar_SetValueQuick(cvar_t *var, float value) { char val[MAX_INPUTLINE]; if ((float)((int)value) == value) dpsnprintf(val, sizeof(val), "%i", (int)value); else dpsnprintf(val, sizeof(val), "%f", value); Cvar_SetQuick(var, val); } void Cvar_SetValue(const char *var_name, float value) { char val[MAX_INPUTLINE]; if ((float)((int)value) == value) dpsnprintf(val, sizeof(val), "%i", (int)value); else dpsnprintf(val, sizeof(val), "%f", value); Cvar_Set(var_name, val); } /* ============ Cvar_RegisterVariable Adds a freestanding variable to the variable list. ============ */ void Cvar_RegisterVariable (cvar_t *variable) { int hashindex; cvar_t *current, *next, *cvar; char *oldstr; size_t alloclen; int i; if (developer_extra.integer) Con_DPrintf("Cvar_RegisterVariable({\"%s\", \"%s\", %i});\n", variable->name, variable->string, variable->flags); // first check to see if it has already been defined cvar = Cvar_FindVar (variable->name); if (cvar) { if (cvar->flags & CVAR_ALLOCATED) { if (developer_extra.integer) Con_DPrintf("... replacing existing allocated cvar {\"%s\", \"%s\", %i}\n", cvar->name, cvar->string, cvar->flags); // fixed variables replace allocated ones // (because the engine directly accesses fixed variables) // NOTE: this isn't actually used currently // (all cvars are registered before config parsing) variable->flags |= (cvar->flags & ~CVAR_ALLOCATED); // cvar->string is now owned by variable instead variable->string = cvar->string; variable->defstring = cvar->defstring; variable->value = atof (variable->string); variable->integer = (int) variable->value; // Preserve autocvar status. memcpy(variable->globaldefindex, cvar->globaldefindex, sizeof(variable->globaldefindex)); memcpy(variable->globaldefindex_stringno, cvar->globaldefindex_stringno, sizeof(variable->globaldefindex_stringno)); // replace cvar with this one... variable->next = cvar->next; if (cvar_vars == cvar) { // head of the list is easy to change cvar_vars = variable; } else { // otherwise find it somewhere in the list for (current = cvar_vars;current->next != cvar;current = current->next) ; current->next = variable; } // get rid of old allocated cvar // (but not cvar->string and cvar->defstring, because we kept those) Z_Free((char *)cvar->name); Z_Free(cvar); } else Con_DPrintf("Can't register variable %s, already defined\n", variable->name); return; } // check for overlap with a command if (Cmd_Exists (variable->name)) { Con_Printf("Cvar_RegisterVariable: %s is a command\n", variable->name); return; } // copy the value off, because future sets will Z_Free it oldstr = (char *)variable->string; alloclen = strlen(variable->string) + 1; variable->string = (char *)Z_Malloc (alloclen); memcpy ((char *)variable->string, oldstr, alloclen); variable->defstring = (char *)Z_Malloc (alloclen); memcpy ((char *)variable->defstring, oldstr, alloclen); variable->value = atof (variable->string); variable->integer = (int) variable->value; // Mark it as not an autocvar. for (i = 0;i < PRVM_PROG_MAX;i++) variable->globaldefindex[i] = -1; // link the variable in // alphanumerical order for( current = NULL, next = cvar_vars ; next && strcmp( next->name, variable->name ) < 0 ; current = next, next = next->next ) ; if( current ) { current->next = variable; } else { cvar_vars = variable; } variable->next = next; // link to head of list in this hash table index hashindex = CRC_Block((const unsigned char *)variable->name, strlen(variable->name)) % CVAR_HASHSIZE; variable->nextonhashchain = cvar_hashtable[hashindex]; cvar_hashtable[hashindex] = variable; } /* ============ Cvar_Get Adds a newly allocated variable to the variable list or sets its value. ============ */ cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription) { int hashindex; cvar_t *current, *next, *cvar; int i; if (developer_extra.integer) Con_DPrintf("Cvar_Get(\"%s\", \"%s\", %i);\n", name, value, flags); // first check to see if it has already been defined cvar = Cvar_FindVar (name); if (cvar) { cvar->flags |= flags; Cvar_SetQuick_Internal (cvar, value); if(newdescription && (cvar->flags & CVAR_ALLOCATED)) { if(cvar->description != cvar_dummy_description) Z_Free((char *)cvar->description); if(*newdescription) cvar->description = (char *)Mem_strdup(zonemempool, newdescription); else cvar->description = cvar_dummy_description; } return cvar; } // check for pure evil if (!*name) { Con_Printf("Cvar_Get: invalid variable name\n"); return NULL; } // check for overlap with a command if (Cmd_Exists (name)) { Con_Printf("Cvar_Get: %s is a command\n", name); return NULL; } // allocate a new cvar, cvar name, and cvar string // TODO: factorize the following code with the one at the end of Cvar_RegisterVariable() // FIXME: these never get Z_Free'd cvar = (cvar_t *)Z_Malloc(sizeof(cvar_t)); cvar->flags = flags | CVAR_ALLOCATED; cvar->name = (char *)Mem_strdup(zonemempool, name); cvar->string = (char *)Mem_strdup(zonemempool, value); cvar->defstring = (char *)Mem_strdup(zonemempool, value); cvar->value = atof (cvar->string); cvar->integer = (int) cvar->value; if(newdescription && *newdescription) cvar->description = (char *)Mem_strdup(zonemempool, newdescription); else cvar->description = cvar_dummy_description; // actually checked by VM_cvar_type // Mark it as not an autocvar. for (i = 0;i < PRVM_PROG_MAX;i++) cvar->globaldefindex[i] = -1; // link the variable in // alphanumerical order for( current = NULL, next = cvar_vars ; next && strcmp( next->name, cvar->name ) < 0 ; current = next, next = next->next ) ; if( current ) current->next = cvar; else cvar_vars = cvar; cvar->next = next; // link to head of list in this hash table index hashindex = CRC_Block((const unsigned char *)cvar->name, strlen(cvar->name)) % CVAR_HASHSIZE; cvar->nextonhashchain = cvar_hashtable[hashindex]; cvar_hashtable[hashindex] = cvar; return cvar; } /* ============ Cvar_Command Handles variable inspection and changing from the console ============ */ qboolean Cvar_Command (void) { cvar_t *v; // check variables v = Cvar_FindVar (Cmd_Argv(0)); if (!v) return false; // perform a variable print or set if (Cmd_Argc() == 1) { Con_Printf("\"%s\" is \"%s\" [\"%s\"]\n", v->name, ((v->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : v->string), v->defstring); return true; } if (developer_extra.integer) Con_DPrint("Cvar_Command: "); if (v->flags & CVAR_READONLY) { Con_Printf("%s is read-only\n", v->name); return true; } Cvar_Set (v->name, Cmd_Argv(1)); if (developer_extra.integer) Con_DPrint("\n"); return true; } void Cvar_UnlockDefaults (void) { cvar_t *var; // unlock the default values of all cvars for (var = cvar_vars ; var ; var = var->next) var->flags &= ~CVAR_DEFAULTSET; } void Cvar_LockDefaults_f (void) { cvar_t *var; // lock in the default values of all cvars for (var = cvar_vars ; var ; var = var->next) { if (!(var->flags & CVAR_DEFAULTSET)) { size_t alloclen; //Con_Printf("locking cvar %s (%s -> %s)\n", var->name, var->string, var->defstring); var->flags |= CVAR_DEFAULTSET; Z_Free((char *)var->defstring); alloclen = strlen(var->string) + 1; var->defstring = (char *)Z_Malloc(alloclen); memcpy((char *)var->defstring, var->string, alloclen); } } } void Cvar_SaveInitState(void) { cvar_t *c; for (c = cvar_vars;c;c = c->next) { c->initstate = true; c->initflags = c->flags; c->initdefstring = Mem_strdup(zonemempool, c->defstring); c->initstring = Mem_strdup(zonemempool, c->string); c->initvalue = c->value; c->initinteger = c->integer; VectorCopy(c->vector, c->initvector); } } void Cvar_RestoreInitState(void) { int hashindex; cvar_t *c, **cp; cvar_t *c2, **cp2; for (cp = &cvar_vars;(c = *cp);) { if (c->initstate) { // restore this cvar, it existed at init if (((c->flags ^ c->initflags) & CVAR_MAXFLAGSVAL) || strcmp(c->defstring ? c->defstring : "", c->initdefstring ? c->initdefstring : "") || strcmp(c->string ? c->string : "", c->initstring ? c->initstring : "")) { Con_DPrintf("Cvar_RestoreInitState: Restoring cvar \"%s\"\n", c->name); if (c->defstring) Z_Free((char *)c->defstring); c->defstring = Mem_strdup(zonemempool, c->initdefstring); if (c->string) Z_Free((char *)c->string); c->string = Mem_strdup(zonemempool, c->initstring); } c->flags = c->initflags; c->value = c->initvalue; c->integer = c->initinteger; VectorCopy(c->initvector, c->vector); cp = &c->next; } else { if (!(c->flags & CVAR_ALLOCATED)) { Con_DPrintf("Cvar_RestoreInitState: Unable to destroy cvar \"%s\", it was registered after init!\n", c->name); // In this case, at least reset it to the default. if((c->flags & CVAR_NORESETTODEFAULTS) == 0) Cvar_SetQuick(c, c->defstring); cp = &c->next; continue; } if (Cvar_IsAutoCvar(c)) { Con_DPrintf("Cvar_RestoreInitState: Unable to destroy cvar \"%s\", it is an autocvar used by running progs!\n", c->name); // In this case, at least reset it to the default. if((c->flags & CVAR_NORESETTODEFAULTS) == 0) Cvar_SetQuick(c, c->defstring); cp = &c->next; continue; } // remove this cvar, it did not exist at init Con_DPrintf("Cvar_RestoreInitState: Destroying cvar \"%s\"\n", c->name); // unlink struct from hash hashindex = CRC_Block((const unsigned char *)c->name, strlen(c->name)) % CVAR_HASHSIZE; for (cp2 = &cvar_hashtable[hashindex];(c2 = *cp2);) { if (c2 == c) { *cp2 = c2->nextonhashchain; break; } else cp2 = &c2->nextonhashchain; } // unlink struct from main list *cp = c->next; // free strings if (c->defstring) Z_Free((char *)c->defstring); if (c->string) Z_Free((char *)c->string); if (c->description && c->description != cvar_dummy_description) Z_Free((char *)c->description); // free struct Z_Free(c); } } } void Cvar_ResetToDefaults_All_f (void) { cvar_t *var; // restore the default values of all cvars for (var = cvar_vars ; var ; var = var->next) if((var->flags & CVAR_NORESETTODEFAULTS) == 0) Cvar_SetQuick(var, var->defstring); } void Cvar_ResetToDefaults_NoSaveOnly_f (void) { cvar_t *var; // restore the default values of all cvars for (var = cvar_vars ; var ; var = var->next) if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == 0) Cvar_SetQuick(var, var->defstring); } void Cvar_ResetToDefaults_SaveOnly_f (void) { cvar_t *var; // restore the default values of all cvars for (var = cvar_vars ; var ; var = var->next) if ((var->flags & (CVAR_NORESETTODEFAULTS | CVAR_SAVE)) == CVAR_SAVE) Cvar_SetQuick(var, var->defstring); } /* ============ Cvar_WriteVariables Writes lines containing "set variable value" for all variables with the archive flag set to true. ============ */ void Cvar_WriteVariables (qfile_t *f) { cvar_t *var; char buf1[MAX_INPUTLINE], buf2[MAX_INPUTLINE]; // don't save cvars that match their default value for (var = cvar_vars ; var ; var = var->next) if ((var->flags & CVAR_SAVE) && (strcmp(var->string, var->defstring) || ((var->flags & CVAR_ALLOCATED) && !(var->flags & CVAR_DEFAULTSET)))) { Cmd_QuoteString(buf1, sizeof(buf1), var->name, "\"\\$", false); Cmd_QuoteString(buf2, sizeof(buf2), var->string, "\"\\$", false); FS_Printf(f, "%s\"%s\" \"%s\"\n", var->flags & CVAR_ALLOCATED ? "seta " : "", buf1, buf2); } } // Added by EvilTypeGuy eviltypeguy@qeradiant.com // 2000-01-09 CvarList command By Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ /* ========= Cvar_List ========= */ void Cvar_List_f (void) { cvar_t *cvar; const char *partial; size_t len; int count; qboolean ispattern; if (Cmd_Argc() > 1) { partial = Cmd_Argv (1); len = strlen(partial); ispattern = (strchr(partial, '*') || strchr(partial, '?')); } else { partial = NULL; len = 0; ispattern = false; } count = 0; for (cvar = cvar_vars; cvar; cvar = cvar->next) { if (len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp (partial,cvar->name,len))) continue; Con_Printf("%s is \"%s\" [\"%s\"] %s\n", cvar->name, ((cvar->flags & CVAR_PRIVATE) ? "********"/*hunter2*/ : cvar->string), cvar->defstring, cvar->description); count++; } if (len) { if(ispattern) Con_Printf("%i cvar%s matching \"%s\"\n", count, (count > 1) ? "s" : "", partial); else Con_Printf("%i cvar%s beginning with \"%s\"\n", count, (count > 1) ? "s" : "", partial); } else Con_Printf("%i cvar(s)\n", count); } // 2000-01-09 CvarList command by Maddes void Cvar_Set_f (void) { cvar_t *cvar; // make sure it's the right number of parameters if (Cmd_Argc() < 3) { Con_Printf("Set: wrong number of parameters, usage: set []\n"); return; } // check if it's read-only cvar = Cvar_FindVar(Cmd_Argv(1)); if (cvar && cvar->flags & CVAR_READONLY) { Con_Printf("Set: %s is read-only\n", cvar->name); return; } if (developer_extra.integer) Con_DPrint("Set: "); // all looks ok, create/modify the cvar Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), 0, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); } void Cvar_SetA_f (void) { cvar_t *cvar; // make sure it's the right number of parameters if (Cmd_Argc() < 3) { Con_Printf("SetA: wrong number of parameters, usage: seta []\n"); return; } // check if it's read-only cvar = Cvar_FindVar(Cmd_Argv(1)); if (cvar && cvar->flags & CVAR_READONLY) { Con_Printf("SetA: %s is read-only\n", cvar->name); return; } if (developer_extra.integer) Con_DPrint("SetA: "); // all looks ok, create/modify the cvar Cvar_Get(Cmd_Argv(1), Cmd_Argv(2), CVAR_SAVE, Cmd_Argc() > 3 ? Cmd_Argv(3) : NULL); } void Cvar_Del_f (void) { int i; cvar_t *cvar, *parent, **link, *prev; if(Cmd_Argc() < 2) { Con_Printf("Del: wrong number of parameters, useage: unset [ ...]\n"); return; } for(i = 1; i < Cmd_Argc(); ++i) { cvar = Cvar_FindVarLink(Cmd_Argv(i), &parent, &link, &prev); if(!cvar) { Con_Printf("Del: %s is not defined\n", Cmd_Argv(i)); continue; } if(cvar->flags & CVAR_READONLY) { Con_Printf("Del: %s is read-only\n", cvar->name); continue; } if(!(cvar->flags & CVAR_ALLOCATED)) { Con_Printf("Del: %s is static and cannot be deleted\n", cvar->name); continue; } if(cvar == cvar_vars) { cvar_vars = cvar->next; } else { // in this case, prev must be set, otherwise there has been some inconsistensy // elsewhere already... should I still check for prev != NULL? prev->next = cvar->next; } if(parent) parent->nextonhashchain = cvar->nextonhashchain; else if(link) *link = cvar->nextonhashchain; if(cvar->description != cvar_dummy_description) Z_Free((char *)cvar->description); Z_Free((char *)cvar->name); Z_Free((char *)cvar->string); Z_Free((char *)cvar->defstring); Z_Free(cvar); } } #ifdef FILLALLCVARSWITHRUBBISH void Cvar_FillAll_f() { char *buf, *p, *q; int n, i; cvar_t *var; qboolean verify; if(Cmd_Argc() != 2) { Con_Printf("Usage: %s length to plant rubbish\n", Cmd_Argv(0)); Con_Printf("Usage: %s -length to verify that the rubbish is still there\n", Cmd_Argv(0)); return; } n = atoi(Cmd_Argv(1)); verify = (n < 0); if(verify) n = -n; buf = Z_Malloc(n + 1); buf[n] = 0; for(var = cvar_vars; var; var = var->next) { for(i = 0, p = buf, q = var->name; i < n; ++i) { *p++ = *q++; if(!*q) q = var->name; } if(verify && strcmp(var->string, buf)) { Con_Printf("\n%s does not contain the right rubbish, either this is the first run or a possible overrun was detected, or something changed it intentionally; it DOES contain: %s\n", var->name, var->string); } Cvar_SetQuick(var, buf); } Z_Free(buf); } #endif /* FILLALLCVARSWITHRUBBISH */ darkplaces/vid_sdl.c0000664000175000017500000036253413067716222013772 0ustar kalevkalev/* Copyright (C) 2003 T. Joseph Carter This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #undef WIN32_LEAN_AND_MEAN //hush a warning, SDL.h redefines this #include #include #include "quakedef.h" #include "image.h" #include "dpsoftrast.h" #include "utf8lib.h" #ifndef __IPHONEOS__ #ifdef MACOSX #include #include #include #include static cvar_t apple_mouse_noaccel = {CVAR_SAVE, "apple_mouse_noaccel", "1", "disables mouse acceleration while DarkPlaces is active"}; static qboolean vid_usingnoaccel; static double originalMouseSpeed = -1.0; io_connect_t IN_GetIOHandle(void) { io_connect_t iohandle = MACH_PORT_NULL; kern_return_t status; io_service_t iohidsystem = MACH_PORT_NULL; mach_port_t masterport; status = IOMasterPort(MACH_PORT_NULL, &masterport); if(status != KERN_SUCCESS) return 0; iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem"); if(!iohidsystem) return 0; status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle); IOObjectRelease(iohidsystem); return iohandle; } #endif #endif #ifdef WIN32 #define SDL_R_RESTART #endif // Tell startup code that we have a client int cl_available = true; qboolean vid_supportrefreshrate = false; static qboolean vid_usingmouse = false; static qboolean vid_usingmouse_relativeworks = false; // SDL2 workaround for unimplemented RelativeMouse mode static qboolean vid_usinghidecursor = false; static qboolean vid_hasfocus = false; static qboolean vid_isfullscreen; #if SDL_MAJOR_VERSION != 1 static qboolean vid_usingvsync = false; #endif static SDL_Joystick *vid_sdljoystick = NULL; // GAME_STEELSTORM specific static cvar_t *steelstorm_showing_map = NULL; // detect but do not create the cvar static cvar_t *steelstorm_showing_mousecursor = NULL; // detect but do not create the cvar static int win_half_width = 50; static int win_half_height = 50; static int video_bpp; #if SDL_MAJOR_VERSION == 1 static SDL_Surface *video_screen; static int video_flags; #else static SDL_GLContext context; static SDL_Window *window; static int window_flags; #endif static SDL_Surface *vid_softsurface; static vid_mode_t desktop_mode; ///////////////////////// // Input handling //// //TODO: Add error checking #ifndef SDLK_PERCENT #define SDLK_PERCENT '%' #if SDL_MAJOR_VERSION == 1 #define SDLK_PRINTSCREEN SDLK_PRINT #define SDLK_SCROLLLOCK SDLK_SCROLLOCK #define SDLK_NUMLOCKCLEAR SDLK_NUMLOCK #define SDLK_KP_1 SDLK_KP1 #define SDLK_KP_2 SDLK_KP2 #define SDLK_KP_3 SDLK_KP3 #define SDLK_KP_4 SDLK_KP4 #define SDLK_KP_5 SDLK_KP5 #define SDLK_KP_6 SDLK_KP6 #define SDLK_KP_7 SDLK_KP7 #define SDLK_KP_8 SDLK_KP8 #define SDLK_KP_9 SDLK_KP9 #define SDLK_KP_0 SDLK_KP0 #endif #endif static int MapKey( unsigned int sdlkey ) { switch(sdlkey) { default: return 0; // case SDLK_UNKNOWN: return K_UNKNOWN; case SDLK_RETURN: return K_ENTER; case SDLK_ESCAPE: return K_ESCAPE; case SDLK_BACKSPACE: return K_BACKSPACE; case SDLK_TAB: return K_TAB; case SDLK_SPACE: return K_SPACE; case SDLK_EXCLAIM: return '!'; case SDLK_QUOTEDBL: return '"'; case SDLK_HASH: return '#'; case SDLK_PERCENT: return '%'; case SDLK_DOLLAR: return '$'; case SDLK_AMPERSAND: return '&'; case SDLK_QUOTE: return '\''; case SDLK_LEFTPAREN: return '('; case SDLK_RIGHTPAREN: return ')'; case SDLK_ASTERISK: return '*'; case SDLK_PLUS: return '+'; case SDLK_COMMA: return ','; case SDLK_MINUS: return '-'; case SDLK_PERIOD: return '.'; case SDLK_SLASH: return '/'; case SDLK_0: return '0'; case SDLK_1: return '1'; case SDLK_2: return '2'; case SDLK_3: return '3'; case SDLK_4: return '4'; case SDLK_5: return '5'; case SDLK_6: return '6'; case SDLK_7: return '7'; case SDLK_8: return '8'; case SDLK_9: return '9'; case SDLK_COLON: return ':'; case SDLK_SEMICOLON: return ';'; case SDLK_LESS: return '<'; case SDLK_EQUALS: return '='; case SDLK_GREATER: return '>'; case SDLK_QUESTION: return '?'; case SDLK_AT: return '@'; case SDLK_LEFTBRACKET: return '['; case SDLK_BACKSLASH: return '\\'; case SDLK_RIGHTBRACKET: return ']'; case SDLK_CARET: return '^'; case SDLK_UNDERSCORE: return '_'; case SDLK_BACKQUOTE: return '`'; case SDLK_a: return 'a'; case SDLK_b: return 'b'; case SDLK_c: return 'c'; case SDLK_d: return 'd'; case SDLK_e: return 'e'; case SDLK_f: return 'f'; case SDLK_g: return 'g'; case SDLK_h: return 'h'; case SDLK_i: return 'i'; case SDLK_j: return 'j'; case SDLK_k: return 'k'; case SDLK_l: return 'l'; case SDLK_m: return 'm'; case SDLK_n: return 'n'; case SDLK_o: return 'o'; case SDLK_p: return 'p'; case SDLK_q: return 'q'; case SDLK_r: return 'r'; case SDLK_s: return 's'; case SDLK_t: return 't'; case SDLK_u: return 'u'; case SDLK_v: return 'v'; case SDLK_w: return 'w'; case SDLK_x: return 'x'; case SDLK_y: return 'y'; case SDLK_z: return 'z'; case SDLK_CAPSLOCK: return K_CAPSLOCK; case SDLK_F1: return K_F1; case SDLK_F2: return K_F2; case SDLK_F3: return K_F3; case SDLK_F4: return K_F4; case SDLK_F5: return K_F5; case SDLK_F6: return K_F6; case SDLK_F7: return K_F7; case SDLK_F8: return K_F8; case SDLK_F9: return K_F9; case SDLK_F10: return K_F10; case SDLK_F11: return K_F11; case SDLK_F12: return K_F12; case SDLK_PRINTSCREEN: return K_PRINTSCREEN; case SDLK_SCROLLLOCK: return K_SCROLLOCK; case SDLK_PAUSE: return K_PAUSE; case SDLK_INSERT: return K_INS; case SDLK_HOME: return K_HOME; case SDLK_PAGEUP: return K_PGUP; #ifdef __IPHONEOS__ case SDLK_DELETE: return K_BACKSPACE; #else case SDLK_DELETE: return K_DEL; #endif case SDLK_END: return K_END; case SDLK_PAGEDOWN: return K_PGDN; case SDLK_RIGHT: return K_RIGHTARROW; case SDLK_LEFT: return K_LEFTARROW; case SDLK_DOWN: return K_DOWNARROW; case SDLK_UP: return K_UPARROW; case SDLK_NUMLOCKCLEAR: return K_NUMLOCK; case SDLK_KP_DIVIDE: return K_KP_DIVIDE; case SDLK_KP_MULTIPLY: return K_KP_MULTIPLY; case SDLK_KP_MINUS: return K_KP_MINUS; case SDLK_KP_PLUS: return K_KP_PLUS; case SDLK_KP_ENTER: return K_KP_ENTER; case SDLK_KP_1: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_1 : K_END); case SDLK_KP_2: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_2 : K_DOWNARROW); case SDLK_KP_3: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_3 : K_PGDN); case SDLK_KP_4: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_4 : K_LEFTARROW); case SDLK_KP_5: return K_KP_5; case SDLK_KP_6: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_6 : K_RIGHTARROW); case SDLK_KP_7: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_7 : K_HOME); case SDLK_KP_8: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_8 : K_UPARROW); case SDLK_KP_9: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_9 : K_PGUP); case SDLK_KP_0: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_0 : K_INS); case SDLK_KP_PERIOD: return ((SDL_GetModState() & KMOD_NUM) ? K_KP_PERIOD : K_DEL); // case SDLK_APPLICATION: return K_APPLICATION; // case SDLK_POWER: return K_POWER; case SDLK_KP_EQUALS: return K_KP_EQUALS; // case SDLK_F13: return K_F13; // case SDLK_F14: return K_F14; // case SDLK_F15: return K_F15; // case SDLK_F16: return K_F16; // case SDLK_F17: return K_F17; // case SDLK_F18: return K_F18; // case SDLK_F19: return K_F19; // case SDLK_F20: return K_F20; // case SDLK_F21: return K_F21; // case SDLK_F22: return K_F22; // case SDLK_F23: return K_F23; // case SDLK_F24: return K_F24; // case SDLK_EXECUTE: return K_EXECUTE; // case SDLK_HELP: return K_HELP; // case SDLK_MENU: return K_MENU; // case SDLK_SELECT: return K_SELECT; // case SDLK_STOP: return K_STOP; // case SDLK_AGAIN: return K_AGAIN; // case SDLK_UNDO: return K_UNDO; // case SDLK_CUT: return K_CUT; // case SDLK_COPY: return K_COPY; // case SDLK_PASTE: return K_PASTE; // case SDLK_FIND: return K_FIND; // case SDLK_MUTE: return K_MUTE; // case SDLK_VOLUMEUP: return K_VOLUMEUP; // case SDLK_VOLUMEDOWN: return K_VOLUMEDOWN; // case SDLK_KP_COMMA: return K_KP_COMMA; // case SDLK_KP_EQUALSAS400: return K_KP_EQUALSAS400; // case SDLK_ALTERASE: return K_ALTERASE; // case SDLK_SYSREQ: return K_SYSREQ; // case SDLK_CANCEL: return K_CANCEL; // case SDLK_CLEAR: return K_CLEAR; // case SDLK_PRIOR: return K_PRIOR; // case SDLK_RETURN2: return K_RETURN2; // case SDLK_SEPARATOR: return K_SEPARATOR; // case SDLK_OUT: return K_OUT; // case SDLK_OPER: return K_OPER; // case SDLK_CLEARAGAIN: return K_CLEARAGAIN; // case SDLK_CRSEL: return K_CRSEL; // case SDLK_EXSEL: return K_EXSEL; // case SDLK_KP_00: return K_KP_00; // case SDLK_KP_000: return K_KP_000; // case SDLK_THOUSANDSSEPARATOR: return K_THOUSANDSSEPARATOR; // case SDLK_DECIMALSEPARATOR: return K_DECIMALSEPARATOR; // case SDLK_CURRENCYUNIT: return K_CURRENCYUNIT; // case SDLK_CURRENCYSUBUNIT: return K_CURRENCYSUBUNIT; // case SDLK_KP_LEFTPAREN: return K_KP_LEFTPAREN; // case SDLK_KP_RIGHTPAREN: return K_KP_RIGHTPAREN; // case SDLK_KP_LEFTBRACE: return K_KP_LEFTBRACE; // case SDLK_KP_RIGHTBRACE: return K_KP_RIGHTBRACE; // case SDLK_KP_TAB: return K_KP_TAB; // case SDLK_KP_BACKSPACE: return K_KP_BACKSPACE; // case SDLK_KP_A: return K_KP_A; // case SDLK_KP_B: return K_KP_B; // case SDLK_KP_C: return K_KP_C; // case SDLK_KP_D: return K_KP_D; // case SDLK_KP_E: return K_KP_E; // case SDLK_KP_F: return K_KP_F; // case SDLK_KP_XOR: return K_KP_XOR; // case SDLK_KP_POWER: return K_KP_POWER; // case SDLK_KP_PERCENT: return K_KP_PERCENT; // case SDLK_KP_LESS: return K_KP_LESS; // case SDLK_KP_GREATER: return K_KP_GREATER; // case SDLK_KP_AMPERSAND: return K_KP_AMPERSAND; // case SDLK_KP_DBLAMPERSAND: return K_KP_DBLAMPERSAND; // case SDLK_KP_VERTICALBAR: return K_KP_VERTICALBAR; // case SDLK_KP_DBLVERTICALBAR: return K_KP_DBLVERTICALBAR; // case SDLK_KP_COLON: return K_KP_COLON; // case SDLK_KP_HASH: return K_KP_HASH; // case SDLK_KP_SPACE: return K_KP_SPACE; // case SDLK_KP_AT: return K_KP_AT; // case SDLK_KP_EXCLAM: return K_KP_EXCLAM; // case SDLK_KP_MEMSTORE: return K_KP_MEMSTORE; // case SDLK_KP_MEMRECALL: return K_KP_MEMRECALL; // case SDLK_KP_MEMCLEAR: return K_KP_MEMCLEAR; // case SDLK_KP_MEMADD: return K_KP_MEMADD; // case SDLK_KP_MEMSUBTRACT: return K_KP_MEMSUBTRACT; // case SDLK_KP_MEMMULTIPLY: return K_KP_MEMMULTIPLY; // case SDLK_KP_MEMDIVIDE: return K_KP_MEMDIVIDE; // case SDLK_KP_PLUSMINUS: return K_KP_PLUSMINUS; // case SDLK_KP_CLEAR: return K_KP_CLEAR; // case SDLK_KP_CLEARENTRY: return K_KP_CLEARENTRY; // case SDLK_KP_BINARY: return K_KP_BINARY; // case SDLK_KP_OCTAL: return K_KP_OCTAL; // case SDLK_KP_DECIMAL: return K_KP_DECIMAL; // case SDLK_KP_HEXADECIMAL: return K_KP_HEXADECIMAL; case SDLK_LCTRL: return K_CTRL; case SDLK_LSHIFT: return K_SHIFT; case SDLK_LALT: return K_ALT; // case SDLK_LGUI: return K_LGUI; case SDLK_RCTRL: return K_CTRL; case SDLK_RSHIFT: return K_SHIFT; case SDLK_RALT: return K_ALT; // case SDLK_RGUI: return K_RGUI; // case SDLK_MODE: return K_MODE; #if SDL_MAJOR_VERSION != 1 // case SDLK_AUDIONEXT: return K_AUDIONEXT; // case SDLK_AUDIOPREV: return K_AUDIOPREV; // case SDLK_AUDIOSTOP: return K_AUDIOSTOP; // case SDLK_AUDIOPLAY: return K_AUDIOPLAY; // case SDLK_AUDIOMUTE: return K_AUDIOMUTE; // case SDLK_MEDIASELECT: return K_MEDIASELECT; // case SDLK_WWW: return K_WWW; // case SDLK_MAIL: return K_MAIL; // case SDLK_CALCULATOR: return K_CALCULATOR; // case SDLK_COMPUTER: return K_COMPUTER; // case SDLK_AC_SEARCH: return K_AC_SEARCH; // Android button // case SDLK_AC_HOME: return K_AC_HOME; // Android button case SDLK_AC_BACK: return K_ESCAPE; // Android button // case SDLK_AC_FORWARD: return K_AC_FORWARD; // Android button // case SDLK_AC_STOP: return K_AC_STOP; // Android button // case SDLK_AC_REFRESH: return K_AC_REFRESH; // Android button // case SDLK_AC_BOOKMARKS: return K_AC_BOOKMARKS; // Android button // case SDLK_BRIGHTNESSDOWN: return K_BRIGHTNESSDOWN; // case SDLK_BRIGHTNESSUP: return K_BRIGHTNESSUP; // case SDLK_DISPLAYSWITCH: return K_DISPLAYSWITCH; // case SDLK_KBDILLUMTOGGLE: return K_KBDILLUMTOGGLE; // case SDLK_KBDILLUMDOWN: return K_KBDILLUMDOWN; // case SDLK_KBDILLUMUP: return K_KBDILLUMUP; // case SDLK_EJECT: return K_EJECT; // case SDLK_SLEEP: return K_SLEEP; #endif } } qboolean VID_HasScreenKeyboardSupport(void) { #if SDL_MAJOR_VERSION != 1 return SDL_HasScreenKeyboardSupport() != SDL_FALSE; #else return false; #endif } void VID_ShowKeyboard(qboolean show) { #if SDL_MAJOR_VERSION != 1 if (!SDL_HasScreenKeyboardSupport()) return; if (show) { if (!SDL_IsTextInputActive()) SDL_StartTextInput(); } else { if (SDL_IsTextInputActive()) SDL_StopTextInput(); } #endif } qboolean VID_ShowingKeyboard(void) { #if SDL_MAJOR_VERSION != 1 return SDL_IsTextInputActive() != 0; #else return false; #endif } void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) { #ifndef DP_MOBILETOUCH #ifdef MACOSX if(relative) if(vid_usingmouse && (vid_usingnoaccel != !!apple_mouse_noaccel.integer)) VID_SetMouse(false, false, false); // ungrab first! #endif if (vid_usingmouse != relative) { vid_usingmouse = relative; cl_ignoremousemoves = 2; #if SDL_MAJOR_VERSION == 1 SDL_WM_GrabInput( relative ? SDL_GRAB_ON : SDL_GRAB_OFF ); #else vid_usingmouse_relativeworks = SDL_SetRelativeMouseMode(relative ? SDL_TRUE : SDL_FALSE) == 0; // Con_Printf("VID_SetMouse(%i, %i, %i) relativeworks = %i\n", (int)fullscreengrab, (int)relative, (int)hidecursor, (int)vid_usingmouse_relativeworks); #endif #ifdef MACOSX if(relative) { // Save the status of mouse acceleration originalMouseSpeed = -1.0; // in case of error if(apple_mouse_noaccel.integer) { io_connect_t mouseDev = IN_GetIOHandle(); if(mouseDev != 0) { if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) { Con_DPrintf("previous mouse acceleration: %f\n", originalMouseSpeed); if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) { Con_Print("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } } else { Con_Print("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } IOServiceClose(mouseDev); } else { Con_Print("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } } vid_usingnoaccel = !!apple_mouse_noaccel.integer; } else { if(originalMouseSpeed != -1.0) { io_connect_t mouseDev = IN_GetIOHandle(); if(mouseDev != 0) { Con_DPrintf("restoring mouse acceleration to: %f\n", originalMouseSpeed); if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) Con_Print("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); IOServiceClose(mouseDev); } else Con_Print("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); } } #endif } if (vid_usinghidecursor != hidecursor) { vid_usinghidecursor = hidecursor; SDL_ShowCursor( hidecursor ? SDL_DISABLE : SDL_ENABLE); } #endif } // multitouch[10][] represents the mouse pointer // multitouch[][0]: finger active // multitouch[][1]: Y // multitouch[][2]: Y // X and Y coordinates are 0-1. #define MAXFINGERS 11 float multitouch[MAXFINGERS][3]; // this one stores how many areas this finger has touched int multitouchs[MAXFINGERS]; // modified heavily by ELUAN static qboolean VID_TouchscreenArea(int corner, float px, float py, float pwidth, float pheight, const char *icon, float textheight, const char *text, float *resultmove, qboolean *resultbutton, keynum_t key, const char *typedtext, float deadzone, float oversizepixels_x, float oversizepixels_y, qboolean iamexclusive) { int finger; float fx, fy, fwidth, fheight; float overfx, overfy, overfwidth, overfheight; float rel[3]; float sqsum; qboolean button = false; VectorClear(rel); if (pwidth > 0 && pheight > 0) { if (corner & 1) px += vid_conwidth.value; if (corner & 2) py += vid_conheight.value; if (corner & 4) px += vid_conwidth.value * 0.5f; if (corner & 8) py += vid_conheight.value * 0.5f; if (corner & 16) {px *= vid_conwidth.value * (1.0f / 640.0f);py *= vid_conheight.value * (1.0f / 480.0f);pwidth *= vid_conwidth.value * (1.0f / 640.0f);pheight *= vid_conheight.value * (1.0f / 480.0f);} fx = px / vid_conwidth.value; fy = py / vid_conheight.value; fwidth = pwidth / vid_conwidth.value; fheight = pheight / vid_conheight.value; // try to prevent oversizepixels_* from interfering with the iamexclusive cvar by not letting we start controlling from too far of the actual touch area (areas without resultbuttons should NEVER have the oversizepixels_* parameters set to anything other than 0) if (resultbutton) if (!(*resultbutton)) { oversizepixels_x *= 0.2; oversizepixels_y *= 0.2; } oversizepixels_x /= vid_conwidth.value; oversizepixels_y /= vid_conheight.value; overfx = fx - oversizepixels_x; overfy = fy - oversizepixels_y; overfwidth = fwidth + 2*oversizepixels_x; overfheight = fheight + 2*oversizepixels_y; for (finger = 0;finger < MAXFINGERS;finger++) { if (multitouchs[finger] && iamexclusive) // for this to work correctly, you must call touch areas in order of highest to lowest priority continue; if (multitouch[finger][0] && multitouch[finger][1] >= overfx && multitouch[finger][2] >= overfy && multitouch[finger][1] < overfx + overfwidth && multitouch[finger][2] < overfy + overfheight) { multitouchs[finger]++; rel[0] = bound(-1, (multitouch[finger][1] - (fx + 0.5f * fwidth)) * (2.0f / fwidth), 1); rel[1] = bound(-1, (multitouch[finger][2] - (fy + 0.5f * fheight)) * (2.0f / fheight), 1); rel[2] = 0; sqsum = rel[0]*rel[0] + rel[1]*rel[1]; // 2d deadzone if (sqsum < deadzone*deadzone) { rel[0] = 0; rel[1] = 0; } else if (sqsum > 1) { // ignore the third component Vector2Normalize2(rel, rel); } button = true; break; } } if (scr_numtouchscreenareas < 128) { scr_touchscreenareas[scr_numtouchscreenareas].pic = icon; scr_touchscreenareas[scr_numtouchscreenareas].text = text; scr_touchscreenareas[scr_numtouchscreenareas].textheight = textheight; scr_touchscreenareas[scr_numtouchscreenareas].rect[0] = px; scr_touchscreenareas[scr_numtouchscreenareas].rect[1] = py; scr_touchscreenareas[scr_numtouchscreenareas].rect[2] = pwidth; scr_touchscreenareas[scr_numtouchscreenareas].rect[3] = pheight; scr_touchscreenareas[scr_numtouchscreenareas].active = button; // the pics may have alpha too. scr_touchscreenareas[scr_numtouchscreenareas].activealpha = 1.f; scr_touchscreenareas[scr_numtouchscreenareas].inactivealpha = 0.95f; scr_numtouchscreenareas++; } } if (resultmove) { if (button) VectorCopy(rel, resultmove); else VectorClear(resultmove); } if (resultbutton) { if (*resultbutton != button) { if ((int)key > 0) Key_Event(key, 0, button); if (typedtext && typedtext[0] && !*resultbutton) { // FIXME: implement UTF8 support - nothing actually specifies a UTF8 string here yet, but should support it... int i; for (i = 0;typedtext[i];i++) { Key_Event(K_TEXT, typedtext[i], true); Key_Event(K_TEXT, typedtext[i], false); } } } *resultbutton = button; } return button; } // ELUAN: // not reentrant, but we only need one mouse cursor anyway... static void VID_TouchscreenCursor(float px, float py, float pwidth, float pheight, qboolean *resultbutton, keynum_t key) { int finger; float fx, fy, fwidth, fheight; qboolean button = false; static int cursorfinger = -1; static int cursorfreemovement = false; static int canclick = false; static int clickxy[2]; static int relclickxy[2]; static double clickrealtime = 0; if (steelstorm_showing_mousecursor && steelstorm_showing_mousecursor->integer) if (pwidth > 0 && pheight > 0) { fx = px / vid_conwidth.value; fy = py / vid_conheight.value; fwidth = pwidth / vid_conwidth.value; fheight = pheight / vid_conheight.value; for (finger = 0;finger < MAXFINGERS;finger++) { if (multitouch[finger][0] && multitouch[finger][1] >= fx && multitouch[finger][2] >= fy && multitouch[finger][1] < fx + fwidth && multitouch[finger][2] < fy + fheight) { if (cursorfinger == -1) { clickxy[0] = multitouch[finger][1] * vid_width.value - 0.5f * pwidth; clickxy[1] = multitouch[finger][2] * vid_height.value - 0.5f * pheight; relclickxy[0] = (multitouch[finger][1] - fx) * vid_width.value - 0.5f * pwidth; relclickxy[1] = (multitouch[finger][2] - fy) * vid_height.value - 0.5f * pheight; } cursorfinger = finger; button = true; canclick = true; cursorfreemovement = false; break; } } if (scr_numtouchscreenareas < 128) { if (clickrealtime + 1 > realtime) { scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_click.tga"; } else if (button) { scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_touch.tga"; } else { switch ((int)realtime * 10 % 20) { case 0: scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_touch.tga"; break; default: scr_touchscreenareas[scr_numtouchscreenareas].pic = "gfx/gui/touch_puck_cur_idle.tga"; } } scr_touchscreenareas[scr_numtouchscreenareas].text = ""; scr_touchscreenareas[scr_numtouchscreenareas].textheight = 0; scr_touchscreenareas[scr_numtouchscreenareas].rect[0] = px; scr_touchscreenareas[scr_numtouchscreenareas].rect[1] = py; scr_touchscreenareas[scr_numtouchscreenareas].rect[2] = pwidth; scr_touchscreenareas[scr_numtouchscreenareas].rect[3] = pheight; scr_touchscreenareas[scr_numtouchscreenareas].active = button; scr_touchscreenareas[scr_numtouchscreenareas].activealpha = 1.0f; scr_touchscreenareas[scr_numtouchscreenareas].inactivealpha = 1.0f; scr_numtouchscreenareas++; } } if (cursorfinger != -1) { if (multitouch[cursorfinger][0]) { if (multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth < clickxy[0] - 1 || multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth > clickxy[0] + 1 || multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight< clickxy[1] - 1 || multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight> clickxy[1] + 1) // finger drifted more than the allowed amount { cursorfreemovement = true; } if (cursorfreemovement) { // in_windowmouse_x* is in screen resolution coordinates, not console resolution in_windowmouse_x = multitouch[cursorfinger][1] * vid_width.value - 0.5f * pwidth - relclickxy[0]; in_windowmouse_y = multitouch[cursorfinger][2] * vid_height.value - 0.5f * pheight - relclickxy[1]; } } else { cursorfinger = -1; } } if (resultbutton) { if (/**resultbutton != button && */(int)key > 0) { if (!button && !cursorfreemovement && canclick) { Key_Event(key, 0, true); canclick = false; clickrealtime = realtime; } // SS:BR can't qc can't cope with presses and releases on the same frame if (clickrealtime && clickrealtime + 0.1 < realtime) { Key_Event(key, 0, false); clickrealtime = 0; } } *resultbutton = button; } } void VID_BuildJoyState(vid_joystate_t *joystate) { VID_Shared_BuildJoyState_Begin(joystate); if (vid_sdljoystick) { SDL_Joystick *joy = vid_sdljoystick; int j; int numaxes; int numbuttons; numaxes = SDL_JoystickNumAxes(joy); for (j = 0;j < numaxes;j++) joystate->axis[j] = SDL_JoystickGetAxis(joy, j) * (1.0f / 32767.0f); numbuttons = SDL_JoystickNumButtons(joy); for (j = 0;j < numbuttons;j++) joystate->button[j] = SDL_JoystickGetButton(joy, j); } VID_Shared_BuildJoyState_Finish(joystate); } // clear every touch screen area, except the one with button[skip] #define Vid_ClearAllTouchscreenAreas(skip) \ if (skip != 0) \ VID_TouchscreenCursor(0, 0, 0, 0, &buttons[0], K_MOUSE1); \ if (skip != 1) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, move, &buttons[1], K_MOUSE4, NULL, 0, 0, 0, false); \ if (skip != 2) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, aim, &buttons[2], K_MOUSE5, NULL, 0, 0, 0, false); \ if (skip != 3) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[3], K_SHIFT, NULL, 0, 0, 0, false); \ if (skip != 4) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, false); \ if (skip != 9) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[9], K_MOUSE3, NULL, 0, 0, 0, false); \ if (skip != 10) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, false); \ if (skip != 11) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[11], (keynum_t)'b', NULL, 0, 0, 0, false); \ if (skip != 12) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[12], (keynum_t)'q', NULL, 0, 0, 0, false); \ if (skip != 13) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, false); \ if (skip != 14) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false); \ if (skip != 15) \ VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, false); \ ///////////////////// // Movement handling //// static void IN_Move_TouchScreen_SteelStorm(void) { // ELUAN int i, numfingers; float xscale, yscale; float move[3], aim[3]; static qboolean oldbuttons[128]; static qboolean buttons[128]; keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest; memcpy(oldbuttons, buttons, sizeof(oldbuttons)); memset(multitouchs, 0, sizeof(multitouchs)); for (i = 0, numfingers = 0; i < MAXFINGERS - 1; i++) if (multitouch[i][0]) numfingers++; /* Enable this to use a mouse as a touch device (it may conflict with the iamexclusive parameter if a finger is also reported as a mouse at the same location if (numfingers == 1) { multitouch[MAXFINGERS-1][0] = SDL_GetMouseState(&x, &y) ? 11 : 0; multitouch[MAXFINGERS-1][1] = (float)x / vid.width; multitouch[MAXFINGERS-1][2] = (float)y / vid.height; } else { // disable it so it doesn't get stuck, because SDL seems to stop updating it if there are more than 1 finger on screen multitouch[MAXFINGERS-1][0] = 0; }*/ // TODO: make touchscreen areas controlled by a config file or the VMs. THIS IS A MESS! // TODO: can't just clear buttons[] when entering a new keydest, some keys would remain pressed // SS:BR menuqc has many peculiarities, including that it can't accept more than one command per frame and pressing and releasing on the same frame // Tuned for the SGS3, use it's value as a base. CLEAN THIS. xscale = vid_touchscreen_density.value / 2.0f; yscale = vid_touchscreen_density.value / 2.0f; switch(keydest) { case key_console: Vid_ClearAllTouchscreenAreas(14); VID_TouchscreenArea( 0, 0, 160, 64, 64, "gfx/gui/touch_menu_button.tga" , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false); break; case key_game: if (steelstorm_showing_map && steelstorm_showing_map->integer) // FIXME: another hack to be removed when touchscreen areas go to QC { VID_TouchscreenArea( 0, 0, 0, vid_conwidth.value, vid_conheight.value, NULL , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, false); Vid_ClearAllTouchscreenAreas(10); } else if (steelstorm_showing_mousecursor && steelstorm_showing_mousecursor->integer) { // in_windowmouse_x* is in screen resolution coordinates, not console resolution VID_TouchscreenCursor((float)in_windowmouse_x/vid_width.value*vid_conwidth.value, (float)in_windowmouse_y/vid_height.value*vid_conheight.value, 192*xscale, 192*yscale, &buttons[0], K_MOUSE1); Vid_ClearAllTouchscreenAreas(0); } else { VID_TouchscreenCursor(0, 0, 0, 0, &buttons[0], K_MOUSE1); VID_TouchscreenArea( 2,16*xscale,-240*yscale, 224*xscale, 224*yscale, "gfx/gui/touch_l_thumb_dpad.tga", 0.0f, NULL, move, &buttons[1], (keynum_t)0, NULL, 0.15, 112*xscale, 112*yscale, false); VID_TouchscreenArea( 3,-240*xscale,-160*yscale, 224*xscale, 128*yscale, "gfx/gui/touch_r_thumb_turn_n_shoot.tga" , 0.0f, NULL, NULL, 0, (keynum_t)0, NULL, 0, 56*xscale, 0, false); VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale, 224*yscale, NULL , 0.0f, NULL, aim, &buttons[2], (keynum_t)0, NULL, 0.2, 56*xscale, 0, false); VID_TouchscreenArea( 2, (vid_conwidth.value / 2) - 128,-80, 256, 80, NULL, 0.0f, NULL, NULL, &buttons[3], K_SHIFT, NULL, 0, 0, 0, true); VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale, 64*yscale, "gfx/gui/touch_secondary_slide.tga", 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 56*xscale, 0, false); VID_TouchscreenArea( 3,-240*xscale,-256*yscale, 224*xscale, 160*yscale, NULL , 0.0f, NULL, NULL, &buttons[9], K_MOUSE3, NULL, 0.2, 56*xscale, 0, false); VID_TouchscreenArea( 1,-100, 0, 100, 100, NULL , 0.0f, NULL, NULL, &buttons[10], (keynum_t)'m', NULL, 0, 0, 0, true); VID_TouchscreenArea( 1,-100, 120, 100, 100, NULL , 0.0f, NULL, NULL, &buttons[11], (keynum_t)'b', NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , 0.0f, NULL, NULL, &buttons[12], (keynum_t)'q', NULL, 0, 0, 0, true); if (developer.integer) VID_TouchscreenArea( 0, 0, 96, 64, 64, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true); else VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, false); VID_TouchscreenArea( 0, 0, 160, 64, 64, "gfx/gui/touch_menu_button.tga" , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true); switch(cl.activeweapon) { case 14: VID_TouchscreenArea( 2, 16*xscale,-320*yscale, 224*xscale, 64*yscale, "gfx/gui/touch_booster.tga" , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, true); break; case 12: VID_TouchscreenArea( 2, 16*xscale,-320*yscale, 224*xscale, 64*yscale, "gfx/gui/touch_shockwave.tga" , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, true); break; default: VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[15], K_SPACE, NULL, 0, 0, 0, false); } } break; default: if (!steelstorm_showing_mousecursor || !steelstorm_showing_mousecursor->integer) { Vid_ClearAllTouchscreenAreas(14); // this way we can skip cutscenes VID_TouchscreenArea( 0, 0, 0, vid_conwidth.value, vid_conheight.value, NULL , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, false); } else { // in_windowmouse_x* is in screen resolution coordinates, not console resolution VID_TouchscreenCursor((float)in_windowmouse_x/vid_width.value*vid_conwidth.value, (float)in_windowmouse_y/vid_height.value*vid_conheight.value, 192*xscale, 192*yscale, &buttons[0], K_MOUSE1); Vid_ClearAllTouchscreenAreas(0); } break; } if (VID_ShowingKeyboard() && (float)in_windowmouse_y > vid_height.value / 2 - 10) in_windowmouse_y = 128; cl.cmd.forwardmove -= move[1] * cl_forwardspeed.value; cl.cmd.sidemove += move[0] * cl_sidespeed.value; cl.viewangles[0] += aim[1] * cl_pitchspeed.value * cl.realframetime; cl.viewangles[1] -= aim[0] * cl_yawspeed.value * cl.realframetime; } static void IN_Move_TouchScreen_Quake(void) { int x, y; float move[3], aim[3], click[3]; static qboolean oldbuttons[128]; static qboolean buttons[128]; keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest; memcpy(oldbuttons, buttons, sizeof(oldbuttons)); memset(multitouchs, 0, sizeof(multitouchs)); // simple quake controls multitouch[MAXFINGERS-1][0] = SDL_GetMouseState(&x, &y); multitouch[MAXFINGERS-1][1] = x * 32768 / vid.width; multitouch[MAXFINGERS-1][2] = y * 32768 / vid.height; // top of screen is toggleconsole and K_ESCAPE switch(keydest) { case key_console: VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true); if (!VID_ShowingKeyboard()) { // user entered a command, close the console now Con_ToggleConsole_f(); } VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[15], (keynum_t)0, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, aim, &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, click,&buttons[2], K_MOUSE1, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true); break; case key_game: VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true); VID_TouchscreenArea( 2, 0,-128, 128, 128, "gfx/touch_movebutton.tga" , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true); VID_TouchscreenArea( 3,-128,-128, 128, 128, "gfx/touch_aimbutton.tga" , 0.0f, NULL, aim, &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true); VID_TouchscreenArea( 2, 0,-160, 64, 32, "gfx/touch_jumpbutton.tga" , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true); VID_TouchscreenArea( 3,-128,-160, 64, 32, "gfx/touch_attackbutton.tga" , 0.0f, NULL, NULL, &buttons[2], K_MOUSE1, NULL, 0, 0, 0, true); VID_TouchscreenArea( 3, -64,-160, 64, 32, "gfx/touch_attack2button.tga", 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true); buttons[15] = false; break; default: VID_TouchscreenArea( 0, 0, 0, 64, 64, NULL , 0.0f, NULL, NULL, &buttons[13], (keynum_t)'`', NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 64, 0, 64, 64, "gfx/touch_menu.tga" , 0.0f, NULL, NULL, &buttons[14], K_ESCAPE, NULL, 0, 0, 0, true); // in menus, an icon in the corner activates keyboard VID_TouchscreenArea( 2, 0, -32, 32, 32, "gfx/touch_keyboard.tga" , 0.0f, NULL, NULL, &buttons[15], (keynum_t)0, NULL, 0, 0, 0, true); if (buttons[15]) VID_ShowKeyboard(true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, move, &buttons[0], K_MOUSE4, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, aim, &buttons[1], K_MOUSE5, NULL, 0, 0, 0, true); VID_TouchscreenArea(16, -320,-480,640, 960, NULL , 0.0f, NULL, click,&buttons[2], K_MOUSE1, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[3], K_SPACE, NULL, 0, 0, 0, true); VID_TouchscreenArea( 0, 0, 0, 0, 0, NULL , 0.0f, NULL, NULL, &buttons[4], K_MOUSE2, NULL, 0, 0, 0, true); if (buttons[2]) { in_windowmouse_x = x; in_windowmouse_y = y; } break; } cl.cmd.forwardmove -= move[1] * cl_forwardspeed.value; cl.cmd.sidemove += move[0] * cl_sidespeed.value; cl.viewangles[0] += aim[1] * cl_pitchspeed.value * cl.realframetime; cl.viewangles[1] -= aim[0] * cl_yawspeed.value * cl.realframetime; } void IN_Move( void ) { static int old_x = 0, old_y = 0; static int stuck = 0; static keydest_t oldkeydest; static qboolean oldshowkeyboard; int x, y; vid_joystate_t joystate; keydest_t keydest = (key_consoleactive & KEY_CONSOLEACTIVE_USER) ? key_console : key_dest; scr_numtouchscreenareas = 0; // Only apply the new keyboard state if the input changes. if (keydest != oldkeydest || !!vid_touchscreen_showkeyboard.integer != oldshowkeyboard) { switch(keydest) { case key_console: VID_ShowKeyboard(true);break; case key_message: VID_ShowKeyboard(true);break; default: VID_ShowKeyboard(!!vid_touchscreen_showkeyboard.integer); break; } } oldkeydest = keydest; oldshowkeyboard = !!vid_touchscreen_showkeyboard.integer; if (vid_touchscreen.integer) { switch(gamemode) { case GAME_STEELSTORM: IN_Move_TouchScreen_SteelStorm(); break; default: IN_Move_TouchScreen_Quake(); break; } } else { if (vid_usingmouse) { if (vid_stick_mouse.integer || !vid_usingmouse_relativeworks) { // have the mouse stuck in the middle, example use: prevent expose effect of beryl during the game when not using // window grabbing. --blub // we need 2 frames to initialize the center position if(!stuck) { #if SDL_MAJOR_VERSION == 1 SDL_WarpMouse(win_half_width, win_half_height); #else SDL_WarpMouseInWindow(window, win_half_width, win_half_height); #endif SDL_GetMouseState(&x, &y); SDL_GetRelativeMouseState(&x, &y); ++stuck; } else { SDL_GetRelativeMouseState(&x, &y); in_mouse_x = x + old_x; in_mouse_y = y + old_y; SDL_GetMouseState(&x, &y); old_x = x - win_half_width; old_y = y - win_half_height; #if SDL_MAJOR_VERSION == 1 SDL_WarpMouse(win_half_width, win_half_height); #else SDL_WarpMouseInWindow(window, win_half_width, win_half_height); #endif } } else { SDL_GetRelativeMouseState( &x, &y ); in_mouse_x = x; in_mouse_y = y; } } SDL_GetMouseState(&x, &y); in_windowmouse_x = x; in_windowmouse_y = y; } VID_BuildJoyState(&joystate); VID_ApplyJoyState(&joystate); } ///////////////////// // Message Handling //// #ifdef SDL_R_RESTART static qboolean sdl_needs_restart; static void sdl_start(void) { } static void sdl_shutdown(void) { sdl_needs_restart = false; } static void sdl_newmap(void) { } #endif static keynum_t buttonremap[] = { K_MOUSE1, K_MOUSE3, K_MOUSE2, #if SDL_MAJOR_VERSION == 1 // TODO Find out how SDL maps these buttons. It looks like we should // still include these for sdl2? At least the button indexes don't // differ between SDL1 and SDL2 for me, thus this array should stay the // same (in X11 button order). K_MWHEELUP, K_MWHEELDOWN, #endif K_MOUSE4, K_MOUSE5, K_MOUSE6, K_MOUSE7, K_MOUSE8, K_MOUSE9, K_MOUSE10, K_MOUSE11, K_MOUSE12, K_MOUSE13, K_MOUSE14, K_MOUSE15, K_MOUSE16, }; #if SDL_MAJOR_VERSION == 1 // SDL void Sys_SendKeyEvents( void ) { static qboolean sound_active = true; int keycode; SDL_Event event; VID_EnableJoystick(true); while( SDL_PollEvent( &event ) ) switch( event.type ) { case SDL_QUIT: Sys_Quit(0); break; case SDL_KEYDOWN: case SDL_KEYUP: keycode = MapKey(event.key.keysym.sym); if (!VID_JoyBlockEmulatedKeys(keycode)) { if(keycode == K_NUMLOCK || keycode == K_CAPSLOCK) { // simulate down followed by up Key_Event(keycode, event.key.keysym.unicode, true); Key_Event(keycode, event.key.keysym.unicode, false); break; } Key_Event(keycode, event.key.keysym.unicode, (event.key.state == SDL_PRESSED)); } break; case SDL_ACTIVEEVENT: if( event.active.state & SDL_APPACTIVE ) { if( event.active.gain ) vid_hidden = false; else vid_hidden = true; } break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if (!vid_touchscreen.integer) if (event.button.button > 0 && event.button.button <= ARRAY_SIZE(buttonremap)) Key_Event( buttonremap[event.button.button - 1], 0, event.button.state == SDL_PRESSED ); break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYAXISMOTION: case SDL_JOYBALLMOTION: case SDL_JOYHATMOTION: break; case SDL_VIDEOEXPOSE: break; case SDL_VIDEORESIZE: if(vid_resizable.integer < 2 || vid_isfullscreen) { vid.width = event.resize.w; vid.height = event.resize.h; if (!vid_isfullscreen) video_screen = SDL_SetVideoMode(vid.width, vid.height, video_bpp, video_flags); if (vid_softsurface) { SDL_FreeSurface(vid_softsurface); vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, vid.width, vid.height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); SDL_SetAlpha(vid_softsurface, 0, 255); vid.softpixels = (unsigned int *)vid_softsurface->pixels; if (vid.softdepthpixels) free(vid.softdepthpixels); vid.softdepthpixels = (unsigned int*)calloc(1, vid.width * vid.height * 4); } #ifdef SDL_R_RESTART // better not call R_Modules_Restart from here directly, as this may wreak havoc... // so, let's better queue it for next frame if(!sdl_needs_restart) { Cbuf_AddText("\nr_restart\n"); sdl_needs_restart = true; } #endif } break; #if SDL_MAJOR_VERSION != 1 case SDL_TEXTEDITING: break; case SDL_TEXTINPUT: break; #endif case SDL_MOUSEMOTION: break; default: Con_DPrintf("Received unrecognized SDL_Event type 0x%x\n", event.type); break; } // enable/disable sound on focus gain/loss if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) { if (!sound_active) { S_UnblockSound (); sound_active = true; } } else { if (sound_active) { S_BlockSound (); sound_active = false; } } } #else //#define DEBUGSDLEVENTS // SDL2 void Sys_SendKeyEvents( void ) { static qboolean sound_active = true; int keycode; int i; Uchar unicode; SDL_Event event; VID_EnableJoystick(true); while( SDL_PollEvent( &event ) ) switch( event.type ) { case SDL_QUIT: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_QUIT\n"); #endif Sys_Quit(0); break; case SDL_KEYDOWN: case SDL_KEYUP: #ifdef DEBUGSDLEVENTS if (event.type == SDL_KEYDOWN) Con_DPrintf("SDL_Event: SDL_KEYDOWN %i\n", event.key.keysym.sym); else Con_DPrintf("SDL_Event: SDL_KEYUP %i\n", event.key.keysym.sym); #endif keycode = MapKey(event.key.keysym.sym); if (!VID_JoyBlockEmulatedKeys(keycode)) Key_Event(keycode, 0, (event.key.state == SDL_PRESSED)); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: #ifdef DEBUGSDLEVENTS if (event.type == SDL_MOUSEBUTTONDOWN) Con_DPrintf("SDL_Event: SDL_MOUSEBUTTONDOWN\n"); else Con_DPrintf("SDL_Event: SDL_MOUSEBUTTONUP\n"); #endif if (!vid_touchscreen.integer) if (event.button.button > 0 && event.button.button <= ARRAY_SIZE(buttonremap)) Key_Event( buttonremap[event.button.button - 1], 0, event.button.state == SDL_PRESSED ); break; case SDL_MOUSEWHEEL: // TODO support wheel x direction. i = event.wheel.y; while (i > 0) { --i; Key_Event( K_MWHEELUP, 0, true ); Key_Event( K_MWHEELUP, 0, false ); } while (i < 0) { ++i; Key_Event( K_MWHEELDOWN, 0, true ); Key_Event( K_MWHEELDOWN, 0, false ); } break; case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYAXISMOTION: case SDL_JOYBALLMOTION: case SDL_JOYHATMOTION: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_JOY*\n"); #endif break; case SDL_WINDOWEVENT: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_WINDOWEVENT %i\n", (int)event.window.event); #endif //if (event.window.windowID == window) // how to compare? { switch(event.window.event) { case SDL_WINDOWEVENT_SHOWN: vid_hidden = false; break; case SDL_WINDOWEVENT_HIDDEN: vid_hidden = true; break; case SDL_WINDOWEVENT_EXPOSED: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_WINDOWEVENT_EXPOSED\n"); #endif break; case SDL_WINDOWEVENT_MOVED: break; case SDL_WINDOWEVENT_RESIZED: if(vid_resizable.integer < 2) { vid.width = event.window.data1; vid.height = event.window.data2; if (vid_softsurface) { SDL_FreeSurface(vid_softsurface); vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, vid.width, vid.height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); SDL_SetSurfaceBlendMode(vid_softsurface, SDL_BLENDMODE_NONE); vid.softpixels = (unsigned int *)vid_softsurface->pixels; if (vid.softdepthpixels) free(vid.softdepthpixels); vid.softdepthpixels = (unsigned int*)calloc(1, vid.width * vid.height * 4); } #ifdef SDL_R_RESTART // better not call R_Modules_Restart from here directly, as this may wreak havoc... // so, let's better queue it for next frame if(!sdl_needs_restart) { Cbuf_AddText("\nr_restart\n"); sdl_needs_restart = true; } #endif } break; case SDL_WINDOWEVENT_MINIMIZED: break; case SDL_WINDOWEVENT_MAXIMIZED: break; case SDL_WINDOWEVENT_RESTORED: break; case SDL_WINDOWEVENT_ENTER: break; case SDL_WINDOWEVENT_LEAVE: break; case SDL_WINDOWEVENT_FOCUS_GAINED: vid_hasfocus = true; break; case SDL_WINDOWEVENT_FOCUS_LOST: vid_hasfocus = false; break; case SDL_WINDOWEVENT_CLOSE: Sys_Quit(0); break; } } break; case SDL_TEXTEDITING: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_TEXTEDITING - composition = %s, cursor = %d, selection lenght = %d\n", event.edit.text, event.edit.start, event.edit.length); #endif // FIXME! this is where composition gets supported break; case SDL_TEXTINPUT: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_Event: SDL_TEXTINPUT - text: %s\n", event.text.text); #endif // convert utf8 string to char // NOTE: this code is supposed to run even if utf8enable is 0 unicode = u8_getchar_utf8_enabled(event.text.text + (int)u8_bytelen(event.text.text, 0), NULL); Key_Event(K_TEXT, unicode, true); Key_Event(K_TEXT, unicode, false); break; case SDL_MOUSEMOTION: break; case SDL_FINGERDOWN: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_FINGERDOWN for finger %i\n", (int)event.tfinger.fingerId); #endif for (i = 0;i < MAXFINGERS-1;i++) { if (!multitouch[i][0]) { multitouch[i][0] = event.tfinger.fingerId + 1; multitouch[i][1] = event.tfinger.x; multitouch[i][2] = event.tfinger.y; // TODO: use event.tfinger.pressure? break; } } if (i == MAXFINGERS-1) Con_DPrintf("Too many fingers at once!\n"); break; case SDL_FINGERUP: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_FINGERUP for finger %i\n", (int)event.tfinger.fingerId); #endif for (i = 0;i < MAXFINGERS-1;i++) { if (multitouch[i][0] == event.tfinger.fingerId + 1) { multitouch[i][0] = 0; break; } } if (i == MAXFINGERS-1) Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n"); break; case SDL_FINGERMOTION: #ifdef DEBUGSDLEVENTS Con_DPrintf("SDL_FINGERMOTION for finger %i\n", (int)event.tfinger.fingerId); #endif for (i = 0;i < MAXFINGERS-1;i++) { if (multitouch[i][0] == event.tfinger.fingerId + 1) { multitouch[i][1] = event.tfinger.x; multitouch[i][2] = event.tfinger.y; break; } } if (i == MAXFINGERS-1) Con_DPrintf("No SDL_FINGERDOWN event matches this SDL_FINGERMOTION event\n"); break; default: #ifdef DEBUGSDLEVENTS Con_DPrintf("Received unrecognized SDL_Event type 0x%x\n", event.type); #endif break; } // enable/disable sound on focus gain/loss if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) { if (!sound_active) { S_UnblockSound (); sound_active = true; } } else { if (sound_active) { S_BlockSound (); sound_active = false; } } } #endif ///////////////// // Video system //// #ifdef USE_GLES2 #ifndef qglClear #ifdef __IPHONEOS__ #include #else #include #endif //#define PRECALL //Con_Printf("GLCALL %s:%i\n", __FILE__, __LINE__) #define PRECALL #define POSTCALL GLboolean wrapglIsBuffer(GLuint buffer) {PRECALL;return glIsBuffer(buffer);POSTCALL;} GLboolean wrapglIsEnabled(GLenum cap) {PRECALL;return glIsEnabled(cap);POSTCALL;} GLboolean wrapglIsFramebuffer(GLuint framebuffer) {PRECALL;return glIsFramebuffer(framebuffer);POSTCALL;} //GLboolean wrapglIsQuery(GLuint qid) {PRECALL;return glIsQuery(qid);POSTCALL;} GLboolean wrapglIsRenderbuffer(GLuint renderbuffer) {PRECALL;return glIsRenderbuffer(renderbuffer);POSTCALL;} //GLboolean wrapglUnmapBuffer(GLenum target) {PRECALL;return glUnmapBuffer(target);POSTCALL;} GLenum wrapglCheckFramebufferStatus(GLenum target) {PRECALL;return glCheckFramebufferStatus(target);POSTCALL;} GLenum wrapglGetError(void) {PRECALL;return glGetError();POSTCALL;} GLuint wrapglCreateProgram(void) {PRECALL;return glCreateProgram();POSTCALL;} GLuint wrapglCreateShader(GLenum shaderType) {PRECALL;return glCreateShader(shaderType);POSTCALL;} //GLuint wrapglGetHandle(GLenum pname) {PRECALL;return glGetHandle(pname);POSTCALL;} GLint wrapglGetAttribLocation(GLuint programObj, const GLchar *name) {PRECALL;return glGetAttribLocation(programObj, name);POSTCALL;} GLint wrapglGetUniformLocation(GLuint programObj, const GLchar *name) {PRECALL;return glGetUniformLocation(programObj, name);POSTCALL;} //GLvoid* wrapglMapBuffer(GLenum target, GLenum access) {PRECALL;return glMapBuffer(target, access);POSTCALL;} const GLubyte* wrapglGetString(GLenum name) {PRECALL;return (const GLubyte*)glGetString(name);POSTCALL;} void wrapglActiveStencilFace(GLenum e) {PRECALL;Con_Printf("glActiveStencilFace(e)\n");POSTCALL;} void wrapglActiveTexture(GLenum e) {PRECALL;glActiveTexture(e);POSTCALL;} void wrapglAlphaFunc(GLenum func, GLclampf ref) {PRECALL;Con_Printf("glAlphaFunc(func, ref)\n");POSTCALL;} void wrapglArrayElement(GLint i) {PRECALL;Con_Printf("glArrayElement(i)\n");POSTCALL;} void wrapglAttachShader(GLuint containerObj, GLuint obj) {PRECALL;glAttachShader(containerObj, obj);POSTCALL;} //void wrapglBegin(GLenum mode) {PRECALL;Con_Printf("glBegin(mode)\n");POSTCALL;} //void wrapglBeginQuery(GLenum target, GLuint qid) {PRECALL;glBeginQuery(target, qid);POSTCALL;} void wrapglBindAttribLocation(GLuint programObj, GLuint index, const GLchar *name) {PRECALL;glBindAttribLocation(programObj, index, name);POSTCALL;} //void wrapglBindFragDataLocation(GLuint programObj, GLuint index, const GLchar *name) {PRECALL;glBindFragDataLocation(programObj, index, name);POSTCALL;} void wrapglBindBuffer(GLenum target, GLuint buffer) {PRECALL;glBindBuffer(target, buffer);POSTCALL;} void wrapglBindFramebuffer(GLenum target, GLuint framebuffer) {PRECALL;glBindFramebuffer(target, framebuffer);POSTCALL;} void wrapglBindRenderbuffer(GLenum target, GLuint renderbuffer) {PRECALL;glBindRenderbuffer(target, renderbuffer);POSTCALL;} void wrapglBindTexture(GLenum target, GLuint texture) {PRECALL;glBindTexture(target, texture);POSTCALL;} void wrapglBlendEquation(GLenum e) {PRECALL;glBlendEquation(e);POSTCALL;} void wrapglBlendFunc(GLenum sfactor, GLenum dfactor) {PRECALL;glBlendFunc(sfactor, dfactor);POSTCALL;} void wrapglBufferData(GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage) {PRECALL;glBufferData(target, size, data, usage);POSTCALL;} void wrapglBufferSubData(GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data) {PRECALL;glBufferSubData(target, offset, size, data);POSTCALL;} void wrapglClear(GLbitfield mask) {PRECALL;glClear(mask);POSTCALL;} void wrapglClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha) {PRECALL;glClearColor(red, green, blue, alpha);POSTCALL;} void wrapglClearDepth(GLclampd depth) {PRECALL;/*Con_Printf("glClearDepth(%f)\n", depth);glClearDepthf((float)depth);*/POSTCALL;} void wrapglClearStencil(GLint s) {PRECALL;glClearStencil(s);POSTCALL;} void wrapglClientActiveTexture(GLenum target) {PRECALL;Con_Printf("glClientActiveTexture(target)\n");POSTCALL;} void wrapglColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {PRECALL;Con_Printf("glColor4f(red, green, blue, alpha)\n");POSTCALL;} void wrapglColor4ub(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha) {PRECALL;Con_Printf("glColor4ub(red, green, blue, alpha)\n");POSTCALL;} void wrapglColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) {PRECALL;glColorMask(red, green, blue, alpha);POSTCALL;} void wrapglColorPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {PRECALL;Con_Printf("glColorPointer(size, type, stride, ptr)\n");POSTCALL;} void wrapglCompileShader(GLuint shaderObj) {PRECALL;glCompileShader(shaderObj);POSTCALL;} void wrapglCompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data) {PRECALL;glCompressedTexImage2D(target, level, internalformat, width, height, border, imageSize, data);POSTCALL;} void wrapglCompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data) {PRECALL;Con_Printf("glCompressedTexImage3D(target, level, internalformat, width, height, depth, border, imageSize, data)\n");POSTCALL;} void wrapglCompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data) {PRECALL;glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, imageSize, data);POSTCALL;} void wrapglCompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data) {PRECALL;Con_Printf("glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data)\n");POSTCALL;} void wrapglCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {PRECALL;glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);POSTCALL;} void wrapglCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) {PRECALL;glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);POSTCALL;} void wrapglCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) {PRECALL;Con_Printf("glCopyTexSubImage3D(target, level, xoffset, yoffset, zoffset, x, y, width, height)\n");POSTCALL;} void wrapglCullFace(GLenum mode) {PRECALL;glCullFace(mode);POSTCALL;} void wrapglDeleteBuffers(GLsizei n, const GLuint *buffers) {PRECALL;glDeleteBuffers(n, buffers);POSTCALL;} void wrapglDeleteFramebuffers(GLsizei n, const GLuint *framebuffers) {PRECALL;glDeleteFramebuffers(n, framebuffers);POSTCALL;} void wrapglDeleteShader(GLuint obj) {PRECALL;glDeleteShader(obj);POSTCALL;} void wrapglDeleteProgram(GLuint obj) {PRECALL;glDeleteProgram(obj);POSTCALL;} //void wrapglDeleteQueries(GLsizei n, const GLuint *ids) {PRECALL;glDeleteQueries(n, ids);POSTCALL;} void wrapglDeleteRenderbuffers(GLsizei n, const GLuint *renderbuffers) {PRECALL;glDeleteRenderbuffers(n, renderbuffers);POSTCALL;} void wrapglDeleteTextures(GLsizei n, const GLuint *textures) {PRECALL;glDeleteTextures(n, textures);POSTCALL;} void wrapglDepthFunc(GLenum func) {PRECALL;glDepthFunc(func);POSTCALL;} void wrapglDepthMask(GLboolean flag) {PRECALL;glDepthMask(flag);POSTCALL;} //void wrapglDepthRange(GLclampd near_val, GLclampd far_val) {PRECALL;glDepthRangef((float)near_val, (float)far_val);POSTCALL;} void wrapglDepthRangef(GLclampf near_val, GLclampf far_val) {PRECALL;glDepthRangef(near_val, far_val);POSTCALL;} void wrapglDetachShader(GLuint containerObj, GLuint attachedObj) {PRECALL;glDetachShader(containerObj, attachedObj);POSTCALL;} void wrapglDisable(GLenum cap) {PRECALL;glDisable(cap);POSTCALL;} void wrapglDisableClientState(GLenum cap) {PRECALL;Con_Printf("glDisableClientState(cap)\n");POSTCALL;} void wrapglDisableVertexAttribArray(GLuint index) {PRECALL;glDisableVertexAttribArray(index);POSTCALL;} void wrapglDrawArrays(GLenum mode, GLint first, GLsizei count) {PRECALL;glDrawArrays(mode, first, count);POSTCALL;} void wrapglDrawBuffer(GLenum mode) {PRECALL;Con_Printf("glDrawBuffer(mode)\n");POSTCALL;} void wrapglDrawBuffers(GLsizei n, const GLenum *bufs) {PRECALL;Con_Printf("glDrawBuffers(n, bufs)\n");POSTCALL;} void wrapglDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices) {PRECALL;glDrawElements(mode, count, type, indices);POSTCALL;} //void wrapglDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) {PRECALL;glDrawRangeElements(mode, start, end, count, type, indices);POSTCALL;} //void wrapglDrawRangeElementsEXT(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices) {PRECALL;glDrawRangeElements(mode, start, end, count, type, indices);POSTCALL;} void wrapglEnable(GLenum cap) {PRECALL;glEnable(cap);POSTCALL;} void wrapglEnableClientState(GLenum cap) {PRECALL;Con_Printf("glEnableClientState(cap)\n");POSTCALL;} void wrapglEnableVertexAttribArray(GLuint index) {PRECALL;glEnableVertexAttribArray(index);POSTCALL;} //void wrapglEnd(void) {PRECALL;Con_Printf("glEnd()\n");POSTCALL;} //void wrapglEndQuery(GLenum target) {PRECALL;glEndQuery(target);POSTCALL;} void wrapglFinish(void) {PRECALL;glFinish();POSTCALL;} void wrapglFlush(void) {PRECALL;glFlush();POSTCALL;} void wrapglFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) {PRECALL;glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);POSTCALL;} void wrapglFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) {PRECALL;glFramebufferTexture2D(target, attachment, textarget, texture, level);POSTCALL;} void wrapglFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset) {PRECALL;Con_Printf("glFramebufferTexture3D()\n");POSTCALL;} void wrapglGenBuffers(GLsizei n, GLuint *buffers) {PRECALL;glGenBuffers(n, buffers);POSTCALL;} void wrapglGenFramebuffers(GLsizei n, GLuint *framebuffers) {PRECALL;glGenFramebuffers(n, framebuffers);POSTCALL;} //void wrapglGenQueries(GLsizei n, GLuint *ids) {PRECALL;glGenQueries(n, ids);POSTCALL;} void wrapglGenRenderbuffers(GLsizei n, GLuint *renderbuffers) {PRECALL;glGenRenderbuffers(n, renderbuffers);POSTCALL;} void wrapglGenTextures(GLsizei n, GLuint *textures) {PRECALL;glGenTextures(n, textures);POSTCALL;} void wrapglGenerateMipmap(GLenum target) {PRECALL;glGenerateMipmap(target);POSTCALL;} void wrapglGetActiveAttrib(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name) {PRECALL;glGetActiveAttrib(programObj, index, maxLength, length, size, type, name);POSTCALL;} void wrapglGetActiveUniform(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name) {PRECALL;glGetActiveUniform(programObj, index, maxLength, length, size, type, name);POSTCALL;} void wrapglGetAttachedShaders(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj) {PRECALL;glGetAttachedShaders(containerObj, maxCount, count, obj);POSTCALL;} void wrapglGetBooleanv(GLenum pname, GLboolean *params) {PRECALL;glGetBooleanv(pname, params);POSTCALL;} void wrapglGetCompressedTexImage(GLenum target, GLint lod, void *img) {PRECALL;Con_Printf("glGetCompressedTexImage(target, lod, img)\n");POSTCALL;} void wrapglGetDoublev(GLenum pname, GLdouble *params) {PRECALL;Con_Printf("glGetDoublev(pname, params)\n");POSTCALL;} void wrapglGetFloatv(GLenum pname, GLfloat *params) {PRECALL;glGetFloatv(pname, params);POSTCALL;} void wrapglGetFramebufferAttachmentParameteriv(GLenum target, GLenum attachment, GLenum pname, GLint *params) {PRECALL;glGetFramebufferAttachmentParameteriv(target, attachment, pname, params);POSTCALL;} void wrapglGetShaderInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog) {PRECALL;glGetShaderInfoLog(obj, maxLength, length, infoLog);POSTCALL;} void wrapglGetProgramInfoLog(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog) {PRECALL;glGetProgramInfoLog(obj, maxLength, length, infoLog);POSTCALL;} void wrapglGetIntegerv(GLenum pname, GLint *params) {PRECALL;glGetIntegerv(pname, params);POSTCALL;} void wrapglGetShaderiv(GLuint obj, GLenum pname, GLint *params) {PRECALL;glGetShaderiv(obj, pname, params);POSTCALL;} void wrapglGetProgramiv(GLuint obj, GLenum pname, GLint *params) {PRECALL;glGetProgramiv(obj, pname, params);POSTCALL;} //void wrapglGetQueryObjectiv(GLuint qid, GLenum pname, GLint *params) {PRECALL;glGetQueryObjectiv(qid, pname, params);POSTCALL;} //void wrapglGetQueryObjectuiv(GLuint qid, GLenum pname, GLuint *params) {PRECALL;glGetQueryObjectuiv(qid, pname, params);POSTCALL;} //void wrapglGetQueryiv(GLenum target, GLenum pname, GLint *params) {PRECALL;glGetQueryiv(target, pname, params);POSTCALL;} void wrapglGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint *params) {PRECALL;glGetRenderbufferParameteriv(target, pname, params);POSTCALL;} void wrapglGetShaderSource(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source) {PRECALL;glGetShaderSource(obj, maxLength, length, source);POSTCALL;} void wrapglGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels) {PRECALL;Con_Printf("glGetTexImage(target, level, format, type, pixels)\n");POSTCALL;} void wrapglGetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat *params) {PRECALL;Con_Printf("glGetTexLevelParameterfv(target, level, pname, params)\n");POSTCALL;} void wrapglGetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint *params) {PRECALL;Con_Printf("glGetTexLevelParameteriv(target, level, pname, params)\n");POSTCALL;} void wrapglGetTexParameterfv(GLenum target, GLenum pname, GLfloat *params) {PRECALL;glGetTexParameterfv(target, pname, params);POSTCALL;} void wrapglGetTexParameteriv(GLenum target, GLenum pname, GLint *params) {PRECALL;glGetTexParameteriv(target, pname, params);POSTCALL;} void wrapglGetUniformfv(GLuint programObj, GLint location, GLfloat *params) {PRECALL;glGetUniformfv(programObj, location, params);POSTCALL;} void wrapglGetUniformiv(GLuint programObj, GLint location, GLint *params) {PRECALL;glGetUniformiv(programObj, location, params);POSTCALL;} void wrapglHint(GLenum target, GLenum mode) {PRECALL;glHint(target, mode);POSTCALL;} void wrapglLineWidth(GLfloat width) {PRECALL;glLineWidth(width);POSTCALL;} void wrapglLinkProgram(GLuint programObj) {PRECALL;glLinkProgram(programObj);POSTCALL;} void wrapglLoadIdentity(void) {PRECALL;Con_Printf("glLoadIdentity()\n");POSTCALL;} void wrapglLoadMatrixf(const GLfloat *m) {PRECALL;Con_Printf("glLoadMatrixf(m)\n");POSTCALL;} void wrapglMatrixMode(GLenum mode) {PRECALL;Con_Printf("glMatrixMode(mode)\n");POSTCALL;} void wrapglMultiTexCoord1f(GLenum target, GLfloat s) {PRECALL;Con_Printf("glMultiTexCoord1f(target, s)\n");POSTCALL;} void wrapglMultiTexCoord2f(GLenum target, GLfloat s, GLfloat t) {PRECALL;Con_Printf("glMultiTexCoord2f(target, s, t)\n");POSTCALL;} void wrapglMultiTexCoord3f(GLenum target, GLfloat s, GLfloat t, GLfloat r) {PRECALL;Con_Printf("glMultiTexCoord3f(target, s, t, r)\n");POSTCALL;} void wrapglMultiTexCoord4f(GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q) {PRECALL;Con_Printf("glMultiTexCoord4f(target, s, t, r, q)\n");POSTCALL;} void wrapglNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr) {PRECALL;Con_Printf("glNormalPointer(type, stride, ptr)\n");POSTCALL;} void wrapglPixelStorei(GLenum pname, GLint param) {PRECALL;glPixelStorei(pname, param);POSTCALL;} void wrapglPointSize(GLfloat size) {PRECALL;Con_Printf("glPointSize(size)\n");POSTCALL;} //void wrapglPolygonMode(GLenum face, GLenum mode) {PRECALL;Con_Printf("glPolygonMode(face, mode)\n");POSTCALL;} void wrapglPolygonOffset(GLfloat factor, GLfloat units) {PRECALL;glPolygonOffset(factor, units);POSTCALL;} void wrapglPolygonStipple(const GLubyte *mask) {PRECALL;Con_Printf("glPolygonStipple(mask)\n");POSTCALL;} void wrapglReadBuffer(GLenum mode) {PRECALL;Con_Printf("glReadBuffer(mode)\n");POSTCALL;} void wrapglReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels) {PRECALL;glReadPixels(x, y, width, height, format, type, pixels);POSTCALL;} void wrapglRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) {PRECALL;glRenderbufferStorage(target, internalformat, width, height);POSTCALL;} void wrapglScissor(GLint x, GLint y, GLsizei width, GLsizei height) {PRECALL;glScissor(x, y, width, height);POSTCALL;} void wrapglShaderSource(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length) {PRECALL;glShaderSource(shaderObj, count, string, length);POSTCALL;} void wrapglStencilFunc(GLenum func, GLint ref, GLuint mask) {PRECALL;glStencilFunc(func, ref, mask);POSTCALL;} void wrapglStencilFuncSeparate(GLenum func1, GLenum func2, GLint ref, GLuint mask) {PRECALL;Con_Printf("glStencilFuncSeparate(func1, func2, ref, mask)\n");POSTCALL;} void wrapglStencilMask(GLuint mask) {PRECALL;glStencilMask(mask);POSTCALL;} void wrapglStencilOp(GLenum fail, GLenum zfail, GLenum zpass) {PRECALL;glStencilOp(fail, zfail, zpass);POSTCALL;} void wrapglStencilOpSeparate(GLenum e1, GLenum e2, GLenum e3, GLenum e4) {PRECALL;Con_Printf("glStencilOpSeparate(e1, e2, e3, e4)\n");POSTCALL;} void wrapglTexCoord1f(GLfloat s) {PRECALL;Con_Printf("glTexCoord1f(s)\n");POSTCALL;} void wrapglTexCoord2f(GLfloat s, GLfloat t) {PRECALL;Con_Printf("glTexCoord2f(s, t)\n");POSTCALL;} void wrapglTexCoord3f(GLfloat s, GLfloat t, GLfloat r) {PRECALL;Con_Printf("glTexCoord3f(s, t, r)\n");POSTCALL;} void wrapglTexCoord4f(GLfloat s, GLfloat t, GLfloat r, GLfloat q) {PRECALL;Con_Printf("glTexCoord4f(s, t, r, q)\n");POSTCALL;} void wrapglTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {PRECALL;Con_Printf("glTexCoordPointer(size, type, stride, ptr)\n");POSTCALL;} void wrapglTexEnvf(GLenum target, GLenum pname, GLfloat param) {PRECALL;Con_Printf("glTexEnvf(target, pname, param)\n");POSTCALL;} void wrapglTexEnvfv(GLenum target, GLenum pname, const GLfloat *params) {PRECALL;Con_Printf("glTexEnvfv(target, pname, params)\n");POSTCALL;} void wrapglTexEnvi(GLenum target, GLenum pname, GLint param) {PRECALL;Con_Printf("glTexEnvi(target, pname, param)\n");POSTCALL;} void wrapglTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) {PRECALL;glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);POSTCALL;} void wrapglTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels) {PRECALL;Con_Printf("glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels)\n");POSTCALL;} void wrapglTexParameterf(GLenum target, GLenum pname, GLfloat param) {PRECALL;glTexParameterf(target, pname, param);POSTCALL;} void wrapglTexParameterfv(GLenum target, GLenum pname, GLfloat *params) {PRECALL;glTexParameterfv(target, pname, params);POSTCALL;} void wrapglTexParameteri(GLenum target, GLenum pname, GLint param) {PRECALL;glTexParameteri(target, pname, param);POSTCALL;} void wrapglTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels) {PRECALL;glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);POSTCALL;} void wrapglTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels) {PRECALL;Con_Printf("glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels)\n");POSTCALL;} void wrapglUniform1f(GLint location, GLfloat v0) {PRECALL;glUniform1f(location, v0);POSTCALL;} void wrapglUniform1fv(GLint location, GLsizei count, const GLfloat *value) {PRECALL;glUniform1fv(location, count, value);POSTCALL;} void wrapglUniform1i(GLint location, GLint v0) {PRECALL;glUniform1i(location, v0);POSTCALL;} void wrapglUniform1iv(GLint location, GLsizei count, const GLint *value) {PRECALL;glUniform1iv(location, count, value);POSTCALL;} void wrapglUniform2f(GLint location, GLfloat v0, GLfloat v1) {PRECALL;glUniform2f(location, v0, v1);POSTCALL;} void wrapglUniform2fv(GLint location, GLsizei count, const GLfloat *value) {PRECALL;glUniform2fv(location, count, value);POSTCALL;} void wrapglUniform2i(GLint location, GLint v0, GLint v1) {PRECALL;glUniform2i(location, v0, v1);POSTCALL;} void wrapglUniform2iv(GLint location, GLsizei count, const GLint *value) {PRECALL;glUniform2iv(location, count, value);POSTCALL;} void wrapglUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2) {PRECALL;glUniform3f(location, v0, v1, v2);POSTCALL;} void wrapglUniform3fv(GLint location, GLsizei count, const GLfloat *value) {PRECALL;glUniform3fv(location, count, value);POSTCALL;} void wrapglUniform3i(GLint location, GLint v0, GLint v1, GLint v2) {PRECALL;glUniform3i(location, v0, v1, v2);POSTCALL;} void wrapglUniform3iv(GLint location, GLsizei count, const GLint *value) {PRECALL;glUniform3iv(location, count, value);POSTCALL;} void wrapglUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {PRECALL;glUniform4f(location, v0, v1, v2, v3);POSTCALL;} void wrapglUniform4fv(GLint location, GLsizei count, const GLfloat *value) {PRECALL;glUniform4fv(location, count, value);POSTCALL;} void wrapglUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3) {PRECALL;glUniform4i(location, v0, v1, v2, v3);POSTCALL;} void wrapglUniform4iv(GLint location, GLsizei count, const GLint *value) {PRECALL;glUniform4iv(location, count, value);POSTCALL;} void wrapglUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {PRECALL;glUniformMatrix2fv(location, count, transpose, value);POSTCALL;} void wrapglUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {PRECALL;glUniformMatrix3fv(location, count, transpose, value);POSTCALL;} void wrapglUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value) {PRECALL;glUniformMatrix4fv(location, count, transpose, value);POSTCALL;} void wrapglUseProgram(GLuint programObj) {PRECALL;glUseProgram(programObj);POSTCALL;} void wrapglValidateProgram(GLuint programObj) {PRECALL;glValidateProgram(programObj);POSTCALL;} void wrapglVertex2f(GLfloat x, GLfloat y) {PRECALL;Con_Printf("glVertex2f(x, y)\n");POSTCALL;} void wrapglVertex3f(GLfloat x, GLfloat y, GLfloat z) {PRECALL;Con_Printf("glVertex3f(x, y, z)\n");POSTCALL;} void wrapglVertex4f(GLfloat x, GLfloat y, GLfloat z, GLfloat w) {PRECALL;Con_Printf("glVertex4f(x, y, z, w)\n");POSTCALL;} void wrapglVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer) {PRECALL;glVertexAttribPointer(index, size, type, normalized, stride, pointer);POSTCALL;} void wrapglVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) {PRECALL;Con_Printf("glVertexPointer(size, type, stride, ptr)\n");POSTCALL;} void wrapglViewport(GLint x, GLint y, GLsizei width, GLsizei height) {PRECALL;glViewport(x, y, width, height);POSTCALL;} void wrapglVertexAttrib1f(GLuint index, GLfloat v0) {PRECALL;glVertexAttrib1f(index, v0);POSTCALL;} //void wrapglVertexAttrib1s(GLuint index, GLshort v0) {PRECALL;glVertexAttrib1s(index, v0);POSTCALL;} //void wrapglVertexAttrib1d(GLuint index, GLdouble v0) {PRECALL;glVertexAttrib1d(index, v0);POSTCALL;} void wrapglVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1) {PRECALL;glVertexAttrib2f(index, v0, v1);POSTCALL;} //void wrapglVertexAttrib2s(GLuint index, GLshort v0, GLshort v1) {PRECALL;glVertexAttrib2s(index, v0, v1);POSTCALL;} //void wrapglVertexAttrib2d(GLuint index, GLdouble v0, GLdouble v1) {PRECALL;glVertexAttrib2d(index, v0, v1);POSTCALL;} void wrapglVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2) {PRECALL;glVertexAttrib3f(index, v0, v1, v2);POSTCALL;} //void wrapglVertexAttrib3s(GLuint index, GLshort v0, GLshort v1, GLshort v2) {PRECALL;glVertexAttrib3s(index, v0, v1, v2);POSTCALL;} //void wrapglVertexAttrib3d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2) {PRECALL;glVertexAttrib3d(index, v0, v1, v2);POSTCALL;} void wrapglVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {PRECALL;glVertexAttrib4f(index, v0, v1, v2, v3);POSTCALL;} //void wrapglVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3) {PRECALL;glVertexAttrib4s(index, v0, v1, v2, v3);POSTCALL;} //void wrapglVertexAttrib4d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {PRECALL;glVertexAttrib4d(index, v0, v1, v2, v3);POSTCALL;} //void wrapglVertexAttrib4Nub(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w) {PRECALL;glVertexAttrib4Nub(index, x, y, z, w);POSTCALL;} void wrapglVertexAttrib1fv(GLuint index, const GLfloat *v) {PRECALL;glVertexAttrib1fv(index, v);POSTCALL;} //void wrapglVertexAttrib1sv(GLuint index, const GLshort *v) {PRECALL;glVertexAttrib1sv(index, v);POSTCALL;} //void wrapglVertexAttrib1dv(GLuint index, const GLdouble *v) {PRECALL;glVertexAttrib1dv(index, v);POSTCALL;} void wrapglVertexAttrib2fv(GLuint index, const GLfloat *v) {PRECALL;glVertexAttrib2fv(index, v);POSTCALL;} //void wrapglVertexAttrib2sv(GLuint index, const GLshort *v) {PRECALL;glVertexAttrib2sv(index, v);POSTCALL;} //void wrapglVertexAttrib2dv(GLuint index, const GLdouble *v) {PRECALL;glVertexAttrib2dv(index, v);POSTCALL;} void wrapglVertexAttrib3fv(GLuint index, const GLfloat *v) {PRECALL;glVertexAttrib3fv(index, v);POSTCALL;} //void wrapglVertexAttrib3sv(GLuint index, const GLshort *v) {PRECALL;glVertexAttrib3sv(index, v);POSTCALL;} //void wrapglVertexAttrib3dv(GLuint index, const GLdouble *v) {PRECALL;glVertexAttrib3dv(index, v);POSTCALL;} void wrapglVertexAttrib4fv(GLuint index, const GLfloat *v) {PRECALL;glVertexAttrib4fv(index, v);POSTCALL;} //void wrapglVertexAttrib4sv(GLuint index, const GLshort *v) {PRECALL;glVertexAttrib4sv(index, v);POSTCALL;} //void wrapglVertexAttrib4dv(GLuint index, const GLdouble *v) {PRECALL;glVertexAttrib4dv(index, v);POSTCALL;} //void wrapglVertexAttrib4iv(GLuint index, const GLint *v) {PRECALL;glVertexAttrib4iv(index, v);POSTCALL;} //void wrapglVertexAttrib4bv(GLuint index, const GLbyte *v) {PRECALL;glVertexAttrib4bv(index, v);POSTCALL;} //void wrapglVertexAttrib4ubv(GLuint index, const GLubyte *v) {PRECALL;glVertexAttrib4ubv(index, v);POSTCALL;} //void wrapglVertexAttrib4usv(GLuint index, const GLushort *v) {PRECALL;glVertexAttrib4usv(index, GLushort v);POSTCALL;} //void wrapglVertexAttrib4uiv(GLuint index, const GLuint *v) {PRECALL;glVertexAttrib4uiv(index, v);POSTCALL;} //void wrapglVertexAttrib4Nbv(GLuint index, const GLbyte *v) {PRECALL;glVertexAttrib4Nbv(index, v);POSTCALL;} //void wrapglVertexAttrib4Nsv(GLuint index, const GLshort *v) {PRECALL;glVertexAttrib4Nsv(index, v);POSTCALL;} //void wrapglVertexAttrib4Niv(GLuint index, const GLint *v) {PRECALL;glVertexAttrib4Niv(index, v);POSTCALL;} //void wrapglVertexAttrib4Nubv(GLuint index, const GLubyte *v) {PRECALL;glVertexAttrib4Nubv(index, v);POSTCALL;} //void wrapglVertexAttrib4Nusv(GLuint index, const GLushort *v) {PRECALL;glVertexAttrib4Nusv(index, GLushort v);POSTCALL;} //void wrapglVertexAttrib4Nuiv(GLuint index, const GLuint *v) {PRECALL;glVertexAttrib4Nuiv(index, v);POSTCALL;} //void wrapglGetVertexAttribdv(GLuint index, GLenum pname, GLdouble *params) {PRECALL;glGetVertexAttribdv(index, pname, params);POSTCALL;} void wrapglGetVertexAttribfv(GLuint index, GLenum pname, GLfloat *params) {PRECALL;glGetVertexAttribfv(index, pname, params);POSTCALL;} void wrapglGetVertexAttribiv(GLuint index, GLenum pname, GLint *params) {PRECALL;glGetVertexAttribiv(index, pname, params);POSTCALL;} void wrapglGetVertexAttribPointerv(GLuint index, GLenum pname, GLvoid **pointer) {PRECALL;glGetVertexAttribPointerv(index, pname, pointer);POSTCALL;} #endif #if SDL_MAJOR_VERSION == 1 #define SDL_GL_ExtensionSupported(x) (strstr(gl_extensions, x) || strstr(gl_platformextensions, x)) #endif void GLES_Init(void) { #ifndef qglClear qglIsBufferARB = wrapglIsBuffer; qglIsEnabled = wrapglIsEnabled; qglIsFramebufferEXT = wrapglIsFramebuffer; // qglIsQueryARB = wrapglIsQuery; qglIsRenderbufferEXT = wrapglIsRenderbuffer; // qglUnmapBufferARB = wrapglUnmapBuffer; qglCheckFramebufferStatus = wrapglCheckFramebufferStatus; qglGetError = wrapglGetError; qglCreateProgram = wrapglCreateProgram; qglCreateShader = wrapglCreateShader; // qglGetHandleARB = wrapglGetHandle; qglGetAttribLocation = wrapglGetAttribLocation; qglGetUniformLocation = wrapglGetUniformLocation; // qglMapBufferARB = wrapglMapBuffer; qglGetString = wrapglGetString; // qglActiveStencilFaceEXT = wrapglActiveStencilFace; qglActiveTexture = wrapglActiveTexture; qglAlphaFunc = wrapglAlphaFunc; qglArrayElement = wrapglArrayElement; qglAttachShader = wrapglAttachShader; // qglBegin = wrapglBegin; // qglBeginQueryARB = wrapglBeginQuery; qglBindAttribLocation = wrapglBindAttribLocation; // qglBindFragDataLocation = wrapglBindFragDataLocation; qglBindBufferARB = wrapglBindBuffer; qglBindFramebuffer = wrapglBindFramebuffer; qglBindRenderbuffer = wrapglBindRenderbuffer; qglBindTexture = wrapglBindTexture; qglBlendEquationEXT = wrapglBlendEquation; qglBlendFunc = wrapglBlendFunc; qglBufferDataARB = wrapglBufferData; qglBufferSubDataARB = wrapglBufferSubData; qglClear = wrapglClear; qglClearColor = wrapglClearColor; qglClearDepth = wrapglClearDepth; qglClearStencil = wrapglClearStencil; qglClientActiveTexture = wrapglClientActiveTexture; qglColor4f = wrapglColor4f; qglColor4ub = wrapglColor4ub; qglColorMask = wrapglColorMask; qglColorPointer = wrapglColorPointer; qglCompileShader = wrapglCompileShader; qglCompressedTexImage2DARB = wrapglCompressedTexImage2D; qglCompressedTexImage3DARB = wrapglCompressedTexImage3D; qglCompressedTexSubImage2DARB = wrapglCompressedTexSubImage2D; qglCompressedTexSubImage3DARB = wrapglCompressedTexSubImage3D; qglCopyTexImage2D = wrapglCopyTexImage2D; qglCopyTexSubImage2D = wrapglCopyTexSubImage2D; qglCopyTexSubImage3D = wrapglCopyTexSubImage3D; qglCullFace = wrapglCullFace; qglDeleteBuffersARB = wrapglDeleteBuffers; qglDeleteFramebuffers = wrapglDeleteFramebuffers; qglDeleteProgram = wrapglDeleteProgram; qglDeleteShader = wrapglDeleteShader; // qglDeleteQueriesARB = wrapglDeleteQueries; qglDeleteRenderbuffers = wrapglDeleteRenderbuffers; qglDeleteTextures = wrapglDeleteTextures; qglDepthFunc = wrapglDepthFunc; qglDepthMask = wrapglDepthMask; qglDepthRangef = wrapglDepthRangef; qglDetachShader = wrapglDetachShader; qglDisable = wrapglDisable; qglDisableClientState = wrapglDisableClientState; qglDisableVertexAttribArray = wrapglDisableVertexAttribArray; qglDrawArrays = wrapglDrawArrays; // qglDrawBuffer = wrapglDrawBuffer; // qglDrawBuffersARB = wrapglDrawBuffers; qglDrawElements = wrapglDrawElements; // qglDrawRangeElements = wrapglDrawRangeElements; qglEnable = wrapglEnable; qglEnableClientState = wrapglEnableClientState; qglEnableVertexAttribArray = wrapglEnableVertexAttribArray; // qglEnd = wrapglEnd; // qglEndQueryARB = wrapglEndQuery; qglFinish = wrapglFinish; qglFlush = wrapglFlush; qglFramebufferRenderbufferEXT = wrapglFramebufferRenderbuffer; qglFramebufferTexture2DEXT = wrapglFramebufferTexture2D; qglFramebufferTexture3DEXT = wrapglFramebufferTexture3D; qglGenBuffersARB = wrapglGenBuffers; qglGenFramebuffers = wrapglGenFramebuffers; // qglGenQueriesARB = wrapglGenQueries; qglGenRenderbuffers = wrapglGenRenderbuffers; qglGenTextures = wrapglGenTextures; qglGenerateMipmapEXT = wrapglGenerateMipmap; qglGetActiveAttrib = wrapglGetActiveAttrib; qglGetActiveUniform = wrapglGetActiveUniform; qglGetAttachedShaders = wrapglGetAttachedShaders; qglGetBooleanv = wrapglGetBooleanv; // qglGetCompressedTexImageARB = wrapglGetCompressedTexImage; qglGetDoublev = wrapglGetDoublev; qglGetFloatv = wrapglGetFloatv; qglGetFramebufferAttachmentParameterivEXT = wrapglGetFramebufferAttachmentParameteriv; qglGetProgramInfoLog = wrapglGetProgramInfoLog; qglGetShaderInfoLog = wrapglGetShaderInfoLog; qglGetIntegerv = wrapglGetIntegerv; qglGetShaderiv = wrapglGetShaderiv; qglGetProgramiv = wrapglGetProgramiv; // qglGetQueryObjectivARB = wrapglGetQueryObjectiv; // qglGetQueryObjectuivARB = wrapglGetQueryObjectuiv; // qglGetQueryivARB = wrapglGetQueryiv; qglGetRenderbufferParameterivEXT = wrapglGetRenderbufferParameteriv; qglGetShaderSource = wrapglGetShaderSource; qglGetTexImage = wrapglGetTexImage; qglGetTexLevelParameterfv = wrapglGetTexLevelParameterfv; qglGetTexLevelParameteriv = wrapglGetTexLevelParameteriv; qglGetTexParameterfv = wrapglGetTexParameterfv; qglGetTexParameteriv = wrapglGetTexParameteriv; qglGetUniformfv = wrapglGetUniformfv; qglGetUniformiv = wrapglGetUniformiv; qglHint = wrapglHint; qglLineWidth = wrapglLineWidth; qglLinkProgram = wrapglLinkProgram; qglLoadIdentity = wrapglLoadIdentity; qglLoadMatrixf = wrapglLoadMatrixf; qglMatrixMode = wrapglMatrixMode; qglMultiTexCoord1f = wrapglMultiTexCoord1f; qglMultiTexCoord2f = wrapglMultiTexCoord2f; qglMultiTexCoord3f = wrapglMultiTexCoord3f; qglMultiTexCoord4f = wrapglMultiTexCoord4f; qglNormalPointer = wrapglNormalPointer; qglPixelStorei = wrapglPixelStorei; qglPointSize = wrapglPointSize; // qglPolygonMode = wrapglPolygonMode; qglPolygonOffset = wrapglPolygonOffset; // qglPolygonStipple = wrapglPolygonStipple; qglReadBuffer = wrapglReadBuffer; qglReadPixels = wrapglReadPixels; qglRenderbufferStorage = wrapglRenderbufferStorage; qglScissor = wrapglScissor; qglShaderSource = wrapglShaderSource; qglStencilFunc = wrapglStencilFunc; qglStencilFuncSeparate = wrapglStencilFuncSeparate; qglStencilMask = wrapglStencilMask; qglStencilOp = wrapglStencilOp; qglStencilOpSeparate = wrapglStencilOpSeparate; qglTexCoord1f = wrapglTexCoord1f; qglTexCoord2f = wrapglTexCoord2f; qglTexCoord3f = wrapglTexCoord3f; qglTexCoord4f = wrapglTexCoord4f; qglTexCoordPointer = wrapglTexCoordPointer; qglTexEnvf = wrapglTexEnvf; qglTexEnvfv = wrapglTexEnvfv; qglTexEnvi = wrapglTexEnvi; qglTexImage2D = wrapglTexImage2D; qglTexImage3D = wrapglTexImage3D; qglTexParameterf = wrapglTexParameterf; qglTexParameterfv = wrapglTexParameterfv; qglTexParameteri = wrapglTexParameteri; qglTexSubImage2D = wrapglTexSubImage2D; qglTexSubImage3D = wrapglTexSubImage3D; qglUniform1f = wrapglUniform1f; qglUniform1fv = wrapglUniform1fv; qglUniform1i = wrapglUniform1i; qglUniform1iv = wrapglUniform1iv; qglUniform2f = wrapglUniform2f; qglUniform2fv = wrapglUniform2fv; qglUniform2i = wrapglUniform2i; qglUniform2iv = wrapglUniform2iv; qglUniform3f = wrapglUniform3f; qglUniform3fv = wrapglUniform3fv; qglUniform3i = wrapglUniform3i; qglUniform3iv = wrapglUniform3iv; qglUniform4f = wrapglUniform4f; qglUniform4fv = wrapglUniform4fv; qglUniform4i = wrapglUniform4i; qglUniform4iv = wrapglUniform4iv; qglUniformMatrix2fv = wrapglUniformMatrix2fv; qglUniformMatrix3fv = wrapglUniformMatrix3fv; qglUniformMatrix4fv = wrapglUniformMatrix4fv; qglUseProgram = wrapglUseProgram; qglValidateProgram = wrapglValidateProgram; qglVertex2f = wrapglVertex2f; qglVertex3f = wrapglVertex3f; qglVertex4f = wrapglVertex4f; qglVertexAttribPointer = wrapglVertexAttribPointer; qglVertexPointer = wrapglVertexPointer; qglViewport = wrapglViewport; qglVertexAttrib1f = wrapglVertexAttrib1f; // qglVertexAttrib1s = wrapglVertexAttrib1s; // qglVertexAttrib1d = wrapglVertexAttrib1d; qglVertexAttrib2f = wrapglVertexAttrib2f; // qglVertexAttrib2s = wrapglVertexAttrib2s; // qglVertexAttrib2d = wrapglVertexAttrib2d; qglVertexAttrib3f = wrapglVertexAttrib3f; // qglVertexAttrib3s = wrapglVertexAttrib3s; // qglVertexAttrib3d = wrapglVertexAttrib3d; qglVertexAttrib4f = wrapglVertexAttrib4f; // qglVertexAttrib4s = wrapglVertexAttrib4s; // qglVertexAttrib4d = wrapglVertexAttrib4d; // qglVertexAttrib4Nub = wrapglVertexAttrib4Nub; qglVertexAttrib1fv = wrapglVertexAttrib1fv; // qglVertexAttrib1sv = wrapglVertexAttrib1sv; // qglVertexAttrib1dv = wrapglVertexAttrib1dv; qglVertexAttrib2fv = wrapglVertexAttrib2fv; // qglVertexAttrib2sv = wrapglVertexAttrib2sv; // qglVertexAttrib2dv = wrapglVertexAttrib2dv; qglVertexAttrib3fv = wrapglVertexAttrib3fv; // qglVertexAttrib3sv = wrapglVertexAttrib3sv; // qglVertexAttrib3dv = wrapglVertexAttrib3dv; qglVertexAttrib4fv = wrapglVertexAttrib4fv; // qglVertexAttrib4sv = wrapglVertexAttrib4sv; // qglVertexAttrib4dv = wrapglVertexAttrib4dv; // qglVertexAttrib4iv = wrapglVertexAttrib4iv; // qglVertexAttrib4bv = wrapglVertexAttrib4bv; // qglVertexAttrib4ubv = wrapglVertexAttrib4ubv; // qglVertexAttrib4usv = wrapglVertexAttrib4usv; // qglVertexAttrib4uiv = wrapglVertexAttrib4uiv; // qglVertexAttrib4Nbv = wrapglVertexAttrib4Nbv; // qglVertexAttrib4Nsv = wrapglVertexAttrib4Nsv; // qglVertexAttrib4Niv = wrapglVertexAttrib4Niv; // qglVertexAttrib4Nubv = wrapglVertexAttrib4Nubv; // qglVertexAttrib4Nusv = wrapglVertexAttrib4Nusv; // qglVertexAttrib4Nuiv = wrapglVertexAttrib4Nuiv; // qglGetVertexAttribdv = wrapglGetVertexAttribdv; qglGetVertexAttribfv = wrapglGetVertexAttribfv; qglGetVertexAttribiv = wrapglGetVertexAttribiv; qglGetVertexAttribPointerv = wrapglGetVertexAttribPointerv; #endif gl_renderer = (const char *)qglGetString(GL_RENDERER); gl_vendor = (const char *)qglGetString(GL_VENDOR); gl_version = (const char *)qglGetString(GL_VERSION); gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); if (!gl_extensions) gl_extensions = ""; if (!gl_platformextensions) gl_platformextensions = ""; Con_Printf("GL_VENDOR: %s\n", gl_vendor); Con_Printf("GL_RENDERER: %s\n", gl_renderer); Con_Printf("GL_VERSION: %s\n", gl_version); Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); // LordHavoc: report supported extensions Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); // GLES devices in general do not like GL_BGRA, so use GL_RGBA vid.forcetextype = TEXTYPE_RGBA; vid.support.gl20shaders = true; vid.support.amd_texture_texture4 = false; vid.support.arb_depth_texture = SDL_GL_ExtensionSupported("GL_OES_depth_texture") != 0; // renderbuffer used anyway on gles2? vid.support.arb_draw_buffers = false; vid.support.arb_multitexture = false; vid.support.arb_occlusion_query = false; vid.support.arb_query_buffer_object = false; vid.support.arb_shadow = false; vid.support.arb_texture_compression = false; // different (vendor-specific) formats than on desktop OpenGL... vid.support.arb_texture_cube_map = SDL_GL_ExtensionSupported("GL_OES_texture_cube_map") != 0; vid.support.arb_texture_env_combine = false; vid.support.arb_texture_gather = false; vid.support.arb_texture_non_power_of_two = strstr(gl_extensions, "GL_OES_texture_npot") != NULL; vid.support.arb_vertex_buffer_object = true; // GLES2 core vid.support.ati_separate_stencil = false; vid.support.ext_blend_minmax = false; vid.support.ext_blend_subtract = true; // GLES2 core vid.support.ext_blend_func_separate = true; // GLES2 core vid.support.ext_draw_range_elements = false; /* ELUAN: Note: "In OS 2.1, the functions in GL_OES_framebuffer_object were not usable from the Java API. Calling them just threw an exception. Android developer relations confirmed that they forgot to implement these. (yeah...) It's apparently been fixed in 2.2, though I haven't tested." */ vid.support.ext_framebuffer_object = false;//true; vid.support.ext_packed_depth_stencil = false; vid.support.ext_stencil_two_side = false; vid.support.ext_texture_3d = SDL_GL_ExtensionSupported("GL_OES_texture_3D") != 0; vid.support.ext_texture_compression_s3tc = SDL_GL_ExtensionSupported("GL_EXT_texture_compression_s3tc") != 0; vid.support.ext_texture_edge_clamp = true; // GLES2 core vid.support.ext_texture_filter_anisotropic = false; // probably don't want to use it... vid.support.ext_texture_srgb = false; vid.support.arb_texture_float = SDL_GL_ExtensionSupported("GL_OES_texture_float") != 0; vid.support.arb_half_float_pixel = SDL_GL_ExtensionSupported("GL_OES_texture_half_float") != 0; vid.support.arb_half_float_vertex = SDL_GL_ExtensionSupported("GL_OES_vertex_half_float") != 0; // NOTE: On some devices, a value of 512 gives better FPS than the maximum. qglGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_2d); #ifdef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT if (vid.support.ext_texture_filter_anisotropic) qglGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, (GLint*)&vid.max_anisotropy); #endif if (vid.support.arb_texture_cube_map) qglGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_cubemap); #ifdef GL_MAX_3D_TEXTURE_SIZE if (vid.support.ext_texture_3d) qglGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, (GLint*)&vid.maxtexturesize_3d); #endif Con_Printf("GL_MAX_CUBE_MAP_TEXTURE_SIZE = %i\n", vid.maxtexturesize_cubemap); Con_Printf("GL_MAX_3D_TEXTURE_SIZE = %i\n", vid.maxtexturesize_3d); { #define GL_ALPHA_BITS 0x0D55 #define GL_RED_BITS 0x0D52 #define GL_GREEN_BITS 0x0D53 #define GL_BLUE_BITS 0x0D54 #define GL_DEPTH_BITS 0x0D56 #define GL_STENCIL_BITS 0x0D57 int fb_r = -1, fb_g = -1, fb_b = -1, fb_a = -1, fb_d = -1, fb_s = -1; qglGetIntegerv(GL_RED_BITS , &fb_r); qglGetIntegerv(GL_GREEN_BITS , &fb_g); qglGetIntegerv(GL_BLUE_BITS , &fb_b); qglGetIntegerv(GL_ALPHA_BITS , &fb_a); qglGetIntegerv(GL_DEPTH_BITS , &fb_d); qglGetIntegerv(GL_STENCIL_BITS, &fb_s); Con_Printf("Framebuffer depth is R%iG%iB%iA%iD%iS%i\n", fb_r, fb_g, fb_b, fb_a, fb_d, fb_s); } // verify that cubemap textures are really supported if (vid.support.arb_texture_cube_map && vid.maxtexturesize_cubemap < 256) vid.support.arb_texture_cube_map = false; // verify that 3d textures are really supported if (vid.support.ext_texture_3d && vid.maxtexturesize_3d < 32) { vid.support.ext_texture_3d = false; Con_Printf("GL_OES_texture_3d reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n"); } vid.texunits = 4; vid.teximageunits = 8; vid.texarrayunits = 5; //qglGetIntegerv(GL_MAX_TEXTURE_UNITS, (GLint*)&vid.texunits); qglGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, (GLint*)&vid.teximageunits);CHECKGLERROR //qglGetIntegerv(GL_MAX_TEXTURE_COORDS, (GLint*)&vid.texarrayunits);CHECKGLERROR vid.texunits = bound(1, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = bound(1, vid.teximageunits, MAX_TEXTUREUNITS); vid.texarrayunits = bound(1, vid.texarrayunits, MAX_TEXTUREUNITS); Con_DPrintf("Using GLES2.0 rendering path - %i texture matrix, %i texture images, %i texcoords%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.support.ext_framebuffer_object ? ", shadowmapping supported" : ""); vid.renderpath = RENDERPATH_GLES2; vid.useinterleavedarrays = false; vid.sRGBcapable2D = false; vid.sRGBcapable3D = false; // VorteX: set other info (maybe place them in VID_InitMode?) extern cvar_t gl_info_vendor; extern cvar_t gl_info_renderer; extern cvar_t gl_info_version; extern cvar_t gl_info_platform; extern cvar_t gl_info_driver; Cvar_SetQuick(&gl_info_vendor, gl_vendor); Cvar_SetQuick(&gl_info_renderer, gl_renderer); Cvar_SetQuick(&gl_info_version, gl_version); Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); Cvar_SetQuick(&gl_info_driver, gl_driver); } #endif void *GL_GetProcAddress(const char *name) { void *p = NULL; p = SDL_GL_GetProcAddress(name); return p; } static qboolean vid_sdl_initjoysticksystem = false; void VID_Init (void) { #ifndef __IPHONEOS__ #ifdef MACOSX Cvar_RegisterVariable(&apple_mouse_noaccel); #endif #endif #ifdef DP_MOBILETOUCH Cvar_SetValueQuick(&vid_touchscreen, 1); #endif #ifdef SDL_R_RESTART R_RegisterModule("SDL", sdl_start, sdl_shutdown, sdl_newmap, NULL, NULL); #endif if (SDL_Init(SDL_INIT_VIDEO) < 0) Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError()); vid_sdl_initjoysticksystem = SDL_InitSubSystem(SDL_INIT_JOYSTICK) >= 0; if (vid_sdl_initjoysticksystem) Con_Printf("Failed to init SDL joystick subsystem: %s\n", SDL_GetError()); vid_isfullscreen = false; } static int vid_sdljoystickindex = -1; void VID_EnableJoystick(qboolean enable) { int index = joy_enable.integer > 0 ? joy_index.integer : -1; int numsdljoysticks; qboolean success = false; int sharedcount = 0; int sdlindex = -1; sharedcount = VID_Shared_SetJoystick(index); if (index >= 0 && index < sharedcount) success = true; sdlindex = index - sharedcount; numsdljoysticks = SDL_NumJoysticks(); if (sdlindex < 0 || sdlindex >= numsdljoysticks) sdlindex = -1; // update cvar containing count of XInput joysticks + SDL joysticks if (joy_detected.integer != sharedcount + numsdljoysticks) Cvar_SetValueQuick(&joy_detected, sharedcount + numsdljoysticks); if (vid_sdljoystickindex != sdlindex) { vid_sdljoystickindex = sdlindex; // close SDL joystick if active if (vid_sdljoystick) SDL_JoystickClose(vid_sdljoystick); vid_sdljoystick = NULL; if (sdlindex >= 0) { vid_sdljoystick = SDL_JoystickOpen(sdlindex); if (vid_sdljoystick) { #if SDL_MAJOR_VERSION == 1 const char *joystickname = SDL_JoystickName(sdlindex); #else const char *joystickname = SDL_JoystickName(vid_sdljoystick); #endif Con_Printf("Joystick %i opened (SDL_Joystick %i is \"%s\" with %i axes, %i buttons, %i balls)\n", index, sdlindex, joystickname, (int)SDL_JoystickNumAxes(vid_sdljoystick), (int)SDL_JoystickNumButtons(vid_sdljoystick), (int)SDL_JoystickNumBalls(vid_sdljoystick)); } else { Con_Printf("Joystick %i failed (SDL_JoystickOpen(%i) returned: %s)\n", index, sdlindex, SDL_GetError()); sdlindex = -1; } } } if (sdlindex >= 0) success = true; if (joy_active.integer != (success ? 1 : 0)) Cvar_SetValueQuick(&joy_active, success ? 1 : 0); } #if SDL_MAJOR_VERSION == 1 // set the icon (we dont use SDL here since it would be too much a PITA) #ifdef WIN32 #include "resource.h" #include static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) { SDL_Surface *screen = NULL; SDL_SysWMinfo info; HICON icon; SDL_WM_SetCaption( gamename, NULL ); screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); if (screen) { // get the HWND handle SDL_VERSION( &info.version ); if (SDL_GetWMInfo(&info)) { icon = LoadIcon( GetModuleHandle( NULL ), MAKEINTRESOURCE( IDI_ICON1 ) ); #ifndef _W64 //If Windows 64bit data types don't exist #ifndef SetClassLongPtr #define SetClassLongPtr SetClassLong #endif #ifndef GCLP_HICON #define GCLP_HICON GCL_HICON #endif #ifndef LONG_PTR #define LONG_PTR LONG #endif #endif SetClassLongPtr( info.window, GCLP_HICON, (LONG_PTR)icon ); } } return screen; } #elif defined(MACOSX) static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) { SDL_Surface *screen = NULL; SDL_WM_SetCaption( gamename, NULL ); screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); // we don't use SDL_WM_SetIcon here because the icon in the .app should be used return screen; } #else // Adding the OS independent XPM version --blub #include "darkplaces.xpm" #include "nexuiz.xpm" #if SDL_MAJOR_VERSION == 1 #if SDL_VIDEO_DRIVER_X11 && !SDL_VIDEO_DRIVER_QUARTZ #include #endif #endif static SDL_Surface *icon = NULL; static SDL_Surface *VID_WrapSDL_SetVideoMode(int screenwidth, int screenheight, int screenbpp, int screenflags) { /* * Somewhat restricted XPM reader. Only supports XPMs saved by GIMP 2.4 at * default settings with less than 91 colors and transparency. */ int width, height, colors, isize, i, j; int thenone = -1; static SDL_Color palette[256]; unsigned short palenc[256]; // store color id by char char *xpm; char **idata, *data; const SDL_version *version; SDL_Surface *screen = NULL; if (icon) SDL_FreeSurface(icon); icon = NULL; version = SDL_Linked_Version(); // only use non-XPM icon support in SDL v1.3 and higher // SDL v1.2 does not support "smooth" transparency, and thus is better // off the xpm way if(version->major >= 2 || (version->major == 1 && version->minor >= 3)) { data = (char *) loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); if(data) { unsigned int red = 0x00FF0000; unsigned int green = 0x0000FF00; unsigned int blue = 0x000000FF; unsigned int alpha = 0xFF000000; width = image_width; height = image_height; // reallocate with malloc, as this is in tempmempool (do not want) xpm = data; data = (char *) malloc(width * height * 4); memcpy(data, xpm, width * height * 4); Mem_Free(xpm); xpm = NULL; icon = SDL_CreateRGBSurface(SDL_SRCALPHA, width, height, 32, LittleLong(red), LittleLong(green), LittleLong(blue), LittleLong(alpha)); if (icon) icon->pixels = data; else { Con_Printf( "Failed to create surface for the window Icon!\n" "%s\n", SDL_GetError()); free(data); } } } // we only get here if non-XPM icon was missing, or SDL version is not // sufficient for transparent non-XPM icons if(!icon) { xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); idata = NULL; if(xpm) idata = XPM_DecodeString(xpm); if(!idata) idata = ENGINE_ICON; if(xpm) Mem_Free(xpm); data = idata[0]; if(sscanf(data, "%i %i %i %i", &width, &height, &colors, &isize) == 4) { if(isize == 1) { for(i = 0; i < colors; ++i) { unsigned int r, g, b; char idx; if(sscanf(idata[i+1], "%c c #%02x%02x%02x", &idx, &r, &g, &b) != 4) { char foo[2]; if(sscanf(idata[i+1], "%c c Non%1[e]", &idx, foo) != 2) // I take the DailyWTF credit for this. --div0 break; else { palette[i].r = 255; // color key palette[i].g = 0; palette[i].b = 255; thenone = i; // weeeee palenc[(unsigned char) idx] = i; } } else { palette[i].r = r - (r == 255 && g == 0 && b == 255); // change 255/0/255 pink to 254/0/255 for color key palette[i].g = g; palette[i].b = b; palenc[(unsigned char) idx] = i; } } if (i == colors) { // allocate the image data data = (char*) malloc(width*height); for(j = 0; j < height; ++j) { for(i = 0; i < width; ++i) { // casting to the safest possible datatypes ^^ data[j * width + i] = palenc[((unsigned char*)idata[colors+j+1])[i]]; } } if(icon != NULL) { // SDL_FreeSurface should free the data too // but for completeness' sake... if(icon->flags & SDL_PREALLOC) { free(icon->pixels); icon->pixels = NULL; // safety } SDL_FreeSurface(icon); } icon = SDL_CreateRGBSurface(SDL_SRCCOLORKEY, width, height, 8, 0,0,0,0);// rmask, gmask, bmask, amask); no mask needed // 8 bit surfaces get an empty palette allocated according to the docs // so it's a palette image for sure :) no endian check necessary for the mask if(icon) { icon->pixels = data; SDL_SetPalette(icon, SDL_PHYSPAL|SDL_LOGPAL, palette, 0, colors); SDL_SetColorKey(icon, SDL_SRCCOLORKEY, thenone); } else { Con_Printf( "Failed to create surface for the window Icon!\n" "%s\n", SDL_GetError()); free(data); } } else { Con_Printf("This XPM's palette looks odd. Can't continue.\n"); } } else { // NOTE: Only 1-char colornames are supported Con_Printf("This XPM's palette is either huge or idiotically unoptimized. It's key size is %i\n", isize); } } else { // NOTE: Only 1-char colornames are supported Con_Printf("Sorry, but this does not even look similar to an XPM.\n"); } } if (icon) SDL_WM_SetIcon(icon, NULL); SDL_WM_SetCaption( gamename, NULL ); screen = SDL_SetVideoMode(screenwidth, screenheight, screenbpp, screenflags); #if SDL_MAJOR_VERSION == 1 // LordHavoc: info.info.x11.lock_func and accompanying code do not seem to compile with SDL 1.3 #if SDL_VIDEO_DRIVER_X11 && !SDL_VIDEO_DRIVER_QUARTZ version = SDL_Linked_Version(); // only use non-XPM icon support in SDL v1.3 and higher // SDL v1.2 does not support "smooth" transparency, and thus is better // off the xpm way if(screen && (!(version->major >= 2 || (version->major == 1 && version->minor >= 3)))) { // in this case, we did not set the good icon yet SDL_SysWMinfo info; SDL_VERSION(&info.version); if(SDL_GetWMInfo(&info) == 1 && info.subsystem == SDL_SYSWM_X11) { data = (char *) loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); if(data) { // use _NET_WM_ICON too static long netwm_icon[MAX_NETWM_ICON]; int pos = 0; int i = 1; char vabuf[1024]; while(data) { if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) { netwm_icon[pos++] = image_width; netwm_icon[pos++] = image_height; for(i = 0; i < image_height; ++i) for(j = 0; j < image_width; ++j) netwm_icon[pos++] = BuffLittleLong((unsigned char *) &data[(i*image_width+j)*4]); } else { Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); } ++i; Mem_Free(data); data = (char *) loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "darkplaces-icon%d", i), false, false, false, NULL); } info.info.x11.lock_func(); { Atom net_wm_icon = XInternAtom(info.info.x11.display, "_NET_WM_ICON", false); XChangeProperty(info.info.x11.display, info.info.x11.wmwindow, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); } info.info.x11.unlock_func(); } } } #endif #endif return screen; } #endif #endif static void VID_OutputVersion(void) { SDL_version version; #if SDL_MAJOR_VERSION == 1 version = *SDL_Linked_Version(); #else SDL_GetVersion(&version); #endif Con_Printf( "Linked against SDL version %d.%d.%d\n" "Using SDL library version %d.%d.%d\n", SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL, version.major, version.minor, version.patch ); } #ifdef WIN32 static void AdjustWindowBounds(viddef_mode_t *mode, RECT *rect) { LONG width = mode->width; // vid_width LONG height = mode->height; // vid_height // adjust width and height for the space occupied by window decorators (title bar, borders) rect->top = 0; rect->left = 0; rect->right = width; rect->bottom = height; AdjustWindowRectEx(rect, WS_CAPTION|WS_THICKFRAME, false, 0); RECT workArea; SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0); int workWidth = workArea.right - workArea.left; int workHeight = workArea.bottom - workArea.top; // SDL forces the window height to be <= screen height - 27px (on Win8.1 - probably intended for the title bar) // If the task bar is docked to the the left screen border and we move the window to negative y, // there would be some part of the regular desktop visible on the bottom of the screen. int titleBarPixels = 2; int screenHeight = GetSystemMetrics(SM_CYSCREEN); if (screenHeight == workHeight) titleBarPixels = -rect->top; //Con_Printf("window mode: %dx%d, workArea: %d/%d-%d/%d (%dx%d), title: %d\n", width, height, workArea.left, workArea.top, workArea.right, workArea.bottom, workArea.right - workArea.left, workArea.bottom - workArea.top, titleBarPixels); // if height and width matches the physical or previously adjusted screen height and width, adjust it to available desktop area if ((width == GetSystemMetrics(SM_CXSCREEN) || width == workWidth) && (height == screenHeight || height == workHeight - titleBarPixels)) { rect->left = workArea.left; mode->width = workWidth; rect->top = workArea.top + titleBarPixels; mode->height = workHeight - titleBarPixels; } else { rect->left = workArea.left + max(0, (workWidth - width) / 2); rect->top = workArea.top + max(0, (workHeight - height) / 2); } } #endif static qboolean VID_InitModeGL(viddef_mode_t *mode) { #if SDL_MAJOR_VERSION == 1 static int notfirstvideomode = false; int flags = SDL_OPENGL; #else int windowflags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL; int xPos = SDL_WINDOWPOS_UNDEFINED; int yPos = SDL_WINDOWPOS_UNDEFINED; #endif #ifndef USE_GLES2 int i; const char *drivername; #endif win_half_width = mode->width>>1; win_half_height = mode->height>>1; if(vid_resizable.integer) #if SDL_MAJOR_VERSION == 1 flags |= SDL_RESIZABLE; #else windowflags |= SDL_WINDOW_RESIZABLE; #endif VID_OutputVersion(); #if SDL_MAJOR_VERSION == 1 /* SDL 1.2 Hack We cant switch from one OpenGL video mode to another. Thus we first switch to some stupid 2D mode and then back to OpenGL. */ if (notfirstvideomode) SDL_SetVideoMode( 0, 0, 0, 0 ); notfirstvideomode = true; #endif #ifndef USE_GLES2 // SDL usually knows best drivername = NULL; // COMMANDLINEOPTION: SDL GL: -gl_driver selects a GL driver library, default is whatever SDL recommends, useful only for 3dfxogl.dll/3dfxvgl.dll or fxmesa or similar, if you don't know what this is for, you don't need it i = COM_CheckParm("-gl_driver"); if (i && i < com_argc - 1) drivername = com_argv[i + 1]; if (SDL_GL_LoadLibrary(drivername) < 0) { Con_Printf("Unable to load GL driver \"%s\": %s\n", drivername, SDL_GetError()); return false; } #endif #ifdef DP_MOBILETOUCH // mobile platforms are always fullscreen, we'll get the resolution after opening the window mode->fullscreen = true; // hide the menu with SDL_WINDOW_BORDERLESS windowflags |= SDL_WINDOW_FULLSCREEN | SDL_WINDOW_BORDERLESS; #endif #ifndef USE_GLES2 if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) { VID_Shutdown(); Con_Print("Required OpenGL function glGetString not found\n"); return false; } #endif // Knghtbrd: should do platform-specific extension string function here vid_isfullscreen = false; #if SDL_MAJOR_VERSION == 1 { const SDL_VideoInfo *vi = SDL_GetVideoInfo(); desktop_mode.width = vi->current_w; desktop_mode.height = vi->current_h; desktop_mode.bpp = vi->vfmt->BitsPerPixel; desktop_mode.pixelheight_num = 1; desktop_mode.pixelheight_denom = 1; // SDL does not provide this if (mode->fullscreen) { if (vid_desktopfullscreen.integer) { mode->width = vi->current_w; mode->height = vi->current_h; mode->bitsperpixel = vi->vfmt->BitsPerPixel; } flags |= SDL_FULLSCREEN; vid_isfullscreen = true; } } #else { if (mode->fullscreen) { if (vid_desktopfullscreen.integer) { vid_mode_t *m = VID_GetDesktopMode(); mode->width = m->width; mode->height = m->height; windowflags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else windowflags |= SDL_WINDOW_FULLSCREEN; vid_isfullscreen = true; } else { #ifdef WIN32 RECT rect; AdjustWindowBounds(mode, &rect); xPos = rect.left; yPos = rect.top; #endif } } #endif //flags |= SDL_HWSURFACE; SDL_GL_SetAttribute (SDL_GL_DOUBLEBUFFER, 1); if (mode->bitsperpixel >= 32) { SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute (SDL_GL_ALPHA_SIZE, 8); SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute (SDL_GL_STENCIL_SIZE, 8); } else { SDL_GL_SetAttribute (SDL_GL_RED_SIZE, 5); SDL_GL_SetAttribute (SDL_GL_GREEN_SIZE, 5); SDL_GL_SetAttribute (SDL_GL_BLUE_SIZE, 5); SDL_GL_SetAttribute (SDL_GL_DEPTH_SIZE, 16); } if (mode->stereobuffer) SDL_GL_SetAttribute (SDL_GL_STEREO, 1); if (mode->samples > 1) { SDL_GL_SetAttribute (SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute (SDL_GL_MULTISAMPLESAMPLES, mode->samples); } #if SDL_MAJOR_VERSION == 1 if (vid_vsync.integer) SDL_GL_SetAttribute (SDL_GL_SWAP_CONTROL, 1); else SDL_GL_SetAttribute (SDL_GL_SWAP_CONTROL, 0); #else #ifdef USE_GLES2 SDL_GL_SetAttribute (SDL_GL_CONTEXT_MAJOR_VERSION, 2); SDL_GL_SetAttribute (SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute (SDL_GL_RETAINED_BACKING, 1); #endif #endif video_bpp = mode->bitsperpixel; #if SDL_MAJOR_VERSION == 1 video_flags = flags; video_screen = VID_WrapSDL_SetVideoMode(mode->width, mode->height, mode->bitsperpixel, flags); if (video_screen == NULL) { Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); VID_Shutdown(); return false; } mode->width = video_screen->w; mode->height = video_screen->h; #else window_flags = windowflags; window = SDL_CreateWindow(gamename, xPos, yPos, mode->width, mode->height, windowflags); if (window == NULL) { Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); VID_Shutdown(); return false; } SDL_GetWindowSize(window, &mode->width, &mode->height); context = SDL_GL_CreateContext(window); if (context == NULL) { Con_Printf("Failed to initialize OpenGL context: %s\n", SDL_GetError()); VID_Shutdown(); return false; } #endif vid_softsurface = NULL; vid.softpixels = NULL; #if SDL_MAJOR_VERSION == 1 // init keyboard SDL_EnableUNICODE( SDL_ENABLE ); // enable key repeat since everyone expects it SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); #endif #if SDL_MAJOR_VERSION != 1 SDL_GL_SetSwapInterval(vid_vsync.integer != 0); vid_usingvsync = (vid_vsync.integer != 0); #endif gl_platform = "SDL"; gl_platformextensions = ""; #ifdef USE_GLES2 GLES_Init(); #else GL_Init(); #endif vid_hidden = false; vid_activewindow = false; vid_hasfocus = true; vid_usingmouse = false; vid_usinghidecursor = false; #if SDL_MAJOR_VERSION == 1 SDL_WM_GrabInput(SDL_GRAB_OFF); #endif return true; } extern cvar_t gl_info_extensions; extern cvar_t gl_info_vendor; extern cvar_t gl_info_renderer; extern cvar_t gl_info_version; extern cvar_t gl_info_platform; extern cvar_t gl_info_driver; static qboolean VID_InitModeSoft(viddef_mode_t *mode) { #if SDL_MAJOR_VERSION == 1 int flags = SDL_HWSURFACE; if(!COM_CheckParm("-noasyncblit")) flags |= SDL_ASYNCBLIT; #else int windowflags = SDL_WINDOW_SHOWN; #endif win_half_width = mode->width>>1; win_half_height = mode->height>>1; if(vid_resizable.integer) #if SDL_MAJOR_VERSION == 1 flags |= SDL_RESIZABLE; #else windowflags |= SDL_WINDOW_RESIZABLE; #endif VID_OutputVersion(); vid_isfullscreen = false; if (mode->fullscreen) { #if SDL_MAJOR_VERSION == 1 const SDL_VideoInfo *vi = SDL_GetVideoInfo(); mode->width = vi->current_w; mode->height = vi->current_h; mode->bitsperpixel = vi->vfmt->BitsPerPixel; flags |= SDL_FULLSCREEN; #else if (vid_desktopfullscreen.integer) windowflags |= SDL_WINDOW_FULLSCREEN_DESKTOP; else windowflags |= SDL_WINDOW_FULLSCREEN; #endif vid_isfullscreen = true; } video_bpp = mode->bitsperpixel; #if SDL_MAJOR_VERSION == 1 video_flags = flags; video_screen = VID_WrapSDL_SetVideoMode(mode->width, mode->height, mode->bitsperpixel, flags); if (video_screen == NULL) { Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); VID_Shutdown(); return false; } mode->width = video_screen->w; mode->height = video_screen->h; #else window_flags = windowflags; window = SDL_CreateWindow(gamename, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, mode->width, mode->height, windowflags); if (window == NULL) { Con_Printf("Failed to set video mode to %ix%i: %s\n", mode->width, mode->height, SDL_GetError()); VID_Shutdown(); return false; } SDL_GetWindowSize(window, &mode->width, &mode->height); #endif // create a framebuffer using our specific color format, we let the SDL blit function convert it in VID_Finish vid_softsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, mode->width, mode->height, 32, 0x00FF0000, 0x0000FF00, 0x00000000FF, 0xFF000000); if (vid_softsurface == NULL) { Con_Printf("Failed to setup software rasterizer framebuffer %ix%ix32bpp: %s\n", mode->width, mode->height, SDL_GetError()); VID_Shutdown(); return false; } #if SDL_MAJOR_VERSION == 1 SDL_SetAlpha(vid_softsurface, 0, 255); #else SDL_SetSurfaceBlendMode(vid_softsurface, SDL_BLENDMODE_NONE); #endif vid.softpixels = (unsigned int *)vid_softsurface->pixels; vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid_softsurface->pixels, (unsigned int *)vid.softdepthpixels) < 0) { Con_Printf("Failed to initialize software rasterizer\n"); VID_Shutdown(); return false; } #if SDL_MAJOR_VERSION == 1 // init keyboard SDL_EnableUNICODE( SDL_ENABLE ); // enable key repeat since everyone expects it SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); #endif VID_Soft_SharedSetup(); vid_hidden = false; vid_activewindow = false; vid_hasfocus = true; vid_usingmouse = false; vid_usinghidecursor = false; #if SDL_MAJOR_VERSION == 1 SDL_WM_GrabInput(SDL_GRAB_OFF); #endif return true; } qboolean VID_InitMode(viddef_mode_t *mode) { // GAME_STEELSTORM specific steelstorm_showing_map = Cvar_FindVar("steelstorm_showing_map"); steelstorm_showing_mousecursor = Cvar_FindVar("steelstorm_showing_mousecursor"); if (!SDL_WasInit(SDL_INIT_VIDEO) && SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) Sys_Error ("Failed to init SDL video subsystem: %s", SDL_GetError()); #if SDL_MAJOR_VERSION != 1 Cvar_SetValueQuick(&vid_touchscreen_supportshowkeyboard, SDL_HasScreenKeyboardSupport() ? 1 : 0); #endif #ifdef SSE_POSSIBLE if (vid_soft.integer) return VID_InitModeSoft(mode); else #endif return VID_InitModeGL(mode); } void VID_Shutdown (void) { VID_EnableJoystick(false); VID_SetMouse(false, false, false); VID_RestoreSystemGamma(); #if SDL_MAJOR_VERSION == 1 #ifndef WIN32 #ifndef MACOSX if (icon) SDL_FreeSurface(icon); icon = NULL; #endif #endif #endif if (vid_softsurface) SDL_FreeSurface(vid_softsurface); vid_softsurface = NULL; vid.softpixels = NULL; if (vid.softdepthpixels) free(vid.softdepthpixels); vid.softdepthpixels = NULL; #if SDL_MAJOR_VERSION != 1 SDL_DestroyWindow(window); window = NULL; #endif SDL_QuitSubSystem(SDL_INIT_VIDEO); gl_driver[0] = 0; gl_extensions = ""; gl_platform = ""; gl_platformextensions = ""; } int VID_SetGamma (unsigned short *ramps, int rampsize) { #if SDL_MAJOR_VERSION == 1 return !SDL_SetGammaRamp (ramps, ramps + rampsize, ramps + rampsize*2); #else return !SDL_SetWindowGammaRamp (window, ramps, ramps + rampsize, ramps + rampsize*2); #endif } int VID_GetGamma (unsigned short *ramps, int rampsize) { #if SDL_MAJOR_VERSION == 1 return !SDL_GetGammaRamp (ramps, ramps + rampsize, ramps + rampsize*2); #else return !SDL_GetWindowGammaRamp (window, ramps, ramps + rampsize, ramps + rampsize*2); #endif } void VID_Finish (void) { #if SDL_MAJOR_VERSION == 1 Uint8 appstate; //react on appstate changes appstate = SDL_GetAppState(); vid_hidden = !(appstate & SDL_APPACTIVE); vid_hasfocus = (appstate & SDL_APPINPUTFOCUS) != 0; #endif vid_activewindow = !vid_hidden && vid_hasfocus; VID_UpdateGamma(false, 256); if (!vid_hidden) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (r_speeds.integer == 2 || gl_finish.integer) GL_Finish(); #if SDL_MAJOR_VERSION != 1 { qboolean vid_usevsync; vid_usevsync = (vid_vsync.integer && !cls.timedemo); if (vid_usingvsync != vid_usevsync) { vid_usingvsync = vid_usevsync; if (SDL_GL_SetSwapInterval(vid_usevsync != 0) >= 0) Con_DPrintf("Vsync %s\n", vid_usevsync ? "activated" : "deactivated"); else Con_DPrintf("ERROR: can't %s vsync\n", vid_usevsync ? "activate" : "deactivate"); } } #endif #if SDL_MAJOR_VERSION == 1 SDL_GL_SwapBuffers(); #else SDL_GL_SwapWindow(window); #endif break; case RENDERPATH_SOFT: DPSOFTRAST_Finish(); #if SDL_MAJOR_VERSION == 1 // if (!r_test.integer) { SDL_BlitSurface(vid_softsurface, NULL, video_screen, NULL); SDL_Flip(video_screen); } #else { SDL_Surface *screen = SDL_GetWindowSurface(window); SDL_BlitSurface(vid_softsurface, NULL, screen, NULL); SDL_UpdateWindowSurface(window); } #endif break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: if (r_speeds.integer == 2 || gl_finish.integer) GL_Finish(); break; } } } vid_mode_t *VID_GetDesktopMode(void) { #if SDL_MAJOR_VERSION != 1 SDL_DisplayMode mode; int bpp; Uint32 rmask, gmask, bmask, amask; SDL_GetDesktopDisplayMode(0, &mode); SDL_PixelFormatEnumToMasks(mode.format, &bpp, &rmask, &gmask, &bmask, &amask); desktop_mode.width = mode.w; desktop_mode.height = mode.h; desktop_mode.bpp = bpp; desktop_mode.refreshrate = mode.refresh_rate; desktop_mode.pixelheight_num = 1; desktop_mode.pixelheight_denom = 1; // SDL does not provide this // TODO check whether this actually works, or whether we do still need // a read-window-size-after-entering-desktop-fullscreen hack for // multiscreen setups. #endif return &desktop_mode; } size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) { size_t k = 0; #if SDL_MAJOR_VERSION == 1 SDL_Rect **vidmodes; int bpp = SDL_GetVideoInfo()->vfmt->BitsPerPixel; #ifdef WIN64 SDL_Rect **ENDRECT = (SDL_Rect**)-1LL; #else SDL_Rect **ENDRECT = (SDL_Rect**)-1; #endif for(vidmodes = SDL_ListModes(NULL, SDL_FULLSCREEN|SDL_HWSURFACE); vidmodes && vidmodes != ENDRECT && *vidmodes; ++vidmodes) { if(k >= maxcount) break; modes[k].width = (*vidmodes)->w; modes[k].height = (*vidmodes)->h; modes[k].bpp = bpp; modes[k].refreshrate = 60; // no support for refresh rate in SDL modes[k].pixelheight_num = 1; modes[k].pixelheight_denom = 1; // SDL does not provide this ++k; } #else int modenum; int nummodes = SDL_GetNumDisplayModes(0); SDL_DisplayMode mode; for (modenum = 0;modenum < nummodes;modenum++) { if (k >= maxcount) break; if (SDL_GetDisplayMode(0, modenum, &mode)) continue; modes[k].width = mode.w; modes[k].height = mode.h; // FIXME bpp? modes[k].refreshrate = mode.refresh_rate; modes[k].pixelheight_num = 1; modes[k].pixelheight_denom = 1; // SDL does not provide this k++; } #endif return k; } darkplaces/Darkplaces.app/0000775000175000017500000000000013067716406015021 5ustar kalevkalevdarkplaces/Darkplaces.app/Contents/0000775000175000017500000000000013067716406016616 5ustar kalevkalevdarkplaces/Darkplaces.app/Contents/MacOS/0000775000175000017500000000000013067716406017560 5ustar kalevkalevdarkplaces/Darkplaces.app/Contents/MacOS/darkplaces-osx-sdl0000775000175000017500000000020513067716216023202 0ustar kalevkalev#!/bin/sh # Get dylib files from the same dir as the executable export DYLD_LIBRARY_PATH="${0%/*}" set -- "$0"-bin "$@" exec "$@" darkplaces/Darkplaces.app/Contents/Resources/0000775000175000017500000000000013067716406020570 5ustar kalevkalevdarkplaces/Darkplaces.app/Contents/Resources/._Darkplaces.icns0000664000175000017500000000012213067716216023726 0ustar kalevkalevMac OS X  2 Rdarkplaces/Darkplaces.app/Contents/Resources/English.lproj/0000775000175000017500000000000013067716406023306 5ustar kalevkalevdarkplaces/Darkplaces.app/Contents/Resources/English.lproj/InfoPlist.strings0000775000175000017500000000071013067716216026630 0ustar kalevkalevþÿ/* Localized versions of Info.plist keys */ CFBundleName = "DarkPlaces"; CFBundleShortVersionString = "DarkPlaces"; CFBundleGetInfoString = "DarkPlaces by Forest 'LordHavoc' Hale"; NSHumanReadableCopyright = "Copyright 2011"; darkplaces/Darkplaces.app/Contents/Resources/Darkplaces.icns0000664000175000017500000014672613067716216023536 0ustar kalevkalevicnsÍÖics#Hp ?wpÎpþpü0øðàÀp ?wpÎpþpü0øðàÀis32üƒY‹w‹´ÌnWdA†›–€R˜ÿ™wLR}™&$ö€wf Ò®‚B³€ÿÚ,…‚ æŒ7ÿªjUZª€e¡Y#vØ‹€d¨‚ZÕ°”¿€ËŽÉ¸u] §Õº€Ìœ„iïϵ÷º¶‹°Ä‹/“ƒY‹wó‹mÌnWÙA†Us€R>ÿ™wÌ*]&ö€wÉx‘‚ø¢€ŠÚ¿?‚î•€ 3æŒå¼ªjUZÚŒ€¡Yòvl5€¨‚ Õ°R¿€gGÉ‚t8 ?¬º€UÌœ„i…€]÷…¶‹fÄ‹J/“ƒY‹wå‹OÇnW¡A†)y€Rô™w&X&ö€w¤_‚Ó‹€QÚ¬/‚îm€ æŒÐ¦ªjUZ£g¡Yëv<¨‚ Õ°6¿€;(É‚l%  ™º€LÌœ„i_fD÷r¶‹8Ä‹3/“s8mkG˜:Ð?.CuBWí]."«¨~šëp5!Ó%Ê…Fˆár6oíO Ã^Slám5 ?ý|4Ý‘i:n~T<Fú|C ò”V)3?Ì)‹Ív> Ú¦Q#»ŸLKêxf+ ŠÛV'  ű£Âkk9#È´4Ï´tXX3 )•¥Z Ážf@"  :9–zN#  cK2 !* ICN#ÿÀÿðÿÿøÿÿøÿÿüÿÿüÿÿþÿÿþ?¿ÿþ?ÿþ?ÿþ?ÿþ?ÿþ?ÿü?ÿüÿø€ÿðÀÿààÿ€üüüüü|xxx0ÿÀÿðÿÿøÿÿøÿÿüÿÿüÿÿþÿÿþ?¿ÿþ?ÿþ?ÿþ?ÿþ?ÿþ?ÿü?ÿüÿø€ÿðÀÿààÿ€üüüüü|xxx0il32 Œˆªõâ?—/hwÅÑH–7Mwë— ¤0o[²7H¿«LyN„`<F²Q£HŒHC×@h›Ÿn¯•JXôÕ—f‹¢]$”:U¹`ð dHĪˆè †¿?®Wz\ÿ6Kœ`‹ð—lQ&ÿi¯²ƒ3?ÔA€uÿ5FŠ‹Ô7`܈Z½za· H‚Œ—5k?T¦5E‡‹ŒW¤’ÿVkÿ/¿€/ñn;NW;×7I’²H?6®Qÿ;bõ/? ¯6Už· 7a8NH‚>®?_¿_‡5ÿ6Dƒ`ÔHÿL5CrðâäP>99f®»óFjf¿¢‡$ÿ6E‚W p¬Y7HŠj®"Ž]PPYS—`¨õ_®Zú6Fˆ`¤èZ8NšÁë®jݤ —u;OŽW/Q¤×C7L——¤Óÿd8PªõH37âN¢ÿ8J‡/²QV5?Z¼ „7M¢õH¿3Ôÿ6H€ÿ†×}58I~®“‡HIŽ—_‚?î5Cp,@­66D_¼âª¢ì0r“Amë‚ë3IP[89DVšFf¿¿ÑƒÿWcS ²ª€/`–ãHW<<;DN^œ®H€£Üe(wxb·²ë¿Ôu4:CNT`}·/Œª€õ¨]G£ “ÁªQvvÿ5>W{žÅÑ/_‚—¤bASN}jÙYÁõãÔk6Dsà‹Qfª¿ƒ?¤ÃwVJDEW‚âÂb9Jjë‡(¹€g`ož ÿ\>V² Š?7NÿÝÜÑ/6@Gj访3ª£ÔÔ/¢H¤EU‹N뎀¿ ßRi·Ñª–®Õƒˆèm†ˆªõâ?—/PwÅÑH–7Mwë— ¤o[²7H¿ƒyN„`<F²Q£HŒHu¿@h›Ÿn’?ôÕ—f‹¢J’:U¹`ð dHJªˆè †¿?®WhXÿ6Kœ`‹ð—lQ ÿi¯²ƒ3?Ô€Bû5FŠ‹Ô7`܈Z•za· H‚Œ8k?C¦5E‡‹ŒW¤’ÿVkÿ/¿€/ñi;NW1¿7I’²H?6®ÿ;bõ/? †6Už· .#IH‚>®?_¿_ÿ6Dƒ`ÔÔL5CrðâäP>99e¯ÇóFjf¿¢ƒÿ6E‚W wY7HŠj®"Ž]PPY—`¨õ_® d…6Fˆ`¤ÄZ8NšÁë®jݤ —Ÿu;OŽW/Q¤¿C7L——¤Pèd8PªõH37âN¦ÿ8J‡/²5F5?Z¼ sX7M¢õH¿3Ôÿ6H€ÿr¿m58I~®“†HIŽ—_‚?ç5Cp66D_¼âª¢ìBKAmë‚ë,J€X89DVšFf¿¿Ñƒ¹cS ²ª€/I–ÜFU<<;DN^œ®H€£ÜeIxb·²ë¿Ôi]4:CNT`}·/Œª€õ¨]0T “ÁªQsnÿ5>W{žÅÑ/_‚—¤bAL(::ÙYÁõѲk6Dsà‹Qfª¿ƒ?¤ÃwVJDEW‚âÂb9Jjë‡(¹€g`ož ÿ\>V² Š?7NÿÝÜÑ/6@Gj访3ª£ÔÔ/¢H¤uEU‹N뎀¿ ÇRi·Ñª–®Õƒˆèm†ˆªõâ?—/BwÅÑH–7Mwë— ¤o[²7H¿syN„`<F²Q£HŒHn±@h›Ÿd‰7ôÕ—f‹¢@“:U¹`ð dH ªˆè †¿?®Wb_ÿ6Kœ`‹ð—lQ ÿi¯²ƒ3?Ô€<÷5FŠ‹Ô7`܈Zza· H‚Œk?@¦5E‡‹ŒW¤’ÿVkÿ/¿€/àg;NW/±7I’²H?6®ÿ;bõ/? ^6Už· DH‚>®?_¿_ÿ6Dƒ`Ô­M5CrðâäP>99e¯ËóFjf¿¢‡ÿ6E‚W [Y7HŠj®"Ž]PPY—`¨õ_® ‚6Fˆ`¤§Z8NšÁë®jݤ —u;OŽW/Q¤±C7L——¤Ád8PªõH37âN¨ÿ8J‡/²+:5?Z¼ n=7M¢õH¿3Ôÿ6H€ÿh±e58I~®“€HIŽ—_‚?å5Cpl66D_¼âª¢ì#0Amë‚ë-K€U89DVšFf¿¿ÑƒŠcS ²ª€/@–ÝGU<<;DN^œ®H€£Üe5xb·²ë¿Ôe]4:CNT`}·/Œª€õ¨]%= “ÁªQrrÿ5>W{žÅÑ/_‚—¤bAK1ÙYÁõÅ¡k6Dsà‹Qfª¿ƒ?¤ÃwVJDEW‚âÂb9Jjë‡(¹€g`ož ÿ\>V² Š?7NÿÝÜÑ/6@Gj访3ª£ÔÔ/¢H¤iEU‹N뎀¿ ¸Ri·Ñª–®Õƒˆèm†l8mk š ÿmBÿ§m0 ÿÄŽI@ÿâªb%"6m3(”ÿÿÉ};HQ ÿÿéd=  ¯ÿÿà™F%6Q´ÿÿÖ_8  &©ÿÿî­U%!6VxŸóÿÿ{J  $Îÿÿÿÿÿò¸^!%;_þÿõ…Gèÿÿ¾Íéÿøòº`!  &OÿÿÖy4:ÿÿÙÛÖ·çÿÿð·^  !Yôÿú¯[ ÿÿéæϪ”Øÿÿê°Y *¯ÿÿÜ„5 Ýÿøîרw[¶ÿúè©Ucÿÿë¦I"ÿÿïê¸}K> ÿüé«W Hÿÿð¶Z ÂÿÿôЙR9âÿÿÿø³d) Bÿÿï½c%ÿÿúòÂr69¢ÑãáÓ«|x•W$ ˆÿÿð»d&ÿÿûë´^$-[‹¡¢’ðÿÿ¶‡Míÿóð¸_%ÿÿûç¦T $cÝŒ3ª6ƒZJDDEl˜¦ÙÝ(m3€9ª6äÀ s{K;P‚ë_ÂAˆšB7K“¤_€ Q8ÿ”6IŽ¤H€¿ª¢?¢£Ñ |œC=VœFë ]/m?jÔ¢îÂG@i/ë?zÿÊ`>8I{ ¿ g7P¼=Ucõf‰¾!†ÿH=fè¸É¼ƒX?7DfÌ⪠3W «VPª ‡34dF<’µDlbNA8C^¨të¿‚ m:\U^q£‡ÍO;V,TNF7=If¨F3„ ¢«¸Îœ[zœ²_…ªÜ4ÿÞ4QMWWM:>GVwÃjH… 7¯Œ…&i™Â⪄Ä|½ÿT6ZÂQ3›€_]\DjìÔ¿¤¿N`lQƒ>ë¤õÆWh²²ª¦f¡ Œ¦_â/"¤£¿Ž3¦y®?¨¯Ñ—§E¦sÎQ¦U’NˆW¤ ‘Cdð/¥¿<ÀUU´Q3¤_¦QI²_¿åC×®£_šëwVBvFUb)¼UBj­7Ñ/–®ÿ[cÝŒ3ª6ƒZJDDEi–€¦ÙÝ(m3€9ª#À‡8@kë<<È’5Cv(ë7²ŠytyŠÎ5<A}’ŠjXnr5D{F¢€ å»5F„tª€£F/>6äÀ GoK;P‚ë_ =N?7K“¤_€ 0ñ”6IŽ¤H€¿ª¢?¢£Ñ `œC=VœFë (G?jÔ¢ 툺F@i/ë(dÿ´V>8I{ ¿ g4¸3Ucõf‰¾dÿH=f踥 ˜tT?7DfÌ⪠3WkoVPª ‡3$HE<’§UKA8C^¨të¿‚ m2i2^q£‡ËD7V&5HF7=If¨F3„ ¢™‰pGVwÃjH… 7¯Œ3ûi™Â⪄¾_·ÿT6pŒ³ÖGp›H5^ïŸF=cÝâfH¿3/WªhJYROPPwÜ.Œ3_!ášAAnf¿‘¿?ÑðgUJGJUo•äõ_F;LCDzN¢•H—ÿ¯Šxqz“Î67_¿¤S`7I‹_–¿_£7‹6/—m?Œ™'Y9P¢â™3_¢ë£ë¢H¿ë"]>ZÂQ3›€_]k\DjìÔ¿¤¿NQlQƒ>ë¤õ¦Wh²²ª¦f¡ Œ¦_â/"¤£¿Ž3¦y®?¨§Ñ—§>¦sÎQ¦U’NˆW¤ ‘Cdð/¥¿0ÀUU´Q3¤_—QI²_¿åC×®£_šëmVBvFUb´J.U¢4Ñ/–®ÿ[cÝŒ3ª6ƒZJDDEf–¦ÙÝ(m3€9ª¨8@kë½’5Cv(ë7²ŠytyŠÎ.@|’Šj Qq5D{F¢€ å­5F„tª€£F/>6äÀ 4lK;P‚ë_ž >7K“¤_€  é”6IŽ¤H€¿ª¢?¢£Ñ ]œC=VœFë 7?ÿ–EI²H„€¢j\ÿJ?cÌõª¿?k"A6Euf 7aÁHF‡—_ˆ¿Ÿ_ÿI>jÔ¢ ê…¸E@i/ë[ü¬S>8I{ ¿ g(¶0Ucõf‰¾_ÿH=f踘 †mP?7DfÌ⪠3WbMVPª ‡3 &GD<’¥NKA8C^¨të¿‚ mW!^q£‡Ë|D7V&!DF7=If¨F3„ ¢s@Rzœ²_…ªÇ ÿÖ0NKQUM:>GVwÃjH… 7¯‰îi™Â⪄¾Y·ÿT6ZÂQ3›€_]Z\DjìÔ¿¤¿NHlQƒ>ë¤õ•Wh²²ª¦fª Œ¦_â/"¤£¿h8mk N,  ®o> ÀÂq?çÕ¦_& AòôÁ6Uÿôà˜HŠþøí²Z  ¥þþ÷Ån( 1diÃ×ÛºR.ÜÿÿûÖ3 &Fa–ÅîüòÐ…@#âþþñâ“> *E`ƒ©ÛøþñÏj8 âþþóéŸH1HfŒÄ÷þúî’K!  8äÿÿôì¨N+Goªôÿÿù¤U$ eŸÊ×ìùþþóíªR /Z¡ùþþ÷œQ #…ëþòçÒÉøþþóìªR$R¸ùþûê‰A nÜüÿôæÝÍÀôÿÿÿë¦Q $^èÿÿ÷Ìo+ ùÿ÷óïãŤ”ñþþýæ¢L0Ÿüÿþò¢L ŸþþòøóÞ¹‘f^íþþýâšGUïÿþüÍr* j÷ÿúûöß³P38éÿÿüá•C 1Öÿÿúæ”?(Ùÿþñùæ¸|G%$âþþüà“A¤ÿþöò¯U þÿôüñdžI"·þþüà“Aœÿþû÷Ág#!êÿÿòøÜžX' CØÿÿýã™GÿÿÿúÎv+ oþþüýð¾s7!ˆãüýýóõ²W!–ÿþþûÕ0  ²þþöûâ R V“ËçóôêÃp2 ªÿþõùÔ€3 Ïÿÿúøу< *c®¿Ãº¤µ«§¥§¨¦x,Ïÿÿü÷Ëy/ "îþþøôÁn, .I^kpk^tÕüþÿñÅ^$ 3óÿþóô¾i( (ðþþ÷ò¸b# "(+)*9÷þøðÙ¢f/ ‚þÿöüë¬X!ßÿÿøð²[ ]õÿûøÓ—U( "åÿÿõøØ‘DÐþþùñ²Z Hñþÿù΃A  œþþüüð»o0±þþ÷ô¸` @òþÿùÏz3 tòþþý÷×–M xúþýùÄm( ;óþÿúÑ{0 Xêÿûñùæ±i1 Eìÿÿò؃5;òÿÿúÒ8$‰ðÿüûøë¿A &«úÿùð¢NaöþÿúÖ€Óÿþù÷ôåÀŠM# WìÿþòÍs.rþþÿýññûøòðïéÕ²‚M( /–øÿýö¤S wÿÿÿþõæÞÞâßÑ·—mD$J¿ôþõéC  ‚þþÿÿðØƾ³¥qO3 %_ÄûÿþéF  ÿÿÿü湑yjYG3  .eµõûü÷²_2 þþÿûÛ—[;," .]šÜöñôï¶R3+ VòþýúÔƒ< &N}¯ÔâçãÕ§„]/Dóÿù÷Ìv/ 6Z~œ®µ®™uW9(Ùþýò¾j'  4J^lsjX?*¸þúë±\!!*/0+  –ýóá¡P   [÷éÐCLêÔ½z7'ϾŸc) ‚•}I <\Q2+-it32_:­UWbZ?.3-$ó Q]rcE5.10ó 3Ui‹qE3,.+-'3ï =NeŠnE6/0- !3ï 31J_ˆjD73-,*&ï/:Ek±I81//-."$î83120-+-*é1A<#C¤ÍñoD41131,("ˆ3'€$Ô3@<"E±ÛÿwJ41313//‚?3:6??GUXQKIBCE=.(Ð30A?*XÛñÿU62321/0+‚FKMLOVWbi]VPMOKJ93)'%Ì37=EE8bÔìÿV7331210&3?Z[bdda`ftlbFDlmWTRMLHFFD>433É5FIJJHjÇçÿ‚V72330/3).$R]bimnkfnr`87}}\XWTSPQKJE93*--'Ç:IND<9¦×ÿ‚W<7432/-/-$ACEE@CLPXQJBAZ[W‚«“‡RIIL\UJ8.-,33'3ÂIK@6)Óÿ„Z@;3211-3+€!'3.,25ADEECCDKNS‹À¡‘NCHNea[E8430"ÄD@4ÇéÿŸ|NC33231/*3$.033242€45569AKUH@B^¥ÉçˆbQLF<03/3ÀA:5 ¨¹²„VLF5312'-»3HF4Æéÿ¬ŒPD323113,"$‚ 3--3,3*31/1€8@VND"'Žœ™¢¬kUB=12+&º3GG :Úóÿ±“QC3€21--!3„-€3.(.30.0€126A3#jz}¼ÿŽfKE62.+)± U$CUBDQNCCLNJ:Öñÿ°‘NA3320310'U„$3$/('.,,./..1:A=4 xÞ÷ø•~JA32.3)©U33ILMC?7( •Ã÷ó£ˆB;001+*3Ÿ!$.P_oll†„`I,7/-,+*03?Ÿ¬±€lJA€30303%-U’"'20:=<3/FnƒïúÓ²RF84€3"*Ÿ,69Sbtqt™¢ƒ^'/?A=:38<-ovuwuLB3320303%-U”-",25B?5VxJFåÿÿ×bQ?632-33.UU\^ai|~g^aQ%+49:76542:>")Œ¹Ý›€KA3223110%-U•$*/39:>5-‡¥ùôÖŒA610-+&3šDEUZho‚‚€UFVM06?>?3€219>%+›Òÿ¤‚I>32230.0'-U•3-*,17B(dƒ÷ÿÿ D73230+™&KJ/Œ—Ÿ{UNLEA<8633223129?/( Tçùÿžy>421,+U—-'+6C3) -Lóÿÿ°_E:42/.)3˜?CE"2˜¦zNLKA>=743231€2 9>-'$Yòÿÿx;232,,U—'3)8B70 'Fïüÿ¶jI<42/,*"–(?UD< T`dx¹€HA>334221133100:?$[òÿÿ›v=3€21/3,!3™3').=C 5Q×éÿÌ•V=523203,–F\lYN"PXUf˜oE=:3ƒ200€38))06=544€23231010/<> ;âøÿh;3233030)3'›(.BG?;""=oÿÿõ€R62231--33’9DIQbp}’‘- 9:7884‚3230€/..<@ .°Ýÿ˜r<310110.,3'œ&6>FIVZ]…ÿÿõƒU:53310/3$U4<><;>`€ÀÈ!GH>84331232312.-32-?A "€Ãÿ¤€<33130/,)(*$*.Q[’}œÿÿöŠ]?7221123*5H?(!<Œ×íêjTJD4432332130/-02)+0CF ,‘Ìÿ¡}<33121.-*(/YcŠŒr’øüø«„D9300/0&*B>$J£÷üô‚jHA3233€2//./3-'33.FH( .’Íÿ¡};23122/3+ž3+Zb††pŽí÷ú¾›J:310.,.+GD&7žÓþüòoW75331121€310./,"-FJC:$t½ÿ¡};23122/+&-3ž$-PSgmˆ•µÚþôÜW>2230/+*Œ733:8#!-W¿ãþáÐbN53231202232-+&-DLC:#p¸øy;23122/+&-3ž/LPV\}„’Éÿÿæ[A322//&%Œ'U0('u ôýÿwF>42233110/0+#!-IL6,'u¯âo:23122/+&žLL8:QQK¥ÿÿæ\A3€213,*Œ7:N>5#Åûùó†_=72322122/10(33$;>*"J‡ÁŒv<33122/+&ŸLH "DXˆÄÿÿæ]@32.,33‹APJBÀäýóçyS84332213/1-)'ƒ3+`Ÿ‰}<33122/+&Ÿ-LG 732113-*-3ŸLPVXf‚ÑèÿÿèX<3213322%(Š3K<3 7^éÿÿ¹qK>5€3230/3'--„1FD"<—¡¢yhA:2210/,)ž3MSiks‹ÐèÿÿèW;3€2360-3Š8Zag\]Áèüüò¦Y>521322330,-3‚*FVfTD ;dÁÙ秎]R=90//01*Uœ33JD$dz¬Öÿÿç]A3320//,!Š9XbthgÕÿÿùåœT<4223210/-'*3€/.0G]YRDk¼ÌÔ£ŽbW@;42330%Uœ33IDaw§Ôÿÿç^B32131.+-‰$8A\–qWÓÿÿã˜iA642231232)3"€33;5#>v¡,Wj{DXpniNLJ@2.,3-33PM25[q¬ÖÿÿçaD32231.,3'‰3?OfœlºÙåÎa<523131220)'€33>9$*>lŽ2T`j=Ocb`KIH?133+&3œ3QOBFiÀàÿûä^A32231.,3'‰7Um}¤­°qc˜—~W23322031.+'3 'NLIGCDC==;:<98;<=<987785213..*-$!†"*-'33ŠDE47RpÅâüѲO:223221/3'‰6*hÃæßbJA;4332210.*33„1022123322112233113€1420.0;2&#$--22??DDBJ?-ŠA=9“È÷¯ŠI;23200/,3'‡3;6+=p©ãôõÔ¯WB4231+'… 3,.0210223212‚312368*%'7dB€1/1""‡GUILƒ—Ø×Å {ob:322310-+‡$(6lgI>;‹²óÏM:22332/-)†13$&+3-..0-/..-..//30.27C; )5|”¥–.(1>H8332331..(+†3W[ej’¡ÍÈ­dAL8€2313.+3†3##&53'Lw¹ÖøÏN;21200/,-"‡(U3))3(,/2,++,,2,,--/3<7+#CW¹¸UH7;=51€2112.+'ƒ35?>GLr{•š£k3@B4313**$‡"" d¿ìÿÿÐO;23212,,33"!3'ƒ% $#).0.136 ËØye=8431221301//.ƒ=.-*-NT_nšuOC<3€231/**-*‡-",)#IxÜÿÿÐQ<32211.3$3†*3.+138)Ýê‹r?8421€231/.(3‚$?H8G/(8˜wT=5223021.33‡-"11()-DeÖÿÿÐQ<3223030%-3‰-).,37,ãñ’x@94312201/-0*+‚>F?Q/% +”vU<423201-,31‡37>RQNr›ãÿÿÐP<31121--'-ž3058ßð„K?322310/.+)„HDEB^º¼nhiUA631123213"!‰$:AYZZ©çÿÿÐQ<31121--'-ž*3138ÞòªM@32322/.0+)ƒG@<%>U·¼zp`O=523‚13$$‰:Cbm‹¨ÈñÿÿÒX@€23001'-Ÿ3'-3 áÿùÍNA33200/,(#$„*7F3NŠ&jhRC431320.-),($‰3;D_j‹£ÀïÿûÑZ@2122330%-žU).3 áÿÿÒNA3320/10''‚UUZeV6a=#/7c`L?43€203/+-*'Š=ET`ŒžäüõÍdF223120-$  /13ßÿÿÒL@3€120+--‚$Uhx¡¦¤x„‹¸°TH=73€2022.'$Š8:ED?Up®Ä»¡[E6443213  )4:àÿÿÒL@31132--'33BJI`ÆÒÒ£sœ©œŽH@753€2€1/'"$‹005&>zŽ‡xTF;5321.,&$ 39431230/-+,*(3C4‡³Ë«OGF7213/.(Ÿ3Nd?"*:ãÿÿÓL?12303€3=KNX—´óÿÿÏ p`UP74€232//.+&--4H9 Ää¿GBH823211+'Ÿ3UjJ113201320=KBBK@*Pv¡©z‡Ç˜cE;3233€10./+)3$!38=4+GpÉ¢tL>3223..343K:3àþñÇI>13200/3AKMGE=34W®¿^R`P@6423312103./3"‘08B3&'bŒçÔ¼hK63/0.-/34M2# –¦‰xG=1127:=>@&&eiWI*>{ˆPGF>8423232101.,.--333:I/!3E‰®ùÿÿ„Y71€2.3(3œ%L/  LO!'E<124<@DA?z†q\"#(GPC>3342 3223..-'!“378:<537X¬—{ZN<9310//3œ43H, 3CNF >?ACB?7%&) 1?rf47;@@96223232302011*)($U’*+,@H6/"@˜}ZRNA<433/2$$3š43L, JaYN9=BJK<3$ )me:=@><44322331122.13.-"3U’33@F5:qŽÄÙ裇SI><13/,(**š!L. ­ãæ߬o‚ +/58EE>8644232033,-€3–3$,@D8@‚Ìå÷­]SD@441.,"3šL1¿÷üõ‘oƒ "0589>>=8312331203-.3*--3–"-2HZ¦»ÖÒËž‡€]T>:1/.)'™N;*BZÖÿÿöµ”?;;KO<:988993€2323313221012,+2/"š!22FV’§Ç¿¶~oqdA<61-*$-3˜'L8'KpÆãüøÆ¥I@;BD<:988774€321122122301/+-,.!-3˜3+05Ldž‘ ž²£‹HE>6/.+ ˜$L3_¢‘’ïÿÿÖZF32433‡233120033.+-+*"3›&.3@RtgW,'¤»Î·kZF?:1.2&—DK8&`˜ˆ‰ðÿÿ×]F3221€2 312122321022€/.-3/+33*ž033:F;/:E©Àî׊rXKC632,-•DM@1^‹„ïÿÿ×^I4213€2322331012321,.30)+*-3¡$'239;>KU•™^\b™ÉqNEC@8.333“DM<)Ea‚”ïÿÿÓM@42132€0 22123011.02€(3,3¤3../28<ªÀÖã×r^UMD7.033,3Ž"DM@2>KgyîÿÿÒJ>321323/..-13(+,+3#3©'*/30;=63!?ž§³ÜÙncuk]KA;9A53($ŽDOA1TvbfëÿÿÓK?1223€1 .3/3*$'&+3(3«"330,.13>?@Ia`e ©Vgê‡eXLIGB23.+3-$‰6I7)ðc?æÿÿÓK?223110-3('"-°*33)-0299:>FINqv@J|zs€‚yta\KKFEGACFHGH?3'=73Ç£òùʨI=13300/-'$$3U¯(3-+-,201338@GE.,/F]”¦–ŽleTQLMMLKLNOII'"‚/:?m‘äÿÿð–}G<123003-*¹$3.10€1 4688789>DV\ZYKF><:GNZ^c[UMG3,3€*;HPXÖÿÿñ’x?9113113( "»%-€0!211223:<;<=@AHGA=866DL[_bZRJF51 3@LHGÅðþðv=823310-*#¼+3/./12€34343;‚=<==?@9786355.+)&34AK\f43âíˆp@91221/1.'¼3./++-3123229;::;;<;<=<4231023# -@KW_ÒáƒmA:3€233+3À 33/3.001/112…32310023/3.-.'€ "AM%„aVA9€211-*$3À#3(3*3,01322121122322322313211..0&"€;N$ Š•bV?822131*%Á63!-+)/-.03012/01022021/313./+('-33AK, ¬¸kY43223€/)Ä33()/3,03113/00131//30030('#3B4/7DËÏ`O4212121/&-Æ-$+331-12+,/03-€. 320/..3(-3‚"2?CcuäàXG422110/€3Ê(3#*.3++3,(),.)(3$'-3ƒ 7>@#%”™I@32203%Ë $.,+33*€(3-3%*(„ 33223133.*Ð33ƒ33ˆ3EL45x{E=23123--3ëDKVX`\>82322311 ë GM\_YT8623€2.,%3ë3KOacRL4321121.,!ì4:DFUR4323203133ì.0:=SR4‚23+)3ì3*>@FE33131/0-#ì3$(ACEB3€2113)$í-NPIE3€24.+1+í3KNDA33220/../í$3-..0€21,++$ï -.//1033.+)3-î3-3.011€.'&('Uî 3%,0.132311ï 3$**13311*-3«­UWbZ?.3-$ó Q]rcE5.10ó 3Ui‹qE3,.+-'3ï =LcŠnE6/0- !3ï 31D[ˆjD73-,*&ï/:>d«~I81//-."$î8<8a±K932321'-îA5+RNE9401/-(3íK1%EEC632113+'íF6&3120-+-*é)5, ~¹ñoD41131,("ˆ3'€$Ô$.(#ŠÇÿwJ41313//‚?3:6??GUSQKD;,08;;BDBD>433É5FA*! +’Íÿ‚V72330/3).$JYbIEPQUW`YN):5%2<@EHIGE93*--'Ç:ID'’Íÿ‚W<7432/-/-$AC879:IJOJF:7A;)2;-&!9@QOJ8.-,33'3ÂID (–Ïÿ„Z@;3211-3+€!'3.,25ADEEC@?A=.4?)!5>UVWA5430"ÄD@ 3Æéÿž{NC33231/*3$.033242ƒ47;<=)! #g‰©vaOLF<03/3À%BA 4Èæö˜vMD3130/3*3(+-132100311035€;,$aˆ¬‚o\VI@7/.*$¿&F@+¥»ÇZ8DC31322+)-'(+/+-2340021€3:<-(+S€¥¬‡vPJE6,.+3½DA +¥ÁÒjHFC32.(3€'$+,++32103132128:3/ "(Dc‚ŒŠ‚hZL?60+)3¼FC -®Üÿ¦…NC3€2/0133'33"3-313-01.12202<=<8-# ¢£}VLF5312'-»3EB 3Âæÿ«‹PD323113,"$‚ 3--3,3*31/188786* Xm‹œ«kUB=12+&º3DA 8Õñÿ±“QC3€21--!3„-€3.(.30.0€126@1" 7nµøfKE62.+)± U$CUBQNCCIJD 6Ñîÿ°‘NA3320310'U„$3$/('.,,./..1:@/%S¦éø•~JA32.3)©U33IGJA7OTa^ACLMJ9Øòÿ¯NA€30033%-UŒ3!)(3114474#ÈþîÙUH@9/3/%§*9>9))%$#AKJG46BD?7Ñëù¤ƒLA€30033%-U‹3$$')2,00399& ®æúêkZA;013"¤3?LMI>&0&%"0·Ôå„`F@€30033%-U’ 231>?3%t óó£ˆB;001+*3Ÿ!$.PPN=3?="+$$,+*-- #†™¥yhIA€30303%-U’"'20:=9/=XäõÓ²RF84€3"*Ÿ69€O'8+=@%>?3€217;'–Ïÿ¤‚I>32230.0'-U•3-*,17?%UtöÿüŸD73230+™&421,+U—-'+6C, )ðûîª_E:42/.)3˜346VeL,5;<<743231€2 9; =ìýÿx;232,,U—'3)8B0%âðî®jI<42/,*"–(?M'"Pb•i>==224221133100:> ?ðÿÿ›v=3€21/3,!3™3').:> ‰¬øÉ•V=523203,–€F )AR|^?<:3ƒ200€3<@ >ëüþ˜r<32212002!3™U$#.;B  fú×®bC530101-•5SMD0 )3=544€23231010/<> 9Ýò÷f;3233030)3'›(.BG/( Iÿÿõ€R62231--33’985/&+3W^ /34784‚3230€/..<@ -¬Úü˜q<310110.,3'œ&6><<96RùüõƒU:53310/3$U4<7%0R§·GH>84331232312.-32-?A !|Áÿ¤€<33130/,)(*$*.MTlg,ZñøöŠ]?7221123*5H?&f»ãæiSJD4432332130/-02)+0CC #ˆÈÿ¡}<33121.-*(/RWSK HÎçø«„D9300/0&*B> ‚áóðiHA3233€2//./3-'33.CC "‡Çÿ¡};23122/3+ž3+RUE>A¶Ûú¾›J:310.,.+B<#…ºæáØkW75331121€310./,"-BA ^±ÿ¡};23122/+&-3ž$-PO4/'9s¹þôÜW>2230/+*Œ7330+9¬Ñì̺_N53231202232-+&-DCXªöœy;23122/+&-3ž/LK(%!/[­ÿÿæ[A322//&%Œ'U5kíüþ›uF>42233110/0+#!-IG"ZÚ‹m:23122/+&žID 8œÿÿæ\A3€213,*Œ7:H w¥øøñ†_=72322122/10(33$;732113-*-3ŸLK821}¾ÿÿçW;2213322%(Š3K* $Iæÿÿ¹qK>5€3230/3'--„1CB"k}‰pe@:2210/,)ž3MLB=4y¼ÿÿçV:2360-3Š8WH7('™Êøüò¦Y>521322330,-3‚*FVfK5 -q£Î‹\R=90//01*Uœ33JB$9p¸ÿøà[?3320//,!Š.SKA/+¯âûùåœT<4223210/-'*3€/.0G\OA3k˜¾—‡_U?:42330%Uœ33IB $9r¹ÿøà]A32131.+-‰$17FiG3Çýÿã˜iA642231232)3"€3"3/ 7hŽ$BIE( ?P]ZCDJ@2.,3-33JB :ŠÅÿÿçaD32231.,3'‰349C^J>©ÒÝÈŒa<523131220)'€3363$(9a~,EKE*:JVTBCH?133+&3œ3QF$D£Ñÿûä^A32231.,3'‰7;E@6J[EFz€zV23322031.+'3 'NLIGCDC==;:<98;<=<987785213..*-$!†"*-'33Š@=A·ÛüѲO:223221/3'‰6*O ÆÁSAA;4332210.*33„1022123322112233113€1320,0;2&#$--22??DDBJ?-Š75&ˆÁô­‰I;23200/,3'‡3.$ )f¦ÞíЭWB4231+'… 3,.0210223212‚312366)$&7dB9( '2(EZnUMNG20/3/'(3…GU"+kz˜“Œzk;3232€03‡3#':93JiÅéùÏO:€2€0.'+$†**&*-./23..10332030308?A=-")5/+9N`G?B>€1/1""‡AK%1nx„ugha:322310-+‡$"y©îÌM:22332/-)†13$&+3-..0-/..-..//30.27C; !0oi .65:=51€2112.+'ƒ35.. !*HNZ:9A4313**$‡""€4qØÿÿÐO;23212,,33"!3'ƒ% $#).0.122 ¾Ëta=8431221301//.ƒ=#.MA4;<3€231/**-*‡-"& €JÏÿÿÐQ<32211.3$3†*3.+136ØèŠq?8421€231/.(3‚$?E (  CEG:5223021.33‡-"*">ÌÿÿÐQ<3223030%-3‰-).,35àñ’x@94312201/-0*+‚7B(2 ?FM:423201-,31‡351$3_ÓÿÿÐP<31121--'-ž3036Þð„K?322310/.+)„H- PR%+AA@631123213"!‰$75*$9eÔÿÿÐQ<31121--'-ž*3136ÞòªM@32322/.0+)ƒG- OS/1C@=523‚13$$‰761.)GoÙÿÿÒX@€23001'-Ÿ3'-3àÿùÍNA33200/,(#$„*/B*/_CJPB431320.-),($‰3871-'Bg×ÿúÐZ@2122330%-žU).3àÿÿÒNA3320/10''‚UUZaP+Gg1 ,1GJL?43€203/+-*'Š961+.IÍüôÍdF223120-$  /13ÞÿÿÒL@3€120+--‚$Uhx¡¦¢‰m{ƒ³¬RG=73€2022.'$‹84-$(”¹¥UB6443213  )4:àÿÿÒL@31132--'33BFDZÅÒÑŸl–¢—ŠG@753€2€1/'"$‹0-*^y]QC>;5321.,&$ 39431230/-+,*(3=* p—£†7:F7213/.(Ÿ3Nb*ÞÿÿÓL?12303€3=K:>†¦ëùÿĈbVSO74€232//.+&--4D. x¥ÃŸ-5H823211+'Ÿ3Uh-ßÿÿÓL?12300/-)€37?;B˜½àë÷ÃŽH2EE5223410313(''39J9 µî»!?<;€3/,-3Ÿ43>M( âÿÿÓK?1321100'3EUL;$>W…–‹™ÌŸnG;652311/22-.,"-$-G9&aŠÁ¤<=H?<321/+(<Ÿ*19A" áÿøÌJ>13201320=<3,+&3Pƒoy¦„]C;3233€10./+)3$!3432Z¹•iJ>3223..343K6/àüã¼I>13200/3AKD&1ƒ”GBYM@6423312103./3"‘05;! *]Ú̶fJ63/0.-/34M2! ŽœtfD;1127:;77" $`oC=B=8423232101.,.--333:H,/góÿÿ‚W71€2.3(3œ%L/ ;;?9124<>B;3 >KB>3342 3223..-'!“37840 3š}W:188310//3œ43H, -=@9:=ABA?2  0/#,5>?96223232302011*)($U’*+,88$~Y,&%79433/2$$3š43L, CXMC5;BII<, ::/7>=<44322331122.13.-"3U’33@A%$=R}©ÐjF::=<13/,(**š!L. šÏâÜ£xiz !#-0=>=8544232033,-€3–3$,@B*,K^®×lG>>B@441.,"3šL. ­äúô·ˆi{ "(,35€=8312331203-.3*--3–"-2BKs˜|^8/KPXS>:1/.)'™N07WÔþÿõ®878IM;9‚83€2323313221012,+2/"š!2/AKlzmH*$]gmcA<61-*$-3˜'L/?iÂáû÷ÁžC<9BD;9€8774€321122122301/+-,.!-3˜3+05LZyN" ‰¡‹HE>6/.+ ˜$L.S’‚…íÿÿÖZF32433‡233120033.+-+*"3›&.3@J^@!e|¶¥hWE?:1.2&—DK2N‚y~îÿÿ×]F3221€2 312122321022€/.-3/+33*ž0338@0" ?W¿µ„nVJC632,-•DM8$Hlpyîÿÿ×^I4213€2322331012321,.30)+*-3¡$'23789-*-.40(%UnìÿÿÒJ>321323/..-13(+,+3#3©'*/30;=2.!'AN_•XUhd]KA;9A53($ŽDO?.CZT\êÿÿÓK?1223€1 .3/3*$'&+3(3«330,.13€=<>3*9;'D¤š‡eXLIGB23.+3-$‰6I3!†æ[:åÿÿÓK?223110-3('"-°33)-0299€84065$1_YOjsnkaZHDDEGACFHGH?3'=3,s²›šòùʨI=13300/-'$$3U¯(3-+-,201337=:6' mŠƒkcPLFKMLKLNOII'"‚/7:XpÜÿÿð–}G<123003-*¹$3.10€1 355365221EKHF<5#(-8AW^c[UMG3,3€*8B1%Åúÿñ’x?9113113( "»%-€0!211223:<;::7543/)!)3=X_bZRJF51 3:D%³ëýðv=823310-*#¼+3/./12€34343:;:9:;>=9786355.+)&34AG4& -áí‚k@91221/1.'¼3./++-3123229;::;;<;<=<4231023# -223133.*Ð33ƒ33ˆ3BJ hoD=23123--3ëAI$!NR=82322311 ë GM98NN8623€2.,%3ë3KOMNOL4321121.,!ì4:79RR4323203133ì.004QQ4‚23+)3ì3*36DD33131/0-#ì3$(57DB2113)$í-HKFC3€24.+1+í3FHA@33220/../í$3-..0€21,++$ï -.//1033.+)3-î3-3.011€.'&('Uî 3%,0.132311ï 3$**13311*-3«­UWbZ?.3-$ó Q]rcE5.10ó 3Ui‹qE3,.+-'3ï =H`ŠnE6/0- !3ï 31?XˆjD73-,*&ï/4:a«~I81//-."$î867^°€K932321'-îA1*NLE9401/-(3íK1#@BC632113+'íF3",^fk@122130,"3ê U*H:(upgC6€23.,'ë3G7%‹|gE94401-+3ê$.D8(˜˜‘R<33030.3-é*?5(˜¦¬Z>3120-+-*é),$z·ñoD41131,("ˆ3'€$Ô& "…ÄÿwJ41313//‚?3:6??GUSQKD<<==.(Ð3$.°ÛÿU62321/0+‚FKMLOVWbg[UEGLKJ93)'%Ì373)›ÐÿV7331210&3?Z[GGKKLOXWR5+)%$*25=?BD>433É5F= !ƒÅÿ‚V72330/3).$JYbIELNKOWQG 19AEIGE93*--'Ç:IA %ŽËÿ‚W<7432/-/-$AC879:GGLGE6370 5=MLJ8.-,33'3ÂID(–Ïÿ„Z@;3211-3+€!'3.,25ADEEC=QNCCIJD 6Òïÿ°‘NA3320310'U„$3$/('.,,./..19>*Gãø•~JA32.3)©U33IGF>7JOa`ACLMK9×òÿ¯NA€30033%-UŒ3!)(3114474 ÆþîÙUH@9/3/%§*970)&#$#?GJG67BD@7Ïêø¤ƒLA€30033%-U‹3$$')2,00399&©áúêkZA;013"¤3?FI?4%/&%"/´Ñâ€]F@€30033%-U’ 231>>3#fñó£ˆB;001+*3Ÿ!$.PF6& !)%%,+*-- "‚—¤wfIA€30303%-U’"'20:=8-1LáóÓ²RF84€3"*Ÿ69OD4  #>?3€217;'–Ïÿ¤‚I>32230.0'-U•3-*,15=$MlõÿûŸD73230+™&421,+U—-'+6C, (ðøà ]E:42/.)3˜343;:3ƒ200€3<@ >ëûý—q<32212002!3™U$#.;A FtòÓ®bC530101-•5SC/‚#0=544€23231010/<> 9Ýñö‹d:3233030)3'›(.BG'?ÿÿõ€R62231--33’983'KV +.1584‚3230€/..<@ -¬Ùû–p<310110.,3'œ&6>76/*<ñøõƒU:53310/3$U4<4#:š¯GH>84331232312.-32-?A !|Áÿ¤€<33130/,)(*$*.JNYQ8àðöŠ]?7221123*5H?"V¬ÝähSJD4432332130/-02)+0CC #ˆÈÿ¡}<33121.-*(/QR8/-´Úø«„D9300/0&*B>t×ðïiHA3233€2//./3-'33.CC "‡Çÿ¡};23122/3+ž3+RR) &™Íú¾›J:310.,.+=:i¦ÝÙÐjW75331121€310./,"-BA ^±ÿ¡};23122/+&-3ž$-NJUªþôÜW>2230/+*Œ733,) *–Ãæij^N53231202232-+&-DCXªö›x;23122/+&-3ž/IHC¡ÿÿæ[A322//&%Œ'U FèúýštF>42233110/0+#!-IG"XœÚŠl923122/+&žIC  1˜ÿÿæ\A3€213,*Œ7:E _öøð†_=72322122/10(33$;31303-+"3Š?$ .×öÿÈWC6323311.-++!"„<@!Fh‡rh>732113-*-3ŸLI&H¤ÿÿçV:2213322%(Š3K'>äÿÿ¹qK>5€3230/3'--„1C@Wp…nc@:2210/,)ž3MI,%D¢ÿþæU92360-3Š8U; ‚·õüò¦Y>521322330,-3‚*FVbE-  QŽÆ˜‡\R=90//01*Uœ33JB I¥þîÔY?3320//,!Š.N<% —ÏùùåœT<4223210/-'*3€%&0DZI9 %L„·‘^U>:42330%Uœ33IB P¨þîÔ[A32131.+-‰$*/8J+½øÿã˜iA642231232)3"€3"/+3dŠ#>@0 2@VU<>J@2.,3-33G@ y»þüæaD32231.,3'‰3..1:'šÊÜÇ‹a<523131220)'€3"43"(6_|+BD3!1>PP=>H?133+&3œ3K@ $’Èþûä^A32231.,3'‰73+*,7pxyV23322031.+'3'NLIGCDC==;:<98;<=<€9:97785213..*-$!†"*-'33Š;8*ªÕüѲO:223221/3'‰6*E´±M=?:4332210.*33„1022123322112233113€1320,0;2&#$--22??DDBJ?-Š52!¼ò­ˆH:23200/,3'‡3+" ;}ÏéάWB4231+'… 3,.0210223212‚312354&!&7dB9$ AXnUMNG20/3/'(3…GU$:tzvk;3232€03‡3!!J»çùÏO:€2€0.'+$†**&*-./23..10332030308?A=+ 6L`G?B>€1/1""‡AK ,:]\_fa:322310-+‡$"€t¤ëÉM:22332/-)†13$&+3-..0-/..-..//30.27C;  ba ,;H8332331..(+†3@E <>: :J7€2313.+3†3# € $ÎôÌN;21200/,-"‡(U3))3(,/2,++,,2,,--/3<6) “˜>649=51€2112.+'ƒ35+)#+7@4313**$‡""€>ÊÿÿÐO;23212,,33"!3'ƒ% $#).0.121 ¼Éta=8431221301//.ƒ=!'7<3€231/**-*‡-"$€.ÈÿÿÐQ<32211.3$3†*3.+134ØèŠq?8421€231/.(3‚$?E"-@85223021.33‡-"&€,ÇÿÿÐQ<3223030%-3‰-).,35àñ’x@94312201/-0*+‚7B"+2I9423201-,31‡33( >ÊÿÿÐP<31121--'-ž3034Þð„K?322310/.+)„H*  -6?631123213"!‰$3* BËÿÿÐQ<31121--'-ž*3133ÞòªM@32322/.0+)ƒG* € 49=523‚13$$‰7.HÏÿÿÒX@€23001'-Ÿ3'-3àÿùÍNA33200/,(#$„*/@)'O39431230/-+,*(3;(gŠs)4F7213/.(Ÿ3N`)ÞÿÿÓL?12303€3=K43}Üíû½\PRN74€232//.+&--4D- o˜±Ž"0H823211+'Ÿ3Ue,ÞÿÿÓL?12300/-)€37<24³ÎÚð¹‚A-DE5223410313(''39J9 xªê¸ ><;€3/,-3Ÿ43>K!àÿÿÓK?1321100'3EEI70Dv‰†’¼“dE;652311/22-.,"-$-G8#[‚½¡;;F><321/+(<Ÿ*19?àÿøÌJ>13201320=<'&*#'>rjr™zVB;3233€10./+)3$!3/- #L³dH>3223..343K6/àüã¼I>13200/3AKD&s„>:A<8423232101.,.--3337F*>ìøôU51€2.3(3œL/ ::=8124<>@81 3342 3223..-'!“3763- '“qF.'68310//3œ43H, ,:=6896223232302011*)($U’*+,44 tJ48433/2$$3š3L, AUI@4:BJK<,€)3==<44322331122.13.-"3U’33@A% $4]½F ,1=<13/,(**š!L. —ËâÜ£xj{ &)89<7544232033,-€3–3$,@B*(/=_‘ÀE,3B@441.,"3šL. ªáùô·ˆj| "%&-0<==8312331203-.3*--3–"-2?E_i{T+ 0;VS>:1/.)'™N09ZÔýÿõ®878JO;9‚82122323313221012,+2/"š!2/>E]hwHDQkcA<61-*$-3˜'L/AmÃáû÷ÁžC<9BD;9€8774€321122122301/+-,.!-3˜3+05LVh3{ ‹HE>6/.+ ˜$L.T”‚„íÿÿÖZF32433‡233120033.+-+*"3›&.3@GR0H]ªgWE?:1.2&—DK0M‚y~îÿÿ×]F3221€2 312122321022€/.-3/+33*ž0335:-" *©¥lVJC632,-•DM6Cioyîÿÿ×^I4213€2322331012321,.30)+*-3¡$'21458& -7K‹ÃnLEC@8.333“DM6!lŒîÿÿÓM@42132€0 22123011.02€(3,3¤3../2:@3,  1‡Ö{XLIH?3+,!“DM8  hîÿÿÒH=43211011002€3033,€-'3$¦(-233<=*$1_†Ž]RLID7.033,3Ž"DM>0&!TnìÿÿÒJ>321323/..-13(+,+3#3©'*/30;<2.  !D>=72#&+5>W^c[UMG3,3€*5:'Áùÿñ’x?9113113( "»%-€0!211223:<;9741.-)%%1:X_bZRJF51 37<¯êýðv=823310-*#¼+3/./12€34332:;99::9:;>=9786355.+)&34=G$ -áì}h@91221/1.'¼3./++-3123229;::;;<;<=<4231023# -B luF>223133.*Ð33ƒ33ˆ3BH bjC=23123--3ëAI IP=82322311 ë GM&"JM7623€2.,%3ë3KODDML3321121.,!ì4:/1PQ4323203133ì.0(+PQ4‚23+)3ì3*,/CD33131/0-#ì3$(01CB2113)$í-DFDA3€24.+1+í3DB@>33220/../í$3-..0€21,++$ï -.//1033.+)3-î3-3.011€.'&('Uî 3%,0.132311ï 3$**13311*-3«t8mk@*=`D$! 8Oz\;0,_‚¾ |^P1) xœÕ¿¥ƒuJ>  ²æÚÉ¥•`Q*! '“·ëêåȺ…u>1*™½ìðïÔÈ“‚G9>ÀßüúöèಡaN& "CÇåýüøí溩hU) $FÐíþþüôî;~h5"-QÓîþýûõñÒÄ„n:& FnÚðþýúøöÝД}E.  Q{åøÿýûúøâÖž‡L4 ]‰ïÿÿþüüûçܨT:i–ðÿÿþüþýïæ³›^B# qžòÿÿýúüýñ軣dG'  Áöþÿûö÷÷ôîǯrP,    #šÌ÷þÿûö÷÷öñͶxV0  './/-*&! *§ÚúÿÿýúûûøóÖÀ…`:%.2CJ]r‡ˆ‡|vj^R>7+' 6¯ßûÿÿþüüüùõÜÇŽh?! "2NYfit¤²ÀÂÂÀ¼±©£Ž…piPG1' Bµâúþÿÿþýýû÷áÍ—pF% ?]€Œ™œ¢´åêðòóýûñðîÚдªƒuQB2$ -aÉðýÿÿÿÿþþüùæÔ£{O+ #9_nŠ§ÁÑáëðö÷öøûõóäàØѾ¤†dS8. 4lÏôþÿÿÿÿþþýûéØ©€T- -SbƒŠ›¡³ÆÚçíôöøûÿÿÿõôóïãƧ€mM@* ?Úûþþÿÿþüüÿýðᶌ]6$ !?Kip„¡¯¿ÑÚçìó÷ûúúøøþýûôìàÔ©“iP:# AÛüþþÿÿþüüþýðâ¹a7$ 8C_f|„˜¥³ÇÑÞäïôøùùøøþþþû÷ð義€cJ0& AÚüÿÿÿÿÿýýöõóçÙk@+   &:@SZkw„˜£¶ÀÐÜæñõòòôöÿÿÿþýòéÓ±cQ1) CÛüÿÿÿÿÿýýöõôèÆpB,   /4FLZeqƒŒ¡ª¼ÊÖåêïðô÷üþÿÿþøñáÅ«}kD:  E…Üüþþÿÿþýýø÷÷íΦyH2  #'29ANUhr…˜¨ÁËãéø÷ñøÿÿÿþüöòîÁ­xi>3F†Üüÿÿÿÿÿýýø÷÷îÑ©|K4 "(.7¤ßéùüÿÿþòôüúøôòüýùøöööúüÿÿÿÿÿùø÷÷øïÔ¯„R: $4aÄáøþÿÿÿÿýöùúäÒ™}J/  ":œÁêòüüþþþùøûûøûÿüûøöðìãæê÷üþþÿÿþöôòóøîÑ«Q::TŒÃõýÿþþÿÿýüüóè¾¢kH+ 5T·ÙùþÿÿþþþùøûûøûÿüûõóéäÚßåöüþþÿÿþöôóó÷îЪP9 1H~¹ïûÿþþÿÿþýüöîƬuP0 V–Ðòýÿÿÿþøûýüúö÷ÿüùòîáÛÇÁ³ÀÑñýÿÿÿÿÿÿÿÿþôê̦{K4 +RËîúÿÿÿÿÿýûûøàÉ–kE# :´âøÿÿÿüúöùüüûøùýùôêäÑË´¯ ²ÇïýÿÿÿÿÿÿÿÿþóéÉ£yI3$G¸éùÿÿÿÿÿýüüøæÒ£vO) RèøþÿÿþýóóñöûþÿÿýöîæÑÈ«¢…p´èüþþÿÿþÿÿÿþòæÃsE1  ,\‹Ü÷ýþÿÿþÿÿúøóä¿‘g8' -‹³óüÿÿÿÿþöööúýýýøôêÜÏ´ª‹‚gbX~©æüÿÿÿÿÿùøÿþñåÀšoB. !KrÍìýÿÿÿÿÿÿüú÷ëÌ wD0 .NÇäýÿÿÿÿÿÿùúüþÿüúñêÚÆ´•ˆh_FB>lŸãüÿÿÿÿÿôñþþðã¾—k?+ 6W¼àûÿÿÿÿÿÿýýúòÚ±ŠR=$x¢åôÿÿþÿÿúúþüöùüöðàÓ¸ Šk_D=+)*^—áüþþÿÿþôñüüð㼕i>*  &@­ÓøýÿÿþÿÿüüüõäÁžaJ!5›ÇñúÿÿþþþùùþüõøúðêÓĦ‹sVK3-!W”àüþþÿÿþôñüûð⻓g<) 5 Çõüÿÿþÿÿüûü÷êɨiP&  2eÈíýÿÿÿÿöóþþ÷ùþúóâÕ³ |cM5,Pßüÿÿÿÿÿôñüüð⻓g;( '{žë÷ÿÿÿÿÿõòõôñÕ¹y`/$ J‡ØöþÿÿÿÿöóýþøùýõìÕÇ¡ŽiQ=)! K‰Üúÿÿÿÿÿöôüûð⻓g;(  r”èöÿÿÿÿÿôñôóóÚÀg5)  -~Òóÿÿÿÿÿÿöôùúÿý÷ëݽ©kI5$ B|Ôõþÿÿÿÿÿÿýûð⻓g;(  h‰æõÿÿÿÿÿöó÷÷öàËŽv<. TŸêúÿÿÿÿÿÿúùüüýùïÝÉ£ŽeS5& 8jÍòþÿÿÿÿùøþüð⻓g;(  a‚åõÿÿÿÿÿúùùø÷äÑ—B4 'xºüþþÿÿÿÿÿÿÿÿÿúõçжŒwN>%  -XÆðüþÿÿþôñþýð⻓g;(  Zzåöÿÿþÿÿþþûûùèמ‡I:FÂáÿÿÿÿÿÿûöùúÿþóëÒ¶•mY5) ?rÎñýÿÿÿÿöóüüð㽕i>*  Poàóÿÿÿÿÿÿÿûûûìݨ‘QB%UÜîÿÿÿÿÿÿúôøùÿþðæɪ‰`K-"  L‚Òñþÿÿÿÿöóüüñä¿—k?+  LkßòÿÿÿÿÿÿÿüûûîକUEZ…ôúÿþþÿÿÿÿþÿÿùöäÖ­ŒiE4  8‡ªÑñýþþÿÿþüûø÷ôèÇ¡wI4 Ooáôÿÿþÿÿþþýýüðå³\K!k”øüÿþþÿÿÿÿÿÿÿøôàФ„`=-!5R£Ááöþþþÿÿþüüø÷ôêͧ}P: Ppáôÿÿþÿÿþþýýüñç´Ÿ^M"$«ÇÿÿÿÿÿÿþûûûþÿòìͺˆiG+ …¾Ìíóúûüüüüüüüüø÷óðæ™iQ(  Rsäöÿÿÿÿÿþþýýýó鹤bP#,ºÓÿÿÿÿÿÿþúúúýýðéð~`?% ‚›ÅÑïôúûüúúûûûûûø÷öóëʦsZ/! Uwãõÿÿÿÿÿþþüüýó鹤bQ# <ÚìþÿÿþþÿýõööúúèÞ±›gL/ [r¹Çâìõúû÷÷ùúúúú÷÷ýûöàÅŽq@/  ]€äôÿÿþÿÿÿÿûüüò躥cR$ .Káðÿÿÿÿÿþýúúûù÷áÕ£ŽZA& H]£³×âîõ÷÷øùúûûûøøúøóáËœ‚RB*" sšìùÿÿÿÿÿþþúûüñæ·£bQ# =[çõÿÿÿÿÿþþÿÿÿùõØÌ”M6  4E‰œÇÔâëïôöøøùúúø÷õóîâÒ­˜m^IB<988888888860,"  ‰µöÿÿÿÿÿÿýýúúûï㳟aP#  RpéöþÿÿþþüüÿÿÿöðÎÀ…q@-  (4btž°ÂÏÕÜÞâåæææææâß×ØÙÖÓÇ»¸·µµµµµµ¶¶¶µ´«–]H9" –Âöþÿÿþÿÿüúøøúìß­™ZJ"`}ëöþÿÿþþüüÿÿÿôîɺj9'  *O_‡™®¼ÂËÍÒÕ×××××ÒÐÈÍÓàæèèççæååååååååææåÚÀ}bL.! $ Ìøþÿÿþÿÿûù÷÷ùëÜ©“XH  ÷þÿÿÿÿÿüûûýÿðé¾®p]1 .8QbtƒŠ•˜ž ¢£¤¢¡ ž˜œ¢ÁÑëñ÷úüüüüüüùòòñïíçÜÀ—pF6 <ÀìýÿÿÿÿÿÿÿþóôøæÔŸ‰OA ˆ¦ùÿÿÿÿÿÿûûûýþï縧jW- !*>M\jqy}„‡‡ˆˆ‡„‚~„Œ¬¾äî÷ûÿÿÿÿÿüúõôóòñëãΦ€Q>+QÊòþÿÿÿÿýüÿþöõöãКƒI;–³úÿÿÿÿÿÿûûúúûêᯞaN% !)2=BGIOQRSTSSQQPYc„˜ËÛïøÿÿÿÿþòôûöòøøðèÖ¹œcM&  C}ÖôþÿÿÿÿøñüÿÿüóÞÈxB5•²úÿÿÿÿÿÿûûùúûèÞª˜\J"  %(,.0345544344=Iex­Äì÷ÿÿÿÿþö÷ûùøù÷îäѵšfR) ]¦äùÿÿÿÿÿùôúüþúð×¾…n<0•²úÿþÿÿþþûûùúûæÛ¤“VE  %1J]­ê÷þÿÿþþûûûýÿú÷ìâʯ—hV+! tÊòÿþþÿÿþû÷÷øü÷ìд|e6* Š¦ðøÿÿÿÿÿûûùúúäÙ¡RA ,:g‹Ùðÿÿÿÿÿûûùüÿ÷òßѲ–|VG& T¥ðüÿÿÿÿÿÿþýþþùòáÁ£lW-# …¡íöÿÿÿÿÿüûùúúäÙ ŽP? ,V}ÕîÿÿÿÿÿûûùüÿõîÙɨ‹pN?$  %l¸ýÿÿÿÿÿÿÿÿÿÿÿøðݼœfQ( nŠéôþÿÿþþûûúúúãØ‹N=:dÈèþÿÿþþýüùúúí㮈lR5+ #N·àþÿÿþþùøöøúþþòçË¥ƒS@ i…éôþÿÿþþûûúúúãØ‹N= 4_ÆçþÿÿþþýüùúúìἨ€cJ/% 2_ÉéþÿÿþþùøöøúþýðãÄŸ|N< \yéõÿÿÿÿÿýüûûûåÚ ŽO> 'TÂåÿÿÿÿÿÿþúûúæج“eJ2  ƒ¬ðúÿÿÿÿÿÿÿÿÿÿúöåÔ¯ˆd=. VsèôÿÿÿÿÿýýüüüçÛ¡P? %RÁåÿÿÿÿÿÿþúûúæØ©aD- !;ŸÂöüÿÿÿþþÿÿýþÿøóÝÌ£}[5'  Gcãðþÿÿþþÿÿýýüéݦ“TC MÀäþÿÿþþÿþûûúçؤ‰U:" _ˆÞðþÿþÿÿúúÿý÷ûþóêÌ·‰fG'  ;W×èþÿÿÿÿÿý÷úýë᪘XFM¿äÿÿÿÿÿÿþûûúçØ¥‰T8  :‘¹îøÿÿÿÿÿøøýüûûúêß»¤wW;  /IÈÝýþÿÿÿÿüñ÷ýíã°Ÿ^K! L¾ãÿÿÿÿÿÿÿüüûéÙ§‹T6 6fÆëýÿÿÿÿÿÿ÷÷ûüÿúôàÒ§dG.&<§Á÷ûÿþþÿþú÷ôï껪gS& K»âþÿÿþþÿÿüüûêÛ¨ŒU7  D~¼êúþþÿüøùùýýþýûóé̺ŒuM5! "5˜µõúÿþþÿÿýøòðìÁ°mX) KºáþÿÿþþÿÿüüûêÜ©V8  .i£ß÷ÿþþÿûö÷øþþÿþøîáÀ¬}gA, (x›óùÿÿÿÿÿÿýúöò;}g4"J¶ßÿÿÿÿÿÿÿüüûëÝ«Y: !aÒæúþÿÿþúöòùûóóüùñßÊ£_L. "hìöÿÿÿÿÿÿýûøõÕƆp:&K¸àÿÿÿÿÿÿÿüüûëݬZ=%!"AP‘«êôþþþÿþûøôúüôôùõèÓ»’}Q@%Jp×ëÿÿÿÿÿÿüùûúâÕ™‚H1 "OÀäÿÿÿÿÿÿÿüüûëÝ­’`E/DR«ÀóùþÿÿûúüüÿýúþÿûúðèÓºs^;- :[·×øþÿÿÿÿþüûùêଔX= +[Ðìÿÿÿÿÿÿÿüüûìß²žxro“¤ÙäþÿÿþýüûüûûüüýýõòáÖºŸ€]K,"  -J˜Åñûþÿÿÿÿþúùñé¼£fH(  5gàõþÿÿþþÿÿüüûíᶧ’Ÿ®ßðýþÿÿþýûüýüúöúÿûùïêÒÅ£‡hG8  3l¥ßïöþÿÿÿÿúùùôÒ½„a; 8læøÿÿÿÿÿÿÿüþÿóîâÞÙáë÷üÿÿüüýüûùùÿÿýûøïëÕͬyaH0%)[–Ôêóþÿÿÿÿúùû÷ÜÈ‘kB#  9mèùÿÿÿÿÿÿÿüþÿöôððñøÿÿÿÿÿûüýüûøøþÿþùôéãÉÀœhS;&  eªÊëôÿÿÿÿÿúùôîß¼•aH%  AtèùÿÿÿÿÿÿÿÿüúóòûüüüýýýýýüûúøõðìàØų£…z[R803V˜·åðýþÿÿÿûúõòèÉ¥rY0#  Cuèùÿÿÿÿÿÿÿÿüùõõúûûûûûûúúø÷ôñîäÞÐƲ rgLD.&  5j‰ÑäöûþÿÿþþúúûåË›€M: Fxèøþÿÿþþÿÿÿúõýÿúúø÷öôôòñîêãÛÒÄ»¤˜‚n]G>)$ (Sm®Çí÷ýþþÿÿýüùïḠmW3! GyéùÿÿÿÿÿÿÿÿüúûûöóîêæâàÝÛÓÐƽ±¡—‚waRD2+  :P‡¦àðúüýÿÿÿþøù÷ØÅxL4  HzéùÿÿÿÿÿÿÿÿÿÿûøðêßØÒÌÊÁ¾¶±¤™Œzq\RA5+ #4`{°ÑìõøõõÿÿýüúòéÅ®~^B( Hzèøþÿÿþþÿÿüþÿ÷òáÖÁ³§œ˜‚~rh]OG92& &Le—½ßîôòóþþþýûû÷ÜÇ›xY:.HzèøþÿÿþþÿÿüþÿõîØʱŸ„usidYPF93'"  ,>fŒ®ÖäøûøùÿÿÿúøôîÞ¼œo]:2 HzéùÿÿÿÿÿÿÿüþþñçŲŒvcSNCA95.)#   0Qs“ÀÒíò÷ùþþþûúùöíÓº‘ZP3+ Gyèøÿÿÿÿÿÿÿüþþï㾩€gRA;20*'! /Jb£ÐÚõøøøøüýÿþøòìÚÏŸ‘dX=2& Ewå÷ÿÿÿÿÿÿÿüüûí೚jN7&   1Cjz¨µÚâñõùúúþýùöóíêÌèƒwiQG:5/*'  :iÖïÿÿÿÿÿþýûûûëÝ®”aD- )IV„’¿Íëòúøøûûù÷ôýÿóðçÞĸ©„sa[PKG4-  0\ÅæþÿÿþþüûûûûêÛ©ŽZ=$(1R^†•»Ìàëñùúûøô÷ø÷öóñèåàÊ¿¯¨ŸŒ{cY7. *V¿äÿÿÿÿÿúúøúûçצŠU8  "?Io¦»Ñåìöøû÷óõö÷÷÷÷ôôòâÙɸ£tiC9 (S»âÿÿÿÿÿùø÷ùùæפˆT6  #;Ge{“¯½ÔÜéíñö÷øùø÷÷õòéãÑɺ®£†{WM+! BžÍôýÿþþø÷ôööàÏœM1  /:Ti¬ÅÎÞåêñóõõöööôðéãÒʹ®¤‰}ZP-$ ?•Èóüÿþþø÷õöõÞΘ}K/  (6F]jƒ¦µÂÎÒÞàåçéçåÞÙÊÁ±¢•xXN1' 3y¸ñüÿÿÿÿÿÿúóÙÆvD* *7JUmxŽœ©·½ÉÌÔÖÚÙ×Ñ̾¶¥—ŠvmSJ.% /r´ðüÿÿÿÿÿÿúòÖ‹qB)  ")7@O^j}„”˜¥©°±°¬¨œ•…xlZR@:&  'e¬ïûÿþþÿÿÿøïϹg;% &+6AJY_mq}ˆ‰Š‡…{vi^TF?2-  QŸèúÿøøýýÿ÷ì˵za6! #(48CGRV\^__]WSJB:0,! =ß÷ÿòòüüÿöêÅ®t[2 $&-/5798741+&# 3ƒÑñüööùúüó徦kT-  #$&&&#" -zÄîü÷ö÷ùüñâ¹ hP*    "^™áùõôòôùìÜ°–_H$   XßùõôõöøëÙ«‘ZD# RŠÞú÷÷ýûöåС‡R>GzÖõ÷øüúôáÊ™L9=jÎðúûûùòÞÅ’wG4 ,NÂêöøøõìÖ»ˆn>- &D¼åõ÷öóéѶƒi;+ 6 ÆæìïëÝæv^3$ 2˜½àæìçØ¿¡rZ1#"x™ÈÑØÓƬbL) i†ºÃÍÈ»¡„[G&  AT“ž®«žˆmK: 3Bp{‡s]@1 "-LUqrm]L4( 4:LNPG<) *0>AF>4$ darkplaces/Darkplaces.app/Contents/Info.plist0000664000175000017500000000104113067716216020561 0ustar kalevkalev CFBundleDevelopmentRegion English CFBundleExecutable darkplaces-osx-sdl CFBundleIconFile Darkplaces CFBundlePackageType APPL CFBundleSignature ???? CFBundleVersion 1.0 darkplaces/Darkplaces.app/Contents/PkgInfo0000664000175000017500000000001013067716216020064 0ustar kalevkalevAPPL????darkplaces/prvm_offsets.h0000664000175000017500000010137613067716222015063 0ustar kalevkalevPRVM_DECLARE_clientfieldedict(aiment) PRVM_DECLARE_clientfieldedict(chain) PRVM_DECLARE_clientfieldedict(enemy) PRVM_DECLARE_clientfieldedict(groundentity) PRVM_DECLARE_clientfieldedict(owner) PRVM_DECLARE_clientfieldedict(tag_entity) PRVM_DECLARE_clientfieldfloat(alpha) PRVM_DECLARE_clientfieldfloat(bouncefactor) PRVM_DECLARE_clientfieldfloat(bouncestop) PRVM_DECLARE_clientfieldfloat(colormap) PRVM_DECLARE_clientfieldfloat(dphitcontentsmask) PRVM_DECLARE_clientfieldfloat(drawmask) PRVM_DECLARE_clientfieldfloat(effects) PRVM_DECLARE_clientfieldfloat(entnum) PRVM_DECLARE_clientfieldfloat(flags) PRVM_DECLARE_clientfieldfloat(frame) PRVM_DECLARE_clientfieldfloat(frame1time) PRVM_DECLARE_clientfieldfloat(frame2) PRVM_DECLARE_clientfieldfloat(frame2time) PRVM_DECLARE_clientfieldfloat(frame3) PRVM_DECLARE_clientfieldfloat(frame3time) PRVM_DECLARE_clientfieldfloat(frame4) PRVM_DECLARE_clientfieldfloat(frame4time) PRVM_DECLARE_clientfieldfloat(gravity) PRVM_DECLARE_clientfieldfloat(ideal_yaw) PRVM_DECLARE_clientfieldfloat(idealpitch) PRVM_DECLARE_clientfieldfloat(geomtype) PRVM_DECLARE_clientfieldfloat(jointtype) PRVM_DECLARE_clientfieldfloat(forcetype) PRVM_DECLARE_clientfieldfloat(lerpfrac) PRVM_DECLARE_clientfieldfloat(lerpfrac3) PRVM_DECLARE_clientfieldfloat(lerpfrac4) PRVM_DECLARE_clientfieldfloat(mass) PRVM_DECLARE_clientfieldvector(massofs) PRVM_DECLARE_clientfieldfloat(friction) PRVM_DECLARE_clientfieldfloat(maxcontacts) PRVM_DECLARE_clientfieldfloat(erp) PRVM_DECLARE_clientfieldfloat(modelindex) PRVM_DECLARE_clientfieldfloat(movetype) PRVM_DECLARE_clientfieldfloat(nextthink) PRVM_DECLARE_clientfieldfloat(pitch_speed) PRVM_DECLARE_clientfieldfloat(pmove_flags) PRVM_DECLARE_clientfieldfloat(renderflags) PRVM_DECLARE_clientfieldfloat(scale) PRVM_DECLARE_clientfieldvector(modelscale_vec) PRVM_DECLARE_clientfieldfloat(shadertime) PRVM_DECLARE_clientfieldfloat(skeletonindex) PRVM_DECLARE_clientfieldfloat(skin) PRVM_DECLARE_clientfieldfloat(solid) PRVM_DECLARE_clientfieldfloat(tag_index) PRVM_DECLARE_clientfieldfloat(userwavefunc_param0) PRVM_DECLARE_clientfieldfloat(userwavefunc_param1) PRVM_DECLARE_clientfieldfloat(userwavefunc_param2) PRVM_DECLARE_clientfieldfloat(userwavefunc_param3) PRVM_DECLARE_clientfieldfloat(yaw_speed) PRVM_DECLARE_clientfieldfunction(blocked) PRVM_DECLARE_clientfieldfunction(camera_transform) PRVM_DECLARE_clientfieldfunction(predraw) PRVM_DECLARE_clientfieldfunction(think) PRVM_DECLARE_clientfieldfunction(touch) PRVM_DECLARE_clientfieldfunction(use) PRVM_DECLARE_clientfieldstring(classname) PRVM_DECLARE_clientfieldstring(message) PRVM_DECLARE_clientfieldstring(model) PRVM_DECLARE_clientfieldstring(netname) PRVM_DECLARE_clientfieldvector(absmax) PRVM_DECLARE_clientfieldvector(absmin) PRVM_DECLARE_clientfieldvector(angles) PRVM_DECLARE_clientfieldvector(avelocity) PRVM_DECLARE_clientfieldvector(colormod) PRVM_DECLARE_clientfieldvector(glowmod) PRVM_DECLARE_clientfieldvector(maxs) PRVM_DECLARE_clientfieldvector(mins) PRVM_DECLARE_clientfieldvector(movedir) PRVM_DECLARE_clientfieldvector(oldorigin) PRVM_DECLARE_clientfieldvector(origin) PRVM_DECLARE_clientfieldvector(size) PRVM_DECLARE_clientfieldvector(velocity) PRVM_DECLARE_clientfieldvector(modellight_ambient) PRVM_DECLARE_clientfieldvector(modellight_diffuse) PRVM_DECLARE_clientfieldvector(modellight_dir) PRVM_DECLARE_clientfunction(CSQC_ConsoleCommand) PRVM_DECLARE_clientfunction(CSQC_Ent_Remove) PRVM_DECLARE_clientfunction(CSQC_Ent_Spawn) PRVM_DECLARE_clientfunction(CSQC_Ent_Update) PRVM_DECLARE_clientfunction(CSQC_Event) PRVM_DECLARE_clientfunction(CSQC_Event_Sound) PRVM_DECLARE_clientfunction(CSQC_Init) PRVM_DECLARE_clientfunction(CSQC_InputEvent) PRVM_DECLARE_clientfunction(CSQC_Parse_CenterPrint) PRVM_DECLARE_clientfunction(CSQC_Parse_Print) PRVM_DECLARE_clientfunction(CSQC_Parse_StuffCmd) PRVM_DECLARE_clientfunction(CSQC_Parse_TempEntity) PRVM_DECLARE_clientfunction(CSQC_Shutdown) PRVM_DECLARE_clientfunction(CSQC_UpdateView) PRVM_DECLARE_clientfunction(GameCommand) PRVM_DECLARE_clientfunction(URI_Get_Callback) PRVM_DECLARE_clientglobaledict(other) PRVM_DECLARE_clientglobaledict(self) PRVM_DECLARE_clientglobaledict(trace_ent) PRVM_DECLARE_clientglobaledict(world) PRVM_DECLARE_clientglobalfloat(clientcommandframe) PRVM_DECLARE_clientglobalfloat(cltime) PRVM_DECLARE_clientglobalfloat(coop) PRVM_DECLARE_clientglobalfloat(deathmatch) PRVM_DECLARE_clientglobalfloat(dmg_save) PRVM_DECLARE_clientglobalfloat(dmg_take) PRVM_DECLARE_clientglobalfloat(drawfont) PRVM_DECLARE_clientglobalfloat(frametime) PRVM_DECLARE_clientglobalfloat(gettaginfo_parent) PRVM_DECLARE_clientglobalvector(getlight_ambient) PRVM_DECLARE_clientglobalvector(getlight_diffuse) PRVM_DECLARE_clientglobalvector(getlight_dir) PRVM_DECLARE_clientglobalfloat(input_buttons) PRVM_DECLARE_clientglobalfloat(input_timelength) PRVM_DECLARE_clientglobalfloat(intermission) PRVM_DECLARE_clientglobalfloat(maxclients) PRVM_DECLARE_clientglobalfloat(movevar_accelerate) PRVM_DECLARE_clientglobalfloat(movevar_airaccelerate) PRVM_DECLARE_clientglobalfloat(movevar_entgravity) PRVM_DECLARE_clientglobalfloat(movevar_friction) PRVM_DECLARE_clientglobalfloat(movevar_gravity) PRVM_DECLARE_clientglobalfloat(movevar_maxspeed) PRVM_DECLARE_clientglobalfloat(movevar_spectatormaxspeed) PRVM_DECLARE_clientglobalfloat(movevar_stopspeed) PRVM_DECLARE_clientglobalfloat(movevar_wateraccelerate) PRVM_DECLARE_clientglobalfloat(movevar_waterfriction) PRVM_DECLARE_clientglobalfloat(particle_airfriction) PRVM_DECLARE_clientglobalfloat(particle_alpha) PRVM_DECLARE_clientglobalfloat(particle_alphafade) PRVM_DECLARE_clientglobalfloat(particle_angle) PRVM_DECLARE_clientglobalfloat(particle_blendmode) PRVM_DECLARE_clientglobalfloat(particle_bounce) PRVM_DECLARE_clientglobalfloat(particle_delaycollision) PRVM_DECLARE_clientglobalfloat(particle_delayspawn) PRVM_DECLARE_clientglobalfloat(particle_gravity) PRVM_DECLARE_clientglobalfloat(particle_liquidfriction) PRVM_DECLARE_clientglobalfloat(particle_orientation) PRVM_DECLARE_clientglobalfloat(particle_originjitter) PRVM_DECLARE_clientglobalfloat(particle_qualityreduction) PRVM_DECLARE_clientglobalfloat(particle_size) PRVM_DECLARE_clientglobalfloat(particle_sizeincrease) PRVM_DECLARE_clientglobalfloat(particle_spin) PRVM_DECLARE_clientglobalfloat(particle_stainalpha) PRVM_DECLARE_clientglobalfloat(particle_stainsize) PRVM_DECLARE_clientglobalfloat(particle_staintex) PRVM_DECLARE_clientglobalfloat(particle_stretch) PRVM_DECLARE_clientglobalfloat(particle_tex) PRVM_DECLARE_clientglobalfloat(particle_time) PRVM_DECLARE_clientglobalfloat(particle_type) PRVM_DECLARE_clientglobalfloat(particle_velocityjitter) PRVM_DECLARE_clientglobalfloat(particles_alphamax) PRVM_DECLARE_clientglobalfloat(particles_alphamin) PRVM_DECLARE_clientglobalfloat(particles_fade) PRVM_DECLARE_clientglobalfloat(player_localentnum) PRVM_DECLARE_clientglobalfloat(player_localnum) PRVM_DECLARE_clientglobalfloat(require_spawnfunc_prefix) PRVM_DECLARE_clientglobalfloat(sb_showscores) PRVM_DECLARE_clientglobalfloat(servercommandframe) PRVM_DECLARE_clientglobalfloat(serverdeltatime) PRVM_DECLARE_clientglobalfloat(serverprevtime) PRVM_DECLARE_clientglobalfloat(servertime) PRVM_DECLARE_clientglobalfloat(time) PRVM_DECLARE_clientglobalfloat(trace_allsolid) PRVM_DECLARE_clientglobalfloat(trace_dphitcontents) PRVM_DECLARE_clientglobalfloat(trace_dphitq3surfaceflags) PRVM_DECLARE_clientglobalfloat(trace_dpstartcontents) PRVM_DECLARE_clientglobalfloat(trace_fraction) PRVM_DECLARE_clientglobalfloat(trace_inopen) PRVM_DECLARE_clientglobalfloat(trace_inwater) PRVM_DECLARE_clientglobalfloat(trace_networkentity) PRVM_DECLARE_clientglobalfloat(trace_plane_dist) PRVM_DECLARE_clientglobalfloat(trace_startsolid) PRVM_DECLARE_clientglobalfloat(transparent_offset) PRVM_DECLARE_clientglobalstring(gettaginfo_name) PRVM_DECLARE_clientglobalstring(mapname) PRVM_DECLARE_clientglobalstring(trace_dphittexturename) PRVM_DECLARE_clientglobalvector(dmg_origin) PRVM_DECLARE_clientglobalvector(drawfontscale) PRVM_DECLARE_clientglobalvector(gettaginfo_forward) PRVM_DECLARE_clientglobalvector(gettaginfo_offset) PRVM_DECLARE_clientglobalvector(gettaginfo_right) PRVM_DECLARE_clientglobalvector(gettaginfo_up) PRVM_DECLARE_clientglobalvector(input_angles) PRVM_DECLARE_clientglobalvector(input_movevalues) PRVM_DECLARE_clientglobalvector(particle_color1) PRVM_DECLARE_clientglobalvector(particle_color2) PRVM_DECLARE_clientglobalvector(particle_staincolor1) PRVM_DECLARE_clientglobalvector(particle_staincolor2) PRVM_DECLARE_clientglobalvector(particles_colormax) PRVM_DECLARE_clientglobalvector(particles_colormin) PRVM_DECLARE_clientglobalvector(pmove_inwater) PRVM_DECLARE_clientglobalvector(pmove_maxs) PRVM_DECLARE_clientglobalvector(pmove_mins) PRVM_DECLARE_clientglobalvector(pmove_onground) PRVM_DECLARE_clientglobalfloat(pmove_waterjumptime) PRVM_DECLARE_clientglobalfloat(pmove_jump_held) PRVM_DECLARE_clientglobalvector(pmove_org) PRVM_DECLARE_clientglobalvector(pmove_vel) PRVM_DECLARE_clientglobalvector(trace_endpos) PRVM_DECLARE_clientglobalvector(trace_plane_normal) PRVM_DECLARE_clientglobalvector(v_forward) PRVM_DECLARE_clientglobalvector(v_right) PRVM_DECLARE_clientglobalvector(v_up) PRVM_DECLARE_clientglobalvector(view_angles) PRVM_DECLARE_clientglobalvector(view_punchangle) PRVM_DECLARE_clientglobalvector(view_punchvector) PRVM_DECLARE_clientglobalfloat(sound_starttime) PRVM_DECLARE_field(SendEntity) PRVM_DECLARE_field(SendFlags) PRVM_DECLARE_field(Version) PRVM_DECLARE_field(absmax) PRVM_DECLARE_field(absmin) PRVM_DECLARE_field(aiment) PRVM_DECLARE_field(alpha) PRVM_DECLARE_field(ammo_cells) PRVM_DECLARE_field(ammo_cells1) PRVM_DECLARE_field(ammo_lava_nails) PRVM_DECLARE_field(ammo_multi_rockets) PRVM_DECLARE_field(ammo_nails) PRVM_DECLARE_field(ammo_nails1) PRVM_DECLARE_field(ammo_plasma) PRVM_DECLARE_field(ammo_rockets) PRVM_DECLARE_field(ammo_rockets1) PRVM_DECLARE_field(ammo_shells) PRVM_DECLARE_field(ammo_shells1) PRVM_DECLARE_field(angles) PRVM_DECLARE_field(armortype) PRVM_DECLARE_field(armorvalue) PRVM_DECLARE_field(avelocity) PRVM_DECLARE_field(blocked) PRVM_DECLARE_field(bouncefactor) PRVM_DECLARE_field(bouncestop) PRVM_DECLARE_field(button0) PRVM_DECLARE_field(button1) PRVM_DECLARE_field(button2) PRVM_DECLARE_field(button3) PRVM_DECLARE_field(button4) PRVM_DECLARE_field(button5) PRVM_DECLARE_field(button6) PRVM_DECLARE_field(button7) PRVM_DECLARE_field(button8) PRVM_DECLARE_field(button9) PRVM_DECLARE_field(button10) PRVM_DECLARE_field(button11) PRVM_DECLARE_field(button12) PRVM_DECLARE_field(button13) PRVM_DECLARE_field(button14) PRVM_DECLARE_field(button15) PRVM_DECLARE_field(button16) PRVM_DECLARE_field(buttonchat) PRVM_DECLARE_field(buttonuse) PRVM_DECLARE_field(camera_transform) PRVM_DECLARE_field(chain) PRVM_DECLARE_field(classname) PRVM_DECLARE_field(clientcamera) PRVM_DECLARE_field(clientcolors) PRVM_DECLARE_field(clientstatus) PRVM_DECLARE_field(color) PRVM_DECLARE_field(colormap) PRVM_DECLARE_field(colormod) PRVM_DECLARE_field(contentstransition) PRVM_DECLARE_field(crypto_encryptmethod) PRVM_DECLARE_field(crypto_idfp) PRVM_DECLARE_field(crypto_idfp_signed) PRVM_DECLARE_field(crypto_keyfp) PRVM_DECLARE_field(crypto_mykeyfp) PRVM_DECLARE_field(crypto_signmethod) PRVM_DECLARE_field(currentammo) PRVM_DECLARE_field(cursor_active) PRVM_DECLARE_field(cursor_screen) PRVM_DECLARE_field(cursor_trace_endpos) PRVM_DECLARE_field(cursor_trace_ent) PRVM_DECLARE_field(cursor_trace_start) PRVM_DECLARE_field(customizeentityforclient) PRVM_DECLARE_field(deadflag) PRVM_DECLARE_field(disableclientprediction) PRVM_DECLARE_field(discardabledemo) PRVM_DECLARE_field(dmg_inflictor) PRVM_DECLARE_field(dmg_save) PRVM_DECLARE_field(dmg_take) PRVM_DECLARE_field(dphitcontentsmask) PRVM_DECLARE_field(drawmask) PRVM_DECLARE_field(drawonlytoclient) PRVM_DECLARE_field(effects) PRVM_DECLARE_field(enemy) PRVM_DECLARE_field(entnum) PRVM_DECLARE_field(exteriormodeltoclient) PRVM_DECLARE_field(fixangle) PRVM_DECLARE_field(flags) PRVM_DECLARE_field(frags) PRVM_DECLARE_field(frame) PRVM_DECLARE_field(frame1time) PRVM_DECLARE_field(frame2) PRVM_DECLARE_field(frame2time) PRVM_DECLARE_field(frame3) PRVM_DECLARE_field(frame3time) PRVM_DECLARE_field(frame4) PRVM_DECLARE_field(frame4time) PRVM_DECLARE_field(fullbright) PRVM_DECLARE_field(glow_color) PRVM_DECLARE_field(glow_size) PRVM_DECLARE_field(glow_trail) PRVM_DECLARE_field(glowmod) PRVM_DECLARE_field(goalentity) PRVM_DECLARE_field(gravity) PRVM_DECLARE_field(groundentity) PRVM_DECLARE_field(health) PRVM_DECLARE_field(ideal_yaw) PRVM_DECLARE_field(idealpitch) PRVM_DECLARE_field(impulse) PRVM_DECLARE_field(items) PRVM_DECLARE_field(items2) PRVM_DECLARE_field(geomtype) PRVM_DECLARE_field(jointtype) PRVM_DECLARE_field(forcetype) PRVM_DECLARE_field(lerpfrac) PRVM_DECLARE_field(lerpfrac3) PRVM_DECLARE_field(lerpfrac4) PRVM_DECLARE_field(light_lev) PRVM_DECLARE_field(ltime) PRVM_DECLARE_field(mass) PRVM_DECLARE_field(massofs) PRVM_DECLARE_field(friction) PRVM_DECLARE_field(maxcontacts) PRVM_DECLARE_field(erp) PRVM_DECLARE_field(max_health) PRVM_DECLARE_field(maxs) PRVM_DECLARE_field(message) PRVM_DECLARE_field(mins) PRVM_DECLARE_field(model) PRVM_DECLARE_field(modelflags) PRVM_DECLARE_field(modelindex) PRVM_DECLARE_field(movedir) PRVM_DECLARE_field(movement) PRVM_DECLARE_field(movetype) PRVM_DECLARE_field(movetypesteplandevent) PRVM_DECLARE_field(netaddress) PRVM_DECLARE_field(netname) PRVM_DECLARE_field(nextthink) PRVM_DECLARE_field(nodrawtoclient) PRVM_DECLARE_field(noise) PRVM_DECLARE_field(noise1) PRVM_DECLARE_field(noise2) PRVM_DECLARE_field(noise3) PRVM_DECLARE_field(oldorigin) PRVM_DECLARE_field(origin) PRVM_DECLARE_field(owner) PRVM_DECLARE_field(pflags) PRVM_DECLARE_field(ping) PRVM_DECLARE_field(ping_movementloss) PRVM_DECLARE_field(ping_packetloss) PRVM_DECLARE_field(pitch_speed) PRVM_DECLARE_field(playermodel) PRVM_DECLARE_field(playerskin) PRVM_DECLARE_field(pmodel) PRVM_DECLARE_field(pmove_flags) PRVM_DECLARE_field(predraw) PRVM_DECLARE_field(punchangle) PRVM_DECLARE_field(punchvector) PRVM_DECLARE_field(renderamt) PRVM_DECLARE_field(renderflags) PRVM_DECLARE_field(scale) PRVM_DECLARE_field(modelscale_vec) PRVM_DECLARE_field(sendcomplexanimation) PRVM_DECLARE_field(shadertime) PRVM_DECLARE_field(size) PRVM_DECLARE_field(skeletonindex) PRVM_DECLARE_field(skin) PRVM_DECLARE_field(solid) PRVM_DECLARE_field(sounds) PRVM_DECLARE_field(spawnflags) PRVM_DECLARE_field(style) PRVM_DECLARE_field(tag_entity) PRVM_DECLARE_field(tag_index) PRVM_DECLARE_field(takedamage) PRVM_DECLARE_field(target) PRVM_DECLARE_field(targetname) PRVM_DECLARE_field(team) PRVM_DECLARE_field(teleport_time) PRVM_DECLARE_field(think) PRVM_DECLARE_field(touch) PRVM_DECLARE_field(traileffectnum) PRVM_DECLARE_field(use) PRVM_DECLARE_field(userwavefunc_param0) PRVM_DECLARE_field(userwavefunc_param1) PRVM_DECLARE_field(userwavefunc_param2) PRVM_DECLARE_field(userwavefunc_param3) PRVM_DECLARE_field(v_angle) PRVM_DECLARE_field(velocity) PRVM_DECLARE_field(modellight_ambient) PRVM_DECLARE_field(modellight_diffuse) PRVM_DECLARE_field(modellight_dir) PRVM_DECLARE_field(view_ofs) PRVM_DECLARE_field(viewmodelforclient) PRVM_DECLARE_field(viewzoom) PRVM_DECLARE_field(waterlevel) PRVM_DECLARE_field(watertype) PRVM_DECLARE_field(weapon) PRVM_DECLARE_field(weaponframe) PRVM_DECLARE_field(weaponmodel) PRVM_DECLARE_field(yaw_speed) PRVM_DECLARE_function(CSQC_ConsoleCommand) PRVM_DECLARE_function(CSQC_Ent_Remove) PRVM_DECLARE_function(CSQC_Ent_Spawn) PRVM_DECLARE_function(CSQC_Ent_Update) PRVM_DECLARE_function(CSQC_Event) PRVM_DECLARE_function(CSQC_Event_Sound) PRVM_DECLARE_function(CSQC_Init) PRVM_DECLARE_function(CSQC_InputEvent) PRVM_DECLARE_function(CSQC_Parse_CenterPrint) PRVM_DECLARE_function(CSQC_Parse_Print) PRVM_DECLARE_function(CSQC_Parse_StuffCmd) PRVM_DECLARE_function(CSQC_Parse_TempEntity) PRVM_DECLARE_function(CSQC_Shutdown) PRVM_DECLARE_function(CSQC_UpdateView) PRVM_DECLARE_function(ClientConnect) PRVM_DECLARE_function(ClientDisconnect) PRVM_DECLARE_function(ClientKill) PRVM_DECLARE_function(EndFrame) PRVM_DECLARE_function(GameCommand) PRVM_DECLARE_function(PlayerPostThink) PRVM_DECLARE_function(PlayerPreThink) PRVM_DECLARE_function(PutClientInServer) PRVM_DECLARE_function(RestoreGame) PRVM_DECLARE_function(SV_ChangeTeam) PRVM_DECLARE_function(SV_OnEntityNoSpawnFunction) PRVM_DECLARE_function(SV_OnEntityPostSpawnFunction) PRVM_DECLARE_function(SV_OnEntityPreSpawnFunction) PRVM_DECLARE_function(SV_ParseClientCommand) PRVM_DECLARE_function(SV_PausedTic) PRVM_DECLARE_function(SV_PlayerPhysics) PRVM_DECLARE_function(SV_Shutdown) PRVM_DECLARE_function(SetChangeParms) PRVM_DECLARE_function(SetNewParms) PRVM_DECLARE_function(StartFrame) PRVM_DECLARE_function(URI_Get_Callback) PRVM_DECLARE_function(m_draw) PRVM_DECLARE_function(m_init) PRVM_DECLARE_function(m_keydown) PRVM_DECLARE_function(m_keyup) PRVM_DECLARE_function(m_newmap) PRVM_DECLARE_function(m_gethostcachecategory) PRVM_DECLARE_function(m_shutdown) PRVM_DECLARE_function(m_toggle) PRVM_DECLARE_function(main) PRVM_DECLARE_global(SV_InitCmd) PRVM_DECLARE_global(clientcommandframe) PRVM_DECLARE_global(cltime) PRVM_DECLARE_global(coop) PRVM_DECLARE_global(deathmatch) PRVM_DECLARE_global(dmg_origin) PRVM_DECLARE_global(dmg_save) PRVM_DECLARE_global(dmg_take) PRVM_DECLARE_global(drawfont) PRVM_DECLARE_global(drawfontscale) PRVM_DECLARE_global(force_retouch) PRVM_DECLARE_global(found_secrets) PRVM_DECLARE_global(frametime) PRVM_DECLARE_global(gettaginfo_forward) PRVM_DECLARE_global(gettaginfo_name) PRVM_DECLARE_global(gettaginfo_offset) PRVM_DECLARE_global(gettaginfo_parent) PRVM_DECLARE_global(gettaginfo_right) PRVM_DECLARE_global(gettaginfo_up) PRVM_DECLARE_global(getlight_ambient) PRVM_DECLARE_global(getlight_diffuse) PRVM_DECLARE_global(getlight_dir) PRVM_DECLARE_global(input_angles) PRVM_DECLARE_global(input_buttons) PRVM_DECLARE_global(input_movevalues) PRVM_DECLARE_global(input_timelength) PRVM_DECLARE_global(intermission) PRVM_DECLARE_global(killed_monsters) PRVM_DECLARE_global(mapname) PRVM_DECLARE_global(maxclients) PRVM_DECLARE_global(movevar_accelerate) PRVM_DECLARE_global(movevar_airaccelerate) PRVM_DECLARE_global(movevar_entgravity) PRVM_DECLARE_global(movevar_friction) PRVM_DECLARE_global(movevar_gravity) PRVM_DECLARE_global(movevar_maxspeed) PRVM_DECLARE_global(movevar_spectatormaxspeed) PRVM_DECLARE_global(movevar_stopspeed) PRVM_DECLARE_global(movevar_wateraccelerate) PRVM_DECLARE_global(movevar_waterfriction) PRVM_DECLARE_global(msg_entity) PRVM_DECLARE_global(other) PRVM_DECLARE_global(parm1) PRVM_DECLARE_global(parm2) PRVM_DECLARE_global(parm3) PRVM_DECLARE_global(parm4) PRVM_DECLARE_global(parm5) PRVM_DECLARE_global(parm6) PRVM_DECLARE_global(parm7) PRVM_DECLARE_global(parm8) PRVM_DECLARE_global(parm9) PRVM_DECLARE_global(parm10) PRVM_DECLARE_global(parm11) PRVM_DECLARE_global(parm12) PRVM_DECLARE_global(parm13) PRVM_DECLARE_global(parm14) PRVM_DECLARE_global(parm15) PRVM_DECLARE_global(parm16) PRVM_DECLARE_global(particle_airfriction) PRVM_DECLARE_global(particle_alpha) PRVM_DECLARE_global(particle_alphafade) PRVM_DECLARE_global(particle_angle) PRVM_DECLARE_global(particle_blendmode) PRVM_DECLARE_global(particle_bounce) PRVM_DECLARE_global(particle_color1) PRVM_DECLARE_global(particle_color2) PRVM_DECLARE_global(particle_delaycollision) PRVM_DECLARE_global(particle_delayspawn) PRVM_DECLARE_global(particle_gravity) PRVM_DECLARE_global(particle_liquidfriction) PRVM_DECLARE_global(particle_orientation) PRVM_DECLARE_global(particle_originjitter) PRVM_DECLARE_global(particle_qualityreduction) PRVM_DECLARE_global(particle_size) PRVM_DECLARE_global(particle_sizeincrease) PRVM_DECLARE_global(particle_spin) PRVM_DECLARE_global(particle_stainalpha) PRVM_DECLARE_global(particle_staincolor1) PRVM_DECLARE_global(particle_staincolor2) PRVM_DECLARE_global(particle_stainsize) PRVM_DECLARE_global(particle_staintex) PRVM_DECLARE_global(particle_stretch) PRVM_DECLARE_global(particle_tex) PRVM_DECLARE_global(particle_time) PRVM_DECLARE_global(particle_type) PRVM_DECLARE_global(particle_velocityjitter) PRVM_DECLARE_global(particles_alphamax) PRVM_DECLARE_global(particles_alphamin) PRVM_DECLARE_global(particles_fade) PRVM_DECLARE_global(particles_colormax) PRVM_DECLARE_global(particles_colormin) PRVM_DECLARE_global(player_localentnum) PRVM_DECLARE_global(player_localnum) PRVM_DECLARE_global(pmove_inwater) PRVM_DECLARE_global(pmove_maxs) PRVM_DECLARE_global(pmove_mins) PRVM_DECLARE_global(pmove_onground) PRVM_DECLARE_global(pmove_waterjumptime) PRVM_DECLARE_global(pmove_jump_held) PRVM_DECLARE_global(pmove_org) PRVM_DECLARE_global(pmove_vel) PRVM_DECLARE_global(require_spawnfunc_prefix) PRVM_DECLARE_global(sb_showscores) PRVM_DECLARE_global(self) PRVM_DECLARE_global(servercommandframe) PRVM_DECLARE_global(serverdeltatime) PRVM_DECLARE_global(serverflags) PRVM_DECLARE_global(serverprevtime) PRVM_DECLARE_global(servertime) PRVM_DECLARE_global(teamplay) PRVM_DECLARE_global(time) PRVM_DECLARE_global(total_monsters) PRVM_DECLARE_global(total_secrets) PRVM_DECLARE_global(trace_allsolid) PRVM_DECLARE_global(trace_dphitcontents) PRVM_DECLARE_global(trace_dphitq3surfaceflags) PRVM_DECLARE_global(trace_dphittexturename) PRVM_DECLARE_global(trace_dpstartcontents) PRVM_DECLARE_global(trace_endpos) PRVM_DECLARE_global(trace_ent) PRVM_DECLARE_global(trace_fraction) PRVM_DECLARE_global(trace_inopen) PRVM_DECLARE_global(trace_inwater) PRVM_DECLARE_global(trace_networkentity) PRVM_DECLARE_global(trace_plane_dist) PRVM_DECLARE_global(trace_plane_normal) PRVM_DECLARE_global(trace_startsolid) PRVM_DECLARE_global(transparent_offset) PRVM_DECLARE_global(v_forward) PRVM_DECLARE_global(v_right) PRVM_DECLARE_global(v_up) PRVM_DECLARE_global(view_angles) PRVM_DECLARE_global(view_punchangle) PRVM_DECLARE_global(view_punchvector) PRVM_DECLARE_global(world) PRVM_DECLARE_global(worldstatus) PRVM_DECLARE_global(sound_starttime) PRVM_DECLARE_menufieldstring(classname) PRVM_DECLARE_menufunction(GameCommand) PRVM_DECLARE_menufunction(URI_Get_Callback) PRVM_DECLARE_menufunction(m_draw) PRVM_DECLARE_menufunction(m_init) PRVM_DECLARE_menufunction(m_keydown) PRVM_DECLARE_menufunction(m_keyup) PRVM_DECLARE_menufunction(m_newmap) PRVM_DECLARE_menufunction(m_shutdown) PRVM_DECLARE_menufunction(m_toggle) PRVM_DECLARE_menuglobaledict(self) PRVM_DECLARE_menuglobalfloat(drawfont) PRVM_DECLARE_menuglobalfloat(require_spawnfunc_prefix) PRVM_DECLARE_menuglobalvector(drawfontscale) PRVM_DECLARE_serverfieldedict(aiment) PRVM_DECLARE_serverfieldedict(chain) PRVM_DECLARE_serverfieldedict(clientcamera) PRVM_DECLARE_serverfieldedict(cursor_trace_ent) PRVM_DECLARE_serverfieldedict(dmg_inflictor) PRVM_DECLARE_serverfieldedict(drawonlytoclient) PRVM_DECLARE_serverfieldedict(enemy) PRVM_DECLARE_serverfieldedict(exteriormodeltoclient) PRVM_DECLARE_serverfieldedict(goalentity) PRVM_DECLARE_serverfieldedict(groundentity) PRVM_DECLARE_serverfieldedict(nodrawtoclient) PRVM_DECLARE_serverfieldedict(owner) PRVM_DECLARE_serverfieldedict(tag_entity) PRVM_DECLARE_serverfieldedict(viewmodelforclient) PRVM_DECLARE_serverfieldfloat(SendFlags) PRVM_DECLARE_serverfieldfloat(Version) PRVM_DECLARE_serverfieldfloat(alpha) PRVM_DECLARE_serverfieldfloat(ammo_cells) PRVM_DECLARE_serverfieldfloat(ammo_cells1) PRVM_DECLARE_serverfieldfloat(ammo_lava_nails) PRVM_DECLARE_serverfieldfloat(ammo_multi_rockets) PRVM_DECLARE_serverfieldfloat(ammo_nails) PRVM_DECLARE_serverfieldfloat(ammo_nails1) PRVM_DECLARE_serverfieldfloat(ammo_plasma) PRVM_DECLARE_serverfieldfloat(ammo_rockets) PRVM_DECLARE_serverfieldfloat(ammo_rockets1) PRVM_DECLARE_serverfieldfloat(ammo_shells) PRVM_DECLARE_serverfieldfloat(ammo_shells1) PRVM_DECLARE_serverfieldfloat(armortype) PRVM_DECLARE_serverfieldfloat(armorvalue) PRVM_DECLARE_serverfieldfloat(bouncefactor) PRVM_DECLARE_serverfieldfloat(bouncestop) PRVM_DECLARE_serverfieldfloat(button0) PRVM_DECLARE_serverfieldfloat(button1) PRVM_DECLARE_serverfieldfloat(button2) PRVM_DECLARE_serverfieldfloat(button3) PRVM_DECLARE_serverfieldfloat(button4) PRVM_DECLARE_serverfieldfloat(button5) PRVM_DECLARE_serverfieldfloat(button6) PRVM_DECLARE_serverfieldfloat(button7) PRVM_DECLARE_serverfieldfloat(button8) PRVM_DECLARE_serverfieldfloat(button9) PRVM_DECLARE_serverfieldfloat(button10) PRVM_DECLARE_serverfieldfloat(button11) PRVM_DECLARE_serverfieldfloat(button12) PRVM_DECLARE_serverfieldfloat(button13) PRVM_DECLARE_serverfieldfloat(button14) PRVM_DECLARE_serverfieldfloat(button15) PRVM_DECLARE_serverfieldfloat(button16) PRVM_DECLARE_serverfieldfloat(buttonchat) PRVM_DECLARE_serverfieldfloat(buttonuse) PRVM_DECLARE_serverfieldfloat(clientcolors) PRVM_DECLARE_serverfieldfloat(colormap) PRVM_DECLARE_serverfieldfloat(currentammo) PRVM_DECLARE_serverfieldfloat(cursor_active) PRVM_DECLARE_serverfieldfloat(deadflag) PRVM_DECLARE_serverfieldfloat(disableclientprediction) PRVM_DECLARE_serverfieldfloat(discardabledemo) PRVM_DECLARE_serverfieldfloat(dmg_save) PRVM_DECLARE_serverfieldfloat(dmg_take) PRVM_DECLARE_serverfieldfloat(dphitcontentsmask) PRVM_DECLARE_serverfieldfloat(effects) PRVM_DECLARE_serverfieldfloat(fixangle) PRVM_DECLARE_serverfieldfloat(flags) PRVM_DECLARE_serverfieldfloat(frags) PRVM_DECLARE_serverfieldfloat(frame) PRVM_DECLARE_serverfieldfloat(frame1time) PRVM_DECLARE_serverfieldfloat(frame2) PRVM_DECLARE_serverfieldfloat(frame2time) PRVM_DECLARE_serverfieldfloat(frame3) PRVM_DECLARE_serverfieldfloat(frame3time) PRVM_DECLARE_serverfieldfloat(frame4) PRVM_DECLARE_serverfieldfloat(frame4time) PRVM_DECLARE_serverfieldfloat(fullbright) PRVM_DECLARE_serverfieldfloat(glow_color) PRVM_DECLARE_serverfieldfloat(glow_size) PRVM_DECLARE_serverfieldfloat(glow_trail) PRVM_DECLARE_serverfieldfloat(gravity) PRVM_DECLARE_serverfieldfloat(health) PRVM_DECLARE_serverfieldfloat(ideal_yaw) PRVM_DECLARE_serverfieldfloat(idealpitch) PRVM_DECLARE_serverfieldfloat(impulse) PRVM_DECLARE_serverfieldfloat(items) PRVM_DECLARE_serverfieldfloat(items2) PRVM_DECLARE_serverfieldfloat(geomtype) PRVM_DECLARE_serverfieldfloat(jointtype) PRVM_DECLARE_serverfieldfloat(forcetype) PRVM_DECLARE_serverfieldfloat(lerpfrac) PRVM_DECLARE_serverfieldfloat(lerpfrac3) PRVM_DECLARE_serverfieldfloat(lerpfrac4) PRVM_DECLARE_serverfieldfloat(light_lev) PRVM_DECLARE_serverfieldfloat(ltime) PRVM_DECLARE_serverfieldfloat(mass) PRVM_DECLARE_serverfieldvector(massofs) PRVM_DECLARE_serverfieldfloat(friction) PRVM_DECLARE_serverfieldfloat(maxcontacts) PRVM_DECLARE_serverfieldfloat(erp) PRVM_DECLARE_serverfieldfloat(max_health) PRVM_DECLARE_serverfieldfloat(modelflags) PRVM_DECLARE_serverfieldfloat(modelindex) PRVM_DECLARE_serverfieldfloat(movetype) PRVM_DECLARE_serverfieldfloat(nextthink) PRVM_DECLARE_serverfieldfloat(pflags) PRVM_DECLARE_serverfieldfloat(ping) PRVM_DECLARE_serverfieldfloat(ping_movementloss) PRVM_DECLARE_serverfieldfloat(ping_packetloss) PRVM_DECLARE_serverfieldfloat(pitch_speed) PRVM_DECLARE_serverfieldfloat(pmodel) PRVM_DECLARE_serverfieldfloat(renderamt) PRVM_DECLARE_serverfieldfloat(scale) PRVM_DECLARE_serverfieldvector(modelscale_vec) PRVM_DECLARE_serverfieldfloat(sendcomplexanimation) PRVM_DECLARE_serverfieldfloat(skeletonindex) PRVM_DECLARE_serverfieldfloat(skin) PRVM_DECLARE_serverfieldfloat(solid) PRVM_DECLARE_serverfieldfloat(sounds) PRVM_DECLARE_serverfieldfloat(spawnflags) PRVM_DECLARE_serverfieldfloat(style) PRVM_DECLARE_serverfieldfloat(tag_index) PRVM_DECLARE_serverfieldfloat(takedamage) PRVM_DECLARE_serverfieldfloat(team) PRVM_DECLARE_serverfieldfloat(teleport_time) PRVM_DECLARE_serverfieldfloat(traileffectnum) PRVM_DECLARE_serverfieldfloat(viewzoom) PRVM_DECLARE_serverfieldfloat(waterlevel) PRVM_DECLARE_serverfieldfloat(watertype) PRVM_DECLARE_serverfieldfloat(weapon) PRVM_DECLARE_serverfieldfloat(weaponframe) PRVM_DECLARE_serverfieldfloat(yaw_speed) PRVM_DECLARE_serverfieldfunction(SendEntity) PRVM_DECLARE_serverfieldfunction(blocked) PRVM_DECLARE_serverfieldfunction(camera_transform) PRVM_DECLARE_serverfieldfunction(contentstransition) PRVM_DECLARE_serverfieldfunction(customizeentityforclient) PRVM_DECLARE_serverfieldfunction(movetypesteplandevent) PRVM_DECLARE_serverfieldfunction(think) PRVM_DECLARE_serverfieldfunction(touch) PRVM_DECLARE_serverfieldfunction(use) PRVM_DECLARE_serverfieldstring(classname) PRVM_DECLARE_serverfieldstring(clientstatus) PRVM_DECLARE_serverfieldstring(crypto_encryptmethod) PRVM_DECLARE_serverfieldstring(crypto_idfp) PRVM_DECLARE_serverfieldfloat(crypto_idfp_signed) PRVM_DECLARE_serverfieldstring(crypto_keyfp) PRVM_DECLARE_serverfieldstring(crypto_mykeyfp) PRVM_DECLARE_serverfieldstring(crypto_signmethod) PRVM_DECLARE_serverfieldstring(message) PRVM_DECLARE_serverfieldstring(model) PRVM_DECLARE_serverfieldstring(netaddress) PRVM_DECLARE_serverfieldstring(netname) PRVM_DECLARE_serverfieldstring(noise) PRVM_DECLARE_serverfieldstring(noise1) PRVM_DECLARE_serverfieldstring(noise2) PRVM_DECLARE_serverfieldstring(noise3) PRVM_DECLARE_serverfieldstring(playermodel) PRVM_DECLARE_serverfieldstring(playerskin) PRVM_DECLARE_serverfieldstring(target) PRVM_DECLARE_serverfieldstring(targetname) PRVM_DECLARE_serverfieldstring(weaponmodel) PRVM_DECLARE_serverfieldvector(absmax) PRVM_DECLARE_serverfieldvector(absmin) PRVM_DECLARE_serverfieldvector(angles) PRVM_DECLARE_serverfieldvector(avelocity) PRVM_DECLARE_serverfieldvector(color) PRVM_DECLARE_serverfieldvector(colormod) PRVM_DECLARE_serverfieldvector(cursor_screen) PRVM_DECLARE_serverfieldvector(cursor_trace_endpos) PRVM_DECLARE_serverfieldvector(cursor_trace_start) PRVM_DECLARE_serverfieldvector(glowmod) PRVM_DECLARE_serverfieldvector(maxs) PRVM_DECLARE_serverfieldvector(mins) PRVM_DECLARE_serverfieldvector(movedir) PRVM_DECLARE_serverfieldvector(movement) PRVM_DECLARE_serverfieldvector(oldorigin) PRVM_DECLARE_serverfieldvector(origin) PRVM_DECLARE_serverfieldvector(punchangle) PRVM_DECLARE_serverfieldvector(punchvector) PRVM_DECLARE_serverfieldvector(size) PRVM_DECLARE_serverfieldvector(v_angle) PRVM_DECLARE_serverfieldvector(velocity) PRVM_DECLARE_serverfieldvector(view_ofs) PRVM_DECLARE_serverfunction(ClientConnect) PRVM_DECLARE_serverfunction(ClientDisconnect) PRVM_DECLARE_serverfunction(ClientKill) PRVM_DECLARE_serverfunction(EndFrame) PRVM_DECLARE_serverfunction(GameCommand) PRVM_DECLARE_serverfunction(PlayerPostThink) PRVM_DECLARE_serverfunction(PlayerPreThink) PRVM_DECLARE_serverfunction(PutClientInServer) PRVM_DECLARE_serverfunction(RestoreGame) PRVM_DECLARE_serverfunction(SV_ChangeTeam) PRVM_DECLARE_serverfunction(SV_OnEntityNoSpawnFunction) PRVM_DECLARE_serverfunction(SV_OnEntityPostSpawnFunction) PRVM_DECLARE_serverfunction(SV_OnEntityPreSpawnFunction) PRVM_DECLARE_serverfunction(SV_ParseClientCommand) PRVM_DECLARE_serverfunction(SV_PausedTic) PRVM_DECLARE_serverfunction(SV_PlayerPhysics) PRVM_DECLARE_serverfunction(SV_Shutdown) PRVM_DECLARE_serverfunction(SetChangeParms) PRVM_DECLARE_serverfunction(SetNewParms) PRVM_DECLARE_serverfunction(StartFrame) PRVM_DECLARE_serverfunction(URI_Get_Callback) PRVM_DECLARE_serverfunction(main) PRVM_DECLARE_serverglobaledict(msg_entity) PRVM_DECLARE_serverglobaledict(other) PRVM_DECLARE_serverglobaledict(self) PRVM_DECLARE_serverglobaledict(trace_ent) PRVM_DECLARE_serverglobaledict(world) PRVM_DECLARE_serverglobalfloat(coop) PRVM_DECLARE_serverglobalfloat(deathmatch) PRVM_DECLARE_serverglobalfloat(force_retouch) PRVM_DECLARE_serverglobalfloat(found_secrets) PRVM_DECLARE_serverglobalfloat(frametime) PRVM_DECLARE_serverglobalfloat(gettaginfo_parent) PRVM_DECLARE_serverglobalfloat(killed_monsters) PRVM_DECLARE_serverglobalfloat(parm1) PRVM_DECLARE_serverglobalfloat(parm2) PRVM_DECLARE_serverglobalfloat(parm3) PRVM_DECLARE_serverglobalfloat(parm4) PRVM_DECLARE_serverglobalfloat(parm5) PRVM_DECLARE_serverglobalfloat(parm6) PRVM_DECLARE_serverglobalfloat(parm7) PRVM_DECLARE_serverglobalfloat(parm8) PRVM_DECLARE_serverglobalfloat(parm9) PRVM_DECLARE_serverglobalfloat(parm10) PRVM_DECLARE_serverglobalfloat(parm11) PRVM_DECLARE_serverglobalfloat(parm12) PRVM_DECLARE_serverglobalfloat(parm13) PRVM_DECLARE_serverglobalfloat(parm14) PRVM_DECLARE_serverglobalfloat(parm15) PRVM_DECLARE_serverglobalfloat(parm16) PRVM_DECLARE_serverglobalfloat(require_spawnfunc_prefix) PRVM_DECLARE_serverglobalfloat(serverflags) PRVM_DECLARE_serverglobalfloat(teamplay) PRVM_DECLARE_serverglobalfloat(time) PRVM_DECLARE_serverglobalfloat(total_monsters) PRVM_DECLARE_serverglobalfloat(total_secrets) PRVM_DECLARE_serverglobalfloat(trace_allsolid) PRVM_DECLARE_serverglobalfloat(trace_dphitcontents) PRVM_DECLARE_serverglobalfloat(trace_dphitq3surfaceflags) PRVM_DECLARE_serverglobalfloat(trace_dpstartcontents) PRVM_DECLARE_serverglobalfloat(trace_fraction) PRVM_DECLARE_serverglobalfloat(trace_inopen) PRVM_DECLARE_serverglobalfloat(trace_inwater) PRVM_DECLARE_serverglobalfloat(trace_plane_dist) PRVM_DECLARE_serverglobalfloat(trace_startsolid) PRVM_DECLARE_serverglobalstring(SV_InitCmd) PRVM_DECLARE_serverglobalstring(gettaginfo_name) PRVM_DECLARE_serverglobalstring(mapname) PRVM_DECLARE_serverglobalstring(trace_dphittexturename) PRVM_DECLARE_serverglobalstring(worldstatus) PRVM_DECLARE_serverglobalvector(gettaginfo_forward) PRVM_DECLARE_serverglobalvector(gettaginfo_offset) PRVM_DECLARE_serverglobalvector(gettaginfo_right) PRVM_DECLARE_serverglobalvector(gettaginfo_up) PRVM_DECLARE_serverglobalvector(trace_endpos) PRVM_DECLARE_serverglobalvector(trace_plane_normal) PRVM_DECLARE_serverglobalvector(v_forward) PRVM_DECLARE_serverglobalvector(v_right) PRVM_DECLARE_serverglobalvector(v_up) darkplaces/dpsoftrast.c0000664000175000017500000073471413067716220014540 0ustar kalevkalev#include #include #define _USE_MATH_DEFINES #include #include "quakedef.h" #include "thread.h" #include "dpsoftrast.h" #ifdef _MSC_VER #pragma warning(disable : 4324) #endif #ifndef __cplusplus typedef qboolean bool; #endif #define ALIGN_SIZE 16 #define ATOMIC_SIZE 4 #ifdef SSE_POSSIBLE #if defined(__APPLE__) #include #define ALIGN(var) var __attribute__((__aligned__(16))) #define ATOMIC(var) var __attribute__((__aligned__(4))) #define MEMORY_BARRIER (_mm_sfence()) #define ATOMIC_COUNTER volatile int32_t #define ATOMIC_INCREMENT(counter) (OSAtomicIncrement32Barrier(&(counter))) #define ATOMIC_DECREMENT(counter) (OSAtomicDecrement32Barrier(&(counter))) #define ATOMIC_ADD(counter, val) ((void)OSAtomicAdd32Barrier((val), &(counter))) #elif defined(__GNUC__) && defined(WIN32) #define ALIGN(var) var __attribute__((__aligned__(16))) #define ATOMIC(var) var __attribute__((__aligned__(4))) #define MEMORY_BARRIER (_mm_sfence()) //(__sync_synchronize()) #define ATOMIC_COUNTER volatile LONG // this LONG * cast serves to fix an issue with broken mingw // packages on Ubuntu; these only declare the function to take // a LONG *, causing a compile error here. This seems to be // error- and warn-free on platforms that DO declare // InterlockedIncrement correctly, like mingw on Windows. #define ATOMIC_INCREMENT(counter) (InterlockedIncrement((LONG *) &(counter))) #define ATOMIC_DECREMENT(counter) (InterlockedDecrement((LONG *) &(counter))) #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd((LONG *) &(counter), (val))) #elif defined(__GNUC__) #define ALIGN(var) var __attribute__((__aligned__(16))) #define ATOMIC(var) var __attribute__((__aligned__(4))) #define MEMORY_BARRIER (_mm_sfence()) //(__sync_synchronize()) #define ATOMIC_COUNTER volatile int #define ATOMIC_INCREMENT(counter) (__sync_add_and_fetch(&(counter), 1)) #define ATOMIC_DECREMENT(counter) (__sync_add_and_fetch(&(counter), -1)) #define ATOMIC_ADD(counter, val) ((void)__sync_fetch_and_add(&(counter), (val))) #elif defined(_MSC_VER) #define ALIGN(var) __declspec(align(16)) var #define ATOMIC(var) __declspec(align(4)) var #define MEMORY_BARRIER (_mm_sfence()) //(MemoryBarrier()) #define ATOMIC_COUNTER volatile LONG #define ATOMIC_INCREMENT(counter) (InterlockedIncrement(&(counter))) #define ATOMIC_DECREMENT(counter) (InterlockedDecrement(&(counter))) #define ATOMIC_ADD(counter, val) ((void)InterlockedExchangeAdd(&(counter), (val))) #endif #endif #ifndef ALIGN #define ALIGN(var) var #endif #ifndef ATOMIC #define ATOMIC(var) var #endif #ifndef MEMORY_BARRIER #define MEMORY_BARRIER ((void)0) #endif #ifndef ATOMIC_COUNTER #define ATOMIC_COUNTER int #endif #ifndef ATOMIC_INCREMENT #define ATOMIC_INCREMENT(counter) (++(counter)) #endif #ifndef ATOMIC_DECREMENT #define ATOMIC_DECREMENT(counter) (--(counter)) #endif #ifndef ATOMIC_ADD #define ATOMIC_ADD(counter, val) ((void)((counter) += (val))) #endif #ifdef SSE_POSSIBLE #include #if defined(__GNUC__) && (__GNUC < 4 || __GNUC_MINOR__ < 6) && !defined(__clang__) #define _mm_cvtss_f32(val) (__builtin_ia32_vec_ext_v4sf ((__v4sf)(val), 0)) #endif #define MM_MALLOC(size) _mm_malloc(size, ALIGN_SIZE) static void *MM_CALLOC(size_t nmemb, size_t size) { void *ptr = _mm_malloc(nmemb*size, ALIGN_SIZE); if (ptr != NULL) memset(ptr, 0, nmemb*size); return ptr; } #define MM_FREE _mm_free #else #define MM_MALLOC(size) malloc(size) #define MM_CALLOC(nmemb, size) calloc(nmemb, size) #define MM_FREE free #endif typedef enum DPSOFTRAST_ARRAY_e { DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, DPSOFTRAST_ARRAY_TEXCOORD7, DPSOFTRAST_ARRAY_TOTAL } DPSOFTRAST_ARRAY; typedef struct DPSOFTRAST_Texture_s { int flags; int width; int height; int depth; int sides; DPSOFTRAST_TEXTURE_FILTER filter; int mipmaps; int size; ATOMIC_COUNTER binds; unsigned char *bytes; int mipmap[DPSOFTRAST_MAXMIPMAPS][5]; } DPSOFTRAST_Texture; #define COMMAND_SIZE ALIGN_SIZE #define COMMAND_ALIGN(var) ALIGN(var) typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_s { unsigned char opcode; unsigned short commandsize; } DPSOFTRAST_Command); enum { DPSOFTRAST_OPCODE_Reset = 0 }; #define DEFCOMMAND(opcodeval, name, fields) \ enum { DPSOFTRAST_OPCODE_##name = opcodeval }; \ typedef COMMAND_ALIGN(struct DPSOFTRAST_Command_##name##_s \ { \ unsigned char opcode; \ unsigned short commandsize; \ fields \ } DPSOFTRAST_Command_##name ); #define DPSOFTRAST_DRAW_MAXCOMMANDPOOL 2097152 #define DPSOFTRAST_DRAW_MAXCOMMANDSIZE 16384 typedef ALIGN(struct DPSOFTRAST_State_Command_Pool_s { int freecommand; int usedcommands; ALIGN(unsigned char commands[DPSOFTRAST_DRAW_MAXCOMMANDPOOL]); } DPSOFTRAST_State_Command_Pool); typedef ALIGN(struct DPSOFTRAST_State_Triangle_s { unsigned char mip[DPSOFTRAST_MAXTEXTUREUNITS]; // texcoord to screen space density values (for picking mipmap of textures) float w[3]; ALIGN(float attribs[DPSOFTRAST_ARRAY_TOTAL][3][4]); } DPSOFTRAST_State_Triangle); #define DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex) { \ slope = _mm_load_ps((triangle)->attribs[arrayindex][0]); \ data = _mm_add_ps(_mm_load_ps((triangle)->attribs[arrayindex][2]), \ _mm_add_ps(_mm_mul_ps(_mm_set1_ps((span)->x), slope), \ _mm_mul_ps(_mm_set1_ps((span)->y), _mm_load_ps((triangle)->attribs[arrayindex][1])))); \ } #define DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex) { \ slope[0] = (triangle)->attribs[arrayindex][0][0]; \ slope[1] = (triangle)->attribs[arrayindex][0][1]; \ slope[2] = (triangle)->attribs[arrayindex][0][2]; \ slope[3] = (triangle)->attribs[arrayindex][0][3]; \ data[0] = (triangle)->attribs[arrayindex][2][0] + (span->x)*slope[0] + (span->y)*(triangle)->attribs[arrayindex][1][0]; \ data[1] = (triangle)->attribs[arrayindex][2][1] + (span->x)*slope[1] + (span->y)*(triangle)->attribs[arrayindex][1][1]; \ data[2] = (triangle)->attribs[arrayindex][2][2] + (span->x)*slope[2] + (span->y)*(triangle)->attribs[arrayindex][1][2]; \ data[3] = (triangle)->attribs[arrayindex][2][3] + (span->x)*slope[3] + (span->y)*(triangle)->attribs[arrayindex][1][3]; \ } #define DPSOFTRAST_DRAW_MAXSUBSPAN 16 typedef ALIGN(struct DPSOFTRAST_State_Span_s { int triangle; // triangle this span was generated by int x; // framebuffer x coord int y; // framebuffer y coord int startx; // usable range (according to pixelmask) int endx; // usable range (according to pixelmask) unsigned char *pixelmask; // true for pixels that passed depth test, false for others int depthbase; // depthbuffer value at x (add depthslope*startx to get first pixel's depthbuffer value) int depthslope; // depthbuffer value pixel delta } DPSOFTRAST_State_Span); #define DPSOFTRAST_DRAW_MAXSPANS 1024 #define DPSOFTRAST_DRAW_MAXTRIANGLES 128 #define DPSOFTRAST_DRAW_MAXSPANLENGTH 256 #define DPSOFTRAST_VALIDATE_FB 1 #define DPSOFTRAST_VALIDATE_DEPTHFUNC 2 #define DPSOFTRAST_VALIDATE_BLENDFUNC 4 #define DPSOFTRAST_VALIDATE_DRAW (DPSOFTRAST_VALIDATE_FB | DPSOFTRAST_VALIDATE_DEPTHFUNC | DPSOFTRAST_VALIDATE_BLENDFUNC) typedef enum DPSOFTRAST_BLENDMODE_e { DPSOFTRAST_BLENDMODE_OPAQUE, DPSOFTRAST_BLENDMODE_ALPHA, DPSOFTRAST_BLENDMODE_ADDALPHA, DPSOFTRAST_BLENDMODE_ADD, DPSOFTRAST_BLENDMODE_INVMOD, DPSOFTRAST_BLENDMODE_MUL, DPSOFTRAST_BLENDMODE_MUL2, DPSOFTRAST_BLENDMODE_SUBALPHA, DPSOFTRAST_BLENDMODE_PSEUDOALPHA, DPSOFTRAST_BLENDMODE_INVADD, DPSOFTRAST_BLENDMODE_TOTAL } DPSOFTRAST_BLENDMODE; typedef ALIGN(struct DPSOFTRAST_State_Thread_s { void *thread; int index; int cullface; int colormask[4]; int blendfunc[2]; int blendsubtract; int depthmask; int depthtest; int depthfunc; int scissortest; int viewport[4]; int scissor[4]; float depthrange[2]; float polygonoffset[2]; float clipplane[4]; ALIGN(float fb_clipplane[4]); int shader_mode; int shader_permutation; int shader_exactspecularmath; DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; // DPSOFTRAST_VALIDATE_ flags int validate; // derived values (DPSOFTRAST_VALIDATE_FB) int fb_colormask; int fb_scissor[4]; ALIGN(float fb_viewportcenter[4]); ALIGN(float fb_viewportscale[4]); // derived values (DPSOFTRAST_VALIDATE_DEPTHFUNC) int fb_depthfunc; // derived values (DPSOFTRAST_VALIDATE_BLENDFUNC) int fb_blendmode; // band boundaries int miny1; int maxy1; int miny2; int maxy2; ATOMIC(volatile int commandoffset); volatile bool waiting; volatile bool starving; void *waitcond; void *drawcond; void *drawmutex; int numspans; int numtriangles; DPSOFTRAST_State_Span spans[DPSOFTRAST_DRAW_MAXSPANS]; DPSOFTRAST_State_Triangle triangles[DPSOFTRAST_DRAW_MAXTRIANGLES]; unsigned char pixelmaskarray[DPSOFTRAST_DRAW_MAXSPANLENGTH+4]; // LordHavoc: padded to allow some termination bytes } DPSOFTRAST_State_Thread); typedef ALIGN(struct DPSOFTRAST_State_s { int fb_width; int fb_height; unsigned int *fb_depthpixels; unsigned int *fb_colorpixels[4]; int viewport[4]; ALIGN(float fb_viewportcenter[4]); ALIGN(float fb_viewportscale[4]); float color[4]; ALIGN(float uniform4f[DPSOFTRAST_UNIFORM_TOTAL*4]); int uniform1i[DPSOFTRAST_UNIFORM_TOTAL]; const float *pointer_vertex3f; const float *pointer_color4f; const unsigned char *pointer_color4ub; const float *pointer_texcoordf[DPSOFTRAST_MAXTEXCOORDARRAYS]; int stride_vertex; int stride_color; int stride_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; int components_texcoord[DPSOFTRAST_MAXTEXCOORDARRAYS]; DPSOFTRAST_Texture *texbound[DPSOFTRAST_MAXTEXTUREUNITS]; int firstvertex; int numvertices; float *post_array4f[DPSOFTRAST_ARRAY_TOTAL]; float *screencoord4f; int drawstarty; int drawendy; int drawclipped; int shader_mode; int shader_permutation; int shader_exactspecularmath; int texture_max; int texture_end; int texture_firstfree; DPSOFTRAST_Texture *texture; int bigendian; // error reporting const char *errorstring; bool usethreads; int interlace; int numthreads; DPSOFTRAST_State_Thread *threads; ATOMIC(volatile int drawcommand); DPSOFTRAST_State_Command_Pool commandpool; } DPSOFTRAST_State); DPSOFTRAST_State dpsoftrast; #define DPSOFTRAST_DEPTHSCALE (1024.0f*1048576.0f) #define DPSOFTRAST_DEPTHOFFSET (128.0f) #define DPSOFTRAST_BGRA8_FROM_RGBA32F(r,g,b,a) (((int)(r * 255.0f + 0.5f) << 16) | ((int)(g * 255.0f + 0.5f) << 8) | (int)(b * 255.0f + 0.5f) | ((int)(a * 255.0f + 0.5f) << 24)) #define DPSOFTRAST_DEPTH32_FROM_DEPTH32F(d) ((int)(DPSOFTRAST_DEPTHSCALE * (1-d))) static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span); static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span); static void DPSOFTRAST_RecalcViewport(const int *viewport, float *fb_viewportcenter, float *fb_viewportscale) { fb_viewportcenter[1] = viewport[0] + 0.5f * viewport[2] - 0.5f; fb_viewportcenter[2] = dpsoftrast.fb_height - viewport[1] - 0.5f * viewport[3] - 0.5f; fb_viewportcenter[3] = 0.5f; fb_viewportcenter[0] = 0.0f; fb_viewportscale[1] = 0.5f * viewport[2]; fb_viewportscale[2] = -0.5f * viewport[3]; fb_viewportscale[3] = 0.5f; fb_viewportscale[0] = 1.0f; } static void DPSOFTRAST_RecalcThread(DPSOFTRAST_State_Thread *thread) { if (dpsoftrast.interlace) { thread->miny1 = (thread->index*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); thread->maxy1 = ((thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); thread->miny2 = ((dpsoftrast.numthreads+thread->index)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); thread->maxy2 = ((dpsoftrast.numthreads+thread->index+1)*dpsoftrast.fb_height)/(2*dpsoftrast.numthreads); } else { thread->miny1 = thread->miny2 = (thread->index*dpsoftrast.fb_height)/dpsoftrast.numthreads; thread->maxy1 = thread->maxy2 = ((thread->index+1)*dpsoftrast.fb_height)/dpsoftrast.numthreads; } } static void DPSOFTRAST_RecalcClipPlane(DPSOFTRAST_State_Thread *thread) { thread->fb_clipplane[0] = thread->clipplane[0] / thread->fb_viewportscale[1]; thread->fb_clipplane[1] = thread->clipplane[1] / thread->fb_viewportscale[2]; thread->fb_clipplane[2] = thread->clipplane[2] / thread->fb_viewportscale[3]; thread->fb_clipplane[3] = thread->clipplane[3] / thread->fb_viewportscale[0]; thread->fb_clipplane[3] -= thread->fb_viewportcenter[1]*thread->fb_clipplane[0] + thread->fb_viewportcenter[2]*thread->fb_clipplane[1] + thread->fb_viewportcenter[3]*thread->fb_clipplane[2] + thread->fb_viewportcenter[0]*thread->fb_clipplane[3]; } static void DPSOFTRAST_RecalcFB(DPSOFTRAST_State_Thread *thread) { // calculate framebuffer scissor, viewport, viewport clipped by scissor, // and viewport projection values int x1, x2; int y1, y2; x1 = thread->scissor[0]; x2 = thread->scissor[0] + thread->scissor[2]; y1 = dpsoftrast.fb_height - thread->scissor[1] - thread->scissor[3]; y2 = dpsoftrast.fb_height - thread->scissor[1]; if (!thread->scissortest) {x1 = 0;y1 = 0;x2 = dpsoftrast.fb_width;y2 = dpsoftrast.fb_height;} if (x1 < 0) x1 = 0; if (x2 > dpsoftrast.fb_width) x2 = dpsoftrast.fb_width; if (y1 < 0) y1 = 0; if (y2 > dpsoftrast.fb_height) y2 = dpsoftrast.fb_height; thread->fb_scissor[0] = x1; thread->fb_scissor[1] = y1; thread->fb_scissor[2] = x2 - x1; thread->fb_scissor[3] = y2 - y1; DPSOFTRAST_RecalcViewport(thread->viewport, thread->fb_viewportcenter, thread->fb_viewportscale); DPSOFTRAST_RecalcClipPlane(thread); DPSOFTRAST_RecalcThread(thread); } static void DPSOFTRAST_RecalcDepthFunc(DPSOFTRAST_State_Thread *thread) { thread->fb_depthfunc = thread->depthtest ? thread->depthfunc : GL_ALWAYS; } static void DPSOFTRAST_RecalcBlendFunc(DPSOFTRAST_State_Thread *thread) { if (thread->blendsubtract) { switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) { #define BLENDFUNC(sfactor, dfactor, blendmode) \ case (sfactor<<16)|dfactor: thread->fb_blendmode = blendmode; break; BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_SUBALPHA) default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; } } else { switch ((thread->blendfunc[0]<<16)|thread->blendfunc[1]) { BLENDFUNC(GL_ONE, GL_ZERO, DPSOFTRAST_BLENDMODE_OPAQUE) BLENDFUNC(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_ALPHA) BLENDFUNC(GL_SRC_ALPHA, GL_ONE, DPSOFTRAST_BLENDMODE_ADDALPHA) BLENDFUNC(GL_ONE, GL_ONE, DPSOFTRAST_BLENDMODE_ADD) BLENDFUNC(GL_ZERO, GL_ONE_MINUS_SRC_COLOR, DPSOFTRAST_BLENDMODE_INVMOD) BLENDFUNC(GL_ZERO, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL) BLENDFUNC(GL_DST_COLOR, GL_ZERO, DPSOFTRAST_BLENDMODE_MUL) BLENDFUNC(GL_DST_COLOR, GL_SRC_COLOR, DPSOFTRAST_BLENDMODE_MUL2) BLENDFUNC(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, DPSOFTRAST_BLENDMODE_PSEUDOALPHA) BLENDFUNC(GL_ONE_MINUS_DST_COLOR, GL_ONE, DPSOFTRAST_BLENDMODE_INVADD) default: thread->fb_blendmode = DPSOFTRAST_BLENDMODE_OPAQUE; break; } } } #define DPSOFTRAST_ValidateQuick(thread, f) ((thread->validate & (f)) ? (DPSOFTRAST_Validate(thread, f), 0) : 0) static void DPSOFTRAST_Validate(DPSOFTRAST_State_Thread *thread, int mask) { mask &= thread->validate; if (!mask) return; if (mask & DPSOFTRAST_VALIDATE_FB) { thread->validate &= ~DPSOFTRAST_VALIDATE_FB; DPSOFTRAST_RecalcFB(thread); } if (mask & DPSOFTRAST_VALIDATE_DEPTHFUNC) { thread->validate &= ~DPSOFTRAST_VALIDATE_DEPTHFUNC; DPSOFTRAST_RecalcDepthFunc(thread); } if (mask & DPSOFTRAST_VALIDATE_BLENDFUNC) { thread->validate &= ~DPSOFTRAST_VALIDATE_BLENDFUNC; DPSOFTRAST_RecalcBlendFunc(thread); } } static DPSOFTRAST_Texture *DPSOFTRAST_Texture_GetByIndex(int index) { if (index >= 1 && index < dpsoftrast.texture_end && dpsoftrast.texture[index].bytes) return &dpsoftrast.texture[index]; return NULL; } static void DPSOFTRAST_Texture_Grow(void) { DPSOFTRAST_Texture *oldtexture = dpsoftrast.texture; DPSOFTRAST_State_Thread *thread; int i; int j; DPSOFTRAST_Flush(); // expand texture array as needed if (dpsoftrast.texture_max < 1024) dpsoftrast.texture_max = 1024; else dpsoftrast.texture_max *= 2; dpsoftrast.texture = (DPSOFTRAST_Texture *)realloc(dpsoftrast.texture, dpsoftrast.texture_max * sizeof(DPSOFTRAST_Texture)); for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) if (dpsoftrast.texbound[i]) dpsoftrast.texbound[i] = dpsoftrast.texture + (dpsoftrast.texbound[i] - oldtexture); for (j = 0; j < dpsoftrast.numthreads; j++) { thread = &dpsoftrast.threads[j]; for (i = 0; i < DPSOFTRAST_MAXTEXTUREUNITS; i++) if (thread->texbound[i]) thread->texbound[i] = dpsoftrast.texture + (thread->texbound[i] - oldtexture); } } int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth) { int w; int h; int d; int size; int s; int texnum; int mipmaps; int sides = (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) ? 6 : 1; int texformat = flags & DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK; DPSOFTRAST_Texture *texture; if (width*height*depth < 1) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: width, height or depth is less than 1"; return 0; } if (width > DPSOFTRAST_TEXTURE_MAXSIZE || height > DPSOFTRAST_TEXTURE_MAXSIZE || depth > DPSOFTRAST_TEXTURE_MAXSIZE) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: texture size is too large"; return 0; } switch(texformat) { case DPSOFTRAST_TEXTURE_FORMAT_BGRA8: case DPSOFTRAST_TEXTURE_FORMAT_RGBA8: case DPSOFTRAST_TEXTURE_FORMAT_ALPHA8: break; case DPSOFTRAST_TEXTURE_FORMAT_DEPTH: if (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; return 0; } if (depth != 1) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH only permitted on 2D textures"; return 0; } if ((flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && (texformat == DPSOFTRAST_TEXTURE_FORMAT_DEPTH)) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FORMAT_DEPTH does not permit mipmaps"; return 0; } break; } if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP)) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_CUBEMAP can not be used on 3D textures"; return 0; } if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; return 0; } if (depth != 1 && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on 3D textures"; return 0; } if ((flags & DPSOFTRAST_TEXTURE_FLAG_CUBEMAP) && (flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: DPSOFTRAST_TEXTURE_FLAG_MIPMAP can not be used on cubemap textures"; return 0; } if ((width & (width-1)) || (height & (height-1)) || (depth & (depth-1))) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_New: dimensions are not power of two"; return 0; } // find first empty slot in texture array for (texnum = dpsoftrast.texture_firstfree;texnum < dpsoftrast.texture_end;texnum++) if (!dpsoftrast.texture[texnum].bytes) break; dpsoftrast.texture_firstfree = texnum + 1; if (dpsoftrast.texture_max <= texnum) DPSOFTRAST_Texture_Grow(); if (dpsoftrast.texture_end <= texnum) dpsoftrast.texture_end = texnum + 1; texture = &dpsoftrast.texture[texnum]; memset(texture, 0, sizeof(*texture)); texture->flags = flags; texture->width = width; texture->height = height; texture->depth = depth; texture->sides = sides; texture->binds = 0; w = width; h = height; d = depth; size = 0; mipmaps = 0; for (;;) { s = w * h * d * sides * 4; texture->mipmap[mipmaps][0] = size; texture->mipmap[mipmaps][1] = s; texture->mipmap[mipmaps][2] = w; texture->mipmap[mipmaps][3] = h; texture->mipmap[mipmaps][4] = d; size += s; mipmaps++; if (w * h * d == 1 || !(flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP)) break; if (w > 1) w >>= 1; if (h > 1) h >>= 1; if (d > 1) d >>= 1; } texture->mipmaps = mipmaps; texture->size = size; // allocate the pixels now texture->bytes = (unsigned char *)MM_CALLOC(1, size); return texnum; } void DPSOFTRAST_Texture_Free(int index) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (texture->binds) DPSOFTRAST_Flush(); if (texture->bytes) MM_FREE(texture->bytes); texture->bytes = NULL; memset(texture, 0, sizeof(*texture)); // adjust the free range and used range if (dpsoftrast.texture_firstfree > index) dpsoftrast.texture_firstfree = index; while (dpsoftrast.texture_end > 0 && dpsoftrast.texture[dpsoftrast.texture_end-1].bytes == NULL) dpsoftrast.texture_end--; } static void DPSOFTRAST_Texture_CalculateMipmaps(int index) { int i, x, y, z, w, layer0, layer1, row0, row1; unsigned char *o, *i0, *i1, *i2, *i3; DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (texture->mipmaps <= 1) return; for (i = 1;i < texture->mipmaps;i++) { for (z = 0;z < texture->mipmap[i][4];z++) { layer0 = z*2; layer1 = z*2+1; if (layer1 >= texture->mipmap[i-1][4]) layer1 = texture->mipmap[i-1][4]-1; for (y = 0;y < texture->mipmap[i][3];y++) { row0 = y*2; row1 = y*2+1; if (row1 >= texture->mipmap[i-1][3]) row1 = texture->mipmap[i-1][3]-1; o = texture->bytes + texture->mipmap[i ][0] + 4*((texture->mipmap[i ][3] * z + y ) * texture->mipmap[i ][2]); i0 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row0) * texture->mipmap[i-1][2]); i1 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer0 + row1) * texture->mipmap[i-1][2]); i2 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row0) * texture->mipmap[i-1][2]); i3 = texture->bytes + texture->mipmap[i-1][0] + 4*((texture->mipmap[i-1][3] * layer1 + row1) * texture->mipmap[i-1][2]); w = texture->mipmap[i][2]; if (layer1 > layer0) { if (texture->mipmap[i-1][2] > 1) { // average 3D texture for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8, i2 += 8, i3 += 8) { o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + i2[0] + i2[4] + i3[0] + i3[4] + 4) >> 3; o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + i2[1] + i2[5] + i3[1] + i3[5] + 4) >> 3; o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + i2[2] + i2[6] + i3[2] + i3[6] + 4) >> 3; o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + i2[3] + i2[7] + i3[3] + i3[7] + 4) >> 3; } } else { // average 3D mipmap with parent width == 1 for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) { o[0] = (i0[0] + i1[0] + i2[0] + i3[0] + 2) >> 2; o[1] = (i0[1] + i1[1] + i2[1] + i3[1] + 2) >> 2; o[2] = (i0[2] + i1[2] + i2[2] + i3[2] + 2) >> 2; o[3] = (i0[3] + i1[3] + i2[3] + i3[3] + 2) >> 2; } } } else { if (texture->mipmap[i-1][2] > 1) { // average 2D texture (common case) for (x = 0;x < w;x++, o += 4, i0 += 8, i1 += 8) { o[0] = (i0[0] + i0[4] + i1[0] + i1[4] + 2) >> 2; o[1] = (i0[1] + i0[5] + i1[1] + i1[5] + 2) >> 2; o[2] = (i0[2] + i0[6] + i1[2] + i1[6] + 2) >> 2; o[3] = (i0[3] + i0[7] + i1[3] + i1[7] + 2) >> 2; } } else { // 2D texture with parent width == 1 o[0] = (i0[0] + i1[0] + 1) >> 1; o[1] = (i0[1] + i1[1] + 1) >> 1; o[2] = (i0[2] + i1[2] + 1) >> 1; o[3] = (i0[3] + i1[3] + 1) >> 1; } } } } } } void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight) { DPSOFTRAST_Texture *texture; unsigned char *dst; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (texture->binds) DPSOFTRAST_Flush(); if (pixels) { dst = texture->bytes + texture->mipmap[0][1] +(-blocky * texture->mipmap[0][2] + blockx) * 4; while (blockheight > 0) { dst -= texture->mipmap[0][2] * 4; memcpy(dst, pixels, blockwidth * 4); pixels += blockwidth * 4; blockheight--; } } DPSOFTRAST_Texture_CalculateMipmaps(index); } void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (texture->binds) DPSOFTRAST_Flush(); if (pixels) { int i, stride = texture->mipmap[0][2]*4; unsigned char *dst = texture->bytes + texture->mipmap[0][1]; for (i = texture->mipmap[0][3];i > 0;i--) { dst -= stride; memcpy(dst, pixels, stride); pixels += stride; } } DPSOFTRAST_Texture_CalculateMipmaps(index); } int DPSOFTRAST_Texture_GetWidth(int index, int mip) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; return texture->mipmap[mip][2]; } int DPSOFTRAST_Texture_GetHeight(int index, int mip) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; return texture->mipmap[mip][3]; } int DPSOFTRAST_Texture_GetDepth(int index, int mip) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; return texture->mipmap[mip][4]; } unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return 0; if (texture->binds) DPSOFTRAST_Flush(); return texture->bytes + texture->mipmap[mip][0]; } void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter) { DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (!(texture->flags & DPSOFTRAST_TEXTURE_FLAG_MIPMAP) && filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) { dpsoftrast.errorstring = "DPSOFTRAST_Texture_Filter: requested filter mode requires mipmaps"; return; } if (texture->binds) DPSOFTRAST_Flush(); texture->filter = filter; } static void DPSOFTRAST_Draw_FlushThreads(void); static void DPSOFTRAST_Draw_SyncCommands(void) { if(dpsoftrast.usethreads) MEMORY_BARRIER; dpsoftrast.drawcommand = dpsoftrast.commandpool.freecommand; } static void DPSOFTRAST_Draw_FreeCommandPool(int space) { DPSOFTRAST_State_Thread *thread; int i; int freecommand = dpsoftrast.commandpool.freecommand; int usedcommands = dpsoftrast.commandpool.usedcommands; if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space) return; DPSOFTRAST_Draw_SyncCommands(); for(;;) { int waitindex = -1; int commandoffset; usedcommands = 0; for (i = 0; i < dpsoftrast.numthreads; i++) { thread = &dpsoftrast.threads[i]; commandoffset = freecommand - thread->commandoffset; if (commandoffset < 0) commandoffset += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; if (commandoffset > usedcommands) { waitindex = i; usedcommands = commandoffset; } } if (usedcommands <= DPSOFTRAST_DRAW_MAXCOMMANDPOOL-space || waitindex < 0) break; thread = &dpsoftrast.threads[waitindex]; Thread_LockMutex(thread->drawmutex); if (thread->commandoffset != dpsoftrast.drawcommand) { thread->waiting = true; if (thread->starving) Thread_CondSignal(thread->drawcond); Thread_CondWait(thread->waitcond, thread->drawmutex); thread->waiting = false; } Thread_UnlockMutex(thread->drawmutex); } dpsoftrast.commandpool.usedcommands = usedcommands; } #define DPSOFTRAST_ALIGNCOMMAND(size) \ ((size) + ((COMMAND_SIZE - ((size)&(COMMAND_SIZE-1))) & (COMMAND_SIZE-1))) #define DPSOFTRAST_ALLOCATECOMMAND(name) \ ((DPSOFTRAST_Command_##name *) DPSOFTRAST_AllocateCommand( DPSOFTRAST_OPCODE_##name , DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )))) static void *DPSOFTRAST_AllocateCommand(int opcode, int size) { DPSOFTRAST_Command *command; int freecommand = dpsoftrast.commandpool.freecommand; int usedcommands = dpsoftrast.commandpool.usedcommands; int extra = sizeof(DPSOFTRAST_Command); if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) extra += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; if (usedcommands > DPSOFTRAST_DRAW_MAXCOMMANDPOOL - (size + extra)) { if (dpsoftrast.usethreads) DPSOFTRAST_Draw_FreeCommandPool(size + extra); else DPSOFTRAST_Draw_FlushThreads(); freecommand = dpsoftrast.commandpool.freecommand; usedcommands = dpsoftrast.commandpool.usedcommands; } if (DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand < size) { command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; command->opcode = DPSOFTRAST_OPCODE_Reset; usedcommands += DPSOFTRAST_DRAW_MAXCOMMANDPOOL - freecommand; freecommand = 0; } command = (DPSOFTRAST_Command *) &dpsoftrast.commandpool.commands[freecommand]; command->opcode = opcode; command->commandsize = size; freecommand += size; if (freecommand >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) freecommand = 0; dpsoftrast.commandpool.freecommand = freecommand; dpsoftrast.commandpool.usedcommands = usedcommands + size; return command; } static void DPSOFTRAST_UndoCommand(int size) { int freecommand = dpsoftrast.commandpool.freecommand; int usedcommands = dpsoftrast.commandpool.usedcommands; freecommand -= size; if (freecommand < 0) freecommand += DPSOFTRAST_DRAW_MAXCOMMANDPOOL; usedcommands -= size; dpsoftrast.commandpool.freecommand = freecommand; dpsoftrast.commandpool.usedcommands = usedcommands; } DEFCOMMAND(1, Viewport, int x; int y; int width; int height;) static void DPSOFTRAST_Interpret_Viewport(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_Viewport *command) { thread->viewport[0] = command->x; thread->viewport[1] = command->y; thread->viewport[2] = command->width; thread->viewport[3] = command->height; thread->validate |= DPSOFTRAST_VALIDATE_FB; } void DPSOFTRAST_Viewport(int x, int y, int width, int height) { DPSOFTRAST_Command_Viewport *command = DPSOFTRAST_ALLOCATECOMMAND(Viewport); command->x = x; command->y = y; command->width = width; command->height = height; dpsoftrast.viewport[0] = x; dpsoftrast.viewport[1] = y; dpsoftrast.viewport[2] = width; dpsoftrast.viewport[3] = height; DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); } DEFCOMMAND(2, ClearColor, float r; float g; float b; float a;) static void DPSOFTRAST_Interpret_ClearColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_ClearColor *command) { int i, x1, y1, x2, y2, w, h, x, y; int miny1, maxy1, miny2, maxy2; int bandy; unsigned int *p; unsigned int c; DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); miny1 = thread->miny1; maxy1 = thread->maxy1; miny2 = thread->miny2; maxy2 = thread->maxy2; x1 = thread->fb_scissor[0]; y1 = thread->fb_scissor[1]; x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; if (y1 < miny1) y1 = miny1; if (y2 > maxy2) y2 = maxy2; w = x2 - x1; h = y2 - y1; if (w < 1 || h < 1) return; // FIXME: honor fb_colormask? c = DPSOFTRAST_BGRA8_FROM_RGBA32F(command->r,command->g,command->b,command->a); for (i = 0;i < 4;i++) { if (!dpsoftrast.fb_colorpixels[i]) continue; for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) for (;y < bandy;y++) { p = dpsoftrast.fb_colorpixels[i] + y * dpsoftrast.fb_width; for (x = x1;x < x2;x++) p[x] = c; } } } void DPSOFTRAST_ClearColor(float r, float g, float b, float a) { DPSOFTRAST_Command_ClearColor *command = DPSOFTRAST_ALLOCATECOMMAND(ClearColor); command->r = r; command->g = g; command->b = b; command->a = a; } DEFCOMMAND(3, ClearDepth, float depth;) static void DPSOFTRAST_Interpret_ClearDepth(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClearDepth *command) { int x1, y1, x2, y2, w, h, x, y; int miny1, maxy1, miny2, maxy2; int bandy; unsigned int *p; unsigned int c; DPSOFTRAST_Validate(thread, DPSOFTRAST_VALIDATE_FB); miny1 = thread->miny1; maxy1 = thread->maxy1; miny2 = thread->miny2; maxy2 = thread->maxy2; x1 = thread->fb_scissor[0]; y1 = thread->fb_scissor[1]; x2 = thread->fb_scissor[0] + thread->fb_scissor[2]; y2 = thread->fb_scissor[1] + thread->fb_scissor[3]; if (y1 < miny1) y1 = miny1; if (y2 > maxy2) y2 = maxy2; w = x2 - x1; h = y2 - y1; if (w < 1 || h < 1) return; c = DPSOFTRAST_DEPTH32_FROM_DEPTH32F(command->depth); for (y = y1, bandy = min(y2, maxy1); y < y2; bandy = min(y2, maxy2), y = max(y, miny2)) for (;y < bandy;y++) { p = dpsoftrast.fb_depthpixels + y * dpsoftrast.fb_width; for (x = x1;x < x2;x++) p[x] = c; } } void DPSOFTRAST_ClearDepth(float d) { DPSOFTRAST_Command_ClearDepth *command = DPSOFTRAST_ALLOCATECOMMAND(ClearDepth); command->depth = d; } DEFCOMMAND(4, ColorMask, int r; int g; int b; int a;) static void DPSOFTRAST_Interpret_ColorMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ColorMask *command) { thread->colormask[0] = command->r != 0; thread->colormask[1] = command->g != 0; thread->colormask[2] = command->b != 0; thread->colormask[3] = command->a != 0; thread->fb_colormask = ((-thread->colormask[0]) & 0x00FF0000) | ((-thread->colormask[1]) & 0x0000FF00) | ((-thread->colormask[2]) & 0x000000FF) | ((-thread->colormask[3]) & 0xFF000000); } void DPSOFTRAST_ColorMask(int r, int g, int b, int a) { DPSOFTRAST_Command_ColorMask *command = DPSOFTRAST_ALLOCATECOMMAND(ColorMask); command->r = r; command->g = g; command->b = b; command->a = a; } DEFCOMMAND(5, DepthTest, int enable;) static void DPSOFTRAST_Interpret_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthTest *command) { thread->depthtest = command->enable; thread->validate |= DPSOFTRAST_VALIDATE_DEPTHFUNC; } void DPSOFTRAST_DepthTest(int enable) { DPSOFTRAST_Command_DepthTest *command = DPSOFTRAST_ALLOCATECOMMAND(DepthTest); command->enable = enable; } DEFCOMMAND(6, ScissorTest, int enable;) static void DPSOFTRAST_Interpret_ScissorTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ScissorTest *command) { thread->scissortest = command->enable; thread->validate |= DPSOFTRAST_VALIDATE_FB; } void DPSOFTRAST_ScissorTest(int enable) { DPSOFTRAST_Command_ScissorTest *command = DPSOFTRAST_ALLOCATECOMMAND(ScissorTest); command->enable = enable; } DEFCOMMAND(7, Scissor, float x; float y; float width; float height;) static void DPSOFTRAST_Interpret_Scissor(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Scissor *command) { thread->scissor[0] = command->x; thread->scissor[1] = command->y; thread->scissor[2] = command->width; thread->scissor[3] = command->height; thread->validate |= DPSOFTRAST_VALIDATE_FB; } void DPSOFTRAST_Scissor(float x, float y, float width, float height) { DPSOFTRAST_Command_Scissor *command = DPSOFTRAST_ALLOCATECOMMAND(Scissor); command->x = x; command->y = y; command->width = width; command->height = height; } DEFCOMMAND(8, BlendFunc, int sfactor; int dfactor;) static void DPSOFTRAST_Interpret_BlendFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendFunc *command) { thread->blendfunc[0] = command->sfactor; thread->blendfunc[1] = command->dfactor; thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; } void DPSOFTRAST_BlendFunc(int sfactor, int dfactor) { DPSOFTRAST_Command_BlendFunc *command = DPSOFTRAST_ALLOCATECOMMAND(BlendFunc); command->sfactor = sfactor; command->dfactor = dfactor; } DEFCOMMAND(9, BlendSubtract, int enable;) static void DPSOFTRAST_Interpret_BlendSubtract(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_BlendSubtract *command) { thread->blendsubtract = command->enable; thread->validate |= DPSOFTRAST_VALIDATE_BLENDFUNC; } void DPSOFTRAST_BlendSubtract(int enable) { DPSOFTRAST_Command_BlendSubtract *command = DPSOFTRAST_ALLOCATECOMMAND(BlendSubtract); command->enable = enable; } DEFCOMMAND(10, DepthMask, int enable;) static void DPSOFTRAST_Interpret_DepthMask(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthMask *command) { thread->depthmask = command->enable; } void DPSOFTRAST_DepthMask(int enable) { DPSOFTRAST_Command_DepthMask *command = DPSOFTRAST_ALLOCATECOMMAND(DepthMask); command->enable = enable; } DEFCOMMAND(11, DepthFunc, int func;) static void DPSOFTRAST_Interpret_DepthFunc(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthFunc *command) { thread->depthfunc = command->func; } void DPSOFTRAST_DepthFunc(int func) { DPSOFTRAST_Command_DepthFunc *command = DPSOFTRAST_ALLOCATECOMMAND(DepthFunc); command->func = func; } DEFCOMMAND(12, DepthRange, float nearval; float farval;) static void DPSOFTRAST_Interpret_DepthRange(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_DepthRange *command) { thread->depthrange[0] = command->nearval; thread->depthrange[1] = command->farval; } void DPSOFTRAST_DepthRange(float nearval, float farval) { DPSOFTRAST_Command_DepthRange *command = DPSOFTRAST_ALLOCATECOMMAND(DepthRange); command->nearval = nearval; command->farval = farval; } DEFCOMMAND(13, PolygonOffset, float alongnormal; float intoview;) static void DPSOFTRAST_Interpret_PolygonOffset(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_PolygonOffset *command) { thread->polygonoffset[0] = command->alongnormal; thread->polygonoffset[1] = command->intoview; } void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview) { DPSOFTRAST_Command_PolygonOffset *command = DPSOFTRAST_ALLOCATECOMMAND(PolygonOffset); command->alongnormal = alongnormal; command->intoview = intoview; } DEFCOMMAND(14, CullFace, int mode;) static void DPSOFTRAST_Interpret_CullFace(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_CullFace *command) { thread->cullface = command->mode; } void DPSOFTRAST_CullFace(int mode) { DPSOFTRAST_Command_CullFace *command = DPSOFTRAST_ALLOCATECOMMAND(CullFace); command->mode = mode; } void DPSOFTRAST_Color4f(float r, float g, float b, float a) { dpsoftrast.color[0] = r; dpsoftrast.color[1] = g; dpsoftrast.color[2] = b; dpsoftrast.color[3] = a; } void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels) { int outstride = blockwidth * 4; int instride = dpsoftrast.fb_width * 4; int bx1 = blockx; int by1 = blocky; int bx2 = blockx + blockwidth; int by2 = blocky + blockheight; int bw; int x; int y; unsigned char *inpixels; unsigned char *b; unsigned char *o; DPSOFTRAST_Flush(); if (bx1 < 0) bx1 = 0; if (by1 < 0) by1 = 0; if (bx2 > dpsoftrast.fb_width) bx2 = dpsoftrast.fb_width; if (by2 > dpsoftrast.fb_height) by2 = dpsoftrast.fb_height; bw = bx2 - bx1; inpixels = (unsigned char *)dpsoftrast.fb_colorpixels[0]; if (dpsoftrast.bigendian) { for (y = by1;y < by2;y++) { b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; o = (unsigned char *)outpixels + (y - by1) * outstride; for (x = bx1;x < bx2;x++) { o[0] = b[3]; o[1] = b[2]; o[2] = b[1]; o[3] = b[0]; o += 4; b += 4; } } } else { for (y = by1;y < by2;y++) { b = (unsigned char *)inpixels + (dpsoftrast.fb_height - 1 - y) * instride + 4 * bx1; o = (unsigned char *)outpixels + (y - by1) * outstride; memcpy(o, b, bw*4); } } } void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height) { int tx1 = tx; int ty1 = ty; int tx2 = tx + width; int ty2 = ty + height; int sx1 = sx; int sy1 = sy; int sx2 = sx + width; int sy2 = sy + height; int swidth; int sheight; int twidth; int theight; int sw; int sh; int tw; int th; int y; unsigned int *spixels; unsigned int *tpixels; DPSOFTRAST_Texture *texture; texture = DPSOFTRAST_Texture_GetByIndex(index);if (!texture) return; if (mip < 0 || mip >= texture->mipmaps) return; DPSOFTRAST_Flush(); spixels = dpsoftrast.fb_colorpixels[0]; swidth = dpsoftrast.fb_width; sheight = dpsoftrast.fb_height; tpixels = (unsigned int *)(texture->bytes + texture->mipmap[mip][0]); twidth = texture->mipmap[mip][2]; theight = texture->mipmap[mip][3]; if (tx1 < 0) tx1 = 0; if (ty1 < 0) ty1 = 0; if (tx2 > twidth) tx2 = twidth; if (ty2 > theight) ty2 = theight; if (sx1 < 0) sx1 = 0; if (sy1 < 0) sy1 = 0; if (sx2 > swidth) sx2 = swidth; if (sy2 > sheight) sy2 = sheight; tw = tx2 - tx1; th = ty2 - ty1; sw = sx2 - sx1; sh = sy2 - sy1; if (tw > sw) tw = sw; if (th > sh) th = sh; if (tw < 1 || th < 1) return; sy1 = sheight - sy1 - th; ty1 = theight - ty1 - th; for (y = 0;y < th;y++) memcpy(tpixels + ((ty1 + y) * twidth + tx1), spixels + ((sy1 + y) * swidth + sx1), tw*4); if (texture->mipmaps > 1) DPSOFTRAST_Texture_CalculateMipmaps(index); } DEFCOMMAND(17, SetTexture, int unitnum; DPSOFTRAST_Texture *texture;) static void DPSOFTRAST_Interpret_SetTexture(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetTexture *command) { if (thread->texbound[command->unitnum]) ATOMIC_DECREMENT(thread->texbound[command->unitnum]->binds); thread->texbound[command->unitnum] = command->texture; } void DPSOFTRAST_SetTexture(int unitnum, int index) { DPSOFTRAST_Command_SetTexture *command; DPSOFTRAST_Texture *texture; if (unitnum < 0 || unitnum >= DPSOFTRAST_MAXTEXTUREUNITS) { dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid unit number"; return; } texture = DPSOFTRAST_Texture_GetByIndex(index); if (index && !texture) { dpsoftrast.errorstring = "DPSOFTRAST_SetTexture: invalid texture handle"; return; } command = DPSOFTRAST_ALLOCATECOMMAND(SetTexture); command->unitnum = unitnum; command->texture = texture; dpsoftrast.texbound[unitnum] = texture; if (texture) ATOMIC_ADD(texture->binds, dpsoftrast.numthreads); } void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride) { dpsoftrast.pointer_vertex3f = vertex3f; dpsoftrast.stride_vertex = (int)stride; } void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride) { dpsoftrast.pointer_color4f = color4f; dpsoftrast.pointer_color4ub = NULL; dpsoftrast.stride_color = (int)stride; } void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride) { dpsoftrast.pointer_color4f = NULL; dpsoftrast.pointer_color4ub = color4ub; dpsoftrast.stride_color = (int)stride; } void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf) { dpsoftrast.pointer_texcoordf[unitnum] = texcoordf; dpsoftrast.components_texcoord[unitnum] = numcomponents; dpsoftrast.stride_texcoord[unitnum] = (int)stride; } DEFCOMMAND(18, SetShader, int mode; int permutation; int exactspecularmath;) static void DPSOFTRAST_Interpret_SetShader(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_SetShader *command) { thread->shader_mode = command->mode; thread->shader_permutation = command->permutation; thread->shader_exactspecularmath = command->exactspecularmath; } void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath) { DPSOFTRAST_Command_SetShader *command = DPSOFTRAST_ALLOCATECOMMAND(SetShader); command->mode = mode; command->permutation = permutation; command->exactspecularmath = exactspecularmath; dpsoftrast.shader_mode = mode; dpsoftrast.shader_permutation = permutation; dpsoftrast.shader_exactspecularmath = exactspecularmath; } DEFCOMMAND(19, Uniform4f, DPSOFTRAST_UNIFORM index; float val[4];) static void DPSOFTRAST_Interpret_Uniform4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform4f *command) { memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); } void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3) { DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); command->index = index; command->val[0] = v0; command->val[1] = v1; command->val[2] = v2; command->val[3] = v3; dpsoftrast.uniform4f[index*4+0] = v0; dpsoftrast.uniform4f[index*4+1] = v1; dpsoftrast.uniform4f[index*4+2] = v2; dpsoftrast.uniform4f[index*4+3] = v3; } void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v) { DPSOFTRAST_Command_Uniform4f *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform4f); command->index = index; memcpy(command->val, v, sizeof(command->val)); memcpy(&dpsoftrast.uniform4f[index*4], v, sizeof(float[4])); } DEFCOMMAND(20, UniformMatrix4f, DPSOFTRAST_UNIFORM index; ALIGN(float val[16]);) static void DPSOFTRAST_Interpret_UniformMatrix4f(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_UniformMatrix4f *command) { memcpy(&thread->uniform4f[command->index*4], command->val, sizeof(command->val)); } void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM uniform, int arraysize, int transpose, const float *v) { #ifdef SSE_POSSIBLE int i, index; for (i = 0, index = (int)uniform;i < arraysize;i++, index += 4, v += 16) { __m128 m0, m1, m2, m3; DPSOFTRAST_Command_UniformMatrix4f *command = DPSOFTRAST_ALLOCATECOMMAND(UniformMatrix4f); command->index = (DPSOFTRAST_UNIFORM)index; if (((size_t)v)&(ALIGN_SIZE-1)) { m0 = _mm_loadu_ps(v); m1 = _mm_loadu_ps(v+4); m2 = _mm_loadu_ps(v+8); m3 = _mm_loadu_ps(v+12); } else { m0 = _mm_load_ps(v); m1 = _mm_load_ps(v+4); m2 = _mm_load_ps(v+8); m3 = _mm_load_ps(v+12); } if (transpose) { __m128 t0, t1, t2, t3; t0 = _mm_unpacklo_ps(m0, m1); t1 = _mm_unpacklo_ps(m2, m3); t2 = _mm_unpackhi_ps(m0, m1); t3 = _mm_unpackhi_ps(m2, m3); m0 = _mm_movelh_ps(t0, t1); m1 = _mm_movehl_ps(t1, t0); m2 = _mm_movelh_ps(t2, t3); m3 = _mm_movehl_ps(t3, t2); } _mm_store_ps(command->val, m0); _mm_store_ps(command->val+4, m1); _mm_store_ps(command->val+8, m2); _mm_store_ps(command->val+12, m3); _mm_store_ps(&dpsoftrast.uniform4f[index*4+0], m0); _mm_store_ps(&dpsoftrast.uniform4f[index*4+4], m1); _mm_store_ps(&dpsoftrast.uniform4f[index*4+8], m2); _mm_store_ps(&dpsoftrast.uniform4f[index*4+12], m3); } #endif } DEFCOMMAND(21, Uniform1i, DPSOFTRAST_UNIFORM index; int val;) static void DPSOFTRAST_Interpret_Uniform1i(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Uniform1i *command) { thread->uniform1i[command->index] = command->val; } void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0) { DPSOFTRAST_Command_Uniform1i *command = DPSOFTRAST_ALLOCATECOMMAND(Uniform1i); command->index = index; command->val = i0; dpsoftrast.uniform1i[command->index] = i0; } DEFCOMMAND(24, ClipPlane, float clipplane[4];) static void DPSOFTRAST_Interpret_ClipPlane(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_ClipPlane *command) { memcpy(thread->clipplane, command->clipplane, 4*sizeof(float)); thread->validate |= DPSOFTRAST_VALIDATE_FB; } void DPSOFTRAST_ClipPlane(float x, float y, float z, float w) { DPSOFTRAST_Command_ClipPlane *command = DPSOFTRAST_ALLOCATECOMMAND(ClipPlane); command->clipplane[0] = x; command->clipplane[1] = y; command->clipplane[2] = z; command->clipplane[3] = w; } #ifdef SSE_POSSIBLE static void DPSOFTRAST_Load4fTo4f(float *dst, const unsigned char *src, int size, int stride) { float *end = dst + size*4; if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) // check for alignment { while (dst < end) { _mm_store_ps(dst, _mm_loadu_ps((const float *)src)); dst += 4; src += stride; } } else { while (dst < end) { _mm_store_ps(dst, _mm_load_ps((const float *)src)); dst += 4; src += stride; } } } static void DPSOFTRAST_Load3fTo4f(float *dst, const unsigned char *src, int size, int stride) { float *end = dst + size*4; if (stride == sizeof(float[3])) { float *end4 = dst + (size&~3)*4; if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment { while (dst < end4) { __m128 v1 = _mm_loadu_ps((const float *)src), v2 = _mm_loadu_ps((const float *)src + 4), v3 = _mm_loadu_ps((const float *)src + 8), dv; dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dst += 16; src += 4*sizeof(float[3]); } } else { while (dst < end4) { __m128 v1 = _mm_load_ps((const float *)src), v2 = _mm_load_ps((const float *)src + 4), v3 = _mm_load_ps((const float *)src + 8), dv; dv = _mm_shuffle_ps(v1, v1, _MM_SHUFFLE(2, 1, 0, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_shuffle_ps(v1, v2, _MM_SHUFFLE(1, 0, 3, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst + 4, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_shuffle_ps(v2, v3, _MM_SHUFFLE(0, 0, 3, 2)); dv = _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(2, 1, 0, 3)); dv = _mm_move_ss(dv, _mm_set_ss(1.0f)); _mm_store_ps(dst + 8, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dv = _mm_move_ss(v3, _mm_set_ss(1.0f)); _mm_store_ps(dst + 12, _mm_shuffle_ps(dv, dv, _MM_SHUFFLE(0, 3, 2, 1))); dst += 16; src += 4*sizeof(float[3]); } } } if ((((size_t)src)|stride)&(ALIGN_SIZE - 1)) { while (dst < end) { __m128 v = _mm_loadu_ps((const float *)src); v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); v = _mm_move_ss(v, _mm_set_ss(1.0f)); v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); _mm_store_ps(dst, v); dst += 4; src += stride; } } else { while (dst < end) { __m128 v = _mm_load_ps((const float *)src); v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 1, 0, 3)); v = _mm_move_ss(v, _mm_set_ss(1.0f)); v = _mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 3, 2, 1)); _mm_store_ps(dst, v); dst += 4; src += stride; } } } static void DPSOFTRAST_Load2fTo4f(float *dst, const unsigned char *src, int size, int stride) { float *end = dst + size*4; __m128 v2 = _mm_setr_ps(0.0f, 0.0f, 0.0f, 1.0f); if (stride == sizeof(float[2])) { float *end2 = dst + (size&~1)*4; if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment { while (dst < end2) { __m128 v = _mm_loadu_ps((const float *)src); _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); dst += 8; src += 2*sizeof(float[2]); } } else { while (dst < end2) { __m128 v = _mm_load_ps((const float *)src); _mm_store_ps(dst, _mm_shuffle_ps(v, v2, _MM_SHUFFLE(3, 2, 1, 0))); _mm_store_ps(dst + 4, _mm_movehl_ps(v2, v)); dst += 8; src += 2*sizeof(float[2]); } } } while (dst < end) { _mm_store_ps(dst, _mm_loadl_pi(v2, (__m64 *)src)); dst += 4; src += stride; } } static void DPSOFTRAST_Load4bTo4f(float *dst, const unsigned char *src, int size, int stride) { float *end = dst + size*4; __m128 scale = _mm_set1_ps(1.0f/255.0f); if (stride == sizeof(unsigned char[4])) { float *end4 = dst + (size&~3)*4; if (((size_t)src)&(ALIGN_SIZE - 1)) // check for alignment { while (dst < end4) { __m128i v = _mm_loadu_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); dst += 16; src += 4*sizeof(unsigned char[4]); } } else { while (dst < end4) { __m128i v = _mm_load_si128((const __m128i *)src), v1 = _mm_unpacklo_epi8(v, _mm_setzero_si128()), v2 = _mm_unpackhi_epi8(v, _mm_setzero_si128()); _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v1, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 4, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v1, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 8, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(v2, _mm_setzero_si128())), scale)); _mm_store_ps(dst + 12, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpackhi_epi16(v2, _mm_setzero_si128())), scale)); dst += 16; src += 4*sizeof(unsigned char[4]); } } } while (dst < end) { __m128i v = _mm_cvtsi32_si128(*(const int *)src); _mm_store_ps(dst, _mm_mul_ps(_mm_cvtepi32_ps(_mm_unpacklo_epi16(_mm_unpacklo_epi8(v, _mm_setzero_si128()), _mm_setzero_si128())), scale)); dst += 4; src += stride; } } static void DPSOFTRAST_Fill4f(float *dst, const float *src, int size) { float *end = dst + 4*size; __m128 v = _mm_loadu_ps(src); while (dst < end) { _mm_store_ps(dst, v); dst += 4; } } #endif static void DPSOFTRAST_Vertex_Transform(float *out4f, const float *in4f, int numitems, const float *inmatrix16f) { #ifdef SSE_POSSIBLE static const float identitymatrix16f[4][4] = {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; __m128 m0, m1, m2, m3; float *end; if (!memcmp(identitymatrix16f, inmatrix16f, sizeof(float[16]))) { // fast case for identity matrix if (out4f != in4f) memcpy(out4f, in4f, numitems * sizeof(float[4])); return; } end = out4f + numitems*4; m0 = _mm_loadu_ps(inmatrix16f); m1 = _mm_loadu_ps(inmatrix16f + 4); m2 = _mm_loadu_ps(inmatrix16f + 8); m3 = _mm_loadu_ps(inmatrix16f + 12); if (((size_t)in4f)&(ALIGN_SIZE-1)) // check alignment { while (out4f < end) { __m128 v = _mm_loadu_ps(in4f); _mm_store_ps(out4f, _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); out4f += 4; in4f += 4; } } else { while (out4f < end) { __m128 v = _mm_load_ps(in4f); _mm_store_ps(out4f, _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(0, 0, 0, 0)), m0), _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(1, 1, 1, 1)), m1), _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(2, 2, 2, 2)), m2), _mm_mul_ps(_mm_shuffle_ps(v, v, _MM_SHUFFLE(3, 3, 3, 3)), m3))))); out4f += 4; in4f += 4; } } #endif } #if 0 static void DPSOFTRAST_Vertex_Copy(float *out4f, const float *in4f, int numitems) { memcpy(out4f, in4f, numitems * sizeof(float[4])); } #endif #ifdef SSE_POSSIBLE #define DPSOFTRAST_PROJECTVERTEX(out, in, viewportcenter, viewportscale) \ { \ __m128 p = (in), w = _mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)); \ p = _mm_move_ss(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 1, 0, 3)), _mm_set_ss(1.0f)); \ p = _mm_add_ps(viewportcenter, _mm_div_ps(_mm_mul_ps(viewportscale, p), w)); \ out = _mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 3, 2, 1)); \ } #define DPSOFTRAST_TRANSFORMVERTEX(out, in, m0, m1, m2, m3) \ { \ __m128 p = (in); \ out = _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(0, 0, 0, 0)), m0), \ _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(1, 1, 1, 1)), m1), \ _mm_add_ps(_mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(2, 2, 2, 2)), m2), \ _mm_mul_ps(_mm_shuffle_ps(p, p, _MM_SHUFFLE(3, 3, 3, 3)), m3)))); \ } static int DPSOFTRAST_Vertex_BoundY(int *starty, int *endy, const float *minposf, const float *maxposf, const float *inmatrix16f) { int clipmask = 0xFF; __m128 viewportcenter = _mm_load_ps(dpsoftrast.fb_viewportcenter), viewportscale = _mm_load_ps(dpsoftrast.fb_viewportscale); __m128 bb[8], clipdist[8], minproj = _mm_set_ss(2.0f), maxproj = _mm_set_ss(-2.0f); __m128 m0 = _mm_loadu_ps(inmatrix16f), m1 = _mm_loadu_ps(inmatrix16f + 4), m2 = _mm_loadu_ps(inmatrix16f + 8), m3 = _mm_loadu_ps(inmatrix16f + 12); __m128 minpos = _mm_load_ps(minposf), maxpos = _mm_load_ps(maxposf); m0 = _mm_shuffle_ps(m0, m0, _MM_SHUFFLE(3, 2, 0, 1)); m1 = _mm_shuffle_ps(m1, m1, _MM_SHUFFLE(3, 2, 0, 1)); m2 = _mm_shuffle_ps(m2, m2, _MM_SHUFFLE(3, 2, 0, 1)); m3 = _mm_shuffle_ps(m3, m3, _MM_SHUFFLE(3, 2, 0, 1)); #define BBFRONT(k, pos) \ { \ DPSOFTRAST_TRANSFORMVERTEX(bb[k], pos, m0, m1, m2, m3); \ clipdist[k] = _mm_add_ss(_mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(2, 2, 2, 2)), _mm_shuffle_ps(bb[k], bb[k], _MM_SHUFFLE(3, 3, 3, 3))); \ if (_mm_ucomige_ss(clipdist[k], _mm_setzero_ps())) \ { \ __m128 proj; \ clipmask &= ~(1<= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; DPSOFTRAST_Vertex_Transform(data, data, dpsoftrast.numvertices, inmatrix16f); return data; } #if 0 static float *DPSOFTRAST_Array_Project(int outarray, int inarray) { #ifdef SSE_POSSIBLE float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; dpsoftrast.drawclipped = DPSOFTRAST_Vertex_Project(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices); return data; #else return NULL; #endif } #endif static float *DPSOFTRAST_Array_TransformProject(int outarray, int inarray, const float *inmatrix16f) { #ifdef SSE_POSSIBLE float *data = inarray >= 0 ? DPSOFTRAST_Array_Load(outarray, inarray) : dpsoftrast.post_array4f[outarray]; dpsoftrast.drawclipped = DPSOFTRAST_Vertex_TransformProject(data, dpsoftrast.screencoord4f, &dpsoftrast.drawstarty, &dpsoftrast.drawendy, data, dpsoftrast.numvertices, inmatrix16f); return data; #else return NULL; #endif } static void DPSOFTRAST_Draw_Span_Begin(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *zf) { int x; int startx = span->startx; int endx = span->endx; float wslope = triangle->w[0]; float w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; float endz = 1.0f / (w + wslope * startx); if (triangle->w[0] == 0) { // LordHavoc: fast flat polygons (HUD/menu) for (x = startx;x < endx;x++) zf[x] = endz; return; } for (x = startx;x < endx;) { int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; float z = endz, dz; if (nextsub >= endx) nextsub = endsub = endx-1; endz = 1.0f / (w + wslope * nextsub); dz = x < nextsub ? (endz - z) / (nextsub - x) : 0.0f; for (; x <= endsub; x++, z += dz) zf[x] = z; } } static void DPSOFTRAST_Draw_Span_FinishBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, const unsigned char* RESTRICT in4ub) { #ifdef SSE_POSSIBLE int x; int startx = span->startx; int endx = span->endx; int maskx; int subx; const unsigned int * RESTRICT ini = (const unsigned int *)in4ub; unsigned char * RESTRICT pixelmask = span->pixelmask; unsigned int * RESTRICT pixeli = (unsigned int *)dpsoftrast.fb_colorpixels[0]; if (!pixeli) return; pixeli += span->y * dpsoftrast.fb_width + span->x; // handle alphatest now (this affects depth writes too) if (thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) for (x = startx;x < endx;x++) if (in4ub[x*4+3] < 128) pixelmask[x] = false; // LordHavoc: clear pixelmask for some pixels in alphablend cases, this // helps sprites, text and hud artwork switch(thread->fb_blendmode) { case DPSOFTRAST_BLENDMODE_ALPHA: case DPSOFTRAST_BLENDMODE_ADDALPHA: case DPSOFTRAST_BLENDMODE_SUBALPHA: maskx = startx; for (x = startx;x < endx;x++) { if (in4ub[x*4+3] >= 1) { startx = x; for (;;) { while (++x < endx && in4ub[x*4+3] >= 1) ; maskx = x; if (x >= endx) break; ++x; while (++x < endx && in4ub[x*4+3] < 1) pixelmask[x] = false; if (x >= endx) break; } break; } } endx = maskx; break; case DPSOFTRAST_BLENDMODE_OPAQUE: case DPSOFTRAST_BLENDMODE_ADD: case DPSOFTRAST_BLENDMODE_INVMOD: case DPSOFTRAST_BLENDMODE_MUL: case DPSOFTRAST_BLENDMODE_MUL2: case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: case DPSOFTRAST_BLENDMODE_INVADD: break; } // put some special values at the end of the mask to ensure the loops end pixelmask[endx] = 1; pixelmask[endx+1] = 0; // LordHavoc: use a double loop to identify subspans, this helps the // optimized copy/blend loops to perform at their best, most triangles // have only one run of pixels, and do the search using wide reads... x = startx; while (x < endx) { // if this pixel is masked off, it's probably not alone... if (!pixelmask[x]) { x++; #if 1 if (x + 8 < endx) { // the 4-item search must be aligned or else it stalls badly if ((x & 3) && !pixelmask[x]) { if(pixelmask[x]) goto endmasked; x++; if (x & 3) { if(pixelmask[x]) goto endmasked; x++; if (x & 3) { if(pixelmask[x]) goto endmasked; x++; } } } while (*(unsigned int *)&pixelmask[x] == 0x00000000) x += 4; } #endif for (;!pixelmask[x];x++) ; // rather than continue the loop, just check the end variable if (x >= endx) break; } endmasked: // find length of subspan subx = x + 1; #if 1 if (subx + 8 < endx) { if (subx & 3) { if(!pixelmask[subx]) goto endunmasked; subx++; if (subx & 3) { if(!pixelmask[subx]) goto endunmasked; subx++; if (subx & 3) { if(!pixelmask[subx]) goto endunmasked; subx++; } } } while (*(unsigned int *)&pixelmask[subx] == 0x01010101) subx += 4; } #endif for (;pixelmask[subx];subx++) ; // the checks can overshoot, so make sure to clip it... if (subx > endx) subx = endx; endunmasked: // now that we know the subspan length... process! switch(thread->fb_blendmode) { case DPSOFTRAST_BLENDMODE_OPAQUE: #if 0 if (subx - x >= 16) { memcpy(pixeli + x, ini + x, (subx - x) * sizeof(pixeli[x])); x = subx; } else #elif 1 while (x + 16 <= subx) { _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); _mm_storeu_si128((__m128i *)&pixeli[x+4], _mm_loadu_si128((const __m128i *)&ini[x+4])); _mm_storeu_si128((__m128i *)&pixeli[x+8], _mm_loadu_si128((const __m128i *)&ini[x+8])); _mm_storeu_si128((__m128i *)&pixeli[x+12], _mm_loadu_si128((const __m128i *)&ini[x+12])); x += 16; } #endif { while (x + 4 <= subx) { _mm_storeu_si128((__m128i *)&pixeli[x], _mm_loadu_si128((const __m128i *)&ini[x])); x += 4; } if (x + 2 <= subx) { pixeli[x] = ini[x]; pixeli[x+1] = ini[x+1]; x += 2; } if (x < subx) { pixeli[x] = ini[x]; x++; } } break; case DPSOFTRAST_BLENDMODE_ALPHA: #define FINISHBLEND(blend2, blend1) \ for (;x + 1 < subx;x += 2) \ { \ __m128i src, dst; \ src = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ini[x]), _mm_setzero_si128()); \ dst = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&pixeli[x]), _mm_setzero_si128()); \ blend2; \ _mm_storel_epi64((__m128i *)&pixeli[x], _mm_packus_epi16(dst, dst)); \ } \ if (x < subx) \ { \ __m128i src, dst; \ src = _mm_unpacklo_epi8(_mm_cvtsi32_si128(ini[x]), _mm_setzero_si128()); \ dst = _mm_unpacklo_epi8(_mm_cvtsi32_si128(pixeli[x]), _mm_setzero_si128()); \ blend1; \ pixeli[x] = _mm_cvtsi128_si32(_mm_packus_epi16(dst, dst)); \ x++; \ } FINISHBLEND({ __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); }, { __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(src, dst), 4), _mm_slli_epi16(blend, 4))); }); break; case DPSOFTRAST_BLENDMODE_ADDALPHA: FINISHBLEND({ __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); }, { __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); }); break; case DPSOFTRAST_BLENDMODE_ADD: FINISHBLEND({ dst = _mm_add_epi16(src, dst); }, { dst = _mm_add_epi16(src, dst); }); break; case DPSOFTRAST_BLENDMODE_INVMOD: FINISHBLEND({ dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); }, { dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, src), 8)); }); break; case DPSOFTRAST_BLENDMODE_MUL: FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 8); }); break; case DPSOFTRAST_BLENDMODE_MUL2: FINISHBLEND({ dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }, { dst = _mm_srli_epi16(_mm_mullo_epi16(src, dst), 7); }); break; case DPSOFTRAST_BLENDMODE_SUBALPHA: FINISHBLEND({ __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); }, { __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(src, blend), 8)); }); break; case DPSOFTRAST_BLENDMODE_PSEUDOALPHA: FINISHBLEND({ __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); }, { __m128i blend = _mm_shufflelo_epi16(src, _MM_SHUFFLE(3, 3, 3, 3)); dst = _mm_add_epi16(src, _mm_sub_epi16(dst, _mm_srli_epi16(_mm_mullo_epi16(dst, blend), 8))); }); break; case DPSOFTRAST_BLENDMODE_INVADD: FINISHBLEND({ dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); }, { dst = _mm_add_epi16(dst, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(_mm_set1_epi16(255), dst), 4), _mm_slli_epi16(src, 4))); }); break; } } #endif } static void DPSOFTRAST_Texture2DBGRA8(DPSOFTRAST_Texture *texture, int mip, float x, float y, unsigned char c[4]) // warning: this is SLOW, only use if the optimized per-span functions won't do { const unsigned char * RESTRICT pixelbase; const unsigned char * RESTRICT pixel[4]; int width = texture->mipmap[mip][2], height = texture->mipmap[mip][3]; int wrapmask[2] = { width-1, height-1 }; pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*width; if(texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR) { unsigned int tc[2] = { (unsigned int)floor(x) * (width<<12) - 2048, (unsigned int)floor(y) * (height<<12) - 2048}; unsigned int frac[2] = { tc[0]&0xFFF, tc[1]&0xFFF }; unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; int tci[2] = { (int)tc[0]>>12, (int)tc[1]>>12 }; int tci1[2] = { (int)tci[0] + 1, (int)tci[1] + 1 }; if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; tci1[0] = tci1[0] >= 0 ? (tci1[0] <= wrapmask[0] ? tci1[0] : wrapmask[0]) : 0; tci1[1] = tci1[1] >= 0 ? (tci1[1] <= wrapmask[1] ? tci1[1] : wrapmask[1]) : 0; } else { tci[0] &= wrapmask[0]; tci[1] &= wrapmask[1]; tci1[0] &= wrapmask[0]; tci1[1] &= wrapmask[1]; } pixel[0] = pixelbase + 4 * (tci[0] - tci[1]*width); pixel[1] = pixelbase + 4 * (tci[0] - tci[1]*width); pixel[2] = pixelbase + 4 * (tci[0] - tci1[1]*width); pixel[3] = pixelbase + 4 * (tci[0] - tci1[1]*width); c[0] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3])>>24; c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3])>>24; c[2] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3])>>24; c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3])>>24; } else { int tci[2] = { (int)floor(x) * width, (int)floor(y) * height }; if (texture->flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { tci[0] = tci[0] >= 0 ? (tci[0] <= wrapmask[0] ? tci[0] : wrapmask[0]) : 0; tci[1] = tci[1] >= 0 ? (tci[1] <= wrapmask[1] ? tci[1] : wrapmask[1]) : 0; } else { tci[0] &= wrapmask[0]; tci[1] &= wrapmask[1]; } pixel[0] = pixelbase + 4 * (tci[0] - tci[1]*width); c[0] = pixel[0][0]; c[1] = pixel[0][1]; c[2] = pixel[0][2]; c[3] = pixel[0][3]; } } #if 0 static void DPSOFTRAST_Draw_Span_Texture2DVarying(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float * RESTRICT out4f, int texunitindex, int arrayindex, const float * RESTRICT zf) { int x; int startx = span->startx; int endx = span->endx; int flags; float c[4]; float data[4]; float slope[4]; float tc[2], endtc[2]; float tcscale[2]; unsigned int tci[2]; unsigned int tci1[2]; unsigned int tcimin[2]; unsigned int tcimax[2]; int tciwrapmask[2]; int tciwidth; int filter; int mip; const unsigned char * RESTRICT pixelbase; const unsigned char * RESTRICT pixel[4]; DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; // if no texture is bound, just fill it with white if (!texture) { for (x = startx;x < endx;x++) { out4f[x*4+0] = 1.0f; out4f[x*4+1] = 1.0f; out4f[x*4+2] = 1.0f; out4f[x*4+3] = 1.0f; } return; } mip = triangle->mip[texunitindex]; pixelbase = (unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*texture->mipmap[mip][2]; // if this mipmap of the texture is 1 pixel, just fill it with that color if (texture->mipmap[mip][1] == 4) { c[0] = texture->bytes[2] * (1.0f/255.0f); c[1] = texture->bytes[1] * (1.0f/255.0f); c[2] = texture->bytes[0] * (1.0f/255.0f); c[3] = texture->bytes[3] * (1.0f/255.0f); for (x = startx;x < endx;x++) { out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } return; } filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); flags = texture->flags; tcscale[0] = texture->mipmap[mip][2]; tcscale[1] = texture->mipmap[mip][3]; tciwidth = -texture->mipmap[mip][2]; tcimin[0] = 0; tcimin[1] = 0; tcimax[0] = texture->mipmap[mip][2]-1; tcimax[1] = texture->mipmap[mip][3]-1; tciwrapmask[0] = texture->mipmap[mip][2]-1; tciwrapmask[1] = texture->mipmap[mip][3]-1; endtc[0] = (data[0] + slope[0]*startx) * zf[startx] * tcscale[0]; endtc[1] = (data[1] + slope[1]*startx) * zf[startx] * tcscale[1]; if (filter) { endtc[0] -= 0.5f; endtc[1] -= 0.5f; } for (x = startx;x < endx;) { unsigned int subtc[2]; unsigned int substep[2]; float subscale = 4096.0f/DPSOFTRAST_DRAW_MAXSUBSPAN; int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; if (nextsub >= endx) { nextsub = endsub = endx-1; if (x < nextsub) subscale = 4096.0f / (nextsub - x); } tc[0] = endtc[0]; tc[1] = endtc[1]; endtc[0] = (data[0] + slope[0]*nextsub) * zf[nextsub] * tcscale[0]; endtc[1] = (data[1] + slope[1]*nextsub) * zf[nextsub] * tcscale[1]; if (filter) { endtc[0] -= 0.5f; endtc[1] -= 0.5f; } substep[0] = (endtc[0] - tc[0]) * subscale; substep[1] = (endtc[1] - tc[1]) * subscale; subtc[0] = tc[0] * (1<<12); subtc[1] = tc[1] * (1<<12); if (filter) { if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) { unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; tci[0] = subtc[0]>>12; tci[1] = subtc[1]>>12; tci1[0] = tci[0] + 1; tci1[1] = tci[1] + 1; tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; tci1[0] = tci1[0] >= tcimin[0] ? (tci1[0] <= tcimax[0] ? tci1[0] : tcimax[0]) : tcimin[0]; tci1[1] = tci1[1] >= tcimin[1] ? (tci1[1] <= tcimax[1] ? tci1[1] : tcimax[1]) : tcimin[1]; pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } } else { for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) { unsigned int frac[2] = { subtc[0]&0xFFF, subtc[1]&0xFFF }; unsigned int ifrac[2] = { 0x1000 - frac[0], 0x1000 - frac[1] }; unsigned int lerp[4] = { ifrac[0]*ifrac[1], frac[0]*ifrac[1], ifrac[0]*frac[1], frac[0]*frac[1] }; tci[0] = subtc[0]>>12; tci[1] = subtc[1]>>12; tci1[0] = tci[0] + 1; tci1[1] = tci[1] + 1; tci[0] &= tciwrapmask[0]; tci[1] &= tciwrapmask[1]; tci1[0] &= tciwrapmask[0]; tci1[1] &= tciwrapmask[1]; pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); pixel[1] = pixelbase + 4 * (tci[1]*tciwidth+tci1[0]); pixel[2] = pixelbase + 4 * (tci1[1]*tciwidth+tci[0]); pixel[3] = pixelbase + 4 * (tci1[1]*tciwidth+tci1[0]); c[0] = (pixel[0][2]*lerp[0]+pixel[1][2]*lerp[1]+pixel[2][2]*lerp[2]+pixel[3][2]*lerp[3]) * (1.0f / 0xFF000000); c[1] = (pixel[0][1]*lerp[0]+pixel[1][1]*lerp[1]+pixel[2][1]*lerp[2]+pixel[3][1]*lerp[3]) * (1.0f / 0xFF000000); c[2] = (pixel[0][0]*lerp[0]+pixel[1][0]*lerp[1]+pixel[2][0]*lerp[2]+pixel[3][0]*lerp[3]) * (1.0f / 0xFF000000); c[3] = (pixel[0][3]*lerp[0]+pixel[1][3]*lerp[1]+pixel[2][3]*lerp[2]+pixel[3][3]*lerp[3]) * (1.0f / 0xFF000000); out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } } } else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) { tci[0] = subtc[0]>>12; tci[1] = subtc[1]>>12; tci[0] = tci[0] >= tcimin[0] ? (tci[0] <= tcimax[0] ? tci[0] : tcimax[0]) : tcimin[0]; tci[1] = tci[1] >= tcimin[1] ? (tci[1] <= tcimax[1] ? tci[1] : tcimax[1]) : tcimin[1]; pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); c[0] = pixel[0][2] * (1.0f / 255.0f); c[1] = pixel[0][1] * (1.0f / 255.0f); c[2] = pixel[0][0] * (1.0f / 255.0f); c[3] = pixel[0][3] * (1.0f / 255.0f); out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } } else { for (; x <= endsub; x++, subtc[0] += substep[0], subtc[1] += substep[1]) { tci[0] = subtc[0]>>12; tci[1] = subtc[1]>>12; tci[0] &= tciwrapmask[0]; tci[1] &= tciwrapmask[1]; pixel[0] = pixelbase + 4 * (tci[1]*tciwidth+tci[0]); c[0] = pixel[0][2] * (1.0f / 255.0f); c[1] = pixel[0][1] * (1.0f / 255.0f); c[2] = pixel[0][0] * (1.0f / 255.0f); c[3] = pixel[0][3] * (1.0f / 255.0f); out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } } } } #endif static void DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) { #ifdef SSE_POSSIBLE int x; int startx = span->startx; int endx = span->endx; int flags; __m128 data, slope, tcscale; __m128i tcsize, tcmask, tcoffset, tcmax; __m128 tc, endtc; __m128i subtc, substep, endsubtc; int filter; int mip; int affine; // LordHavoc: optimized affine texturing case unsigned int * RESTRICT outi = (unsigned int *)out4ub; const unsigned char * RESTRICT pixelbase; DPSOFTRAST_Texture *texture = thread->texbound[texunitindex]; // if no texture is bound, just fill it with white if (!texture) { memset(out4ub + startx*4, 255, (span->endx - span->startx)*4); return; } mip = triangle->mip[texunitindex]; pixelbase = (const unsigned char *)texture->bytes + texture->mipmap[mip][0] + texture->mipmap[mip][1] - 4*texture->mipmap[mip][2]; // if this mipmap of the texture is 1 pixel, just fill it with that color if (texture->mipmap[mip][1] == 4) { unsigned int k = *((const unsigned int *)pixelbase); for (x = startx;x < endx;x++) outi[x] = k; return; } affine = zf[startx] == zf[endx-1]; filter = texture->filter & DPSOFTRAST_TEXTURE_FILTER_LINEAR; DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); flags = texture->flags; tcsize = _mm_shuffle_epi32(_mm_loadu_si128((const __m128i *)&texture->mipmap[mip][0]), _MM_SHUFFLE(3, 2, 3, 2)); tcmask = _mm_sub_epi32(tcsize, _mm_set1_epi32(1)); tcscale = _mm_cvtepi32_ps(tcsize); data = _mm_mul_ps(_mm_movelh_ps(data, data), tcscale); slope = _mm_mul_ps(_mm_movelh_ps(slope, slope), tcscale); endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); if (filter) endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); tcoffset = _mm_add_epi32(_mm_slli_epi32(_mm_sub_epi32(_mm_setzero_si128(), _mm_shuffle_epi32(tcsize, _MM_SHUFFLE(0, 0, 0, 0))), 18), _mm_set1_epi32(4)); tcmax = _mm_packs_epi32(tcmask, tcmask); for (x = startx;x < endx;) { int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; __m128 subscale = _mm_set1_ps(65536.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); if (nextsub >= endx || affine) { nextsub = endsub = endx-1; if (x < nextsub) subscale = _mm_set1_ps(65536.0f / (nextsub - x)); } tc = endtc; subtc = endsubtc; endtc = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); if (filter) endtc = _mm_sub_ps(endtc, _mm_set1_ps(0.5f)); substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endtc, tc), subscale)); endsubtc = _mm_cvtps_epi32(_mm_mul_ps(endtc, _mm_set1_ps(65536.0f))); subtc = _mm_unpacklo_epi64(subtc, _mm_add_epi32(subtc, substep)); substep = _mm_slli_epi32(substep, 1); if (filter) { __m128i tcrange = _mm_srai_epi32(_mm_unpacklo_epi64(subtc, _mm_add_epi32(endsubtc, substep)), 16); if (_mm_movemask_epi8(_mm_andnot_si128(_mm_cmplt_epi32(tcrange, _mm_setzero_si128()), _mm_cmplt_epi32(tcrange, tcmask))) == 0xFFFF) { int stride = _mm_cvtsi128_si32(tcoffset)>>16; for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) { const unsigned char * RESTRICT ptr1, * RESTRICT ptr2; __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, pix3, pix4, fracm; tci = _mm_madd_epi16(tci, tcoffset); ptr1 = pixelbase + _mm_cvtsi128_si32(tci); ptr2 = pixelbase + _mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2))); pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); pix3 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr2), _mm_setzero_si128()); pix4 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr2 + stride)), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix3 = _mm_add_epi16(pix3, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); pix2 = _mm_unpacklo_epi64(pix1, pix3); pix4 = _mm_unpackhi_epi64(pix1, pix3); pix2 = _mm_add_epi16(pix2, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); } if (x <= endsub) { const unsigned char * RESTRICT ptr1; __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), pix1, pix2, fracm; tci = _mm_madd_epi16(tci, tcoffset); ptr1 = pixelbase + _mm_cvtsi128_si32(tci); pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)ptr1), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)(ptr1 + stride)), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); x++; } } else if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) { __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix3 = _mm_add_epi16(pix3, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); pix2 = _mm_unpacklo_epi64(pix1, pix3); pix4 = _mm_unpackhi_epi64(pix1, pix3); pix2 = _mm_add_epi16(pix2, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); } if (x <= endsub) { __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; tci = _mm_min_epi16(_mm_max_epi16(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), _mm_setzero_si128()), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); x++; } } else { for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) { __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, pix3, pix4, fracm; tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); tci = _mm_shuffle_epi32(_mm_shufflehi_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 2, 3, 2)); tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix3 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix4 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix3 = _mm_add_epi16(pix3, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix3), 1), _mm_shuffle_epi32(_mm_shufflehi_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(3, 2, 3, 2)))); pix2 = _mm_unpacklo_epi64(pix1, pix3); pix4 = _mm_unpackhi_epi64(pix1, pix3); pix2 = _mm_add_epi16(pix2, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix4, pix2), 1), _mm_shufflehi_epi16(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)), _MM_SHUFFLE(0, 0, 0, 0)))); _mm_storel_epi64((__m128i *)&outi[x], _mm_packus_epi16(pix2, _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 2, 3, 2)))); } if (x <= endsub) { __m128i tci = _mm_shuffle_epi32(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(1, 0, 1, 0)), pix1, pix2, fracm; tci = _mm_and_si128(_mm_add_epi16(tci, _mm_setr_epi32(0, 1, 0x10000, 0x10001)), tcmax); tci = _mm_madd_epi16(tci, tcoffset); pix1 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(1, 1, 1, 1)))])), _mm_setzero_si128()); pix2 = _mm_unpacklo_epi8(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]), _mm_cvtsi32_si128(*(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(3, 3, 3, 3)))])), _mm_setzero_si128()); fracm = _mm_srli_epi16(subtc, 1); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shuffle_epi32(_mm_shufflelo_epi16(fracm, _MM_SHUFFLE(2, 2, 2, 2)), _MM_SHUFFLE(1, 0, 1, 0)))); pix2 = _mm_shuffle_epi32(pix1, _MM_SHUFFLE(3, 2, 3, 2)); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 1), _mm_shufflelo_epi16(fracm, _MM_SHUFFLE(0, 0, 0, 0)))); outi[x] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); x++; } } } else { if (flags & DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE) { for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) { __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); tci = _mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); tci = _mm_madd_epi16(tci, tcoffset); outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; } if (x <= endsub) { __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); tci =_mm_min_epi16(_mm_max_epi16(tci, _mm_setzero_si128()), tcmax); tci = _mm_madd_epi16(tci, tcoffset); outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; x++; } } else { for (; x + 1 <= endsub; x += 2, subtc = _mm_add_epi32(subtc, substep)) { __m128i tci = _mm_shufflehi_epi16(_mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)), _MM_SHUFFLE(3, 1, 3, 1)); tci = _mm_and_si128(tci, tcmax); tci = _mm_madd_epi16(tci, tcoffset); outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; outi[x+1] = *(const int *)&pixelbase[_mm_cvtsi128_si32(_mm_shuffle_epi32(tci, _MM_SHUFFLE(2, 2, 2, 2)))]; } if (x <= endsub) { __m128i tci = _mm_shufflelo_epi16(subtc, _MM_SHUFFLE(3, 1, 3, 1)); tci = _mm_and_si128(tci, tcmax); tci = _mm_madd_epi16(tci, tcoffset); outi[x] = *(const int *)&pixelbase[_mm_cvtsi128_si32(tci)]; x++; } } } } #endif } static void DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char * RESTRICT out4ub, int texunitindex, int arrayindex, const float * RESTRICT zf) { // TODO: IMPLEMENT memset(out4ub + span->startx*4, 255, (span->startx - span->endx)*4); } static float DPSOFTRAST_SampleShadowmap(const float *vector) { // TODO: IMPLEMENT return 1.0f; } #if 0 static void DPSOFTRAST_Draw_Span_MultiplyVarying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, int arrayindex, const float *zf) { int x; int startx = span->startx; int endx = span->endx; float c[4]; float data[4]; float slope[4]; float z; DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); for (x = startx;x < endx;x++) { z = zf[x]; c[0] = (data[0] + slope[0]*x) * z; c[1] = (data[1] + slope[1]*x) * z; c[2] = (data[2] + slope[2]*x) * z; c[3] = (data[3] + slope[3]*x) * z; out4f[x*4+0] = in4f[x*4+0] * c[0]; out4f[x*4+1] = in4f[x*4+1] * c[1]; out4f[x*4+2] = in4f[x*4+2] * c[2]; out4f[x*4+3] = in4f[x*4+3] * c[3]; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_Varying(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, int arrayindex, const float *zf) { int x; int startx = span->startx; int endx = span->endx; float c[4]; float data[4]; float slope[4]; float z; DPSOFTRAST_CALCATTRIB4F(triangle, span, data, slope, arrayindex); for (x = startx;x < endx;x++) { z = zf[x]; c[0] = (data[0] + slope[0]*x) * z; c[1] = (data[1] + slope[1]*x) * z; c[2] = (data[2] + slope[2]*x) * z; c[3] = (data[3] + slope[3]*x) * z; out4f[x*4+0] = c[0]; out4f[x*4+1] = c[1]; out4f[x*4+2] = c[2]; out4f[x*4+3] = c[3]; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_AddBloom(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f, const float *subcolor) { int x, startx = span->startx, endx = span->endx; float c[4], localcolor[4]; localcolor[0] = subcolor[0]; localcolor[1] = subcolor[1]; localcolor[2] = subcolor[2]; localcolor[3] = subcolor[3]; for (x = startx;x < endx;x++) { c[0] = inb4f[x*4+0] - localcolor[0];if (c[0] < 0.0f) c[0] = 0.0f; c[1] = inb4f[x*4+1] - localcolor[1];if (c[1] < 0.0f) c[1] = 0.0f; c[2] = inb4f[x*4+2] - localcolor[2];if (c[2] < 0.0f) c[2] = 0.0f; c[3] = inb4f[x*4+3] - localcolor[3];if (c[3] < 0.0f) c[3] = 0.0f; out4f[x*4+0] = ina4f[x*4+0] + c[0]; out4f[x*4+1] = ina4f[x*4+1] + c[1]; out4f[x*4+2] = ina4f[x*4+2] + c[2]; out4f[x*4+3] = ina4f[x*4+3] + c[3]; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_MultiplyBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) { int x, startx = span->startx, endx = span->endx; for (x = startx;x < endx;x++) { out4f[x*4+0] = ina4f[x*4+0] * inb4f[x*4+0]; out4f[x*4+1] = ina4f[x*4+1] * inb4f[x*4+1]; out4f[x*4+2] = ina4f[x*4+2] * inb4f[x*4+2]; out4f[x*4+3] = ina4f[x*4+3] * inb4f[x*4+3]; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_AddBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) { int x, startx = span->startx, endx = span->endx; for (x = startx;x < endx;x++) { out4f[x*4+0] = ina4f[x*4+0] + inb4f[x*4+0]; out4f[x*4+1] = ina4f[x*4+1] + inb4f[x*4+1]; out4f[x*4+2] = ina4f[x*4+2] + inb4f[x*4+2]; out4f[x*4+3] = ina4f[x*4+3] + inb4f[x*4+3]; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_MixBuffers(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *ina4f, const float *inb4f) { int x, startx = span->startx, endx = span->endx; float a, b; for (x = startx;x < endx;x++) { a = 1.0f - inb4f[x*4+3]; b = inb4f[x*4+3]; out4f[x*4+0] = ina4f[x*4+0] * a + inb4f[x*4+0] * b; out4f[x*4+1] = ina4f[x*4+1] * a + inb4f[x*4+1] * b; out4f[x*4+2] = ina4f[x*4+2] * a + inb4f[x*4+2] * b; out4f[x*4+3] = ina4f[x*4+3] * a + inb4f[x*4+3] * b; } } #endif #if 0 static void DPSOFTRAST_Draw_Span_MixUniformColor(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, float *out4f, const float *in4f, const float *color) { int x, startx = span->startx, endx = span->endx; float localcolor[4], ilerp, lerp; localcolor[0] = color[0]; localcolor[1] = color[1]; localcolor[2] = color[2]; localcolor[3] = color[3]; ilerp = 1.0f - localcolor[3]; lerp = localcolor[3]; for (x = startx;x < endx;x++) { out4f[x*4+0] = in4f[x*4+0] * ilerp + localcolor[0] * lerp; out4f[x*4+1] = in4f[x*4+1] * ilerp + localcolor[1] * lerp; out4f[x*4+2] = in4f[x*4+2] * ilerp + localcolor[2] * lerp; out4f[x*4+3] = in4f[x*4+3] * ilerp + localcolor[3] * lerp; } } #endif static void DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, int arrayindex, const float *zf) { #ifdef SSE_POSSIBLE int x; int startx = span->startx; int endx = span->endx; __m128 data, slope; __m128 mod, endmod; __m128i submod, substep, endsubmod; DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); for (x = startx; x < endx;) { int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; __m128 subscale = _mm_set1_ps(256.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); if (nextsub >= endx) { nextsub = endsub = endx-1; if (x < nextsub) subscale = _mm_set1_ps(256.0f / (nextsub - x)); } mod = endmod; submod = endsubmod; endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(256.0f))); submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); substep = _mm_packs_epi32(substep, substep); for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) { __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&in4ub[x*4])); pix = _mm_mulhi_epu16(pix, submod); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); } if (x <= endsub) { __m128i pix = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&in4ub[x*4])); pix = _mm_mulhi_epu16(pix, submod); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); x++; } } #endif } static void DPSOFTRAST_Draw_Span_VaryingBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, int arrayindex, const float *zf) { #ifdef SSE_POSSIBLE int x; int startx = span->startx; int endx = span->endx; __m128 data, slope; __m128 mod, endmod; __m128i submod, substep, endsubmod; DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))), _mm_load1_ps(&zf[startx])); endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); for (x = startx; x < endx;) { int nextsub = x + DPSOFTRAST_DRAW_MAXSUBSPAN, endsub = nextsub - 1; __m128 subscale = _mm_set1_ps(4095.0f/DPSOFTRAST_DRAW_MAXSUBSPAN); if (nextsub >= endx) { nextsub = endsub = endx-1; if (x < nextsub) subscale = _mm_set1_ps(4095.0f / (nextsub - x)); } mod = endmod; submod = endsubmod; endmod = _mm_mul_ps(_mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(nextsub))), _mm_load1_ps(&zf[nextsub])); substep = _mm_cvtps_epi32(_mm_mul_ps(_mm_sub_ps(endmod, mod), subscale)); endsubmod = _mm_cvtps_epi32(_mm_mul_ps(endmod, _mm_set1_ps(4095.0f))); submod = _mm_packs_epi32(submod, _mm_add_epi32(submod, substep)); substep = _mm_packs_epi32(substep, substep); for (; x + 1 <= endsub; x += 2, submod = _mm_add_epi16(submod, substep)) { __m128i pix = _mm_srai_epi16(submod, 4); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); } if (x <= endsub) { __m128i pix = _mm_srai_epi16(submod, 4); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); x++; } } #endif } static void DPSOFTRAST_Draw_Span_AddBloomBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *subcolor) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(subcolor), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)); localcolor = _mm_packs_epi32(localcolor, localcolor); for (x = startx;x+2 <= endx;x+=2) { __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); } if (x < endx) { __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); pix1 = _mm_add_epi16(pix1, _mm_subs_epu16(pix2, localcolor)); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); } #endif } static void DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; for (x = startx;x+2 <= endx;x+=2) { __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); pix1 = _mm_mulhi_epu16(pix1, pix2); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); } if (x < endx) { __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); pix1 = _mm_mulhi_epu16(pix1, pix2); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); } #endif } static void DPSOFTRAST_Draw_Span_AddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; for (x = startx;x+2 <= endx;x+=2) { __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); pix1 = _mm_add_epi16(pix1, pix2); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); } if (x < endx) { __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); pix1 = _mm_add_epi16(pix1, pix2); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); } #endif } #if 0 static void DPSOFTRAST_Draw_Span_TintedAddBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub, const float *inbtintbgra) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; __m128i tint = _mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(inbtintbgra), _mm_set1_ps(256.0f))); tint = _mm_packs_epi32(tint, tint); for (x = startx;x+2 <= endx;x+=2) { __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_loadl_epi64((const __m128i *)&inb4ub[x*4])); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); } if (x < endx) { __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&inb4ub[x*4])); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epu16(tint, pix2)); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); } #endif } #endif static void DPSOFTRAST_Draw_Span_MixBuffersBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *ina4ub, const unsigned char *inb4ub) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; for (x = startx;x+2 <= endx;x+=2) { __m128i pix1 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&inb4ub[x*4]), _mm_setzero_si128()); __m128i blend = _mm_shufflehi_epi16(_mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix1, pix1)); } if (x < endx) { __m128i pix1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&ina4ub[x*4]), _mm_setzero_si128()); __m128i pix2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&inb4ub[x*4]), _mm_setzero_si128()); __m128i blend = _mm_shufflelo_epi16(pix2, _MM_SHUFFLE(3, 3, 3, 3)); pix1 = _mm_add_epi16(pix1, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(pix2, pix1), 4), _mm_slli_epi16(blend, 4))); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix1, pix1)); } #endif } static void DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span, unsigned char *out4ub, const unsigned char *in4ub, const float *color) { #ifdef SSE_POSSIBLE int x, startx = span->startx, endx = span->endx; __m128i localcolor = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_loadu_ps(color), _mm_set1_ps(255.0f))), _MM_SHUFFLE(3, 0, 1, 2)), blend; localcolor = _mm_packs_epi32(localcolor, localcolor); blend = _mm_slli_epi16(_mm_shufflehi_epi16(_mm_shufflelo_epi16(localcolor, _MM_SHUFFLE(3, 3, 3, 3)), _MM_SHUFFLE(3, 3, 3, 3)), 4); for (x = startx;x+2 <= endx;x+=2) { __m128i pix = _mm_unpacklo_epi8(_mm_loadl_epi64((const __m128i *)&in4ub[x*4]), _mm_setzero_si128()); pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); _mm_storel_epi64((__m128i *)&out4ub[x*4], _mm_packus_epi16(pix, pix)); } if (x < endx) { __m128i pix = _mm_unpacklo_epi8(_mm_cvtsi32_si128(*(const int *)&in4ub[x*4]), _mm_setzero_si128()); pix = _mm_add_epi16(pix, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(localcolor, pix), 4), blend)); *(int *)&out4ub[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); } #endif } static void DPSOFTRAST_VertexShader_Generic(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); if (dpsoftrast.shader_permutation & SHADERPERMUTATION_SPECULAR) DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); } static void DPSOFTRAST_PixelShader_Generic(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_FIRST, 2, buffer_z); DPSOFTRAST_Draw_Span_MultiplyVaryingBGRA8(triangle, span, buffer_FragColorbgra8, buffer_texture_colorbgra8, 1, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_SECOND, 2, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { // multiply DPSOFTRAST_Draw_Span_MultiplyBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); } else if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { // add DPSOFTRAST_Draw_Span_AddBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); } else if (thread->shader_permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) { // alphablend DPSOFTRAST_Draw_Span_MixBuffersBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_lightmapbgra8); } } } else DPSOFTRAST_Draw_Span_VaryingBGRA8(triangle, span, buffer_FragColorbgra8, 1, buffer_z); if(thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) { int x; for (x = span->startx;x < span->endx;x++) buffer_FragColorbgra8[x*4+3] = buffer_FragColorbgra8[x*4+3] * thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_PostProcess(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD4); } static void DPSOFTRAST_PixelShader_PostProcess(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { // TODO: optimize!! at the very least there is no reason to use texture sampling on the frame texture float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_FragColorbgra8, GL20TU_FIRST, 2, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_BLOOM) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_SECOND, 3, buffer_z); DPSOFTRAST_Draw_Span_AddBloomBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, buffer_texture_colorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_BloomColorSubtract * 4); } DPSOFTRAST_Draw_Span_MixUniformColorBGRA8(triangle, span, buffer_FragColorbgra8, buffer_FragColorbgra8, thread->uniform4f + DPSOFTRAST_UNIFORM_ViewTintColor * 4); if (thread->shader_permutation & SHADERPERMUTATION_SATURATION) { // TODO: implement saturation } if (thread->shader_permutation & SHADERPERMUTATION_GAMMARAMPS) { // TODO: implement gammaramps } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_Depth_Or_Shadow(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); } static void DPSOFTRAST_PixelShader_Depth_Or_Shadow(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { // this is never called (because colormask is off when this shader is used) float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_FlatColor(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); } static void DPSOFTRAST_PixelShader_FlatColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { #ifdef SSE_POSSIBLE unsigned char * RESTRICT pixelmask = span->pixelmask; unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; int x, startx = span->startx, endx = span->endx; __m128i Color_Ambientm; float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) pixel = buffer_FragColorbgra8; Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); for (x = startx;x < endx;x++) { __m128i color, pix; if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) { __m128i pix2; color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); pix = _mm_mulhi_epu16(Color_Ambientm, _mm_unpacklo_epi8(_mm_setzero_si128(), color)); pix2 = _mm_mulhi_epu16(Color_Ambientm, _mm_unpackhi_epi8(_mm_setzero_si128(), color)); _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); x += 3; continue; } if (!pixelmask[x]) continue; color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); pix = _mm_mulhi_epu16(Color_Ambientm, color); *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); } if (pixel == buffer_FragColorbgra8) DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); #endif } static void DPSOFTRAST_VertexShader_VertexColor(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_COLOR); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); } static void DPSOFTRAST_PixelShader_VertexColor(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { #ifdef SSE_POSSIBLE unsigned char * RESTRICT pixelmask = span->pixelmask; unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; int x, startx = span->startx, endx = span->endx; __m128i Color_Ambientm, Color_Diffusem; __m128 data, slope; float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; int arrayindex = DPSOFTRAST_ARRAY_COLOR; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, 2, buffer_z); if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) pixel = buffer_FragColorbgra8; Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(4096.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); DPSOFTRAST_CALCATTRIB(triangle, span, data, slope, arrayindex); data = _mm_shuffle_ps(data, data, _MM_SHUFFLE(3, 0, 1, 2)); slope = _mm_shuffle_ps(slope, slope, _MM_SHUFFLE(3, 0, 1, 2)); data = _mm_add_ps(data, _mm_mul_ps(slope, _mm_set1_ps(startx))); data = _mm_mul_ps(data, _mm_set1_ps(4096.0f)); slope = _mm_mul_ps(slope, _mm_set1_ps(4096.0f)); for (x = startx;x < endx;x++, data = _mm_add_ps(data, slope)) { __m128i color, mod, pix; if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) { __m128i pix2, mod2; __m128 z = _mm_loadu_ps(&buffer_z[x]); color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(0, 0, 0, 0)))); data = _mm_add_ps(data, slope); mod = _mm_packs_epi32(mod, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(1, 1, 1, 1))))); data = _mm_add_ps(data, slope); mod2 = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(2, 2, 2, 2)))); data = _mm_add_ps(data, slope); mod2 = _mm_packs_epi32(mod2, _mm_cvtps_epi32(_mm_mul_ps(data, _mm_shuffle_ps(z, z, _MM_SHUFFLE(3, 3, 3, 3))))); pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod), Color_Ambientm), _mm_unpacklo_epi8(_mm_setzero_si128(), color)); pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, mod2), Color_Ambientm), _mm_unpackhi_epi8(_mm_setzero_si128(), color)); _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); x += 3; continue; } if (!pixelmask[x]) continue; color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); mod = _mm_cvtps_epi32(_mm_mul_ps(data, _mm_load1_ps(&buffer_z[x]))); mod = _mm_packs_epi32(mod, mod); pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(mod, Color_Diffusem), Color_Ambientm), color); *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); } if (pixel == buffer_FragColorbgra8) DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); #endif } static void DPSOFTRAST_VertexShader_Lightmap(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); } static void DPSOFTRAST_PixelShader_Lightmap(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { #ifdef SSE_POSSIBLE unsigned char * RESTRICT pixelmask = span->pixelmask; unsigned char * RESTRICT pixel = (unsigned char *)dpsoftrast.fb_colorpixels[0] + (span->y * dpsoftrast.fb_width + span->x) * 4; int x, startx = span->startx, endx = span->endx; __m128i Color_Ambientm, Color_Diffusem, Color_Glowm, Color_AmbientGlowm; float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); if ((thread->shader_permutation & SHADERPERMUTATION_ALPHAKILL) || thread->fb_blendmode != DPSOFTRAST_BLENDMODE_OPAQUE) pixel = buffer_FragColorbgra8; Color_Ambientm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Ambientm = _mm_and_si128(Color_Ambientm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Ambientm = _mm_or_si128(Color_Ambientm, _mm_setr_epi32(0, 0, 0, (int)(thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]*255.0f))); Color_Ambientm = _mm_packs_epi32(Color_Ambientm, Color_Ambientm); Color_Diffusem = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Diffusem = _mm_and_si128(Color_Diffusem, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Diffusem = _mm_packs_epi32(Color_Diffusem, Color_Diffusem); if (thread->shader_permutation & SHADERPERMUTATION_GLOW) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); Color_Glowm = _mm_shuffle_epi32(_mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(&thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4]), _mm_set1_ps(256.0f))), _MM_SHUFFLE(3, 0, 1, 2)); Color_Glowm = _mm_and_si128(Color_Glowm, _mm_setr_epi32(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0)); Color_Glowm = _mm_packs_epi32(Color_Glowm, Color_Glowm); Color_AmbientGlowm = _mm_unpacklo_epi64(Color_Ambientm, Color_Glowm); for (x = startx;x < endx;x++) { __m128i color, lightmap, glow, pix; if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) { __m128i pix2; color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); glow = _mm_loadu_si128((const __m128i *)&buffer_texture_glowbgra8[x*4]); pix = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), _mm_unpacklo_epi8(_mm_setzero_si128(), color)), _mm_mulhi_epu16(Color_Glowm, _mm_unpacklo_epi8(_mm_setzero_si128(), glow))); pix2 = _mm_add_epi16(_mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), _mm_unpackhi_epi8(_mm_setzero_si128(), color)), _mm_mulhi_epu16(Color_Glowm, _mm_unpackhi_epi8(_mm_setzero_si128(), glow))); _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); x += 3; continue; } if (!pixelmask[x]) continue; color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); glow = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_glowbgra8[x*4])); pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, lightmap), Color_AmbientGlowm), _mm_unpacklo_epi64(color, glow)); pix = _mm_add_epi16(pix, _mm_shuffle_epi32(pix, _MM_SHUFFLE(3, 2, 3, 2))); *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); } } else { for (x = startx;x < endx;x++) { __m128i color, lightmap, pix; if (x + 4 <= endx && *(const unsigned int *)&pixelmask[x] == 0x01010101) { __m128i pix2; color = _mm_loadu_si128((const __m128i *)&buffer_texture_colorbgra8[x*4]); lightmap = _mm_loadu_si128((const __m128i *)&buffer_texture_lightmapbgra8[x*4]); pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpacklo_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), _mm_unpacklo_epi8(_mm_setzero_si128(), color)); pix2 = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(Color_Diffusem, _mm_unpackhi_epi8(_mm_setzero_si128(), lightmap)), Color_Ambientm), _mm_unpackhi_epi8(_mm_setzero_si128(), color)); _mm_storeu_si128((__m128i *)&pixel[x*4], _mm_packus_epi16(pix, pix2)); x += 3; continue; } if (!pixelmask[x]) continue; color = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_colorbgra8[x*4])); lightmap = _mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(*(const int *)&buffer_texture_lightmapbgra8[x*4])); pix = _mm_mulhi_epu16(_mm_add_epi16(_mm_mulhi_epu16(lightmap, Color_Diffusem), Color_Ambientm), color); *(int *)&pixel[x*4] = _mm_cvtsi128_si32(_mm_packus_epi16(pix, pix)); } } if (pixel == buffer_FragColorbgra8) DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); #endif } void DPSOFTRAST_VertexShader_LightDirection(void); void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); static void DPSOFTRAST_VertexShader_FakeLight(void) { DPSOFTRAST_VertexShader_LightDirection(); } static void DPSOFTRAST_PixelShader_FakeLight(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); } static void DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace(void) { DPSOFTRAST_VertexShader_LightDirection(); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); } static void DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); } static void DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace(void) { DPSOFTRAST_VertexShader_LightDirection(); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); } static void DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { DPSOFTRAST_PixelShader_LightDirection(thread, triangle, span); } void DPSOFTRAST_VertexShader_LightDirection(void) { int i; int numvertices = dpsoftrast.numvertices; float LightDir[4]; float LightVector[4]; float EyePosition[4]; float EyeVectorModelSpace[4]; float EyeVector[4]; float position[4]; float svector[4]; float tvector[4]; float normal[4]; LightDir[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+0]; LightDir[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+1]; LightDir[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+2]; LightDir[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightDir*4+3]; EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); for (i = 0;i < numvertices;i++) { position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; LightVector[0] = svector[0] * LightDir[0] + svector[1] * LightDir[1] + svector[2] * LightDir[2]; LightVector[1] = tvector[0] * LightDir[0] + tvector[1] * LightDir[1] + tvector[2] * LightDir[2]; LightVector[2] = normal[0] * LightDir[0] + normal[1] * LightDir[1] + normal[2] * LightDir[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+0] = LightVector[0]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+1] = LightVector[1]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+2] = LightVector[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD5][i*4+3] = 0.0f; EyeVectorModelSpace[0] = EyePosition[0] - position[0]; EyeVectorModelSpace[1] = EyePosition[1] - position[1]; EyeVectorModelSpace[2] = EyePosition[2] - position[2]; EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; } DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); } #define DPSOFTRAST_Min(a,b) ((a) < (b) ? (a) : (b)) #define DPSOFTRAST_Max(a,b) ((a) > (b) ? (a) : (b)) #define DPSOFTRAST_Vector3Dot(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) #define DPSOFTRAST_Vector3LengthSquared(v) (DPSOFTRAST_Vector3Dot((v),(v))) #define DPSOFTRAST_Vector3Length(v) (sqrt(DPSOFTRAST_Vector3LengthSquared(v))) #define DPSOFTRAST_Vector3Normalize(v)\ do\ {\ float len = sqrt(DPSOFTRAST_Vector3Dot(v,v));\ if (len)\ {\ len = 1.0f / len;\ v[0] *= len;\ v[1] *= len;\ v[2] *= len;\ }\ }\ while(0) void DPSOFTRAST_PixelShader_LightDirection(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_glowbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_deluxemapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_lightmapbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; int x, startx = span->startx, endx = span->endx; float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], Color_Glow[4], Color_Pants[4], Color_Shirt[4], LightColor[4]; float LightVectordata[4]; float LightVectorslope[4]; float EyeVectordata[4]; float EyeVectorslope[4]; float VectorSdata[4]; float VectorSslope[4]; float VectorTdata[4]; float VectorTslope[4]; float VectorRdata[4]; float VectorRslope[4]; float z; float diffusetex[4]; float glosstex[4]; float surfacenormal[4]; float lightnormal[4]; float lightnormal_modelspace[4]; float eyenormal[4]; float specularnormal[4]; float diffuse; float specular; float SpecularPower; int d[4]; Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; Color_Glow[3] = 0.0f; Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; Color_Pants[3] = 0.0f; Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; Color_Shirt[3] = 0.0f; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); } if (thread->shader_permutation & SHADERPERMUTATION_GLOW) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glowbgra8, GL20TU_GLOW, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); } if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) { Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; Color_Diffuse[3] = 0.0f; LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; LightColor[3] = 0.0f; DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; Color_Specular[3] = 0.0f; SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) { DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); } else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); } else if(thread->shader_mode == SHADERMODE_FAKELIGHT) { // nothing of this needed } else { DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); } for (x = startx;x < endx;x++) { z = buffer_z[x]; diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; } glosstex[0] = buffer_texture_glossbgra8[x*4+0]; glosstex[1] = buffer_texture_glossbgra8[x*4+1]; glosstex[2] = buffer_texture_glossbgra8[x*4+2]; glosstex[3] = buffer_texture_glossbgra8[x*4+3]; surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(surfacenormal); if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) { // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" DPSOFTRAST_Vector3Normalize(lightnormal); // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; { float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; } } else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) { lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; { float f = 1.0f / 256.0f; LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; } } else if(thread->shader_mode == SHADERMODE_FAKELIGHT) { lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); LightColor[0] = 1.0; LightColor[1] = 1.0; LightColor[2] = 1.0; } else { lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); } diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; if(thread->shader_exactspecularmath) { // reflect lightnormal at surfacenormal, take the negative of that // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal float f; f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; // dot of this and normalize(EyeVectorFogDepth.xyz) eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(eyenormal); specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; } else { eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(eyenormal); specularnormal[0] = lightnormal[0] + eyenormal[0]; specularnormal[1] = lightnormal[1] + eyenormal[1]; specularnormal[2] = lightnormal[2] + eyenormal[2]; DPSOFTRAST_Vector3Normalize(specularnormal); specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; } specular = pow(specular, 1.0f + SpecularPower * glosstex[3]); if (thread->shader_permutation & SHADERPERMUTATION_GLOW) { d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; } else { d[0] = (int)( diffusetex[0] * Color_Ambient[0] + (diffusetex[0] * Color_Diffuse[0] * diffuse + glosstex[0] * Color_Specular[0] * specular) * LightColor[0]);if (d[0] > 255) d[0] = 255; d[1] = (int)( diffusetex[1] * Color_Ambient[1] + (diffusetex[1] * Color_Diffuse[1] * diffuse + glosstex[1] * Color_Specular[1] * specular) * LightColor[1]);if (d[1] > 255) d[1] = 255; d[2] = (int)( diffusetex[2] * Color_Ambient[2] + (diffusetex[2] * Color_Diffuse[2] * diffuse + glosstex[2] * Color_Specular[2] * specular) * LightColor[2]);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) { Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; Color_Diffuse[3] = 0.0f; LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; LightColor[3] = 0.0f; DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) { DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorSdata, VectorSslope, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorTdata, VectorTslope, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_CALCATTRIB4F(triangle, span, VectorRdata, VectorRslope, DPSOFTRAST_ARRAY_TEXCOORD3); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); } else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_lightmapbgra8, GL20TU_LIGHTMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_deluxemapbgra8, GL20TU_DELUXEMAP, DPSOFTRAST_ARRAY_TEXCOORD4, buffer_z); } else if(thread->shader_mode == SHADERMODE_FAKELIGHT) { DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); } else { DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD5); } for (x = startx;x < endx;x++) { z = buffer_z[x]; diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(surfacenormal); if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE) { // myhalf3 lightnormal_modelspace = myhalf3(dp_texture2D(Texture_Deluxemap, TexCoordSurfaceLightmap.zw)) * 2.0 + myhalf3(-1.0, -1.0, -1.0);\n"; lightnormal_modelspace[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; lightnormal_modelspace[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; lightnormal_modelspace[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; // lightnormal.x = dot(lightnormal_modelspace, myhalf3(VectorS));\n" lightnormal[0] = lightnormal_modelspace[0] * (VectorSdata[0] + VectorSslope[0] * x) + lightnormal_modelspace[1] * (VectorSdata[1] + VectorSslope[1] * x) + lightnormal_modelspace[2] * (VectorSdata[2] + VectorSslope[2] * x); // lightnormal.y = dot(lightnormal_modelspace, myhalf3(VectorT));\n" lightnormal[1] = lightnormal_modelspace[0] * (VectorTdata[0] + VectorTslope[0] * x) + lightnormal_modelspace[1] * (VectorTdata[1] + VectorTslope[1] * x) + lightnormal_modelspace[2] * (VectorTdata[2] + VectorTslope[2] * x); // lightnormal.z = dot(lightnormal_modelspace, myhalf3(VectorR));\n" lightnormal[2] = lightnormal_modelspace[0] * (VectorRdata[0] + VectorRslope[0] * x) + lightnormal_modelspace[1] * (VectorRdata[1] + VectorRslope[1] * x) + lightnormal_modelspace[2] * (VectorRdata[2] + VectorRslope[2] * x); // lightnormal = normalize(lightnormal); // VectorS/T/R are not always perfectly normalized, and EXACTSPECULARMATH is very picky about this\n" DPSOFTRAST_Vector3Normalize(lightnormal); // myhalf3 lightcolor = myhalf3(dp_texture2D(Texture_Lightmap, TexCoordSurfaceLightmap.zw));\n"; { float f = 1.0f / (256.0f * max(0.25f, lightnormal[2])); LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; } } else if(thread->shader_mode == SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE) { lightnormal[0] = buffer_texture_deluxemapbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; lightnormal[1] = buffer_texture_deluxemapbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; lightnormal[2] = buffer_texture_deluxemapbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; { float f = 1.0f / 256.0f; LightColor[0] = buffer_texture_lightmapbgra8[x*4+0] * f; LightColor[1] = buffer_texture_lightmapbgra8[x*4+1] * f; LightColor[2] = buffer_texture_lightmapbgra8[x*4+2] * f; } } else if(thread->shader_mode == SHADERMODE_FAKELIGHT) { lightnormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; lightnormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; lightnormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); LightColor[0] = 1.0; LightColor[1] = 1.0; LightColor[2] = 1.0; } else { lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); } diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; if (thread->shader_permutation & SHADERPERMUTATION_GLOW) { d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; } else { d[0] = (int)( + diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse * LightColor[0]));if (d[0] > 255) d[0] = 255; d[1] = (int)( + diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse * LightColor[1]));if (d[1] > 255) d[1] = 255; d[2] = (int)( + diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse * LightColor[2]));if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * (Color_Ambient[3] ));if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } else { for (x = startx;x < endx;x++) { // z = buffer_z[x]; diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; if (thread->shader_permutation & SHADERPERMUTATION_GLOW) { d[0] = (int)(buffer_texture_glowbgra8[x*4+0] * Color_Glow[0] + diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; d[1] = (int)(buffer_texture_glowbgra8[x*4+1] * Color_Glow[1] + diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; d[2] = (int)(buffer_texture_glowbgra8[x*4+2] * Color_Glow[2] + diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; } else { d[0] = (int)( diffusetex[0] * Color_Ambient[0]);if (d[0] > 255) d[0] = 255; d[1] = (int)( diffusetex[1] * Color_Ambient[1]);if (d[1] > 255) d[1] = 255; d[2] = (int)( diffusetex[2] * Color_Ambient[2]);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] * Color_Ambient[3]);if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_LightSource(void) { int i; int numvertices = dpsoftrast.numvertices; float LightPosition[4]; float LightVector[4]; float LightVectorModelSpace[4]; float EyePosition[4]; float EyeVectorModelSpace[4]; float EyeVector[4]; float position[4]; float svector[4]; float tvector[4]; float normal[4]; LightPosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+0]; LightPosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+1]; LightPosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+2]; LightPosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_LightPosition*4+3]; EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD4); for (i = 0;i < numvertices;i++) { position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; LightVectorModelSpace[0] = LightPosition[0] - position[0]; LightVectorModelSpace[1] = LightPosition[1] - position[1]; LightVectorModelSpace[2] = LightPosition[2] - position[2]; LightVector[0] = svector[0] * LightVectorModelSpace[0] + svector[1] * LightVectorModelSpace[1] + svector[2] * LightVectorModelSpace[2]; LightVector[1] = tvector[0] * LightVectorModelSpace[0] + tvector[1] * LightVectorModelSpace[1] + tvector[2] * LightVectorModelSpace[2]; LightVector[2] = normal[0] * LightVectorModelSpace[0] + normal[1] * LightVectorModelSpace[1] + normal[2] * LightVectorModelSpace[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0] = LightVector[0]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1] = LightVector[1]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2] = LightVector[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+3] = 0.0f; EyeVectorModelSpace[0] = EyePosition[0] - position[0]; EyeVectorModelSpace[1] = EyePosition[1] - position[1]; EyeVectorModelSpace[2] = EyePosition[2] - position[2]; EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0] = EyeVector[0]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1] = EyeVector[1]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2] = EyeVector[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+3] = 0.0f; } DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelToLightM1); } static void DPSOFTRAST_PixelShader_LightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { #ifdef SSE_POSSIBLE float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_texture_colorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_glossbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_cubebgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_pantsbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_texture_shirtbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; int x, startx = span->startx, endx = span->endx; float Color_Ambient[4], Color_Diffuse[4], Color_Specular[4], /*Color_Glow[4],*/ Color_Pants[4], Color_Shirt[4], LightColor[4]; float CubeVectordata[4]; float CubeVectorslope[4]; float LightVectordata[4]; float LightVectorslope[4]; float EyeVectordata[4]; float EyeVectorslope[4]; float z; float diffusetex[4]; float glosstex[4]; float surfacenormal[4]; float lightnormal[4]; float eyenormal[4]; float specularnormal[4]; float diffuse; float specular; float SpecularPower; float CubeVector[4]; float attenuation; int d[4]; #if 0 Color_Glow[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+0]; Color_Glow[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+1]; Color_Glow[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Glow*4+2]; Color_Glow[3] = 0.0f; #endif Color_Ambient[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+0]; Color_Ambient[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+1]; Color_Ambient[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Ambient*4+2]; Color_Ambient[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_Alpha*4+0]; Color_Diffuse[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+0]; Color_Diffuse[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+1]; Color_Diffuse[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Diffuse*4+2]; Color_Diffuse[3] = 0.0f; Color_Specular[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+0]; Color_Specular[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+1]; Color_Specular[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Specular*4+2]; Color_Specular[3] = 0.0f; Color_Pants[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+0]; Color_Pants[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+1]; Color_Pants[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Pants*4+2]; Color_Pants[3] = 0.0f; Color_Shirt[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+0]; Color_Shirt[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+1]; Color_Shirt[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_Color_Shirt*4+2]; Color_Shirt[3] = 0.0f; LightColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+0]; LightColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+1]; LightColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_LightColor*4+2]; LightColor[3] = 0.0f; SpecularPower = thread->uniform4f[DPSOFTRAST_UNIFORM_SpecularPower*4+0] * (1.0f / 255.0f); DPSOFTRAST_CALCATTRIB4F(triangle, span, LightVectordata, LightVectorslope, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_CALCATTRIB4F(triangle, span, CubeVectordata, CubeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD3); DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); memset(buffer_FragColorbgra8 + startx*4, 0, (endx-startx)*4); // clear first, because we skip writing black pixels, and there are a LOT of them... DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_colorbgra8, GL20TU_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_pantsbgra8, GL20TU_PANTS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_shirtbgra8, GL20TU_SHIRT, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); } if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) DPSOFTRAST_Draw_Span_TextureCubeVaryingBGRA8(triangle, span, buffer_texture_cubebgra8, GL20TU_CUBE, DPSOFTRAST_ARRAY_TEXCOORD3, buffer_z); if (thread->shader_permutation & SHADERPERMUTATION_SPECULAR) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_glossbgra8, GL20TU_GLOSS, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); for (x = startx;x < endx;x++) { z = buffer_z[x]; CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); if (attenuation < 0.01f) continue; if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) { attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); if (attenuation < 0.01f) continue; } diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; } glosstex[0] = buffer_texture_glossbgra8[x*4+0]; glosstex[1] = buffer_texture_glossbgra8[x*4+1]; glosstex[2] = buffer_texture_glossbgra8[x*4+2]; glosstex[3] = buffer_texture_glossbgra8[x*4+3]; surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(surfacenormal); lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; if(thread->shader_exactspecularmath) { // reflect lightnormal at surfacenormal, take the negative of that // i.e. we want (2*dot(N, i) * N - I) for N=surfacenormal, I=lightnormal float f; f = DPSOFTRAST_Vector3Dot(lightnormal, surfacenormal); specularnormal[0] = 2*f*surfacenormal[0] - lightnormal[0]; specularnormal[1] = 2*f*surfacenormal[1] - lightnormal[1]; specularnormal[2] = 2*f*surfacenormal[2] - lightnormal[2]; // dot of this and normalize(EyeVectorFogDepth.xyz) eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(eyenormal); specular = DPSOFTRAST_Vector3Dot(eyenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; } else { eyenormal[0] = (EyeVectordata[0] + EyeVectorslope[0]*x) * z; eyenormal[1] = (EyeVectordata[1] + EyeVectorslope[1]*x) * z; eyenormal[2] = (EyeVectordata[2] + EyeVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(eyenormal); specularnormal[0] = lightnormal[0] + eyenormal[0]; specularnormal[1] = lightnormal[1] + eyenormal[1]; specularnormal[2] = lightnormal[2] + eyenormal[2]; DPSOFTRAST_Vector3Normalize(specularnormal); specular = DPSOFTRAST_Vector3Dot(surfacenormal, specularnormal);if (specular < 0.0f) specular = 0.0f; } specular = pow(specular, 1.0f + SpecularPower * glosstex[3]); if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) { // scale down the attenuation to account for the cubefilter multiplying everything by 255 attenuation *= (1.0f / 255.0f); d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } else { d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse) + glosstex[0] * Color_Specular[0] * specular) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse) + glosstex[1] * Color_Specular[1] * specular) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse) + glosstex[2] * Color_Specular[2] * specular) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } else if (thread->shader_permutation & SHADERPERMUTATION_DIFFUSE) { DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); for (x = startx;x < endx;x++) { z = buffer_z[x]; CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); if (attenuation < 0.01f) continue; if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) { attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); if (attenuation < 0.01f) continue; } diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; } surfacenormal[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; surfacenormal[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; surfacenormal[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(surfacenormal); lightnormal[0] = (LightVectordata[0] + LightVectorslope[0]*x) * z; lightnormal[1] = (LightVectordata[1] + LightVectorslope[1]*x) * z; lightnormal[2] = (LightVectordata[2] + LightVectorslope[2]*x) * z; DPSOFTRAST_Vector3Normalize(lightnormal); diffuse = DPSOFTRAST_Vector3Dot(surfacenormal, lightnormal);if (diffuse < 0.0f) diffuse = 0.0f; if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) { // scale down the attenuation to account for the cubefilter multiplying everything by 255 attenuation *= (1.0f / 255.0f); d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } else { d[0] = (int)((diffusetex[0] * (Color_Ambient[0] + Color_Diffuse[0] * diffuse)) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1] + Color_Diffuse[1] * diffuse)) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2] + Color_Diffuse[2] * diffuse)) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } else { for (x = startx;x < endx;x++) { z = buffer_z[x]; CubeVector[0] = (CubeVectordata[0] + CubeVectorslope[0]*x) * z; CubeVector[1] = (CubeVectordata[1] + CubeVectorslope[1]*x) * z; CubeVector[2] = (CubeVectordata[2] + CubeVectorslope[2]*x) * z; attenuation = 1.0f - DPSOFTRAST_Vector3LengthSquared(CubeVector); if (attenuation < 0.01f) continue; if (thread->shader_permutation & SHADERPERMUTATION_SHADOWMAP2D) { attenuation *= DPSOFTRAST_SampleShadowmap(CubeVector); if (attenuation < 0.01f) continue; } diffusetex[0] = buffer_texture_colorbgra8[x*4+0]; diffusetex[1] = buffer_texture_colorbgra8[x*4+1]; diffusetex[2] = buffer_texture_colorbgra8[x*4+2]; diffusetex[3] = buffer_texture_colorbgra8[x*4+3]; if (thread->shader_permutation & SHADERPERMUTATION_COLORMAPPING) { diffusetex[0] += buffer_texture_pantsbgra8[x*4+0] * Color_Pants[0] + buffer_texture_shirtbgra8[x*4+0] * Color_Shirt[0]; diffusetex[1] += buffer_texture_pantsbgra8[x*4+1] * Color_Pants[1] + buffer_texture_shirtbgra8[x*4+1] * Color_Shirt[1]; diffusetex[2] += buffer_texture_pantsbgra8[x*4+2] * Color_Pants[2] + buffer_texture_shirtbgra8[x*4+2] * Color_Shirt[2]; diffusetex[3] += buffer_texture_pantsbgra8[x*4+3] * Color_Pants[3] + buffer_texture_shirtbgra8[x*4+3] * Color_Shirt[3]; } if (thread->shader_permutation & SHADERPERMUTATION_CUBEFILTER) { // scale down the attenuation to account for the cubefilter multiplying everything by 255 attenuation *= (1.0f / 255.0f); d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * buffer_texture_cubebgra8[x*4+0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * buffer_texture_cubebgra8[x*4+1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * buffer_texture_cubebgra8[x*4+2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } else { d[0] = (int)((diffusetex[0] * (Color_Ambient[0])) * LightColor[0] * attenuation);if (d[0] > 255) d[0] = 255; d[1] = (int)((diffusetex[1] * (Color_Ambient[1])) * LightColor[1] * attenuation);if (d[1] > 255) d[1] = 255; d[2] = (int)((diffusetex[2] * (Color_Ambient[2])) * LightColor[2] * attenuation);if (d[2] > 255) d[2] = 255; d[3] = (int)( diffusetex[3] );if (d[3] > 255) d[3] = 255; } buffer_FragColorbgra8[x*4+0] = d[0]; buffer_FragColorbgra8[x*4+1] = d[1]; buffer_FragColorbgra8[x*4+2] = d[2]; buffer_FragColorbgra8[x*4+3] = d[3]; } } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); #endif } static void DPSOFTRAST_VertexShader_Refraction(void) { DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); } static void DPSOFTRAST_PixelShader_Refraction(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; //float z; int x, startx = span->startx, endx = span->endx; // texture reads unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; // varyings float ModelViewProjectionPositiondata[4]; float ModelViewProjectionPositionslope[4]; // uniforms float ScreenScaleRefractReflect[2]; float ScreenCenterRefractReflect[2]; float DistortScaleRefractReflect[2]; float RefractColor[4]; DPSOFTRAST_Texture *texture = thread->texbound[GL20TU_REFRACTION]; if(!texture) return; // read textures DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); // read varyings DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); // read uniforms ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; // do stuff for (x = startx;x < endx;x++) { float SafeScreenTexCoord[2]; float ScreenTexCoord[2]; float v[3]; float iw; unsigned char c[4]; //z = buffer_z[x]; // " vec2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n" iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z // " vec2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n" SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) // " vec2 ScreenTexCoord = SafeScreenTexCoord + vec3(normalize(myhalf3(dp_texture2D(Texture_Normal, TexCoord)) - myhalf3(0.5))).xy * DistortScaleRefractReflect.zw;\n" v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(v); ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" DPSOFTRAST_Texture2DBGRA8(texture, 0, ScreenTexCoord[0], ScreenTexCoord[1], c); buffer_FragColorbgra8[x*4+0] = c[0] * RefractColor[0]; buffer_FragColorbgra8[x*4+1] = c[1] * RefractColor[1]; buffer_FragColorbgra8[x*4+2] = c[2] * RefractColor[2]; buffer_FragColorbgra8[x*4+3] = min(RefractColor[3] * 256, 255); } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_Water(void) { int i; int numvertices = dpsoftrast.numvertices; float EyePosition[4]; float EyeVectorModelSpace[4]; float EyeVector[4]; float position[4]; float svector[4]; float tvector[4]; float normal[4]; EyePosition[0] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+0]; EyePosition[1] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+1]; EyePosition[2] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+2]; EyePosition[3] = dpsoftrast.uniform4f[DPSOFTRAST_UNIFORM_EyePosition*4+3]; DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD1); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD2); DPSOFTRAST_Array_Load(DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD3); for (i = 0;i < numvertices;i++) { position[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+0]; position[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+1]; position[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION][i*4+2]; svector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+0]; svector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+1]; svector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD1][i*4+2]; tvector[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+0]; tvector[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+1]; tvector[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD2][i*4+2]; normal[0] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+0]; normal[1] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+1]; normal[2] = dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD3][i*4+2]; EyeVectorModelSpace[0] = EyePosition[0] - position[0]; EyeVectorModelSpace[1] = EyePosition[1] - position[1]; EyeVectorModelSpace[2] = EyePosition[2] - position[2]; EyeVector[0] = svector[0] * EyeVectorModelSpace[0] + svector[1] * EyeVectorModelSpace[1] + svector[2] * EyeVectorModelSpace[2]; EyeVector[1] = tvector[0] * EyeVectorModelSpace[0] + tvector[1] * EyeVectorModelSpace[1] + tvector[2] * EyeVectorModelSpace[2]; EyeVector[2] = normal[0] * EyeVectorModelSpace[0] + normal[1] * EyeVectorModelSpace[1] + normal[2] * EyeVectorModelSpace[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+0] = EyeVector[0]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+1] = EyeVector[1]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+2] = EyeVector[2]; dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_TEXCOORD6][i*4+3] = 0.0f; } DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, -1, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); DPSOFTRAST_Array_Transform(DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD0, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_TexMatrixM1); } static void DPSOFTRAST_PixelShader_Water(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; // float z; int x, startx = span->startx, endx = span->endx; // texture reads unsigned char buffer_texture_normalbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; // varyings float ModelViewProjectionPositiondata[4]; float ModelViewProjectionPositionslope[4]; float EyeVectordata[4]; float EyeVectorslope[4]; // uniforms float ScreenScaleRefractReflect[4]; float ScreenCenterRefractReflect[4]; float DistortScaleRefractReflect[4]; float RefractColor[4]; float ReflectColor[4]; float ReflectFactor; float ReflectOffset; DPSOFTRAST_Texture *texture_refraction = thread->texbound[GL20TU_REFRACTION]; DPSOFTRAST_Texture *texture_reflection = thread->texbound[GL20TU_REFLECTION]; if(!texture_refraction || !texture_reflection) return; // read textures DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); DPSOFTRAST_Draw_Span_Texture2DVaryingBGRA8(thread, triangle, span, buffer_texture_normalbgra8, GL20TU_NORMAL, DPSOFTRAST_ARRAY_TEXCOORD0, buffer_z); // read varyings DPSOFTRAST_CALCATTRIB4F(triangle, span, ModelViewProjectionPositiondata, ModelViewProjectionPositionslope, DPSOFTRAST_ARRAY_TEXCOORD4); DPSOFTRAST_CALCATTRIB4F(triangle, span, EyeVectordata, EyeVectorslope, DPSOFTRAST_ARRAY_TEXCOORD6); // read uniforms ScreenScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+0]; ScreenScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+1]; ScreenScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+2]; ScreenScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect*4+3]; ScreenCenterRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+0]; ScreenCenterRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+1]; ScreenCenterRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+2]; ScreenCenterRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect*4+3]; DistortScaleRefractReflect[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+0]; DistortScaleRefractReflect[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+1]; DistortScaleRefractReflect[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+2]; DistortScaleRefractReflect[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_DistortScaleRefractReflect*4+3]; RefractColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+2]; RefractColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+1]; RefractColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+0]; RefractColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_RefractColor*4+3]; ReflectColor[0] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+2]; ReflectColor[1] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+1]; ReflectColor[2] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+0]; ReflectColor[3] = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectColor*4+3]; ReflectFactor = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectFactor*4+0]; ReflectOffset = thread->uniform4f[DPSOFTRAST_UNIFORM_ReflectOffset*4+0]; // do stuff for (x = startx;x < endx;x++) { float SafeScreenTexCoord[4]; float ScreenTexCoord[4]; float v[3]; float iw; unsigned char c1[4]; unsigned char c2[4]; float Fresnel; // z = buffer_z[x]; // " vec4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n" iw = 1.0f / (ModelViewProjectionPositiondata[3] + ModelViewProjectionPositionslope[3]*x); // / z // " vec4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n" SafeScreenTexCoord[0] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[0] + ScreenCenterRefractReflect[0]; // * z (disappears) SafeScreenTexCoord[1] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[1] + ScreenCenterRefractReflect[1]; // * z (disappears) SafeScreenTexCoord[2] = (ModelViewProjectionPositiondata[0] + ModelViewProjectionPositionslope[0]*x) * iw * ScreenScaleRefractReflect[2] + ScreenCenterRefractReflect[2]; // * z (disappears) SafeScreenTexCoord[3] = (ModelViewProjectionPositiondata[1] + ModelViewProjectionPositionslope[1]*x) * iw * ScreenScaleRefractReflect[3] + ScreenCenterRefractReflect[3]; // * z (disappears) // " vec4 ScreenTexCoord = SafeScreenTexCoord + vec2(normalize(vec3(dp_texture2D(Texture_Normal, TexCoord)) - vec3(0.5))).xyxy * DistortScaleRefractReflect;\n" v[0] = buffer_texture_normalbgra8[x*4+2] * (1.0f / 128.0f) - 1.0f; v[1] = buffer_texture_normalbgra8[x*4+1] * (1.0f / 128.0f) - 1.0f; v[2] = buffer_texture_normalbgra8[x*4+0] * (1.0f / 128.0f) - 1.0f; DPSOFTRAST_Vector3Normalize(v); ScreenTexCoord[0] = SafeScreenTexCoord[0] + v[0] * DistortScaleRefractReflect[0]; ScreenTexCoord[1] = SafeScreenTexCoord[1] + v[1] * DistortScaleRefractReflect[1]; ScreenTexCoord[2] = SafeScreenTexCoord[2] + v[0] * DistortScaleRefractReflect[2]; ScreenTexCoord[3] = SafeScreenTexCoord[3] + v[1] * DistortScaleRefractReflect[3]; // " float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * ReflectFactor + ReflectOffset;\n" v[0] = (EyeVectordata[0] + EyeVectorslope[0] * x); // * z (disappears) v[1] = (EyeVectordata[1] + EyeVectorslope[1] * x); // * z (disappears) v[2] = (EyeVectordata[2] + EyeVectorslope[2] * x); // * z (disappears) DPSOFTRAST_Vector3Normalize(v); Fresnel = 1.0f - v[2]; Fresnel = min(1.0f, Fresnel); Fresnel = Fresnel * Fresnel * ReflectFactor + ReflectOffset; // " dp_FragColor = vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord).rgb, 1.0) * RefractColor;\n" // " dp_FragColor = mix(vec4(dp_texture2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * RefractColor, vec4(dp_texture2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n" DPSOFTRAST_Texture2DBGRA8(texture_refraction, 0, ScreenTexCoord[0], ScreenTexCoord[1], c1); DPSOFTRAST_Texture2DBGRA8(texture_reflection, 0, ScreenTexCoord[2], ScreenTexCoord[3], c2); buffer_FragColorbgra8[x*4+0] = (c1[0] * RefractColor[0]) * (1.0f - Fresnel) + (c2[0] * ReflectColor[0]) * Fresnel; buffer_FragColorbgra8[x*4+1] = (c1[1] * RefractColor[1]) * (1.0f - Fresnel) + (c2[1] * ReflectColor[1]) * Fresnel; buffer_FragColorbgra8[x*4+2] = (c1[2] * RefractColor[2]) * (1.0f - Fresnel) + (c2[2] * ReflectColor[2]) * Fresnel; buffer_FragColorbgra8[x*4+3] = min(( RefractColor[3] * (1.0f - Fresnel) + ReflectColor[3] * Fresnel) * 256, 255); } DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_DeferredGeometry(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); } static void DPSOFTRAST_PixelShader_DeferredGeometry(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { // TODO: IMPLEMENT float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } static void DPSOFTRAST_VertexShader_DeferredLightSource(void) { DPSOFTRAST_Array_TransformProject(DPSOFTRAST_ARRAY_POSITION, DPSOFTRAST_ARRAY_POSITION, dpsoftrast.uniform4f + 4*DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1); } static void DPSOFTRAST_PixelShader_DeferredLightSource(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span) { // TODO: IMPLEMENT float buffer_z[DPSOFTRAST_DRAW_MAXSPANLENGTH]; unsigned char buffer_FragColorbgra8[DPSOFTRAST_DRAW_MAXSPANLENGTH*4]; DPSOFTRAST_Draw_Span_Begin(thread, triangle, span, buffer_z); memset(buffer_FragColorbgra8 + span->startx*4, 0, (span->endx - span->startx)*4); DPSOFTRAST_Draw_Span_FinishBGRA8(thread, triangle, span, buffer_FragColorbgra8); } typedef struct DPSOFTRAST_ShaderModeInfo_s { int lodarrayindex; void (*Vertex)(void); void (*Span)(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Triangle * RESTRICT triangle, const DPSOFTRAST_State_Span * RESTRICT span); unsigned char arrays[DPSOFTRAST_ARRAY_TOTAL]; unsigned char texunits[DPSOFTRAST_MAXTEXTUREUNITS]; } DPSOFTRAST_ShaderModeInfo; static const DPSOFTRAST_ShaderModeInfo DPSOFTRAST_ShaderModeTable[SHADERMODE_COUNT] = { {2, DPSOFTRAST_VertexShader_Generic, DPSOFTRAST_PixelShader_Generic, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, 0xFF}, {GL20TU_FIRST, GL20TU_SECOND, 0xFF}}, {2, DPSOFTRAST_VertexShader_PostProcess, DPSOFTRAST_PixelShader_PostProcess, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, 0xFF}, {GL20TU_FIRST, GL20TU_SECOND, 0xFF}}, {2, DPSOFTRAST_VertexShader_Depth_Or_Shadow, DPSOFTRAST_PixelShader_Depth_Or_Shadow, {0xFF}, {0xFF}}, {2, DPSOFTRAST_VertexShader_FlatColor, DPSOFTRAST_PixelShader_FlatColor, {DPSOFTRAST_ARRAY_TEXCOORD0, 0xFF}, {GL20TU_COLOR, 0xFF}}, {2, DPSOFTRAST_VertexShader_VertexColor, DPSOFTRAST_PixelShader_VertexColor, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, 0xFF}, {GL20TU_COLOR, 0xFF}}, {2, DPSOFTRAST_VertexShader_Lightmap, DPSOFTRAST_PixelShader_Lightmap, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, 0xFF}, {GL20TU_COLOR, GL20TU_LIGHTMAP, GL20TU_GLOW, 0xFF}}, {2, DPSOFTRAST_VertexShader_FakeLight, DPSOFTRAST_PixelShader_FakeLight, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, 0xFF}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, 0xFF}}, {2, DPSOFTRAST_VertexShader_LightDirectionMap_ModelSpace, DPSOFTRAST_PixelShader_LightDirectionMap_ModelSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, 0xFF}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, 0xFF}}, {2, DPSOFTRAST_VertexShader_LightDirectionMap_TangentSpace, DPSOFTRAST_PixelShader_LightDirectionMap_TangentSpace, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, 0xFF}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_LIGHTMAP, GL20TU_DELUXEMAP, 0xFF}}, {2, DPSOFTRAST_VertexShader_Lightmap, DPSOFTRAST_PixelShader_Lightmap, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, 0xFF}, {GL20TU_COLOR, GL20TU_LIGHTMAP, GL20TU_GLOW, 0xFF}}, {2, DPSOFTRAST_VertexShader_VertexColor, DPSOFTRAST_PixelShader_VertexColor, {DPSOFTRAST_ARRAY_COLOR, DPSOFTRAST_ARRAY_TEXCOORD0, 0xFF}, {GL20TU_COLOR, 0xFF}}, {2, DPSOFTRAST_VertexShader_LightDirection, DPSOFTRAST_PixelShader_LightDirection, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD5, DPSOFTRAST_ARRAY_TEXCOORD6, 0xFF}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, 0xFF}}, {2, DPSOFTRAST_VertexShader_LightSource, DPSOFTRAST_PixelShader_LightSource, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, 0xFF}, {GL20TU_COLOR, GL20TU_PANTS, GL20TU_SHIRT, GL20TU_GLOW, GL20TU_NORMAL, GL20TU_GLOSS, GL20TU_CUBE, 0xFF}}, {2, DPSOFTRAST_VertexShader_Refraction, DPSOFTRAST_PixelShader_Refraction, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD4, 0xFF}, {GL20TU_NORMAL, GL20TU_REFRACTION, 0xFF}}, {2, DPSOFTRAST_VertexShader_Water, DPSOFTRAST_PixelShader_Water, {DPSOFTRAST_ARRAY_TEXCOORD0, DPSOFTRAST_ARRAY_TEXCOORD1, DPSOFTRAST_ARRAY_TEXCOORD2, DPSOFTRAST_ARRAY_TEXCOORD3, DPSOFTRAST_ARRAY_TEXCOORD4, DPSOFTRAST_ARRAY_TEXCOORD6, 0xFF}, {GL20TU_NORMAL, GL20TU_REFLECTION, GL20TU_REFRACTION, 0xFF}}, {2, DPSOFTRAST_VertexShader_DeferredGeometry, DPSOFTRAST_PixelShader_DeferredGeometry, {0xFF}}, {2, DPSOFTRAST_VertexShader_DeferredLightSource, DPSOFTRAST_PixelShader_DeferredLightSource, {0xFF}}, }; static void DPSOFTRAST_Draw_DepthTest(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_State_Span *span) { int x; int startx; int endx; unsigned int *depthpixel; int depth; int depthslope; unsigned int d; unsigned char *pixelmask; depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; startx = span->startx; endx = span->endx; depth = span->depthbase; depthslope = span->depthslope; pixelmask = thread->pixelmaskarray; if (thread->depthtest && dpsoftrast.fb_depthpixels) { switch(thread->fb_depthfunc) { default: case GL_ALWAYS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = true; break; case GL_LESS: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] < d; break; case GL_LEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] <= d; break; case GL_EQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] == d; break; case GL_GEQUAL: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] >= d; break; case GL_GREATER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = depthpixel[x] > d; break; case GL_NEVER: for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) pixelmask[x] = false; break; } while (startx < endx && !pixelmask[startx]) startx++; while (endx > startx && !pixelmask[endx-1]) endx--; } else { // no depth testing means we're just dealing with color... memset(pixelmask + startx, 1, endx - startx); } span->pixelmask = pixelmask; span->startx = startx; span->endx = endx; } static void DPSOFTRAST_Draw_DepthWrite(const DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_State_Span *span) { int x, d, depth, depthslope, startx, endx; const unsigned char *pixelmask; unsigned int *depthpixel; if (thread->depthmask && thread->depthtest && dpsoftrast.fb_depthpixels) { depth = span->depthbase; depthslope = span->depthslope; pixelmask = span->pixelmask; startx = span->startx; endx = span->endx; depthpixel = dpsoftrast.fb_depthpixels + span->y * dpsoftrast.fb_width + span->x; for (x = startx, d = depth + depthslope*startx;x < endx;x++, d += depthslope) if (pixelmask[x]) depthpixel[x] = d; } } static void DPSOFTRAST_Draw_ProcessSpans(DPSOFTRAST_State_Thread *thread) { int i; DPSOFTRAST_State_Triangle *triangle; DPSOFTRAST_State_Span *span; for (i = 0; i < thread->numspans; i++) { span = &thread->spans[i]; triangle = &thread->triangles[span->triangle]; DPSOFTRAST_Draw_DepthTest(thread, span); if (span->startx >= span->endx) continue; // run pixel shader if appropriate // do this before running depthmask code, to allow the pixelshader // to clear pixelmask values for alpha testing if (dpsoftrast.fb_colorpixels[0] && thread->fb_colormask) DPSOFTRAST_ShaderModeTable[thread->shader_mode].Span(thread, triangle, span); DPSOFTRAST_Draw_DepthWrite(thread, span); } thread->numspans = 0; } DEFCOMMAND(22, Draw, int datasize; int starty; int endy; ATOMIC_COUNTER refcount; int clipped; int firstvertex; int numvertices; int numtriangles; float *arrays; int *element3i; unsigned short *element3s;) static void DPSOFTRAST_Interpret_Draw(DPSOFTRAST_State_Thread *thread, DPSOFTRAST_Command_Draw *command) { #ifdef SSE_POSSIBLE int cullface = thread->cullface; int minx, maxx, miny, maxy; int miny1, maxy1, miny2, maxy2; __m128i fbmin, fbmax; __m128 viewportcenter, viewportscale; int firstvertex = command->firstvertex; int numvertices = command->numvertices; int numtriangles = command->numtriangles; const int *element3i = command->element3i; const unsigned short *element3s = command->element3s; int clipped = command->clipped; int i; int j; int k; int y; int e[3]; __m128i screeny; int starty, endy, bandy; int numpoints; int clipcase; float clipdist[4]; float clip0origin, clip0slope; int clip0dir; __m128 triangleedge1, triangleedge2, trianglenormal; __m128 clipfrac[3]; __m128 screen[4]; DPSOFTRAST_State_Triangle *triangle; DPSOFTRAST_Texture *texture; DPSOFTRAST_ValidateQuick(thread, DPSOFTRAST_VALIDATE_DRAW); miny = thread->fb_scissor[1]; maxy = thread->fb_scissor[1] + thread->fb_scissor[3]; miny1 = bound(miny, thread->miny1, maxy); maxy1 = bound(miny, thread->maxy1, maxy); miny2 = bound(miny, thread->miny2, maxy); maxy2 = bound(miny, thread->maxy2, maxy); if ((command->starty >= maxy1 || command->endy <= miny1) && (command->starty >= maxy2 || command->endy <= miny2)) { if (!ATOMIC_DECREMENT(command->refcount)) { if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) MM_FREE(command->arrays); } return; } minx = thread->fb_scissor[0]; maxx = thread->fb_scissor[0] + thread->fb_scissor[2]; fbmin = _mm_setr_epi16(minx, miny1, minx, miny1, minx, miny1, minx, miny1); fbmax = _mm_sub_epi16(_mm_setr_epi16(maxx, maxy2, maxx, maxy2, maxx, maxy2, maxx, maxy2), _mm_set1_epi16(1)); viewportcenter = _mm_load_ps(thread->fb_viewportcenter); viewportscale = _mm_load_ps(thread->fb_viewportscale); screen[3] = _mm_setzero_ps(); clipfrac[0] = clipfrac[1] = clipfrac[2] = _mm_setzero_ps(); for (i = 0;i < numtriangles;i++) { const float *screencoord4f = command->arrays; const float *arrays = screencoord4f + numvertices*4; // generate the 3 edges of this triangle // generate spans for the triangle - switch based on left split or right split classification of triangle if (element3s) { e[0] = element3s[i*3+0] - firstvertex; e[1] = element3s[i*3+1] - firstvertex; e[2] = element3s[i*3+2] - firstvertex; } else if (element3i) { e[0] = element3i[i*3+0] - firstvertex; e[1] = element3i[i*3+1] - firstvertex; e[2] = element3i[i*3+2] - firstvertex; } else { e[0] = i*3+0; e[1] = i*3+1; e[2] = i*3+2; } #define SKIPBACKFACE \ triangleedge1 = _mm_sub_ps(screen[0], screen[1]); \ triangleedge2 = _mm_sub_ps(screen[2], screen[1]); \ /* store normal in 2, 0, 1 order instead of 0, 1, 2 as it requires fewer shuffles and leaves z component accessible as scalar */ \ trianglenormal = _mm_sub_ss(_mm_mul_ss(triangleedge1, _mm_shuffle_ps(triangleedge2, triangleedge2, _MM_SHUFFLE(3, 0, 2, 1))), \ _mm_mul_ss(_mm_shuffle_ps(triangleedge1, triangleedge1, _MM_SHUFFLE(3, 0, 2, 1)), triangleedge2)); \ switch(cullface) \ { \ case GL_BACK: \ if (_mm_ucomilt_ss(trianglenormal, _mm_setzero_ps())) \ continue; \ break; \ case GL_FRONT: \ if (_mm_ucomigt_ss(trianglenormal, _mm_setzero_ps())) \ continue; \ break; \ } #define CLIPPEDVERTEXLERP(k,p1, p2) \ clipfrac[p1] = _mm_set1_ps(clipdist[p1] / (clipdist[p1] - clipdist[p2])); \ { \ __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ DPSOFTRAST_PROJECTVERTEX(screen[k], _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])), viewportcenter, viewportscale); \ } #define CLIPPEDVERTEXCOPY(k,p1) \ screen[k] = _mm_load_ps(&screencoord4f[e[p1]*4]); #define GENATTRIBCOPY(attrib, p1) \ attrib = _mm_load_ps(&arrays[e[p1]*4]); #define GENATTRIBLERP(attrib, p1, p2) \ { \ __m128 v1 = _mm_load_ps(&arrays[e[p1]*4]), v2 = _mm_load_ps(&arrays[e[p2]*4]); \ attrib = _mm_add_ps(v1, _mm_mul_ps(_mm_sub_ps(v2, v1), clipfrac[p1])); \ } #define GENATTRIBS(attrib0, attrib1, attrib2) \ switch(clipcase) \ { \ default: \ case 0: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ case 1: GENATTRIBCOPY(attrib0, 0); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ case 2: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ case 3: GENATTRIBCOPY(attrib0, 0); GENATTRIBLERP(attrib1, 0, 1); GENATTRIBLERP(attrib2, 2, 0); break; \ case 4: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBCOPY(attrib2, 2); break; \ case 5: GENATTRIBLERP(attrib0, 0, 1); GENATTRIBCOPY(attrib1, 1); GENATTRIBLERP(attrib2, 1, 2); break; \ case 6: GENATTRIBLERP(attrib0, 1, 2); GENATTRIBCOPY(attrib1, 2); GENATTRIBLERP(attrib2, 2, 0); break; \ } if (! clipped) goto notclipped; // calculate distance from nearplane clipdist[0] = arrays[e[0]*4+2] + arrays[e[0]*4+3]; clipdist[1] = arrays[e[1]*4+2] + arrays[e[1]*4+3]; clipdist[2] = arrays[e[2]*4+2] + arrays[e[2]*4+3]; if (clipdist[0] >= 0.0f) { if (clipdist[1] >= 0.0f) { if (clipdist[2] >= 0.0f) { notclipped: // triangle is entirely in front of nearplane CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); SKIPBACKFACE; numpoints = 3; clipcase = 0; } else { CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXLERP(3,2,0); SKIPBACKFACE; numpoints = 4; clipcase = 1; } } else { if (clipdist[2] >= 0.0f) { CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,1,2); CLIPPEDVERTEXCOPY(3,2); SKIPBACKFACE; numpoints = 4; clipcase = 2; } else { CLIPPEDVERTEXCOPY(0,0); CLIPPEDVERTEXLERP(1,0,1); CLIPPEDVERTEXLERP(2,2,0); SKIPBACKFACE; numpoints = 3; clipcase = 3; } } } else if (clipdist[1] >= 0.0f) { if (clipdist[2] >= 0.0f) { CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXCOPY(2,2); CLIPPEDVERTEXLERP(3,2,0); SKIPBACKFACE; numpoints = 4; clipcase = 4; } else { CLIPPEDVERTEXLERP(0,0,1); CLIPPEDVERTEXCOPY(1,1); CLIPPEDVERTEXLERP(2,1,2); SKIPBACKFACE; numpoints = 3; clipcase = 5; } } else if (clipdist[2] >= 0.0f) { CLIPPEDVERTEXLERP(0,1,2); CLIPPEDVERTEXCOPY(1,2); CLIPPEDVERTEXLERP(2,2,0); SKIPBACKFACE; numpoints = 3; clipcase = 6; } else continue; // triangle is entirely behind nearplane { // calculate integer y coords for triangle points __m128i screeni = _mm_packs_epi32(_mm_cvttps_epi32(_mm_movelh_ps(screen[0], screen[1])), _mm_cvttps_epi32(_mm_movelh_ps(screen[2], numpoints > 3 ? screen[3] : screen[2]))), screenir = _mm_shuffle_epi32(screeni, _MM_SHUFFLE(1, 0, 3, 2)), screenmin = _mm_min_epi16(screeni, screenir), screenmax = _mm_max_epi16(screeni, screenir); screenmin = _mm_min_epi16(screenmin, _mm_shufflelo_epi16(screenmin, _MM_SHUFFLE(1, 0, 3, 2))); screenmax = _mm_max_epi16(screenmax, _mm_shufflelo_epi16(screenmax, _MM_SHUFFLE(1, 0, 3, 2))); screenmin = _mm_max_epi16(screenmin, fbmin); screenmax = _mm_min_epi16(screenmax, fbmax); // skip offscreen triangles if (_mm_cvtsi128_si32(_mm_cmplt_epi16(screenmax, screenmin))) continue; starty = _mm_extract_epi16(screenmin, 1); endy = _mm_extract_epi16(screenmax, 1)+1; if (starty >= maxy1 && endy <= miny2) continue; screeny = _mm_srai_epi32(screeni, 16); } triangle = &thread->triangles[thread->numtriangles]; // calculate attribute plans for triangle data... // okay, this triangle is going to produce spans, we'd better project // the interpolants now (this is what gives perspective texturing), // this consists of simply multiplying all arrays by the W coord // (which is basically 1/Z), which will be undone per-pixel // (multiplying by Z again) to get the perspective-correct array // values { __m128 attribuvslope, attribuxslope, attribuyslope, attribvxslope, attribvyslope, attriborigin, attribedge1, attribedge2, attribxslope, attribyslope, w0, w1, w2, x1, y1; __m128 mipedgescale, mipdensity; attribuvslope = _mm_div_ps(_mm_movelh_ps(triangleedge1, triangleedge2), _mm_shuffle_ps(trianglenormal, trianglenormal, _MM_SHUFFLE(0, 0, 0, 0))); attribuxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(3, 3, 3, 3)); attribuyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(2, 2, 2, 2)); attribvxslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(1, 1, 1, 1)); attribvyslope = _mm_shuffle_ps(attribuvslope, attribuvslope, _MM_SHUFFLE(0, 0, 0, 0)); w0 = _mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(3, 3, 3, 3)); w1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(3, 3, 3, 3)); w2 = _mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(3, 3, 3, 3)); attribedge1 = _mm_sub_ss(w0, w1); attribedge2 = _mm_sub_ss(w2, w1); attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); x1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(0, 0, 0, 0)); y1 = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(1, 1, 1, 1)); attriborigin = _mm_sub_ss(w1, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); _mm_store_ss(&triangle->w[0], attribxslope); _mm_store_ss(&triangle->w[1], attribyslope); _mm_store_ss(&triangle->w[2], attriborigin); clip0origin = 0; clip0slope = 0; clip0dir = 0; if(thread->fb_clipplane[0] || thread->fb_clipplane[1] || thread->fb_clipplane[2]) { float cliporigin, clipxslope, clipyslope; attriborigin = _mm_shuffle_ps(screen[1], screen[1], _MM_SHUFFLE(2, 2, 2, 2)); attribedge1 = _mm_sub_ss(_mm_shuffle_ps(screen[0], screen[0], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); attribedge2 = _mm_sub_ss(_mm_shuffle_ps(screen[2], screen[2], _MM_SHUFFLE(2, 2, 2, 2)), attriborigin); attribxslope = _mm_sub_ss(_mm_mul_ss(attribuxslope, attribedge1), _mm_mul_ss(attribvxslope, attribedge2)); attribyslope = _mm_sub_ss(_mm_mul_ss(attribvyslope, attribedge2), _mm_mul_ss(attribuyslope, attribedge1)); attriborigin = _mm_sub_ss(attriborigin, _mm_add_ss(_mm_mul_ss(attribxslope, x1), _mm_mul_ss(attribyslope, y1))); cliporigin = _mm_cvtss_f32(attriborigin)*thread->fb_clipplane[2] + thread->fb_clipplane[3]; clipxslope = thread->fb_clipplane[0] + _mm_cvtss_f32(attribxslope)*thread->fb_clipplane[2]; clipyslope = thread->fb_clipplane[1] + _mm_cvtss_f32(attribyslope)*thread->fb_clipplane[2]; if(clipxslope != 0) { clip0origin = -cliporigin/clipxslope; clip0slope = -clipyslope/clipxslope; clip0dir = clipxslope > 0 ? 1 : -1; } else if(clipyslope > 0) { clip0origin = dpsoftrast.fb_width*floor(cliporigin/clipyslope); clip0slope = dpsoftrast.fb_width; clip0dir = -1; } else if(clipyslope < 0) { clip0origin = dpsoftrast.fb_width*ceil(cliporigin/clipyslope); clip0slope = -dpsoftrast.fb_width; clip0dir = -1; } else if(clip0origin < 0) continue; } mipedgescale = _mm_setzero_ps(); for (j = 0;j < DPSOFTRAST_ARRAY_TOTAL; j++) { __m128 attrib0, attrib1, attrib2; k = DPSOFTRAST_ShaderModeTable[thread->shader_mode].arrays[j]; if (k >= DPSOFTRAST_ARRAY_TOTAL) break; arrays += numvertices*4; GENATTRIBS(attrib0, attrib1, attrib2); attriborigin = _mm_mul_ps(attrib1, w1); attribedge1 = _mm_sub_ps(_mm_mul_ps(attrib0, w0), attriborigin); attribedge2 = _mm_sub_ps(_mm_mul_ps(attrib2, w2), attriborigin); attribxslope = _mm_sub_ps(_mm_mul_ps(attribuxslope, attribedge1), _mm_mul_ps(attribvxslope, attribedge2)); attribyslope = _mm_sub_ps(_mm_mul_ps(attribvyslope, attribedge2), _mm_mul_ps(attribuyslope, attribedge1)); attriborigin = _mm_sub_ps(attriborigin, _mm_add_ps(_mm_mul_ps(attribxslope, x1), _mm_mul_ps(attribyslope, y1))); _mm_storeu_ps(triangle->attribs[k][0], attribxslope); _mm_storeu_ps(triangle->attribs[k][1], attribyslope); _mm_storeu_ps(triangle->attribs[k][2], attriborigin); if (k == DPSOFTRAST_ShaderModeTable[thread->shader_mode].lodarrayindex) { mipedgescale = _mm_movelh_ps(triangleedge1, triangleedge2); mipedgescale = _mm_mul_ps(mipedgescale, mipedgescale); mipedgescale = _mm_rsqrt_ps(_mm_add_ps(mipedgescale, _mm_shuffle_ps(mipedgescale, mipedgescale, _MM_SHUFFLE(2, 3, 0, 1)))); mipedgescale = _mm_mul_ps(_mm_sub_ps(_mm_movelh_ps(attrib0, attrib2), _mm_movelh_ps(attrib1, attrib1)), mipedgescale); } } memset(triangle->mip, 0, sizeof(triangle->mip)); for (j = 0;j < DPSOFTRAST_MAXTEXTUREUNITS;j++) { int texunit = DPSOFTRAST_ShaderModeTable[thread->shader_mode].texunits[j]; if (texunit >= DPSOFTRAST_MAXTEXTUREUNITS) break; texture = thread->texbound[texunit]; if (texture && texture->filter > DPSOFTRAST_TEXTURE_FILTER_LINEAR) { mipdensity = _mm_mul_ps(mipedgescale, _mm_cvtepi32_ps(_mm_shuffle_epi32(_mm_loadl_epi64((const __m128i *)&texture->mipmap[0][2]), _MM_SHUFFLE(1, 0, 1, 0)))); mipdensity = _mm_mul_ps(mipdensity, mipdensity); mipdensity = _mm_add_ps(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 3, 0, 1))); mipdensity = _mm_min_ss(mipdensity, _mm_shuffle_ps(mipdensity, mipdensity, _MM_SHUFFLE(2, 2, 2, 2))); // this will be multiplied in the texturing routine by the texture resolution y = _mm_cvtss_si32(mipdensity); if (y > 0) { y = (int)(log((float)y)*0.5f/M_LN2); if (y > texture->mipmaps - 1) y = texture->mipmaps - 1; triangle->mip[texunit] = y; } } } } for (y = starty, bandy = min(endy, maxy1); y < endy; bandy = min(endy, maxy2), y = max(y, miny2)) for (; y < bandy;) { __m128 xcoords, xslope; __m128i ycc = _mm_cmpgt_epi32(_mm_set1_epi32(y), screeny); int yccmask = _mm_movemask_epi8(ycc); int edge0p, edge0n, edge1p, edge1n; int nexty; float w, wslope; float clip0; if (numpoints == 4) { switch(yccmask) { default: case 0xFFFF: /*0000*/ y = endy; continue; case 0xFFF0: /*1000*/ edge0p = 3;edge0n = 0;edge1p = 1;edge1n = 0;break; case 0xFF0F: /*0100*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; case 0xFF00: /*1100*/ edge0p = 3;edge0n = 0;edge1p = 2;edge1n = 1;break; case 0xF0FF: /*0010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; case 0xF0F0: /*1010*/ edge0p = 1;edge0n = 2;edge1p = 3;edge1n = 2;break; // concave - nonsense case 0xF00F: /*0110*/ edge0p = 0;edge0n = 1;edge1p = 3;edge1n = 2;break; case 0xF000: /*1110*/ edge0p = 3;edge0n = 0;edge1p = 3;edge1n = 2;break; case 0x0FFF: /*0001*/ edge0p = 2;edge0n = 3;edge1p = 0;edge1n = 3;break; case 0x0FF0: /*1001*/ edge0p = 2;edge0n = 3;edge1p = 1;edge1n = 0;break; case 0x0F0F: /*0101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; // concave - nonsense case 0x0F00: /*1101*/ edge0p = 2;edge0n = 3;edge1p = 2;edge1n = 1;break; case 0x00FF: /*0011*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 3;break; case 0x00F0: /*1011*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; case 0x000F: /*0111*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 3;break; case 0x0000: /*1111*/ y++; continue; } } else { switch(yccmask) { default: case 0xFFFF: /*000*/ y = endy; continue; case 0xFFF0: /*100*/ edge0p = 2;edge0n = 0;edge1p = 1;edge1n = 0;break; case 0xFF0F: /*010*/ edge0p = 0;edge0n = 1;edge1p = 2;edge1n = 1;break; case 0xFF00: /*110*/ edge0p = 2;edge0n = 0;edge1p = 2;edge1n = 1;break; case 0x00FF: /*001*/ edge0p = 1;edge0n = 2;edge1p = 0;edge1n = 2;break; case 0x00F0: /*101*/ edge0p = 1;edge0n = 2;edge1p = 1;edge1n = 0;break; case 0x000F: /*011*/ edge0p = 0;edge0n = 1;edge1p = 0;edge1n = 2;break; case 0x0000: /*111*/ y++; continue; } } ycc = _mm_max_epi16(_mm_srli_epi16(ycc, 1), screeny); ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(1, 0, 3, 2))); ycc = _mm_min_epi16(ycc, _mm_shuffle_epi32(ycc, _MM_SHUFFLE(2, 3, 0, 1))); nexty = _mm_extract_epi16(ycc, 0); if (nexty >= bandy) nexty = bandy-1; xslope = _mm_sub_ps(_mm_movelh_ps(screen[edge0n], screen[edge1n]), _mm_movelh_ps(screen[edge0p], screen[edge1p])); xslope = _mm_div_ps(xslope, _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(3, 3, 1, 1))); xcoords = _mm_add_ps(_mm_movelh_ps(screen[edge0p], screen[edge1p]), _mm_mul_ps(xslope, _mm_sub_ps(_mm_set1_ps(y), _mm_shuffle_ps(screen[edge0p], screen[edge1p], _MM_SHUFFLE(1, 1, 1, 1))))); xcoords = _mm_add_ps(xcoords, _mm_set1_ps(0.5f)); if (_mm_ucomigt_ss(xcoords, _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)))) { xcoords = _mm_shuffle_ps(xcoords, xcoords, _MM_SHUFFLE(1, 0, 3, 2)); xslope = _mm_shuffle_ps(xslope, xslope, _MM_SHUFFLE(1, 0, 3, 2)); } clip0 = clip0origin + (y+0.5f)*clip0slope + 0.5f; for(; y <= nexty; y++, xcoords = _mm_add_ps(xcoords, xslope), clip0 += clip0slope) { int startx, endx, offset; startx = _mm_cvtss_si32(xcoords); endx = _mm_cvtss_si32(_mm_movehl_ps(xcoords, xcoords)); if (startx < minx) startx = minx; if (endx > maxx) endx = maxx; if (startx >= endx) continue; if (clip0dir) { if (clip0dir > 0) { if (startx < clip0) { if(endx <= clip0) continue; startx = (int)clip0; } } else if (endx > clip0) { if(startx >= clip0) continue; endx = (int)clip0; } } for (offset = startx; offset < endx;offset += DPSOFTRAST_DRAW_MAXSPANLENGTH) { DPSOFTRAST_State_Span *span = &thread->spans[thread->numspans]; span->triangle = thread->numtriangles; span->x = offset; span->y = y; span->startx = 0; span->endx = min(endx - offset, DPSOFTRAST_DRAW_MAXSPANLENGTH); if (span->startx >= span->endx) continue; wslope = triangle->w[0]; w = triangle->w[2] + span->x*wslope + span->y*triangle->w[1]; span->depthslope = (int)(wslope*DPSOFTRAST_DEPTHSCALE); span->depthbase = (int)(w*DPSOFTRAST_DEPTHSCALE - DPSOFTRAST_DEPTHOFFSET*(thread->polygonoffset[1] + fabs(wslope)*thread->polygonoffset[0])); if (++thread->numspans >= DPSOFTRAST_DRAW_MAXSPANS) DPSOFTRAST_Draw_ProcessSpans(thread); } } } if (++thread->numtriangles >= DPSOFTRAST_DRAW_MAXTRIANGLES) { DPSOFTRAST_Draw_ProcessSpans(thread); thread->numtriangles = 0; } } if (!ATOMIC_DECREMENT(command->refcount)) { if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) MM_FREE(command->arrays); } if (thread->numspans > 0 || thread->numtriangles > 0) { DPSOFTRAST_Draw_ProcessSpans(thread); thread->numtriangles = 0; } #endif } static DPSOFTRAST_Command_Draw *DPSOFTRAST_Draw_AllocateDrawCommand(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) { int i; int j; int commandsize = DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw)); int datasize = 2*numvertices*sizeof(float[4]); DPSOFTRAST_Command_Draw *command; unsigned char *data; for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) { j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; if (j >= DPSOFTRAST_ARRAY_TOTAL) break; datasize += numvertices*sizeof(float[4]); } if (element3s) datasize += numtriangles*sizeof(unsigned short[3]); else if (element3i) datasize += numtriangles*sizeof(int[3]); datasize = DPSOFTRAST_ALIGNCOMMAND(datasize); if (commandsize + datasize > DPSOFTRAST_DRAW_MAXCOMMANDSIZE) { command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize); data = (unsigned char *)MM_CALLOC(datasize, 1); } else { command = (DPSOFTRAST_Command_Draw *) DPSOFTRAST_AllocateCommand(DPSOFTRAST_OPCODE_Draw, commandsize + datasize); data = (unsigned char *)command + commandsize; } command->firstvertex = firstvertex; command->numvertices = numvertices; command->numtriangles = numtriangles; command->arrays = (float *)data; memset(dpsoftrast.post_array4f, 0, sizeof(dpsoftrast.post_array4f)); dpsoftrast.firstvertex = firstvertex; dpsoftrast.numvertices = numvertices; dpsoftrast.screencoord4f = (float *)data; data += numvertices*sizeof(float[4]); dpsoftrast.post_array4f[DPSOFTRAST_ARRAY_POSITION] = (float *)data; data += numvertices*sizeof(float[4]); for (i = 0; i < DPSOFTRAST_ARRAY_TOTAL; i++) { j = DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].arrays[i]; if (j >= DPSOFTRAST_ARRAY_TOTAL) break; dpsoftrast.post_array4f[j] = (float *)data; data += numvertices*sizeof(float[4]); } command->element3i = NULL; command->element3s = NULL; if (element3s) { command->element3s = (unsigned short *)data; memcpy(command->element3s, element3s, numtriangles*sizeof(unsigned short[3])); } else if (element3i) { command->element3i = (int *)data; memcpy(command->element3i, element3i, numtriangles*sizeof(int[3])); } return command; } void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s) { DPSOFTRAST_Command_Draw *command = DPSOFTRAST_Draw_AllocateDrawCommand(firstvertex, numvertices, numtriangles, element3i, element3s); DPSOFTRAST_ShaderModeTable[dpsoftrast.shader_mode].Vertex(); command->starty = bound(0, dpsoftrast.drawstarty, dpsoftrast.fb_height); command->endy = bound(0, dpsoftrast.drawendy, dpsoftrast.fb_height); if (command->starty >= command->endy) { if (command->commandsize <= DPSOFTRAST_ALIGNCOMMAND(sizeof(DPSOFTRAST_Command_Draw))) MM_FREE(command->arrays); DPSOFTRAST_UndoCommand(command->commandsize); return; } command->clipped = dpsoftrast.drawclipped; command->refcount = dpsoftrast.numthreads; if (dpsoftrast.usethreads) { int i; DPSOFTRAST_Draw_SyncCommands(); for (i = 0; i < dpsoftrast.numthreads; i++) { DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; if (((command->starty < thread->maxy1 && command->endy > thread->miny1) || (command->starty < thread->maxy2 && command->endy > thread->miny2)) && thread->starving) Thread_CondSignal(thread->drawcond); } } else { DPSOFTRAST_Draw_FlushThreads(); } } DEFCOMMAND(23, SetRenderTargets, int width; int height;) static void DPSOFTRAST_Interpret_SetRenderTargets(DPSOFTRAST_State_Thread *thread, const DPSOFTRAST_Command_SetRenderTargets *command) { thread->validate |= DPSOFTRAST_VALIDATE_FB; } void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3) { DPSOFTRAST_Command_SetRenderTargets *command; if (width != dpsoftrast.fb_width || height != dpsoftrast.fb_height || depthpixels != dpsoftrast.fb_depthpixels || colorpixels0 != dpsoftrast.fb_colorpixels[0] || colorpixels1 != dpsoftrast.fb_colorpixels[1] || colorpixels2 != dpsoftrast.fb_colorpixels[2] || colorpixels3 != dpsoftrast.fb_colorpixels[3]) DPSOFTRAST_Flush(); dpsoftrast.fb_width = width; dpsoftrast.fb_height = height; dpsoftrast.fb_depthpixels = depthpixels; dpsoftrast.fb_colorpixels[0] = colorpixels0; dpsoftrast.fb_colorpixels[1] = colorpixels1; dpsoftrast.fb_colorpixels[2] = colorpixels2; dpsoftrast.fb_colorpixels[3] = colorpixels3; DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); command = DPSOFTRAST_ALLOCATECOMMAND(SetRenderTargets); command->width = width; command->height = height; } static void DPSOFTRAST_Draw_InterpretCommands(DPSOFTRAST_State_Thread *thread, int endoffset) { int commandoffset = thread->commandoffset; while (commandoffset != endoffset) { DPSOFTRAST_Command *command = (DPSOFTRAST_Command *)&dpsoftrast.commandpool.commands[commandoffset]; switch (command->opcode) { #define INTERPCOMMAND(name) \ case DPSOFTRAST_OPCODE_##name : \ DPSOFTRAST_Interpret_##name (thread, (DPSOFTRAST_Command_##name *)command); \ commandoffset += DPSOFTRAST_ALIGNCOMMAND(sizeof( DPSOFTRAST_Command_##name )); \ if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) \ commandoffset = 0; \ break; INTERPCOMMAND(Viewport) INTERPCOMMAND(ClearColor) INTERPCOMMAND(ClearDepth) INTERPCOMMAND(ColorMask) INTERPCOMMAND(DepthTest) INTERPCOMMAND(ScissorTest) INTERPCOMMAND(Scissor) INTERPCOMMAND(BlendFunc) INTERPCOMMAND(BlendSubtract) INTERPCOMMAND(DepthMask) INTERPCOMMAND(DepthFunc) INTERPCOMMAND(DepthRange) INTERPCOMMAND(PolygonOffset) INTERPCOMMAND(CullFace) INTERPCOMMAND(SetTexture) INTERPCOMMAND(SetShader) INTERPCOMMAND(Uniform4f) INTERPCOMMAND(UniformMatrix4f) INTERPCOMMAND(Uniform1i) INTERPCOMMAND(SetRenderTargets) INTERPCOMMAND(ClipPlane) case DPSOFTRAST_OPCODE_Draw: DPSOFTRAST_Interpret_Draw(thread, (DPSOFTRAST_Command_Draw *)command); commandoffset += command->commandsize; if (commandoffset >= DPSOFTRAST_DRAW_MAXCOMMANDPOOL) commandoffset = 0; thread->commandoffset = commandoffset; break; case DPSOFTRAST_OPCODE_Reset: commandoffset = 0; break; } } thread->commandoffset = commandoffset; } static int DPSOFTRAST_Draw_Thread(void *data) { DPSOFTRAST_State_Thread *thread = (DPSOFTRAST_State_Thread *)data; while(thread->index >= 0) { if (thread->commandoffset != dpsoftrast.drawcommand) { DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); } else { Thread_LockMutex(thread->drawmutex); if (thread->commandoffset == dpsoftrast.drawcommand && thread->index >= 0) { if (thread->waiting) Thread_CondSignal(thread->waitcond); thread->starving = true; Thread_CondWait(thread->drawcond, thread->drawmutex); thread->starving = false; } Thread_UnlockMutex(thread->drawmutex); } } return 0; } static void DPSOFTRAST_Draw_FlushThreads(void) { DPSOFTRAST_State_Thread *thread; int i; DPSOFTRAST_Draw_SyncCommands(); if (dpsoftrast.usethreads) { for (i = 0; i < dpsoftrast.numthreads; i++) { thread = &dpsoftrast.threads[i]; if (thread->commandoffset != dpsoftrast.drawcommand) { Thread_LockMutex(thread->drawmutex); if (thread->commandoffset != dpsoftrast.drawcommand && thread->starving) Thread_CondSignal(thread->drawcond); Thread_UnlockMutex(thread->drawmutex); } } for (i = 0; i < dpsoftrast.numthreads; i++) { thread = &dpsoftrast.threads[i]; if (thread->commandoffset != dpsoftrast.drawcommand) { Thread_LockMutex(thread->drawmutex); if (thread->commandoffset != dpsoftrast.drawcommand) { thread->waiting = true; Thread_CondWait(thread->waitcond, thread->drawmutex); thread->waiting = false; } Thread_UnlockMutex(thread->drawmutex); } } } else { for (i = 0; i < dpsoftrast.numthreads; i++) { thread = &dpsoftrast.threads[i]; if (thread->commandoffset != dpsoftrast.drawcommand) DPSOFTRAST_Draw_InterpretCommands(thread, dpsoftrast.drawcommand); } } dpsoftrast.commandpool.usedcommands = 0; } void DPSOFTRAST_Flush(void) { DPSOFTRAST_Draw_FlushThreads(); } void DPSOFTRAST_Finish(void) { DPSOFTRAST_Flush(); } int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels) { int i; union { int i; unsigned char b[4]; } u; u.i = 1; memset(&dpsoftrast, 0, sizeof(dpsoftrast)); dpsoftrast.bigendian = u.b[3]; dpsoftrast.fb_width = width; dpsoftrast.fb_height = height; dpsoftrast.fb_depthpixels = depthpixels; dpsoftrast.fb_colorpixels[0] = colorpixels; dpsoftrast.fb_colorpixels[1] = NULL; dpsoftrast.fb_colorpixels[1] = NULL; dpsoftrast.fb_colorpixels[1] = NULL; dpsoftrast.viewport[0] = 0; dpsoftrast.viewport[1] = 0; dpsoftrast.viewport[2] = dpsoftrast.fb_width; dpsoftrast.viewport[3] = dpsoftrast.fb_height; DPSOFTRAST_RecalcViewport(dpsoftrast.viewport, dpsoftrast.fb_viewportcenter, dpsoftrast.fb_viewportscale); dpsoftrast.texture_firstfree = 1; dpsoftrast.texture_end = 1; dpsoftrast.texture_max = 0; dpsoftrast.color[0] = 1; dpsoftrast.color[1] = 1; dpsoftrast.color[2] = 1; dpsoftrast.color[3] = 1; dpsoftrast.usethreads = numthreads > 0 && Thread_HasThreads(); dpsoftrast.interlace = dpsoftrast.usethreads ? bound(0, interlace, 1) : 0; dpsoftrast.numthreads = dpsoftrast.usethreads ? bound(1, numthreads, 64) : 1; dpsoftrast.threads = (DPSOFTRAST_State_Thread *)MM_CALLOC(dpsoftrast.numthreads, sizeof(DPSOFTRAST_State_Thread)); for (i = 0; i < dpsoftrast.numthreads; i++) { DPSOFTRAST_State_Thread *thread = &dpsoftrast.threads[i]; thread->index = i; thread->cullface = GL_BACK; thread->colormask[0] = 1; thread->colormask[1] = 1; thread->colormask[2] = 1; thread->colormask[3] = 1; thread->blendfunc[0] = GL_ONE; thread->blendfunc[1] = GL_ZERO; thread->depthmask = true; thread->depthtest = true; thread->depthfunc = GL_LEQUAL; thread->scissortest = false; thread->viewport[0] = 0; thread->viewport[1] = 0; thread->viewport[2] = dpsoftrast.fb_width; thread->viewport[3] = dpsoftrast.fb_height; thread->scissor[0] = 0; thread->scissor[1] = 0; thread->scissor[2] = dpsoftrast.fb_width; thread->scissor[3] = dpsoftrast.fb_height; thread->depthrange[0] = 0; thread->depthrange[1] = 1; thread->polygonoffset[0] = 0; thread->polygonoffset[1] = 0; thread->clipplane[0] = 0; thread->clipplane[1] = 0; thread->clipplane[2] = 0; thread->clipplane[3] = 1; thread->numspans = 0; thread->numtriangles = 0; thread->commandoffset = 0; thread->waiting = false; thread->starving = false; thread->validate = -1; DPSOFTRAST_Validate(thread, -1); if (dpsoftrast.usethreads) { thread->waitcond = Thread_CreateCond(); thread->drawcond = Thread_CreateCond(); thread->drawmutex = Thread_CreateMutex(); thread->thread = Thread_CreateThread(DPSOFTRAST_Draw_Thread, thread); } } return 0; } void DPSOFTRAST_Shutdown(void) { int i; if (dpsoftrast.usethreads && dpsoftrast.numthreads > 0) { DPSOFTRAST_State_Thread *thread; for (i = 0; i < dpsoftrast.numthreads; i++) { thread = &dpsoftrast.threads[i]; Thread_LockMutex(thread->drawmutex); thread->index = -1; Thread_CondSignal(thread->drawcond); Thread_UnlockMutex(thread->drawmutex); Thread_WaitThread(thread->thread, 0); Thread_DestroyCond(thread->waitcond); Thread_DestroyCond(thread->drawcond); Thread_DestroyMutex(thread->drawmutex); } } for (i = 0;i < dpsoftrast.texture_end;i++) if (dpsoftrast.texture[i].bytes) MM_FREE(dpsoftrast.texture[i].bytes); if (dpsoftrast.texture) free(dpsoftrast.texture); if (dpsoftrast.threads) MM_FREE(dpsoftrast.threads); memset(&dpsoftrast, 0, sizeof(dpsoftrast)); } darkplaces/cd_bsd.c0000664000175000017500000001234613067716216013560 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include #include #include #include #include #include #include #include #ifndef __FreeBSD__ # include #endif #include "cdaudio.h" #ifndef __FreeBSD__ # define DEFAULT_CD_DEVICE _PATH_DEV "cd0" #else # define DEFAULT_CD_DEVICE "/dev/acd0c" #endif static int cdfile = -1; static char cd_dev[64] = DEFAULT_CD_DEVICE; void CDAudio_SysEject (void) { if (cdfile == -1) return; ioctl(cdfile, CDIOCALLOW); if (ioctl(cdfile, CDIOCEJECT) == -1) Con_Print("ioctl CDIOCEJECT failed\n"); } void CDAudio_SysCloseDoor (void) { if (cdfile == -1) return; ioctl(cdfile, CDIOCALLOW); if (ioctl(cdfile, CDIOCCLOSE) == -1) Con_Print("ioctl CDIOCCLOSE failed\n"); } int CDAudio_SysGetAudioDiskInfo (void) { struct ioc_toc_header tochdr; if (cdfile == -1) return -1; if (ioctl(cdfile, CDIOREADTOCHEADER, &tochdr) == -1) { Con_Print("ioctl CDIOREADTOCHEADER failed\n"); return -1; } if (tochdr.starting_track < 1) { Con_Print("CDAudio: no music tracks\n"); return -1; } return tochdr.ending_track; } float CDAudio_SysGetVolume (void) { struct ioc_vol vol; if (cdfile == -1) return -1.0f; if (ioctl (cdfile, CDIOCGETVOL, &vol) == -1) { Con_Print("ioctl CDIOCGETVOL failed\n"); return -1.0f; } return (vol.vol[0] + vol.vol[1]) / 2.0f / 255.0f; } void CDAudio_SysSetVolume (float volume) { struct ioc_vol vol; if (cdfile == -1) return; vol.vol[0] = vol.vol[1] = volume * 255; vol.vol[2] = vol.vol[3] = 0; if (ioctl (cdfile, CDIOCSETVOL, &vol) == -1) Con_Printf ("ioctl CDIOCSETVOL failed\n"); } int CDAudio_SysPlay (int track) { struct ioc_read_toc_entry rte; struct cd_toc_entry entry; struct ioc_play_track ti; if (cdfile == -1) return -1; // don't try to play a non-audio track rte.address_format = CD_MSF_FORMAT; rte.starting_track = track; rte.data_len = sizeof(entry); rte.data = &entry; if (ioctl(cdfile, CDIOREADTOCENTRYS, &rte) == -1) { Con_Print("ioctl CDIOREADTOCENTRYS failed\n"); return -1; } if (entry.control & 4) // if it's a data track { Con_Printf("CDAudio: track %i is not audio\n", track); return -1; } if (cdPlaying) CDAudio_Stop(); ti.start_track = track; ti.end_track = track; ti.start_index = 1; ti.end_index = 99; if (ioctl(cdfile, CDIOCPLAYTRACKS, &ti) == -1) { Con_Print("ioctl CDIOCPLAYTRACKS failed\n"); return -1; } if (ioctl(cdfile, CDIOCRESUME) == -1) { Con_Print("ioctl CDIOCRESUME failed\n"); return -1; } return 0; } int CDAudio_SysStop (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDIOCSTOP) == -1) { Con_Printf("ioctl CDIOCSTOP failed (%d)\n", errno); return -1; } ioctl(cdfile, CDIOCALLOW); return 0; } int CDAudio_SysPause (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDIOCPAUSE) == -1) { Con_Print("ioctl CDIOCPAUSE failed\n"); return -1; } return 0; } int CDAudio_SysResume (void) { if (cdfile == -1) return -1; if (ioctl(cdfile, CDIOCRESUME) == -1) Con_Print("ioctl CDIOCRESUME failed\n"); return 0; } int CDAudio_SysUpdate (void) { static time_t lastchk = 0; struct ioc_read_subchannel subchnl; struct cd_sub_channel_info data; if (cdPlaying && lastchk < time(NULL)) { lastchk = time(NULL) + 2; //two seconds between chks bzero(&subchnl, sizeof(subchnl)); subchnl.data = &data; subchnl.data_len = sizeof(data); subchnl.address_format = CD_MSF_FORMAT; subchnl.data_format = CD_CURRENT_POSITION; if (ioctl(cdfile, CDIOCREADSUBCHANNEL, &subchnl) == -1) { Con_Print("ioctl CDIOCREADSUBCHANNEL failed\n"); cdPlaying = false; return -1; } if (data.header.audio_status != CD_AS_PLAY_IN_PROGRESS && data.header.audio_status != CD_AS_PLAY_PAUSED) { cdPlaying = false; if (cdPlayLooping) CDAudio_Play(cdPlayTrack, true); } else cdPlayTrack = data.what.position.track_number; } return 0; } void CDAudio_SysInit (void) { int i; // COMMANDLINEOPTION: BSD Sound: -cddev chooses which CD drive to use if ((i = COM_CheckParm("-cddev")) != 0 && i < com_argc - 1) strlcpy(cd_dev, com_argv[i + 1], sizeof(cd_dev)); } int CDAudio_SysStartup (void) { #ifndef __FreeBSD__ char buff [80]; if ((cdfile = opendisk(cd_dev, O_RDONLY, buff, sizeof(buff), 0)) == -1) #else if ((cdfile = open(cd_dev, O_RDONLY)) < 0) #endif { Con_Printf("CDAudio_SysStartup: open of \"%s\" failed (%i)\n", cd_dev, errno); cdfile = -1; return -1; } return 0; } void CDAudio_SysShutdown (void) { close(cdfile); cdfile = -1; } darkplaces/cap_ogg.c0000664000175000017500000011175213067716216013742 0ustar kalevkalev#ifndef _MSC_VER #include #endif #include #include "quakedef.h" #include "client.h" #include "cap_ogg.h" // video capture cvars static cvar_t cl_capturevideo_ogg_theora_vp3compat = {CVAR_SAVE, "cl_capturevideo_ogg_theora_vp3compat", "1", "make VP3 compatible theora streams"}; static cvar_t cl_capturevideo_ogg_theora_quality = {CVAR_SAVE, "cl_capturevideo_ogg_theora_quality", "48", "video quality factor (0 to 63), or -1 to use bitrate only; higher is better; setting both to -1 achieves unlimited quality"}; static cvar_t cl_capturevideo_ogg_theora_bitrate = {CVAR_SAVE, "cl_capturevideo_ogg_theora_bitrate", "-1", "video bitrate (45 to 2000 kbps), or -1 to use quality only; higher is better; setting both to -1 achieves unlimited quality"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier", "1.5", "how much more bit rate to use for keyframes, specified as a factor of at least 1"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_maxinterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_maxinterval", "64", "maximum keyframe interval (1 to 1000)"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_mininterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_mininterval", "8", "minimum keyframe interval (1 to 1000)"}; static cvar_t cl_capturevideo_ogg_theora_keyframe_auto_threshold = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_auto_threshold", "80", "threshold for key frame decision (0 to 100)"}; static cvar_t cl_capturevideo_ogg_theora_noise_sensitivity = {CVAR_SAVE, "cl_capturevideo_ogg_theora_noise_sensitivity", "1", "video noise sensitivity (0 to 6); lower is better"}; static cvar_t cl_capturevideo_ogg_theora_sharpness = {CVAR_SAVE, "cl_capturevideo_ogg_theora_sharpness", "0", "sharpness (0 to 2); lower is sharper"}; static cvar_t cl_capturevideo_ogg_vorbis_quality = {CVAR_SAVE, "cl_capturevideo_ogg_vorbis_quality", "3", "audio quality (-1 to 10); higher is better"}; // ogg.h stuff #ifdef _MSC_VER typedef __int16 ogg_int16_t; typedef unsigned __int16 ogg_uint16_t; typedef __int32 ogg_int32_t; typedef unsigned __int32 ogg_uint32_t; typedef __int64 ogg_int64_t; #else typedef int16_t ogg_int16_t; typedef uint16_t ogg_uint16_t; typedef int32_t ogg_int32_t; typedef uint32_t ogg_uint32_t; typedef int64_t ogg_int64_t; #endif typedef struct { long endbyte; int endbit; unsigned char *buffer; unsigned char *ptr; long storage; } oggpack_buffer; /* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/ typedef struct { unsigned char *header; long header_len; unsigned char *body; long body_len; } ogg_page; /* ogg_stream_state contains the current encode/decode state of a logical Ogg bitstream **********************************************************/ typedef struct { unsigned char *body_data; /* bytes from packet bodies */ long body_storage; /* storage elements allocated */ long body_fill; /* elements stored; fill mark */ long body_returned; /* elements of fill returned */ int *lacing_vals; /* The values that will go to the segment table */ ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact this way, but it is simple coupled to the lacing fifo */ long lacing_storage; long lacing_fill; long lacing_packet; long lacing_returned; unsigned char header[282]; /* working space for header encode */ int header_fill; int e_o_s; /* set when we have buffered the last packet in the logical bitstream */ int b_o_s; /* set after we've written the initial page of a logical bitstream */ long serialno; long pageno; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ ogg_int64_t granulepos; } ogg_stream_state; /* ogg_packet is used to encapsulate the data and metadata belonging to a single raw Ogg/Vorbis packet *************************************/ typedef struct { unsigned char *packet; long bytes; long b_o_s; long e_o_s; ogg_int64_t granulepos; ogg_int64_t packetno; /* sequence number for decode; the framing knows where there's a hole in the data, but we need coupling so that the codec (which is in a seperate abstraction layer) also knows about the gap */ } ogg_packet; typedef struct { unsigned char *data; int storage; int fill; int returned; int unsynced; int headerbytes; int bodybytes; } ogg_sync_state; /* Ogg BITSTREAM PRIMITIVES: encoding **************************/ static int (*qogg_stream_packetin) (ogg_stream_state *os, ogg_packet *op); static int (*qogg_stream_pageout) (ogg_stream_state *os, ogg_page *og); static int (*qogg_stream_flush) (ogg_stream_state *os, ogg_page *og); /* Ogg BITSTREAM PRIMITIVES: general ***************************/ static int (*qogg_stream_init) (ogg_stream_state *os,int serialno); static int (*qogg_stream_clear) (ogg_stream_state *os); static ogg_int64_t (*qogg_page_granulepos) (ogg_page *og); // end of ogg.h stuff // vorbis/codec.h stuff typedef struct vorbis_info{ int version; int channels; long rate; /* The below bitrate declarations are *hints*. Combinations of the three values carry the following implications: all three set to the same value: implies a fixed rate bitstream only nominal set: implies a VBR stream that averages the nominal bitrate. No hard upper/lower limit upper and or lower set: implies a VBR bitstream that obeys the bitrate limits. nominal may also be set to give a nominal rate. none set: the coder does not care to speculate. */ long bitrate_upper; long bitrate_nominal; long bitrate_lower; long bitrate_window; void *codec_setup; } vorbis_info; /* vorbis_dsp_state buffers the current vorbis audio analysis/synthesis state. The DSP state belongs to a specific logical bitstream ****************************************************/ typedef struct vorbis_dsp_state{ int analysisp; vorbis_info *vi; float **pcm; float **pcmret; int pcm_storage; int pcm_current; int pcm_returned; int preextrapolate; int eofflag; long lW; long W; long nW; long centerW; ogg_int64_t granulepos; ogg_int64_t sequence; ogg_int64_t glue_bits; ogg_int64_t time_bits; ogg_int64_t floor_bits; ogg_int64_t res_bits; void *backend_state; } vorbis_dsp_state; typedef struct vorbis_block{ /* necessary stream state for linking to the framing abstraction */ float **pcm; /* this is a pointer into local storage */ oggpack_buffer opb; long lW; long W; long nW; int pcmend; int mode; int eofflag; ogg_int64_t granulepos; ogg_int64_t sequence; vorbis_dsp_state *vd; /* For read-only access of configuration */ /* local storage to avoid remallocing; it's up to the mapping to structure it */ void *localstore; long localtop; long localalloc; long totaluse; struct alloc_chain *reap; /* bitmetrics for the frame */ long glue_bits; long time_bits; long floor_bits; long res_bits; void *internal; } vorbis_block; /* vorbis_block is a single block of data to be processed as part of the analysis/synthesis stream; it belongs to a specific logical bitstream, but is independant from other vorbis_blocks belonging to that logical bitstream. *************************************************/ struct alloc_chain{ void *ptr; struct alloc_chain *next; }; /* vorbis_info contains all the setup information specific to the specific compression/decompression mode in progress (eg, psychoacoustic settings, channel setup, options, codebook etc). vorbis_info and substructures are in backends.h. *********************************************************************/ /* the comments are not part of vorbis_info so that vorbis_info can be static storage */ typedef struct vorbis_comment{ /* unlimited user comment fields. libvorbis writes 'libvorbis' whatever vendor is set to in encode */ char **user_comments; int *comment_lengths; int comments; char *vendor; } vorbis_comment; /* libvorbis encodes in two abstraction layers; first we perform DSP and produce a packet (see docs/analysis.txt). The packet is then coded into a framed OggSquish bitstream by the second layer (see docs/framing.txt). Decode is the reverse process; we sync/frame the bitstream and extract individual packets, then decode the packet back into PCM audio. The extra framing/packetizing is used in streaming formats, such as files. Over the net (such as with UDP), the framing and packetization aren't necessary as they're provided by the transport and the streaming layer is not used */ /* Vorbis PRIMITIVES: general ***************************************/ static void (*qvorbis_info_init) (vorbis_info *vi); static void (*qvorbis_info_clear) (vorbis_info *vi); static void (*qvorbis_comment_init) (vorbis_comment *vc); static void (*qvorbis_comment_clear) (vorbis_comment *vc); static int (*qvorbis_block_init) (vorbis_dsp_state *v, vorbis_block *vb); static int (*qvorbis_block_clear) (vorbis_block *vb); static void (*qvorbis_dsp_clear) (vorbis_dsp_state *v); static double (*qvorbis_granule_time) (vorbis_dsp_state *v, ogg_int64_t granulepos); /* Vorbis PRIMITIVES: analysis/DSP layer ****************************/ static int (*qvorbis_analysis_init) (vorbis_dsp_state *v,vorbis_info *vi); static int (*qvorbis_commentheader_out) (vorbis_comment *vc, ogg_packet *op); static int (*qvorbis_analysis_headerout) (vorbis_dsp_state *v, vorbis_comment *vc, ogg_packet *op, ogg_packet *op_comm, ogg_packet *op_code); static float ** (*qvorbis_analysis_buffer) (vorbis_dsp_state *v,int vals); static int (*qvorbis_analysis_wrote) (vorbis_dsp_state *v,int vals); static int (*qvorbis_analysis_blockout) (vorbis_dsp_state *v,vorbis_block *vb); static int (*qvorbis_analysis) (vorbis_block *vb,ogg_packet *op); static int (*qvorbis_bitrate_addblock) (vorbis_block *vb); static int (*qvorbis_bitrate_flushpacket) (vorbis_dsp_state *vd, ogg_packet *op); // end of vorbis/codec.h stuff // vorbisenc.h stuff static int (*qvorbis_encode_init_vbr) (vorbis_info *vi, long channels, long rate, float base_quality /* quality level from 0. (lo) to 1. (hi) */ ); // end of vorbisenc.h stuff // theora.h stuff #define TH_ENCCTL_SET_VP3_COMPATIBLE (10) typedef struct { int y_width; /**< Width of the Y' luminance plane */ int y_height; /**< Height of the luminance plane */ int y_stride; /**< Offset in bytes between successive rows */ int uv_width; /**< Width of the Cb and Cr chroma planes */ int uv_height; /**< Height of the chroma planes */ int uv_stride; /**< Offset between successive chroma rows */ unsigned char *y; /**< Pointer to start of luminance data */ unsigned char *u; /**< Pointer to start of Cb data */ unsigned char *v; /**< Pointer to start of Cr data */ } yuv_buffer; /** * A Colorspace. */ typedef enum { OC_CS_UNSPECIFIED, /**< The colorspace is unknown or unspecified */ OC_CS_ITU_REC_470M, /**< This is the best option for 'NTSC' content */ OC_CS_ITU_REC_470BG, /**< This is the best option for 'PAL' content */ OC_CS_NSPACES /**< This marks the end of the defined colorspaces */ } theora_colorspace; /** * A Chroma subsampling * * These enumerate the available chroma subsampling options supported * by the theora format. See Section 4.4 of the specification for * exact definitions. */ typedef enum { OC_PF_420, /**< Chroma subsampling by 2 in each direction (4:2:0) */ OC_PF_RSVD, /**< Reserved value */ OC_PF_422, /**< Horizonatal chroma subsampling by 2 (4:2:2) */ OC_PF_444 /**< No chroma subsampling at all (4:4:4) */ } theora_pixelformat; /** * Theora bitstream info. * Contains the basic playback parameters for a stream, * corresponding to the initial 'info' header packet. * * Encoded theora frames must be a multiple of 16 in width and height. * To handle other frame sizes, a crop rectangle is specified in * frame_height and frame_width, offset_x and * offset_y. The offset * and size should still be a multiple of 2 to avoid chroma sampling * shifts. Offset values in this structure are measured from the * upper left of the image. * * Frame rate, in frames per second, is stored as a rational * fraction. Aspect ratio is also stored as a rational fraction, and * refers to the aspect ratio of the frame pixels, not of the * overall frame itself. * * See * examples/encoder_example.c for usage examples of the * other paramters and good default settings for the encoder parameters. */ typedef struct { ogg_uint32_t width; /**< encoded frame width */ ogg_uint32_t height; /**< encoded frame height */ ogg_uint32_t frame_width; /**< display frame width */ ogg_uint32_t frame_height; /**< display frame height */ ogg_uint32_t offset_x; /**< horizontal offset of the displayed frame */ ogg_uint32_t offset_y; /**< vertical offset of the displayed frame */ ogg_uint32_t fps_numerator; /**< frame rate numerator **/ ogg_uint32_t fps_denominator; /**< frame rate denominator **/ ogg_uint32_t aspect_numerator; /**< pixel aspect ratio numerator */ ogg_uint32_t aspect_denominator; /**< pixel aspect ratio denominator */ theora_colorspace colorspace; /**< colorspace */ int target_bitrate; /**< nominal bitrate in bits per second */ int quality; /**< Nominal quality setting, 0-63 */ int quick_p; /**< Quick encode/decode */ /* decode only */ unsigned char version_major; unsigned char version_minor; unsigned char version_subminor; void *codec_setup; /* encode only */ int dropframes_p; int keyframe_auto_p; ogg_uint32_t keyframe_frequency; ogg_uint32_t keyframe_frequency_force; /* also used for decode init to get granpos shift correct */ ogg_uint32_t keyframe_data_target_bitrate; ogg_int32_t keyframe_auto_threshold; ogg_uint32_t keyframe_mindistance; ogg_int32_t noise_sensitivity; ogg_int32_t sharpness; theora_pixelformat pixelformat; /**< chroma subsampling mode to expect */ } theora_info; /** Codec internal state and context. */ typedef struct{ theora_info *i; ogg_int64_t granulepos; void *internal_encode; void *internal_decode; } theora_state; /** * Comment header metadata. * * This structure holds the in-stream metadata corresponding to * the 'comment' header packet. * * Meta data is stored as a series of (tag, value) pairs, in * length-encoded string vectors. The first occurence of the * '=' character delimits the tag and value. A particular tag * may occur more than once. The character set encoding for * the strings is always UTF-8, but the tag names are limited * to case-insensitive ASCII. See the spec for details. * * In filling in this structure, qtheora_decode_header() will * null-terminate the user_comment strings for safety. However, * the bitstream format itself treats them as 8-bit clean, * and so the length array should be treated as authoritative * for their length. */ typedef struct theora_comment{ char **user_comments; /**< An array of comment string vectors */ int *comment_lengths; /**< An array of corresponding string vector lengths in bytes */ int comments; /**< The total number of comment string vectors */ char *vendor; /**< The vendor string identifying the encoder, null terminated */ } theora_comment; static int (*qtheora_encode_init) (theora_state *th, theora_info *ti); static int (*qtheora_encode_YUVin) (theora_state *t, yuv_buffer *yuv); static int (*qtheora_encode_packetout) ( theora_state *t, int last_p, ogg_packet *op); static int (*qtheora_encode_header) (theora_state *t, ogg_packet *op); static int (*qtheora_encode_comment) (theora_comment *tc, ogg_packet *op); static int (*qtheora_encode_tables) (theora_state *t, ogg_packet *op); static void (*qtheora_info_init) (theora_info *c); static void (*qtheora_info_clear) (theora_info *c); static void (*qtheora_clear) (theora_state *t); static void (*qtheora_comment_init) (theora_comment *tc); static void (*qtheora_comment_clear) (theora_comment *tc); static double (*qtheora_granule_time) (theora_state *th,ogg_int64_t granulepos); static int (*qtheora_control) (theora_state *th,int req,void *buf,size_t buf_sz); // end of theora.h stuff static dllfunction_t oggfuncs[] = { {"ogg_stream_packetin", (void **) &qogg_stream_packetin}, {"ogg_stream_pageout", (void **) &qogg_stream_pageout}, {"ogg_stream_flush", (void **) &qogg_stream_flush}, {"ogg_stream_init", (void **) &qogg_stream_init}, {"ogg_stream_clear", (void **) &qogg_stream_clear}, {"ogg_page_granulepos", (void **) &qogg_page_granulepos}, {NULL, NULL} }; static dllfunction_t vorbisencfuncs[] = { {"vorbis_encode_init_vbr", (void **) &qvorbis_encode_init_vbr}, {NULL, NULL} }; static dllfunction_t vorbisfuncs[] = { {"vorbis_info_init", (void **) &qvorbis_info_init}, {"vorbis_info_clear", (void **) &qvorbis_info_clear}, {"vorbis_comment_init", (void **) &qvorbis_comment_init}, {"vorbis_comment_clear", (void **) &qvorbis_comment_clear}, {"vorbis_block_init", (void **) &qvorbis_block_init}, {"vorbis_block_clear", (void **) &qvorbis_block_clear}, {"vorbis_dsp_clear", (void **) &qvorbis_dsp_clear}, {"vorbis_analysis_init", (void **) &qvorbis_analysis_init}, {"vorbis_commentheader_out", (void **) &qvorbis_commentheader_out}, {"vorbis_analysis_headerout", (void **) &qvorbis_analysis_headerout}, {"vorbis_analysis_buffer", (void **) &qvorbis_analysis_buffer}, {"vorbis_analysis_wrote", (void **) &qvorbis_analysis_wrote}, {"vorbis_analysis_blockout", (void **) &qvorbis_analysis_blockout}, {"vorbis_analysis", (void **) &qvorbis_analysis}, {"vorbis_bitrate_addblock", (void **) &qvorbis_bitrate_addblock}, {"vorbis_bitrate_flushpacket", (void **) &qvorbis_bitrate_flushpacket}, {"vorbis_granule_time", (void **) &qvorbis_granule_time}, {NULL, NULL} }; static dllfunction_t theorafuncs[] = { {"theora_info_init", (void **) &qtheora_info_init}, {"theora_info_clear", (void **) &qtheora_info_clear}, {"theora_comment_init", (void **) &qtheora_comment_init}, {"theora_comment_clear", (void **) &qtheora_comment_clear}, {"theora_encode_init", (void **) &qtheora_encode_init}, {"theora_encode_YUVin", (void **) &qtheora_encode_YUVin}, {"theora_encode_packetout", (void **) &qtheora_encode_packetout}, {"theora_encode_header", (void **) &qtheora_encode_header}, {"theora_encode_comment", (void **) &qtheora_encode_comment}, {"theora_encode_tables", (void **) &qtheora_encode_tables}, {"theora_clear", (void **) &qtheora_clear}, {"theora_granule_time", (void **) &qtheora_granule_time}, {"theora_control", (void **) &qtheora_control}, {NULL, NULL} }; static dllhandle_t og_dll = NULL, vo_dll = NULL, ve_dll = NULL, th_dll = NULL; static qboolean SCR_CaptureVideo_Ogg_OpenLibrary(void) { const char* dllnames_og [] = { #if defined(WIN32) "libogg-0.dll", "libogg.dll", "ogg.dll", #elif defined(MACOSX) "libogg.dylib", #else "libogg.so.0", "libogg.so", #endif NULL }; const char* dllnames_vo [] = { #if defined(WIN32) "libvorbis-0.dll", "libvorbis.dll", "vorbis.dll", #elif defined(MACOSX) "libvorbis.dylib", #else "libvorbis.so.0", "libvorbis.so", #endif NULL }; const char* dllnames_ve [] = { #if defined(WIN32) "libvorbisenc-2.dll", "libvorbisenc.dll", "vorbisenc.dll", #elif defined(MACOSX) "libvorbisenc.dylib", #else "libvorbisenc.so.2", "libvorbisenc.so", #endif NULL }; const char* dllnames_th [] = { #if defined(WIN32) "libtheora-0.dll", "libtheora.dll", "theora.dll", #elif defined(MACOSX) "libtheora.dylib", #else "libtheora.so.0", "libtheora.so", #endif NULL }; return Sys_LoadLibrary (dllnames_og, &og_dll, oggfuncs) && Sys_LoadLibrary (dllnames_th, &th_dll, theorafuncs) && Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs) && Sys_LoadLibrary (dllnames_ve, &ve_dll, vorbisencfuncs); } void SCR_CaptureVideo_Ogg_Init(void) { SCR_CaptureVideo_Ogg_OpenLibrary(); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_vp3compat); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_quality); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_bitrate); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_maxinterval); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_mininterval); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_auto_threshold); Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_noise_sensitivity); Cvar_RegisterVariable(&cl_capturevideo_ogg_vorbis_quality); } qboolean SCR_CaptureVideo_Ogg_Available(void) { return og_dll && th_dll && vo_dll && ve_dll; } void SCR_CaptureVideo_Ogg_CloseDLL(void) { Sys_UnloadLibrary (&ve_dll); Sys_UnloadLibrary (&vo_dll); Sys_UnloadLibrary (&th_dll); Sys_UnloadLibrary (&og_dll); } // this struct should not be needed // however, libogg appears to pull the ogg_page's data element away from our // feet before we get to write the data due to interleaving // so this struct is used to keep the page data around until it actually gets // written typedef struct allocatedoggpage_s { size_t len; double time; unsigned char data[65307]; // this number is from RFC 3533. In case libogg writes more, we'll have to increase this // but we'll get a Host_Error in this case so we can track it down } allocatedoggpage_t; typedef struct capturevideostate_ogg_formatspecific_s { ogg_stream_state to, vo; int serial1, serial2; theora_state ts; vorbis_dsp_state vd; vorbis_block vb; vorbis_info vi; yuv_buffer yuv[2]; int yuvi; int lastnum; int channels; allocatedoggpage_t videopage, audiopage; } capturevideostate_ogg_formatspecific_t; #define LOAD_FORMATSPECIFIC_OGG() capturevideostate_ogg_formatspecific_t *format = (capturevideostate_ogg_formatspecific_t *) cls.capturevideo.formatspecific static void SCR_CaptureVideo_Ogg_Interleave(void) { LOAD_FORMATSPECIFIC_OGG(); ogg_page pg; if(!cls.capturevideo.soundrate) { while(qogg_stream_pageout(&format->to, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } return; } for(;;) { // first: make sure we have a page of both types if(!format->videopage.len) if(qogg_stream_pageout(&format->to, &pg) > 0) { format->videopage.len = pg.header_len + pg.body_len; format->videopage.time = qtheora_granule_time(&format->ts, qogg_page_granulepos(&pg)); if(format->videopage.len > sizeof(format->videopage.data)) Sys_Error("video page too long"); memcpy(format->videopage.data, pg.header, pg.header_len); memcpy(format->videopage.data + pg.header_len, pg.body, pg.body_len); } if(!format->audiopage.len) if(qogg_stream_pageout(&format->vo, &pg) > 0) { format->audiopage.len = pg.header_len + pg.body_len; format->audiopage.time = qvorbis_granule_time(&format->vd, qogg_page_granulepos(&pg)); if(format->audiopage.len > sizeof(format->audiopage.data)) Sys_Error("audio page too long"); memcpy(format->audiopage.data, pg.header, pg.header_len); memcpy(format->audiopage.data + pg.header_len, pg.body, pg.body_len); } if(format->videopage.len && format->audiopage.len) { // output the page that ends first if(format->videopage.time < format->audiopage.time) { FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); format->videopage.len = 0; } else { FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); format->audiopage.len = 0; } } else break; } } static void SCR_CaptureVideo_Ogg_FlushInterleaving(void) { LOAD_FORMATSPECIFIC_OGG(); if(cls.capturevideo.soundrate) if(format->audiopage.len) { FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len); format->audiopage.len = 0; } if(format->videopage.len) { FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len); format->videopage.len = 0; } } static void SCR_CaptureVideo_Ogg_EndVideo(void) { LOAD_FORMATSPECIFIC_OGG(); ogg_page pg; ogg_packet pt; if(format->yuvi >= 0) { // send the previous (and last) frame while(format->lastnum-- > 0) { qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); while(qtheora_encode_packetout(&format->ts, !format->lastnum, &pt)) qogg_stream_packetin(&format->to, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } if(cls.capturevideo.soundrate) { qvorbis_analysis_wrote(&format->vd, 0); while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) { qvorbis_analysis(&format->vb, NULL); qvorbis_bitrate_addblock(&format->vb); while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) qogg_stream_packetin(&format->vo, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } SCR_CaptureVideo_Ogg_FlushInterleaving(); while(qogg_stream_pageout(&format->to, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) { while(qogg_stream_pageout(&format->vo, &pg) > 0) { FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } } while (1) { int result = qogg_stream_flush (&format->to, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) { while (1) { int result = qogg_stream_flush (&format->vo, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } qogg_stream_clear(&format->vo); qvorbis_block_clear(&format->vb); qvorbis_dsp_clear(&format->vd); } qogg_stream_clear(&format->to); qtheora_clear(&format->ts); qvorbis_info_clear(&format->vi); Mem_Free(format->yuv[0].y); Mem_Free(format->yuv[0].u); Mem_Free(format->yuv[0].v); Mem_Free(format->yuv[1].y); Mem_Free(format->yuv[1].u); Mem_Free(format->yuv[1].v); Mem_Free(format); FS_Close(cls.capturevideo.videofile); cls.capturevideo.videofile = NULL; } static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void) { LOAD_FORMATSPECIFIC_OGG(); yuv_buffer *yuv; int x, y; int blockr, blockg, blockb; unsigned char *b; int w = cls.capturevideo.width; int h = cls.capturevideo.height; int inpitch = w*4; yuv = &format->yuv[format->yuvi]; for(y = 0; y < h; ++y) { for(b = cls.capturevideo.outbuffer + (h-1-y)*w*4, x = 0; x < w; ++x) { blockr = b[2]; blockg = b[1]; blockb = b[0]; yuv->y[x + yuv->y_stride * y] = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]]; b += 4; } if ((y & 1) == 0 && y/2 < h/2) // if h is odd, this skips the last row { for(b = cls.capturevideo.outbuffer + (h-2-y)*w*4, x = 0; x < w/2; ++x) { blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2; blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2; blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2; yuv->u[x + yuv->uv_stride * (y/2)] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128]; yuv->v[x + yuv->uv_stride * (y/2)] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128]; b += 8; } } } } static void SCR_CaptureVideo_Ogg_VideoFrames(int num) { LOAD_FORMATSPECIFIC_OGG(); ogg_packet pt; // data is in cls.capturevideo.outbuffer as BGRA and has size width*height if(format->yuvi >= 0) { // send the previous frame while(format->lastnum-- > 0) { qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]); while(qtheora_encode_packetout(&format->ts, false, &pt)) qogg_stream_packetin(&format->to, &pt); SCR_CaptureVideo_Ogg_Interleave(); } } format->yuvi = (format->yuvi + 1) % 2; SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(); format->lastnum = num; // TODO maybe send num-1 frames from here already } typedef int channelmapping_t[8]; channelmapping_t mapping[8] = { { 0, -1, -1, -1, -1, -1, -1, -1 }, // mono { 0, 1, -1, -1, -1, -1, -1, -1 }, // stereo { 0, 1, 2, -1, -1, -1, -1, -1 }, // L C R { 0, 1, 2, 3, -1, -1, -1, -1 }, // surround40 { 0, 2, 3, 4, 1, -1, -1, -1 }, // FL FC FR RL RR { 0, 2, 3, 4, 1, 5, -1, -1 }, // surround51 { 0, 2, 3, 4, 1, 5, 6, -1 }, // (not defined by vorbis spec) { 0, 2, 3, 4, 1, 5, 6, 7 } // surround71 (not defined by vorbis spec) }; static void SCR_CaptureVideo_Ogg_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) { LOAD_FORMATSPECIFIC_OGG(); float **vorbis_buffer; size_t i; int j; ogg_packet pt; int *map = mapping[bound(1, cls.capturevideo.soundchannels, 8) - 1]; vorbis_buffer = qvorbis_analysis_buffer(&format->vd, (int)length); for(j = 0; j < cls.capturevideo.soundchannels; ++j) { float *b = vorbis_buffer[map[j]]; for(i = 0; i < length; ++i) b[i] = paintbuffer[i].sample[j]; } qvorbis_analysis_wrote(&format->vd, (int)length); while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1) { qvorbis_analysis(&format->vb, NULL); qvorbis_bitrate_addblock(&format->vb); while(qvorbis_bitrate_flushpacket(&format->vd, &pt)) qogg_stream_packetin(&format->vo, &pt); } SCR_CaptureVideo_Ogg_Interleave(); } void SCR_CaptureVideo_Ogg_BeginVideo(void) { char vabuf[1024]; cls.capturevideo.format = CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA; cls.capturevideo.formatextension = "ogv"; cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false); cls.capturevideo.endvideo = SCR_CaptureVideo_Ogg_EndVideo; cls.capturevideo.videoframes = SCR_CaptureVideo_Ogg_VideoFrames; cls.capturevideo.soundframe = SCR_CaptureVideo_Ogg_SoundFrame; cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_ogg_formatspecific_t)); { LOAD_FORMATSPECIFIC_OGG(); int num, denom, i; ogg_page pg; ogg_packet pt, pt2, pt3; theora_comment tc; vorbis_comment vc; theora_info ti; int vp3compat; format->serial1 = rand(); qogg_stream_init(&format->to, format->serial1); if(cls.capturevideo.soundrate) { do { format->serial2 = rand(); } while(format->serial1 == format->serial2); qogg_stream_init(&format->vo, format->serial2); } format->videopage.len = format->audiopage.len = 0; qtheora_info_init(&ti); ti.frame_width = cls.capturevideo.width; ti.frame_height = cls.capturevideo.height; ti.width = (ti.frame_width + 15) & ~15; ti.height = (ti.frame_height + 15) & ~15; //ti.offset_x = ((ti.width - ti.frame_width) / 2) & ~1; //ti.offset_y = ((ti.height - ti.frame_height) / 2) & ~1; for(i = 0; i < 2; ++i) { format->yuv[i].y_width = ti.width; format->yuv[i].y_height = ti.height; format->yuv[i].y_stride = ti.width; format->yuv[i].uv_width = ti.width / 2; format->yuv[i].uv_height = ti.height / 2; format->yuv[i].uv_stride = ti.width / 2; format->yuv[i].y = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].y_stride * format->yuv[i].y_height); format->yuv[i].u = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); format->yuv[i].v = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height); } format->yuvi = -1; // -1: no frame valid yet, write into 0 FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &num, &denom, 1001); ti.fps_numerator = num; ti.fps_denominator = denom; FindFraction(1 / vid_pixelheight.value, &num, &denom, 1000); ti.aspect_numerator = num; ti.aspect_denominator = denom; ti.colorspace = OC_CS_UNSPECIFIED; ti.pixelformat = OC_PF_420; ti.quick_p = true; // http://mlblog.osdir.com/multimedia.ogg.theora.general/2004-07/index.shtml ti.dropframes_p = false; ti.target_bitrate = cl_capturevideo_ogg_theora_bitrate.integer * 1000; ti.quality = cl_capturevideo_ogg_theora_quality.integer; if(ti.target_bitrate <= 0) { ti.target_bitrate = -1; ti.keyframe_data_target_bitrate = (unsigned int)-1; } else { ti.keyframe_data_target_bitrate = (int) (ti.target_bitrate * max(1, cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier.value)); if(ti.target_bitrate < 45000 || ti.target_bitrate > 2000000) Con_DPrintf("WARNING: requesting an odd bitrate for theora (sensible values range from 45 to 2000 kbps)\n"); } if(ti.quality < 0 || ti.quality > 63) { ti.quality = 63; if(ti.target_bitrate <= 0) { ti.target_bitrate = 0x7FFFFFFF; ti.keyframe_data_target_bitrate = 0x7FFFFFFF; } } // this -1 magic is because ti.keyframe_frequency and ti.keyframe_mindistance use different metrics ti.keyframe_frequency = bound(1, cl_capturevideo_ogg_theora_keyframe_maxinterval.integer, 1000); ti.keyframe_mindistance = bound(1, cl_capturevideo_ogg_theora_keyframe_mininterval.integer, (int) ti.keyframe_frequency) - 1; ti.noise_sensitivity = bound(0, cl_capturevideo_ogg_theora_noise_sensitivity.integer, 6); ti.sharpness = bound(0, cl_capturevideo_ogg_theora_sharpness.integer, 2); ti.keyframe_auto_threshold = bound(0, cl_capturevideo_ogg_theora_keyframe_auto_threshold.integer, 100); ti.keyframe_frequency_force = ti.keyframe_frequency; ti.keyframe_auto_p = (ti.keyframe_frequency != ti.keyframe_mindistance + 1); qtheora_encode_init(&format->ts, &ti); qtheora_info_clear(&ti); if(cl_capturevideo_ogg_theora_vp3compat.integer) { vp3compat = 1; qtheora_control(&format->ts, TH_ENCCTL_SET_VP3_COMPATIBLE, &vp3compat, sizeof(vp3compat)); if(!vp3compat) Con_DPrintf("Warning: theora stream is not fully VP3 compatible\n"); } // vorbis? if(cls.capturevideo.soundrate) { qvorbis_info_init(&format->vi); qvorbis_encode_init_vbr(&format->vi, cls.capturevideo.soundchannels, cls.capturevideo.soundrate, bound(-1, cl_capturevideo_ogg_vorbis_quality.value, 10) * 0.099); qvorbis_comment_init(&vc); qvorbis_analysis_init(&format->vd, &format->vi); qvorbis_block_init(&format->vd, &format->vb); } qtheora_comment_init(&tc); /* create the remaining theora headers */ qtheora_encode_header(&format->ts, &pt); qogg_stream_packetin(&format->to, &pt); if (qogg_stream_pageout (&format->to, &pg) != 1) fprintf (stderr, "Internal Ogg library error.\n"); FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); qtheora_encode_comment(&tc, &pt); qogg_stream_packetin(&format->to, &pt); qtheora_encode_tables(&format->ts, &pt); qogg_stream_packetin (&format->to, &pt); qtheora_comment_clear(&tc); if(cls.capturevideo.soundrate) { qvorbis_analysis_headerout(&format->vd, &vc, &pt, &pt2, &pt3); qogg_stream_packetin(&format->vo, &pt); if (qogg_stream_pageout (&format->vo, &pg) != 1) fprintf (stderr, "Internal Ogg library error.\n"); FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); qogg_stream_packetin(&format->vo, &pt2); qogg_stream_packetin(&format->vo, &pt3); qvorbis_comment_clear(&vc); } for(;;) { int result = qogg_stream_flush (&format->to, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } if(cls.capturevideo.soundrate) for(;;) { int result = qogg_stream_flush (&format->vo, &pg); if (result < 0) fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error if (result <= 0) break; FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len); FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len); } } } darkplaces/cl_screen.h0000664000175000017500000000113513067716216014276 0ustar kalevkalev #ifndef CL_SCREEN_H #define CL_SCREEN_H void SHOWLMP_decodehide(void); void SHOWLMP_decodeshow(void); void SHOWLMP_drawall(void); extern cvar_t vid_conwidth; extern cvar_t vid_conheight; extern cvar_t vid_pixelheight; extern cvar_t scr_screenshot_jpeg; extern cvar_t scr_screenshot_jpeg_quality; extern cvar_t scr_screenshot_png; extern cvar_t scr_screenshot_gammaboost; extern cvar_t scr_screenshot_name; void CL_Screen_NewMap(void); void CL_Screen_Init(void); void CL_Screen_Shutdown(void); void CL_UpdateScreen(void); qboolean R_Stereo_Active(void); qboolean R_Stereo_ColorMasking(void); #endif darkplaces/cl_demo.c0000664000175000017500000003566013067716216013750 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #ifdef CONFIG_VIDEO_CAPTURE extern cvar_t cl_capturevideo; extern cvar_t cl_capturevideo_demo_stop; #endif int old_vsync = 0; static void CL_FinishTimeDemo (void); /* ============================================================================== DEMO CODE When a demo is playing back, all outgoing network messages are skipped, and incoming messages are read from the demo file. Whenever cl.time gets past the last received message, another message is read from the demo file. ============================================================================== */ /* ===================== CL_NextDemo Called to play the next demo in the demo loop ===================== */ void CL_NextDemo (void) { char str[MAX_INPUTLINE]; if (cls.demonum == -1) return; // don't play demos if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS) { cls.demonum = 0; if (!cls.demos[cls.demonum][0]) { Con_Print("No demos listed with startdemos\n"); cls.demonum = -1; return; } } dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]); Cbuf_InsertText (str); cls.demonum++; } /* ============== CL_StopPlayback Called when a demo file runs out, or the user starts a game ============== */ // LordHavoc: now called only by CL_Disconnect void CL_StopPlayback (void) { #ifdef CONFIG_VIDEO_CAPTURE if (cl_capturevideo_demo_stop.integer) Cvar_Set("cl_capturevideo", "0"); #endif if (!cls.demoplayback) return; FS_Close (cls.demofile); cls.demoplayback = false; cls.demofile = NULL; if (cls.timedemo) CL_FinishTimeDemo (); if (!cls.demostarting) // only quit if not starting another demo if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) Host_Quit_f(); } /* ==================== CL_WriteDemoMessage Dumps the current net message, prefixed by the length and view angles #==================== */ void CL_WriteDemoMessage (sizebuf_t *message) { int len; int i; float f; if (cls.demopaused) // LordHavoc: pausedemo return; len = LittleLong (message->cursize); FS_Write (cls.demofile, &len, 4); for (i=0 ; i<3 ; i++) { f = LittleFloat (cl.viewangles[i]); FS_Write (cls.demofile, &f, 4); } FS_Write (cls.demofile, message->data, message->cursize); } /* ==================== CL_CutDemo Dumps the current demo to a buffer, and resets the demo to its starting point. Used to insert csprogs.dat files as a download to the beginning of a demo file. ==================== */ void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize) { *buf = NULL; *filesize = 0; FS_Close(cls.demofile); *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize); // restart the demo recording cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false); if(!cls.demofile) Sys_Error("failed to reopen the demo file"); FS_Printf(cls.demofile, "%i\n", cls.forcetrack); } /* ==================== CL_PasteDemo Adds the cut stuff back to the demo. Also frees the buffer. Used to insert csprogs.dat files as a download to the beginning of a demo file. ==================== */ void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize) { fs_offset_t startoffset = 0; if(!*buf) return; // skip cdtrack while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n') ++startoffset; if(startoffset < *filesize) ++startoffset; FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset); Mem_Free(*buf); *buf = NULL; *filesize = 0; } /* ==================== CL_ReadDemoMessage Handles playback of demos ==================== */ void CL_ReadDemoMessage(void) { int i; float f; if (!cls.demoplayback) return; // LordHavoc: pausedemo if (cls.demopaused) return; for (;;) { // decide if it is time to grab the next message // always grab until fully connected if (cls.signon == SIGNONS) { if (cls.timedemo) { cls.td_frames++; cls.td_onesecondframes++; // if this is the first official frame we can now grab the real // td_starttime so the bogus time on the first frame doesn't // count against the final report if (cls.td_frames == 0) { cls.td_starttime = realtime; cls.td_onesecondnexttime = cl.time + 1; cls.td_onesecondrealtime = realtime; cls.td_onesecondframes = 0; cls.td_onesecondminfps = 0; cls.td_onesecondmaxfps = 0; cls.td_onesecondavgfps = 0; cls.td_onesecondavgcount = 0; } if (cl.time >= cls.td_onesecondnexttime) { double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime); if (cls.td_onesecondavgcount == 0) { cls.td_onesecondminfps = fps; cls.td_onesecondmaxfps = fps; } cls.td_onesecondrealtime = realtime; cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps); cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps); cls.td_onesecondavgfps += fps; cls.td_onesecondavgcount++; cls.td_onesecondframes = 0; cls.td_onesecondnexttime++; } } else if (cl.time <= cl.mtime[0]) { // don't need another message yet return; } } // get the next message FS_Read(cls.demofile, &cl_message.cursize, 4); cl_message.cursize = LittleLong(cl_message.cursize); if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now! { // skip over demo packet FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR); continue; } if (cl_message.cursize > cl_message.maxsize) { Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize); cl_message.cursize = 0; CL_Disconnect(); return; } VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); for (i = 0;i < 3;i++) { FS_Read(cls.demofile, &f, 4); cl.mviewangles[0][i] = LittleFloat(f); } if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize) { MSG_BeginReading(&cl_message); CL_ParseServerMessage(); if (cls.signon != SIGNONS) Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect! // In case the demo contains a "svc_disconnect" message if (!cls.demoplayback) return; if (cls.timedemo) return; } else { CL_Disconnect(); return; } } } /* ==================== CL_Stop_f stop recording a demo ==================== */ void CL_Stop_f (void) { sizebuf_t buf; unsigned char bufdata[64]; if (!cls.demorecording) { Con_Print("Not recording a demo.\n"); return; } // write a disconnect message to the demo file // LordHavoc: don't replace the cl_message when doing this buf.data = bufdata; buf.maxsize = sizeof(bufdata); SZ_Clear(&buf); MSG_WriteByte(&buf, svc_disconnect); CL_WriteDemoMessage(&buf); // finish up if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1)) { FS_RemoveOnClose(cls.demofile); Con_Print("Completed and deleted demo\n"); } else Con_Print("Completed demo\n"); FS_Close (cls.demofile); cls.demofile = NULL; cls.demorecording = false; } /* ==================== CL_Record_f record [cd track] ==================== */ void CL_Record_f (void) { int c, track; char name[MAX_OSPATH]; char vabuf[1024]; c = Cmd_Argc(); if (c != 2 && c != 3 && c != 4) { Con_Print("record [ [cd track]]\n"); return; } if (strstr(Cmd_Argv(1), "..")) { Con_Print("Relative pathnames are not allowed.\n"); return; } if (c == 2 && cls.state == ca_connected) { Con_Print("Can not record - already connected to server\nClient demo recording must be started before connecting\n"); return; } if (cls.state == ca_connected) CL_Disconnect(); // write the forced cd track number, or -1 if (c == 4) { track = atoi(Cmd_Argv(3)); Con_Printf("Forcing CD track to %i\n", cls.forcetrack); } else track = -1; // get the demo name strlcpy (name, Cmd_Argv(1), sizeof (name)); FS_DefaultExtension (name, ".dem", sizeof (name)); // start the map up if (c > 2) Cmd_ExecuteString ( va(vabuf, sizeof(vabuf), "map %s", Cmd_Argv(2)), src_command, false); // open the demo file Con_Printf("recording to %s.\n", name); cls.demofile = FS_OpenRealFile(name, "wb", false); if (!cls.demofile) { Con_Print("ERROR: couldn't open.\n"); return; } strlcpy(cls.demoname, name, sizeof(cls.demoname)); cls.forcetrack = track; FS_Printf(cls.demofile, "%i\n", cls.forcetrack); cls.demorecording = true; cls.demo_lastcsprogssize = -1; cls.demo_lastcsprogscrc = -1; } /* ==================== CL_PlayDemo_f play [demoname] ==================== */ void CL_PlayDemo_f (void) { char name[MAX_QPATH]; int c; qboolean neg = false; qfile_t *f; if (Cmd_Argc() != 2) { Con_Print("play : plays a demo\n"); return; } // open the demo file strlcpy (name, Cmd_Argv(1), sizeof (name)); FS_DefaultExtension (name, ".dem", sizeof (name)); f = FS_OpenVirtualFile(name, false); if (!f) { Con_Printf("ERROR: couldn't open %s.\n", name); cls.demonum = -1; // stop demo loop return; } cls.demostarting = true; // disconnect from server CL_Disconnect (); Host_ShutdownServer (); // update networking ports (this is mainly just needed at startup) NetConn_UpdateSockets(); cls.protocol = PROTOCOL_QUAKE; Con_Printf("Playing demo %s.\n", name); cls.demofile = f; strlcpy(cls.demoname, name, sizeof(cls.demoname)); cls.demoplayback = true; cls.state = ca_connected; cls.forcetrack = 0; while ((c = FS_Getc (cls.demofile)) != '\n') if (c == '-') neg = true; else cls.forcetrack = cls.forcetrack * 10 + (c - '0'); if (neg) cls.forcetrack = -cls.forcetrack; cls.demostarting = false; } typedef struct { int frames; double time, totalfpsavg; double fpsmin, fpsavg, fpsmax; } benchmarkhistory_t; static size_t doublecmp_offset; static int doublecmp_withoffset(const void *a_, const void *b_) { const double *a = (const double *) ((const char *) a_ + doublecmp_offset); const double *b = (const double *) ((const char *) b_ + doublecmp_offset); if(*a > *b) return +1; if(*a < *b) return -1; return 0; } /* ==================== CL_FinishTimeDemo ==================== */ static void CL_FinishTimeDemo (void) { int frames; int i; double time, totalfpsavg; double fpsmin, fpsavg, fpsmax; // report min/avg/max fps static int benchmark_runs = 0; char vabuf[1024]; cls.timedemo = false; frames = cls.td_frames; time = realtime - cls.td_starttime; totalfpsavg = time > 0 ? frames / time : 0; fpsmin = cls.td_onesecondminfps; fpsavg = cls.td_onesecondavgcount ? cls.td_onesecondavgfps / cls.td_onesecondavgcount : 0; fpsmax = cls.td_onesecondmaxfps; // LordHavoc: timedemo now prints out 7 digits of fraction, and min/avg/max Con_Printf("%i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); Log_Printf("benchmark.log", "date %s | enginedate %s | demo %s | commandline %s | run %d | result %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", Sys_TimeString("%Y-%m-%d %H:%M:%S"), buildstring, cls.demoname, cmdline.string, benchmark_runs + 1, frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); if (COM_CheckParm("-benchmark")) { ++benchmark_runs; i = COM_CheckParm("-benchmarkruns"); if(i && i + 1 < com_argc) { static benchmarkhistory_t *history = NULL; if(!history) history = (benchmarkhistory_t *)Z_Malloc(sizeof(*history) * atoi(com_argv[i + 1])); history[benchmark_runs - 1].frames = frames; history[benchmark_runs - 1].time = time; history[benchmark_runs - 1].totalfpsavg = totalfpsavg; history[benchmark_runs - 1].fpsmin = fpsmin; history[benchmark_runs - 1].fpsavg = fpsavg; history[benchmark_runs - 1].fpsmax = fpsmax; if(atoi(com_argv[i + 1]) > benchmark_runs) { // restart the benchmark Cbuf_AddText(va(vabuf, sizeof(vabuf), "timedemo %s\n", cls.demoname)); // cannot execute here } else { // print statistics int first = COM_CheckParm("-benchmarkruns_skipfirst") ? 1 : 0; if(benchmark_runs > first) { #define DO_MIN(f) \ for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f < f)) f = history[i].f #define DO_MAX(f) \ for(i = first; i < benchmark_runs; ++i) if((i == first) || (history[i].f > f)) f = history[i].f #define DO_MED(f) \ doublecmp_offset = (char *)&history->f - (char *)history; \ qsort(history + first, benchmark_runs - first, sizeof(*history), doublecmp_withoffset); \ if((first + benchmark_runs) & 1) \ f = history[(first + benchmark_runs - 1) / 2].f; \ else \ f = (history[(first + benchmark_runs - 2) / 2].f + history[(first + benchmark_runs) / 2].f) / 2 DO_MIN(frames); DO_MAX(time); DO_MIN(totalfpsavg); DO_MIN(fpsmin); DO_MIN(fpsavg); DO_MIN(fpsmax); Con_Printf("MIN: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); DO_MED(frames); DO_MED(time); DO_MED(totalfpsavg); DO_MED(fpsmin); DO_MED(fpsavg); DO_MED(fpsmax); Con_Printf("MED: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); DO_MAX(frames); DO_MIN(time); DO_MAX(totalfpsavg); DO_MAX(fpsmin); DO_MAX(fpsavg); DO_MAX(fpsmax); Con_Printf("MAX: %i frames %5.7f seconds %5.7f fps, one-second fps min/avg/max: %.0f %.0f %.0f (%i seconds)\n", frames, time, totalfpsavg, fpsmin, fpsavg, fpsmax, cls.td_onesecondavgcount); } Z_Free(history); history = NULL; Host_Quit_f(); } } else Host_Quit_f(); } } /* ==================== CL_TimeDemo_f timedemo [demoname] ==================== */ void CL_TimeDemo_f (void) { if (Cmd_Argc() != 2) { Con_Print("timedemo : gets demo speeds\n"); return; } srand(0); // predictable random sequence for benchmarking CL_PlayDemo_f (); // cls.td_starttime will be grabbed at the second frame of the demo, so // all the loading time doesn't get counted // instantly hide console and deactivate it key_dest = key_game; key_consoleactive = 0; scr_con_current = 0; cls.timedemo = true; cls.td_frames = -2; // skip the first frame cls.demonum = -1; // stop demo loop } darkplaces/vid_agl_mackeys.h0000664000175000017500000000722013067716222015460 0ustar kalevkalev/* SDL - Simple DirectMedia Layer Copyright (C) 1997, 1998, 1999, 2000, 2001 Sam Lantinga This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Sam Lantinga slouken@devolution.com */ #ifdef SAVE_RCSID static char rcsid = "@(#) $Id$"; #endif /* These are the Macintosh key scancode constants -- from Inside Macintosh */ #define MK_ESCAPE 0x35 #define MK_F1 0x7A #define MK_F2 0x78 #define MK_F3 0x63 #define MK_F4 0x76 #define MK_F5 0x60 #define MK_F6 0x61 #define MK_F7 0x62 #define MK_F8 0x64 #define MK_F9 0x65 #define MK_F10 0x6D #define MK_F11 0x67 #define MK_F12 0x6F #define MK_PRINT 0x69 #define MK_SCROLLOCK 0x6B #define MK_PAUSE 0x71 #define MK_POWER 0x7F #define MK_BACKQUOTE 0x32 #define MK_1 0x12 #define MK_2 0x13 #define MK_3 0x14 #define MK_4 0x15 #define MK_5 0x17 #define MK_6 0x16 #define MK_7 0x1A #define MK_8 0x1C #define MK_9 0x19 #define MK_0 0x1D #define MK_MINUS 0x1B #define MK_EQUALS 0x18 #define MK_BACKSPACE 0x33 #define MK_INSERT 0x72 #define MK_HOME 0x73 #define MK_PAGEUP 0x74 #define MK_NUMLOCK 0x47 #define MK_KP_EQUALS 0x51 #define MK_KP_DIVIDE 0x4B #define MK_KP_MULTIPLY 0x43 #define MK_TAB 0x30 #define MK_q 0x0C #define MK_w 0x0D #define MK_e 0x0E #define MK_r 0x0F #define MK_t 0x11 #define MK_y 0x10 #define MK_u 0x20 #define MK_i 0x22 #define MK_o 0x1F #define MK_p 0x23 #define MK_LEFTBRACKET 0x21 #define MK_RIGHTBRACKET 0x1E #define MK_BACKSLASH 0x2A #define MK_DELETE 0x75 #define MK_END 0x77 #define MK_PAGEDOWN 0x79 #define MK_KP7 0x59 #define MK_KP8 0x5B #define MK_KP9 0x5C #define MK_KP_MINUS 0x4E #define MK_CAPSLOCK 0x39 #define MK_a 0x00 #define MK_s 0x01 #define MK_d 0x02 #define MK_f 0x03 #define MK_g 0x05 #define MK_h 0x04 #define MK_j 0x26 #define MK_k 0x28 #define MK_l 0x25 #define MK_SEMICOLON 0x29 #define MK_QUOTE 0x27 #define MK_RETURN 0x24 #define MK_KP4 0x56 #define MK_KP5 0x57 #define MK_KP6 0x58 #define MK_KP_PLUS 0x45 #define MK_LSHIFT 0x38 #define MK_z 0x06 #define MK_x 0x07 #define MK_c 0x08 #define MK_v 0x09 #define MK_b 0x0B #define MK_n 0x2D #define MK_m 0x2E #define MK_COMMA 0x2B #define MK_PERIOD 0x2F #define MK_SLASH 0x2C #if 0 /* These are the same as the left versions - use left by default */ #define MK_RSHIFT 0x38 #endif #define MK_UP 0x7E #define MK_KP1 0x53 #define MK_KP2 0x54 #define MK_KP3 0x55 #define MK_KP_ENTER 0x4C #define MK_LCTRL 0x3B #define MK_LALT 0x3A #define MK_LMETA 0x37 #define MK_SPACE 0x31 #if 0 /* These are the same as the left versions - use left by default */ #define MK_RMETA 0x37 #define MK_RALT 0x3A #define MK_RCTRL 0x3B #endif #define MK_LEFT 0x7B #define MK_DOWN 0x7D #define MK_RIGHT 0x7C #define MK_KP0 0x52 #define MK_KP_PERIOD 0x41 /* Wierd, these keys are on my iBook under MacOS X */ #define MK_IBOOK_ENTER 0x34 #define MK_IBOOK_LEFT 0x3B #define MK_IBOOK_RIGHT 0x3C #define MK_IBOOK_DOWN 0x3D #define MK_IBOOK_UP 0x3E darkplaces/cl_dyntexture.h0000664000175000017500000000132013067716216015226 0ustar kalevkalev// Andreas 'Black' Kirsch 07 #ifndef CL_DYNTEXTURE_H #define CL_DYNTEXTURE_H #define CLDYNTEXTUREPREFIX "_dynamic/" // always path fully specified names to the dynamic texture functions! (ie. with the _dynamic/ prefix, etc.!) // return a valid texture handle for a dynamic texture (might be filler texture if it hasnt been initialized yet) // or NULL if its not a valid dynamic texture name rtexture_t * CL_GetDynTexture( const char *name ); // link a texture handle as dynamic texture and update texture handles in the renderer and draw_* accordingly void CL_LinkDynTexture( const char *name, rtexture_t *texture ); // unlink a texture handle from its name void CL_UnlinkDynTexture( const char *name ); #endif darkplaces/fs.c0000664000175000017500000031772013067716220012751 0ustar kalevkalev/* DarkPlaces file system Copyright (C) 2003-2006 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include #include #ifdef WIN32 # include # include # include # include # include #else # include # include # include #endif #include "quakedef.h" #if TARGET_OS_IPHONE // include SDL for IPHONEOS code # include #endif #include "thread.h" #include "fs.h" #include "wad.h" // Win32 requires us to add O_BINARY, but the other OSes don't have it #ifndef O_BINARY # define O_BINARY 0 #endif // In case the system doesn't support the O_NONBLOCK flag #ifndef O_NONBLOCK # define O_NONBLOCK 0 #endif // largefile support for Win32 #ifdef WIN32 #undef lseek # define lseek _lseeki64 #endif // suppress deprecated warnings #if _MSC_VER >= 1400 # define read _read # define write _write # define close _close # define unlink _unlink # define dup _dup #endif #if USE_RWOPS # include typedef SDL_RWops *filedesc_t; # define FILEDESC_INVALID NULL # define FILEDESC_ISVALID(fd) ((fd) != NULL) # define FILEDESC_READ(fd,buf,count) ((fs_offset_t)SDL_RWread(fd, buf, 1, count)) # define FILEDESC_WRITE(fd,buf,count) ((fs_offset_t)SDL_RWwrite(fd, buf, 1, count)) # define FILEDESC_CLOSE SDL_RWclose # define FILEDESC_SEEK SDL_RWseek static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) { filedesc_t new_fd = SDL_RWFromFile(filename, "rb"); if (SDL_RWseek(new_fd, SDL_RWseek(fd, 0, RW_SEEK_CUR), RW_SEEK_SET) < 0) { SDL_RWclose(new_fd); return NULL; } return new_fd; } # define unlink(name) Con_DPrintf("Sorry, no unlink support when trying to unlink %s.\n", (name)) #else typedef int filedesc_t; # define FILEDESC_INVALID -1 # define FILEDESC_ISVALID(fd) ((fd) != -1) # define FILEDESC_READ read # define FILEDESC_WRITE write # define FILEDESC_CLOSE close # define FILEDESC_SEEK lseek static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) { return dup(fd); } #endif /** \page fs File System All of Quake's data access is through a hierchal file system, but the contents of the file system can be transparently merged from several sources. The "base directory" is the path to the directory holding the quake.exe and all game directories. The sys_* files pass this to host_init in quakeparms_t->basedir. This can be overridden with the "-basedir" command line parm to allow code debugging in a different directory. The base directory is only used during filesystem initialization. The "game directory" is the first tree on the search path and directory that all generated files (savegames, screenshots, demos, config files) will be saved to. This can be overridden with the "-game" command line parameter. The game directory can never be changed while quake is executing. This is a precaution against having a malicious server instruct clients to write files over areas they shouldn't. */ /* ============================================================================= CONSTANTS ============================================================================= */ // Magic numbers of a ZIP file (big-endian format) #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4" #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2" #define ZIP_END_HEADER 0x504B0506 // "PK\5\6" // Other constants for ZIP files #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF) #define ZIP_END_CDIR_SIZE 22 #define ZIP_CDIR_CHUNK_BASE_SIZE 46 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30 #ifdef LINK_TO_ZLIB #include #define qz_inflate inflate #define qz_inflateEnd inflateEnd #define qz_inflateInit2_ inflateInit2_ #define qz_inflateReset inflateReset #define qz_deflateInit2_ deflateInit2_ #define qz_deflateEnd deflateEnd #define qz_deflate deflate #define Z_MEMLEVEL_DEFAULT 8 #else // Zlib constants (from zlib.h) #define Z_SYNC_FLUSH 2 #define MAX_WBITS 15 #define Z_OK 0 #define Z_STREAM_END 1 #define Z_STREAM_ERROR (-2) #define Z_DATA_ERROR (-3) #define Z_MEM_ERROR (-4) #define Z_BUF_ERROR (-5) #define ZLIB_VERSION "1.2.3" #define Z_BINARY 0 #define Z_DEFLATED 8 #define Z_MEMLEVEL_DEFAULT 8 #define Z_NULL 0 #define Z_DEFAULT_COMPRESSION (-1) #define Z_NO_FLUSH 0 #define Z_SYNC_FLUSH 2 #define Z_FULL_FLUSH 3 #define Z_FINISH 4 // Uncomment the following line if the zlib DLL you have still uses // the 1.1.x series calling convention on Win32 (WINAPI) //#define ZLIB_USES_WINAPI /* ============================================================================= TYPES ============================================================================= */ /*! Zlib stream (from zlib.h) * \warning: some pointers we don't use directly have * been cast to "void*" for a matter of simplicity */ typedef struct { unsigned char *next_in; ///< next input byte unsigned int avail_in; ///< number of bytes available at next_in unsigned long total_in; ///< total nb of input bytes read so far unsigned char *next_out; ///< next output byte should be put there unsigned int avail_out; ///< remaining free space at next_out unsigned long total_out; ///< total nb of bytes output so far char *msg; ///< last error message, NULL if no error void *state; ///< not visible by applications void *zalloc; ///< used to allocate the internal state void *zfree; ///< used to free the internal state void *opaque; ///< private data object passed to zalloc and zfree int data_type; ///< best guess about the data type: ascii or binary unsigned long adler; ///< adler32 value of the uncompressed data unsigned long reserved; ///< reserved for future use } z_stream; #endif /// inside a package (PAK or PK3) #define QFILE_FLAG_PACKED (1 << 0) /// file is compressed using the deflate algorithm (PK3 only) #define QFILE_FLAG_DEFLATED (1 << 1) /// file is actually already loaded data #define QFILE_FLAG_DATA (1 << 2) /// real file will be removed on close #define QFILE_FLAG_REMOVE (1 << 3) #define FILE_BUFF_SIZE 2048 typedef struct { z_stream zstream; size_t comp_length; ///< length of the compressed file size_t in_ind, in_len; ///< input buffer current index and length size_t in_position; ///< position in the compressed file unsigned char input [FILE_BUFF_SIZE]; } ztoolkit_t; struct qfile_s { int flags; filedesc_t handle; ///< file descriptor fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode) fs_offset_t position; ///< current position in the file fs_offset_t offset; ///< offset into the package (0 if external file) int ungetc; ///< single stored character from ungetc, cleared to EOF when read // Contents buffer fs_offset_t buff_ind, buff_len; ///< buffer current index and length unsigned char buff [FILE_BUFF_SIZE]; ztoolkit_t* ztk; ///< For zipped files. const unsigned char *data; ///< For data files. const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise }; // ------ PK3 files on disk ------ // // You can get the complete ZIP format description from PKWARE website typedef struct pk3_endOfCentralDir_s { unsigned int signature; unsigned short disknum; unsigned short cdir_disknum; ///< number of the disk with the start of the central directory unsigned short localentries; ///< number of entries in the central directory on this disk unsigned short nbentries; ///< total number of entries in the central directory on this disk unsigned int cdir_size; ///< size of the central directory unsigned int cdir_offset; ///< with respect to the starting disk number unsigned short comment_size; fs_offset_t prepended_garbage; } pk3_endOfCentralDir_t; // ------ PAK files on disk ------ // typedef struct dpackfile_s { char name[56]; int filepos, filelen; } dpackfile_t; typedef struct dpackheader_s { char id[4]; int dirofs; int dirlen; } dpackheader_t; /*! \name Packages in memory * @{ */ /// the offset in packfile_t is the true contents offset #define PACKFILE_FLAG_TRUEOFFS (1 << 0) /// file compressed using the deflate algorithm #define PACKFILE_FLAG_DEFLATED (1 << 1) /// file is a symbolic link #define PACKFILE_FLAG_SYMLINK (1 << 2) typedef struct packfile_s { char name [MAX_QPATH]; int flags; fs_offset_t offset; fs_offset_t packsize; ///< size in the package fs_offset_t realsize; ///< real file size (uncompressed) } packfile_t; typedef struct pack_s { char filename [MAX_OSPATH]; char shortname [MAX_QPATH]; filedesc_t handle; int ignorecase; ///< PK3 ignores case int numfiles; qboolean vpack; packfile_t *files; } pack_t; //@} /// Search paths for files (including packages) typedef struct searchpath_s { // only one of filename / pack will be used char filename[MAX_OSPATH]; pack_t *pack; struct searchpath_s *next; } searchpath_t; /* ============================================================================= FUNCTION PROTOTYPES ============================================================================= */ void FS_Dir_f(void); void FS_Ls_f(void); void FS_Which_f(void); static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet); static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, fs_offset_t offset, fs_offset_t packsize, fs_offset_t realsize, int flags); /* ============================================================================= VARIABLES ============================================================================= */ mempool_t *fs_mempool; void *fs_mutex = NULL; searchpath_t *fs_searchpaths = NULL; const char *const fs_checkgamedir_missing = "missing"; #define MAX_FILES_IN_PACK 65536 char fs_userdir[MAX_OSPATH]; char fs_gamedir[MAX_OSPATH]; char fs_basedir[MAX_OSPATH]; static pack_t *fs_selfpack = NULL; // list of active game directories (empty if not running a mod) int fs_numgamedirs = 0; char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; // list of all gamedirs with modinfo.txt gamedir_t *fs_all_gamedirs = NULL; int fs_all_gamedirs_count = 0; cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"}; cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"}; cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"}; /* ============================================================================= PRIVATE FUNCTIONS - PK3 HANDLING ============================================================================= */ #ifndef LINK_TO_ZLIB // Functions exported from zlib #if defined(WIN32) && defined(ZLIB_USES_WINAPI) # define ZEXPORT WINAPI #else # define ZEXPORT #endif static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush); static int (ZEXPORT *qz_inflateEnd) (z_stream* strm); static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size); static int (ZEXPORT *qz_inflateReset) (z_stream* strm); static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size); static int (ZEXPORT *qz_deflateEnd) (z_stream* strm); static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush); #endif #define qz_inflateInit2(strm, windowBits) \ qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream)) #ifndef LINK_TO_ZLIB // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) static dllfunction_t zlibfuncs[] = { {"inflate", (void **) &qz_inflate}, {"inflateEnd", (void **) &qz_inflateEnd}, {"inflateInit2_", (void **) &qz_inflateInit2_}, {"inflateReset", (void **) &qz_inflateReset}, {"deflateInit2_", (void **) &qz_deflateInit2_}, {"deflateEnd", (void **) &qz_deflateEnd}, {"deflate", (void **) &qz_deflate}, {NULL, NULL} }; /// Handle for Zlib DLL static dllhandle_t zlib_dll = NULL; #endif #ifdef WIN32 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath); static dllfunction_t shfolderfuncs[] = { {"SHGetFolderPathA", (void **) &qSHGetFolderPath}, {NULL, NULL} }; static const char* shfolderdllnames [] = { "shfolder.dll", // IE 4, or Win NT and higher NULL }; static dllhandle_t shfolder_dll = NULL; const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; #define qREFKNOWNFOLDERID const GUID * #define qKF_FLAG_CREATE 0x8000 #define qKF_FLAG_NO_ALIAS 0x1000 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); static dllfunction_t shell32funcs[] = { {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath}, {NULL, NULL} }; static const char* shell32dllnames [] = { "shell32.dll", // Vista and higher NULL }; static dllhandle_t shell32_dll = NULL; static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit); static void (WINAPI *qCoUninitialize)(void); static void (WINAPI *qCoTaskMemFree)(LPVOID pv); static dllfunction_t ole32funcs[] = { {"CoInitializeEx", (void **) &qCoInitializeEx}, {"CoUninitialize", (void **) &qCoUninitialize}, {"CoTaskMemFree", (void **) &qCoTaskMemFree}, {NULL, NULL} }; static const char* ole32dllnames [] = { "ole32.dll", // 2000 and higher NULL }; static dllhandle_t ole32_dll = NULL; #endif /* ==================== PK3_CloseLibrary Unload the Zlib DLL ==================== */ static void PK3_CloseLibrary (void) { #ifndef LINK_TO_ZLIB Sys_UnloadLibrary (&zlib_dll); #endif } /* ==================== PK3_OpenLibrary Try to load the Zlib DLL ==================== */ static qboolean PK3_OpenLibrary (void) { #ifdef LINK_TO_ZLIB return true; #else const char* dllnames [] = { #if defined(WIN32) # ifdef ZLIB_USES_WINAPI "zlibwapi.dll", "zlib.dll", # else "zlib1.dll", # endif #elif defined(MACOSX) "libz.dylib", #else "libz.so.1", "libz.so", #endif NULL }; // Already loaded? if (zlib_dll) return true; // Load the DLL return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs); #endif } /* ==================== FS_HasZlib See if zlib is available ==================== */ qboolean FS_HasZlib(void) { #ifdef LINK_TO_ZLIB return true; #else PK3_OpenLibrary(); // to be safe return (zlib_dll != 0); #endif } /* ==================== PK3_GetEndOfCentralDir Extract the end of the central directory from a PK3 package ==================== */ static qboolean PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd) { fs_offset_t filesize, maxsize; unsigned char *buffer, *ptr; int ind; // Get the package size filesize = FILEDESC_SEEK (packhandle, 0, SEEK_END); if (filesize < ZIP_END_CDIR_SIZE) return false; // Load the end of the file in memory if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE) maxsize = filesize; else maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE; buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize); FILEDESC_SEEK (packhandle, filesize - maxsize, SEEK_SET); if (FILEDESC_READ (packhandle, buffer, maxsize) != (fs_offset_t) maxsize) { Mem_Free (buffer); return false; } // Look for the end of central dir signature around the end of the file maxsize -= ZIP_END_CDIR_SIZE; ptr = &buffer[maxsize]; ind = 0; while (BuffBigLong (ptr) != ZIP_END_HEADER) { if (ind == maxsize) { Mem_Free (buffer); return false; } ind++; ptr--; } memcpy (eocd, ptr, ZIP_END_CDIR_SIZE); eocd->signature = LittleLong (eocd->signature); eocd->disknum = LittleShort (eocd->disknum); eocd->cdir_disknum = LittleShort (eocd->cdir_disknum); eocd->localentries = LittleShort (eocd->localentries); eocd->nbentries = LittleShort (eocd->nbentries); eocd->cdir_size = LittleLong (eocd->cdir_size); eocd->cdir_offset = LittleLong (eocd->cdir_offset); eocd->comment_size = LittleShort (eocd->comment_size); eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files eocd->cdir_offset += eocd->prepended_garbage; Mem_Free (buffer); if ( eocd->cdir_size > filesize || eocd->cdir_offset >= filesize || eocd->cdir_offset + eocd->cdir_size > filesize ) { // Obviously invalid central directory. return false; } return true; } /* ==================== PK3_BuildFileList Extract the file list from a PK3 file ==================== */ static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd) { unsigned char *central_dir, *ptr; unsigned int ind; fs_offset_t remaining; // Load the central directory in memory central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size); if (FILEDESC_SEEK (pack->handle, eocd->cdir_offset, SEEK_SET) == -1) { Mem_Free (central_dir); return -1; } if(FILEDESC_READ (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size) { Mem_Free (central_dir); return -1; } // Extract the files properties // The parsing is done "by hand" because some fields have variable sizes and // the constant part isn't 4-bytes aligned, which makes the use of structs difficult remaining = eocd->cdir_size; pack->numfiles = 0; ptr = central_dir; for (ind = 0; ind < eocd->nbentries; ind++) { fs_offset_t namesize, count; // Checking the remaining size if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE) { Mem_Free (central_dir); return -1; } remaining -= ZIP_CDIR_CHUNK_BASE_SIZE; // Check header if (BuffBigLong (ptr) != ZIP_CDIR_HEADER) { Mem_Free (central_dir); return -1; } namesize = BuffLittleShort (&ptr[28]); // filename length // Check encryption, compression, and attributes // 1st uint8 : general purpose bit flag // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?)) // // LordHavoc: bit 3 would be a problem if we were scanning the archive // but is not a problem in the central directory where the values are // always real. // // bit 3 seems to always be set by the standard Mac OSX zip maker // // 2nd uint8 : external file attributes // Check bits 3 (file is a directory) and 5 (file is a volume (?)) if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0) { // Still enough bytes for the name? if (namesize < 0 || remaining < namesize || namesize >= (int)sizeof (*pack->files)) { Mem_Free (central_dir); return -1; } // WinZip doesn't use the "directory" attribute, so we need to check the name directly if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/') { char filename [sizeof (pack->files[0].name)]; fs_offset_t offset, packsize, realsize; int flags; // Extract the name (strip it if necessary) namesize = min(namesize, (int)sizeof (filename) - 1); memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize); filename[namesize] = '\0'; if (BuffLittleShort (&ptr[10])) flags = PACKFILE_FLAG_DEFLATED; else flags = 0; offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage); packsize = (unsigned int)BuffLittleLong (&ptr[20]); realsize = (unsigned int)BuffLittleLong (&ptr[24]); switch(ptr[5]) // C_VERSION_MADE_BY_1 { case 3: // UNIX_ case 2: // VMS_ case 16: // BEOS_ if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000) // can't use S_ISLNK here, as this has to compile on non-UNIX too flags |= PACKFILE_FLAG_SYMLINK; break; } FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags); } } // Skip the name, additionnal field, and comment // 1er uint16 : extra field length // 2eme uint16 : file comment length count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]); ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count; remaining -= count; } // If the package is empty, central_dir is NULL here if (central_dir != NULL) Mem_Free (central_dir); return pack->numfiles; } /* ==================== FS_LoadPackPK3 Create a package entry associated with a PK3 file ==================== */ static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qboolean silent) { pk3_endOfCentralDir_t eocd; pack_t *pack; int real_nb_files; if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd)) { if(!silent) Con_Printf ("%s is not a PK3 file\n", packfile); FILEDESC_CLOSE(packhandle); return NULL; } // Multi-volume ZIP archives are NOT allowed if (eocd.disknum != 0 || eocd.cdir_disknum != 0) { Con_Printf ("%s is a multi-volume ZIP archive\n", packfile); FILEDESC_CLOSE(packhandle); return NULL; } // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535 // since eocd.nbentries is an unsigned 16 bits integer #if MAX_FILES_IN_PACK < 65535 if (eocd.nbentries > MAX_FILES_IN_PACK) { Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries); FILEDESC_CLOSE(packhandle); return NULL; } #endif // Create a package structure in memory pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); pack->ignorecase = true; // PK3 ignores case strlcpy (pack->filename, packfile, sizeof (pack->filename)); pack->handle = packhandle; pack->numfiles = eocd.nbentries; pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t)); real_nb_files = PK3_BuildFileList (pack, &eocd); if (real_nb_files < 0) { Con_Printf ("%s is not a valid PK3 file\n", packfile); FILEDESC_CLOSE(pack->handle); Mem_Free(pack); return NULL; } Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files); return pack; } static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking); static pack_t *FS_LoadPackPK3 (const char *packfile) { filedesc_t packhandle; packhandle = FS_SysOpenFiledesc (packfile, "rb", false); if (!FILEDESC_ISVALID(packhandle)) return NULL; return FS_LoadPackPK3FromFD(packfile, packhandle, false); } /* ==================== PK3_GetTrueFileOffset Find where the true file data offset is ==================== */ static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack) { unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE]; fs_offset_t count; // Already found? if (pfile->flags & PACKFILE_FLAG_TRUEOFFS) return true; // Load the local file description if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1) { Con_Printf ("Can't seek in package %s\n", pack->filename); return false; } count = FILEDESC_READ (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE); if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER) { Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename); return false; } // Skip name and extra field pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE; pfile->flags |= PACKFILE_FLAG_TRUEOFFS; return true; } /* ============================================================================= OTHER PRIVATE FUNCTIONS ============================================================================= */ /* ==================== FS_AddFileToPack Add a file to the list of files contained into a package ==================== */ static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack, fs_offset_t offset, fs_offset_t packsize, fs_offset_t realsize, int flags) { int (*strcmp_funct) (const char* str1, const char* str2); int left, right, middle; packfile_t *pfile; strcmp_funct = pack->ignorecase ? strcasecmp : strcmp; // Look for the slot we should put that file into (binary search) left = 0; right = pack->numfiles - 1; while (left <= right) { int diff; middle = (left + right) / 2; diff = strcmp_funct (pack->files[middle].name, name); // If we found the file, there's a problem if (!diff) Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name); // If we're too far in the list if (diff > 0) right = middle - 1; else left = middle + 1; } // We have to move the right of the list by one slot to free the one we need pfile = &pack->files[left]; memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile)); pack->numfiles++; strlcpy (pfile->name, name, sizeof (pfile->name)); pfile->offset = offset; pfile->packsize = packsize; pfile->realsize = realsize; pfile->flags = flags; return pfile; } static void FS_mkdir (const char *path) { if(COM_CheckParm("-readonly")) return; #if WIN32 if (_mkdir (path) == -1) #else if (mkdir (path, 0777) == -1) #endif { // No logging for this. The only caller is FS_CreatePath (which // calls it in ways that will intentionally produce EEXIST), // and its own callers always use the directory afterwards and // thus will detect failure that way. } } /* ============ FS_CreatePath Only used for FS_OpenRealFile. ============ */ void FS_CreatePath (char *path) { char *ofs, save; for (ofs = path+1 ; *ofs ; ofs++) { if (*ofs == '/' || *ofs == '\\') { // create the directory save = *ofs; *ofs = 0; FS_mkdir (path); *ofs = save; } } } /* ============ FS_Path_f ============ */ static void FS_Path_f (void) { searchpath_t *s; Con_Print("Current search path:\n"); for (s=fs_searchpaths ; s ; s=s->next) { if (s->pack) { if(s->pack->vpack) Con_Printf("%sdir (virtual pack)\n", s->pack->filename); else Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles); } else Con_Printf("%s\n", s->filename); } } /* ================= FS_LoadPackPAK ================= */ /*! Takes an explicit (not game tree related) path to a pak file. *Loads the header and directory, adding the files at the beginning *of the list so they override previous pack files. */ static pack_t *FS_LoadPackPAK (const char *packfile) { dpackheader_t header; int i, numpackfiles; filedesc_t packhandle; pack_t *pack; dpackfile_t *info; packhandle = FS_SysOpenFiledesc(packfile, "rb", false); if (!FILEDESC_ISVALID(packhandle)) return NULL; if(FILEDESC_READ (packhandle, (void *)&header, sizeof(header)) != sizeof(header)) { Con_Printf ("%s is not a packfile\n", packfile); FILEDESC_CLOSE(packhandle); return NULL; } if (memcmp(header.id, "PACK", 4)) { Con_Printf ("%s is not a packfile\n", packfile); FILEDESC_CLOSE(packhandle); return NULL; } header.dirofs = LittleLong (header.dirofs); header.dirlen = LittleLong (header.dirlen); if (header.dirlen % sizeof(dpackfile_t)) { Con_Printf ("%s has an invalid directory size\n", packfile); FILEDESC_CLOSE(packhandle); return NULL; } numpackfiles = header.dirlen / sizeof(dpackfile_t); if (numpackfiles < 0 || numpackfiles > MAX_FILES_IN_PACK) { Con_Printf ("%s has %i files\n", packfile, numpackfiles); FILEDESC_CLOSE(packhandle); return NULL; } info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles); FILEDESC_SEEK (packhandle, header.dirofs, SEEK_SET); if(header.dirlen != FILEDESC_READ (packhandle, (void *)info, header.dirlen)) { Con_Printf("%s is an incomplete PAK, not loading\n", packfile); Mem_Free(info); FILEDESC_CLOSE(packhandle); return NULL; } pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2 strlcpy (pack->filename, packfile, sizeof (pack->filename)); pack->handle = packhandle; pack->numfiles = 0; pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t)); // parse the directory for (i = 0;i < numpackfiles;i++) { fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos); fs_offset_t size = (unsigned int)LittleLong (info[i].filelen); // Ensure a zero terminated file name (required by format). info[i].name[sizeof(info[i].name) - 1] = 0; FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS); } Mem_Free(info); Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles); return pack; } /* ==================== FS_LoadPackVirtual Create a package entry associated with a directory file ==================== */ static pack_t *FS_LoadPackVirtual (const char *dirname) { pack_t *pack; pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t)); pack->vpack = true; pack->ignorecase = false; strlcpy (pack->filename, dirname, sizeof(pack->filename)); pack->handle = FILEDESC_INVALID; pack->numfiles = -1; pack->files = NULL; Con_DPrintf("Added packfile %s (virtual pack)\n", dirname); return pack; } /* ================ FS_AddPack_Fullpath ================ */ /*! Adds the given pack to the search path. * The pack type is autodetected by the file extension. * * Returns true if the file was successfully added to the * search path or if it was already included. * * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of * plain directories. * */ static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs) { searchpath_t *search; pack_t *pak = NULL; const char *ext = FS_FileExtension(pakfile); size_t l; for(search = fs_searchpaths; search; search = search->next) { if(search->pack && !strcasecmp(search->pack->filename, pakfile)) { if(already_loaded) *already_loaded = true; return true; // already loaded } } if(already_loaded) *already_loaded = false; if(!strcasecmp(ext, "pk3dir")) pak = FS_LoadPackVirtual (pakfile); else if(!strcasecmp(ext, "pak")) pak = FS_LoadPackPAK (pakfile); else if(!strcasecmp(ext, "pk3")) pak = FS_LoadPackPK3 (pakfile); else if(!strcasecmp(ext, "obb")) // android apk expansion pak = FS_LoadPackPK3 (pakfile); else Con_Printf("\"%s\" does not have a pack extension\n", pakfile); if(pak) { strlcpy(pak->shortname, shortname, sizeof(pak->shortname)); //Con_DPrintf(" Registered pack with short name %s\n", shortname); if(keep_plain_dirs) { // find the first item whose next one is a pack or NULL searchpath_t *insertion_point = 0; if(fs_searchpaths && !fs_searchpaths->pack) { insertion_point = fs_searchpaths; for(;;) { if(!insertion_point->next) break; if(insertion_point->next->pack) break; insertion_point = insertion_point->next; } } // If insertion_point is NULL, this means that either there is no // item in the list yet, or that the very first item is a pack. In // that case, we want to insert at the beginning... if(!insertion_point) { search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); search->next = fs_searchpaths; fs_searchpaths = search; } else // otherwise we want to append directly after insertion_point. { search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); search->next = insertion_point->next; insertion_point->next = search; } } else { search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); search->next = fs_searchpaths; fs_searchpaths = search; } search->pack = pak; if(pak->vpack) { dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile); // if shortname ends with "pk3dir", strip that suffix to make it just "pk3" // same goes for the name inside the pack structure l = strlen(pak->shortname); if(l >= 7) if(!strcasecmp(pak->shortname + l - 7, ".pk3dir")) pak->shortname[l - 3] = 0; l = strlen(pak->filename); if(l >= 7) if(!strcasecmp(pak->filename + l - 7, ".pk3dir")) pak->filename[l - 3] = 0; } return true; } else { Con_Printf("unable to load pak \"%s\"\n", pakfile); return false; } } /* ================ FS_AddPack ================ */ /*! Adds the given pack to the search path and searches for it in the game path. * The pack type is autodetected by the file extension. * * Returns true if the file was successfully added to the * search path or if it was already included. * * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of * plain directories. */ qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs) { char fullpath[MAX_OSPATH]; int index; searchpath_t *search; if(already_loaded) *already_loaded = false; // then find the real name... search = FS_FindFile(pakfile, &index, true); if(!search || search->pack) { Con_Printf("could not find pak \"%s\"\n", pakfile); return false; } dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile); return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs); } /* ================ FS_AddGameDirectory Sets fs_gamedir, adds the directory to the head of the path, then loads and adds pak1.pak pak2.pak ... ================ */ static void FS_AddGameDirectory (const char *dir) { int i; stringlist_t list; searchpath_t *search; strlcpy (fs_gamedir, dir, sizeof (fs_gamedir)); stringlistinit(&list); listdirectory(&list, "", dir); stringlistsort(&list, false); // add any PAK package in the directory for (i = 0;i < list.numstrings;i++) { if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak")) { FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); } } // add any PK3 package in the directory for (i = 0;i < list.numstrings;i++) { if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")) { FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false); } } stringlistfreecontents(&list); // Add the directory to the search path // (unpacked files have the priority over packed files) search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); strlcpy (search->filename, dir, sizeof (search->filename)); search->next = fs_searchpaths; fs_searchpaths = search; } /* ================ FS_AddGameHierarchy ================ */ static void FS_AddGameHierarchy (const char *dir) { char vabuf[1024]; // Add the common game directory FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir)); if (*fs_userdir) FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir)); } /* ============ FS_FileExtension ============ */ const char *FS_FileExtension (const char *in) { const char *separator, *backslash, *colon, *dot; separator = strrchr(in, '/'); backslash = strrchr(in, '\\'); if (!separator || separator < backslash) separator = backslash; colon = strrchr(in, ':'); if (!separator || separator < colon) separator = colon; dot = strrchr(in, '.'); if (dot == NULL || (separator && (dot < separator))) return ""; return dot + 1; } /* ============ FS_FileWithoutPath ============ */ const char *FS_FileWithoutPath (const char *in) { const char *separator, *backslash, *colon; separator = strrchr(in, '/'); backslash = strrchr(in, '\\'); if (!separator || separator < backslash) separator = backslash; colon = strrchr(in, ':'); if (!separator || separator < colon) separator = colon; return separator ? separator + 1 : in; } /* ================ FS_ClearSearchPath ================ */ static void FS_ClearSearchPath (void) { // unload all packs and directory information, close all pack files // (if a qfile is still reading a pack it won't be harmed because it used // dup() to get its own handle already) while (fs_searchpaths) { searchpath_t *search = fs_searchpaths; fs_searchpaths = search->next; if (search->pack && search->pack != fs_selfpack) { if(!search->pack->vpack) { // close the file FILEDESC_CLOSE(search->pack->handle); // free any memory associated with it if (search->pack->files) Mem_Free(search->pack->files); } Mem_Free(search->pack); } Mem_Free(search); } } static void FS_AddSelfPack(void) { if(fs_selfpack) { searchpath_t *search; search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t)); search->next = fs_searchpaths; search->pack = fs_selfpack; fs_searchpaths = search; } } /* ================ FS_Rescan ================ */ void FS_Rescan (void) { int i; qboolean fs_modified = false; qboolean reset = false; char gamedirbuf[MAX_INPUTLINE]; char vabuf[1024]; if (fs_searchpaths) reset = true; FS_ClearSearchPath(); // automatically activate gamemode for the gamedirs specified if (reset) COM_ChangeGameTypeForGameDirs(); // add the game-specific paths // gamedirname1 (typically id1) FS_AddGameHierarchy (gamedirname1); // update the com_modname (used for server info) if (gamedirname2 && gamedirname2[0]) strlcpy(com_modname, gamedirname2, sizeof(com_modname)); else strlcpy(com_modname, gamedirname1, sizeof(com_modname)); // add the game-specific path, if any // (only used for mission packs and the like, which should set fs_modified) if (gamedirname2 && gamedirname2[0]) { fs_modified = true; FS_AddGameHierarchy (gamedirname2); } // -game // Adds basedir/gamedir as an override game // LordHavoc: now supports multiple -game directories // set the com_modname (reported in server info) *gamedirbuf = 0; for (i = 0;i < fs_numgamedirs;i++) { fs_modified = true; FS_AddGameHierarchy (fs_gamedirs[i]); // update the com_modname (used server info) strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname)); if(i) strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf)); else strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf)); } Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it // add back the selfpack as new first item FS_AddSelfPack(); // set the default screenshot name to either the mod name or the // gamemode screenshot name if (strcmp(com_modname, gamedirname1)) Cvar_SetQuick (&scr_screenshot_name, com_modname); else Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname); if((i = COM_CheckParm("-modname")) && i < com_argc - 1) strlcpy(com_modname, com_argv[i+1], sizeof(com_modname)); // If "-condebug" is in the command line, remove the previous log file if (COM_CheckParm ("-condebug") != 0) unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir)); // look for the pop.lmp file and set registered to true if it is found if (FS_FileExists("gfx/pop.lmp")) Cvar_Set ("registered", "1"); switch(gamemode) { case GAME_NORMAL: case GAME_HIPNOTIC: case GAME_ROGUE: if (!registered.integer) { if (fs_modified) Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n"); else Con_Print("Playing shareware version.\n"); } else Con_Print("Playing registered version.\n"); break; case GAME_STEELSTORM: if (registered.integer) Con_Print("Playing registered version.\n"); else Con_Print("Playing shareware version.\n"); break; default: break; } // unload all wads so that future queries will return the new data W_UnloadAll(); } static void FS_Rescan_f(void) { FS_Rescan(); } /* ================ FS_ChangeGameDirs ================ */ extern qboolean vid_opened; qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing) { int i; const char *p; if (fs_numgamedirs == numgamedirs) { for (i = 0;i < numgamedirs;i++) if (strcasecmp(fs_gamedirs[i], gamedirs[i])) break; if (i == numgamedirs) return true; // already using this set of gamedirs, do nothing } if (numgamedirs > MAX_GAMEDIRS) { if (complain) Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); return false; // too many gamedirs } for (i = 0;i < numgamedirs;i++) { // if string is nasty, reject it p = FS_CheckGameDir(gamedirs[i]); if(!p) { if (complain) Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]); return false; // nasty gamedirs } if(p == fs_checkgamedir_missing && failmissing) { if (complain) Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]); return false; // missing gamedirs } } Host_SaveConfig(); fs_numgamedirs = numgamedirs; for (i = 0;i < fs_numgamedirs;i++) strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i])); // reinitialize filesystem to detect the new paks FS_Rescan(); if (cls.demoplayback) { CL_Disconnect_f(); cls.demonum = 0; } // unload all sounds so they will be reloaded from the new files as needed S_UnloadAllSounds_f(); // close down the video subsystem, it will start up again when the config finishes... VID_Stop(); vid_opened = false; // restart the video subsystem after the config is executed Cbuf_InsertText("\nloadconfig\nvid_restart\n\n"); return true; } /* ================ FS_GameDir_f ================ */ static void FS_GameDir_f (void) { int i; int numgamedirs; char gamedirs[MAX_GAMEDIRS][MAX_QPATH]; if (Cmd_Argc() < 2) { Con_Printf("gamedirs active:"); for (i = 0;i < fs_numgamedirs;i++) Con_Printf(" %s", fs_gamedirs[i]); Con_Printf("\n"); return; } numgamedirs = Cmd_Argc() - 1; if (numgamedirs > MAX_GAMEDIRS) { Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS); return; } for (i = 0;i < numgamedirs;i++) strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i])); if ((cls.state == ca_connected && !cls.demoplayback) || sv.active) { // actually, changing during game would work fine, but would be stupid Con_Printf("Can not change gamedir while client is connected or server is running!\n"); return; } // halt demo playback to close the file CL_Disconnect(); FS_ChangeGameDirs(numgamedirs, gamedirs, true, true); } static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength) { qboolean success; qfile_t *f; stringlist_t list; fs_offset_t n; char vabuf[1024]; stringlistinit(&list); listdirectory(&list, gamedir, ""); success = list.numstrings > 0; stringlistfreecontents(&list); if(success) { f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false); if(f) { n = FS_Read (f, buf, buflength - 1); if(n >= 0) buf[n] = 0; else *buf = 0; FS_Close(f); } else *buf = 0; return buf; } return NULL; } /* ================ FS_CheckGameDir ================ */ const char *FS_CheckGameDir(const char *gamedir) { const char *ret; static char buf[8192]; char vabuf[1024]; if (FS_CheckNastyPath(gamedir, true)) return NULL; ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf)); if(ret) { if(!*ret) { // get description from basedir ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf)); if(ret) return ret; return ""; } return ret; } ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf)); if(ret) return ret; return fs_checkgamedir_missing; } static void FS_ListGameDirs(void) { stringlist_t list, list2; int i; const char *info; char vabuf[1024]; fs_all_gamedirs_count = 0; if(fs_all_gamedirs) Mem_Free(fs_all_gamedirs); stringlistinit(&list); listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), ""); listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), ""); stringlistsort(&list, false); stringlistinit(&list2); for(i = 0; i < list.numstrings; ++i) { if(i) if(!strcmp(list.strings[i-1], list.strings[i])) continue; info = FS_CheckGameDir(list.strings[i]); if(!info) continue; if(info == fs_checkgamedir_missing) continue; if(!*info) continue; stringlistappend(&list2, list.strings[i]); } stringlistfreecontents(&list); fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs)); for(i = 0; i < list2.numstrings; ++i) { info = FS_CheckGameDir(list2.strings[i]); // all this cannot happen any more, but better be safe than sorry if(!info) continue; if(info == fs_checkgamedir_missing) continue; if(!*info) continue; strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name)); strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description)); ++fs_all_gamedirs_count; } } /* #ifdef WIN32 #pragma comment(lib, "shell32.lib") #include #endif */ static void COM_InsertFlags(const char *buf) { const char *p; char *q; const char **new_argv; int i = 0; int args_left = 256; new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2)); if(com_argc == 0) new_argv[0] = "dummy"; // Can't really happen. else new_argv[0] = com_argv[0]; ++i; p = buf; while(COM_ParseToken_Console(&p)) { size_t sz = strlen(com_token) + 1; // shut up clang if(i > args_left) break; q = (char *)Mem_Alloc(fs_mempool, sz); strlcpy(q, com_token, sz); new_argv[i] = q; ++i; } // Now: i <= args_left + 1. if (com_argc >= 1) { memcpy((char *)(&new_argv[i]), &com_argv[1], sizeof(*com_argv) * (com_argc - 1)); i += com_argc - 1; } // Now: i <= args_left + (com_argc || 1). new_argv[i] = NULL; com_argv = new_argv; com_argc = i; } /* ================ FS_Init_SelfPack ================ */ void FS_Init_SelfPack (void) { PK3_OpenLibrary (); fs_mempool = Mem_AllocPool("file management", 0, NULL); // Load darkplaces.opt from the FS. if (!COM_CheckParm("-noopt")) { char *buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL); if(buf) COM_InsertFlags(buf); Mem_Free(buf); } #ifndef USE_RWOPS // Provide the SelfPack. if (!COM_CheckParm("-noselfpack")) { if (com_selffd >= 0) { fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true); if(fs_selfpack) { FS_AddSelfPack(); if (!COM_CheckParm("-noopt")) { char *buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL); if(buf) COM_InsertFlags(buf); Mem_Free(buf); } } } } #endif } static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize) { #if defined(__IPHONEOS__) if (userdirmode == USERDIRMODE_HOME) { // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode // fs_userdir stores configurations to the Documents folder of the app strlcpy(userdir, "../Documents/", MAX_OSPATH); return 1; } return -1; #elif defined(WIN32) char *homedir; #if _MSC_VER >= 1400 size_t homedirlen; #endif TCHAR mydocsdir[MAX_PATH + 1]; wchar_t *savedgamesdirw; char savedgamesdir[MAX_OSPATH]; int fd; char vabuf[1024]; userdir[0] = 0; switch(userdirmode) { default: return -1; case USERDIRMODE_NOHOME: strlcpy(userdir, fs_basedir, userdirsize); break; case USERDIRMODE_MYGAMES: if (!shfolder_dll) Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs); mydocsdir[0] = 0; if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK) { dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname); break; } #if _MSC_VER >= 1400 _dupenv_s(&homedir, &homedirlen, "USERPROFILE"); if(homedir) { dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); free(homedir); break; } #else homedir = getenv("USERPROFILE"); if(homedir) { dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); break; } #endif return -1; case USERDIRMODE_SAVEDGAMES: if (!shell32_dll) Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs); if (!ole32_dll) Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs); if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize) { savedgamesdir[0] = 0; qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED); /* #ifdef __cplusplus if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) #else if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) #endif */ if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK) { memset(savedgamesdir, 0, sizeof(savedgamesdir)); #if _MSC_VER >= 1400 wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1); #else wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1); #endif qCoTaskMemFree(savedgamesdirw); } qCoUninitialize(); if (savedgamesdir[0]) { dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname); break; } } return -1; } #else int fd; char *homedir; char vabuf[1024]; userdir[0] = 0; switch(userdirmode) { default: return -1; case USERDIRMODE_NOHOME: strlcpy(userdir, fs_basedir, userdirsize); break; case USERDIRMODE_HOME: homedir = getenv("HOME"); if(homedir) { dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname); break; } return -1; case USERDIRMODE_SAVEDGAMES: homedir = getenv("HOME"); if(homedir) { #ifdef MACOSX dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname); #else // the XDG say some files would need to go in: // XDG_CONFIG_HOME (or ~/.config/%s/) // XDG_DATA_HOME (or ~/.local/share/%s/) // XDG_CACHE_HOME (or ~/.cache/%s/) // and also search the following global locations if defined: // XDG_CONFIG_DIRS (normally /etc/xdg/%s/) // XDG_DATA_DIRS (normally /usr/share/%s/) // this would be too complicated... return -1; #endif break; } return -1; } #endif #if !defined(__IPHONEOS__) #ifdef WIN32 // historical behavior... if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1")) return 0; // don't bother checking if the basedir folder is writable, it's annoying... unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case #endif // see if we can write to this path (note: won't create path) #ifdef WIN32 // no access() here, we must try to open the file for appending fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false); if(fd >= 0) FILEDESC_CLOSE(fd); #else // on Unix, we don't need to ACTUALLY attempt to open the file if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0) fd = 1; else fd = -1; #endif if(fd >= 0) { return 1; // good choice - the path exists and is writable } else { if (userdirmode == USERDIRMODE_NOHOME) return -1; // path usually already exists, we lack permissions else return 0; // probably good - failed to write but maybe we need to create path } #endif } /* ================ FS_Init ================ */ void FS_Init (void) { const char *p; int i; *fs_basedir = 0; *fs_userdir = 0; *fs_gamedir = 0; // -basedir // Overrides the system supplied base directory (under GAMENAME) // COMMANDLINEOPTION: Filesystem: -basedir chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) i = COM_CheckParm ("-basedir"); if (i && i < com_argc-1) { strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir)); i = (int)strlen (fs_basedir); if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/')) fs_basedir[i-1] = 0; } else { // If the base directory is explicitly defined by the compilation process #ifdef DP_FS_BASEDIR strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir)); #elif defined(__ANDROID__) dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname); #elif defined(MACOSX) // FIXME: is there a better way to find the directory outside the .app, without using Objective-C? if (strstr(com_argv[0], ".app/")) { char *split; strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir)); split = strstr(fs_basedir, ".app/"); if (split) { struct stat statresult; char vabuf[1024]; // truncate to just after the .app/ split[5] = 0; // see if gamedir exists in Resources if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0) { // found gamedir inside Resources, use it strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir)); } else { // no gamedir found in Resources, gamedir is probably // outside the .app, remove .app part of path while (split > fs_basedir && *split != '/') split--; *split = 0; } } } #endif } // make sure the appending of a path separator won't create an unterminated string memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2); // add a path separator to the end of the basedir if it lacks one if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\') strlcat(fs_basedir, "/", sizeof(fs_basedir)); // Add the personal game directory if((i = COM_CheckParm("-userdir")) && i < com_argc - 1) dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]); else if (COM_CheckParm("-nohome")) *fs_userdir = 0; // user wants roaming installation, no userdir else { #ifdef DP_FS_USERDIR strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir)); #else int dirmode; int highestuserdirmode = USERDIRMODE_COUNT - 1; int preferreduserdirmode = USERDIRMODE_COUNT - 1; int userdirstatus[USERDIRMODE_COUNT]; # ifdef WIN32 // historical behavior... if (!strcmp(gamedirname1, "id1")) preferreduserdirmode = USERDIRMODE_NOHOME; # endif // check what limitations the user wants to impose if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME; if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES; if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES; // gather the status of the possible userdirs for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++) { userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); if (userdirstatus[dirmode] == 1) Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir); else if (userdirstatus[dirmode] == 0) Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir); else Con_DPrintf("userdir %i (not applicable)\n", dirmode); } // some games may prefer writing to basedir, but if write fails we // have to search for a real userdir... if (preferreduserdirmode == 0 && userdirstatus[0] < 1) preferreduserdirmode = highestuserdirmode; // check for an existing userdir and continue using it if possible... for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--) if (userdirstatus[dirmode] == 1) break; // if no existing userdir found, make a new one... if (dirmode == 0 && preferreduserdirmode > 0) for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--) if (userdirstatus[dirmode] >= 0) break; // and finally, we picked one... FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir)); Con_DPrintf("userdir %i is the winner\n", dirmode); #endif } // if userdir equal to basedir, clear it to avoid confusion later if (!strcmp(fs_basedir, fs_userdir)) fs_userdir[0] = 0; FS_ListGameDirs(); p = FS_CheckGameDir(gamedirname1); if(!p || p == fs_checkgamedir_missing) Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1); if(gamedirname2) { p = FS_CheckGameDir(gamedirname2); if(!p || p == fs_checkgamedir_missing) Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2); } // -game // Adds basedir/gamedir as an override game // LordHavoc: now supports multiple -game directories for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++) { if (!com_argv[i]) continue; if (!strcmp (com_argv[i], "-game") && i < com_argc-1) { i++; p = FS_CheckGameDir(com_argv[i]); if(!p) Sys_Error("Nasty -game name rejected: %s", com_argv[i]); if(p == fs_checkgamedir_missing) Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]); // add the gamedir to the list of active gamedirs strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs])); fs_numgamedirs++; } } // generate the searchpath FS_Rescan(); if (Thread_HasThreads()) fs_mutex = Thread_CreateMutex(); } void FS_Init_Commands(void) { Cvar_RegisterVariable (&scr_screenshot_name); Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions); Cvar_RegisterVariable (&cvar_fs_gamedir); Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)"); Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes"); Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)"); Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line"); Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line"); Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from"); } /* ================ FS_Shutdown ================ */ void FS_Shutdown (void) { // close all pack files and such // (hopefully there aren't any other open files, but they'll be cleaned up // by the OS anyway) FS_ClearSearchPath(); Mem_FreePool (&fs_mempool); PK3_CloseLibrary (); #ifdef WIN32 Sys_UnloadLibrary (&shfolder_dll); Sys_UnloadLibrary (&shell32_dll); Sys_UnloadLibrary (&ole32_dll); #endif if (fs_mutex) Thread_DestroyMutex(fs_mutex); } static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking) { filedesc_t handle = FILEDESC_INVALID; int mod, opt; unsigned int ind; qboolean dolock = false; // Parse the mode string switch (mode[0]) { case 'r': mod = O_RDONLY; opt = 0; break; case 'w': mod = O_WRONLY; opt = O_CREAT | O_TRUNC; break; case 'a': mod = O_WRONLY; opt = O_CREAT | O_APPEND; break; default: Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode); return FILEDESC_INVALID; } for (ind = 1; mode[ind] != '\0'; ind++) { switch (mode[ind]) { case '+': mod = O_RDWR; break; case 'b': opt |= O_BINARY; break; case 'l': dolock = true; break; default: Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n", filepath, mode, mode[ind]); } } if (nonblocking) opt |= O_NONBLOCK; if(COM_CheckParm("-readonly") && mod != O_RDONLY) return FILEDESC_INVALID; #if USE_RWOPS if (dolock) return FILEDESC_INVALID; handle = SDL_RWFromFile(filepath, mode); #else # ifdef WIN32 # if _MSC_VER >= 1400 _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE); # else handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE); # endif # else handle = open (filepath, mod | opt, 0666); if(handle >= 0 && dolock) { struct flock l; l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK); l.l_whence = SEEK_SET; l.l_start = 0; l.l_len = 0; if(fcntl(handle, F_SETLK, &l) == -1) { FILEDESC_CLOSE(handle); handle = -1; } } # endif #endif return handle; } int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking) { #ifdef USE_RWOPS return -1; #else return FS_SysOpenFiledesc(filepath, mode, nonblocking); #endif } /* ==================== FS_SysOpen Internal function used to create a qfile_t and open the relevant non-packed file on disk ==================== */ qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking) { qfile_t* file; file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); file->ungetc = EOF; file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking); if (!FILEDESC_ISVALID(file->handle)) { Mem_Free (file); return NULL; } file->filename = Mem_strdup(fs_mempool, filepath); file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END); // For files opened in append mode, we start at the end of the file if (mode[0] == 'a') file->position = file->real_length; else FILEDESC_SEEK (file->handle, 0, SEEK_SET); return file; } /* =========== FS_OpenPackedFile Open a packed file using its package file descriptor =========== */ static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind) { packfile_t *pfile; filedesc_t dup_handle; qfile_t* file; pfile = &pack->files[pack_ind]; // If we don't have the true offset, get it now if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS)) if (!PK3_GetTrueFileOffset (pfile, pack)) return NULL; #ifndef LINK_TO_ZLIB // No Zlib DLL = no compressed files if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED)) { Con_Printf("WARNING: can't open the compressed file %s\n" "You need the Zlib DLL to use compressed files\n", pfile->name); return NULL; } #endif // LordHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before // the dup() call to avoid having to close the dup_handle on error here if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1) { Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n", pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset)); return NULL; } dup_handle = FILEDESC_DUP (pack->filename, pack->handle); if (!FILEDESC_ISVALID(dup_handle)) { Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename); return NULL; } file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); memset (file, 0, sizeof (*file)); file->handle = dup_handle; file->flags = QFILE_FLAG_PACKED; file->real_length = pfile->realsize; file->offset = pfile->offset; file->position = 0; file->ungetc = EOF; if (pfile->flags & PACKFILE_FLAG_DEFLATED) { ztoolkit_t *ztk; file->flags |= QFILE_FLAG_DEFLATED; // We need some more variables ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk)); ztk->comp_length = pfile->packsize; // Initialize zlib stream ztk->zstream.next_in = ztk->input; ztk->zstream.avail_in = 0; /* From Zlib's "unzip.c": * * windowBits is passed < 0 to tell that there is no zlib header. * Note that in this case inflate *requires* an extra "dummy" byte * after the compressed stream in order to complete decompression and * return Z_STREAM_END. * In unzip, i don't wait absolutely Z_STREAM_END because I known the * size of both compressed and uncompressed data */ if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK) { Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name); FILEDESC_CLOSE(dup_handle); Mem_Free(file); return NULL; } ztk->zstream.next_out = file->buff; ztk->zstream.avail_out = sizeof (file->buff); file->ztk = ztk; } return file; } /* ==================== FS_CheckNastyPath Return true if the path should be rejected due to one of the following: 1: path elements that are non-portable 2: path elements that would allow access to files outside the game directory, or are just not a good idea for a mod to be using. ==================== */ int FS_CheckNastyPath (const char *path, qboolean isgamedir) { // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless if (!path[0]) return 2; // Windows: don't allow \ in filenames (windows-only), period. // (on Windows \ is a directory separator, but / is also supported) if (strstr(path, "\\")) return 1; // non-portable // Mac: don't allow Mac-only filenames - : is a directory separator // instead of /, but we rely on / working already, so there's no reason to // support a Mac-only path // Amiga and Windows: : tries to go to root of drive if (strstr(path, ":")) return 1; // non-portable attempt to go to root of drive // Amiga: // is parent directory if (strstr(path, "//")) return 1; // non-portable attempt to go to parent directory // all: don't allow going to parent directory (../ or /../) if (strstr(path, "..")) return 2; // attempt to go outside the game directory // Windows and UNIXes: don't allow absolute paths if (path[0] == '/') return 2; // attempt to go outside the game directory // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc if (strstr(path, "./")) return 2; // possible attempt to go outside the game directory // all: forbid trailing slash on gamedir if (isgamedir && path[strlen(path)-1] == '/') return 2; // all: forbid leading dot on any filename for any reason if (strstr(path, "/.")) return 2; // attempt to go outside the game directory // after all these checks we're pretty sure it's a / separated filename // and won't do much if any harm return false; } /* ==================== FS_FindFile Look for a file in the packages and in the filesystem Return the searchpath where the file was found (or NULL) and the file index in the package if relevant ==================== */ static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet) { searchpath_t *search; pack_t *pak; // search through the path, one element at a time for (search = fs_searchpaths;search;search = search->next) { // is the element a pak file? if (search->pack && !search->pack->vpack) { int (*strcmp_funct) (const char* str1, const char* str2); int left, right, middle; pak = search->pack; strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; // Look for the file (binary search) left = 0; right = pak->numfiles - 1; while (left <= right) { int diff; middle = (left + right) / 2; diff = strcmp_funct (pak->files[middle].name, name); // Found it if (!diff) { if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0) { // yes, but the first one is empty so we treat it as not being there if (!quiet && developer_extra.integer) Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name); if (index != NULL) *index = -1; return NULL; } if (!quiet && developer_extra.integer) Con_DPrintf("FS_FindFile: %s in %s\n", pak->files[middle].name, pak->filename); if (index != NULL) *index = middle; return search; } // If we're too far in the list if (diff > 0) right = middle - 1; else left = middle + 1; } } else { char netpath[MAX_OSPATH]; dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name); if (FS_SysFileExists (netpath)) { if (!quiet && developer_extra.integer) Con_DPrintf("FS_FindFile: %s\n", netpath); if (index != NULL) *index = -1; return search; } } } if (!quiet && developer_extra.integer) Con_DPrintf("FS_FindFile: can't find %s\n", name); if (index != NULL) *index = -1; return NULL; } /* =========== FS_OpenReadFile Look for a file in the search paths and open it in read-only mode =========== */ static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels) { searchpath_t *search; int pack_ind; search = FS_FindFile (filename, &pack_ind, quiet); // Not found? if (search == NULL) return NULL; // Found in the filesystem? if (pack_ind < 0) { // this works with vpacks, so we are fine char path [MAX_OSPATH]; dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename); return FS_SysOpen (path, "rb", nonblocking); } // So, we found it in a package... // Is it a PK3 symlink? // TODO also handle directory symlinks by parsing the whole structure... // but heck, file symlinks are good enough for now if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK) { if(symlinkLevels <= 0) { Con_Printf("symlink: %s: too many levels of symbolic links\n", filename); return NULL; } else { char linkbuf[MAX_QPATH]; fs_offset_t count; qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind); const char *mergeslash; char *mergestart; if(!linkfile) return NULL; count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1); FS_Close(linkfile); if(count < 0) return NULL; linkbuf[count] = 0; // Now combine the paths... mergeslash = strrchr(filename, '/'); mergestart = linkbuf; if(!mergeslash) mergeslash = filename; while(!strncmp(mergestart, "../", 3)) { mergestart += 3; while(mergeslash > filename) { --mergeslash; if(*mergeslash == '/') break; } } // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended if(mergeslash == filename) { // Either mergeslash == filename, then we just replace the name (done below) } else { // Or, we append the name after mergeslash; // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first int spaceNeeded = mergeslash - filename + 1; int spaceRemoved = mergestart - linkbuf; if(count - spaceRemoved + spaceNeeded >= MAX_QPATH) { Con_DPrintf("symlink: too long path rejected\n"); return NULL; } memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved); memcpy(linkbuf, filename, spaceNeeded); linkbuf[count - spaceRemoved + spaceNeeded] = 0; mergestart = linkbuf; } if (!quiet && developer_loading.integer) Con_DPrintf("symlink: %s -> %s\n", filename, mergestart); if(FS_CheckNastyPath (mergestart, false)) { Con_DPrintf("symlink: nasty path %s rejected\n", mergestart); return NULL; } return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1); } } return FS_OpenPackedFile (search->pack, pack_ind); } /* ============================================================================= MAIN PUBLIC FUNCTIONS ============================================================================= */ /* ==================== FS_OpenRealFile Open a file in the userpath. The syntax is the same as fopen Used for savegame scanning in menu, and all file writing. ==================== */ qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet) { char real_path [MAX_OSPATH]; if (FS_CheckNastyPath(filepath, false)) { Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false"); return NULL; } dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack // If the file is opened in "write", "append", or "read/write" mode, // create directories up to the file. if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+')) FS_CreatePath (real_path); return FS_SysOpen (real_path, mode, false); } /* ==================== FS_OpenVirtualFile Open a file. The syntax is the same as fopen ==================== */ qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet) { qfile_t *result = NULL; if (FS_CheckNastyPath(filepath, false)) { Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false"); return NULL; } if (fs_mutex) Thread_LockMutex(fs_mutex); result = FS_OpenReadFile (filepath, quiet, false, 16); if (fs_mutex) Thread_UnlockMutex(fs_mutex); return result; } /* ==================== FS_FileFromData Open a file. The syntax is the same as fopen ==================== */ qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet) { qfile_t* file; file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file)); memset (file, 0, sizeof (*file)); file->flags = QFILE_FLAG_DATA; file->ungetc = EOF; file->real_length = size; file->data = data; return file; } /* ==================== FS_Close Close a file ==================== */ int FS_Close (qfile_t* file) { if(file->flags & QFILE_FLAG_DATA) { Mem_Free(file); return 0; } if (FILEDESC_CLOSE (file->handle)) return EOF; if (file->filename) { if (file->flags & QFILE_FLAG_REMOVE) { if (remove(file->filename) == -1) { // No need to report this. If removing a just // written file failed, this most likely means // someone else deleted it first - which we // like. } } Mem_Free((void *) file->filename); } if (file->ztk) { qz_inflateEnd (&file->ztk->zstream); Mem_Free (file->ztk); } Mem_Free (file); return 0; } void FS_RemoveOnClose(qfile_t* file) { file->flags |= QFILE_FLAG_REMOVE; } /* ==================== FS_Write Write "datasize" bytes into a file ==================== */ fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize) { fs_offset_t written = 0; // If necessary, seek to the exact file position we're supposed to be if (file->buff_ind != file->buff_len) { if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1) { Con_Printf("WARNING: could not seek in %s.\n", file->filename); } } // Purge cached data FS_Purge (file); // Write the buffer and update the position // LordHavoc: to hush a warning about passing size_t to an unsigned int parameter on Win64 we do this as multiple writes if the size would be too big for an integer (we never write that big in one go, but it's a theory) while (written < (fs_offset_t)datasize) { // figure out how much to write in one chunk fs_offset_t maxchunk = 1<<30; // 1 GiB int chunk = (int)min((fs_offset_t)datasize - written, maxchunk); int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk); // if at least some was written, add it to our accumulator if (result > 0) written += result; // if the result is not what we expected, consider the write to be incomplete if (result != chunk) break; } file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR); if (file->real_length < file->position) file->real_length = file->position; // note that this will never be less than 0 even if the write failed return written; } /* ==================== FS_Read Read up to "buffersize" bytes from a file ==================== */ fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize) { fs_offset_t count, done; if (buffersize == 0) return 0; // Get rid of the ungetc character if (file->ungetc != EOF) { ((char*)buffer)[0] = file->ungetc; buffersize--; file->ungetc = EOF; done = 1; } else done = 0; if(file->flags & QFILE_FLAG_DATA) { size_t left = file->real_length - file->position; if(buffersize > left) buffersize = left; memcpy(buffer, file->data + file->position, buffersize); file->position += buffersize; return buffersize; } // First, we copy as many bytes as we can from "buff" if (file->buff_ind < file->buff_len) { count = file->buff_len - file->buff_ind; count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize; done += count; memcpy (buffer, &file->buff[file->buff_ind], count); file->buff_ind += count; buffersize -= count; if (buffersize == 0) return done; } // NOTE: at this point, the read buffer is always empty // If the file isn't compressed if (! (file->flags & QFILE_FLAG_DEFLATED)) { fs_offset_t nb; // We must take care to not read after the end of the file count = file->real_length - file->position; // If we have a lot of data to get, put them directly into "buffer" if (buffersize > sizeof (file->buff) / 2) { if (count > (fs_offset_t)buffersize) count = (fs_offset_t)buffersize; if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1) { // Seek failed. When reading from a pipe, and // the caller never called FS_Seek, this still // works fine. So no reporting this error. } nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count); if (nb > 0) { done += nb; file->position += nb; // Purge cached data FS_Purge (file); } } else { if (count > (fs_offset_t)sizeof (file->buff)) count = (fs_offset_t)sizeof (file->buff); if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1) { // Seek failed. When reading from a pipe, and // the caller never called FS_Seek, this still // works fine. So no reporting this error. } nb = FILEDESC_READ (file->handle, file->buff, count); if (nb > 0) { file->buff_len = nb; file->position += nb; // Copy the requested data in "buffer" (as much as we can) count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; memcpy (&((unsigned char*)buffer)[done], file->buff, count); file->buff_ind = count; done += count; } } return done; } // If the file is compressed, it's more complicated... // We cycle through a few operations until we have read enough data while (buffersize > 0) { ztoolkit_t *ztk = file->ztk; int error; // NOTE: at this point, the read buffer is always empty // If "input" is also empty, we need to refill it if (ztk->in_ind == ztk->in_len) { // If we are at the end of the file if (file->position == file->real_length) return done; count = (fs_offset_t)(ztk->comp_length - ztk->in_position); if (count > (fs_offset_t)sizeof (ztk->input)) count = (fs_offset_t)sizeof (ztk->input); FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET); if (FILEDESC_READ (file->handle, ztk->input, count) != count) { Con_Printf ("FS_Read: unexpected end of file\n"); break; } ztk->in_ind = 0; ztk->in_len = count; ztk->in_position += count; } ztk->zstream.next_in = &ztk->input[ztk->in_ind]; ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind); // Now that we are sure we have compressed data available, we need to determine // if it's better to inflate it in "file->buff" or directly in "buffer" // Inflate the data in "file->buff" if (buffersize < sizeof (file->buff) / 2) { ztk->zstream.next_out = file->buff; ztk->zstream.avail_out = sizeof (file->buff); error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); if (error != Z_OK && error != Z_STREAM_END) { Con_Printf ("FS_Read: Can't inflate file\n"); break; } ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out; file->position += file->buff_len; // Copy the requested data in "buffer" (as much as we can) count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize; memcpy (&((unsigned char*)buffer)[done], file->buff, count); file->buff_ind = count; } // Else, we inflate directly in "buffer" else { ztk->zstream.next_out = &((unsigned char*)buffer)[done]; ztk->zstream.avail_out = (unsigned int)buffersize; error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH); if (error != Z_OK && error != Z_STREAM_END) { Con_Printf ("FS_Read: Can't inflate file\n"); break; } ztk->in_ind = ztk->in_len - ztk->zstream.avail_in; // How much data did it inflate? count = (fs_offset_t)(buffersize - ztk->zstream.avail_out); file->position += count; // Purge cached data FS_Purge (file); } done += count; buffersize -= count; } return done; } /* ==================== FS_Print Print a string into a file ==================== */ int FS_Print (qfile_t* file, const char *msg) { return (int)FS_Write (file, msg, strlen (msg)); } /* ==================== FS_Printf Print a string into a file ==================== */ int FS_Printf(qfile_t* file, const char* format, ...) { int result; va_list args; va_start (args, format); result = FS_VPrintf (file, format, args); va_end (args); return result; } /* ==================== FS_VPrintf Print a string into a file ==================== */ int FS_VPrintf (qfile_t* file, const char* format, va_list ap) { int len; fs_offset_t buff_size = MAX_INPUTLINE; char *tempbuff; for (;;) { tempbuff = (char *)Mem_Alloc (tempmempool, buff_size); len = dpvsnprintf (tempbuff, buff_size, format, ap); if (len >= 0 && len < buff_size) break; Mem_Free (tempbuff); buff_size *= 2; } len = FILEDESC_WRITE (file->handle, tempbuff, len); Mem_Free (tempbuff); return len; } /* ==================== FS_Getc Get the next character of a file ==================== */ int FS_Getc (qfile_t* file) { unsigned char c; if (FS_Read (file, &c, 1) != 1) return EOF; return c; } /* ==================== FS_UnGetc Put a character back into the read buffer (only supports one character!) ==================== */ int FS_UnGetc (qfile_t* file, unsigned char c) { // If there's already a character waiting to be read if (file->ungetc != EOF) return EOF; file->ungetc = c; return c; } /* ==================== FS_Seek Move the position index in a file ==================== */ int FS_Seek (qfile_t* file, fs_offset_t offset, int whence) { ztoolkit_t *ztk; unsigned char* buffer; fs_offset_t buffersize; // Compute the file offset switch (whence) { case SEEK_CUR: offset += file->position - file->buff_len + file->buff_ind; break; case SEEK_SET: break; case SEEK_END: offset += file->real_length; break; default: return -1; } if (offset < 0 || offset > file->real_length) return -1; if(file->flags & QFILE_FLAG_DATA) { file->position = offset; return 0; } // If we have the data in our read buffer, we don't need to actually seek if (file->position - file->buff_len <= offset && offset <= file->position) { file->buff_ind = offset + file->buff_len - file->position; return 0; } // Purge cached data FS_Purge (file); // Unpacked or uncompressed files can seek directly if (! (file->flags & QFILE_FLAG_DEFLATED)) { if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1) return -1; file->position = offset; return 0; } // Seeking in compressed files is more a hack than anything else, // but we need to support it, so here we go. ztk = file->ztk; // If we have to go back in the file, we need to restart from the beginning if (offset <= file->position) { ztk->in_ind = 0; ztk->in_len = 0; ztk->in_position = 0; file->position = 0; if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1) Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n"); // Reset the Zlib stream ztk->zstream.next_in = ztk->input; ztk->zstream.avail_in = 0; qz_inflateReset (&ztk->zstream); } // We need a big buffer to force inflating into it directly buffersize = 2 * sizeof (file->buff); buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize); // Skip all data until we reach the requested offset while (offset > file->position) { fs_offset_t diff = offset - file->position; fs_offset_t count, len; count = (diff > buffersize) ? buffersize : diff; len = FS_Read (file, buffer, count); if (len != count) { Mem_Free (buffer); return -1; } } Mem_Free (buffer); return 0; } /* ==================== FS_Tell Give the current position in a file ==================== */ fs_offset_t FS_Tell (qfile_t* file) { return file->position - file->buff_len + file->buff_ind; } /* ==================== FS_FileSize Give the total size of a file ==================== */ fs_offset_t FS_FileSize (qfile_t* file) { return file->real_length; } /* ==================== FS_Purge Erases any buffered input or output data ==================== */ void FS_Purge (qfile_t* file) { file->buff_len = 0; file->buff_ind = 0; file->ungetc = EOF; } /* ============ FS_LoadAndCloseQFile Loads full content of a qfile_t and closes it. Always appends a 0 byte. ============ */ static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer) { unsigned char *buf = NULL; fs_offset_t filesize = 0; if (file) { filesize = file->real_length; if(filesize < 0) { Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false"); FS_Close(file); return NULL; } buf = (unsigned char *)Mem_Alloc (pool, filesize + 1); buf[filesize] = '\0'; FS_Read (file, buf, filesize); FS_Close (file); if (developer_loadfile.integer) Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize); } if (filesizepointer) *filesizepointer = filesize; return buf; } /* ============ FS_LoadFile Filename are relative to the quake directory. Always appends a 0 byte. ============ */ unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer) { qfile_t *file = FS_OpenVirtualFile(path, quiet); return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer); } /* ============ FS_SysLoadFile Filename are OS paths. Always appends a 0 byte. ============ */ unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer) { qfile_t *file = FS_SysOpen(path, "rb", false); return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer); } /* ============ FS_WriteFile The filename will be prefixed by the current game directory ============ */ qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count) { qfile_t *file; size_t i; fs_offset_t lentotal; file = FS_OpenRealFile(filename, "wb", false); if (!file) { Con_Printf("FS_WriteFile: failed on %s\n", filename); return false; } lentotal = 0; for(i = 0; i < count; ++i) lentotal += len[i]; Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal); for(i = 0; i < count; ++i) FS_Write (file, data[i], len[i]); FS_Close (file); return true; } qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len) { return FS_WriteFileInBlocks(filename, &data, &len, 1); } /* ============================================================================= OTHERS PUBLIC FUNCTIONS ============================================================================= */ /* ============ FS_StripExtension ============ */ void FS_StripExtension (const char *in, char *out, size_t size_out) { char *last = NULL; char currentchar; if (size_out == 0) return; while ((currentchar = *in) && size_out > 1) { if (currentchar == '.') last = out; else if (currentchar == '/' || currentchar == '\\' || currentchar == ':') last = NULL; *out++ = currentchar; in++; size_out--; } if (last) *last = 0; else *out = 0; } /* ================== FS_DefaultExtension ================== */ void FS_DefaultExtension (char *path, const char *extension, size_t size_path) { const char *src; // if path doesn't have a .EXT, append extension // (extension should include the .) src = path + strlen(path); while (*src != '/' && src != path) { if (*src == '.') return; // it has an extension src--; } strlcat (path, extension, size_path); } /* ================== FS_FileType Look for a file in the packages and in the filesystem ================== */ int FS_FileType (const char *filename) { searchpath_t *search; char fullpath[MAX_OSPATH]; search = FS_FindFile (filename, NULL, true); if(!search) return FS_FILETYPE_NONE; if(search->pack && !search->pack->vpack) return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename); return FS_SysFileType(fullpath); } /* ================== FS_FileExists Look for a file in the packages and in the filesystem ================== */ qboolean FS_FileExists (const char *filename) { return (FS_FindFile (filename, NULL, true) != NULL); } /* ================== FS_SysFileExists Look for a file in the filesystem only ================== */ int FS_SysFileType (const char *path) { #if WIN32 // Sajt - some older sdks are missing this define # ifndef INVALID_FILE_ATTRIBUTES # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) # endif DWORD result = GetFileAttributes(path); if(result == INVALID_FILE_ATTRIBUTES) return FS_FILETYPE_NONE; if(result & FILE_ATTRIBUTE_DIRECTORY) return FS_FILETYPE_DIRECTORY; return FS_FILETYPE_FILE; #else struct stat buf; if (stat (path,&buf) == -1) return FS_FILETYPE_NONE; #ifndef S_ISDIR #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR) #endif if(S_ISDIR(buf.st_mode)) return FS_FILETYPE_DIRECTORY; return FS_FILETYPE_FILE; #endif } qboolean FS_SysFileExists (const char *path) { return FS_SysFileType (path) != FS_FILETYPE_NONE; } /* =========== FS_Search Allocate and fill a search structure with information on matching filenames. =========== */ fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet) { fssearch_t *search; searchpath_t *searchpath; pack_t *pak; int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex; stringlist_t resultlist; stringlist_t dirlist; const char *slash, *backslash, *colon, *separator; char *basepath; for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++) ; if (i > 0) { Con_Printf("Don't use punctuation at the beginning of a search pattern!\n"); return NULL; } stringlistinit(&resultlist); stringlistinit(&dirlist); search = NULL; slash = strrchr(pattern, '/'); backslash = strrchr(pattern, '\\'); colon = strrchr(pattern, ':'); separator = max(slash, backslash); separator = max(separator, colon); basepathlength = separator ? (separator + 1 - pattern) : 0; basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1); if (basepathlength) memcpy(basepath, pattern, basepathlength); basepath[basepathlength] = 0; // search through the path, one element at a time for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next) { // is the element a pak file? if (searchpath->pack && !searchpath->pack->vpack) { // look through all the pak file elements pak = searchpath->pack; for (i = 0;i < pak->numfiles;i++) { char temp[MAX_OSPATH]; strlcpy(temp, pak->files[i].name, sizeof(temp)); while (temp[0]) { if (matchpattern(temp, (char *)pattern, true)) { for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) if (!strcmp(resultlist.strings[resultlistindex], temp)) break; if (resultlistindex == resultlist.numstrings) { stringlistappend(&resultlist, temp); if (!quiet && developer_loading.integer) Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp); } } // strip off one path element at a time until empty // this way directories are added to the listing if they match the pattern slash = strrchr(temp, '/'); backslash = strrchr(temp, '\\'); colon = strrchr(temp, ':'); separator = temp; if (separator < slash) separator = slash; if (separator < backslash) separator = backslash; if (separator < colon) separator = colon; *((char *)separator) = 0; } } } else { stringlist_t matchedSet, foundSet; const char *start = pattern; stringlistinit(&matchedSet); stringlistinit(&foundSet); // add a first entry to the set stringlistappend(&matchedSet, ""); // iterate through pattern's path while (*start) { const char *asterisk, *wildcard, *nextseparator, *prevseparator; char subpath[MAX_OSPATH]; char subpattern[MAX_OSPATH]; // find the next wildcard wildcard = strchr(start, '?'); asterisk = strchr(start, '*'); if (asterisk && (!wildcard || asterisk < wildcard)) { wildcard = asterisk; } if (wildcard) { nextseparator = strchr( wildcard, '/' ); } else { nextseparator = NULL; } if( !nextseparator ) { nextseparator = start + strlen( start ); } // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string) // copy everything up except nextseperator strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1))); // find the last '/' before the wildcard prevseparator = strrchr( subpattern, '/' ); if (!prevseparator) prevseparator = subpattern; else prevseparator++; // copy everything from start to the previous including the '/' (before the wildcard) // everything up to start is already included in the path of matchedSet's entries strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1))); // for each entry in matchedSet try to open the subdirectories specified in subpath for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) { char temp[MAX_OSPATH]; strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) ); strlcat( temp, subpath, sizeof(temp) ); listdirectory( &foundSet, searchpath->filename, temp ); } if( dirlistindex == 0 ) { break; } // reset the current result set stringlistfreecontents( &matchedSet ); // match against the pattern for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) { const char *direntry = foundSet.strings[ dirlistindex ]; if (matchpattern(direntry, subpattern, true)) { stringlistappend( &matchedSet, direntry ); } } stringlistfreecontents( &foundSet ); start = nextseparator; } for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++) { const char *matchtemp = matchedSet.strings[dirlistindex]; if (matchpattern(matchtemp, (char *)pattern, true)) { for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) if (!strcmp(resultlist.strings[resultlistindex], matchtemp)) break; if (resultlistindex == resultlist.numstrings) { stringlistappend(&resultlist, matchtemp); if (!quiet && developer_loading.integer) Con_Printf("SearchDirFile: %s\n", matchtemp); } } } stringlistfreecontents( &matchedSet ); } } if (resultlist.numstrings) { stringlistsort(&resultlist, true); numfiles = resultlist.numstrings; numchars = 0; for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1; search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *)); search->filenames = (char **)((char *)search + sizeof(fssearch_t)); search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *)); search->numfilenames = (int)numfiles; numfiles = 0; numchars = 0; for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++) { size_t textlen; search->filenames[numfiles] = search->filenamesbuffer + numchars; textlen = strlen(resultlist.strings[resultlistindex]) + 1; memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen); numfiles++; numchars += (int)textlen; } } stringlistfreecontents(&resultlist); Mem_Free(basepath); return search; } void FS_FreeSearch(fssearch_t *search) { Z_Free(search); } extern int con_linewidth; static int FS_ListDirectory(const char *pattern, int oneperline) { int numfiles; int numcolumns; int numlines; int columnwidth; int linebufpos; int i, j, k, l; const char *name; char linebuf[MAX_INPUTLINE]; fssearch_t *search; search = FS_Search(pattern, true, true); if (!search) return 0; numfiles = search->numfilenames; if (!oneperline) { // FIXME: the names could be added to one column list and then // gradually shifted into the next column if they fit, and then the // next to make a compact variable width listing but it's a lot more // complicated... // find width for columns columnwidth = 0; for (i = 0;i < numfiles;i++) { l = (int)strlen(search->filenames[i]); if (columnwidth < l) columnwidth = l; } // count the spacing character columnwidth++; // calculate number of columns numcolumns = con_linewidth / columnwidth; // don't bother with the column printing if it's only one column if (numcolumns >= 2) { numlines = (numfiles + numcolumns - 1) / numcolumns; for (i = 0;i < numlines;i++) { linebufpos = 0; for (k = 0;k < numcolumns;k++) { l = i * numcolumns + k; if (l < numfiles) { name = search->filenames[l]; for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++) linebuf[linebufpos++] = name[j]; // space out name unless it's the last on the line if (k + 1 < numcolumns && l + 1 < numfiles) for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++) linebuf[linebufpos++] = ' '; } } linebuf[linebufpos] = 0; Con_Printf("%s\n", linebuf); } } else oneperline = true; } if (oneperline) for (i = 0;i < numfiles;i++) Con_Printf("%s\n", search->filenames[i]); FS_FreeSearch(search); return (int)numfiles; } static void FS_ListDirectoryCmd (const char* cmdname, int oneperline) { const char *pattern; if (Cmd_Argc() >= 3) { Con_Printf("usage:\n%s [path/pattern]\n", cmdname); return; } if (Cmd_Argc() == 2) pattern = Cmd_Argv(1); else pattern = "*"; if (!FS_ListDirectory(pattern, oneperline)) Con_Print("No files found.\n"); } void FS_Dir_f(void) { FS_ListDirectoryCmd("dir", true); } void FS_Ls_f(void) { FS_ListDirectoryCmd("ls", false); } void FS_Which_f(void) { const char *filename; int index; searchpath_t *sp; if (Cmd_Argc() != 2) { Con_Printf("usage:\n%s \n", Cmd_Argv(0)); return; } filename = Cmd_Argv(1); sp = FS_FindFile(filename, &index, true); if (!sp) { Con_Printf("%s isn't anywhere\n", filename); return; } if (sp->pack) { if(sp->pack->vpack) Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname); else Con_Printf("%s is in package %s\n", filename, sp->pack->shortname); } else Con_Printf("%s is file %s%s\n", filename, sp->filename, filename); } const char *FS_WhichPack(const char *filename) { int index; searchpath_t *sp = FS_FindFile(filename, &index, true); if(sp && sp->pack) return sp->pack->shortname; else if(sp) return ""; else return 0; } /* ==================== FS_IsRegisteredQuakePack Look for a proof of purchase file file in the requested package If it is found, this file should NOT be downloaded. ==================== */ qboolean FS_IsRegisteredQuakePack(const char *name) { searchpath_t *search; pack_t *pak; // search through the path, one element at a time for (search = fs_searchpaths;search;search = search->next) { if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name)) // TODO do we want to support vpacks in here too? { int (*strcmp_funct) (const char* str1, const char* str2); int left, right, middle; pak = search->pack; strcmp_funct = pak->ignorecase ? strcasecmp : strcmp; // Look for the file (binary search) left = 0; right = pak->numfiles - 1; while (left <= right) { int diff; middle = (left + right) / 2; diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp"); // Found it if (!diff) return true; // If we're too far in the list if (diff > 0) right = middle - 1; else left = middle + 1; } // we found the requested pack but it is not registered quake return false; } } return false; } int FS_CRCFile(const char *filename, size_t *filesizepointer) { int crc = -1; unsigned char *filedata; fs_offset_t filesize; if (filesizepointer) *filesizepointer = 0; if (!filename || !*filename) return crc; filedata = FS_LoadFile(filename, tempmempool, true, &filesize); if (filedata) { if (filesizepointer) *filesizepointer = filesize; crc = CRC_Block(filedata, filesize); Mem_Free(filedata); } return crc; } unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool) { z_stream strm; unsigned char *out = NULL; unsigned char *tmp; *deflated_size = 0; #ifndef LINK_TO_ZLIB if(!zlib_dll) return NULL; #endif memset(&strm, 0, sizeof(strm)); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; if(level < 0) level = Z_DEFAULT_COMPRESSION; if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK) { Con_Printf("FS_Deflate: deflate init error!\n"); return NULL; } strm.next_in = (unsigned char*)data; strm.avail_in = (unsigned int)size; tmp = (unsigned char *) Mem_Alloc(tempmempool, size); if(!tmp) { Con_Printf("FS_Deflate: not enough memory in tempmempool!\n"); qz_deflateEnd(&strm); return NULL; } strm.next_out = tmp; strm.avail_out = (unsigned int)size; if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END) { Con_Printf("FS_Deflate: deflate failed!\n"); qz_deflateEnd(&strm); Mem_Free(tmp); return NULL; } if(qz_deflateEnd(&strm) != Z_OK) { Con_Printf("FS_Deflate: deflateEnd failed\n"); Mem_Free(tmp); return NULL; } if(strm.total_out >= size) { Con_Printf("FS_Deflate: deflate is useless on this data!\n"); Mem_Free(tmp); return NULL; } out = (unsigned char *) Mem_Alloc(mempool, strm.total_out); if(!out) { Con_Printf("FS_Deflate: not enough memory in target mempool!\n"); Mem_Free(tmp); return NULL; } *deflated_size = (size_t)strm.total_out; memcpy(out, tmp, strm.total_out); Mem_Free(tmp); return out; } static void AssertBufsize(sizebuf_t *buf, int length) { if(buf->cursize + length > buf->maxsize) { int oldsize = buf->maxsize; unsigned char *olddata; olddata = buf->data; buf->maxsize += length; buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize); if(olddata) { memcpy(buf->data, olddata, oldsize); Mem_Free(olddata); } } } unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool) { int ret; z_stream strm; unsigned char *out = NULL; unsigned char tmp[2048]; unsigned int have; sizebuf_t outbuf; *inflated_size = 0; #ifndef LINK_TO_ZLIB if(!zlib_dll) return NULL; #endif memset(&outbuf, 0, sizeof(outbuf)); outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp)); outbuf.maxsize = sizeof(tmp); memset(&strm, 0, sizeof(strm)); strm.zalloc = Z_NULL; strm.zfree = Z_NULL; strm.opaque = Z_NULL; if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK) { Con_Printf("FS_Inflate: inflate init error!\n"); Mem_Free(outbuf.data); return NULL; } strm.next_in = (unsigned char*)data; strm.avail_in = (unsigned int)size; do { strm.next_out = tmp; strm.avail_out = sizeof(tmp); ret = qz_inflate(&strm, Z_NO_FLUSH); // it either returns Z_OK on progress, Z_STREAM_END on end // or an error code switch(ret) { case Z_STREAM_END: case Z_OK: break; case Z_STREAM_ERROR: Con_Print("FS_Inflate: stream error!\n"); break; case Z_DATA_ERROR: Con_Print("FS_Inflate: data error!\n"); break; case Z_MEM_ERROR: Con_Print("FS_Inflate: mem error!\n"); break; case Z_BUF_ERROR: Con_Print("FS_Inflate: buf error!\n"); break; default: Con_Print("FS_Inflate: unknown error!\n"); break; } if(ret != Z_OK && ret != Z_STREAM_END) { Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in); Mem_Free(outbuf.data); qz_inflateEnd(&strm); return NULL; } have = sizeof(tmp) - strm.avail_out; AssertBufsize(&outbuf, max(have, sizeof(tmp))); SZ_Write(&outbuf, tmp, have); } while(ret != Z_STREAM_END); qz_inflateEnd(&strm); out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize); if(!out) { Con_Printf("FS_Inflate: not enough memory in target mempool!\n"); Mem_Free(outbuf.data); return NULL; } memcpy(out, outbuf.data, outbuf.cursize); Mem_Free(outbuf.data); *inflated_size = (size_t)outbuf.cursize; return out; } darkplaces/cd_win.c0000664000175000017500000001474113067716216013606 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All // rights reserved. #include "quakedef.h" #include #include #include "cdaudio.h" #if defined(_MSC_VER) && (_MSC_VER < 1300) typedef DWORD DWORD_PTR; #endif extern HWND mainwindow; UINT wDeviceID; void CDAudio_SysEject(void) { DWORD dwReturn; if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, (DWORD_PTR)NULL))) Con_Printf("MCI_SET_DOOR_OPEN failed (%x)\n", (unsigned)dwReturn); } void CDAudio_SysCloseDoor(void) { DWORD dwReturn; if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, (DWORD_PTR)NULL))) Con_Printf("MCI_SET_DOOR_CLOSED failed (%x)\n", (unsigned)dwReturn); } int CDAudio_SysGetAudioDiskInfo(void) { DWORD dwReturn; MCI_STATUS_PARMS mciStatusParms; mciStatusParms.dwItem = MCI_STATUS_READY; dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); if (dwReturn) { Con_Print("CDAudio_SysGetAudioDiskInfo: drive ready test - get status failed\n"); return -1; } if (!mciStatusParms.dwReturn) { Con_Print("CDAudio_SysGetAudioDiskInfo: drive not ready\n"); return -1; } mciStatusParms.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); if (dwReturn) { Con_Print("CDAudio_SysGetAudioDiskInfo: get tracks - status failed\n"); return -1; } if (mciStatusParms.dwReturn < 1) { Con_Print("CDAudio_SysGetAudioDiskInfo: no music tracks\n"); return -1; } return mciStatusParms.dwReturn; } float CDAudio_SysGetVolume (void) { // IMPLEMENTME return -1.0f; } void CDAudio_SysSetVolume (float fvolume) { // IMPLEMENTME } int CDAudio_SysPlay (int track) { DWORD dwReturn; MCI_PLAY_PARMS mciPlayParms; MCI_STATUS_PARMS mciStatusParms; // don't try to play a non-audio track mciStatusParms.dwItem = MCI_CDA_STATUS_TYPE_TRACK; mciStatusParms.dwTrack = track; dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); if (dwReturn) { Con_Printf("CDAudio_SysPlay: MCI_STATUS failed (%x)\n", (unsigned)dwReturn); return -1; } if (mciStatusParms.dwReturn != MCI_CDA_TRACK_AUDIO) { Con_Printf("CDAudio_SysPlay: track %i is not audio\n", track); return -1; } if (cdPlaying) CDAudio_Stop(); // get the length of the track to be played mciStatusParms.dwItem = MCI_STATUS_LENGTH; mciStatusParms.dwTrack = track; dwReturn = mciSendCommand(wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK | MCI_WAIT, (DWORD_PTR) (LPVOID) &mciStatusParms); if (dwReturn) { Con_Printf("CDAudio_SysPlay: MCI_STATUS failed (%x)\n", (unsigned)dwReturn); return -1; } mciPlayParms.dwFrom = MCI_MAKE_TMSF(track, 0, 0, 0); mciPlayParms.dwTo = (mciStatusParms.dwReturn << 8) | track; mciPlayParms.dwCallback = (DWORD_PTR)mainwindow; dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_NOTIFY | MCI_FROM | MCI_TO, (DWORD_PTR)(LPVOID) &mciPlayParms); if (dwReturn) { Con_Printf("CDAudio_SysPlay: MCI_PLAY failed (%x)\n", (unsigned)dwReturn); return -1; } return 0; } int CDAudio_SysStop (void) { DWORD dwReturn; if ((dwReturn = mciSendCommand(wDeviceID, MCI_STOP, 0, (DWORD_PTR)NULL))) { Con_Printf("MCI_STOP failed (%x)\n", (unsigned)dwReturn); return -1; } return 0; } int CDAudio_SysPause (void) { DWORD dwReturn; MCI_GENERIC_PARMS mciGenericParms; mciGenericParms.dwCallback = (DWORD_PTR)mainwindow; if ((dwReturn = mciSendCommand(wDeviceID, MCI_PAUSE, 0, (DWORD_PTR)(LPVOID) &mciGenericParms))) { Con_Printf("MCI_PAUSE failed (%x)\n", (unsigned)dwReturn); return -1; } return 0; } int CDAudio_SysResume (void) { DWORD dwReturn; MCI_PLAY_PARMS mciPlayParms; mciPlayParms.dwFrom = MCI_MAKE_TMSF(cdPlayTrack, 0, 0, 0); mciPlayParms.dwTo = MCI_MAKE_TMSF(cdPlayTrack + 1, 0, 0, 0); mciPlayParms.dwCallback = (DWORD_PTR)mainwindow; dwReturn = mciSendCommand(wDeviceID, MCI_PLAY, MCI_TO | MCI_NOTIFY, (DWORD_PTR)(LPVOID) &mciPlayParms); if (dwReturn) { Con_Printf("CDAudio_SysResume: MCI_PLAY failed (%x)\n", (unsigned)dwReturn); return -1; } return 0; } LONG CDAudio_MessageHandler (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (lParam != (LPARAM)wDeviceID) return 1; switch (wParam) { case MCI_NOTIFY_SUCCESSFUL: if (cdPlaying) { cdPlaying = false; if (cdPlayLooping) CDAudio_Play(cdPlayTrack, true); } break; case MCI_NOTIFY_ABORTED: case MCI_NOTIFY_SUPERSEDED: break; case MCI_NOTIFY_FAILURE: Con_Print("MCI_NOTIFY_FAILURE\n"); CDAudio_Stop (); cdValid = false; break; default: Con_Printf("Unexpected MM_MCINOTIFY type (%i)\n", (int)wParam); return 1; } return 0; } int CDAudio_SysUpdate (void) { return 0; } void CDAudio_SysInit (void) { } int CDAudio_SysStartup (void) { DWORD dwReturn; MCI_OPEN_PARMS mciOpenParms; MCI_SET_PARMS mciSetParms; mciOpenParms.lpstrDeviceType = "cdaudio"; if ((dwReturn = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_SHAREABLE, (DWORD_PTR) (LPVOID) &mciOpenParms))) { Con_Printf("CDAudio_SysStartup: MCI_OPEN failed (%x)\n", (unsigned)dwReturn); return -1; } wDeviceID = mciOpenParms.wDeviceID; // Set the time format to track/minute/second/frame (TMSF). mciSetParms.dwTimeFormat = MCI_FORMAT_TMSF; if ((dwReturn = mciSendCommand(wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD_PTR)(LPVOID) &mciSetParms))) { Con_Printf("CDAudio_SysStartup: MCI_SET_TIME_FORMAT failed (%x)\n", (unsigned)dwReturn); mciSendCommand(wDeviceID, MCI_CLOSE, 0, (DWORD_PTR)NULL); return -1; } return 0; } void CDAudio_SysShutdown (void) { if (mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD_PTR)NULL)) Con_Print("CDAudio_SysShutdown: MCI_CLOSE failed\n"); } darkplaces/utf8lib.c0000664000175000017500000025407013067716222013716 0ustar kalevkalev#include "quakedef.h" #include "utf8lib.h" /* ================================================================================ Initialization of UTF-8 support and new cvars. ================================================================================ */ // for compatibility this defaults to 0 cvar_t utf8_enable = {CVAR_SAVE, "utf8_enable", "0", "Enable UTF-8 support. For compatibility, this is disabled by default in most games."}; void u8_Init(void) { Cvar_RegisterVariable(&utf8_enable); } /* ================================================================================ UTF-8 encoding and decoding functions follow. ================================================================================ */ unsigned char utf8_lengths[256] = { // 0 = invalid 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // ascii characters 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0xBF are within multibyte sequences 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // they could be interpreted as 2-byte starts but 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // the codepoint would be < 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0 and C1 would also result in overlong encodings 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // with F5 the codepoint is above 0x10FFFF, // F8-FB would start 5-byte sequences // FC-FD would start 6-byte sequences // ... }; Uchar utf8_range[5] = { 1, // invalid - let's not allow the creation of 0-bytes :P 1, // ascii minimum 0x80, // 2-byte minimum 0x800, // 3-byte minimum 0x10000, // 4-byte minimum }; /** Analyze the next character and return various information if requested. * @param _s An utf-8 string. * @param _start Filled with the start byte-offset of the next valid character * @param _len Fileed with the length of the next valid character * @param _ch Filled with the unicode value of the next character * @param _maxlen Maximum number of bytes to read from _s * @return Whether or not another valid character is in the string */ #define U8_ANALYZE_INFINITY 7 static qboolean u8_analyze(const char *_s, size_t *_start, size_t *_len, Uchar *_ch, size_t _maxlen) { const unsigned char *s = (const unsigned char*)_s; size_t i, j; size_t bits = 0; Uchar ch; i = 0; findchar: while (i < _maxlen && s[i] && (bits = utf8_lengths[s[i]]) == 0) ++i; if (i >= _maxlen || !s[i]) { if (_start) *_start = i; if (_len) *_len = 0; return false; } if (bits == 1) { // ascii if (_start) *_start = i; if (_len) *_len = 1; if (_ch) *_ch = (Uchar)s[i]; return true; } ch = (s[i] & (0xFF >> bits)); for (j = 1; j < bits; ++j) { if ( (s[i+j] & 0xC0) != 0x80 ) { i += j; goto findchar; } ch = (ch << 6) | (s[i+j] & 0x3F); } if (ch < utf8_range[bits] || ch >= 0x10FFFF) { i += bits; goto findchar; } #if 0 // <0xC2 is always an overlong encoding, they're invalid, thus skipped while (i < _maxlen && s[i] && s[i] >= 0x80 && s[i] < 0xC2) { //fprintf(stderr, "skipping\n"); ++i; } // If we hit the end, well, we're out and invalid if(i >= _maxlen || !s[i]) { if (_start) *_start = i; if (_len) *_len = 0; return false; } // I'll leave that in - if you remove it, also change the part below // to support 1-byte chars correctly if (s[i] < 0x80) { if (_start) *_start = i; if (_len) *_len = 1; if (_ch) *_ch = (Uchar)s[i]; //fprintf(stderr, "valid ascii\n"); return true; } // Figure out the next char's length bc = s[i]; bits = 1; // count the 1 bits, they're the # of bytes for (bt = 0x40; bt && (bc & bt); bt >>= 1, ++bits); if (!bt) { //fprintf(stderr, "superlong\n"); ++i; goto findchar; } if(i + bits > _maxlen) { /* if (_start) *_start = i; if (_len) *_len = 0; return false; */ ++i; goto findchar; } // turn bt into a mask and give ch a starting value --bt; ch = (s[i] & bt); // check the byte sequence for invalid bytes for (j = 1; j < bits; ++j) { // valid bit value: 10xx xxxx //if (s[i+j] < 0x80 || s[i+j] >= 0xC0) if ( (s[i+j] & 0xC0) != 0x80 ) { //fprintf(stderr, "sequence of %i f'd at %i by %x\n", bits, j, (unsigned int)s[i+j]); // this byte sequence is invalid, skip it i += j; // find a character after it goto findchar; } // at the same time, decode the character ch = (ch << 6) | (s[i+j] & 0x3F); } // Now check the decoded byte for an overlong encoding if ( (bits >= 2 && ch < 0x80) || (bits >= 3 && ch < 0x800) || (bits >= 4 && ch < 0x10000) || ch >= 0x10FFFF // RFC 3629 ) { i += bits; //fprintf(stderr, "overlong: %i bytes for %x\n", bits, ch); goto findchar; } #endif if (_start) *_start = i; if (_len) *_len = bits; if (_ch) *_ch = ch; //fprintf(stderr, "valid utf8\n"); return true; } /** Get the number of characters in an UTF-8 string. * @param _s An utf-8 encoded null-terminated string. * @return The number of unicode characters in the string. */ size_t u8_strlen(const char *_s) { size_t st, ln; size_t len = 0; const unsigned char *s = (const unsigned char*)_s; if (!utf8_enable.integer) return strlen(_s); while (*s) { // ascii char, skip u8_analyze if (*s < 0x80) { ++len; ++s; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) break; // valid character, skip after it s += st + ln; ++len; } return len; } static int colorcode_skipwidth(const unsigned char *s) { if(*s == STRING_COLOR_TAG) { if(s[1] <= '9' && s[1] >= '0') // ^[0-9] found { return 2; } else if(s[1] == STRING_COLOR_RGB_TAG_CHAR && ((s[2] >= '0' && s[2] <= '9') || (s[2] >= 'a' && s[2] <= 'f') || (s[2] >= 'A' && s[2] <= 'F')) && ((s[3] >= '0' && s[3] <= '9') || (s[3] >= 'a' && s[3] <= 'f') || (s[3] >= 'A' && s[3] <= 'F')) && ((s[4] >= '0' && s[4] <= '9') || (s[4] >= 'a' && s[4] <= 'f') || (s[4] >= 'A' && s[4] <= 'F'))) { return 5; } else if(s[1] == STRING_COLOR_TAG) { return 1; // special case, do NOT call colorcode_skipwidth for next char } } return 0; } /** Get the number of characters in a part of an UTF-8 string. * @param _s An utf-8 encoded null-terminated string. * @param n The maximum number of bytes. * @return The number of unicode characters in the string. */ size_t u8_strnlen(const char *_s, size_t n) { size_t st, ln; size_t len = 0; const unsigned char *s = (const unsigned char*)_s; if (!utf8_enable.integer) { len = strlen(_s); return (len < n) ? len : n; } while (*s && n) { // ascii char, skip u8_analyze if (*s < 0x80) { ++len; ++s; --n; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; --n; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, n)) break; // valid character, see if it's still inside the range specified by n: if (n < st + ln) return len; ++len; n -= st + ln; s += st + ln; } return len; } static size_t u8_strnlen_colorcodes(const char *_s, size_t n) { size_t st, ln; size_t len = 0; const unsigned char *s = (const unsigned char*)_s; while (*s && n) { int w = colorcode_skipwidth(s); n -= w; s += w; if(w > 1) // == 1 means single caret continue; // ascii char, skip u8_analyze if (*s < 0x80 || !utf8_enable.integer) { ++len; ++s; --n; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; --n; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, n)) break; // valid character, see if it's still inside the range specified by n: if (n < st + ln) return len; ++len; n -= st + ln; s += st + ln; } return len; } /** Get the number of bytes used in a string to represent an amount of characters. * @param _s An utf-8 encoded null-terminated string. * @param n The number of characters we want to know the byte-size for. * @return The number of bytes used to represent n characters. */ size_t u8_bytelen(const char *_s, size_t n) { size_t st, ln; size_t len = 0; const unsigned char *s = (const unsigned char*)_s; if (!utf8_enable.integer) { len = strlen(_s); return (len < n) ? len : n; } while (*s && n) { // ascii char, skip u8_analyze if (*s < 0x80) { ++len; ++s; --n; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; ++len; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) break; --n; s += st + ln; len += st + ln; } return len; } static size_t u8_bytelen_colorcodes(const char *_s, size_t n) { size_t st, ln; size_t len = 0; const unsigned char *s = (const unsigned char*)_s; while (*s && n) { int w = colorcode_skipwidth(s); len += w; s += w; if(w > 1) // == 1 means single caret continue; // ascii char, skip u8_analyze if (*s < 0x80 || !utf8_enable.integer) { ++len; ++s; --n; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; ++len; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) break; --n; s += st + ln; len += st + ln; } return len; } /** Get the byte-index for a character-index. * @param _s An utf-8 encoded string. * @param i The character-index for which you want the byte offset. * @param len If not null, character's length will be stored in there. * @return The byte-index at which the character begins, or -1 if the string is too short. */ int u8_byteofs(const char *_s, size_t i, size_t *len) { size_t st, ln; size_t ofs = 0; const unsigned char *s = (const unsigned char*)_s; if (!utf8_enable.integer) { if (strlen(_s) < i) { if (len) *len = 0; return -1; } if (len) *len = 1; return (int)i; } st = ln = 0; do { ofs += ln; if (!u8_analyze((const char*)s + ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) return -1; ofs += st; } while(i-- > 0); if (len) *len = ln; return (int)ofs; } /** Get the char-index for a byte-index. * @param _s An utf-8 encoded string. * @param i The byte offset for which you want the character index. * @param len If not null, the offset within the character is stored here. * @return The character-index, or -1 if the string is too short. */ int u8_charidx(const char *_s, size_t i, size_t *len) { size_t st, ln; size_t ofs = 0; size_t pofs = 0; int idx = 0; const unsigned char *s = (const unsigned char*)_s; if (!utf8_enable.integer) { if (len) *len = 0; return (int)i; } while (ofs < i && s[ofs]) { // ascii character, skip u8_analyze if (s[ofs] < 0x80) { pofs = ofs; ++idx; ++ofs; continue; } // invalid, skip u8_analyze if (s[ofs] < 0xC2) { ++ofs; continue; } if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) return -1; // see if next char is after the bytemark if (ofs + st > i) { if (len) *len = i - pofs; return idx; } ++idx; pofs = ofs + st; ofs += st + ln; // see if bytemark is within the char if (ofs > i) { if (len) *len = i - pofs; return idx; } } if (len) *len = 0; return idx; } /** Get the byte offset of the previous byte. * The result equals: * prevchar_pos = u8_byteofs(text, u8_charidx(text, thischar_pos, NULL) - 1, NULL) * @param _s An utf-8 encoded string. * @param i The current byte offset. * @return The byte offset of the previous character */ size_t u8_prevbyte(const char *_s, size_t i) { size_t st, ln; const unsigned char *s = (const unsigned char*)_s; size_t lastofs = 0; size_t ofs = 0; if (!utf8_enable.integer) { if (i > 0) return i-1; return 0; } while (ofs < i && s[ofs]) { // ascii character, skip u8_analyze if (s[ofs] < 0x80) { lastofs = ofs++; continue; } // invalid, skip u8_analyze if (s[ofs] < 0xC2) { ++ofs; continue; } if (!u8_analyze((const char*)s+ofs, &st, &ln, NULL, U8_ANALYZE_INFINITY)) return lastofs; if (ofs + st > i) return lastofs; if (ofs + st + ln >= i) return ofs + st; lastofs = ofs; ofs += st + ln; } return lastofs; } Uchar u8_quake2utf8map[256] = { 0xE000, 0xE001, 0xE002, 0xE003, 0xE004, 0xE005, 0xE006, 0xE007, 0xE008, 0xE009, 0xE00A, 0xE00B, 0xE00C, 0xE00D, 0xE00E, 0xE00F, // specials 0xE010, 0xE011, 0xE012, 0xE013, 0xE014, 0xE015, 0xE016, 0xE017, 0xE018, 0xE019, 0xE01A, 0xE01B, 0xE01C, 0xE01D, 0xE01E, 0xE01F, // specials 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, // shift+digit line 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, // digits 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, // caps 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, // caps 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, // small 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, // small 0xE080, 0xE081, 0xE082, 0xE083, 0xE084, 0xE085, 0xE086, 0xE087, 0xE088, 0xE089, 0xE08A, 0xE08B, 0xE08C, 0xE08D, 0xE08E, 0xE08F, // specials 0xE090, 0xE091, 0xE092, 0xE093, 0xE094, 0xE095, 0xE096, 0xE097, 0xE098, 0xE099, 0xE09A, 0xE09B, 0xE09C, 0xE09D, 0xE09E, 0xE09F, // faces 0xE0A0, 0xE0A1, 0xE0A2, 0xE0A3, 0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF, 0xE0B0, 0xE0B1, 0xE0B2, 0xE0B3, 0xE0B4, 0xE0B5, 0xE0B6, 0xE0B7, 0xE0B8, 0xE0B9, 0xE0BA, 0xE0BB, 0xE0BC, 0xE0BD, 0xE0BE, 0xE0BF, 0xE0C0, 0xE0C1, 0xE0C2, 0xE0C3, 0xE0C4, 0xE0C5, 0xE0C6, 0xE0C7, 0xE0C8, 0xE0C9, 0xE0CA, 0xE0CB, 0xE0CC, 0xE0CD, 0xE0CE, 0xE0CF, 0xE0D0, 0xE0D1, 0xE0D2, 0xE0D3, 0xE0D4, 0xE0D5, 0xE0D6, 0xE0D7, 0xE0D8, 0xE0D9, 0xE0DA, 0xE0DB, 0xE0DC, 0xE0DD, 0xE0DE, 0xE0DF, 0xE0E0, 0xE0E1, 0xE0E2, 0xE0E3, 0xE0E4, 0xE0E5, 0xE0E6, 0xE0E7, 0xE0E8, 0xE0E9, 0xE0EA, 0xE0EB, 0xE0EC, 0xE0ED, 0xE0EE, 0xE0EF, 0xE0F0, 0xE0F1, 0xE0F2, 0xE0F3, 0xE0F4, 0xE0F5, 0xE0F6, 0xE0F7, 0xE0F8, 0xE0F9, 0xE0FA, 0xE0FB, 0xE0FC, 0xE0FD, 0xE0FE, 0xE0FF, }; /** Fetch a character from an utf-8 encoded string. * @param _s The start of an utf-8 encoded multi-byte character. * @param _end Will point to after the first multi-byte character. * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. */ Uchar u8_getchar_utf8_enabled(const char *_s, const char **_end) { size_t st, ln; Uchar ch; if (!u8_analyze(_s, &st, &ln, &ch, U8_ANALYZE_INFINITY)) ch = 0; if (_end) *_end = _s + st + ln; return ch; } /** Fetch a character from an utf-8 encoded string. * @param _s The start of an utf-8 encoded multi-byte character. * @param _end Will point to after the first multi-byte character. * @return The 32-bit integer representation of the first multi-byte character or 0 for invalid characters. */ Uchar u8_getnchar_utf8_enabled(const char *_s, const char **_end, size_t _maxlen) { size_t st, ln; Uchar ch; if (!u8_analyze(_s, &st, &ln, &ch, _maxlen)) ch = 0; if (_end) *_end = _s + st + ln; return ch; } /** Encode a wide-character into utf-8. * @param w The wide character to encode. * @param to The target buffer the utf-8 encoded string is stored to. * @param maxlen The maximum number of bytes that fit into the target buffer. * @return Number of bytes written to the buffer not including the terminating null. * Less or equal to 0 if the buffer is too small. */ int u8_fromchar(Uchar w, char *to, size_t maxlen) { if (maxlen < 1) return 0; if (!w) return 0; if (w >= 0xE000 && !utf8_enable.integer) w -= 0xE000; if (w < 0x80 || !utf8_enable.integer) { to[0] = (char)w; if (maxlen < 2) return -1; to[1] = 0; return 1; } // for a little speedup if (w < 0x800) { if (maxlen < 3) { to[0] = 0; return -1; } to[2] = 0; to[1] = 0x80 | (w & 0x3F); w >>= 6; to[0] = 0xC0 | w; return 2; } if (w < 0x10000) { if (maxlen < 4) { to[0] = 0; return -1; } to[3] = 0; to[2] = 0x80 | (w & 0x3F); w >>= 6; to[1] = 0x80 | (w & 0x3F); w >>= 6; to[0] = 0xE0 | w; return 3; } // RFC 3629 if (w <= 0x10FFFF) { if (maxlen < 5) { to[0] = 0; return -1; } to[4] = 0; to[3] = 0x80 | (w & 0x3F); w >>= 6; to[2] = 0x80 | (w & 0x3F); w >>= 6; to[1] = 0x80 | (w & 0x3F); w >>= 6; to[0] = 0xF0 | w; return 4; } return 0; } /** uses u8_fromchar on a static buffer * @param ch The unicode character to convert to encode * @param l The number of bytes without the terminating null. * @return A statically allocated buffer containing the character's utf8 representation, or NULL if it fails. */ char *u8_encodech(Uchar ch, size_t *l, char *buf16) { size_t len; len = u8_fromchar(ch, buf16, 16); if (len > 0) { if (l) *l = len; return buf16; } return NULL; } /** Convert a utf-8 multibyte string to a wide character string. * @param wcs The target wide-character buffer. * @param mb The utf-8 encoded multibyte string to convert. * @param maxlen The maximum number of wide-characters that fit into the target buffer. * @return The number of characters written to the target buffer. */ size_t u8_mbstowcs(Uchar *wcs, const char *mb, size_t maxlen) { size_t i; Uchar ch; if (maxlen < 1) return 0; for (i = 0; *mb && i < maxlen-1; ++i) { ch = u8_getchar(mb, &mb); if (!ch) break; wcs[i] = ch; } wcs[i] = 0; return i; } /** Convert a wide-character string to a utf-8 multibyte string. * @param mb The target buffer the utf-8 string is written to. * @param wcs The wide-character string to convert. * @param maxlen The number bytes that fit into the multibyte target buffer. * @return The number of bytes written, not including the terminating \0 */ size_t u8_wcstombs(char *mb, const Uchar *wcs, size_t maxlen) { size_t i; const char *start = mb; if (maxlen < 2) return 0; for (i = 0; wcs[i] && i < maxlen-1; ++i) { /* int len; if ( (len = u8_fromchar(wcs[i], mb, maxlen - i)) < 0) return (mb - start); mb += len; */ mb += u8_fromchar(wcs[i], mb, maxlen - i); } *mb = 0; return (mb - start); } /* ============ UTF-8 aware COM_StringLengthNoColors calculates the visible width of a color coded string. *valid is filled with TRUE if the string is a valid colored string (that is, if it does not end with an unfinished color code). If it gets filled with FALSE, a fix would be adding a STRING_COLOR_TAG at the end of the string. valid can be set to NULL if the caller doesn't care. For size_s, specify the maximum number of characters from s to use, or 0 to use all characters until the zero terminator. ============ */ size_t COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); size_t u8_COM_StringLengthNoColors(const char *_s, size_t size_s, qboolean *valid) { const unsigned char *s = (const unsigned char*)_s; const unsigned char *end; size_t len = 0; size_t st, ln; if (!utf8_enable.integer) return COM_StringLengthNoColors(_s, size_s, valid); end = size_s ? (s + size_s) : NULL; for(;;) { switch((s == end) ? 0 : *s) { case 0: if(valid) *valid = TRUE; return len; case STRING_COLOR_TAG: ++s; switch((s == end) ? 0 : *s) { case STRING_COLOR_RGB_TAG_CHAR: if (s+1 != end && isxdigit(s[1]) && s+2 != end && isxdigit(s[2]) && s+3 != end && isxdigit(s[3]) ) { s+=3; break; } ++len; // STRING_COLOR_TAG ++len; // STRING_COLOR_RGB_TAG_CHAR break; case 0: // ends with unfinished color code! ++len; if(valid) *valid = FALSE; return len; case STRING_COLOR_TAG: // escaped ^ ++len; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // color code break; default: // not a color code ++len; // STRING_COLOR_TAG ++len; // the character break; } ++s; continue; default: break; } // ascii char, skip u8_analyze if (*s < 0x80) { ++len; ++s; continue; } // invalid, skip u8_analyze if (*s < 0xC2) { ++s; continue; } if (!u8_analyze((const char*)s, &st, &ln, NULL, U8_ANALYZE_INFINITY)) { // we CAN end up here, if an invalid char is between this one and the end of the string if(valid) *valid = TRUE; return len; } if(end && s + st + ln > end) { // string length exceeded by new character if(valid) *valid = TRUE; return len; } // valid character, skip after it s += st + ln; ++len; } // never get here } /** Pads a utf-8 string * @param out The target buffer the utf-8 string is written to. * @param outsize The size of the target buffer, including the final NUL * @param in The input utf-8 buffer * @param leftalign Left align the output string (by default right alignment is done) * @param minwidth The minimum output width * @param maxwidth The maximum output width * @return The number of bytes written, not including the terminating \0 */ size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth) { if(!utf8_enable.integer) { return dpsnprintf(out, outsize, "%*.*s", leftalign ? -(int) minwidth : (int) minwidth, (int) maxwidth, in); } else { size_t l = u8_bytelen(in, maxwidth); size_t actual_width = u8_strnlen(in, l); int pad = (int)((actual_width >= minwidth) ? 0 : (minwidth - actual_width)); int prec = (int)l; int lpad = leftalign ? 0 : pad; int rpad = leftalign ? pad : 0; return dpsnprintf(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); } } size_t u8_strpad_colorcodes(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth) { size_t l = u8_bytelen_colorcodes(in, maxwidth); size_t actual_width = u8_strnlen_colorcodes(in, l); int pad = (int)((actual_width >= minwidth) ? 0 : (minwidth - actual_width)); int prec = (int)l; int lpad = leftalign ? 0 : pad; int rpad = leftalign ? pad : 0; return dpsnprintf(out, outsize, "%*s%.*s%*s", lpad, "", prec, in, rpad, ""); } /* The two following functions (u8_toupper, u8_tolower) are derived from ftp://ftp.unicode.org/Public/UNIDATA/UnicodeData.txt and the following license holds for these: Copyright © 1991-2011 Unicode, Inc. All rights reserved. Distributed under the Terms of Use in http://www.unicode.org/copyright.html. Permission is hereby granted, free of charge, to any person obtaining a copy of the Unicode data files and any associated documentation (the "Data Files") or Unicode software and any associated documentation (the "Software") to deal in the Data Files or Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Data Files or Software, and to permit persons to whom the Data Files or Software are furnished to do so, provided that (a) the above copyright notice(s) and this permission notice appear with all copies of the Data Files or Software, (b) both the above copyright notice(s) and this permission notice appear in associated documentation, and (c) there is clear notice in each modified Data File or in the Software as well as in the documentation associated with the Data File(s) or Software that the data or software has been modified. THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA FILES OR SOFTWARE. Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. */ Uchar u8_toupper(Uchar ch) { switch(ch) { case 0x0061: return 0x0041; case 0x0062: return 0x0042; case 0x0063: return 0x0043; case 0x0064: return 0x0044; case 0x0065: return 0x0045; case 0x0066: return 0x0046; case 0x0067: return 0x0047; case 0x0068: return 0x0048; case 0x0069: return 0x0049; case 0x006A: return 0x004A; case 0x006B: return 0x004B; case 0x006C: return 0x004C; case 0x006D: return 0x004D; case 0x006E: return 0x004E; case 0x006F: return 0x004F; case 0x0070: return 0x0050; case 0x0071: return 0x0051; case 0x0072: return 0x0052; case 0x0073: return 0x0053; case 0x0074: return 0x0054; case 0x0075: return 0x0055; case 0x0076: return 0x0056; case 0x0077: return 0x0057; case 0x0078: return 0x0058; case 0x0079: return 0x0059; case 0x007A: return 0x005A; case 0x00B5: return 0x039C; case 0x00E0: return 0x00C0; case 0x00E1: return 0x00C1; case 0x00E2: return 0x00C2; case 0x00E3: return 0x00C3; case 0x00E4: return 0x00C4; case 0x00E5: return 0x00C5; case 0x00E6: return 0x00C6; case 0x00E7: return 0x00C7; case 0x00E8: return 0x00C8; case 0x00E9: return 0x00C9; case 0x00EA: return 0x00CA; case 0x00EB: return 0x00CB; case 0x00EC: return 0x00CC; case 0x00ED: return 0x00CD; case 0x00EE: return 0x00CE; case 0x00EF: return 0x00CF; case 0x00F0: return 0x00D0; case 0x00F1: return 0x00D1; case 0x00F2: return 0x00D2; case 0x00F3: return 0x00D3; case 0x00F4: return 0x00D4; case 0x00F5: return 0x00D5; case 0x00F6: return 0x00D6; case 0x00F8: return 0x00D8; case 0x00F9: return 0x00D9; case 0x00FA: return 0x00DA; case 0x00FB: return 0x00DB; case 0x00FC: return 0x00DC; case 0x00FD: return 0x00DD; case 0x00FE: return 0x00DE; case 0x00FF: return 0x0178; case 0x0101: return 0x0100; case 0x0103: return 0x0102; case 0x0105: return 0x0104; case 0x0107: return 0x0106; case 0x0109: return 0x0108; case 0x010B: return 0x010A; case 0x010D: return 0x010C; case 0x010F: return 0x010E; case 0x0111: return 0x0110; case 0x0113: return 0x0112; case 0x0115: return 0x0114; case 0x0117: return 0x0116; case 0x0119: return 0x0118; case 0x011B: return 0x011A; case 0x011D: return 0x011C; case 0x011F: return 0x011E; case 0x0121: return 0x0120; case 0x0123: return 0x0122; case 0x0125: return 0x0124; case 0x0127: return 0x0126; case 0x0129: return 0x0128; case 0x012B: return 0x012A; case 0x012D: return 0x012C; case 0x012F: return 0x012E; case 0x0131: return 0x0049; case 0x0133: return 0x0132; case 0x0135: return 0x0134; case 0x0137: return 0x0136; case 0x013A: return 0x0139; case 0x013C: return 0x013B; case 0x013E: return 0x013D; case 0x0140: return 0x013F; case 0x0142: return 0x0141; case 0x0144: return 0x0143; case 0x0146: return 0x0145; case 0x0148: return 0x0147; case 0x014B: return 0x014A; case 0x014D: return 0x014C; case 0x014F: return 0x014E; case 0x0151: return 0x0150; case 0x0153: return 0x0152; case 0x0155: return 0x0154; case 0x0157: return 0x0156; case 0x0159: return 0x0158; case 0x015B: return 0x015A; case 0x015D: return 0x015C; case 0x015F: return 0x015E; case 0x0161: return 0x0160; case 0x0163: return 0x0162; case 0x0165: return 0x0164; case 0x0167: return 0x0166; case 0x0169: return 0x0168; case 0x016B: return 0x016A; case 0x016D: return 0x016C; case 0x016F: return 0x016E; case 0x0171: return 0x0170; case 0x0173: return 0x0172; case 0x0175: return 0x0174; case 0x0177: return 0x0176; case 0x017A: return 0x0179; case 0x017C: return 0x017B; case 0x017E: return 0x017D; case 0x017F: return 0x0053; case 0x0180: return 0x0243; case 0x0183: return 0x0182; case 0x0185: return 0x0184; case 0x0188: return 0x0187; case 0x018C: return 0x018B; case 0x0192: return 0x0191; case 0x0195: return 0x01F6; case 0x0199: return 0x0198; case 0x019A: return 0x023D; case 0x019E: return 0x0220; case 0x01A1: return 0x01A0; case 0x01A3: return 0x01A2; case 0x01A5: return 0x01A4; case 0x01A8: return 0x01A7; case 0x01AD: return 0x01AC; case 0x01B0: return 0x01AF; case 0x01B4: return 0x01B3; case 0x01B6: return 0x01B5; case 0x01B9: return 0x01B8; case 0x01BD: return 0x01BC; case 0x01BF: return 0x01F7; case 0x01C5: return 0x01C4; case 0x01C6: return 0x01C4; case 0x01C8: return 0x01C7; case 0x01C9: return 0x01C7; case 0x01CB: return 0x01CA; case 0x01CC: return 0x01CA; case 0x01CE: return 0x01CD; case 0x01D0: return 0x01CF; case 0x01D2: return 0x01D1; case 0x01D4: return 0x01D3; case 0x01D6: return 0x01D5; case 0x01D8: return 0x01D7; case 0x01DA: return 0x01D9; case 0x01DC: return 0x01DB; case 0x01DD: return 0x018E; case 0x01DF: return 0x01DE; case 0x01E1: return 0x01E0; case 0x01E3: return 0x01E2; case 0x01E5: return 0x01E4; case 0x01E7: return 0x01E6; case 0x01E9: return 0x01E8; case 0x01EB: return 0x01EA; case 0x01ED: return 0x01EC; case 0x01EF: return 0x01EE; case 0x01F2: return 0x01F1; case 0x01F3: return 0x01F1; case 0x01F5: return 0x01F4; case 0x01F9: return 0x01F8; case 0x01FB: return 0x01FA; case 0x01FD: return 0x01FC; case 0x01FF: return 0x01FE; case 0x0201: return 0x0200; case 0x0203: return 0x0202; case 0x0205: return 0x0204; case 0x0207: return 0x0206; case 0x0209: return 0x0208; case 0x020B: return 0x020A; case 0x020D: return 0x020C; case 0x020F: return 0x020E; case 0x0211: return 0x0210; case 0x0213: return 0x0212; case 0x0215: return 0x0214; case 0x0217: return 0x0216; case 0x0219: return 0x0218; case 0x021B: return 0x021A; case 0x021D: return 0x021C; case 0x021F: return 0x021E; case 0x0223: return 0x0222; case 0x0225: return 0x0224; case 0x0227: return 0x0226; case 0x0229: return 0x0228; case 0x022B: return 0x022A; case 0x022D: return 0x022C; case 0x022F: return 0x022E; case 0x0231: return 0x0230; case 0x0233: return 0x0232; case 0x023C: return 0x023B; case 0x023F: return 0x2C7E; case 0x0240: return 0x2C7F; case 0x0242: return 0x0241; case 0x0247: return 0x0246; case 0x0249: return 0x0248; case 0x024B: return 0x024A; case 0x024D: return 0x024C; case 0x024F: return 0x024E; case 0x0250: return 0x2C6F; case 0x0251: return 0x2C6D; case 0x0252: return 0x2C70; case 0x0253: return 0x0181; case 0x0254: return 0x0186; case 0x0256: return 0x0189; case 0x0257: return 0x018A; case 0x0259: return 0x018F; case 0x025B: return 0x0190; case 0x0260: return 0x0193; case 0x0263: return 0x0194; case 0x0265: return 0xA78D; case 0x0268: return 0x0197; case 0x0269: return 0x0196; case 0x026B: return 0x2C62; case 0x026F: return 0x019C; case 0x0271: return 0x2C6E; case 0x0272: return 0x019D; case 0x0275: return 0x019F; case 0x027D: return 0x2C64; case 0x0280: return 0x01A6; case 0x0283: return 0x01A9; case 0x0288: return 0x01AE; case 0x0289: return 0x0244; case 0x028A: return 0x01B1; case 0x028B: return 0x01B2; case 0x028C: return 0x0245; case 0x0292: return 0x01B7; case 0x0345: return 0x0399; case 0x0371: return 0x0370; case 0x0373: return 0x0372; case 0x0377: return 0x0376; case 0x037B: return 0x03FD; case 0x037C: return 0x03FE; case 0x037D: return 0x03FF; case 0x03AC: return 0x0386; case 0x03AD: return 0x0388; case 0x03AE: return 0x0389; case 0x03AF: return 0x038A; case 0x03B1: return 0x0391; case 0x03B2: return 0x0392; case 0x03B3: return 0x0393; case 0x03B4: return 0x0394; case 0x03B5: return 0x0395; case 0x03B6: return 0x0396; case 0x03B7: return 0x0397; case 0x03B8: return 0x0398; case 0x03B9: return 0x0399; case 0x03BA: return 0x039A; case 0x03BB: return 0x039B; case 0x03BC: return 0x039C; case 0x03BD: return 0x039D; case 0x03BE: return 0x039E; case 0x03BF: return 0x039F; case 0x03C0: return 0x03A0; case 0x03C1: return 0x03A1; case 0x03C2: return 0x03A3; case 0x03C3: return 0x03A3; case 0x03C4: return 0x03A4; case 0x03C5: return 0x03A5; case 0x03C6: return 0x03A6; case 0x03C7: return 0x03A7; case 0x03C8: return 0x03A8; case 0x03C9: return 0x03A9; case 0x03CA: return 0x03AA; case 0x03CB: return 0x03AB; case 0x03CC: return 0x038C; case 0x03CD: return 0x038E; case 0x03CE: return 0x038F; case 0x03D0: return 0x0392; case 0x03D1: return 0x0398; case 0x03D5: return 0x03A6; case 0x03D6: return 0x03A0; case 0x03D7: return 0x03CF; case 0x03D9: return 0x03D8; case 0x03DB: return 0x03DA; case 0x03DD: return 0x03DC; case 0x03DF: return 0x03DE; case 0x03E1: return 0x03E0; case 0x03E3: return 0x03E2; case 0x03E5: return 0x03E4; case 0x03E7: return 0x03E6; case 0x03E9: return 0x03E8; case 0x03EB: return 0x03EA; case 0x03ED: return 0x03EC; case 0x03EF: return 0x03EE; case 0x03F0: return 0x039A; case 0x03F1: return 0x03A1; case 0x03F2: return 0x03F9; case 0x03F5: return 0x0395; case 0x03F8: return 0x03F7; case 0x03FB: return 0x03FA; case 0x0430: return 0x0410; case 0x0431: return 0x0411; case 0x0432: return 0x0412; case 0x0433: return 0x0413; case 0x0434: return 0x0414; case 0x0435: return 0x0415; case 0x0436: return 0x0416; case 0x0437: return 0x0417; case 0x0438: return 0x0418; case 0x0439: return 0x0419; case 0x043A: return 0x041A; case 0x043B: return 0x041B; case 0x043C: return 0x041C; case 0x043D: return 0x041D; case 0x043E: return 0x041E; case 0x043F: return 0x041F; case 0x0440: return 0x0420; case 0x0441: return 0x0421; case 0x0442: return 0x0422; case 0x0443: return 0x0423; case 0x0444: return 0x0424; case 0x0445: return 0x0425; case 0x0446: return 0x0426; case 0x0447: return 0x0427; case 0x0448: return 0x0428; case 0x0449: return 0x0429; case 0x044A: return 0x042A; case 0x044B: return 0x042B; case 0x044C: return 0x042C; case 0x044D: return 0x042D; case 0x044E: return 0x042E; case 0x044F: return 0x042F; case 0x0450: return 0x0400; case 0x0451: return 0x0401; case 0x0452: return 0x0402; case 0x0453: return 0x0403; case 0x0454: return 0x0404; case 0x0455: return 0x0405; case 0x0456: return 0x0406; case 0x0457: return 0x0407; case 0x0458: return 0x0408; case 0x0459: return 0x0409; case 0x045A: return 0x040A; case 0x045B: return 0x040B; case 0x045C: return 0x040C; case 0x045D: return 0x040D; case 0x045E: return 0x040E; case 0x045F: return 0x040F; case 0x0461: return 0x0460; case 0x0463: return 0x0462; case 0x0465: return 0x0464; case 0x0467: return 0x0466; case 0x0469: return 0x0468; case 0x046B: return 0x046A; case 0x046D: return 0x046C; case 0x046F: return 0x046E; case 0x0471: return 0x0470; case 0x0473: return 0x0472; case 0x0475: return 0x0474; case 0x0477: return 0x0476; case 0x0479: return 0x0478; case 0x047B: return 0x047A; case 0x047D: return 0x047C; case 0x047F: return 0x047E; case 0x0481: return 0x0480; case 0x048B: return 0x048A; case 0x048D: return 0x048C; case 0x048F: return 0x048E; case 0x0491: return 0x0490; case 0x0493: return 0x0492; case 0x0495: return 0x0494; case 0x0497: return 0x0496; case 0x0499: return 0x0498; case 0x049B: return 0x049A; case 0x049D: return 0x049C; case 0x049F: return 0x049E; case 0x04A1: return 0x04A0; case 0x04A3: return 0x04A2; case 0x04A5: return 0x04A4; case 0x04A7: return 0x04A6; case 0x04A9: return 0x04A8; case 0x04AB: return 0x04AA; case 0x04AD: return 0x04AC; case 0x04AF: return 0x04AE; case 0x04B1: return 0x04B0; case 0x04B3: return 0x04B2; case 0x04B5: return 0x04B4; case 0x04B7: return 0x04B6; case 0x04B9: return 0x04B8; case 0x04BB: return 0x04BA; case 0x04BD: return 0x04BC; case 0x04BF: return 0x04BE; case 0x04C2: return 0x04C1; case 0x04C4: return 0x04C3; case 0x04C6: return 0x04C5; case 0x04C8: return 0x04C7; case 0x04CA: return 0x04C9; case 0x04CC: return 0x04CB; case 0x04CE: return 0x04CD; case 0x04CF: return 0x04C0; case 0x04D1: return 0x04D0; case 0x04D3: return 0x04D2; case 0x04D5: return 0x04D4; case 0x04D7: return 0x04D6; case 0x04D9: return 0x04D8; case 0x04DB: return 0x04DA; case 0x04DD: return 0x04DC; case 0x04DF: return 0x04DE; case 0x04E1: return 0x04E0; case 0x04E3: return 0x04E2; case 0x04E5: return 0x04E4; case 0x04E7: return 0x04E6; case 0x04E9: return 0x04E8; case 0x04EB: return 0x04EA; case 0x04ED: return 0x04EC; case 0x04EF: return 0x04EE; case 0x04F1: return 0x04F0; case 0x04F3: return 0x04F2; case 0x04F5: return 0x04F4; case 0x04F7: return 0x04F6; case 0x04F9: return 0x04F8; case 0x04FB: return 0x04FA; case 0x04FD: return 0x04FC; case 0x04FF: return 0x04FE; case 0x0501: return 0x0500; case 0x0503: return 0x0502; case 0x0505: return 0x0504; case 0x0507: return 0x0506; case 0x0509: return 0x0508; case 0x050B: return 0x050A; case 0x050D: return 0x050C; case 0x050F: return 0x050E; case 0x0511: return 0x0510; case 0x0513: return 0x0512; case 0x0515: return 0x0514; case 0x0517: return 0x0516; case 0x0519: return 0x0518; case 0x051B: return 0x051A; case 0x051D: return 0x051C; case 0x051F: return 0x051E; case 0x0521: return 0x0520; case 0x0523: return 0x0522; case 0x0525: return 0x0524; case 0x0527: return 0x0526; case 0x0561: return 0x0531; case 0x0562: return 0x0532; case 0x0563: return 0x0533; case 0x0564: return 0x0534; case 0x0565: return 0x0535; case 0x0566: return 0x0536; case 0x0567: return 0x0537; case 0x0568: return 0x0538; case 0x0569: return 0x0539; case 0x056A: return 0x053A; case 0x056B: return 0x053B; case 0x056C: return 0x053C; case 0x056D: return 0x053D; case 0x056E: return 0x053E; case 0x056F: return 0x053F; case 0x0570: return 0x0540; case 0x0571: return 0x0541; case 0x0572: return 0x0542; case 0x0573: return 0x0543; case 0x0574: return 0x0544; case 0x0575: return 0x0545; case 0x0576: return 0x0546; case 0x0577: return 0x0547; case 0x0578: return 0x0548; case 0x0579: return 0x0549; case 0x057A: return 0x054A; case 0x057B: return 0x054B; case 0x057C: return 0x054C; case 0x057D: return 0x054D; case 0x057E: return 0x054E; case 0x057F: return 0x054F; case 0x0580: return 0x0550; case 0x0581: return 0x0551; case 0x0582: return 0x0552; case 0x0583: return 0x0553; case 0x0584: return 0x0554; case 0x0585: return 0x0555; case 0x0586: return 0x0556; case 0x1D79: return 0xA77D; case 0x1D7D: return 0x2C63; case 0x1E01: return 0x1E00; case 0x1E03: return 0x1E02; case 0x1E05: return 0x1E04; case 0x1E07: return 0x1E06; case 0x1E09: return 0x1E08; case 0x1E0B: return 0x1E0A; case 0x1E0D: return 0x1E0C; case 0x1E0F: return 0x1E0E; case 0x1E11: return 0x1E10; case 0x1E13: return 0x1E12; case 0x1E15: return 0x1E14; case 0x1E17: return 0x1E16; case 0x1E19: return 0x1E18; case 0x1E1B: return 0x1E1A; case 0x1E1D: return 0x1E1C; case 0x1E1F: return 0x1E1E; case 0x1E21: return 0x1E20; case 0x1E23: return 0x1E22; case 0x1E25: return 0x1E24; case 0x1E27: return 0x1E26; case 0x1E29: return 0x1E28; case 0x1E2B: return 0x1E2A; case 0x1E2D: return 0x1E2C; case 0x1E2F: return 0x1E2E; case 0x1E31: return 0x1E30; case 0x1E33: return 0x1E32; case 0x1E35: return 0x1E34; case 0x1E37: return 0x1E36; case 0x1E39: return 0x1E38; case 0x1E3B: return 0x1E3A; case 0x1E3D: return 0x1E3C; case 0x1E3F: return 0x1E3E; case 0x1E41: return 0x1E40; case 0x1E43: return 0x1E42; case 0x1E45: return 0x1E44; case 0x1E47: return 0x1E46; case 0x1E49: return 0x1E48; case 0x1E4B: return 0x1E4A; case 0x1E4D: return 0x1E4C; case 0x1E4F: return 0x1E4E; case 0x1E51: return 0x1E50; case 0x1E53: return 0x1E52; case 0x1E55: return 0x1E54; case 0x1E57: return 0x1E56; case 0x1E59: return 0x1E58; case 0x1E5B: return 0x1E5A; case 0x1E5D: return 0x1E5C; case 0x1E5F: return 0x1E5E; case 0x1E61: return 0x1E60; case 0x1E63: return 0x1E62; case 0x1E65: return 0x1E64; case 0x1E67: return 0x1E66; case 0x1E69: return 0x1E68; case 0x1E6B: return 0x1E6A; case 0x1E6D: return 0x1E6C; case 0x1E6F: return 0x1E6E; case 0x1E71: return 0x1E70; case 0x1E73: return 0x1E72; case 0x1E75: return 0x1E74; case 0x1E77: return 0x1E76; case 0x1E79: return 0x1E78; case 0x1E7B: return 0x1E7A; case 0x1E7D: return 0x1E7C; case 0x1E7F: return 0x1E7E; case 0x1E81: return 0x1E80; case 0x1E83: return 0x1E82; case 0x1E85: return 0x1E84; case 0x1E87: return 0x1E86; case 0x1E89: return 0x1E88; case 0x1E8B: return 0x1E8A; case 0x1E8D: return 0x1E8C; case 0x1E8F: return 0x1E8E; case 0x1E91: return 0x1E90; case 0x1E93: return 0x1E92; case 0x1E95: return 0x1E94; case 0x1E9B: return 0x1E60; case 0x1EA1: return 0x1EA0; case 0x1EA3: return 0x1EA2; case 0x1EA5: return 0x1EA4; case 0x1EA7: return 0x1EA6; case 0x1EA9: return 0x1EA8; case 0x1EAB: return 0x1EAA; case 0x1EAD: return 0x1EAC; case 0x1EAF: return 0x1EAE; case 0x1EB1: return 0x1EB0; case 0x1EB3: return 0x1EB2; case 0x1EB5: return 0x1EB4; case 0x1EB7: return 0x1EB6; case 0x1EB9: return 0x1EB8; case 0x1EBB: return 0x1EBA; case 0x1EBD: return 0x1EBC; case 0x1EBF: return 0x1EBE; case 0x1EC1: return 0x1EC0; case 0x1EC3: return 0x1EC2; case 0x1EC5: return 0x1EC4; case 0x1EC7: return 0x1EC6; case 0x1EC9: return 0x1EC8; case 0x1ECB: return 0x1ECA; case 0x1ECD: return 0x1ECC; case 0x1ECF: return 0x1ECE; case 0x1ED1: return 0x1ED0; case 0x1ED3: return 0x1ED2; case 0x1ED5: return 0x1ED4; case 0x1ED7: return 0x1ED6; case 0x1ED9: return 0x1ED8; case 0x1EDB: return 0x1EDA; case 0x1EDD: return 0x1EDC; case 0x1EDF: return 0x1EDE; case 0x1EE1: return 0x1EE0; case 0x1EE3: return 0x1EE2; case 0x1EE5: return 0x1EE4; case 0x1EE7: return 0x1EE6; case 0x1EE9: return 0x1EE8; case 0x1EEB: return 0x1EEA; case 0x1EED: return 0x1EEC; case 0x1EEF: return 0x1EEE; case 0x1EF1: return 0x1EF0; case 0x1EF3: return 0x1EF2; case 0x1EF5: return 0x1EF4; case 0x1EF7: return 0x1EF6; case 0x1EF9: return 0x1EF8; case 0x1EFB: return 0x1EFA; case 0x1EFD: return 0x1EFC; case 0x1EFF: return 0x1EFE; case 0x1F00: return 0x1F08; case 0x1F01: return 0x1F09; case 0x1F02: return 0x1F0A; case 0x1F03: return 0x1F0B; case 0x1F04: return 0x1F0C; case 0x1F05: return 0x1F0D; case 0x1F06: return 0x1F0E; case 0x1F07: return 0x1F0F; case 0x1F10: return 0x1F18; case 0x1F11: return 0x1F19; case 0x1F12: return 0x1F1A; case 0x1F13: return 0x1F1B; case 0x1F14: return 0x1F1C; case 0x1F15: return 0x1F1D; case 0x1F20: return 0x1F28; case 0x1F21: return 0x1F29; case 0x1F22: return 0x1F2A; case 0x1F23: return 0x1F2B; case 0x1F24: return 0x1F2C; case 0x1F25: return 0x1F2D; case 0x1F26: return 0x1F2E; case 0x1F27: return 0x1F2F; case 0x1F30: return 0x1F38; case 0x1F31: return 0x1F39; case 0x1F32: return 0x1F3A; case 0x1F33: return 0x1F3B; case 0x1F34: return 0x1F3C; case 0x1F35: return 0x1F3D; case 0x1F36: return 0x1F3E; case 0x1F37: return 0x1F3F; case 0x1F40: return 0x1F48; case 0x1F41: return 0x1F49; case 0x1F42: return 0x1F4A; case 0x1F43: return 0x1F4B; case 0x1F44: return 0x1F4C; case 0x1F45: return 0x1F4D; case 0x1F51: return 0x1F59; case 0x1F53: return 0x1F5B; case 0x1F55: return 0x1F5D; case 0x1F57: return 0x1F5F; case 0x1F60: return 0x1F68; case 0x1F61: return 0x1F69; case 0x1F62: return 0x1F6A; case 0x1F63: return 0x1F6B; case 0x1F64: return 0x1F6C; case 0x1F65: return 0x1F6D; case 0x1F66: return 0x1F6E; case 0x1F67: return 0x1F6F; case 0x1F70: return 0x1FBA; case 0x1F71: return 0x1FBB; case 0x1F72: return 0x1FC8; case 0x1F73: return 0x1FC9; case 0x1F74: return 0x1FCA; case 0x1F75: return 0x1FCB; case 0x1F76: return 0x1FDA; case 0x1F77: return 0x1FDB; case 0x1F78: return 0x1FF8; case 0x1F79: return 0x1FF9; case 0x1F7A: return 0x1FEA; case 0x1F7B: return 0x1FEB; case 0x1F7C: return 0x1FFA; case 0x1F7D: return 0x1FFB; case 0x1F80: return 0x1F88; case 0x1F81: return 0x1F89; case 0x1F82: return 0x1F8A; case 0x1F83: return 0x1F8B; case 0x1F84: return 0x1F8C; case 0x1F85: return 0x1F8D; case 0x1F86: return 0x1F8E; case 0x1F87: return 0x1F8F; case 0x1F90: return 0x1F98; case 0x1F91: return 0x1F99; case 0x1F92: return 0x1F9A; case 0x1F93: return 0x1F9B; case 0x1F94: return 0x1F9C; case 0x1F95: return 0x1F9D; case 0x1F96: return 0x1F9E; case 0x1F97: return 0x1F9F; case 0x1FA0: return 0x1FA8; case 0x1FA1: return 0x1FA9; case 0x1FA2: return 0x1FAA; case 0x1FA3: return 0x1FAB; case 0x1FA4: return 0x1FAC; case 0x1FA5: return 0x1FAD; case 0x1FA6: return 0x1FAE; case 0x1FA7: return 0x1FAF; case 0x1FB0: return 0x1FB8; case 0x1FB1: return 0x1FB9; case 0x1FB3: return 0x1FBC; case 0x1FBE: return 0x0399; case 0x1FC3: return 0x1FCC; case 0x1FD0: return 0x1FD8; case 0x1FD1: return 0x1FD9; case 0x1FE0: return 0x1FE8; case 0x1FE1: return 0x1FE9; case 0x1FE5: return 0x1FEC; case 0x1FF3: return 0x1FFC; case 0x214E: return 0x2132; case 0x2170: return 0x2160; case 0x2171: return 0x2161; case 0x2172: return 0x2162; case 0x2173: return 0x2163; case 0x2174: return 0x2164; case 0x2175: return 0x2165; case 0x2176: return 0x2166; case 0x2177: return 0x2167; case 0x2178: return 0x2168; case 0x2179: return 0x2169; case 0x217A: return 0x216A; case 0x217B: return 0x216B; case 0x217C: return 0x216C; case 0x217D: return 0x216D; case 0x217E: return 0x216E; case 0x217F: return 0x216F; case 0x2184: return 0x2183; case 0x24D0: return 0x24B6; case 0x24D1: return 0x24B7; case 0x24D2: return 0x24B8; case 0x24D3: return 0x24B9; case 0x24D4: return 0x24BA; case 0x24D5: return 0x24BB; case 0x24D6: return 0x24BC; case 0x24D7: return 0x24BD; case 0x24D8: return 0x24BE; case 0x24D9: return 0x24BF; case 0x24DA: return 0x24C0; case 0x24DB: return 0x24C1; case 0x24DC: return 0x24C2; case 0x24DD: return 0x24C3; case 0x24DE: return 0x24C4; case 0x24DF: return 0x24C5; case 0x24E0: return 0x24C6; case 0x24E1: return 0x24C7; case 0x24E2: return 0x24C8; case 0x24E3: return 0x24C9; case 0x24E4: return 0x24CA; case 0x24E5: return 0x24CB; case 0x24E6: return 0x24CC; case 0x24E7: return 0x24CD; case 0x24E8: return 0x24CE; case 0x24E9: return 0x24CF; case 0x2C30: return 0x2C00; case 0x2C31: return 0x2C01; case 0x2C32: return 0x2C02; case 0x2C33: return 0x2C03; case 0x2C34: return 0x2C04; case 0x2C35: return 0x2C05; case 0x2C36: return 0x2C06; case 0x2C37: return 0x2C07; case 0x2C38: return 0x2C08; case 0x2C39: return 0x2C09; case 0x2C3A: return 0x2C0A; case 0x2C3B: return 0x2C0B; case 0x2C3C: return 0x2C0C; case 0x2C3D: return 0x2C0D; case 0x2C3E: return 0x2C0E; case 0x2C3F: return 0x2C0F; case 0x2C40: return 0x2C10; case 0x2C41: return 0x2C11; case 0x2C42: return 0x2C12; case 0x2C43: return 0x2C13; case 0x2C44: return 0x2C14; case 0x2C45: return 0x2C15; case 0x2C46: return 0x2C16; case 0x2C47: return 0x2C17; case 0x2C48: return 0x2C18; case 0x2C49: return 0x2C19; case 0x2C4A: return 0x2C1A; case 0x2C4B: return 0x2C1B; case 0x2C4C: return 0x2C1C; case 0x2C4D: return 0x2C1D; case 0x2C4E: return 0x2C1E; case 0x2C4F: return 0x2C1F; case 0x2C50: return 0x2C20; case 0x2C51: return 0x2C21; case 0x2C52: return 0x2C22; case 0x2C53: return 0x2C23; case 0x2C54: return 0x2C24; case 0x2C55: return 0x2C25; case 0x2C56: return 0x2C26; case 0x2C57: return 0x2C27; case 0x2C58: return 0x2C28; case 0x2C59: return 0x2C29; case 0x2C5A: return 0x2C2A; case 0x2C5B: return 0x2C2B; case 0x2C5C: return 0x2C2C; case 0x2C5D: return 0x2C2D; case 0x2C5E: return 0x2C2E; case 0x2C61: return 0x2C60; case 0x2C65: return 0x023A; case 0x2C66: return 0x023E; case 0x2C68: return 0x2C67; case 0x2C6A: return 0x2C69; case 0x2C6C: return 0x2C6B; case 0x2C73: return 0x2C72; case 0x2C76: return 0x2C75; case 0x2C81: return 0x2C80; case 0x2C83: return 0x2C82; case 0x2C85: return 0x2C84; case 0x2C87: return 0x2C86; case 0x2C89: return 0x2C88; case 0x2C8B: return 0x2C8A; case 0x2C8D: return 0x2C8C; case 0x2C8F: return 0x2C8E; case 0x2C91: return 0x2C90; case 0x2C93: return 0x2C92; case 0x2C95: return 0x2C94; case 0x2C97: return 0x2C96; case 0x2C99: return 0x2C98; case 0x2C9B: return 0x2C9A; case 0x2C9D: return 0x2C9C; case 0x2C9F: return 0x2C9E; case 0x2CA1: return 0x2CA0; case 0x2CA3: return 0x2CA2; case 0x2CA5: return 0x2CA4; case 0x2CA7: return 0x2CA6; case 0x2CA9: return 0x2CA8; case 0x2CAB: return 0x2CAA; case 0x2CAD: return 0x2CAC; case 0x2CAF: return 0x2CAE; case 0x2CB1: return 0x2CB0; case 0x2CB3: return 0x2CB2; case 0x2CB5: return 0x2CB4; case 0x2CB7: return 0x2CB6; case 0x2CB9: return 0x2CB8; case 0x2CBB: return 0x2CBA; case 0x2CBD: return 0x2CBC; case 0x2CBF: return 0x2CBE; case 0x2CC1: return 0x2CC0; case 0x2CC3: return 0x2CC2; case 0x2CC5: return 0x2CC4; case 0x2CC7: return 0x2CC6; case 0x2CC9: return 0x2CC8; case 0x2CCB: return 0x2CCA; case 0x2CCD: return 0x2CCC; case 0x2CCF: return 0x2CCE; case 0x2CD1: return 0x2CD0; case 0x2CD3: return 0x2CD2; case 0x2CD5: return 0x2CD4; case 0x2CD7: return 0x2CD6; case 0x2CD9: return 0x2CD8; case 0x2CDB: return 0x2CDA; case 0x2CDD: return 0x2CDC; case 0x2CDF: return 0x2CDE; case 0x2CE1: return 0x2CE0; case 0x2CE3: return 0x2CE2; case 0x2CEC: return 0x2CEB; case 0x2CEE: return 0x2CED; case 0x2D00: return 0x10A0; case 0x2D01: return 0x10A1; case 0x2D02: return 0x10A2; case 0x2D03: return 0x10A3; case 0x2D04: return 0x10A4; case 0x2D05: return 0x10A5; case 0x2D06: return 0x10A6; case 0x2D07: return 0x10A7; case 0x2D08: return 0x10A8; case 0x2D09: return 0x10A9; case 0x2D0A: return 0x10AA; case 0x2D0B: return 0x10AB; case 0x2D0C: return 0x10AC; case 0x2D0D: return 0x10AD; case 0x2D0E: return 0x10AE; case 0x2D0F: return 0x10AF; case 0x2D10: return 0x10B0; case 0x2D11: return 0x10B1; case 0x2D12: return 0x10B2; case 0x2D13: return 0x10B3; case 0x2D14: return 0x10B4; case 0x2D15: return 0x10B5; case 0x2D16: return 0x10B6; case 0x2D17: return 0x10B7; case 0x2D18: return 0x10B8; case 0x2D19: return 0x10B9; case 0x2D1A: return 0x10BA; case 0x2D1B: return 0x10BB; case 0x2D1C: return 0x10BC; case 0x2D1D: return 0x10BD; case 0x2D1E: return 0x10BE; case 0x2D1F: return 0x10BF; case 0x2D20: return 0x10C0; case 0x2D21: return 0x10C1; case 0x2D22: return 0x10C2; case 0x2D23: return 0x10C3; case 0x2D24: return 0x10C4; case 0x2D25: return 0x10C5; case 0xA641: return 0xA640; case 0xA643: return 0xA642; case 0xA645: return 0xA644; case 0xA647: return 0xA646; case 0xA649: return 0xA648; case 0xA64B: return 0xA64A; case 0xA64D: return 0xA64C; case 0xA64F: return 0xA64E; case 0xA651: return 0xA650; case 0xA653: return 0xA652; case 0xA655: return 0xA654; case 0xA657: return 0xA656; case 0xA659: return 0xA658; case 0xA65B: return 0xA65A; case 0xA65D: return 0xA65C; case 0xA65F: return 0xA65E; case 0xA661: return 0xA660; case 0xA663: return 0xA662; case 0xA665: return 0xA664; case 0xA667: return 0xA666; case 0xA669: return 0xA668; case 0xA66B: return 0xA66A; case 0xA66D: return 0xA66C; case 0xA681: return 0xA680; case 0xA683: return 0xA682; case 0xA685: return 0xA684; case 0xA687: return 0xA686; case 0xA689: return 0xA688; case 0xA68B: return 0xA68A; case 0xA68D: return 0xA68C; case 0xA68F: return 0xA68E; case 0xA691: return 0xA690; case 0xA693: return 0xA692; case 0xA695: return 0xA694; case 0xA697: return 0xA696; case 0xA723: return 0xA722; case 0xA725: return 0xA724; case 0xA727: return 0xA726; case 0xA729: return 0xA728; case 0xA72B: return 0xA72A; case 0xA72D: return 0xA72C; case 0xA72F: return 0xA72E; case 0xA733: return 0xA732; case 0xA735: return 0xA734; case 0xA737: return 0xA736; case 0xA739: return 0xA738; case 0xA73B: return 0xA73A; case 0xA73D: return 0xA73C; case 0xA73F: return 0xA73E; case 0xA741: return 0xA740; case 0xA743: return 0xA742; case 0xA745: return 0xA744; case 0xA747: return 0xA746; case 0xA749: return 0xA748; case 0xA74B: return 0xA74A; case 0xA74D: return 0xA74C; case 0xA74F: return 0xA74E; case 0xA751: return 0xA750; case 0xA753: return 0xA752; case 0xA755: return 0xA754; case 0xA757: return 0xA756; case 0xA759: return 0xA758; case 0xA75B: return 0xA75A; case 0xA75D: return 0xA75C; case 0xA75F: return 0xA75E; case 0xA761: return 0xA760; case 0xA763: return 0xA762; case 0xA765: return 0xA764; case 0xA767: return 0xA766; case 0xA769: return 0xA768; case 0xA76B: return 0xA76A; case 0xA76D: return 0xA76C; case 0xA76F: return 0xA76E; case 0xA77A: return 0xA779; case 0xA77C: return 0xA77B; case 0xA77F: return 0xA77E; case 0xA781: return 0xA780; case 0xA783: return 0xA782; case 0xA785: return 0xA784; case 0xA787: return 0xA786; case 0xA78C: return 0xA78B; case 0xA791: return 0xA790; case 0xA7A1: return 0xA7A0; case 0xA7A3: return 0xA7A2; case 0xA7A5: return 0xA7A4; case 0xA7A7: return 0xA7A6; case 0xA7A9: return 0xA7A8; case 0xFF41: return 0xFF21; case 0xFF42: return 0xFF22; case 0xFF43: return 0xFF23; case 0xFF44: return 0xFF24; case 0xFF45: return 0xFF25; case 0xFF46: return 0xFF26; case 0xFF47: return 0xFF27; case 0xFF48: return 0xFF28; case 0xFF49: return 0xFF29; case 0xFF4A: return 0xFF2A; case 0xFF4B: return 0xFF2B; case 0xFF4C: return 0xFF2C; case 0xFF4D: return 0xFF2D; case 0xFF4E: return 0xFF2E; case 0xFF4F: return 0xFF2F; case 0xFF50: return 0xFF30; case 0xFF51: return 0xFF31; case 0xFF52: return 0xFF32; case 0xFF53: return 0xFF33; case 0xFF54: return 0xFF34; case 0xFF55: return 0xFF35; case 0xFF56: return 0xFF36; case 0xFF57: return 0xFF37; case 0xFF58: return 0xFF38; case 0xFF59: return 0xFF39; case 0xFF5A: return 0xFF3A; case 0x10428: return 0x10400; case 0x10429: return 0x10401; case 0x1042A: return 0x10402; case 0x1042B: return 0x10403; case 0x1042C: return 0x10404; case 0x1042D: return 0x10405; case 0x1042E: return 0x10406; case 0x1042F: return 0x10407; case 0x10430: return 0x10408; case 0x10431: return 0x10409; case 0x10432: return 0x1040A; case 0x10433: return 0x1040B; case 0x10434: return 0x1040C; case 0x10435: return 0x1040D; case 0x10436: return 0x1040E; case 0x10437: return 0x1040F; case 0x10438: return 0x10410; case 0x10439: return 0x10411; case 0x1043A: return 0x10412; case 0x1043B: return 0x10413; case 0x1043C: return 0x10414; case 0x1043D: return 0x10415; case 0x1043E: return 0x10416; case 0x1043F: return 0x10417; case 0x10440: return 0x10418; case 0x10441: return 0x10419; case 0x10442: return 0x1041A; case 0x10443: return 0x1041B; case 0x10444: return 0x1041C; case 0x10445: return 0x1041D; case 0x10446: return 0x1041E; case 0x10447: return 0x1041F; case 0x10448: return 0x10420; case 0x10449: return 0x10421; case 0x1044A: return 0x10422; case 0x1044B: return 0x10423; case 0x1044C: return 0x10424; case 0x1044D: return 0x10425; case 0x1044E: return 0x10426; case 0x1044F: return 0x10427; default: return ch; } } Uchar u8_tolower(Uchar ch) { switch(ch) { case 0x0041: return 0x0061; case 0x0042: return 0x0062; case 0x0043: return 0x0063; case 0x0044: return 0x0064; case 0x0045: return 0x0065; case 0x0046: return 0x0066; case 0x0047: return 0x0067; case 0x0048: return 0x0068; case 0x0049: return 0x0069; case 0x004A: return 0x006A; case 0x004B: return 0x006B; case 0x004C: return 0x006C; case 0x004D: return 0x006D; case 0x004E: return 0x006E; case 0x004F: return 0x006F; case 0x0050: return 0x0070; case 0x0051: return 0x0071; case 0x0052: return 0x0072; case 0x0053: return 0x0073; case 0x0054: return 0x0074; case 0x0055: return 0x0075; case 0x0056: return 0x0076; case 0x0057: return 0x0077; case 0x0058: return 0x0078; case 0x0059: return 0x0079; case 0x005A: return 0x007A; case 0x00C0: return 0x00E0; case 0x00C1: return 0x00E1; case 0x00C2: return 0x00E2; case 0x00C3: return 0x00E3; case 0x00C4: return 0x00E4; case 0x00C5: return 0x00E5; case 0x00C6: return 0x00E6; case 0x00C7: return 0x00E7; case 0x00C8: return 0x00E8; case 0x00C9: return 0x00E9; case 0x00CA: return 0x00EA; case 0x00CB: return 0x00EB; case 0x00CC: return 0x00EC; case 0x00CD: return 0x00ED; case 0x00CE: return 0x00EE; case 0x00CF: return 0x00EF; case 0x00D0: return 0x00F0; case 0x00D1: return 0x00F1; case 0x00D2: return 0x00F2; case 0x00D3: return 0x00F3; case 0x00D4: return 0x00F4; case 0x00D5: return 0x00F5; case 0x00D6: return 0x00F6; case 0x00D8: return 0x00F8; case 0x00D9: return 0x00F9; case 0x00DA: return 0x00FA; case 0x00DB: return 0x00FB; case 0x00DC: return 0x00FC; case 0x00DD: return 0x00FD; case 0x00DE: return 0x00FE; case 0x0100: return 0x0101; case 0x0102: return 0x0103; case 0x0104: return 0x0105; case 0x0106: return 0x0107; case 0x0108: return 0x0109; case 0x010A: return 0x010B; case 0x010C: return 0x010D; case 0x010E: return 0x010F; case 0x0110: return 0x0111; case 0x0112: return 0x0113; case 0x0114: return 0x0115; case 0x0116: return 0x0117; case 0x0118: return 0x0119; case 0x011A: return 0x011B; case 0x011C: return 0x011D; case 0x011E: return 0x011F; case 0x0120: return 0x0121; case 0x0122: return 0x0123; case 0x0124: return 0x0125; case 0x0126: return 0x0127; case 0x0128: return 0x0129; case 0x012A: return 0x012B; case 0x012C: return 0x012D; case 0x012E: return 0x012F; case 0x0130: return 0x0069; case 0x0132: return 0x0133; case 0x0134: return 0x0135; case 0x0136: return 0x0137; case 0x0139: return 0x013A; case 0x013B: return 0x013C; case 0x013D: return 0x013E; case 0x013F: return 0x0140; case 0x0141: return 0x0142; case 0x0143: return 0x0144; case 0x0145: return 0x0146; case 0x0147: return 0x0148; case 0x014A: return 0x014B; case 0x014C: return 0x014D; case 0x014E: return 0x014F; case 0x0150: return 0x0151; case 0x0152: return 0x0153; case 0x0154: return 0x0155; case 0x0156: return 0x0157; case 0x0158: return 0x0159; case 0x015A: return 0x015B; case 0x015C: return 0x015D; case 0x015E: return 0x015F; case 0x0160: return 0x0161; case 0x0162: return 0x0163; case 0x0164: return 0x0165; case 0x0166: return 0x0167; case 0x0168: return 0x0169; case 0x016A: return 0x016B; case 0x016C: return 0x016D; case 0x016E: return 0x016F; case 0x0170: return 0x0171; case 0x0172: return 0x0173; case 0x0174: return 0x0175; case 0x0176: return 0x0177; case 0x0178: return 0x00FF; case 0x0179: return 0x017A; case 0x017B: return 0x017C; case 0x017D: return 0x017E; case 0x0181: return 0x0253; case 0x0182: return 0x0183; case 0x0184: return 0x0185; case 0x0186: return 0x0254; case 0x0187: return 0x0188; case 0x0189: return 0x0256; case 0x018A: return 0x0257; case 0x018B: return 0x018C; case 0x018E: return 0x01DD; case 0x018F: return 0x0259; case 0x0190: return 0x025B; case 0x0191: return 0x0192; case 0x0193: return 0x0260; case 0x0194: return 0x0263; case 0x0196: return 0x0269; case 0x0197: return 0x0268; case 0x0198: return 0x0199; case 0x019C: return 0x026F; case 0x019D: return 0x0272; case 0x019F: return 0x0275; case 0x01A0: return 0x01A1; case 0x01A2: return 0x01A3; case 0x01A4: return 0x01A5; case 0x01A6: return 0x0280; case 0x01A7: return 0x01A8; case 0x01A9: return 0x0283; case 0x01AC: return 0x01AD; case 0x01AE: return 0x0288; case 0x01AF: return 0x01B0; case 0x01B1: return 0x028A; case 0x01B2: return 0x028B; case 0x01B3: return 0x01B4; case 0x01B5: return 0x01B6; case 0x01B7: return 0x0292; case 0x01B8: return 0x01B9; case 0x01BC: return 0x01BD; case 0x01C4: return 0x01C6; case 0x01C5: return 0x01C6; case 0x01C7: return 0x01C9; case 0x01C8: return 0x01C9; case 0x01CA: return 0x01CC; case 0x01CB: return 0x01CC; case 0x01CD: return 0x01CE; case 0x01CF: return 0x01D0; case 0x01D1: return 0x01D2; case 0x01D3: return 0x01D4; case 0x01D5: return 0x01D6; case 0x01D7: return 0x01D8; case 0x01D9: return 0x01DA; case 0x01DB: return 0x01DC; case 0x01DE: return 0x01DF; case 0x01E0: return 0x01E1; case 0x01E2: return 0x01E3; case 0x01E4: return 0x01E5; case 0x01E6: return 0x01E7; case 0x01E8: return 0x01E9; case 0x01EA: return 0x01EB; case 0x01EC: return 0x01ED; case 0x01EE: return 0x01EF; case 0x01F1: return 0x01F3; case 0x01F2: return 0x01F3; case 0x01F4: return 0x01F5; case 0x01F6: return 0x0195; case 0x01F7: return 0x01BF; case 0x01F8: return 0x01F9; case 0x01FA: return 0x01FB; case 0x01FC: return 0x01FD; case 0x01FE: return 0x01FF; case 0x0200: return 0x0201; case 0x0202: return 0x0203; case 0x0204: return 0x0205; case 0x0206: return 0x0207; case 0x0208: return 0x0209; case 0x020A: return 0x020B; case 0x020C: return 0x020D; case 0x020E: return 0x020F; case 0x0210: return 0x0211; case 0x0212: return 0x0213; case 0x0214: return 0x0215; case 0x0216: return 0x0217; case 0x0218: return 0x0219; case 0x021A: return 0x021B; case 0x021C: return 0x021D; case 0x021E: return 0x021F; case 0x0220: return 0x019E; case 0x0222: return 0x0223; case 0x0224: return 0x0225; case 0x0226: return 0x0227; case 0x0228: return 0x0229; case 0x022A: return 0x022B; case 0x022C: return 0x022D; case 0x022E: return 0x022F; case 0x0230: return 0x0231; case 0x0232: return 0x0233; case 0x023A: return 0x2C65; case 0x023B: return 0x023C; case 0x023D: return 0x019A; case 0x023E: return 0x2C66; case 0x0241: return 0x0242; case 0x0243: return 0x0180; case 0x0244: return 0x0289; case 0x0245: return 0x028C; case 0x0246: return 0x0247; case 0x0248: return 0x0249; case 0x024A: return 0x024B; case 0x024C: return 0x024D; case 0x024E: return 0x024F; case 0x0370: return 0x0371; case 0x0372: return 0x0373; case 0x0376: return 0x0377; case 0x0386: return 0x03AC; case 0x0388: return 0x03AD; case 0x0389: return 0x03AE; case 0x038A: return 0x03AF; case 0x038C: return 0x03CC; case 0x038E: return 0x03CD; case 0x038F: return 0x03CE; case 0x0391: return 0x03B1; case 0x0392: return 0x03B2; case 0x0393: return 0x03B3; case 0x0394: return 0x03B4; case 0x0395: return 0x03B5; case 0x0396: return 0x03B6; case 0x0397: return 0x03B7; case 0x0398: return 0x03B8; case 0x0399: return 0x03B9; case 0x039A: return 0x03BA; case 0x039B: return 0x03BB; case 0x039C: return 0x03BC; case 0x039D: return 0x03BD; case 0x039E: return 0x03BE; case 0x039F: return 0x03BF; case 0x03A0: return 0x03C0; case 0x03A1: return 0x03C1; case 0x03A3: return 0x03C3; case 0x03A4: return 0x03C4; case 0x03A5: return 0x03C5; case 0x03A6: return 0x03C6; case 0x03A7: return 0x03C7; case 0x03A8: return 0x03C8; case 0x03A9: return 0x03C9; case 0x03AA: return 0x03CA; case 0x03AB: return 0x03CB; case 0x03CF: return 0x03D7; case 0x03D8: return 0x03D9; case 0x03DA: return 0x03DB; case 0x03DC: return 0x03DD; case 0x03DE: return 0x03DF; case 0x03E0: return 0x03E1; case 0x03E2: return 0x03E3; case 0x03E4: return 0x03E5; case 0x03E6: return 0x03E7; case 0x03E8: return 0x03E9; case 0x03EA: return 0x03EB; case 0x03EC: return 0x03ED; case 0x03EE: return 0x03EF; case 0x03F4: return 0x03B8; case 0x03F7: return 0x03F8; case 0x03F9: return 0x03F2; case 0x03FA: return 0x03FB; case 0x03FD: return 0x037B; case 0x03FE: return 0x037C; case 0x03FF: return 0x037D; case 0x0400: return 0x0450; case 0x0401: return 0x0451; case 0x0402: return 0x0452; case 0x0403: return 0x0453; case 0x0404: return 0x0454; case 0x0405: return 0x0455; case 0x0406: return 0x0456; case 0x0407: return 0x0457; case 0x0408: return 0x0458; case 0x0409: return 0x0459; case 0x040A: return 0x045A; case 0x040B: return 0x045B; case 0x040C: return 0x045C; case 0x040D: return 0x045D; case 0x040E: return 0x045E; case 0x040F: return 0x045F; case 0x0410: return 0x0430; case 0x0411: return 0x0431; case 0x0412: return 0x0432; case 0x0413: return 0x0433; case 0x0414: return 0x0434; case 0x0415: return 0x0435; case 0x0416: return 0x0436; case 0x0417: return 0x0437; case 0x0418: return 0x0438; case 0x0419: return 0x0439; case 0x041A: return 0x043A; case 0x041B: return 0x043B; case 0x041C: return 0x043C; case 0x041D: return 0x043D; case 0x041E: return 0x043E; case 0x041F: return 0x043F; case 0x0420: return 0x0440; case 0x0421: return 0x0441; case 0x0422: return 0x0442; case 0x0423: return 0x0443; case 0x0424: return 0x0444; case 0x0425: return 0x0445; case 0x0426: return 0x0446; case 0x0427: return 0x0447; case 0x0428: return 0x0448; case 0x0429: return 0x0449; case 0x042A: return 0x044A; case 0x042B: return 0x044B; case 0x042C: return 0x044C; case 0x042D: return 0x044D; case 0x042E: return 0x044E; case 0x042F: return 0x044F; case 0x0460: return 0x0461; case 0x0462: return 0x0463; case 0x0464: return 0x0465; case 0x0466: return 0x0467; case 0x0468: return 0x0469; case 0x046A: return 0x046B; case 0x046C: return 0x046D; case 0x046E: return 0x046F; case 0x0470: return 0x0471; case 0x0472: return 0x0473; case 0x0474: return 0x0475; case 0x0476: return 0x0477; case 0x0478: return 0x0479; case 0x047A: return 0x047B; case 0x047C: return 0x047D; case 0x047E: return 0x047F; case 0x0480: return 0x0481; case 0x048A: return 0x048B; case 0x048C: return 0x048D; case 0x048E: return 0x048F; case 0x0490: return 0x0491; case 0x0492: return 0x0493; case 0x0494: return 0x0495; case 0x0496: return 0x0497; case 0x0498: return 0x0499; case 0x049A: return 0x049B; case 0x049C: return 0x049D; case 0x049E: return 0x049F; case 0x04A0: return 0x04A1; case 0x04A2: return 0x04A3; case 0x04A4: return 0x04A5; case 0x04A6: return 0x04A7; case 0x04A8: return 0x04A9; case 0x04AA: return 0x04AB; case 0x04AC: return 0x04AD; case 0x04AE: return 0x04AF; case 0x04B0: return 0x04B1; case 0x04B2: return 0x04B3; case 0x04B4: return 0x04B5; case 0x04B6: return 0x04B7; case 0x04B8: return 0x04B9; case 0x04BA: return 0x04BB; case 0x04BC: return 0x04BD; case 0x04BE: return 0x04BF; case 0x04C0: return 0x04CF; case 0x04C1: return 0x04C2; case 0x04C3: return 0x04C4; case 0x04C5: return 0x04C6; case 0x04C7: return 0x04C8; case 0x04C9: return 0x04CA; case 0x04CB: return 0x04CC; case 0x04CD: return 0x04CE; case 0x04D0: return 0x04D1; case 0x04D2: return 0x04D3; case 0x04D4: return 0x04D5; case 0x04D6: return 0x04D7; case 0x04D8: return 0x04D9; case 0x04DA: return 0x04DB; case 0x04DC: return 0x04DD; case 0x04DE: return 0x04DF; case 0x04E0: return 0x04E1; case 0x04E2: return 0x04E3; case 0x04E4: return 0x04E5; case 0x04E6: return 0x04E7; case 0x04E8: return 0x04E9; case 0x04EA: return 0x04EB; case 0x04EC: return 0x04ED; case 0x04EE: return 0x04EF; case 0x04F0: return 0x04F1; case 0x04F2: return 0x04F3; case 0x04F4: return 0x04F5; case 0x04F6: return 0x04F7; case 0x04F8: return 0x04F9; case 0x04FA: return 0x04FB; case 0x04FC: return 0x04FD; case 0x04FE: return 0x04FF; case 0x0500: return 0x0501; case 0x0502: return 0x0503; case 0x0504: return 0x0505; case 0x0506: return 0x0507; case 0x0508: return 0x0509; case 0x050A: return 0x050B; case 0x050C: return 0x050D; case 0x050E: return 0x050F; case 0x0510: return 0x0511; case 0x0512: return 0x0513; case 0x0514: return 0x0515; case 0x0516: return 0x0517; case 0x0518: return 0x0519; case 0x051A: return 0x051B; case 0x051C: return 0x051D; case 0x051E: return 0x051F; case 0x0520: return 0x0521; case 0x0522: return 0x0523; case 0x0524: return 0x0525; case 0x0526: return 0x0527; case 0x0531: return 0x0561; case 0x0532: return 0x0562; case 0x0533: return 0x0563; case 0x0534: return 0x0564; case 0x0535: return 0x0565; case 0x0536: return 0x0566; case 0x0537: return 0x0567; case 0x0538: return 0x0568; case 0x0539: return 0x0569; case 0x053A: return 0x056A; case 0x053B: return 0x056B; case 0x053C: return 0x056C; case 0x053D: return 0x056D; case 0x053E: return 0x056E; case 0x053F: return 0x056F; case 0x0540: return 0x0570; case 0x0541: return 0x0571; case 0x0542: return 0x0572; case 0x0543: return 0x0573; case 0x0544: return 0x0574; case 0x0545: return 0x0575; case 0x0546: return 0x0576; case 0x0547: return 0x0577; case 0x0548: return 0x0578; case 0x0549: return 0x0579; case 0x054A: return 0x057A; case 0x054B: return 0x057B; case 0x054C: return 0x057C; case 0x054D: return 0x057D; case 0x054E: return 0x057E; case 0x054F: return 0x057F; case 0x0550: return 0x0580; case 0x0551: return 0x0581; case 0x0552: return 0x0582; case 0x0553: return 0x0583; case 0x0554: return 0x0584; case 0x0555: return 0x0585; case 0x0556: return 0x0586; case 0x10A0: return 0x2D00; case 0x10A1: return 0x2D01; case 0x10A2: return 0x2D02; case 0x10A3: return 0x2D03; case 0x10A4: return 0x2D04; case 0x10A5: return 0x2D05; case 0x10A6: return 0x2D06; case 0x10A7: return 0x2D07; case 0x10A8: return 0x2D08; case 0x10A9: return 0x2D09; case 0x10AA: return 0x2D0A; case 0x10AB: return 0x2D0B; case 0x10AC: return 0x2D0C; case 0x10AD: return 0x2D0D; case 0x10AE: return 0x2D0E; case 0x10AF: return 0x2D0F; case 0x10B0: return 0x2D10; case 0x10B1: return 0x2D11; case 0x10B2: return 0x2D12; case 0x10B3: return 0x2D13; case 0x10B4: return 0x2D14; case 0x10B5: return 0x2D15; case 0x10B6: return 0x2D16; case 0x10B7: return 0x2D17; case 0x10B8: return 0x2D18; case 0x10B9: return 0x2D19; case 0x10BA: return 0x2D1A; case 0x10BB: return 0x2D1B; case 0x10BC: return 0x2D1C; case 0x10BD: return 0x2D1D; case 0x10BE: return 0x2D1E; case 0x10BF: return 0x2D1F; case 0x10C0: return 0x2D20; case 0x10C1: return 0x2D21; case 0x10C2: return 0x2D22; case 0x10C3: return 0x2D23; case 0x10C4: return 0x2D24; case 0x10C5: return 0x2D25; case 0x1E00: return 0x1E01; case 0x1E02: return 0x1E03; case 0x1E04: return 0x1E05; case 0x1E06: return 0x1E07; case 0x1E08: return 0x1E09; case 0x1E0A: return 0x1E0B; case 0x1E0C: return 0x1E0D; case 0x1E0E: return 0x1E0F; case 0x1E10: return 0x1E11; case 0x1E12: return 0x1E13; case 0x1E14: return 0x1E15; case 0x1E16: return 0x1E17; case 0x1E18: return 0x1E19; case 0x1E1A: return 0x1E1B; case 0x1E1C: return 0x1E1D; case 0x1E1E: return 0x1E1F; case 0x1E20: return 0x1E21; case 0x1E22: return 0x1E23; case 0x1E24: return 0x1E25; case 0x1E26: return 0x1E27; case 0x1E28: return 0x1E29; case 0x1E2A: return 0x1E2B; case 0x1E2C: return 0x1E2D; case 0x1E2E: return 0x1E2F; case 0x1E30: return 0x1E31; case 0x1E32: return 0x1E33; case 0x1E34: return 0x1E35; case 0x1E36: return 0x1E37; case 0x1E38: return 0x1E39; case 0x1E3A: return 0x1E3B; case 0x1E3C: return 0x1E3D; case 0x1E3E: return 0x1E3F; case 0x1E40: return 0x1E41; case 0x1E42: return 0x1E43; case 0x1E44: return 0x1E45; case 0x1E46: return 0x1E47; case 0x1E48: return 0x1E49; case 0x1E4A: return 0x1E4B; case 0x1E4C: return 0x1E4D; case 0x1E4E: return 0x1E4F; case 0x1E50: return 0x1E51; case 0x1E52: return 0x1E53; case 0x1E54: return 0x1E55; case 0x1E56: return 0x1E57; case 0x1E58: return 0x1E59; case 0x1E5A: return 0x1E5B; case 0x1E5C: return 0x1E5D; case 0x1E5E: return 0x1E5F; case 0x1E60: return 0x1E61; case 0x1E62: return 0x1E63; case 0x1E64: return 0x1E65; case 0x1E66: return 0x1E67; case 0x1E68: return 0x1E69; case 0x1E6A: return 0x1E6B; case 0x1E6C: return 0x1E6D; case 0x1E6E: return 0x1E6F; case 0x1E70: return 0x1E71; case 0x1E72: return 0x1E73; case 0x1E74: return 0x1E75; case 0x1E76: return 0x1E77; case 0x1E78: return 0x1E79; case 0x1E7A: return 0x1E7B; case 0x1E7C: return 0x1E7D; case 0x1E7E: return 0x1E7F; case 0x1E80: return 0x1E81; case 0x1E82: return 0x1E83; case 0x1E84: return 0x1E85; case 0x1E86: return 0x1E87; case 0x1E88: return 0x1E89; case 0x1E8A: return 0x1E8B; case 0x1E8C: return 0x1E8D; case 0x1E8E: return 0x1E8F; case 0x1E90: return 0x1E91; case 0x1E92: return 0x1E93; case 0x1E94: return 0x1E95; case 0x1E9E: return 0x00DF; case 0x1EA0: return 0x1EA1; case 0x1EA2: return 0x1EA3; case 0x1EA4: return 0x1EA5; case 0x1EA6: return 0x1EA7; case 0x1EA8: return 0x1EA9; case 0x1EAA: return 0x1EAB; case 0x1EAC: return 0x1EAD; case 0x1EAE: return 0x1EAF; case 0x1EB0: return 0x1EB1; case 0x1EB2: return 0x1EB3; case 0x1EB4: return 0x1EB5; case 0x1EB6: return 0x1EB7; case 0x1EB8: return 0x1EB9; case 0x1EBA: return 0x1EBB; case 0x1EBC: return 0x1EBD; case 0x1EBE: return 0x1EBF; case 0x1EC0: return 0x1EC1; case 0x1EC2: return 0x1EC3; case 0x1EC4: return 0x1EC5; case 0x1EC6: return 0x1EC7; case 0x1EC8: return 0x1EC9; case 0x1ECA: return 0x1ECB; case 0x1ECC: return 0x1ECD; case 0x1ECE: return 0x1ECF; case 0x1ED0: return 0x1ED1; case 0x1ED2: return 0x1ED3; case 0x1ED4: return 0x1ED5; case 0x1ED6: return 0x1ED7; case 0x1ED8: return 0x1ED9; case 0x1EDA: return 0x1EDB; case 0x1EDC: return 0x1EDD; case 0x1EDE: return 0x1EDF; case 0x1EE0: return 0x1EE1; case 0x1EE2: return 0x1EE3; case 0x1EE4: return 0x1EE5; case 0x1EE6: return 0x1EE7; case 0x1EE8: return 0x1EE9; case 0x1EEA: return 0x1EEB; case 0x1EEC: return 0x1EED; case 0x1EEE: return 0x1EEF; case 0x1EF0: return 0x1EF1; case 0x1EF2: return 0x1EF3; case 0x1EF4: return 0x1EF5; case 0x1EF6: return 0x1EF7; case 0x1EF8: return 0x1EF9; case 0x1EFA: return 0x1EFB; case 0x1EFC: return 0x1EFD; case 0x1EFE: return 0x1EFF; case 0x1F08: return 0x1F00; case 0x1F09: return 0x1F01; case 0x1F0A: return 0x1F02; case 0x1F0B: return 0x1F03; case 0x1F0C: return 0x1F04; case 0x1F0D: return 0x1F05; case 0x1F0E: return 0x1F06; case 0x1F0F: return 0x1F07; case 0x1F18: return 0x1F10; case 0x1F19: return 0x1F11; case 0x1F1A: return 0x1F12; case 0x1F1B: return 0x1F13; case 0x1F1C: return 0x1F14; case 0x1F1D: return 0x1F15; case 0x1F28: return 0x1F20; case 0x1F29: return 0x1F21; case 0x1F2A: return 0x1F22; case 0x1F2B: return 0x1F23; case 0x1F2C: return 0x1F24; case 0x1F2D: return 0x1F25; case 0x1F2E: return 0x1F26; case 0x1F2F: return 0x1F27; case 0x1F38: return 0x1F30; case 0x1F39: return 0x1F31; case 0x1F3A: return 0x1F32; case 0x1F3B: return 0x1F33; case 0x1F3C: return 0x1F34; case 0x1F3D: return 0x1F35; case 0x1F3E: return 0x1F36; case 0x1F3F: return 0x1F37; case 0x1F48: return 0x1F40; case 0x1F49: return 0x1F41; case 0x1F4A: return 0x1F42; case 0x1F4B: return 0x1F43; case 0x1F4C: return 0x1F44; case 0x1F4D: return 0x1F45; case 0x1F59: return 0x1F51; case 0x1F5B: return 0x1F53; case 0x1F5D: return 0x1F55; case 0x1F5F: return 0x1F57; case 0x1F68: return 0x1F60; case 0x1F69: return 0x1F61; case 0x1F6A: return 0x1F62; case 0x1F6B: return 0x1F63; case 0x1F6C: return 0x1F64; case 0x1F6D: return 0x1F65; case 0x1F6E: return 0x1F66; case 0x1F6F: return 0x1F67; case 0x1F88: return 0x1F80; case 0x1F89: return 0x1F81; case 0x1F8A: return 0x1F82; case 0x1F8B: return 0x1F83; case 0x1F8C: return 0x1F84; case 0x1F8D: return 0x1F85; case 0x1F8E: return 0x1F86; case 0x1F8F: return 0x1F87; case 0x1F98: return 0x1F90; case 0x1F99: return 0x1F91; case 0x1F9A: return 0x1F92; case 0x1F9B: return 0x1F93; case 0x1F9C: return 0x1F94; case 0x1F9D: return 0x1F95; case 0x1F9E: return 0x1F96; case 0x1F9F: return 0x1F97; case 0x1FA8: return 0x1FA0; case 0x1FA9: return 0x1FA1; case 0x1FAA: return 0x1FA2; case 0x1FAB: return 0x1FA3; case 0x1FAC: return 0x1FA4; case 0x1FAD: return 0x1FA5; case 0x1FAE: return 0x1FA6; case 0x1FAF: return 0x1FA7; case 0x1FB8: return 0x1FB0; case 0x1FB9: return 0x1FB1; case 0x1FBA: return 0x1F70; case 0x1FBB: return 0x1F71; case 0x1FBC: return 0x1FB3; case 0x1FC8: return 0x1F72; case 0x1FC9: return 0x1F73; case 0x1FCA: return 0x1F74; case 0x1FCB: return 0x1F75; case 0x1FCC: return 0x1FC3; case 0x1FD8: return 0x1FD0; case 0x1FD9: return 0x1FD1; case 0x1FDA: return 0x1F76; case 0x1FDB: return 0x1F77; case 0x1FE8: return 0x1FE0; case 0x1FE9: return 0x1FE1; case 0x1FEA: return 0x1F7A; case 0x1FEB: return 0x1F7B; case 0x1FEC: return 0x1FE5; case 0x1FF8: return 0x1F78; case 0x1FF9: return 0x1F79; case 0x1FFA: return 0x1F7C; case 0x1FFB: return 0x1F7D; case 0x1FFC: return 0x1FF3; case 0x2126: return 0x03C9; case 0x212A: return 0x006B; case 0x212B: return 0x00E5; case 0x2132: return 0x214E; case 0x2160: return 0x2170; case 0x2161: return 0x2171; case 0x2162: return 0x2172; case 0x2163: return 0x2173; case 0x2164: return 0x2174; case 0x2165: return 0x2175; case 0x2166: return 0x2176; case 0x2167: return 0x2177; case 0x2168: return 0x2178; case 0x2169: return 0x2179; case 0x216A: return 0x217A; case 0x216B: return 0x217B; case 0x216C: return 0x217C; case 0x216D: return 0x217D; case 0x216E: return 0x217E; case 0x216F: return 0x217F; case 0x2183: return 0x2184; case 0x24B6: return 0x24D0; case 0x24B7: return 0x24D1; case 0x24B8: return 0x24D2; case 0x24B9: return 0x24D3; case 0x24BA: return 0x24D4; case 0x24BB: return 0x24D5; case 0x24BC: return 0x24D6; case 0x24BD: return 0x24D7; case 0x24BE: return 0x24D8; case 0x24BF: return 0x24D9; case 0x24C0: return 0x24DA; case 0x24C1: return 0x24DB; case 0x24C2: return 0x24DC; case 0x24C3: return 0x24DD; case 0x24C4: return 0x24DE; case 0x24C5: return 0x24DF; case 0x24C6: return 0x24E0; case 0x24C7: return 0x24E1; case 0x24C8: return 0x24E2; case 0x24C9: return 0x24E3; case 0x24CA: return 0x24E4; case 0x24CB: return 0x24E5; case 0x24CC: return 0x24E6; case 0x24CD: return 0x24E7; case 0x24CE: return 0x24E8; case 0x24CF: return 0x24E9; case 0x2C00: return 0x2C30; case 0x2C01: return 0x2C31; case 0x2C02: return 0x2C32; case 0x2C03: return 0x2C33; case 0x2C04: return 0x2C34; case 0x2C05: return 0x2C35; case 0x2C06: return 0x2C36; case 0x2C07: return 0x2C37; case 0x2C08: return 0x2C38; case 0x2C09: return 0x2C39; case 0x2C0A: return 0x2C3A; case 0x2C0B: return 0x2C3B; case 0x2C0C: return 0x2C3C; case 0x2C0D: return 0x2C3D; case 0x2C0E: return 0x2C3E; case 0x2C0F: return 0x2C3F; case 0x2C10: return 0x2C40; case 0x2C11: return 0x2C41; case 0x2C12: return 0x2C42; case 0x2C13: return 0x2C43; case 0x2C14: return 0x2C44; case 0x2C15: return 0x2C45; case 0x2C16: return 0x2C46; case 0x2C17: return 0x2C47; case 0x2C18: return 0x2C48; case 0x2C19: return 0x2C49; case 0x2C1A: return 0x2C4A; case 0x2C1B: return 0x2C4B; case 0x2C1C: return 0x2C4C; case 0x2C1D: return 0x2C4D; case 0x2C1E: return 0x2C4E; case 0x2C1F: return 0x2C4F; case 0x2C20: return 0x2C50; case 0x2C21: return 0x2C51; case 0x2C22: return 0x2C52; case 0x2C23: return 0x2C53; case 0x2C24: return 0x2C54; case 0x2C25: return 0x2C55; case 0x2C26: return 0x2C56; case 0x2C27: return 0x2C57; case 0x2C28: return 0x2C58; case 0x2C29: return 0x2C59; case 0x2C2A: return 0x2C5A; case 0x2C2B: return 0x2C5B; case 0x2C2C: return 0x2C5C; case 0x2C2D: return 0x2C5D; case 0x2C2E: return 0x2C5E; case 0x2C60: return 0x2C61; case 0x2C62: return 0x026B; case 0x2C63: return 0x1D7D; case 0x2C64: return 0x027D; case 0x2C67: return 0x2C68; case 0x2C69: return 0x2C6A; case 0x2C6B: return 0x2C6C; case 0x2C6D: return 0x0251; case 0x2C6E: return 0x0271; case 0x2C6F: return 0x0250; case 0x2C70: return 0x0252; case 0x2C72: return 0x2C73; case 0x2C75: return 0x2C76; case 0x2C7E: return 0x023F; case 0x2C7F: return 0x0240; case 0x2C80: return 0x2C81; case 0x2C82: return 0x2C83; case 0x2C84: return 0x2C85; case 0x2C86: return 0x2C87; case 0x2C88: return 0x2C89; case 0x2C8A: return 0x2C8B; case 0x2C8C: return 0x2C8D; case 0x2C8E: return 0x2C8F; case 0x2C90: return 0x2C91; case 0x2C92: return 0x2C93; case 0x2C94: return 0x2C95; case 0x2C96: return 0x2C97; case 0x2C98: return 0x2C99; case 0x2C9A: return 0x2C9B; case 0x2C9C: return 0x2C9D; case 0x2C9E: return 0x2C9F; case 0x2CA0: return 0x2CA1; case 0x2CA2: return 0x2CA3; case 0x2CA4: return 0x2CA5; case 0x2CA6: return 0x2CA7; case 0x2CA8: return 0x2CA9; case 0x2CAA: return 0x2CAB; case 0x2CAC: return 0x2CAD; case 0x2CAE: return 0x2CAF; case 0x2CB0: return 0x2CB1; case 0x2CB2: return 0x2CB3; case 0x2CB4: return 0x2CB5; case 0x2CB6: return 0x2CB7; case 0x2CB8: return 0x2CB9; case 0x2CBA: return 0x2CBB; case 0x2CBC: return 0x2CBD; case 0x2CBE: return 0x2CBF; case 0x2CC0: return 0x2CC1; case 0x2CC2: return 0x2CC3; case 0x2CC4: return 0x2CC5; case 0x2CC6: return 0x2CC7; case 0x2CC8: return 0x2CC9; case 0x2CCA: return 0x2CCB; case 0x2CCC: return 0x2CCD; case 0x2CCE: return 0x2CCF; case 0x2CD0: return 0x2CD1; case 0x2CD2: return 0x2CD3; case 0x2CD4: return 0x2CD5; case 0x2CD6: return 0x2CD7; case 0x2CD8: return 0x2CD9; case 0x2CDA: return 0x2CDB; case 0x2CDC: return 0x2CDD; case 0x2CDE: return 0x2CDF; case 0x2CE0: return 0x2CE1; case 0x2CE2: return 0x2CE3; case 0x2CEB: return 0x2CEC; case 0x2CED: return 0x2CEE; case 0xA640: return 0xA641; case 0xA642: return 0xA643; case 0xA644: return 0xA645; case 0xA646: return 0xA647; case 0xA648: return 0xA649; case 0xA64A: return 0xA64B; case 0xA64C: return 0xA64D; case 0xA64E: return 0xA64F; case 0xA650: return 0xA651; case 0xA652: return 0xA653; case 0xA654: return 0xA655; case 0xA656: return 0xA657; case 0xA658: return 0xA659; case 0xA65A: return 0xA65B; case 0xA65C: return 0xA65D; case 0xA65E: return 0xA65F; case 0xA660: return 0xA661; case 0xA662: return 0xA663; case 0xA664: return 0xA665; case 0xA666: return 0xA667; case 0xA668: return 0xA669; case 0xA66A: return 0xA66B; case 0xA66C: return 0xA66D; case 0xA680: return 0xA681; case 0xA682: return 0xA683; case 0xA684: return 0xA685; case 0xA686: return 0xA687; case 0xA688: return 0xA689; case 0xA68A: return 0xA68B; case 0xA68C: return 0xA68D; case 0xA68E: return 0xA68F; case 0xA690: return 0xA691; case 0xA692: return 0xA693; case 0xA694: return 0xA695; case 0xA696: return 0xA697; case 0xA722: return 0xA723; case 0xA724: return 0xA725; case 0xA726: return 0xA727; case 0xA728: return 0xA729; case 0xA72A: return 0xA72B; case 0xA72C: return 0xA72D; case 0xA72E: return 0xA72F; case 0xA732: return 0xA733; case 0xA734: return 0xA735; case 0xA736: return 0xA737; case 0xA738: return 0xA739; case 0xA73A: return 0xA73B; case 0xA73C: return 0xA73D; case 0xA73E: return 0xA73F; case 0xA740: return 0xA741; case 0xA742: return 0xA743; case 0xA744: return 0xA745; case 0xA746: return 0xA747; case 0xA748: return 0xA749; case 0xA74A: return 0xA74B; case 0xA74C: return 0xA74D; case 0xA74E: return 0xA74F; case 0xA750: return 0xA751; case 0xA752: return 0xA753; case 0xA754: return 0xA755; case 0xA756: return 0xA757; case 0xA758: return 0xA759; case 0xA75A: return 0xA75B; case 0xA75C: return 0xA75D; case 0xA75E: return 0xA75F; case 0xA760: return 0xA761; case 0xA762: return 0xA763; case 0xA764: return 0xA765; case 0xA766: return 0xA767; case 0xA768: return 0xA769; case 0xA76A: return 0xA76B; case 0xA76C: return 0xA76D; case 0xA76E: return 0xA76F; case 0xA779: return 0xA77A; case 0xA77B: return 0xA77C; case 0xA77D: return 0x1D79; case 0xA77E: return 0xA77F; case 0xA780: return 0xA781; case 0xA782: return 0xA783; case 0xA784: return 0xA785; case 0xA786: return 0xA787; case 0xA78B: return 0xA78C; case 0xA78D: return 0x0265; case 0xA790: return 0xA791; case 0xA7A0: return 0xA7A1; case 0xA7A2: return 0xA7A3; case 0xA7A4: return 0xA7A5; case 0xA7A6: return 0xA7A7; case 0xA7A8: return 0xA7A9; case 0xFF21: return 0xFF41; case 0xFF22: return 0xFF42; case 0xFF23: return 0xFF43; case 0xFF24: return 0xFF44; case 0xFF25: return 0xFF45; case 0xFF26: return 0xFF46; case 0xFF27: return 0xFF47; case 0xFF28: return 0xFF48; case 0xFF29: return 0xFF49; case 0xFF2A: return 0xFF4A; case 0xFF2B: return 0xFF4B; case 0xFF2C: return 0xFF4C; case 0xFF2D: return 0xFF4D; case 0xFF2E: return 0xFF4E; case 0xFF2F: return 0xFF4F; case 0xFF30: return 0xFF50; case 0xFF31: return 0xFF51; case 0xFF32: return 0xFF52; case 0xFF33: return 0xFF53; case 0xFF34: return 0xFF54; case 0xFF35: return 0xFF55; case 0xFF36: return 0xFF56; case 0xFF37: return 0xFF57; case 0xFF38: return 0xFF58; case 0xFF39: return 0xFF59; case 0xFF3A: return 0xFF5A; case 0x10400: return 0x10428; case 0x10401: return 0x10429; case 0x10402: return 0x1042A; case 0x10403: return 0x1042B; case 0x10404: return 0x1042C; case 0x10405: return 0x1042D; case 0x10406: return 0x1042E; case 0x10407: return 0x1042F; case 0x10408: return 0x10430; case 0x10409: return 0x10431; case 0x1040A: return 0x10432; case 0x1040B: return 0x10433; case 0x1040C: return 0x10434; case 0x1040D: return 0x10435; case 0x1040E: return 0x10436; case 0x1040F: return 0x10437; case 0x10410: return 0x10438; case 0x10411: return 0x10439; case 0x10412: return 0x1043A; case 0x10413: return 0x1043B; case 0x10414: return 0x1043C; case 0x10415: return 0x1043D; case 0x10416: return 0x1043E; case 0x10417: return 0x1043F; case 0x10418: return 0x10440; case 0x10419: return 0x10441; case 0x1041A: return 0x10442; case 0x1041B: return 0x10443; case 0x1041C: return 0x10444; case 0x1041D: return 0x10445; case 0x1041E: return 0x10446; case 0x1041F: return 0x10447; case 0x10420: return 0x10448; case 0x10421: return 0x10449; case 0x10422: return 0x1044A; case 0x10423: return 0x1044B; case 0x10424: return 0x1044C; case 0x10425: return 0x1044D; case 0x10426: return 0x1044E; case 0x10427: return 0x1044F; default: return ch; } } darkplaces/snd_win.c0000664000175000017500000005152713067716222014004 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifdef SUPPORTDIRECTX #ifndef DIRECTSOUND_VERSION # define DIRECTSOUND_VERSION 0x0500 /* Version 5.0 */ #endif #endif #include #include #ifdef SUPPORTDIRECTX #include #endif #include "qtypes.h" #include "quakedef.h" #include "snd_main.h" // ============================================================================== #ifndef _WAVEFORMATEXTENSIBLE_ #define _WAVEFORMATEXTENSIBLE_ typedef struct { WAVEFORMATEX Format; union { WORD wValidBitsPerSample; // bits of precision WORD wSamplesPerBlock; // valid if wBitsPerSample==0 WORD wReserved; // If neither applies, set to zero } Samples; DWORD dwChannelMask; // which channels are present in stream GUID SubFormat; } WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; #endif #if !defined(WAVE_FORMAT_EXTENSIBLE) # define WAVE_FORMAT_EXTENSIBLE 0xFFFE #endif // Some speaker positions #ifndef SPEAKER_FRONT_LEFT # define SPEAKER_FRONT_LEFT 0x1 # define SPEAKER_FRONT_RIGHT 0x2 # define SPEAKER_FRONT_CENTER 0x4 # define SPEAKER_LOW_FREQUENCY 0x8 # define SPEAKER_BACK_LEFT 0x10 # define SPEAKER_BACK_RIGHT 0x20 # define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 # define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 // ... we never use the other values #endif // KSDATAFORMAT_SUBTYPE_PCM = GUID "00000001-0000-0010-8000-00aa00389b71" static const GUID MY_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; // ============================================================================== extern HWND mainwindow; static cvar_t snd_wav_partitionsize = {CVAR_SAVE, "snd_wav_partitionsize", "1024", "controls sound delay in samples, values too low will cause crackling, too high will cause delayed sounds"}; static qboolean sndsys_registeredcvars = false; #ifdef SUPPORTDIRECTX HRESULT (WINAPI *pDirectSoundCreate)(GUID FAR *lpGUID, LPDIRECTSOUND FAR *lplpDS, IUnknown FAR *pUnkOuter); #endif // Wave output: queue of this many sound buffers to play, reused cyclically #define WAV_BUFFERS 16 #define WAV_MASK (WAV_BUFFERS - 1) static unsigned int wav_buffer_size; // DirectSound output: 64KB in 1 buffer //#define SECONDARY_BUFFER_SIZE(fmt_ptr) ((fmt_ptr)->width * (fmt_ptr)->channels * (fmt_ptr)->speed / 2) // LordHavoc: changed this to be a multiple of 32768 #define SECONDARY_BUFFER_SIZE(fmt_ptr) ((fmt_ptr)->channels * 32768) typedef enum sndinitstat_e {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; #ifdef SUPPORTDIRECTX static qboolean dsound_init; static unsigned int dsound_time; static qboolean primary_format_set; #endif static qboolean wav_init; static int snd_sent, snd_completed; static int prev_painted; static unsigned int paintpot; /* * Global variables. Must be visible to window-procedure function * so it can unlock and free the data block after it has been played. */ HANDLE hData; HPSTR lpData, lpData2; HGLOBAL hWaveHdr; LPWAVEHDR lpWaveHdr; HWAVEOUT hWaveOut; WAVEOUTCAPS wavecaps; DWORD gSndBufSize; DWORD dwStartTime; #ifdef SUPPORTDIRECTX LPDIRECTSOUND pDS; LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; HINSTANCE hInstDS; #endif qboolean SNDDMA_InitWav (void); #ifdef SUPPORTDIRECTX sndinitstat SNDDMA_InitDirect (void); #endif /* ================== SndSys_BuildWaveFormat ================== */ static qboolean SndSys_BuildWaveFormat (const snd_format_t* requested, WAVEFORMATEXTENSIBLE* fmt_ptr) { WAVEFORMATEX* pfmtex; memset (fmt_ptr, 0, sizeof(*fmt_ptr)); pfmtex = &fmt_ptr->Format; pfmtex->nChannels = requested->channels; pfmtex->wBitsPerSample = requested->width * 8; pfmtex->nSamplesPerSec = requested->speed; pfmtex->nBlockAlign = pfmtex->nChannels * pfmtex->wBitsPerSample / 8; pfmtex->nAvgBytesPerSec = pfmtex->nSamplesPerSec * pfmtex->nBlockAlign; // LordHavoc: disabled this WAVE_FORMAT_EXTENSIBLE support because it does not seem to be working #if 0 if (requested->channels <= 2) { #endif pfmtex->wFormatTag = WAVE_FORMAT_PCM; pfmtex->cbSize = 0; #if 0 } else { pfmtex->wFormatTag = WAVE_FORMAT_EXTENSIBLE; pfmtex->cbSize = sizeof(*fmt_ptr) - sizeof(fmt_ptr->Format); fmt_ptr->Samples.wValidBitsPerSample = fmt_ptr->Format.wBitsPerSample; fmt_ptr->SubFormat = MY_KSDATAFORMAT_SUBTYPE_PCM; // Build the channel mask fmt_ptr->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; switch (requested->channels) { case 8: fmt_ptr->dwChannelMask |= SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER; // no break case 6: fmt_ptr->dwChannelMask |= SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY; // no break case 4: fmt_ptr->dwChannelMask |= SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break; default: Con_Printf("SndSys_BuildWaveFormat: invalid number of channels (%hu)\n", requested->channels); return false; } } #endif return true; } #ifdef SUPPORTDIRECTX /* ================== SndSys_InitDirectSound DirectSound 5 support ================== */ static sndinitstat SndSys_InitDirectSound (const snd_format_t* requested) { DSBUFFERDESC dsbuf; DSBCAPS dsbcaps; DWORD dwSize; DSCAPS dscaps; WAVEFORMATEXTENSIBLE format, pformat; HRESULT hresult; int reps; if (! SndSys_BuildWaveFormat(requested, &format)) return SIS_FAILURE; if (!hInstDS) { hInstDS = LoadLibrary("dsound.dll"); if (hInstDS == NULL) { Con_Print("Couldn't load dsound.dll\n"); return SIS_FAILURE; } pDirectSoundCreate = (HRESULT (__stdcall *)(GUID *, LPDIRECTSOUND *,IUnknown *))GetProcAddress(hInstDS,"DirectSoundCreate"); if (!pDirectSoundCreate) { Con_Print("Couldn't get DS proc addr\n"); return SIS_FAILURE; } } while ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) { if (hresult != DSERR_ALLOCATED) { Con_Print("DirectSound create failed\n"); return SIS_FAILURE; } if (MessageBox (NULL, "The sound hardware is in use by another app.\n\n" "Select Retry to try to start sound again or Cancel to run Quake with no sound.", "Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) { Con_Print("DirectSoundCreate failure\n hardware already in use\n"); return SIS_NOTAVAIL; } } dscaps.dwSize = sizeof(dscaps); if (DS_OK != IDirectSound_GetCaps (pDS, &dscaps)) { Con_Print("Couldn't get DS caps\n"); } if (dscaps.dwFlags & DSCAPS_EMULDRIVER) { Con_Print("No DirectSound driver installed\n"); SndSys_Shutdown (); return SIS_FAILURE; } if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_EXCLUSIVE)) { Con_Print("Set coop level failed\n"); SndSys_Shutdown (); return SIS_FAILURE; } // get access to the primary buffer, if possible, so we can set the // sound hardware format memset (&dsbuf, 0, sizeof(dsbuf)); dsbuf.dwSize = sizeof(DSBUFFERDESC); dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; dsbuf.dwBufferBytes = 0; dsbuf.lpwfxFormat = NULL; memset(&dsbcaps, 0, sizeof(dsbcaps)); dsbcaps.dwSize = sizeof(dsbcaps); primary_format_set = false; // COMMANDLINEOPTION: Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it if (!COM_CheckParm ("-snoforceformat")) { if (DS_OK == IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSPBuf, NULL)) { pformat = format; if (DS_OK != IDirectSoundBuffer_SetFormat (pDSPBuf, (WAVEFORMATEX*)&pformat)) { Con_Print("Set primary sound buffer format: no\n"); } else { Con_Print("Set primary sound buffer format: yes\n"); primary_format_set = true; } } } // COMMANDLINEOPTION: Windows DirectSound: -primarysound locks the sound hardware for exclusive use if (!primary_format_set || !COM_CheckParm ("-primarysound")) { HRESULT result; // create the secondary buffer we'll actually work with memset (&dsbuf, 0, sizeof(dsbuf)); dsbuf.dwSize = sizeof(DSBUFFERDESC); dsbuf.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE(requested); dsbuf.lpwfxFormat = (WAVEFORMATEX*)&format; memset(&dsbcaps, 0, sizeof(dsbcaps)); dsbcaps.dwSize = sizeof(dsbcaps); result = IDirectSound_CreateSoundBuffer(pDS, &dsbuf, &pDSBuf, NULL); if (result != DS_OK || requested->channels != format.Format.nChannels || requested->width != format.Format.wBitsPerSample / 8 || requested->speed != format.Format.nSamplesPerSec) { Con_Printf("DS:CreateSoundBuffer Failed (%d): channels=%u, width=%u, speed=%u\n", (int)result, (unsigned)format.Format.nChannels, (unsigned)format.Format.wBitsPerSample / 8, (unsigned)format.Format.nSamplesPerSec); SndSys_Shutdown (); return SIS_FAILURE; } if (DS_OK != IDirectSoundBuffer_GetCaps (pDSBuf, &dsbcaps)) { Con_Print("DS:GetCaps failed\n"); SndSys_Shutdown (); return SIS_FAILURE; } Con_Print("Using secondary sound buffer\n"); } else { if (DS_OK != IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_WRITEPRIMARY)) { Con_Print("Set coop level failed\n"); SndSys_Shutdown (); return SIS_FAILURE; } if (DS_OK != IDirectSoundBuffer_GetCaps (pDSPBuf, &dsbcaps)) { Con_Print("DS:GetCaps failed\n"); return SIS_FAILURE; } pDSBuf = pDSPBuf; Con_Print("Using primary sound buffer\n"); } // Make sure mixer is active IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); Con_Printf(" %d channel(s)\n" " %d bits/sample\n" " %d samples/sec\n", requested->channels, requested->width * 8, requested->speed); gSndBufSize = dsbcaps.dwBufferBytes; // initialize the buffer reps = 0; while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK) { if (hresult != DSERR_BUFFERLOST) { Con_Print("SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); SndSys_Shutdown (); return SIS_FAILURE; } if (++reps > 10000) { Con_Print("SNDDMA_InitDirect: DS: couldn't restore buffer\n"); SndSys_Shutdown (); return SIS_FAILURE; } } memset(lpData, 0, dwSize); IDirectSoundBuffer_Unlock(pDSBuf, lpData, dwSize, NULL, 0); IDirectSoundBuffer_Stop(pDSBuf); IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); dwStartTime = 0; dsound_time = 0; snd_renderbuffer = Snd_CreateRingBuffer(requested, gSndBufSize / (requested->width * requested->channels), lpData); dsound_init = true; return SIS_SUCCESS; } #endif /* ================== SndSys_InitMmsystem Crappy windows multimedia base ================== */ static qboolean SndSys_InitMmsystem (const snd_format_t* requested) { WAVEFORMATEXTENSIBLE format; int i; HRESULT hr; if (! SndSys_BuildWaveFormat(requested, &format)) return false; // Open a waveform device for output using window callback while ((hr = waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, (WAVEFORMATEX*)&format, 0, 0L, CALLBACK_NULL)) != MMSYSERR_NOERROR) { if (hr != MMSYSERR_ALLOCATED) { Con_Print("waveOutOpen failed\n"); return false; } if (MessageBox (NULL, "The sound hardware is in use by another app.\n\n" "Select Retry to try to start sound again or Cancel to run Quake with no sound.", "Sound not available", MB_RETRYCANCEL | MB_SETFOREGROUND | MB_ICONEXCLAMATION) != IDRETRY) { Con_Print("waveOutOpen failure;\n hardware already in use\n"); return false; } } wav_buffer_size = bound(128, snd_wav_partitionsize.integer, 8192) * requested->channels * requested->width; /* * Allocate and lock memory for the waveform data. The memory * for waveform data must be globally allocated with * GMEM_MOVEABLE and GMEM_SHARE flags. */ gSndBufSize = WAV_BUFFERS * wav_buffer_size; hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, gSndBufSize); if (!hData) { Con_Print("Sound: Out of memory.\n"); SndSys_Shutdown (); return false; } lpData = (HPSTR)GlobalLock(hData); if (!lpData) { Con_Print("Sound: Failed to lock.\n"); SndSys_Shutdown (); return false; } memset (lpData, 0, gSndBufSize); /* * Allocate and lock memory for the header. This memory must * also be globally allocated with GMEM_MOVEABLE and * GMEM_SHARE flags. */ hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR) * WAV_BUFFERS); if (hWaveHdr == NULL) { Con_Print("Sound: Failed to Alloc header.\n"); SndSys_Shutdown (); return false; } lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr); if (lpWaveHdr == NULL) { Con_Print("Sound: Failed to lock header.\n"); SndSys_Shutdown (); return false; } memset (lpWaveHdr, 0, sizeof(WAVEHDR) * WAV_BUFFERS); // After allocation, set up and prepare headers for (i=0 ; iwidth * requested->channels), lpData); prev_painted = 0; paintpot = 0; snd_sent = 0; snd_completed = 0; wav_init = true; return true; } /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { #ifdef SUPPORTDIRECTX qboolean wavonly; #endif sndinitstat stat; if (!sndsys_registeredcvars) { sndsys_registeredcvars = true; Cvar_RegisterVariable(&snd_wav_partitionsize); } Con_Print ("SndSys_Init: using the Win32 module\n"); #ifdef SUPPORTDIRECTX // COMMANDLINEOPTION: Windows Sound: -wavonly uses wave sound instead of DirectSound wavonly = (COM_CheckParm ("-wavonly") != 0); dsound_init = false; #endif wav_init = false; stat = SIS_FAILURE; // assume DirectSound won't initialize #ifdef SUPPORTDIRECTX // Init DirectSound if (!wavonly) { stat = SndSys_InitDirectSound (requested); if (stat == SIS_SUCCESS) Con_Print("DirectSound initialized\n"); else Con_Print("DirectSound failed to init\n"); } #endif // if DirectSound didn't succeed in initializing, try to initialize // waveOut sound, unless DirectSound failed because the hardware is // already allocated (in which case the user has already chosen not // to have sound) #ifdef SUPPORTDIRECTX if (!dsound_init && (stat != SIS_NOTAVAIL)) #endif { if (SndSys_InitMmsystem (requested)) Con_Print("Wave sound (MMSYSTEM) initialized\n"); else Con_Print("Wave sound failed to init\n"); } #ifdef SUPPORTDIRECTX return (dsound_init || wav_init); #else return wav_init; #endif } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown (void) { #ifdef SUPPORTDIRECTX if (pDSBuf) { IDirectSoundBuffer_Stop(pDSBuf); IDirectSoundBuffer_Release(pDSBuf); } // only release primary buffer if it's not also the mixing buffer we just released if (pDSPBuf && (pDSBuf != pDSPBuf)) { IDirectSoundBuffer_Release(pDSPBuf); } if (pDS) { IDirectSound_SetCooperativeLevel (pDS, mainwindow, DSSCL_NORMAL); IDirectSound_Release(pDS); } #endif if (hWaveOut) { waveOutReset (hWaveOut); if (lpWaveHdr) { unsigned int i; for (i=0 ; i< WAV_BUFFERS ; i++) waveOutUnprepareHeader (hWaveOut, lpWaveHdr+i, sizeof(WAVEHDR)); } waveOutClose (hWaveOut); if (hWaveHdr) { GlobalUnlock(hWaveHdr); GlobalFree(hWaveHdr); } if (hData) { GlobalUnlock(hData); GlobalFree(hData); } } if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } #ifdef SUPPORTDIRECTX pDS = NULL; pDSBuf = NULL; pDSPBuf = NULL; dsound_init = false; #endif hWaveOut = 0; hData = 0; hWaveHdr = 0; lpData = NULL; lpWaveHdr = NULL; wav_init = false; } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { LPWAVEHDR h; int wResult; // DirectSound doesn't need this if (!wav_init) return; paintpot += (snd_renderbuffer->endframe - prev_painted) * snd_renderbuffer->format.channels * snd_renderbuffer->format.width; if (paintpot > WAV_BUFFERS * wav_buffer_size) paintpot = WAV_BUFFERS * wav_buffer_size; prev_painted = snd_renderbuffer->endframe; // submit new sound blocks while (paintpot > wav_buffer_size) { h = lpWaveHdr + (snd_sent & WAV_MASK); /* * Now the data block can be sent to the output device. The * waveOutWrite function returns immediately and waveform * data is sent to the output device in the background. */ wResult = waveOutWrite(hWaveOut, h, sizeof(WAVEHDR)); if (wResult == MMSYSERR_NOERROR) snd_sent++; else if (wResult == WAVERR_STILLPLAYING) { if(developer_insane.integer) Con_DPrint("waveOutWrite failed (too much sound data)\n"); //h->dwFlags |= WHDR_DONE; //snd_sent++; } else { Con_Printf("waveOutWrite failed, error code %d\n", (int) wResult); SndSys_Shutdown (); return; } paintpot -= wav_buffer_size; } } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { unsigned int factor; factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; #ifdef SUPPORTDIRECTX if (dsound_init) { DWORD dwTime; unsigned int diff; IDirectSoundBuffer_GetCurrentPosition(pDSBuf, &dwTime, NULL); diff = (unsigned int)(dwTime - dwStartTime) % (unsigned int)gSndBufSize; dwStartTime = dwTime; dsound_time += diff / factor; return dsound_time; } #endif if (wav_init) { // Find which sound blocks have completed for (;;) { if (snd_completed == snd_sent) { // Con_DPrint("Sound overrun\n"); break; } if (!(lpWaveHdr[snd_completed & WAV_MASK].dwFlags & WHDR_DONE)) break; snd_completed++; // this buffer has been played } return (snd_completed * wav_buffer_size) / factor; /* * S_PaintAndSubmit: WARNING: newsoundtime (soundtime (275 < 134217707) * apparently this sound time wraps quite early? { MMRESULT res; MMTIME mmtime; mmtime.wType = TIME_SAMPLES; res = waveOutGetPosition(hWaveOut, &mmtime, sizeof(mmtime)); if(res == MMSYSERR_NOERROR) return mmtime.u.sample; } */ } return 0; } #ifdef SUPPORTDIRECTX static DWORD dsound_dwSize; static DWORD dsound_dwSize2; static DWORD *dsound_pbuf; static DWORD *dsound_pbuf2; #endif /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { #ifdef SUPPORTDIRECTX int reps; HRESULT hresult; DWORD dwStatus; if (pDSBuf) { // if the buffer was lost or stopped, restore it and/or restart it if (IDirectSoundBuffer_GetStatus (pDSBuf, &dwStatus) != DS_OK) Con_Print("Couldn't get sound buffer status\n"); if (dwStatus & DSBSTATUS_BUFFERLOST) { Con_Print("DSound buffer is lost!!\n"); IDirectSoundBuffer_Restore (pDSBuf); } if (!(dwStatus & DSBSTATUS_PLAYING)) IDirectSoundBuffer_Play(pDSBuf, 0, 0, DSBPLAY_LOOPING); reps = 0; while ((hresult = IDirectSoundBuffer_Lock(pDSBuf, 0, gSndBufSize, (LPVOID*)&dsound_pbuf, &dsound_dwSize, (LPVOID*)&dsound_pbuf2, &dsound_dwSize2, 0)) != DS_OK) { if (hresult != DSERR_BUFFERLOST) { Con_Print("S_LockBuffer: DS: Lock Sound Buffer Failed\n"); S_Shutdown (); S_Startup (); return false; } if (++reps > 10000) { Con_Print("S_LockBuffer: DS: couldn't restore buffer\n"); S_Shutdown (); S_Startup (); return false; } } if ((void*)dsound_pbuf != snd_renderbuffer->ring) Sys_Error("SndSys_LockRenderBuffer: the ring address has changed!!!\n"); return true; } #endif return wav_init; } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { #ifdef SUPPORTDIRECTX if (pDSBuf) IDirectSoundBuffer_Unlock(pDSBuf, dsound_pbuf, dsound_dwSize, dsound_pbuf2, dsound_dwSize2); #endif } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/snd_mem.c0000664000175000017500000002426213067716222013761 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "snd_main.h" #include "snd_ogg.h" #include "snd_wav.h" /* ==================== Snd_CreateRingBuffer If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself (if "sampleframes" is 0, the function chooses the size). ==================== */ snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer) { snd_ringbuffer_t *ringbuffer; // If the caller provides a buffer, it must give us its size if (sampleframes == 0 && buffer != NULL) return NULL; ringbuffer = (snd_ringbuffer_t*)Mem_Alloc(snd_mempool, sizeof (*ringbuffer)); memset(ringbuffer, 0, sizeof(*ringbuffer)); memcpy(&ringbuffer->format, format, sizeof(ringbuffer->format)); // If we haven't been given a buffer if (buffer == NULL) { unsigned int maxframes; size_t memsize; if (sampleframes == 0) maxframes = (format->speed + 1) / 2; // Make the sound buffer large enough for containing 0.5 sec of sound else maxframes = sampleframes; memsize = maxframes * format->width * format->channels; ringbuffer->ring = (unsigned char *) Mem_Alloc(snd_mempool, memsize); ringbuffer->maxframes = maxframes; } else { ringbuffer->ring = (unsigned char *) buffer; ringbuffer->maxframes = sampleframes; } return ringbuffer; } /* ==================== Snd_CreateSndBuffer ==================== */ snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed) { size_t newsampleframes, memsize; snd_buffer_t* sb; newsampleframes = (size_t) ceil((double)sampleframes * (double)sb_speed / (double)in_format->speed); memsize = newsampleframes * in_format->channels * in_format->width; memsize += sizeof (*sb) - sizeof (sb->samples); sb = (snd_buffer_t*)Mem_Alloc (snd_mempool, memsize); sb->format.channels = in_format->channels; sb->format.width = in_format->width; sb->format.speed = sb_speed; sb->maxframes = (unsigned int)newsampleframes; sb->nbframes = 0; if (!Snd_AppendToSndBuffer (sb, samples, sampleframes, in_format)) { Mem_Free (sb); return NULL; } return sb; } /* ==================== Snd_AppendToSndBuffer ==================== */ qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format) { size_t srclength, outcount; unsigned char *out_data; //Con_DPrintf("ResampleSfx: %d samples @ %dHz -> %d samples @ %dHz\n", // sampleframes, format->speed, outcount, sb->format.speed); // If the formats are incompatible if (sb->format.channels != format->channels || sb->format.width != format->width) { Con_Print("AppendToSndBuffer: incompatible sound formats!\n"); return false; } outcount = (size_t) ((double)sampleframes * (double)sb->format.speed / (double)format->speed); // If the sound buffer is too short if (outcount > sb->maxframes - sb->nbframes) { Con_Print("AppendToSndBuffer: sound buffer too short!\n"); return false; } out_data = &sb->samples[sb->nbframes * sb->format.width * sb->format.channels]; srclength = sampleframes * format->channels; // Trivial case (direct transfer) if (format->speed == sb->format.speed) { if (format->width == 1) { size_t i; for (i = 0; i < srclength; i++) ((signed char*)out_data)[i] = samples[i] - 128; } else // if (format->width == 2) memcpy (out_data, samples, srclength * format->width); } // General case (linear interpolation with a fixed-point fractional // step, 18-bit integer part and 14-bit fractional part) // Can handle up to 2^18 (262144) samples per second (> 96KHz stereo) # define FRACTIONAL_BITS 14 # define FRACTIONAL_MASK ((1 << FRACTIONAL_BITS) - 1) # define INTEGER_BITS (sizeof(samplefrac)*8 - FRACTIONAL_BITS) else { const unsigned int fracstep = (unsigned int)((double)format->speed / sb->format.speed * (1 << FRACTIONAL_BITS)); size_t remain_in = srclength, total_out = 0; unsigned int samplefrac; const unsigned char *in_ptr = samples; unsigned char *out_ptr = out_data; // Check that we can handle one second of that sound if (format->speed * format->channels > (1 << INTEGER_BITS)) { Con_Printf ("ResampleSfx: sound quality too high for resampling (%uHz, %u channel(s))\n", format->speed, format->channels); return 0; } // We work 1 sec at a time to make sure we don't accumulate any // significant error when adding "fracstep" over several seconds, and // also to be able to handle very long sounds. while (total_out < outcount) { size_t tmpcount, interpolation_limit, i, j; unsigned int srcsample; samplefrac = 0; // If more than 1 sec of sound remains to be converted if (outcount - total_out > sb->format.speed) { tmpcount = sb->format.speed; interpolation_limit = tmpcount; // all samples can be interpolated } else { tmpcount = outcount - total_out; interpolation_limit = (int)ceil((double)(((remain_in / format->channels) - 1) << FRACTIONAL_BITS) / fracstep); if (interpolation_limit > tmpcount) interpolation_limit = tmpcount; } // 16 bit samples if (format->width == 2) { const short* in_ptr_short; // Interpolated part for (i = 0; i < interpolation_limit; i++) { srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; in_ptr_short = &((const short*)in_ptr)[srcsample]; for (j = 0; j < format->channels; j++) { int a, b; a = *in_ptr_short; b = *(in_ptr_short + format->channels); *((short*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; in_ptr_short++; out_ptr += sizeof (short); } samplefrac += fracstep; } // Non-interpolated part for (/* nothing */; i < tmpcount; i++) { srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; in_ptr_short = &((const short*)in_ptr)[srcsample]; for (j = 0; j < format->channels; j++) { *((short*)out_ptr) = *in_ptr_short; in_ptr_short++; out_ptr += sizeof (short); } samplefrac += fracstep; } } // 8 bit samples else // if (format->width == 1) { const unsigned char* in_ptr_byte; // Convert up to 1 sec of sound for (i = 0; i < interpolation_limit; i++) { srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; for (j = 0; j < format->channels; j++) { int a, b; a = *in_ptr_byte - 128; b = *(in_ptr_byte + format->channels) - 128; *((signed char*)out_ptr) = (((b - a) * (samplefrac & FRACTIONAL_MASK)) >> FRACTIONAL_BITS) + a; in_ptr_byte++; out_ptr += sizeof (signed char); } samplefrac += fracstep; } // Non-interpolated part for (/* nothing */; i < tmpcount; i++) { srcsample = (samplefrac >> FRACTIONAL_BITS) * format->channels; in_ptr_byte = &((const unsigned char*)in_ptr)[srcsample]; for (j = 0; j < format->channels; j++) { *((signed char*)out_ptr) = *in_ptr_byte - 128; in_ptr_byte++; out_ptr += sizeof (signed char); } samplefrac += fracstep; } } // Update the counters and the buffer position remain_in -= format->speed * format->channels; in_ptr += format->speed * format->channels * format->width; total_out += tmpcount; } } sb->nbframes += (unsigned int)outcount; return true; } //============================================================================= /* ============== S_LoadSound ============== */ qboolean S_LoadSound (sfx_t *sfx, qboolean complain) { char namebuffer[MAX_QPATH + 16]; size_t len; // See if already loaded if (sfx->fetcher != NULL) return true; // If we weren't able to load it previously, no need to retry // Note: S_PrecacheSound clears this flag to cause a retry if (sfx->flags & SFXFLAG_FILEMISSING) return false; // No sound? if (snd_renderbuffer == NULL) return false; // Initialize volume peak to 0; if ReplayGain is supported, the loader will change this away sfx->volume_peak = 0.0; if (developer_loading.integer) Con_Printf("loading sound %s\n", sfx->name); SCR_PushLoadingScreen(true, sfx->name, 1); // LordHavoc: if the sound filename does not begin with sound/, try adding it if (strncasecmp(sfx->name, "sound/", 6)) { dpsnprintf (namebuffer, sizeof(namebuffer), "sound/%s", sfx->name); len = strlen(namebuffer); if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) { if (S_LoadWavFile (namebuffer, sfx)) goto loaded; memcpy (namebuffer + len - 3, "ogg", 4); } if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) { if (OGG_LoadVorbisFile (namebuffer, sfx)) goto loaded; } } // LordHavoc: then try without the added sound/ as wav and ogg dpsnprintf (namebuffer, sizeof(namebuffer), "%s", sfx->name); len = strlen(namebuffer); // request foo.wav: tries foo.wav, then foo.ogg // request foo.ogg: tries foo.ogg only // request foo.mod: tries foo.mod only if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".wav")) { if (S_LoadWavFile (namebuffer, sfx)) goto loaded; memcpy (namebuffer + len - 3, "ogg", 4); } if (len >= 4 && !strcasecmp (namebuffer + len - 4, ".ogg")) { if (OGG_LoadVorbisFile (namebuffer, sfx)) goto loaded; } // Can't load the sound! sfx->flags |= SFXFLAG_FILEMISSING; if (complain) Con_DPrintf("failed to load sound \"%s\"\n", sfx->name); SCR_PopLoadingScreen(false); return false; loaded: SCR_PopLoadingScreen(false); return true; } darkplaces/draw.h0000664000175000017500000002126713067716220013301 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // draw.h -- these are the only functions outside the refresh allowed // to touch the vid buffer #ifndef DRAW_H #define DRAW_H // FIXME: move this stuff to cl_screen typedef struct cachepic_s { // size of pic int width, height; // this flag indicates that it should be loaded and unloaded on demand int autoload; // texture flags to upload with int texflags; // texture may be freed after a while int lastusedframe; // renderer texture to use rtexture_t *tex; // used for hash lookups struct cachepic_s *chain; // flags - CACHEPICFLAG_NEWPIC for example unsigned int flags; // has alpha? qboolean hasalpha; // name of pic char name[MAX_QPATH]; // allow to override/free the texture qboolean allow_free_tex; } cachepic_t; typedef enum cachepicflags_e { CACHEPICFLAG_NOTPERSISTENT = 1, CACHEPICFLAG_QUIET = 2, CACHEPICFLAG_NOCOMPRESSION = 4, CACHEPICFLAG_NOCLAMP = 8, CACHEPICFLAG_NEWPIC = 16, // disables matching texflags check, because a pic created with Draw_NewPic should not be subject to that CACHEPICFLAG_MIPMAP = 32, CACHEPICFLAG_NEAREST = 64 // force nearest filtering instead of linear } cachepicflags_t; void Draw_Init (void); void Draw_Frame (void); cachepic_t *Draw_CachePic_Flags (const char *path, unsigned int cachepicflags); cachepic_t *Draw_CachePic (const char *path); // standard function with no options, used throughout engine // create or update a pic's image cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels); // free the texture memory used by a pic void Draw_FreePic(const char *picname); // a triangle mesh.. // each vertex is 3 floats // each texcoord is 2 floats // each color is 4 floats typedef struct drawqueuemesh_s { rtexture_t *texture; int num_triangles; int num_vertices; int *data_element3i; unsigned short *data_element3s; float *data_vertex3f; float *data_texcoord2f; float *data_color4f; } drawqueuemesh_t; enum drawqueue_drawflag_e { DRAWFLAG_NORMAL, DRAWFLAG_ADDITIVE, DRAWFLAG_MODULATE, DRAWFLAG_2XMODULATE, DRAWFLAG_SCREEN, DRAWFLAG_NUMFLAGS, DRAWFLAG_MASK = 0xFF, // ONLY R_BeginPolygon() DRAWFLAG_MIPMAP = 0x100, // ONLY R_BeginPolygon() DRAWFLAG_NOGAMMA = 0x200 // ONLY R_DrawQSuperPic() }; #define DRAWFLAGS_BLEND 0xFF /* this matches all blending flags */ typedef struct ft2_settings_s { float scale, voffset; // cvar parameters (only read on loadfont command) int antialias, hinting; float outline, blur, shadowx, shadowy, shadowz; } ft2_settings_t; #define MAX_FONT_SIZES 16 #define MAX_FONT_FALLBACKS 3 typedef struct dp_font_s { rtexture_t *tex; float width_of[256]; // width_of[0] == max width of any char; 1.0f is base width (1/16 of texture width); therefore, all widths have to be <= 1 (does not include scale) float maxwidth; // precalculated max width of the font (includes scale) char texpath[MAX_QPATH]; char title[MAX_QPATH]; int req_face; // requested face index, usually 0 float req_sizes[MAX_FONT_SIZES]; // sizes to render the font with, 0 still defaults to 16 (backward compatibility when loadfont doesn't get a size parameter) and -1 = disabled char fallbacks[MAX_FONT_FALLBACKS][MAX_QPATH]; int fallback_faces[MAX_FONT_FALLBACKS]; struct ft2_font_s *ft2; ft2_settings_t settings; } dp_font_t; typedef struct dp_fonts_s { dp_font_t *f; int maxsize; } dp_fonts_t; extern dp_fonts_t dp_fonts; #define MAX_FONTS 16 // fonts at the start #define FONTS_EXPAND 8 // fonts grow when no free slots #define FONT_DEFAULT (&dp_fonts.f[0]) // should be fixed width #define FONT_CONSOLE (&dp_fonts.f[1]) // REALLY should be fixed width (ls!) #define FONT_SBAR (&dp_fonts.f[2]) // must be fixed width #define FONT_NOTIFY (&dp_fonts.f[3]) // free #define FONT_CHAT (&dp_fonts.f[4]) // free #define FONT_CENTERPRINT (&dp_fonts.f[5]) // free #define FONT_INFOBAR (&dp_fonts.f[6]) // free #define FONT_MENU (&dp_fonts.f[7]) // should be fixed width #define FONT_USER(i) (&dp_fonts.f[8+i]) // userdefined fonts #define MAX_USERFONTS (dp_fonts.maxsize - 8) // shared color tag printing constants #define STRING_COLOR_TAG '^' #define STRING_COLOR_DEFAULT 7 #define STRING_COLOR_DEFAULT_STR "^7" #define STRING_COLOR_RGB_TAG_CHAR 'x' #define STRING_COLOR_RGB_TAG "^x" // all of these functions will set r_defdef.draw2dstage if not in 2D rendering mode (and of course prepare for 2D rendering in that case) // draw an image (or a filled rectangle if pic == NULL) void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags); // draw a rotated image void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags); // draw a filled rectangle (slightly faster than DrawQ_Pic with pic = NULL) void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags); // draw a text string, // with optional color tag support, // returns final unclipped x coordinate // if outcolor is provided the initial color is read from it, and it is updated at the end with the new value at the end of the text (not at the end of the clipped part) // the color is tinted by the provided base color // if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow) extern float DrawQ_Color[4]; float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); float DrawQ_String_Scale(float x, float y, const char *text, size_t maxlen, float sizex, float sizey, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt); float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); // draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags); // draw a triangle mesh void DrawQ_Mesh(drawqueuemesh_t *mesh, int flags, qboolean hasalpha); // set the clipping area void DrawQ_SetClipArea(float x, float y, float width, float height); // reset the clipping area void DrawQ_ResetClipArea(void); // draw a line void DrawQ_Line(float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags); // draw a lot of lines (call R_Mesh_PrepareVertices_Generic first) void DrawQ_Lines(float width, int numlines, int flags, qboolean hasalpha); // draw a line loop void DrawQ_LineLoop(drawqueuemesh_t *mesh, int flags); // resets r_refdef.draw2dstage void DrawQ_Finish(void); void DrawQ_ProcessDrawFlag(int flags, qboolean alpha); // sets GL_DepthMask and GL_BlendFunc void DrawQ_RecalcView(void); // use this when changing r_refdef.view.* from e.g. csqc rtexture_t *Draw_GetPicTexture(cachepic_t *pic); void R_DrawGamma(void); extern rtexturepool_t *drawtexturepool; // used by ft2.c #endif darkplaces/zone.h0000664000175000017500000001220013067716222013304 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef ZONE_H #define ZONE_H extern qboolean mem_bigendian; // div0: heap overflow detection paranoia #define MEMPARANOIA 0 #define POOLNAMESIZE 128 // if set this pool will be printed in memlist reports #define POOLFLAG_TEMP 1 typedef struct memheader_s { // address returned by Chunk_Alloc (may be significantly before this header to satisify alignment) void *baseaddress; // next and previous memheaders in chain belonging to pool struct memheader_s *next; struct memheader_s *prev; // pool this memheader belongs to struct mempool_s *pool; // size of the memory after the header (excluding header and sentinel2) size_t size; // file name and line where Mem_Alloc was called const char *filename; int fileline; // should always be equal to MEMHEADER_SENTINEL_FOR_ADDRESS() unsigned int sentinel; // immediately followed by data, which is followed by another copy of mem_sentinel[] } memheader_t; typedef struct mempool_s { // should always be MEMPOOL_SENTINEL unsigned int sentinel1; // chain of individual memory allocations struct memheader_s *chain; // POOLFLAG_* int flags; // total memory allocated in this pool (inside memheaders) size_t totalsize; // total memory allocated in this pool (actual malloc total) size_t realsize; // updated each time the pool is displayed by memlist, shows change from previous time (unless pool was freed) size_t lastchecksize; // linked into global mempool list struct mempool_s *next; // parent object (used for nested memory pools) struct mempool_s *parent; // file name and line where Mem_AllocPool was called const char *filename; int fileline; // name of the pool char name[POOLNAMESIZE]; // should always be MEMPOOL_SENTINEL unsigned int sentinel2; } mempool_t; #define Mem_Alloc(pool,size) _Mem_Alloc(pool, NULL, size, 16, __FILE__, __LINE__) #define Mem_Memalign(pool,alignment,size) _Mem_Alloc(pool, NULL, size, alignment, __FILE__, __LINE__) #define Mem_Realloc(pool,data,size) _Mem_Alloc(pool, data, size, 16, __FILE__, __LINE__) #define Mem_Free(mem) _Mem_Free(mem, __FILE__, __LINE__) #define Mem_CheckSentinels(data) _Mem_CheckSentinels(data, __FILE__, __LINE__) #define Mem_CheckSentinelsGlobal() _Mem_CheckSentinelsGlobal(__FILE__, __LINE__) #define Mem_AllocPool(name, flags, parent) _Mem_AllocPool(name, flags, parent, __FILE__, __LINE__) #define Mem_FreePool(pool) _Mem_FreePool(pool, __FILE__, __LINE__) #define Mem_EmptyPool(pool) _Mem_EmptyPool(pool, __FILE__, __LINE__) void *_Mem_Alloc(mempool_t *pool, void *data, size_t size, size_t alignment, const char *filename, int fileline); void _Mem_Free(void *data, const char *filename, int fileline); mempool_t *_Mem_AllocPool(const char *name, int flags, mempool_t *parent, const char *filename, int fileline); void _Mem_FreePool(mempool_t **pool, const char *filename, int fileline); void _Mem_EmptyPool(mempool_t *pool, const char *filename, int fileline); void _Mem_CheckSentinels(void *data, const char *filename, int fileline); void _Mem_CheckSentinelsGlobal(const char *filename, int fileline); // if pool is NULL this searches ALL pools for the allocation qboolean Mem_IsAllocated(mempool_t *pool, void *data); char* Mem_strdup (mempool_t *pool, const char* s); typedef struct memexpandablearray_array_s { unsigned char *data; unsigned char *allocflags; size_t numflaggedrecords; } memexpandablearray_array_t; typedef struct memexpandablearray_s { mempool_t *mempool; size_t recordsize; size_t numrecordsperarray; size_t numarrays; size_t maxarrays; memexpandablearray_array_t *arrays; } memexpandablearray_t; void Mem_ExpandableArray_NewArray(memexpandablearray_t *l, mempool_t *mempool, size_t recordsize, int numrecordsperarray); void Mem_ExpandableArray_FreeArray(memexpandablearray_t *l); void *Mem_ExpandableArray_AllocRecord(memexpandablearray_t *l); void Mem_ExpandableArray_FreeRecord(memexpandablearray_t *l, void *record); size_t Mem_ExpandableArray_IndexRange(const memexpandablearray_t *l) DP_FUNC_PURE; void *Mem_ExpandableArray_RecordAtIndex(const memexpandablearray_t *l, size_t index) DP_FUNC_PURE; // used for temporary allocations extern mempool_t *tempmempool; void Memory_Init (void); void Memory_Shutdown (void); void Memory_Init_Commands (void); extern mempool_t *zonemempool; #define Z_Malloc(size) Mem_Alloc(zonemempool,size) #define Z_Free(data) Mem_Free(data) extern struct cvar_s developer_memory; extern struct cvar_s developer_memorydebug; extern struct cvar_s developer_memoryreportlargerthanmb; #endif darkplaces/fs.h0000664000175000017500000001234113067716220012745 0ustar kalevkalev/* DarkPlaces file system Copyright (C) 2003-2005 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef FS_H #define FS_H // ------ Types ------ // typedef struct qfile_s qfile_t; #ifdef WIN32 //typedef long fs_offset_t; // 32bit typedef __int64 fs_offset_t; ///< 64bit (lots of warnings, and read/write still don't take 64bit on win64) #else typedef long long fs_offset_t; #endif // ------ Variables ------ // extern char fs_gamedir [MAX_OSPATH]; extern char fs_basedir [MAX_OSPATH]; extern char fs_userdir [MAX_OSPATH]; // list of active game directories (empty if not running a mod) #define MAX_GAMEDIRS 16 extern int fs_numgamedirs; extern char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH]; // ------ Main functions ------ // // IMPORTANT: the file path is automatically prefixed by the current game directory for // each file created by FS_WriteFile, or opened in "write" or "append" mode by FS_OpenRealFile qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs); // already_loaded may be NULL if caller does not care const char *FS_WhichPack(const char *filename); void FS_CreatePath (char *path); int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking); // uses absolute path qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking); // uses absolute path qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet); qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet); qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet); int FS_Close (qfile_t* file); void FS_RemoveOnClose(qfile_t* file); fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize); fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize); int FS_Print(qfile_t* file, const char *msg); int FS_Printf(qfile_t* file, const char* format, ...) DP_FUNC_PRINTF(2); int FS_VPrintf(qfile_t* file, const char* format, va_list ap); int FS_Getc (qfile_t* file); int FS_UnGetc (qfile_t* file, unsigned char c); int FS_Seek (qfile_t* file, fs_offset_t offset, int whence); fs_offset_t FS_Tell (qfile_t* file); fs_offset_t FS_FileSize (qfile_t* file); void FS_Purge (qfile_t* file); const char *FS_FileWithoutPath (const char *in); const char *FS_FileExtension (const char *in); int FS_CheckNastyPath (const char *path, qboolean isgamedir); extern const char *const fs_checkgamedir_missing; // "(missing)" const char *FS_CheckGameDir(const char *gamedir); // returns NULL if nasty, fs_checkgamedir_missing (exact pointer) if missing typedef struct { char name[MAX_OSPATH]; char description[8192]; } gamedir_t; extern gamedir_t *fs_all_gamedirs; // terminated by entry with empty name extern int fs_all_gamedirs_count; qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing); qboolean FS_IsRegisteredQuakePack(const char *name); int FS_CRCFile(const char *filename, size_t *filesizepointer); void FS_Rescan(void); typedef struct fssearch_s { int numfilenames; char **filenames; // array of filenames char *filenamesbuffer; } fssearch_t; fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet); void FS_FreeSearch(fssearch_t *search); unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer); unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer); qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count); qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len); // ------ Other functions ------ // void FS_StripExtension (const char *in, char *out, size_t size_out); void FS_DefaultExtension (char *path, const char *extension, size_t size_path); #define FS_FILETYPE_NONE 0 #define FS_FILETYPE_FILE 1 #define FS_FILETYPE_DIRECTORY 2 int FS_FileType (const char *filename); // the file can be into a package int FS_SysFileType (const char *filename); // only look for files outside of packages qboolean FS_FileExists (const char *filename); // the file can be into a package qboolean FS_SysFileExists (const char *filename); // only look for files outside of packages unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool); qboolean FS_HasZlib(void); void FS_Init_SelfPack(void); void FS_Init(void); void FS_Shutdown(void); void FS_Init_Commands(void); #endif darkplaces/snd_mix.c0000664000175000017500000004310413067716222013774 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "snd_main.h" extern cvar_t snd_softclip; static portable_sampleframe_t paintbuffer[PAINTBUFFER_SIZE]; static portable_sampleframe_t paintbuffer_unswapped[PAINTBUFFER_SIZE]; extern speakerlayout_t snd_speakerlayout; // for querying the listeners #ifdef CONFIG_VIDEO_CAPTURE static void S_CaptureAVISound(const portable_sampleframe_t *paintbuffer, size_t length) { size_t i; unsigned int j; if (!cls.capturevideo.active) return; // undo whatever swapping the channel layout (swapstereo, ALSA) did for(j = 0; j < snd_speakerlayout.channels; ++j) { unsigned int j0 = snd_speakerlayout.listeners[j].channel_unswapped; for(i = 0; i < length; ++i) paintbuffer_unswapped[i].sample[j0] = paintbuffer[i].sample[j]; } SCR_CaptureVideo_SoundFrame(paintbuffer_unswapped, length); } #endif extern cvar_t snd_softclip; static void S_SoftClipPaintBuffer(portable_sampleframe_t *painted_ptr, int nbframes, int width, int nchannels) { int i; if((snd_softclip.integer == 1 && width <= 2) || snd_softclip.integer > 1) { portable_sampleframe_t *p = painted_ptr; #if 0 /* Soft clipping, the sound of a dream, thanks to Jon Wattes post to Musicdsp.org */ #define SOFTCLIP(x) (x) = sin(bound(-M_PI/2, (x), M_PI/2)) * 0.25 #endif // let's do a simple limiter instead, seems to sound better static float maxvol = 0; maxvol = max(1.0f, maxvol * (1.0f - nbframes / (0.4f * snd_renderbuffer->format.speed))); #define SOFTCLIP(x) if(fabs(x)>maxvol) maxvol=fabs(x); (x) /= maxvol; if (nchannels == 8) // 7.1 surround { for (i = 0;i < nbframes;i++, p++) { SOFTCLIP(p->sample[0]); SOFTCLIP(p->sample[1]); SOFTCLIP(p->sample[2]); SOFTCLIP(p->sample[3]); SOFTCLIP(p->sample[4]); SOFTCLIP(p->sample[5]); SOFTCLIP(p->sample[6]); SOFTCLIP(p->sample[7]); } } else if (nchannels == 6) // 5.1 surround { for (i = 0; i < nbframes; i++, p++) { SOFTCLIP(p->sample[0]); SOFTCLIP(p->sample[1]); SOFTCLIP(p->sample[2]); SOFTCLIP(p->sample[3]); SOFTCLIP(p->sample[4]); SOFTCLIP(p->sample[5]); } } else if (nchannels == 4) // 4.0 surround { for (i = 0; i < nbframes; i++, p++) { SOFTCLIP(p->sample[0]); SOFTCLIP(p->sample[1]); SOFTCLIP(p->sample[2]); SOFTCLIP(p->sample[3]); } } else if (nchannels == 2) // 2.0 stereo { for (i = 0; i < nbframes; i++, p++) { SOFTCLIP(p->sample[0]); SOFTCLIP(p->sample[1]); } } else if (nchannels == 1) // 1.0 mono { for (i = 0; i < nbframes; i++, p++) { SOFTCLIP(p->sample[0]); } } #undef SOFTCLIP } } static void S_ConvertPaintBuffer(portable_sampleframe_t *painted_ptr, void *rb_ptr, int nbframes, int width, int nchannels) { int i, val; // FIXME: add 24bit and 32bit float formats // FIXME: optimize with SSE intrinsics? if (width == 2) // 16bit { short *snd_out = (short*)rb_ptr; if (nchannels == 8) // 7.1 surround { for (i = 0;i < nbframes;i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[6] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[7] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (nchannels == 6) // 5.1 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[4] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[5] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (nchannels == 4) // 4.0 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[2] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[3] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (nchannels == 2) // 2.0 stereo { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); val = (int)(painted_ptr->sample[1] * 32768.0f);*snd_out++ = bound(-32768, val, 32767); } } else if (nchannels == 1) // 1.0 mono { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 16384.0f);*snd_out++ = bound(-32768, val, 32767); } } // noise is really really annoying if (cls.timedemo) memset(rb_ptr, 0, nbframes * nchannels * width); } else // 8bit { unsigned char *snd_out = (unsigned char*)rb_ptr; if (nchannels == 8) // 7.1 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[6] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[7] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (nchannels == 6) // 5.1 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[4] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[5] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (nchannels == 4) // 4.0 surround { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[2] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[3] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (nchannels == 2) // 2.0 stereo { for (i = 0; i < nbframes; i++, painted_ptr++) { val = (int)(painted_ptr->sample[0] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); val = (int)(painted_ptr->sample[1] * 128.0f) + 128; *snd_out++ = bound(0, val, 255); } } else if (nchannels == 1) // 1.0 mono { for (i = 0;i < nbframes;i++, painted_ptr++) { val = (int)((painted_ptr->sample[0] + painted_ptr->sample[1]) * 64.0f) + 128; *snd_out++ = bound(0, val, 255); } } // noise is really really annoying if (cls.timedemo) memset(rb_ptr, 128, nbframes * nchannels); } } /* =============================================================================== CHANNEL MIXING =============================================================================== */ void S_MixToBuffer(void *stream, unsigned int bufferframes) { int channelindex; channel_t *ch; int totalmixframes; unsigned char *outbytes = (unsigned char *) stream; sfx_t *sfx; portable_sampleframe_t *paint; int wantframes; int i; int count; int fetched; int fetch; int istartframe; int iendframe; int ilengthframes; int totallength; int loopstart; int indexfrac; int indexfracstep; #define S_FETCHBUFFERSIZE 4096 float fetchsampleframes[S_FETCHBUFFERSIZE*2]; const float *fetchsampleframe; float vol[SND_LISTENERS]; float lerp[2]; float sample[3]; double posd; double speedd; float maxvol; qboolean looping; qboolean silent; // mix as many times as needed to fill the requested buffer while (bufferframes) { // limit to the size of the paint buffer totalmixframes = min(bufferframes, PAINTBUFFER_SIZE); // clear the paint buffer memset(paintbuffer, 0, totalmixframes * sizeof(paintbuffer[0])); // paint in the channels. // channels with zero volumes still advance in time but don't paint. ch = channels; // cppcheck complains here but it is wrong, channels is a channel_t[MAX_CHANNELS] and not an int for (channelindex = 0;channelindex < (int)total_channels;channelindex++, ch++) { sfx = ch->sfx; if (sfx == NULL) continue; if (!S_LoadSound (sfx, true)) continue; if (ch->flags & CHANNELFLAG_PAUSED) continue; if (!sfx->total_length) continue; // copy the channel information to the stack for reference, otherwise the // values might change during a mix if the spatializer is updating them // (note: this still may get some old and some new values!) posd = ch->position; speedd = ch->mixspeed * sfx->format.speed / snd_renderbuffer->format.speed; for (i = 0;i < SND_LISTENERS;i++) vol[i] = ch->volume[i]; // check total volume level, because we can skip some code on silent sounds but other code must still run (position updates mainly) maxvol = 0; for (i = 0;i < SND_LISTENERS;i++) if(vol[i] > maxvol) maxvol = vol[i]; switch(snd_renderbuffer->format.width) { case 1: // 8bpp silent = maxvol < (1.0f / (256.0f)); // so silent it has zero effect break; case 2: // 16bpp silent = maxvol < (1.0f / (65536.0f)); // so silent it has zero effect break; default: // floating point silent = maxvol < 1.0e-13f; // 130 dB is difference between hearing // threshold and a jackhammer from // working distance. // therefore, anyone who turns up // volume so much they notice this // cutoff, likely already has their // ear-drums blown out anyway. break; } // when doing prologic mixing, some channels invert one side if (ch->prologic_invert == -1) vol[1] *= -1.0f; // get some sfx info in a consistent form totallength = sfx->total_length; loopstart = (int)sfx->loopstart < totallength ? (int)sfx->loopstart : ((ch->flags & CHANNELFLAG_FORCELOOP) ? 0 : totallength); looping = loopstart < totallength; // do the actual paint now (may skip work if silent) paint = paintbuffer; istartframe = 0; for (wantframes = totalmixframes;wantframes > 0;posd += count * speedd, wantframes -= count) { // check if this is a delayed sound if (posd < 0) { // for a delayed sound we have to eat into the delay first count = (int)floor(-posd / speedd) + 1; count = bound(1, count, wantframes); // let the for loop iterator apply the skip continue; } // compute a fetch size that won't overflow our buffer count = wantframes; for (;;) { istartframe = (int)floor(posd); iendframe = (int)floor(posd + (count-1) * speedd); ilengthframes = count > 1 ? (iendframe - istartframe + 2) : 2; if (ilengthframes <= S_FETCHBUFFERSIZE) break; // reduce count by 25% and try again count -= count >> 2; } // zero whole fetch buffer for safety // (floating point noise from uninitialized memory = HORRIBLE) // otherwise we would only need to clear the excess if (!silent) memset(fetchsampleframes, 0, ilengthframes*sfx->format.channels*sizeof(fetchsampleframes[0])); // if looping, do multiple fetches fetched = 0; for (;;) { fetch = min(ilengthframes - fetched, totallength - istartframe); if (fetch > 0) { if (!silent) sfx->fetcher->getsamplesfloat(ch, sfx, istartframe, fetch, fetchsampleframes + fetched*sfx->format.channels); istartframe += fetch; fetched += fetch; } if (istartframe == totallength && looping && fetched < ilengthframes) { // loop and fetch some more posd += loopstart - totallength; istartframe = loopstart; } else { break; } } // set up our fixedpoint resampling variables (float to int conversions are expensive so do not do one per sampleframe) fetchsampleframe = fetchsampleframes; indexfrac = (int)floor((posd - floor(posd)) * 65536.0); indexfracstep = (int)floor(speedd * 65536.0); if (!silent) { if (sfx->format.channels == 2) { // music is stereo #if SND_LISTENERS != 8 #error the following code only supports up to 8 channels, update it #endif if (snd_speakerlayout.channels > 2) { // surround mixing for (i = 0;i < count;i++, paint++) { lerp[1] = indexfrac * (1.0f / 65536.0f); lerp[0] = 1.0f - lerp[1]; sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; sample[2] = (sample[0] + sample[1]) * 0.5f; paint->sample[0] += sample[0] * vol[0]; paint->sample[1] += sample[1] * vol[1]; paint->sample[2] += sample[0] * vol[2]; paint->sample[3] += sample[1] * vol[3]; paint->sample[4] += sample[2] * vol[4]; paint->sample[5] += sample[2] * vol[5]; paint->sample[6] += sample[0] * vol[6]; paint->sample[7] += sample[1] * vol[7]; indexfrac += indexfracstep; fetchsampleframe += 2 * (indexfrac >> 16); indexfrac &= 0xFFFF; } } else { // stereo mixing for (i = 0;i < count;i++, paint++) { lerp[1] = indexfrac * (1.0f / 65536.0f); lerp[0] = 1.0f - lerp[1]; sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[2] * lerp[1]; sample[1] = fetchsampleframe[1] * lerp[0] + fetchsampleframe[3] * lerp[1]; paint->sample[0] += sample[0] * vol[0]; paint->sample[1] += sample[1] * vol[1]; indexfrac += indexfracstep; fetchsampleframe += 2 * (indexfrac >> 16); indexfrac &= 0xFFFF; } } } else if (sfx->format.channels == 1) { // most sounds are mono #if SND_LISTENERS != 8 #error the following code only supports up to 8 channels, update it #endif if (snd_speakerlayout.channels > 2) { // surround mixing for (i = 0;i < count;i++, paint++) { lerp[1] = indexfrac * (1.0f / 65536.0f); lerp[0] = 1.0f - lerp[1]; sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; paint->sample[0] += sample[0] * vol[0]; paint->sample[1] += sample[0] * vol[1]; paint->sample[2] += sample[0] * vol[2]; paint->sample[3] += sample[0] * vol[3]; paint->sample[4] += sample[0] * vol[4]; paint->sample[5] += sample[0] * vol[5]; paint->sample[6] += sample[0] * vol[6]; paint->sample[7] += sample[0] * vol[7]; indexfrac += indexfracstep; fetchsampleframe += (indexfrac >> 16); indexfrac &= 0xFFFF; } } else { // stereo mixing for (i = 0;i < count;i++, paint++) { lerp[1] = indexfrac * (1.0f / 65536.0f); lerp[0] = 1.0f - lerp[1]; sample[0] = fetchsampleframe[0] * lerp[0] + fetchsampleframe[1] * lerp[1]; paint->sample[0] += sample[0] * vol[0]; paint->sample[1] += sample[0] * vol[1]; indexfrac += indexfracstep; fetchsampleframe += (indexfrac >> 16); indexfrac &= 0xFFFF; } } } } } ch->position = posd; if (!looping && istartframe == totallength) S_StopChannel(ch - channels, false, false); } S_SoftClipPaintBuffer(paintbuffer, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); #ifdef CONFIG_VIDEO_CAPTURE if (!snd_usethreadedmixing) S_CaptureAVISound(paintbuffer, totalmixframes); #endif S_ConvertPaintBuffer(paintbuffer, outbytes, totalmixframes, snd_renderbuffer->format.width, snd_renderbuffer->format.channels); // advance the output pointer outbytes += totalmixframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; bufferframes -= totalmixframes; } } darkplaces/collision.c0000664000175000017500000022661713067716216014345 0ustar kalevkalev #include "quakedef.h" #include "polygon.h" #define COLLISION_EDGEDIR_DOT_EPSILON (0.999f) #define COLLISION_EDGECROSS_MINLENGTH2 (1.0f / 4194304.0f) #define COLLISION_SNAPSCALE (32.0f) #define COLLISION_SNAP (1.0f / COLLISION_SNAPSCALE) #define COLLISION_SNAP2 (2.0f / COLLISION_SNAPSCALE) #define COLLISION_PLANE_DIST_EPSILON (2.0f / COLLISION_SNAPSCALE) cvar_t collision_impactnudge = {0, "collision_impactnudge", "0.03125", "how much to back off from the impact"}; cvar_t collision_extendmovelength = {0, "collision_extendmovelength", "16", "internal bias on trace length to ensure detection of collisions within the collision_impactnudge distance so that short moves do not degrade across frames (this does not alter the final trace length)"}; cvar_t collision_extendtraceboxlength = {0, "collision_extendtraceboxlength", "1", "internal bias for tracebox() qc builtin to account for collision_impactnudge (this does not alter the final trace length)"}; cvar_t collision_extendtracelinelength = {0, "collision_extendtracelinelength", "1", "internal bias for traceline() qc builtin to account for collision_impactnudge (this does not alter the final trace length)"}; cvar_t collision_debug_tracelineasbox = {0, "collision_debug_tracelineasbox", "0", "workaround for any bugs in Collision_TraceLineBrushFloat by using Collision_TraceBrushBrushFloat"}; cvar_t collision_cache = {0, "collision_cache", "1", "store results of collision traces for next frame to reuse if possible (optimization)"}; //cvar_t collision_triangle_neighborsides = {0, "collision_triangle_neighborsides", "1", "override automatic side generation if triangle has neighbors with face planes that form a convex edge (perfect solution, but can not work for all edges)"}; cvar_t collision_triangle_bevelsides = {0, "collision_triangle_bevelsides", "0", "generate sloped edge planes on triangles - if 0, see axialedgeplanes"}; cvar_t collision_triangle_axialsides = {0, "collision_triangle_axialsides", "1", "generate axially-aligned edge planes on triangles - otherwise use perpendicular edge planes"}; mempool_t *collision_mempool; void Collision_Init (void) { Cvar_RegisterVariable(&collision_impactnudge); Cvar_RegisterVariable(&collision_extendmovelength); Cvar_RegisterVariable(&collision_extendtracelinelength); Cvar_RegisterVariable(&collision_extendtraceboxlength); Cvar_RegisterVariable(&collision_debug_tracelineasbox); Cvar_RegisterVariable(&collision_cache); // Cvar_RegisterVariable(&collision_triangle_neighborsides); Cvar_RegisterVariable(&collision_triangle_bevelsides); Cvar_RegisterVariable(&collision_triangle_axialsides); collision_mempool = Mem_AllocPool("collision cache", 0, NULL); Collision_Cache_Init(collision_mempool); } static void Collision_PrintBrushAsQHull(colbrushf_t *brush, const char *name) { int i; Con_Printf("3 %s\n%i\n", name, brush->numpoints); for (i = 0;i < brush->numpoints;i++) Con_Printf("%f %f %f\n", brush->points[i].v[0], brush->points[i].v[1], brush->points[i].v[2]); // FIXME: optimize! Con_Printf("4\n%i\n", brush->numplanes); for (i = 0;i < brush->numplanes;i++) Con_Printf("%f %f %f %f\n", brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist); } static void Collision_ValidateBrush(colbrushf_t *brush) { int j, k, pointsoffplanes, pointonplanes, pointswithinsufficientplanes, printbrush; float d; printbrush = false; if (!brush->numpoints) { Con_Print("Collision_ValidateBrush: brush with no points!\n"); printbrush = true; } #if 0 // it's ok for a brush to have one point and no planes... if (brush->numplanes == 0 && brush->numpoints != 1) { Con_Print("Collision_ValidateBrush: brush with no planes and more than one point!\n"); printbrush = true; } #endif if (brush->numplanes) { pointsoffplanes = 0; pointswithinsufficientplanes = 0; for (k = 0;k < brush->numplanes;k++) if (DotProduct(brush->planes[k].normal, brush->planes[k].normal) < 0.0001f) Con_Printf("Collision_ValidateBrush: plane #%i (%f %f %f %f) is degenerate\n", k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); for (j = 0;j < brush->numpoints;j++) { pointonplanes = 0; for (k = 0;k < brush->numplanes;k++) { d = DotProduct(brush->points[j].v, brush->planes[k].normal) - brush->planes[k].dist; if (d > COLLISION_PLANE_DIST_EPSILON) { Con_Printf("Collision_ValidateBrush: point #%i (%f %f %f) infront of plane #%i (%f %f %f %f)\n", j, brush->points[j].v[0], brush->points[j].v[1], brush->points[j].v[2], k, brush->planes[k].normal[0], brush->planes[k].normal[1], brush->planes[k].normal[2], brush->planes[k].dist); printbrush = true; } if (fabs(d) > COLLISION_PLANE_DIST_EPSILON) pointsoffplanes++; else pointonplanes++; } if (pointonplanes < 3) pointswithinsufficientplanes++; } if (pointswithinsufficientplanes) { Con_Print("Collision_ValidateBrush: some points have insufficient planes, every point must be on at least 3 planes to form a corner.\n"); printbrush = true; } if (pointsoffplanes == 0) // all points are on all planes { Con_Print("Collision_ValidateBrush: all points lie on all planes (degenerate, no brush volume!)\n"); printbrush = true; } } if (printbrush) Collision_PrintBrushAsQHull(brush, "unnamed"); } static float nearestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) { float dist, bestdist; if (!numpoints) return 0; bestdist = DotProduct(points->v, normal); points++; while(--numpoints) { dist = DotProduct(points->v, normal); bestdist = min(bestdist, dist); points++; } return bestdist; } static float furthestplanedist_float(const float *normal, const colpointf_t *points, int numpoints) { float dist, bestdist; if (!numpoints) return 0; bestdist = DotProduct(points->v, normal); points++; while(--numpoints) { dist = DotProduct(points->v, normal); bestdist = max(bestdist, dist); points++; } return bestdist; } static void Collision_CalcEdgeDirsForPolygonBrushFloat(colbrushf_t *brush) { int i, j; for (i = 0, j = brush->numpoints - 1;i < brush->numpoints;j = i, i++) VectorSubtract(brush->points[i].v, brush->points[j].v, brush->edgedirs[j].v); } colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes) { // TODO: planesbuf could be replaced by a remapping table int j, k, w, xyzflags; int numpointsbuf = 0, maxpointsbuf = 256, numedgedirsbuf = 0, maxedgedirsbuf = 256, numplanesbuf = 0, maxplanesbuf = 256, numelementsbuf = 0, maxelementsbuf = 256; int isaabb = true; double maxdist; colbrushf_t *brush; colpointf_t pointsbuf[256]; colpointf_t edgedirsbuf[256]; colplanef_t planesbuf[256]; int elementsbuf[1024]; int polypointbuf[256]; int pmaxpoints = 64; int pnumpoints; double p[2][3*64]; #if 0 // enable these if debugging to avoid seeing garbage in unused data- memset(pointsbuf, 0, sizeof(pointsbuf)); memset(edgedirsbuf, 0, sizeof(edgedirsbuf)); memset(planesbuf, 0, sizeof(planesbuf)); memset(elementsbuf, 0, sizeof(elementsbuf)); memset(polypointbuf, 0, sizeof(polypointbuf)); memset(p, 0, sizeof(p)); #endif // check if there are too many planes and skip the brush if (numoriginalplanes >= maxplanesbuf) { Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many planes for buffer\n"); return NULL; } // figure out how large a bounding box we need to properly compute this brush maxdist = 0; for (j = 0;j < numoriginalplanes;j++) maxdist = max(maxdist, fabs(originalplanes[j].dist)); // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 maxdist = floor(maxdist * (4.0 / 1024.0) + 2) * 1024.0; // construct a collision brush (points, planes, and renderable mesh) from // a set of planes, this also optimizes out any unnecessary planes (ones // whose polygon is clipped away by the other planes) for (j = 0;j < numoriginalplanes;j++) { int n; // add the new plane VectorCopy(originalplanes[j].normal, planesbuf[numplanesbuf].normal); planesbuf[numplanesbuf].dist = originalplanes[j].dist; planesbuf[numplanesbuf].q3surfaceflags = originalplanes[j].q3surfaceflags; planesbuf[numplanesbuf].texture = originalplanes[j].texture; numplanesbuf++; // create a large polygon from the plane w = 0; PolygonD_QuadForPlane(p[w], originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist, maxdist); pnumpoints = 4; // clip it by all other planes for (k = 0;k < numoriginalplanes && pnumpoints >= 3 && pnumpoints <= pmaxpoints;k++) { // skip the plane this polygon // (nothing happens if it is processed, this is just an optimization) if (k != j) { // we want to keep the inside of the brush plane so we flip // the cutting plane PolygonD_Divide(pnumpoints, p[w], -originalplanes[k].normal[0], -originalplanes[k].normal[1], -originalplanes[k].normal[2], -originalplanes[k].dist, COLLISION_PLANE_DIST_EPSILON, pmaxpoints, p[!w], &pnumpoints, 0, NULL, NULL, NULL); w = !w; } } // if nothing is left, skip it if (pnumpoints < 3) { //Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon for plane %f %f %f %f clipped away\n", originalplanes[j].normal[0], originalplanes[j].normal[1], originalplanes[j].normal[2], originalplanes[j].dist); continue; } for (k = 0;k < pnumpoints;k++) { int l, m; m = 0; for (l = 0;l < numoriginalplanes;l++) if (fabs(DotProduct(&p[w][k*3], originalplanes[l].normal) - originalplanes[l].dist) < COLLISION_PLANE_DIST_EPSILON) m++; if (m < 3) break; } if (k < pnumpoints) { Con_DPrintf("Collision_NewBrushFromPlanes: warning: polygon point does not lie on at least 3 planes\n"); //return NULL; } // check if there are too many polygon vertices for buffer if (pnumpoints > pmaxpoints) { Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); return NULL; } // check if there are too many triangle elements for buffer if (numelementsbuf + (pnumpoints - 2) * 3 > maxelementsbuf) { Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many triangle elements for buffer\n"); return NULL; } // add the unique points for this polygon for (k = 0;k < pnumpoints;k++) { int m; float v[3]; // downgrade to float precision before comparing VectorCopy(&p[w][k*3], v); // check if there is already a matching point (no duplicates) for (m = 0;m < numpointsbuf;m++) if (VectorDistance2(v, pointsbuf[m].v) < COLLISION_SNAP2) break; // if there is no match, add a new one if (m == numpointsbuf) { // check if there are too many and skip the brush if (numpointsbuf >= maxpointsbuf) { Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many points for buffer\n"); return NULL; } // add the new one VectorCopy(&p[w][k*3], pointsbuf[numpointsbuf].v); numpointsbuf++; } // store the index into a buffer polypointbuf[k] = m; } // add the triangles for the polygon // (this particular code makes a triangle fan) for (k = 0;k < pnumpoints - 2;k++) { elementsbuf[numelementsbuf++] = polypointbuf[0]; elementsbuf[numelementsbuf++] = polypointbuf[k + 1]; elementsbuf[numelementsbuf++] = polypointbuf[k + 2]; } // add the unique edgedirs for this polygon for (k = 0, n = pnumpoints-1;k < pnumpoints;n = k, k++) { int m; float dir[3]; // downgrade to float precision before comparing VectorSubtract(&p[w][k*3], &p[w][n*3], dir); VectorNormalize(dir); // check if there is already a matching edgedir (no duplicates) for (m = 0;m < numedgedirsbuf;m++) if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) break; // skip this if there is if (m < numedgedirsbuf) continue; // try again with negated edgedir VectorNegate(dir, dir); // check if there is already a matching edgedir (no duplicates) for (m = 0;m < numedgedirsbuf;m++) if (DotProduct(dir, edgedirsbuf[m].v) >= COLLISION_EDGEDIR_DOT_EPSILON) break; // if there is no match, add a new one if (m == numedgedirsbuf) { // check if there are too many and skip the brush if (numedgedirsbuf >= maxedgedirsbuf) { Con_DPrint("Collision_NewBrushFromPlanes: failed to build collision brush: too many edgedirs for buffer\n"); return NULL; } // add the new one VectorCopy(dir, edgedirsbuf[numedgedirsbuf].v); numedgedirsbuf++; } } // if any normal is not purely axial, it's not an axis-aligned box if (isaabb && (originalplanes[j].normal[0] == 0) + (originalplanes[j].normal[1] == 0) + (originalplanes[j].normal[2] == 0) < 2) isaabb = false; } // if nothing is left, there's nothing to allocate if (numplanesbuf < 4) { Con_DPrintf("Collision_NewBrushFromPlanes: failed to build collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); return NULL; } // if no triangles or points could be constructed, then this routine failed but the brush is not discarded if (numelementsbuf < 12 || numpointsbuf < 4) Con_DPrintf("Collision_NewBrushFromPlanes: unable to rebuild triangles/points for collision brush: %i triangles, %i planes (input was %i planes), %i vertices\n", numelementsbuf / 3, numplanesbuf, numoriginalplanes, numpointsbuf); // validate plane distances for (j = 0;j < numplanesbuf;j++) { float d = furthestplanedist_float(planesbuf[j].normal, pointsbuf, numpointsbuf); if (fabs(planesbuf[j].dist - d) > COLLISION_PLANE_DIST_EPSILON) Con_DPrintf("plane %f %f %f %f mismatches dist %f\n", planesbuf[j].normal[0], planesbuf[j].normal[1], planesbuf[j].normal[2], planesbuf[j].dist, d); } // allocate the brush and copy to it brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colpointf_t) * numpointsbuf + sizeof(colpointf_t) * numedgedirsbuf + sizeof(colplanef_t) * numplanesbuf + sizeof(int) * numelementsbuf); brush->isaabb = isaabb; brush->hasaabbplanes = hasaabbplanes; brush->supercontents = supercontents; brush->numplanes = numplanesbuf; brush->numedgedirs = numedgedirsbuf; brush->numpoints = numpointsbuf; brush->numtriangles = numelementsbuf / 3; brush->planes = (colplanef_t *)(brush + 1); brush->points = (colpointf_t *)(brush->planes + brush->numplanes); brush->edgedirs = (colpointf_t *)(brush->points + brush->numpoints); brush->elements = (int *)(brush->points + brush->numpoints); brush->q3surfaceflags = q3surfaceflags; brush->texture = texture; for (j = 0;j < brush->numpoints;j++) { brush->points[j].v[0] = pointsbuf[j].v[0]; brush->points[j].v[1] = pointsbuf[j].v[1]; brush->points[j].v[2] = pointsbuf[j].v[2]; } for (j = 0;j < brush->numedgedirs;j++) { brush->edgedirs[j].v[0] = edgedirsbuf[j].v[0]; brush->edgedirs[j].v[1] = edgedirsbuf[j].v[1]; brush->edgedirs[j].v[2] = edgedirsbuf[j].v[2]; } for (j = 0;j < brush->numplanes;j++) { brush->planes[j].normal[0] = planesbuf[j].normal[0]; brush->planes[j].normal[1] = planesbuf[j].normal[1]; brush->planes[j].normal[2] = planesbuf[j].normal[2]; brush->planes[j].dist = planesbuf[j].dist; brush->planes[j].q3surfaceflags = planesbuf[j].q3surfaceflags; brush->planes[j].texture = planesbuf[j].texture; } for (j = 0;j < brush->numtriangles * 3;j++) brush->elements[j] = elementsbuf[j]; xyzflags = 0; VectorClear(brush->mins); VectorClear(brush->maxs); for (j = 0;j < min(6, numoriginalplanes);j++) { if (originalplanes[j].normal[0] == 1) {xyzflags |= 1;brush->maxs[0] = originalplanes[j].dist;} else if (originalplanes[j].normal[0] == -1) {xyzflags |= 2;brush->mins[0] = -originalplanes[j].dist;} else if (originalplanes[j].normal[1] == 1) {xyzflags |= 4;brush->maxs[1] = originalplanes[j].dist;} else if (originalplanes[j].normal[1] == -1) {xyzflags |= 8;brush->mins[1] = -originalplanes[j].dist;} else if (originalplanes[j].normal[2] == 1) {xyzflags |= 16;brush->maxs[2] = originalplanes[j].dist;} else if (originalplanes[j].normal[2] == -1) {xyzflags |= 32;brush->mins[2] = -originalplanes[j].dist;} } // if not all xyzflags were set, then this is not a brush from q3map/q3map2, and needs reconstruction of the bounding box // (this case works for any brush with valid points, but sometimes brushes are not reconstructed properly and hence the points are not valid, so this is reserved as a fallback case) if (xyzflags != 63) { VectorCopy(brush->points[0].v, brush->mins); VectorCopy(brush->points[0].v, brush->maxs); for (j = 1;j < brush->numpoints;j++) { brush->mins[0] = min(brush->mins[0], brush->points[j].v[0]); brush->mins[1] = min(brush->mins[1], brush->points[j].v[1]); brush->mins[2] = min(brush->mins[2], brush->points[j].v[2]); brush->maxs[0] = max(brush->maxs[0], brush->points[j].v[0]); brush->maxs[1] = max(brush->maxs[1], brush->points[j].v[1]); brush->maxs[2] = max(brush->maxs[2], brush->points[j].v[2]); } } brush->mins[0] -= 1; brush->mins[1] -= 1; brush->mins[2] -= 1; brush->maxs[0] += 1; brush->maxs[1] += 1; brush->maxs[2] += 1; Collision_ValidateBrush(brush); return brush; } void Collision_CalcPlanesForTriangleBrushFloat(colbrushf_t *brush) { float edge0[3], edge1[3], edge2[3]; colpointf_t *p; TriangleNormal(brush->points[0].v, brush->points[1].v, brush->points[2].v, brush->planes[0].normal); if (DotProduct(brush->planes[0].normal, brush->planes[0].normal) < 0.0001f) { // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) // note that some of these exist in q3bsp bspline patches brush->numplanes = 0; return; } // there are 5 planes (front, back, sides) and 3 edges brush->numplanes = 5; brush->numedgedirs = 3; VectorNormalize(brush->planes[0].normal); brush->planes[0].dist = DotProduct(brush->points->v, brush->planes[0].normal); VectorNegate(brush->planes[0].normal, brush->planes[1].normal); brush->planes[1].dist = -brush->planes[0].dist; // edge directions are easy to calculate VectorSubtract(brush->points[2].v, brush->points[0].v, edge0); VectorSubtract(brush->points[0].v, brush->points[1].v, edge1); VectorSubtract(brush->points[1].v, brush->points[2].v, edge2); VectorCopy(edge0, brush->edgedirs[0].v); VectorCopy(edge1, brush->edgedirs[1].v); VectorCopy(edge2, brush->edgedirs[2].v); // now select an algorithm to generate the side planes if (collision_triangle_bevelsides.integer) { // use 45 degree slopes at the edges of the triangle to make a sinking trace error turn into "riding up" the slope rather than getting stuck CrossProduct(edge0, brush->planes->normal, brush->planes[2].normal); CrossProduct(edge1, brush->planes->normal, brush->planes[3].normal); CrossProduct(edge2, brush->planes->normal, brush->planes[4].normal); VectorNormalize(brush->planes[2].normal); VectorNormalize(brush->planes[3].normal); VectorNormalize(brush->planes[4].normal); VectorAdd(brush->planes[2].normal, brush->planes[0].normal, brush->planes[2].normal); VectorAdd(brush->planes[3].normal, brush->planes[0].normal, brush->planes[3].normal); VectorAdd(brush->planes[4].normal, brush->planes[0].normal, brush->planes[4].normal); VectorNormalize(brush->planes[2].normal); VectorNormalize(brush->planes[3].normal); VectorNormalize(brush->planes[4].normal); } else if (collision_triangle_axialsides.integer) { float projectionnormal[3], projectionedge0[3], projectionedge1[3], projectionedge2[3]; int i, best; float dist, bestdist; bestdist = fabs(brush->planes[0].normal[0]); best = 0; for (i = 1;i < 3;i++) { dist = fabs(brush->planes[0].normal[i]); if (bestdist < dist) { bestdist = dist; best = i; } } VectorClear(projectionnormal); if (brush->planes[0].normal[best] < 0) projectionnormal[best] = -1; else projectionnormal[best] = 1; VectorCopy(edge0, projectionedge0); VectorCopy(edge1, projectionedge1); VectorCopy(edge2, projectionedge2); projectionedge0[best] = 0; projectionedge1[best] = 0; projectionedge2[best] = 0; CrossProduct(projectionedge0, projectionnormal, brush->planes[2].normal); CrossProduct(projectionedge1, projectionnormal, brush->planes[3].normal); CrossProduct(projectionedge2, projectionnormal, brush->planes[4].normal); VectorNormalize(brush->planes[2].normal); VectorNormalize(brush->planes[3].normal); VectorNormalize(brush->planes[4].normal); } else { CrossProduct(edge0, brush->planes->normal, brush->planes[2].normal); CrossProduct(edge1, brush->planes->normal, brush->planes[3].normal); CrossProduct(edge2, brush->planes->normal, brush->planes[4].normal); VectorNormalize(brush->planes[2].normal); VectorNormalize(brush->planes[3].normal); VectorNormalize(brush->planes[4].normal); } brush->planes[2].dist = DotProduct(brush->points[2].v, brush->planes[2].normal); brush->planes[3].dist = DotProduct(brush->points[0].v, brush->planes[3].normal); brush->planes[4].dist = DotProduct(brush->points[1].v, brush->planes[4].normal); if (developer_extra.integer) { int i; // validity check - will be disabled later Collision_ValidateBrush(brush); for (i = 0;i < brush->numplanes;i++) { int j; for (j = 0, p = brush->points;j < brush->numpoints;j++, p++) if (DotProduct(p->v, brush->planes[i].normal) > brush->planes[i].dist + COLLISION_PLANE_DIST_EPSILON) Con_DPrintf("Error in brush plane generation, plane %i\n", i); } } } colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture) { colbrushf_t *brush; brush = (colbrushf_t *)Mem_Alloc(mempool, sizeof(colbrushf_t) + sizeof(colplanef_t) * (numpoints + 2) + sizeof(colpointf_t) * numpoints); brush->isaabb = false; brush->hasaabbplanes = false; brush->supercontents = supercontents; brush->numpoints = numpoints; brush->numedgedirs = numpoints; brush->numplanes = numpoints + 2; brush->planes = (colplanef_t *)(brush + 1); brush->points = (colpointf_t *)points; brush->edgedirs = (colpointf_t *)(brush->planes + brush->numplanes); brush->q3surfaceflags = q3surfaceflags; brush->texture = texture; Sys_Error("Collision_AllocBrushFromPermanentPolygonFloat: FIXME: this code needs to be updated to generate a mesh..."); return brush; } // NOTE: start and end of each brush pair must have same numplanes/numpoints void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *trace_start, const colbrushf_t *trace_end, const colbrushf_t *other_start, const colbrushf_t *other_end) { int nplane, nplane2, nedge1, nedge2, hitq3surfaceflags = 0; int tracenumedgedirs = trace_start->numedgedirs; //int othernumedgedirs = other_start->numedgedirs; int tracenumpoints = trace_start->numpoints; int othernumpoints = other_start->numpoints; int numplanes1 = other_start->numplanes; int numplanes2 = numplanes1 + trace_start->numplanes; int numplanes3 = numplanes2 + trace_start->numedgedirs * other_start->numedgedirs * 2; vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; vec4_t startplane; vec4_t endplane; vec4_t newimpactplane; const texture_t *hittexture = NULL; vec_t startdepth = 1; vec3_t startdepthnormal; VectorClear(startdepthnormal); Vector4Clear(newimpactplane); // fast case for AABB vs compiled brushes (which begin with AABB planes and also have precomputed bevels for AABB collisions) if (trace_start->isaabb && other_start->hasaabbplanes) numplanes3 = numplanes2 = numplanes1; // Separating Axis Theorem: // if a supporting vector (plane normal) can be found that separates two // objects, they are not colliding. // // Minkowski Sum: // reduce the size of one object to a point while enlarging the other to // represent the space that point can not occupy. // // try every plane we can construct between the two brushes and measure // the distance between them. for (nplane = 0;nplane < numplanes3;nplane++) { if (nplane < numplanes1) { nplane2 = nplane; VectorCopy(other_start->planes[nplane2].normal, startplane); VectorCopy(other_end->planes[nplane2].normal, endplane); } else if (nplane < numplanes2) { nplane2 = nplane - numplanes1; VectorCopy(trace_start->planes[nplane2].normal, startplane); VectorCopy(trace_end->planes[nplane2].normal, endplane); } else { // pick an edgedir from each brush and cross them nplane2 = nplane - numplanes2; nedge1 = nplane2 >> 1; nedge2 = nedge1 / tracenumedgedirs; nedge1 -= nedge2 * tracenumedgedirs; if (nplane2 & 1) { CrossProduct(trace_start->edgedirs[nedge1].v, other_start->edgedirs[nedge2].v, startplane); CrossProduct(trace_end->edgedirs[nedge1].v, other_end->edgedirs[nedge2].v, endplane); } else { CrossProduct(other_start->edgedirs[nedge2].v, trace_start->edgedirs[nedge1].v, startplane); CrossProduct(other_end->edgedirs[nedge2].v, trace_end->edgedirs[nedge1].v, endplane); } if (VectorLength2(startplane) < COLLISION_EDGECROSS_MINLENGTH2 || VectorLength2(endplane) < COLLISION_EDGECROSS_MINLENGTH2) continue; // degenerate crossproducts VectorNormalize(startplane); VectorNormalize(endplane); } startplane[3] = furthestplanedist_float(startplane, other_start->points, othernumpoints); endplane[3] = furthestplanedist_float(startplane, other_end->points, othernumpoints); startdist = nearestplanedist_float(startplane, trace_start->points, tracenumpoints) - startplane[3]; enddist = nearestplanedist_float(endplane, trace_end->points, tracenumpoints) - endplane[3]; //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); // aside from collisions, this is also used for error correction if (startdist <= 0.0f && nplane < numplanes1 && (startdepth < startdist || startdepth == 1)) { startdepth = startdist; VectorCopy(startplane, startdepthnormal); } if (startdist > enddist) { // moving into brush if (enddist > 0.0f) return; if (startdist >= 0) { // enter imove = 1 / (startdist - enddist); f = startdist * imove; // check if this will reduce the collision time range if (enterfrac < f) { // reduced collision time range enterfrac = f; // if the collision time range is now empty, no collision if (enterfrac > leavefrac) return; // calculate the nudged fraction and impact normal we'll // need if we accept this collision later enterfrac2 = (startdist - collision_impactnudge.value) * imove; // if the collision would be further away than the trace's // existing collision data, we don't care about this // collision if (enterfrac2 >= trace->fraction) return; ie = 1.0f - enterfrac; newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; if (nplane < numplanes1) { // use the plane from other nplane2 = nplane; hitq3surfaceflags = other_start->planes[nplane2].q3surfaceflags; hittexture = other_start->planes[nplane2].texture; } else if (nplane < numplanes2) { // use the plane from trace nplane2 = nplane - numplanes1; hitq3surfaceflags = trace_start->planes[nplane2].q3surfaceflags; hittexture = trace_start->planes[nplane2].texture; } else { hitq3surfaceflags = other_start->q3surfaceflags; hittexture = other_start->texture; } } } } else { // moving out of brush if (startdist >= 0) return; if (enddist > 0) { // leave f = startdist / (startdist - enddist); // check if this will reduce the collision time range if (leavefrac > f) { // reduced collision time range leavefrac = f; // if the collision time range is now empty, no collision if (enterfrac > leavefrac) return; } } } } // at this point we know the trace overlaps the brush because it was not // rejected at any point in the loop above // see if the trace started outside the brush or not if (enterfrac > -1) { // started outside, and overlaps, therefore there is a collision here // store out the impact information if ((trace->hitsupercontentsmask & other_start->supercontents) && !(trace->skipsupercontentsmask & other_start->supercontents)) { trace->hitsupercontents = other_start->supercontents; trace->hitq3surfaceflags = hitq3surfaceflags; trace->hittexture = hittexture; trace->fraction = bound(0, enterfrac2, 1); VectorCopy(newimpactplane, trace->plane.normal); trace->plane.dist = newimpactplane[3]; } } else { // started inside, update startsolid and friends trace->startsupercontents |= other_start->supercontents; if ((trace->hitsupercontentsmask & other_start->supercontents) && !(trace->skipsupercontentsmask & other_start->supercontents)) { trace->startsolid = true; if (leavefrac < 1) trace->allsolid = true; VectorCopy(newimpactplane, trace->plane.normal); trace->plane.dist = newimpactplane[3]; if (trace->startdepth > startdepth) { trace->startdepth = startdepth; VectorCopy(startdepthnormal, trace->startdepthnormal); } } } } // NOTE: start and end of each brush pair must have same numplanes/numpoints void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *other_start, const colbrushf_t *other_end) { int nplane, hitq3surfaceflags = 0; int numplanes = other_start->numplanes; vec_t enterfrac = -1, leavefrac = 1, startdist, enddist, ie, f, imove, enterfrac2 = -1; vec4_t startplane; vec4_t endplane; vec4_t newimpactplane; const texture_t *hittexture = NULL; vec_t startdepth = 1; vec3_t startdepthnormal; if (collision_debug_tracelineasbox.integer) { colboxbrushf_t thisbrush_start, thisbrush_end; Collision_BrushForBox(&thisbrush_start, linestart, linestart, 0, 0, NULL); Collision_BrushForBox(&thisbrush_end, lineend, lineend, 0, 0, NULL); Collision_TraceBrushBrushFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, other_start, other_end); return; } VectorClear(startdepthnormal); Vector4Clear(newimpactplane); // Separating Axis Theorem: // if a supporting vector (plane normal) can be found that separates two // objects, they are not colliding. // // Minkowski Sum: // reduce the size of one object to a point while enlarging the other to // represent the space that point can not occupy. // // try every plane we can construct between the two brushes and measure // the distance between them. for (nplane = 0;nplane < numplanes;nplane++) { VectorCopy(other_start->planes[nplane].normal, startplane); startplane[3] = other_start->planes[nplane].dist; VectorCopy(other_end->planes[nplane].normal, endplane); endplane[3] = other_end->planes[nplane].dist; startdist = DotProduct(linestart, startplane) - startplane[3]; enddist = DotProduct(lineend, endplane) - endplane[3]; //Con_Printf("%c%i: startdist = %f, enddist = %f, startdist / (startdist - enddist) = %f\n", nplane2 != nplane ? 'b' : 'a', nplane2, startdist, enddist, startdist / (startdist - enddist)); // aside from collisions, this is also used for error correction if (startdist <= 0.0f && (startdepth < startdist || startdepth == 1)) { startdepth = startdist; VectorCopy(startplane, startdepthnormal); } if (startdist > enddist) { // moving into brush if (enddist > 0.0f) return; if (startdist > 0) { // enter imove = 1 / (startdist - enddist); f = startdist * imove; // check if this will reduce the collision time range if (enterfrac < f) { // reduced collision time range enterfrac = f; // if the collision time range is now empty, no collision if (enterfrac > leavefrac) return; // calculate the nudged fraction and impact normal we'll // need if we accept this collision later enterfrac2 = (startdist - collision_impactnudge.value) * imove; // if the collision would be further away than the trace's // existing collision data, we don't care about this // collision if (enterfrac2 >= trace->fraction) return; ie = 1.0f - enterfrac; newimpactplane[0] = startplane[0] * ie + endplane[0] * enterfrac; newimpactplane[1] = startplane[1] * ie + endplane[1] * enterfrac; newimpactplane[2] = startplane[2] * ie + endplane[2] * enterfrac; newimpactplane[3] = startplane[3] * ie + endplane[3] * enterfrac; hitq3surfaceflags = other_start->planes[nplane].q3surfaceflags; hittexture = other_start->planes[nplane].texture; } } } else { // moving out of brush if (startdist > 0) return; if (enddist > 0) { // leave f = startdist / (startdist - enddist); // check if this will reduce the collision time range if (leavefrac > f) { // reduced collision time range leavefrac = f; // if the collision time range is now empty, no collision if (enterfrac > leavefrac) return; } } } } // at this point we know the trace overlaps the brush because it was not // rejected at any point in the loop above // see if the trace started outside the brush or not if (enterfrac > -1) { // started outside, and overlaps, therefore there is a collision here // store out the impact information if ((trace->hitsupercontentsmask & other_start->supercontents) && !(trace->skipsupercontentsmask & other_start->supercontents)) { trace->hitsupercontents = other_start->supercontents; trace->hitq3surfaceflags = hitq3surfaceflags; trace->hittexture = hittexture; trace->fraction = bound(0, enterfrac2, 1); VectorCopy(newimpactplane, trace->plane.normal); trace->plane.dist = newimpactplane[3]; } } else { // started inside, update startsolid and friends trace->startsupercontents |= other_start->supercontents; if ((trace->hitsupercontentsmask & other_start->supercontents) && !(trace->skipsupercontentsmask & other_start->supercontents)) { trace->startsolid = true; if (leavefrac < 1) trace->allsolid = true; VectorCopy(newimpactplane, trace->plane.normal); trace->plane.dist = newimpactplane[3]; if (trace->startdepth > startdepth) { trace->startdepth = startdepth; VectorCopy(startdepthnormal, trace->startdepthnormal); } } } } qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush) { int nplane; const colplanef_t *plane; if (!BoxesOverlap(point, point, brush->mins, brush->maxs)) return false; for (nplane = 0, plane = brush->planes;nplane < brush->numplanes;nplane++, plane++) if (DotProduct(plane->normal, point) > plane->dist) return false; return true; } void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush) { if (!Collision_PointInsideBrushFloat(point, thatbrush)) return; trace->startsupercontents |= thatbrush->supercontents; if ((trace->hitsupercontentsmask & thatbrush->supercontents) && !(trace->skipsupercontentsmask & thatbrush->supercontents)) { trace->startsolid = true; trace->allsolid = true; } } static void Collision_SnapCopyPoints(int numpoints, const colpointf_t *in, colpointf_t *out, float fractionprecision, float invfractionprecision) { int i; for (i = 0;i < numpoints;i++) { out[i].v[0] = floor(in[i].v[0] * fractionprecision + 0.5f) * invfractionprecision; out[i].v[1] = floor(in[i].v[1] * fractionprecision + 0.5f) * invfractionprecision; out[i].v[2] = floor(in[i].v[2] * fractionprecision + 0.5f) * invfractionprecision; } } void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) { int i; colpointf_t points[3]; colpointf_t edgedirs[3]; colplanef_t planes[5]; colbrushf_t brush; memset(&brush, 0, sizeof(brush)); brush.isaabb = false; brush.hasaabbplanes = false; brush.numpoints = 3; brush.numedgedirs = 3; brush.numplanes = 5; brush.points = points; brush.edgedirs = edgedirs; brush.planes = planes; brush.supercontents = supercontents; brush.q3surfaceflags = q3surfaceflags; brush.texture = texture; for (i = 0;i < brush.numplanes;i++) { brush.planes[i].q3surfaceflags = q3surfaceflags; brush.planes[i].texture = texture; } if(stride > 0) { int k, cnt, tri; cnt = (numtriangles + stride - 1) / stride; for(i = 0; i < cnt; ++i) { if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) { for(k = 0; k < stride; ++k) { tri = i * stride + k; if(tri >= numtriangles) break; VectorCopy(vertex3f + element3i[tri * 3 + 0] * 3, points[0].v); VectorCopy(vertex3f + element3i[tri * 3 + 1] * 3, points[1].v); VectorCopy(vertex3f + element3i[tri * 3 + 2] * 3, points[2].v); Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); Collision_CalcPlanesForTriangleBrushFloat(&brush); //Collision_PrintBrushAsQHull(&brush, "brush"); Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); } } } } else if(stride == 0) { for (i = 0;i < numtriangles;i++, element3i += 3) { if (TriangleBBoxOverlapsBox(vertex3f + element3i[0]*3, vertex3f + element3i[1]*3, vertex3f + element3i[2]*3, segmentmins, segmentmaxs)) { VectorCopy(vertex3f + element3i[0] * 3, points[0].v); VectorCopy(vertex3f + element3i[1] * 3, points[1].v); VectorCopy(vertex3f + element3i[2] * 3, points[2].v); Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); Collision_CalcPlanesForTriangleBrushFloat(&brush); //Collision_PrintBrushAsQHull(&brush, "brush"); Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); } } } else { for (i = 0;i < numtriangles;i++, element3i += 3) { VectorCopy(vertex3f + element3i[0] * 3, points[0].v); VectorCopy(vertex3f + element3i[1] * 3, points[1].v); VectorCopy(vertex3f + element3i[2] * 3, points[2].v); Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); Collision_CalcPlanesForTriangleBrushFloat(&brush); //Collision_PrintBrushAsQHull(&brush, "brush"); Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); } } } void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs) { int i; // FIXME: snap vertices? if(stride > 0) { int k, cnt, tri; cnt = (numtriangles + stride - 1) / stride; for(i = 0; i < cnt; ++i) { if(BoxesOverlap(bbox6f + i * 6, bbox6f + i * 6 + 3, segmentmins, segmentmaxs)) { for(k = 0; k < stride; ++k) { tri = i * stride + k; if(tri >= numtriangles) break; Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[tri * 3 + 0] * 3, vertex3f + element3i[tri * 3 + 1] * 3, vertex3f + element3i[tri * 3 + 2] * 3, supercontents, q3surfaceflags, texture); } } } } else { for (i = 0;i < numtriangles;i++, element3i += 3) Collision_TraceLineTriangleFloat(trace, linestart, lineend, vertex3f + element3i[0] * 3, vertex3f + element3i[1] * 3, vertex3f + element3i[2] * 3, supercontents, q3surfaceflags, texture); } } void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture) { int i; colpointf_t points[3]; colpointf_t edgedirs[3]; colplanef_t planes[5]; colbrushf_t brush; memset(&brush, 0, sizeof(brush)); brush.isaabb = false; brush.hasaabbplanes = false; brush.numpoints = 3; brush.numedgedirs = 3; brush.numplanes = 5; brush.points = points; brush.edgedirs = edgedirs; brush.planes = planes; brush.supercontents = supercontents; brush.q3surfaceflags = q3surfaceflags; brush.texture = texture; for (i = 0;i < brush.numplanes;i++) { brush.planes[i].q3surfaceflags = q3surfaceflags; brush.planes[i].texture = texture; } VectorCopy(v0, points[0].v); VectorCopy(v1, points[1].v); VectorCopy(v2, points[2].v); Collision_SnapCopyPoints(brush.numpoints, points, points, COLLISION_SNAPSCALE, COLLISION_SNAP); Collision_CalcEdgeDirsForPolygonBrushFloat(&brush); Collision_CalcPlanesForTriangleBrushFloat(&brush); //Collision_PrintBrushAsQHull(&brush, "brush"); Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, &brush, &brush); } void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture) { int i; memset(boxbrush, 0, sizeof(*boxbrush)); boxbrush->brush.isaabb = true; boxbrush->brush.hasaabbplanes = true; boxbrush->brush.points = boxbrush->points; boxbrush->brush.edgedirs = boxbrush->edgedirs; boxbrush->brush.planes = boxbrush->planes; boxbrush->brush.supercontents = supercontents; boxbrush->brush.q3surfaceflags = q3surfaceflags; boxbrush->brush.texture = texture; if (VectorCompare(mins, maxs)) { // point brush boxbrush->brush.numpoints = 1; boxbrush->brush.numedgedirs = 0; boxbrush->brush.numplanes = 0; VectorCopy(mins, boxbrush->brush.points[0].v); } else { boxbrush->brush.numpoints = 8; boxbrush->brush.numedgedirs = 3; boxbrush->brush.numplanes = 6; // there are 8 points on a box // there are 3 edgedirs on a box (both signs are tested in collision) // there are 6 planes on a box VectorSet(boxbrush->brush.points[0].v, mins[0], mins[1], mins[2]); VectorSet(boxbrush->brush.points[1].v, maxs[0], mins[1], mins[2]); VectorSet(boxbrush->brush.points[2].v, mins[0], maxs[1], mins[2]); VectorSet(boxbrush->brush.points[3].v, maxs[0], maxs[1], mins[2]); VectorSet(boxbrush->brush.points[4].v, mins[0], mins[1], maxs[2]); VectorSet(boxbrush->brush.points[5].v, maxs[0], mins[1], maxs[2]); VectorSet(boxbrush->brush.points[6].v, mins[0], maxs[1], maxs[2]); VectorSet(boxbrush->brush.points[7].v, maxs[0], maxs[1], maxs[2]); VectorSet(boxbrush->brush.edgedirs[0].v, 1, 0, 0); VectorSet(boxbrush->brush.edgedirs[1].v, 0, 1, 0); VectorSet(boxbrush->brush.edgedirs[2].v, 0, 0, 1); VectorSet(boxbrush->brush.planes[0].normal, -1, 0, 0);boxbrush->brush.planes[0].dist = -mins[0]; VectorSet(boxbrush->brush.planes[1].normal, 1, 0, 0);boxbrush->brush.planes[1].dist = maxs[0]; VectorSet(boxbrush->brush.planes[2].normal, 0, -1, 0);boxbrush->brush.planes[2].dist = -mins[1]; VectorSet(boxbrush->brush.planes[3].normal, 0, 1, 0);boxbrush->brush.planes[3].dist = maxs[1]; VectorSet(boxbrush->brush.planes[4].normal, 0, 0, -1);boxbrush->brush.planes[4].dist = -mins[2]; VectorSet(boxbrush->brush.planes[5].normal, 0, 0, 1);boxbrush->brush.planes[5].dist = maxs[2]; for (i = 0;i < 6;i++) { boxbrush->brush.planes[i].q3surfaceflags = q3surfaceflags; boxbrush->brush.planes[i].texture = texture; } } boxbrush->brush.supercontents = supercontents; boxbrush->brush.q3surfaceflags = q3surfaceflags; boxbrush->brush.texture = texture; VectorSet(boxbrush->brush.mins, mins[0] - 1, mins[1] - 1, mins[2] - 1); VectorSet(boxbrush->brush.maxs, maxs[0] + 1, maxs[1] + 1, maxs[2] + 1); //Collision_ValidateBrush(&boxbrush->brush); } //pseudocode for detecting line/sphere overlap without calculating an impact point //linesphereorigin = sphereorigin - linestart;linediff = lineend - linestart;linespherefrac = DotProduct(linesphereorigin, linediff) / DotProduct(linediff, linediff);return VectorLength2(linesphereorigin - bound(0, linespherefrac, 1) * linediff) >= sphereradius*sphereradius; // LordHavoc: currently unused, but tested // note: this can be used for tracing a moving sphere vs a stationary sphere, // by simply adding the moving sphere's radius to the sphereradius parameter, // all the results are correct (impactpoint, impactnormal, and fraction) float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal) { double dir[3], scale, v[3], deviationdist2, impactdist, linelength; // make sure the impactpoint and impactnormal are valid even if there is // no collision VectorCopy(lineend, impactpoint); VectorClear(impactnormal); // calculate line direction VectorSubtract(lineend, linestart, dir); // normalize direction linelength = VectorLength(dir); if (linelength) { scale = 1.0 / linelength; VectorScale(dir, scale, dir); } // this dotproduct calculates the distance along the line at which the // sphere origin is (nearest point to the sphere origin on the line) impactdist = DotProduct(sphereorigin, dir) - DotProduct(linestart, dir); // calculate point on line at that distance, and subtract the // sphereorigin from it, so we have a vector to measure for the distance // of the line from the sphereorigin (deviation, how off-center it is) VectorMA(linestart, impactdist, dir, v); VectorSubtract(v, sphereorigin, v); deviationdist2 = sphereradius * sphereradius - VectorLength2(v); // if squared offset length is outside the squared sphere radius, miss if (deviationdist2 < 0) return 1; // miss (off to the side) // nudge back to find the correct impact distance impactdist -= sqrt(deviationdist2); if (impactdist >= linelength) return 1; // miss (not close enough) if (impactdist < 0) return 1; // miss (linestart is past or inside sphere) // calculate new impactpoint VectorMA(linestart, impactdist, dir, impactpoint); // calculate impactnormal (surface normal at point of impact) VectorSubtract(impactpoint, sphereorigin, impactnormal); // normalize impactnormal VectorNormalize(impactnormal); // return fraction of movement distance return impactdist / linelength; } void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture) { float d1, d2, d, f, f2, impact[3], edgenormal[3], faceplanenormal[3], faceplanedist, faceplanenormallength2, edge01[3], edge21[3], edge02[3]; // this function executes: // 32 ops when line starts behind triangle // 38 ops when line ends infront of triangle // 43 ops when line fraction is already closer than this triangle // 72 ops when line is outside edge 01 // 92 ops when line is outside edge 21 // 115 ops when line is outside edge 02 // 123 ops when line impacts triangle and updates trace results // this code is designed for clockwise triangles, conversion to // counterclockwise would require swapping some things around... // it is easier to simply swap the point0 and point2 parameters to this // function when calling it than it is to rewire the internals. // calculate the faceplanenormal of the triangle, this represents the front side // 15 ops VectorSubtract(point0, point1, edge01); VectorSubtract(point2, point1, edge21); CrossProduct(edge01, edge21, faceplanenormal); // there's no point in processing a degenerate triangle (GIGO - Garbage In, Garbage Out) // 6 ops faceplanenormallength2 = DotProduct(faceplanenormal, faceplanenormal); if (faceplanenormallength2 < 0.0001f) return; // calculate the distance // 5 ops faceplanedist = DotProduct(point0, faceplanenormal); // if start point is on the back side there is no collision // (we don't care about traces going through the triangle the wrong way) // calculate the start distance // 6 ops d1 = DotProduct(faceplanenormal, linestart); if (d1 <= faceplanedist) return; // calculate the end distance // 6 ops d2 = DotProduct(faceplanenormal, lineend); // if both are in front, there is no collision if (d2 >= faceplanedist) return; // from here on we know d1 is >= 0 and d2 is < 0 // this means the line starts infront and ends behind, passing through it // calculate the recipricol of the distance delta, // so we can use it multiple times cheaply (instead of division) // 2 ops d = 1.0f / (d1 - d2); // calculate the impact fraction by taking the start distance (> 0) // and subtracting the face plane distance (this is the distance of the // triangle along that same normal) // then multiply by the recipricol distance delta // 4 ops f = (d1 - faceplanedist) * d; f2 = f - collision_impactnudge.value * d; // skip out if this impact is further away than previous ones // 1 ops if (f2 >= trace->fraction) return; // calculate the perfect impact point for classification of insidedness // 9 ops impact[0] = linestart[0] + f * (lineend[0] - linestart[0]); impact[1] = linestart[1] + f * (lineend[1] - linestart[1]); impact[2] = linestart[2] + f * (lineend[2] - linestart[2]); // calculate the edge normal and reject if impact is outside triangle // (an edge normal faces away from the triangle, to get the desired normal // a crossproduct with the faceplanenormal is used, and because of the way // the insidedness comparison is written it does not need to be normalized) // first use the two edges from the triangle plane math // the other edge only gets calculated if the point survives that long // 20 ops CrossProduct(edge01, faceplanenormal, edgenormal); if (DotProduct(impact, edgenormal) > DotProduct(point1, edgenormal)) return; // 20 ops CrossProduct(faceplanenormal, edge21, edgenormal); if (DotProduct(impact, edgenormal) > DotProduct(point2, edgenormal)) return; // 23 ops VectorSubtract(point0, point2, edge02); CrossProduct(faceplanenormal, edge02, edgenormal); if (DotProduct(impact, edgenormal) > DotProduct(point0, edgenormal)) return; // 8 ops (rare) // skip if this trace should not be blocked by these contents if (!(supercontents & trace->hitsupercontentsmask) || (supercontents & trace->skipsupercontentsmask)) return; // store the new trace fraction trace->fraction = f2; // store the new trace plane (because collisions only happen from // the front this is always simply the triangle normal, never flipped) d = 1.0 / sqrt(faceplanenormallength2); VectorScale(faceplanenormal, d, trace->plane.normal); trace->plane.dist = faceplanedist * d; trace->hitsupercontents = supercontents; trace->hitq3surfaceflags = q3surfaceflags; trace->hittexture = texture; } void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac) { int i; colpointf_t *ps, *pe; float tempstart[3], tempend[3]; VectorLerp(start->points[0].v, startfrac, end->points[0].v, mins); VectorCopy(mins, maxs); for (i = 0, ps = start->points, pe = end->points;i < start->numpoints;i++, ps++, pe++) { VectorLerp(ps->v, startfrac, pe->v, tempstart); VectorLerp(ps->v, endfrac, pe->v, tempend); mins[0] = min(mins[0], min(tempstart[0], tempend[0])); mins[1] = min(mins[1], min(tempstart[1], tempend[1])); mins[2] = min(mins[2], min(tempstart[2], tempend[2])); maxs[0] = min(maxs[0], min(tempstart[0], tempend[0])); maxs[1] = min(maxs[1], min(tempstart[1], tempend[1])); maxs[2] = min(maxs[2], min(tempstart[2], tempend[2])); } mins[0] -= 1; mins[1] -= 1; mins[2] -= 1; maxs[0] += 1; maxs[1] += 1; maxs[2] += 1; } //=========================================== static void Collision_TranslateBrush(const vec3_t shift, colbrushf_t *brush) { int i; // now we can transform the data for(i = 0; i < brush->numplanes; ++i) { brush->planes[i].dist += DotProduct(shift, brush->planes[i].normal); } for(i = 0; i < brush->numpoints; ++i) { VectorAdd(brush->points[i].v, shift, brush->points[i].v); } VectorAdd(brush->mins, shift, brush->mins); VectorAdd(brush->maxs, shift, brush->maxs); } static void Collision_TransformBrush(const matrix4x4_t *matrix, colbrushf_t *brush) { int i; vec3_t v; // we're breaking any AABB properties here... brush->isaabb = false; brush->hasaabbplanes = false; // now we can transform the data for(i = 0; i < brush->numplanes; ++i) { Matrix4x4_TransformPositivePlane(matrix, brush->planes[i].normal[0], brush->planes[i].normal[1], brush->planes[i].normal[2], brush->planes[i].dist, brush->planes[i].normal_and_dist); } for(i = 0; i < brush->numedgedirs; ++i) { Matrix4x4_Transform(matrix, brush->edgedirs[i].v, v); VectorCopy(v, brush->edgedirs[i].v); } for(i = 0; i < brush->numpoints; ++i) { Matrix4x4_Transform(matrix, brush->points[i].v, v); VectorCopy(v, brush->points[i].v); } VectorCopy(brush->points[0].v, brush->mins); VectorCopy(brush->points[0].v, brush->maxs); for(i = 1; i < brush->numpoints; ++i) { if(brush->points[i].v[0] < brush->mins[0]) brush->mins[0] = brush->points[i].v[0]; if(brush->points[i].v[1] < brush->mins[1]) brush->mins[1] = brush->points[i].v[1]; if(brush->points[i].v[2] < brush->mins[2]) brush->mins[2] = brush->points[i].v[2]; if(brush->points[i].v[0] > brush->maxs[0]) brush->maxs[0] = brush->points[i].v[0]; if(brush->points[i].v[1] > brush->maxs[1]) brush->maxs[1] = brush->points[i].v[1]; if(brush->points[i].v[2] > brush->maxs[2]) brush->maxs[2] = brush->points[i].v[2]; } } typedef struct collision_cachedtrace_parameters_s { dp_model_t *model; vec3_t end; vec3_t start; int hitsupercontentsmask; int skipsupercontentsmask; matrix4x4_t matrix; } collision_cachedtrace_parameters_t; typedef struct collision_cachedtrace_s { qboolean valid; collision_cachedtrace_parameters_t p; trace_t result; } collision_cachedtrace_t; static mempool_t *collision_cachedtrace_mempool; static collision_cachedtrace_t *collision_cachedtrace_array; static int collision_cachedtrace_firstfree; static int collision_cachedtrace_lastused; static int collision_cachedtrace_max; static unsigned char collision_cachedtrace_sequence; static int collision_cachedtrace_hashsize; static int *collision_cachedtrace_hash; static unsigned int *collision_cachedtrace_arrayfullhashindex; static unsigned int *collision_cachedtrace_arrayhashindex; static unsigned int *collision_cachedtrace_arraynext; static unsigned char *collision_cachedtrace_arrayused; static qboolean collision_cachedtrace_rebuildhash; void Collision_Cache_Reset(qboolean resetlimits) { if (collision_cachedtrace_hash) Mem_Free(collision_cachedtrace_hash); if (collision_cachedtrace_array) Mem_Free(collision_cachedtrace_array); if (collision_cachedtrace_arrayfullhashindex) Mem_Free(collision_cachedtrace_arrayfullhashindex); if (collision_cachedtrace_arrayhashindex) Mem_Free(collision_cachedtrace_arrayhashindex); if (collision_cachedtrace_arraynext) Mem_Free(collision_cachedtrace_arraynext); if (collision_cachedtrace_arrayused) Mem_Free(collision_cachedtrace_arrayused); if (resetlimits || !collision_cachedtrace_max) collision_cachedtrace_max = collision_cache.integer ? 128 : 1; collision_cachedtrace_firstfree = 1; collision_cachedtrace_lastused = 0; collision_cachedtrace_hashsize = collision_cachedtrace_max; collision_cachedtrace_array = (collision_cachedtrace_t *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(collision_cachedtrace_t)); collision_cachedtrace_hash = (int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_hashsize * sizeof(int)); collision_cachedtrace_arrayfullhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); collision_cachedtrace_arrayhashindex = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); collision_cachedtrace_arraynext = (unsigned int *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned int)); collision_cachedtrace_arrayused = (unsigned char *)Mem_Alloc(collision_cachedtrace_mempool, collision_cachedtrace_max * sizeof(unsigned char)); collision_cachedtrace_sequence = 1; collision_cachedtrace_rebuildhash = false; } void Collision_Cache_Init(mempool_t *mempool) { collision_cachedtrace_mempool = mempool; Collision_Cache_Reset(true); } static void Collision_Cache_RebuildHash(void) { int index; int range = collision_cachedtrace_lastused + 1; unsigned char sequence = collision_cachedtrace_sequence; int firstfree = collision_cachedtrace_max; int lastused = 0; int *hash = collision_cachedtrace_hash; unsigned int hashindex; unsigned int *arrayhashindex = collision_cachedtrace_arrayhashindex; unsigned int *arraynext = collision_cachedtrace_arraynext; collision_cachedtrace_rebuildhash = false; memset(collision_cachedtrace_hash, 0, collision_cachedtrace_hashsize * sizeof(int)); for (index = 1;index < range;index++) { if (collision_cachedtrace_arrayused[index] == sequence) { hashindex = arrayhashindex[index]; arraynext[index] = hash[hashindex]; hash[hashindex] = index; lastused = index; } else { if (firstfree > index) firstfree = index; collision_cachedtrace_arrayused[index] = 0; } } collision_cachedtrace_firstfree = firstfree; collision_cachedtrace_lastused = lastused; } void Collision_Cache_NewFrame(void) { if (collision_cache.integer) { if (collision_cachedtrace_max < 128) Collision_Cache_Reset(true); } else { if (collision_cachedtrace_max > 1) Collision_Cache_Reset(true); } // rebuild hash if sequence would overflow byte, otherwise increment if (collision_cachedtrace_sequence == 255) { Collision_Cache_RebuildHash(); collision_cachedtrace_sequence = 1; } else { collision_cachedtrace_rebuildhash = true; collision_cachedtrace_sequence++; } } static unsigned int Collision_Cache_HashIndexForArray(unsigned int *array, unsigned int size) { unsigned int i; unsigned int hashindex = 0; // this is a super-cheesy checksum, designed only for speed for (i = 0;i < size;i++) hashindex += array[i] * (1 + i); return hashindex; } static collision_cachedtrace_t *Collision_Cache_Lookup(dp_model_t *model, const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { int hashindex = 0; unsigned int fullhashindex; int index = 0; int range; unsigned char sequence = collision_cachedtrace_sequence; int *hash = collision_cachedtrace_hash; unsigned int *arrayfullhashindex = collision_cachedtrace_arrayfullhashindex; unsigned int *arraynext = collision_cachedtrace_arraynext; collision_cachedtrace_t *cached = collision_cachedtrace_array + index; collision_cachedtrace_parameters_t params; // all non-cached traces use the same index if (!collision_cache.integer) r_refdef.stats[r_stat_photoncache_traced]++; else { // cached trace lookup memset(¶ms, 0, sizeof(params)); params.model = model; VectorCopy(start, params.start); VectorCopy(end, params.end); params.hitsupercontentsmask = hitsupercontentsmask; params.skipsupercontentsmask = skipsupercontentsmask; params.matrix = *matrix; fullhashindex = Collision_Cache_HashIndexForArray((unsigned int *)¶ms, sizeof(params) / sizeof(unsigned int)); hashindex = (int)(fullhashindex % (unsigned int)collision_cachedtrace_hashsize); for (index = hash[hashindex];index;index = arraynext[index]) { if (arrayfullhashindex[index] != fullhashindex) continue; cached = collision_cachedtrace_array + index; //if (memcmp(&cached->p, ¶ms, sizeof(params))) if (cached->p.model != params.model || cached->p.end[0] != params.end[0] || cached->p.end[1] != params.end[1] || cached->p.end[2] != params.end[2] || cached->p.start[0] != params.start[0] || cached->p.start[1] != params.start[1] || cached->p.start[2] != params.start[2] || cached->p.hitsupercontentsmask != params.hitsupercontentsmask || cached->p.skipsupercontentsmask != params.skipsupercontentsmask || cached->p.matrix.m[0][0] != params.matrix.m[0][0] || cached->p.matrix.m[0][1] != params.matrix.m[0][1] || cached->p.matrix.m[0][2] != params.matrix.m[0][2] || cached->p.matrix.m[0][3] != params.matrix.m[0][3] || cached->p.matrix.m[1][0] != params.matrix.m[1][0] || cached->p.matrix.m[1][1] != params.matrix.m[1][1] || cached->p.matrix.m[1][2] != params.matrix.m[1][2] || cached->p.matrix.m[1][3] != params.matrix.m[1][3] || cached->p.matrix.m[2][0] != params.matrix.m[2][0] || cached->p.matrix.m[2][1] != params.matrix.m[2][1] || cached->p.matrix.m[2][2] != params.matrix.m[2][2] || cached->p.matrix.m[2][3] != params.matrix.m[2][3] || cached->p.matrix.m[3][0] != params.matrix.m[3][0] || cached->p.matrix.m[3][1] != params.matrix.m[3][1] || cached->p.matrix.m[3][2] != params.matrix.m[3][2] || cached->p.matrix.m[3][3] != params.matrix.m[3][3] ) continue; // found a matching trace in the cache r_refdef.stats[r_stat_photoncache_cached]++; cached->valid = true; collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; return cached; } r_refdef.stats[r_stat_photoncache_traced]++; // find an unused cache entry for (index = collision_cachedtrace_firstfree, range = collision_cachedtrace_max;index < range;index++) if (collision_cachedtrace_arrayused[index] == 0) break; if (index == range) { // all claimed, but probably some are stale... for (index = 1, range = collision_cachedtrace_max;index < range;index++) if (collision_cachedtrace_arrayused[index] != sequence) break; if (index < range) { // found a stale one, rebuild the hash Collision_Cache_RebuildHash(); } else { // we need to grow the cache collision_cachedtrace_max *= 2; Collision_Cache_Reset(false); index = 1; } } // link the new cache entry into the hash bucket collision_cachedtrace_firstfree = index + 1; if (collision_cachedtrace_lastused < index) collision_cachedtrace_lastused = index; cached = collision_cachedtrace_array + index; collision_cachedtrace_arraynext[index] = collision_cachedtrace_hash[hashindex]; collision_cachedtrace_hash[hashindex] = index; collision_cachedtrace_arrayhashindex[index] = hashindex; cached->valid = false; cached->p = params; collision_cachedtrace_arrayfullhashindex[index] = fullhashindex; collision_cachedtrace_arrayused[index] = collision_cachedtrace_sequence; } return cached; } void Collision_Cache_ClipLineToGenericEntitySurfaces(trace_t *trace, dp_model_t *model, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { collision_cachedtrace_t *cached = Collision_Cache_Lookup(model, matrix, inversematrix, start, end, hitsupercontentsmask, skipsupercontentsmask); if (cached->valid) { *trace = cached->result; return; } Collision_ClipLineToGenericEntity(trace, model, NULL, NULL, vec3_origin, vec3_origin, 0, matrix, inversematrix, start, end, hitsupercontentsmask, skipsupercontentsmask, collision_extendmovelength.value, true); cached->result = *trace; } void Collision_Cache_ClipLineToWorldSurfaces(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { collision_cachedtrace_t *cached = Collision_Cache_Lookup(model, &identitymatrix, &identitymatrix, start, end, hitsupercontentsmask, skipsupercontentsmask); if (cached->valid) { *trace = cached->result; return; } Collision_ClipLineToWorld(trace, model, start, end, hitsupercontentsmask, skipsupercontentsmask, collision_extendmovelength.value, true); cached->result = *trace; } typedef struct extendtraceinfo_s { trace_t *trace; float realstart[3]; float realend[3]; float realdelta[3]; float extendstart[3]; float extendend[3]; float extenddelta[3]; float reallength; float extendlength; float scaletoextend; float extend; } extendtraceinfo_t; static void Collision_ClipExtendPrepare(extendtraceinfo_t *extendtraceinfo, trace_t *trace, const vec3_t tstart, const vec3_t tend, float textend) { memset(trace, 0, sizeof(*trace)); trace->fraction = 1; extendtraceinfo->trace = trace; VectorCopy(tstart, extendtraceinfo->realstart); VectorCopy(tend, extendtraceinfo->realend); VectorSubtract(extendtraceinfo->realend, extendtraceinfo->realstart, extendtraceinfo->realdelta); VectorCopy(extendtraceinfo->realstart, extendtraceinfo->extendstart); VectorCopy(extendtraceinfo->realend, extendtraceinfo->extendend); VectorCopy(extendtraceinfo->realdelta, extendtraceinfo->extenddelta); extendtraceinfo->reallength = VectorLength(extendtraceinfo->realdelta); extendtraceinfo->extendlength = extendtraceinfo->reallength; extendtraceinfo->scaletoextend = 1.0f; extendtraceinfo->extend = textend; // make the trace longer according to the extend parameter if (extendtraceinfo->reallength && extendtraceinfo->extend) { extendtraceinfo->extendlength = extendtraceinfo->reallength + extendtraceinfo->extend; extendtraceinfo->scaletoextend = extendtraceinfo->extendlength / extendtraceinfo->reallength; VectorMA(extendtraceinfo->realstart, extendtraceinfo->scaletoextend, extendtraceinfo->realdelta, extendtraceinfo->extendend); VectorSubtract(extendtraceinfo->extendend, extendtraceinfo->extendstart, extendtraceinfo->extenddelta); } } static void Collision_ClipExtendFinish(extendtraceinfo_t *extendtraceinfo) { trace_t *trace = extendtraceinfo->trace; if (trace->fraction != 1.0f) { // undo the extended trace length trace->fraction *= extendtraceinfo->scaletoextend; // if the extended trace hit something that the unextended trace did not hit (even considering the collision_impactnudge), then we have to clear the hit information if (trace->fraction > 1.0f) { // note that ent may refer to either startsolid or fraction<1, we can't restore the startsolid ent unfortunately trace->ent = NULL; trace->hitq3surfaceflags = 0; trace->hitsupercontents = 0; trace->hittexture = NULL; VectorClear(trace->plane.normal); trace->plane.dist = 0.0f; } } // clamp things trace->fraction = bound(0, trace->fraction, 1); // calculate the end position VectorMA(extendtraceinfo->realstart, trace->fraction, extendtraceinfo->realdelta, trace->endpos); } void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t tstart, const vec3_t mins, const vec3_t maxs, const vec3_t tend, int hitsupercontentsmask, int skipsupercontentsmask, float extend) { vec3_t starttransformed, endtransformed; extendtraceinfo_t extendtraceinfo; Collision_ClipExtendPrepare(&extendtraceinfo, trace, tstart, tend, extend); Matrix4x4_Transform(inversematrix, extendtraceinfo.extendstart, starttransformed); Matrix4x4_Transform(inversematrix, extendtraceinfo.extendend, endtransformed); #if COLLISIONPARANOID >= 3 Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", extendtraceinfo.extendstart[0], extendtraceinfo.extendstart[1], extendtraceinfo.extendstart[2], starttransformed[0], starttransformed[1], starttransformed[2], extendtraceinfo.extendend[0], extendtraceinfo.extendend[1], extendtraceinfo.extendend[2], endtransformed[0], endtransformed[1], endtransformed[2]); #endif if (model && model->TraceBox) { if(model->TraceBrush && (inversematrix->m[0][1] || inversematrix->m[0][2] || inversematrix->m[1][0] || inversematrix->m[1][2] || inversematrix->m[2][0] || inversematrix->m[2][1])) { // we get here if TraceBrush exists, AND we have a rotation component (SOLID_BSP case) // using starttransformed, endtransformed is WRONG in this case! // should rather build a brush and trace using it colboxbrushf_t thisbrush_start, thisbrush_end; Collision_BrushForBox(&thisbrush_start, mins, maxs, 0, 0, NULL); Collision_BrushForBox(&thisbrush_end, mins, maxs, 0, 0, NULL); Collision_TranslateBrush(extendtraceinfo.extendstart, &thisbrush_start.brush); Collision_TranslateBrush(extendtraceinfo.extendend, &thisbrush_end.brush); Collision_TransformBrush(inversematrix, &thisbrush_start.brush); Collision_TransformBrush(inversematrix, &thisbrush_end.brush); //Collision_TranslateBrush(starttransformed, &thisbrush_start.brush); //Collision_TranslateBrush(endtransformed, &thisbrush_end.brush); model->TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask, skipsupercontentsmask); } else // this is only approximate if rotated, quite useless model->TraceBox(model, frameblend, skeleton, trace, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask, skipsupercontentsmask); } else // and this requires that the transformation matrix doesn't have angles components, like SV_TraceBox ensures; FIXME may get called if a model is SOLID_BSP but has no TraceBox function Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, mins, maxs, endtransformed, hitsupercontentsmask, skipsupercontentsmask, bodysupercontents, 0, NULL); Collision_ClipExtendFinish(&extendtraceinfo); // transform plane // NOTE: this relies on plane.dist being directly after plane.normal Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal_and_dist); } void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t tstart, const vec3_t mins, const vec3_t maxs, const vec3_t tend, int hitsupercontentsmask, int skipsupercontentsmask, float extend) { extendtraceinfo_t extendtraceinfo; Collision_ClipExtendPrepare(&extendtraceinfo, trace, tstart, tend, extend); // ->TraceBox: TraceBrush not needed here, as worldmodel is never rotated if (model && model->TraceBox) model->TraceBox(model, NULL, NULL, trace, extendtraceinfo.extendstart, mins, maxs, extendtraceinfo.extendend, hitsupercontentsmask, skipsupercontentsmask); Collision_ClipExtendFinish(&extendtraceinfo); } void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t tstart, const vec3_t tend, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitsurfaces) { vec3_t starttransformed, endtransformed; extendtraceinfo_t extendtraceinfo; Collision_ClipExtendPrepare(&extendtraceinfo, trace, tstart, tend, extend); Matrix4x4_Transform(inversematrix, extendtraceinfo.extendstart, starttransformed); Matrix4x4_Transform(inversematrix, extendtraceinfo.extendend, endtransformed); #if COLLISIONPARANOID >= 3 Con_Printf("trans(%f %f %f -> %f %f %f, %f %f %f -> %f %f %f)", extendtraceinfo.extendstart[0], extendtraceinfo.extendstart[1], extendtraceinfo.extendstart[2], starttransformed[0], starttransformed[1], starttransformed[2], extendtraceinfo.extendend[0], extendtraceinfo.extendend[1], extendtraceinfo.extendend[2], endtransformed[0], endtransformed[1], endtransformed[2]); #endif if (model && model->TraceLineAgainstSurfaces && hitsurfaces) model->TraceLineAgainstSurfaces(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask, skipsupercontentsmask); else if (model && model->TraceLine) model->TraceLine(model, frameblend, skeleton, trace, starttransformed, endtransformed, hitsupercontentsmask, skipsupercontentsmask); else Collision_ClipTrace_Box(trace, bodymins, bodymaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, hitsupercontentsmask, skipsupercontentsmask, bodysupercontents, 0, NULL); Collision_ClipExtendFinish(&extendtraceinfo); // transform plane // NOTE: this relies on plane.dist being directly after plane.normal Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal_and_dist); } void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t tstart, const vec3_t tend, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitsurfaces) { extendtraceinfo_t extendtraceinfo; Collision_ClipExtendPrepare(&extendtraceinfo, trace, tstart, tend, extend); if (model && model->TraceLineAgainstSurfaces && hitsurfaces) model->TraceLineAgainstSurfaces(model, NULL, NULL, trace, extendtraceinfo.extendstart, extendtraceinfo.extendend, hitsupercontentsmask, skipsupercontentsmask); else if (model && model->TraceLine) model->TraceLine(model, NULL, NULL, trace, extendtraceinfo.extendstart, extendtraceinfo.extendend, hitsupercontentsmask, skipsupercontentsmask); Collision_ClipExtendFinish(&extendtraceinfo); } void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { float starttransformed[3]; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; Matrix4x4_Transform(inversematrix, start, starttransformed); #if COLLISIONPARANOID >= 3 Con_Printf("trans(%f %f %f -> %f %f %f)", start[0], start[1], start[2], starttransformed[0], starttransformed[1], starttransformed[2]); #endif if (model && model->TracePoint) model->TracePoint(model, NULL, NULL, trace, starttransformed, hitsupercontentsmask, skipsupercontentsmask); else Collision_ClipTrace_Point(trace, bodymins, bodymaxs, starttransformed, hitsupercontentsmask, skipsupercontentsmask, bodysupercontents, 0, NULL); VectorCopy(start, trace->endpos); // transform plane // NOTE: this relies on plane.dist being directly after plane.normal Matrix4x4_TransformPositivePlane(matrix, trace->plane.normal[0], trace->plane.normal[1], trace->plane.normal[2], trace->plane.dist, trace->plane.normal_and_dist); } void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { memset(trace, 0, sizeof(*trace)); trace->fraction = 1; if (model && model->TracePoint) model->TracePoint(model, NULL, NULL, trace, start, hitsupercontentsmask, skipsupercontentsmask); VectorCopy(start, trace->endpos); } void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel) { // take the 'best' answers from the new trace and combine with existing data if (trace->allsolid) cliptrace->allsolid = true; if (trace->startsolid) { if (isbmodel) cliptrace->bmodelstartsolid = true; cliptrace->startsolid = true; if (cliptrace->fraction == 1) cliptrace->ent = touch; if (cliptrace->startdepth > trace->startdepth) { cliptrace->startdepth = trace->startdepth; VectorCopy(trace->startdepthnormal, cliptrace->startdepthnormal); } } // don't set this except on the world, because it can easily confuse // monsters underwater if there's a bmodel involved in the trace // (inopen && inwater is how they check water visibility) //if (trace->inopen) // cliptrace->inopen = true; if (trace->inwater) cliptrace->inwater = true; if ((trace->fraction < cliptrace->fraction) && (VectorLength2(trace->plane.normal) > 0)) { cliptrace->fraction = trace->fraction; VectorCopy(trace->endpos, cliptrace->endpos); cliptrace->plane = trace->plane; cliptrace->ent = touch; cliptrace->hitsupercontents = trace->hitsupercontents; cliptrace->hitq3surfaceflags = trace->hitq3surfaceflags; cliptrace->hittexture = trace->hittexture; } cliptrace->startsupercontents |= trace->startsupercontents; } darkplaces/cl_main.c0000664000175000017500000024570413067716216013752 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl_main.c -- client main loop #include "quakedef.h" #include "cl_collision.h" #include "cl_video.h" #include "image.h" #include "csprogs.h" #include "r_shadow.h" #include "libcurl.h" #include "snd_main.h" // we need to declare some mouse variables here, because the menu system // references them even when on a unix system. cvar_t csqc_progname = {0, "csqc_progname","csprogs.dat","name of csprogs.dat file to load"}; cvar_t csqc_progcrc = {CVAR_READONLY, "csqc_progcrc","-1","CRC of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; cvar_t csqc_progsize = {CVAR_READONLY, "csqc_progsize","-1","file size of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1"}; cvar_t csqc_usedemoprogs = {0, "csqc_usedemoprogs","1","use csprogs stored in demos"}; cvar_t cl_shownet = {0, "cl_shownet","0","1 = print packet size, 2 = print packet message list"}; cvar_t cl_nolerp = {0, "cl_nolerp", "0","network update smoothing"}; cvar_t cl_lerpexcess = {0, "cl_lerpexcess", "0","maximum allowed lerp excess (hides, not fixes, some packet loss)"}; cvar_t cl_lerpanim_maxdelta_server = {0, "cl_lerpanim_maxdelta_server", "0.1","maximum frame delta for smoothing between server-controlled animation frames (when 0, one network frame)"}; cvar_t cl_lerpanim_maxdelta_framegroups = {0, "cl_lerpanim_maxdelta_framegroups", "0.1","maximum frame delta for smoothing between framegroups (when 0, one network frame)"}; cvar_t cl_itembobheight = {0, "cl_itembobheight", "0","how much items bob up and down (try 8)"}; cvar_t cl_itembobspeed = {0, "cl_itembobspeed", "0.5","how frequently items bob up and down"}; cvar_t lookspring = {CVAR_SAVE, "lookspring","0","returns pitch to level with the floor when no longer holding a pitch key"}; cvar_t lookstrafe = {CVAR_SAVE, "lookstrafe","0","move instead of turning"}; cvar_t sensitivity = {CVAR_SAVE, "sensitivity","3","mouse speed multiplier"}; cvar_t m_pitch = {CVAR_SAVE, "m_pitch","0.022","mouse pitch speed multiplier"}; cvar_t m_yaw = {CVAR_SAVE, "m_yaw","0.022","mouse yaw speed multiplier"}; cvar_t m_forward = {CVAR_SAVE, "m_forward","1","mouse forward speed multiplier"}; cvar_t m_side = {CVAR_SAVE, "m_side","0.8","mouse side speed multiplier"}; cvar_t freelook = {CVAR_SAVE, "freelook", "1","mouse controls pitch instead of forward/back"}; cvar_t cl_autodemo = {CVAR_SAVE, "cl_autodemo", "0", "records every game played, using the date/time and map name to name the demo file" }; cvar_t cl_autodemo_nameformat = {CVAR_SAVE, "cl_autodemo_nameformat", "autodemos/%Y-%m-%d_%H-%M", "The format of the cl_autodemo filename, followed by the map name (the date is encoded using strftime escapes)" }; cvar_t cl_autodemo_delete = {0, "cl_autodemo_delete", "0", "Delete demos after recording. This is a bitmask, bit 1 gives the default, bit 0 the value for the current demo. Thus, the values are: 0 = disabled; 1 = delete current demo only; 2 = delete all demos except the current demo; 3 = delete all demos from now on" }; cvar_t r_draweffects = {0, "r_draweffects", "1","renders temporary sprite effects"}; cvar_t cl_explosions_alpha_start = {CVAR_SAVE, "cl_explosions_alpha_start", "1.5","starting alpha of an explosion shell"}; cvar_t cl_explosions_alpha_end = {CVAR_SAVE, "cl_explosions_alpha_end", "0","end alpha of an explosion shell (just before it disappears)"}; cvar_t cl_explosions_size_start = {CVAR_SAVE, "cl_explosions_size_start", "16","starting size of an explosion shell"}; cvar_t cl_explosions_size_end = {CVAR_SAVE, "cl_explosions_size_end", "128","ending alpha of an explosion shell (just before it disappears)"}; cvar_t cl_explosions_lifetime = {CVAR_SAVE, "cl_explosions_lifetime", "0.5","how long an explosion shell lasts"}; cvar_t cl_stainmaps = {CVAR_SAVE, "cl_stainmaps", "0","stains lightmaps, much faster than decals but blurred"}; cvar_t cl_stainmaps_clearonload = {CVAR_SAVE, "cl_stainmaps_clearonload", "1","clear stainmaps on map restart"}; cvar_t cl_beams_polygons = {CVAR_SAVE, "cl_beams_polygons", "1","use beam polygons instead of models"}; cvar_t cl_beams_quakepositionhack = {CVAR_SAVE, "cl_beams_quakepositionhack", "1", "makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld)"}; cvar_t cl_beams_instantaimhack = {CVAR_SAVE, "cl_beams_instantaimhack", "0", "makes your lightning gun aiming update instantly"}; cvar_t cl_beams_lightatend = {CVAR_SAVE, "cl_beams_lightatend", "0", "make a light at the end of the beam"}; cvar_t cl_deathfade = {CVAR_SAVE, "cl_deathfade", "0", "fade screen to dark red when dead, value represents how fast the fade is (higher is faster)"}; cvar_t cl_noplayershadow = {CVAR_SAVE, "cl_noplayershadow", "0","hide player shadow"}; cvar_t cl_dlights_decayradius = {CVAR_SAVE, "cl_dlights_decayradius", "1", "reduces size of light flashes over time"}; cvar_t cl_dlights_decaybrightness = {CVAR_SAVE, "cl_dlights_decaybrightness", "1", "reduces brightness of light flashes over time"}; cvar_t qport = {0, "qport", "0", "identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes)"}; cvar_t cl_prydoncursor = {0, "cl_prydoncursor", "0", "enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc"}; cvar_t cl_prydoncursor_notrace = {0, "cl_prydoncursor_notrace", "0", "disables traceline used in prydon cursor reporting to the game, saving some cpu time"}; cvar_t cl_deathnoviewmodel = {0, "cl_deathnoviewmodel", "1", "hides gun model when dead"}; cvar_t cl_locs_enable = {CVAR_SAVE, "locs_enable", "1", "enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level)"}; cvar_t cl_locs_show = {0, "locs_show", "0", "shows defined locations for editing purposes"}; extern cvar_t r_equalize_entities_fullbright; client_static_t cls; client_state_t cl; /* ===================== CL_ClearState ===================== */ void CL_ClearState(void) { int i; entity_t *ent; CL_VM_ShutDown(); // wipe the entire cl structure Mem_EmptyPool(cls.levelmempool); memset (&cl, 0, sizeof(cl)); S_StopAllSounds(); // reset the view zoom interpolation cl.mviewzoom[0] = cl.mviewzoom[1] = 1; cl.sensitivityscale = 1.0f; // enable rendering of the world and such cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; cl.csqc_vidvars.drawenginesbar = true; cl.csqc_vidvars.drawcrosshair = true; // set up the float version of the stats array for easier access to float stats cl.statsf = (float *)cl.stats; cl.num_entities = 0; cl.num_static_entities = 0; cl.num_brushmodel_entities = 0; // tweak these if the game runs out cl.max_csqcrenderentities = 0; cl.max_entities = MAX_ENITIES_INITIAL; cl.max_static_entities = MAX_STATICENTITIES; cl.max_effects = MAX_EFFECTS; cl.max_beams = MAX_BEAMS; cl.max_dlights = MAX_DLIGHTS; cl.max_lightstyle = MAX_LIGHTSTYLES; cl.max_brushmodel_entities = MAX_EDICTS; cl.max_particles = MAX_PARTICLES_INITIAL; // grows dynamically cl.max_decals = MAX_DECALS_INITIAL; // grows dynamically cl.max_showlmps = 0; cl.num_dlights = 0; cl.num_effects = 0; cl.num_beams = 0; cl.csqcrenderentities = NULL; cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); cl.entities_active = (unsigned char *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(unsigned char)); cl.static_entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_static_entities * sizeof(entity_t)); cl.effects = (cl_effect_t *)Mem_Alloc(cls.levelmempool, cl.max_effects * sizeof(cl_effect_t)); cl.beams = (beam_t *)Mem_Alloc(cls.levelmempool, cl.max_beams * sizeof(beam_t)); cl.dlights = (dlight_t *)Mem_Alloc(cls.levelmempool, cl.max_dlights * sizeof(dlight_t)); cl.lightstyle = (lightstyle_t *)Mem_Alloc(cls.levelmempool, cl.max_lightstyle * sizeof(lightstyle_t)); cl.brushmodel_entities = (int *)Mem_Alloc(cls.levelmempool, cl.max_brushmodel_entities * sizeof(int)); cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); cl.showlmps = NULL; // LordHavoc: have to set up the baseline info for alpha and other stuff for (i = 0;i < cl.max_entities;i++) { cl.entities[i].state_baseline = defaultstate; cl.entities[i].state_previous = defaultstate; cl.entities[i].state_current = defaultstate; } if (IS_NEXUIZ_DERIVED(gamemode)) { VectorSet(cl.playerstandmins, -16, -16, -24); VectorSet(cl.playerstandmaxs, 16, 16, 45); VectorSet(cl.playercrouchmins, -16, -16, -24); VectorSet(cl.playercrouchmaxs, 16, 16, 25); } else { VectorSet(cl.playerstandmins, -16, -16, -24); VectorSet(cl.playerstandmaxs, 16, 16, 24); VectorSet(cl.playercrouchmins, -16, -16, -24); VectorSet(cl.playercrouchmaxs, 16, 16, 24); } // disable until we get textures for it R_ResetSkyBox(); ent = &cl.entities[0]; // entire entity array was cleared, so just fill in a few fields ent->state_current.active = true; ent->render.model = cl.worldmodel = NULL; // no world model yet ent->render.alpha = 1; ent->render.flags = RENDER_SHADOW | RENDER_LIGHT; Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1); ent->render.allowdecals = true; CL_UpdateRenderEntity(&ent->render); // noclip is turned off at start noclip_anglehack = false; // mark all frames invalid for delta memset(cl.qw_deltasequence, -1, sizeof(cl.qw_deltasequence)); // set bestweapon data back to Quake data IN_BestWeapon_ResetData(); CL_Screen_NewMap(); } void CL_SetInfo(const char *key, const char *value, qboolean send, qboolean allowstarkey, qboolean allowmodel, qboolean quiet) { int i; qboolean fail = false; char vabuf[1024]; if (!allowstarkey && key[0] == '*') fail = true; if (!allowmodel && (!strcasecmp(key, "pmodel") || !strcasecmp(key, "emodel"))) fail = true; for (i = 0;key[i];i++) if (ISWHITESPACE(key[i]) || key[i] == '\"') fail = true; for (i = 0;value[i];i++) if (value[i] == '\r' || value[i] == '\n' || value[i] == '\"') fail = true; if (fail) { if (!quiet) Con_Printf("Can't setinfo \"%s\" \"%s\"\n", key, value); return; } InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), key, value); if (cls.state == ca_connected && cls.netcon) { if (cls.protocol == PROTOCOL_QUAKEWORLD) { MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "setinfo \"%s\" \"%s\"", key, value)); } else if (!strcasecmp(key, "name")) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "name \"%s\"", value)); } else if (!strcasecmp(key, "playermodel")) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "playermodel \"%s\"", value)); } else if (!strcasecmp(key, "playerskin")) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "playerskin \"%s\"", value)); } else if (!strcasecmp(key, "topcolor")) { // don't send anything, the combined color code will be updated manually } else if (!strcasecmp(key, "bottomcolor")) { // don't send anything, the combined color code will be updated manually } else if (!strcasecmp(key, "rate")) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate \"%s\"", value)); } else if (!strcasecmp(key, "rate_burstsize")) { MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate_burstsize \"%s\"", value)); } } } void CL_ExpandEntities(int num) { int i, oldmaxentities; entity_t *oldentities; if (num >= cl.max_entities) { if (!cl.entities) Sys_Error("CL_ExpandEntities: cl.entities not initialized"); if (num >= MAX_EDICTS) Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); oldmaxentities = cl.max_entities; oldentities = cl.entities; cl.max_entities = (num & ~255) + 256; cl.entities = (entity_t *)Mem_Alloc(cls.levelmempool, cl.max_entities * sizeof(entity_t)); memcpy(cl.entities, oldentities, oldmaxentities * sizeof(entity_t)); Mem_Free(oldentities); for (i = oldmaxentities;i < cl.max_entities;i++) { cl.entities[i].state_baseline = defaultstate; cl.entities[i].state_previous = defaultstate; cl.entities[i].state_current = defaultstate; } } } void CL_ExpandCSQCRenderEntities(int num) { int i; int oldmaxcsqcrenderentities; entity_render_t *oldcsqcrenderentities; if (num >= cl.max_csqcrenderentities) { if (num >= MAX_EDICTS) Host_Error("CL_ExpandEntities: num %i >= %i", num, MAX_EDICTS); oldmaxcsqcrenderentities = cl.max_csqcrenderentities; oldcsqcrenderentities = cl.csqcrenderentities; cl.max_csqcrenderentities = (num & ~255) + 256; cl.csqcrenderentities = (entity_render_t *)Mem_Alloc(cls.levelmempool, cl.max_csqcrenderentities * sizeof(entity_render_t)); if (oldcsqcrenderentities) { memcpy(cl.csqcrenderentities, oldcsqcrenderentities, oldmaxcsqcrenderentities * sizeof(entity_render_t)); for (i = 0;i < r_refdef.scene.numentities;i++) if(r_refdef.scene.entities[i] >= oldcsqcrenderentities && r_refdef.scene.entities[i] < (oldcsqcrenderentities + oldmaxcsqcrenderentities)) r_refdef.scene.entities[i] = cl.csqcrenderentities + (r_refdef.scene.entities[i] - oldcsqcrenderentities); Mem_Free(oldcsqcrenderentities); } } } /* ===================== CL_Disconnect Sends a disconnect message to the server This is also called on Host_Error, so it shouldn't cause any errors ===================== */ void CL_Disconnect(void) { if (cls.state == ca_dedicated) return; if (COM_CheckParm("-profilegameonly")) Sys_AllowProfiling(false); Curl_Clear_forthismap(); Con_DPrintf("CL_Disconnect\n"); Cvar_SetValueQuick(&csqc_progcrc, -1); Cvar_SetValueQuick(&csqc_progsize, -1); CL_VM_ShutDown(); // stop sounds (especially looping!) S_StopAllSounds (); cl.parsingtextexpectingpingforscores = 0; // just in case no reply has come yet // clear contents blends cl.cshifts[0].percent = 0; cl.cshifts[1].percent = 0; cl.cshifts[2].percent = 0; cl.cshifts[3].percent = 0; cl.worldmodel = NULL; CL_Parse_ErrorCleanUp(); if (cls.demoplayback) CL_StopPlayback(); else if (cls.netcon) { sizebuf_t buf; unsigned char bufdata[8]; if (cls.demorecording) CL_Stop_f(); // send disconnect message 3 times to improve chances of server // receiving it (but it still fails sometimes) memset(&buf, 0, sizeof(buf)); buf.data = bufdata; buf.maxsize = sizeof(bufdata); if (cls.protocol == PROTOCOL_QUAKEWORLD) { Con_DPrint("Sending drop command\n"); MSG_WriteByte(&buf, qw_clc_stringcmd); MSG_WriteString(&buf, "drop"); } else { Con_DPrint("Sending clc_disconnect\n"); MSG_WriteByte(&buf, clc_disconnect); } NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, 0, false); NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, 10000, 0, false); NetConn_Close(cls.netcon); cls.netcon = NULL; } cls.state = ca_disconnected; cl.islocalgame = false; cls.demoplayback = cls.timedemo = false; cls.signon = 0; } void CL_Disconnect_f(void) { CL_Disconnect (); if (sv.active) Host_ShutdownServer (); } /* ===================== CL_EstablishConnection Host should be either "local" or a net address ===================== */ void CL_EstablishConnection(const char *host, int firstarg) { if (cls.state == ca_dedicated) return; // don't connect to a server if we're benchmarking a demo if (COM_CheckParm("-benchmark")) return; // clear menu's connect error message #ifdef CONFIG_MENU M_Update_Return_Reason(""); #endif cls.demonum = -1; // stop demo loop in case this fails if (cls.demoplayback) CL_StopPlayback(); // if downloads are running, cancel their finishing action Curl_Clear_forthismap(); // make sure the client ports are open before attempting to connect NetConn_UpdateSockets(); if (LHNETADDRESS_FromString(&cls.connect_address, host, 26000) && (cls.connect_mysocket = NetConn_ChooseClientSocketForAddress(&cls.connect_address))) { cls.connect_trying = true; cls.connect_remainingtries = 3; cls.connect_nextsendtime = 0; // only NOW, set connect_userinfo if(firstarg >= 0) { int i; *cls.connect_userinfo = 0; for(i = firstarg; i+2 <= Cmd_Argc(); i += 2) InfoString_SetValue(cls.connect_userinfo, sizeof(cls.connect_userinfo), Cmd_Argv(i), Cmd_Argv(i+1)); } else if(firstarg < -1) { // -1: keep as is (reconnect) // -2: clear *cls.connect_userinfo = 0; } #ifdef CONFIG_MENU M_Update_Return_Reason("Trying to connect..."); #endif } else { Con_Print("Unable to find a suitable network socket to connect to server.\n"); #ifdef CONFIG_MENU M_Update_Return_Reason("No network"); #endif } } /* ============== CL_PrintEntities_f ============== */ static void CL_PrintEntities_f(void) { entity_t *ent; int i; for (i = 0, ent = cl.entities;i < cl.num_entities;i++, ent++) { const char* modelname; if (!ent->state_current.active) continue; if (ent->render.model) modelname = ent->render.model->name; else modelname = "--no model--"; Con_Printf("%3i: %-25s:%4i (%5i %5i %5i) [%3i %3i %3i] %4.2f %5.3f\n", i, modelname, ent->render.framegroupblend[0].frame, (int) ent->state_current.origin[0], (int) ent->state_current.origin[1], (int) ent->state_current.origin[2], (int) ent->state_current.angles[0] % 360, (int) ent->state_current.angles[1] % 360, (int) ent->state_current.angles[2] % 360, ent->render.scale, ent->render.alpha); } } /* =============== CL_ModelIndexList_f List information on all models in the client modelindex =============== */ static void CL_ModelIndexList_f(void) { int i; dp_model_t *model; // Print Header Con_Printf("%3s: %-30s %-8s %-8s\n", "ID", "Name", "Type", "Triangles"); for (i = -MAX_MODELS;i < MAX_MODELS;i++) { model = CL_GetModelByIndex(i); if (!model) continue; if(model->loaded || i == 1) Con_Printf("%3i: %-30s %-8s %-10i\n", i, model->name, model->modeldatatypestring, model->surfmesh.num_triangles); else Con_Printf("%3i: %-30s %-30s\n", i, model->name, "--no local model found--"); i++; } } /* =============== CL_SoundIndexList_f List all sounds in the client soundindex =============== */ static void CL_SoundIndexList_f(void) { int i = 1; while(cl.sound_precache[i] && i != MAX_SOUNDS) { // Valid Sound Con_Printf("%i : %s\n", i, cl.sound_precache[i]->name); i++; } } /* =============== CL_UpdateRenderEntity Updates inversematrix, animation interpolation factors, scale, and mins/maxs =============== */ void CL_UpdateRenderEntity(entity_render_t *ent) { vec3_t org; vec_t scale; dp_model_t *model = ent->model; // update the inverse matrix for the renderer Matrix4x4_Invert_Simple(&ent->inversematrix, &ent->matrix); // update the animation blend state VM_FrameBlendFromFrameGroupBlend(ent->frameblend, ent->framegroupblend, ent->model, cl.time); // we need the matrix origin to center the box Matrix4x4_OriginFromMatrix(&ent->matrix, org); // update entity->render.scale because the renderer needs it ent->scale = scale = Matrix4x4_ScaleFromMatrix(&ent->matrix); if (model) { // NOTE: this directly extracts vector components from the matrix, which relies on the matrix orientation! #ifdef MATRIX4x4_OPENGLORIENTATION if (ent->matrix.m[0][2] != 0 || ent->matrix.m[1][2] != 0) #else if (ent->matrix.m[2][0] != 0 || ent->matrix.m[2][1] != 0) #endif { // pitch or roll VectorMA(org, scale, model->rotatedmins, ent->mins); VectorMA(org, scale, model->rotatedmaxs, ent->maxs); } #ifdef MATRIX4x4_OPENGLORIENTATION else if (ent->matrix.m[1][0] != 0 || ent->matrix.m[0][1] != 0) #else else if (ent->matrix.m[0][1] != 0 || ent->matrix.m[1][0] != 0) #endif { // yaw VectorMA(org, scale, model->yawmins, ent->mins); VectorMA(org, scale, model->yawmaxs, ent->maxs); } else { VectorMA(org, scale, model->normalmins, ent->mins); VectorMA(org, scale, model->normalmaxs, ent->maxs); } } else { ent->mins[0] = org[0] - 16; ent->mins[1] = org[1] - 16; ent->mins[2] = org[2] - 16; ent->maxs[0] = org[0] + 16; ent->maxs[1] = org[1] + 16; ent->maxs[2] = org[2] + 16; } } /* =============== CL_LerpPoint Determines the fraction between the last two messages that the objects should be put at. =============== */ static float CL_LerpPoint(void) { float f; if (cl_nettimesyncboundmode.integer == 1) cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); // LordHavoc: lerp in listen games as the server is being capped below the client (usually) if (cl.mtime[0] <= cl.mtime[1]) { cl.time = cl.mtime[0]; return 1; } f = (cl.time - cl.mtime[1]) / (cl.mtime[0] - cl.mtime[1]); return bound(0, f, 1 + cl_lerpexcess.value); } void CL_ClearTempEntities (void) { r_refdef.scene.numtempentities = 0; // grow tempentities buffer on request if (r_refdef.scene.expandtempentities) { Con_Printf("CL_NewTempEntity: grow maxtempentities from %i to %i\n", r_refdef.scene.maxtempentities, r_refdef.scene.maxtempentities * 2); r_refdef.scene.maxtempentities *= 2; r_refdef.scene.tempentities = (entity_render_t *)Mem_Realloc(cls.permanentmempool, r_refdef.scene.tempentities, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); r_refdef.scene.expandtempentities = false; } } entity_render_t *CL_NewTempEntity(double shadertime) { entity_render_t *render; if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) return NULL; if (r_refdef.scene.numtempentities >= r_refdef.scene.maxtempentities) { r_refdef.scene.expandtempentities = true; // will be reallocated next frame since current frame may have pointers set already return NULL; } render = &r_refdef.scene.tempentities[r_refdef.scene.numtempentities++]; memset (render, 0, sizeof(*render)); r_refdef.scene.entities[r_refdef.scene.numentities++] = render; render->shadertime = shadertime; render->alpha = 1; VectorSet(render->colormod, 1, 1, 1); VectorSet(render->glowmod, 1, 1, 1); return render; } void CL_Effect(vec3_t org, int modelindex, int startframe, int framecount, float framerate) { int i; cl_effect_t *e; if (!modelindex) // sanity check return; if (framerate < 1) { Con_Printf("CL_Effect: framerate %f is < 1\n", framerate); return; } if (framecount < 1) { Con_Printf("CL_Effect: framecount %i is < 1\n", framecount); return; } for (i = 0, e = cl.effects;i < cl.max_effects;i++, e++) { if (e->active) continue; e->active = true; VectorCopy(org, e->origin); e->modelindex = modelindex; e->starttime = cl.time; e->startframe = startframe; e->endframe = startframe + framecount; e->framerate = framerate; e->frame = 0; e->frame1time = cl.time; e->frame2time = cl.time; cl.num_effects = max(cl.num_effects, i + 1); break; } } void CL_AllocLightFlash(entity_render_t *ent, matrix4x4_t *matrix, float radius, float red, float green, float blue, float decay, float lifetime, int cubemapnum, int style, int shadowenable, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) { int i; dlight_t *dl; // then look for anything else dl = cl.dlights; for (i = 0;i < cl.max_dlights;i++, dl++) if (!dl->radius) break; // unable to find one if (i == cl.max_dlights) return; //Con_Printf("dlight %i : %f %f %f : %f %f %f\n", i, org[0], org[1], org[2], red * radius, green * radius, blue * radius); memset (dl, 0, sizeof(*dl)); cl.num_dlights = max(cl.num_dlights, i + 1); Matrix4x4_Normalize(&dl->matrix, matrix); dl->ent = ent; Matrix4x4_OriginFromMatrix(&dl->matrix, dl->origin); CL_FindNonSolidLocation(dl->origin, dl->origin, 6); Matrix4x4_SetOrigin(&dl->matrix, dl->origin[0], dl->origin[1], dl->origin[2]); dl->radius = radius; dl->color[0] = red; dl->color[1] = green; dl->color[2] = blue; dl->initialradius = radius; dl->initialcolor[0] = red; dl->initialcolor[1] = green; dl->initialcolor[2] = blue; dl->decay = decay / radius; // changed decay to be a percentage decrease dl->intensity = 1; // this is what gets decayed if (lifetime) dl->die = cl.time + lifetime; else dl->die = 0; if (cubemapnum > 0) dpsnprintf(dl->cubemapname, sizeof(dl->cubemapname), "cubemaps/%i", cubemapnum); else dl->cubemapname[0] = 0; dl->style = style; dl->shadow = shadowenable; dl->corona = corona; dl->flags = flags; dl->coronasizescale = coronasizescale; dl->ambientscale = ambientscale; dl->diffusescale = diffusescale; dl->specularscale = specularscale; } static void CL_DecayLightFlashes(void) { int i, oldmax; dlight_t *dl; float time; time = bound(0, cl.time - cl.oldtime, 0.1); oldmax = cl.num_dlights; cl.num_dlights = 0; for (i = 0, dl = cl.dlights;i < oldmax;i++, dl++) { if (dl->radius) { dl->intensity -= time * dl->decay; if (cl.time < dl->die && dl->intensity > 0) { if (cl_dlights_decayradius.integer) dl->radius = dl->initialradius * dl->intensity; else dl->radius = dl->initialradius; if (cl_dlights_decaybrightness.integer) VectorScale(dl->initialcolor, dl->intensity, dl->color); else VectorCopy(dl->initialcolor, dl->color); cl.num_dlights = i + 1; } else dl->radius = 0; } } } // called before entity relinking void CL_RelinkLightFlashes(void) { int i, j, k, l; dlight_t *dl; float frac, f; matrix4x4_t tempmatrix; if (r_dynamic.integer) { for (i = 0, dl = cl.dlights;i < cl.num_dlights && r_refdef.scene.numlights < MAX_DLIGHTS;i++, dl++) { if (dl->radius) { tempmatrix = dl->matrix; Matrix4x4_Scale(&tempmatrix, dl->radius, 1); // we need the corona fading to be persistent R_RTLight_Update(&dl->rtlight, false, &tempmatrix, dl->color, dl->style, dl->cubemapname, dl->shadow, dl->corona, dl->coronasizescale, dl->ambientscale, dl->diffusescale, dl->specularscale, dl->flags); r_refdef.scene.lights[r_refdef.scene.numlights++] = &dl->rtlight; } } } if (!cl.lightstyle) { for (j = 0;j < cl.max_lightstyle;j++) { r_refdef.scene.rtlightstylevalue[j] = 1; r_refdef.scene.lightstylevalue[j] = 256; } return; } // light animations // 'm' is normal light, 'a' is no light, 'z' is double bright f = cl.time * 10; i = (int)floor(f); frac = f - i; for (j = 0;j < cl.max_lightstyle;j++) { if (!cl.lightstyle[j].length) { r_refdef.scene.rtlightstylevalue[j] = 1; r_refdef.scene.lightstylevalue[j] = 256; continue; } // static lightstyle "=value" if (cl.lightstyle[j].map[0] == '=') { r_refdef.scene.rtlightstylevalue[j] = atof(cl.lightstyle[j].map + 1); if ( r_lerplightstyles.integer || ((int)f - f) < 0.01) r_refdef.scene.lightstylevalue[j] = r_refdef.scene.rtlightstylevalue[j]; continue; } k = i % cl.lightstyle[j].length; l = (i-1) % cl.lightstyle[j].length; k = cl.lightstyle[j].map[k] - 'a'; l = cl.lightstyle[j].map[l] - 'a'; // rtlightstylevalue is always interpolated because it has no bad // consequences for performance // lightstylevalue is subject to a cvar for performance reasons; // skipping lightmap updates on most rendered frames substantially // improves framerates (but makes light fades look bad) r_refdef.scene.rtlightstylevalue[j] = ((k*frac)+(l*(1-frac)))*(22/256.0f); r_refdef.scene.lightstylevalue[j] = r_lerplightstyles.integer ? (unsigned short)(((k*frac)+(l*(1-frac)))*22) : k*22; } } static void CL_AddQWCTFFlagModel(entity_t *player, int skin) { int frame = player->render.framegroupblend[0].frame; float f; entity_render_t *flagrender; matrix4x4_t flagmatrix; // this code taken from QuakeWorld f = 14; if (frame >= 29 && frame <= 40) { if (frame >= 29 && frame <= 34) { //axpain if (frame == 29) f = f + 2; else if (frame == 30) f = f + 8; else if (frame == 31) f = f + 12; else if (frame == 32) f = f + 11; else if (frame == 33) f = f + 10; else if (frame == 34) f = f + 4; } else if (frame >= 35 && frame <= 40) { // pain if (frame == 35) f = f + 2; else if (frame == 36) f = f + 10; else if (frame == 37) f = f + 10; else if (frame == 38) f = f + 8; else if (frame == 39) f = f + 4; else if (frame == 40) f = f + 2; } } else if (frame >= 103 && frame <= 118) { if (frame >= 103 && frame <= 104) f = f + 6; //nailattack else if (frame >= 105 && frame <= 106) f = f + 6; //light else if (frame >= 107 && frame <= 112) f = f + 7; //rocketattack else if (frame >= 112 && frame <= 118) f = f + 7; //shotattack } // end of code taken from QuakeWorld flagrender = CL_NewTempEntity(player->render.shadertime); if (!flagrender) return; flagrender->model = CL_GetModelByIndex(cl.qw_modelindex_flag); flagrender->skinnum = skin; flagrender->alpha = 1; VectorSet(flagrender->colormod, 1, 1, 1); VectorSet(flagrender->glowmod, 1, 1, 1); // attach the flag to the player matrix Matrix4x4_CreateFromQuakeEntity(&flagmatrix, -f, -22, 0, 0, 0, -45, 1); Matrix4x4_Concat(&flagrender->matrix, &player->render.matrix, &flagmatrix); CL_UpdateRenderEntity(flagrender); } matrix4x4_t viewmodelmatrix_withbob; matrix4x4_t viewmodelmatrix_nobob; static const vec3_t muzzleflashorigin = {18, 0, 0}; void CL_SetEntityColormapColors(entity_render_t *ent, int colormap) { const unsigned char *cbcolor; if (colormap >= 0) { cbcolor = palette_rgb_pantscolormap[colormap & 0xF]; VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_pantscolor); cbcolor = palette_rgb_shirtcolormap[(colormap & 0xF0) >> 4]; VectorScale(cbcolor, (1.0f / 255.0f), ent->colormap_shirtcolor); } else { VectorClear(ent->colormap_pantscolor); VectorClear(ent->colormap_shirtcolor); } } // note this is a recursive function, recursionlimit should be 32 or so on the initial call static void CL_UpdateNetworkEntity(entity_t *e, int recursionlimit, qboolean interpolate) { const matrix4x4_t *matrix; matrix4x4_t blendmatrix, tempmatrix, matrix2; int frame; vec_t origin[3], angles[3], lerp; entity_t *t; entity_render_t *r; //entity_persistent_t *p = &e->persistent; //entity_render_t *r = &e->render; // skip inactive entities and world if (!e->state_current.active || e == cl.entities) return; if (recursionlimit < 1) return; e->render.alpha = e->state_current.alpha * (1.0f / 255.0f); // FIXME: interpolate? e->render.scale = e->state_current.scale * (1.0f / 16.0f); // FIXME: interpolate? e->render.flags = e->state_current.flags; e->render.effects = e->state_current.effects; VectorScale(e->state_current.colormod, (1.0f / 32.0f), e->render.colormod); VectorScale(e->state_current.glowmod, (1.0f / 32.0f), e->render.glowmod); if(e >= cl.entities && e < cl.entities + cl.num_entities) e->render.entitynumber = e - cl.entities; else e->render.entitynumber = 0; if (e->state_current.flags & RENDER_COLORMAPPED) CL_SetEntityColormapColors(&e->render, e->state_current.colormap); else if (e->state_current.colormap > 0 && e->state_current.colormap <= cl.maxclients && cl.scores != NULL) CL_SetEntityColormapColors(&e->render, cl.scores[e->state_current.colormap-1].colors); else CL_SetEntityColormapColors(&e->render, -1); e->render.skinnum = e->state_current.skin; if (e->state_current.tagentity) { // attached entity (gun held in player model's hand, etc) // if the tag entity is currently impossible, skip it if (e->state_current.tagentity >= cl.num_entities) return; t = cl.entities + e->state_current.tagentity; // if the tag entity is inactive, skip it if (t->state_current.active) { // update the parent first CL_UpdateNetworkEntity(t, recursionlimit - 1, interpolate); r = &t->render; } else { // it may still be a CSQC entity... trying to use its // info from last render frame (better than nothing) if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) return; r = cl.csqcrenderentities + cl.csqc_server2csqcentitynumber[e->state_current.tagentity]; if(!r->entitynumber) return; // neither CSQC nor legacy entity... can't attach } // make relative to the entity matrix = &r->matrix; // some properties of the tag entity carry over e->render.flags |= r->flags & (RENDER_EXTERIORMODEL | RENDER_VIEWMODEL); // if a valid tagindex is used, make it relative to that tag instead if (e->state_current.tagentity && e->state_current.tagindex >= 1 && r->model) { if(!Mod_Alias_GetTagMatrix(r->model, r->frameblend, r->skeleton, e->state_current.tagindex - 1, &blendmatrix)) // i.e. no error { // concat the tag matrices onto the entity matrix Matrix4x4_Concat(&tempmatrix, &r->matrix, &blendmatrix); // use the constructed tag matrix matrix = &tempmatrix; } } } else if (e->render.flags & RENDER_VIEWMODEL) { // view-relative entity (guns and such) if (e->render.effects & EF_NOGUNBOB) matrix = &viewmodelmatrix_nobob; // really attached to view else matrix = &viewmodelmatrix_withbob; // attached to gun bob matrix } else { // world-relative entity (the normal kind) matrix = &identitymatrix; } // movement lerp // if it's the predicted player entity, update according to client movement // but don't lerp if going through a teleporter as it causes a bad lerp // also don't use the predicted location if fixangle was set on both of // the most recent server messages, as that cause means you are spectating // someone or watching a cutscene of some sort if (cl_nolerp.integer || cls.timedemo) interpolate = false; if (e == cl.entities + cl.playerentity && cl.movement_predicted && (!cl.fixangle[1] || !cl.fixangle[0])) { VectorCopy(cl.movement_origin, origin); VectorSet(angles, 0, cl.viewangles[1], 0); } else if (interpolate && e->persistent.lerpdeltatime > 0 && (lerp = (cl.time - e->persistent.lerpstarttime) / e->persistent.lerpdeltatime) < 1 + cl_lerpexcess.value) { // interpolate the origin and angles lerp = max(0, lerp); VectorLerp(e->persistent.oldorigin, lerp, e->persistent.neworigin, origin); #if 0 // this fails at the singularity of euler angles VectorSubtract(e->persistent.newangles, e->persistent.oldangles, delta); if (delta[0] < -180) delta[0] += 360;else if (delta[0] >= 180) delta[0] -= 360; if (delta[1] < -180) delta[1] += 360;else if (delta[1] >= 180) delta[1] -= 360; if (delta[2] < -180) delta[2] += 360;else if (delta[2] >= 180) delta[2] -= 360; VectorMA(e->persistent.oldangles, lerp, delta, angles); #else { vec3_t f0, u0, f1, u1; AngleVectors(e->persistent.oldangles, f0, NULL, u0); AngleVectors(e->persistent.newangles, f1, NULL, u1); VectorMAM(1-lerp, f0, lerp, f1, f0); VectorMAM(1-lerp, u0, lerp, u1, u0); AnglesFromVectors(angles, f0, u0, false); } #endif } else { // no interpolation VectorCopy(e->persistent.neworigin, origin); VectorCopy(e->persistent.newangles, angles); } // model setup and some modelflags frame = e->state_current.frame; e->render.model = CL_GetModelByIndex(e->state_current.modelindex); if (e->render.model) { if (e->render.skinnum >= e->render.model->numskins) e->render.skinnum = 0; if (frame >= e->render.model->numframes) frame = 0; // models can set flags such as EF_ROCKET // this 0xFF800000 mask is EF_NOMODELFLAGS plus all the higher EF_ flags such as EF_ROCKET if (!(e->render.effects & 0xFF800000)) e->render.effects |= e->render.model->effects; // if model is alias or this is a tenebrae-like dlight, reverse pitch direction if (e->render.model->type == mod_alias) angles[0] = -angles[0]; if ((e->render.effects & EF_SELECTABLE) && cl.cmd.cursor_entitynumber == e->state_current.number) { VectorScale(e->render.colormod, 2, e->render.colormod); VectorScale(e->render.glowmod, 2, e->render.glowmod); } } // if model is alias or this is a tenebrae-like dlight, reverse pitch direction else if (e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) angles[0] = -angles[0]; // NOTE: this must be synced to SV_GetPitchSign! if ((e->render.effects & EF_ROTATE) && !(e->render.flags & RENDER_VIEWMODEL)) { angles[1] = ANGLEMOD(100*cl.time); if (cl_itembobheight.value) origin[2] += (cos(cl.time * cl_itembobspeed.value * (2.0 * M_PI)) + 1.0) * 0.5 * cl_itembobheight.value; } // animation lerp e->render.skeleton = NULL; if (e->render.flags & RENDER_COMPLEXANIMATION) { e->render.framegroupblend[0] = e->state_current.framegroupblend[0]; e->render.framegroupblend[1] = e->state_current.framegroupblend[1]; e->render.framegroupblend[2] = e->state_current.framegroupblend[2]; e->render.framegroupblend[3] = e->state_current.framegroupblend[3]; if (e->state_current.skeletonobject.model && e->state_current.skeletonobject.relativetransforms) e->render.skeleton = &e->state_current.skeletonobject; } else if (e->render.framegroupblend[0].frame == frame) { // update frame lerp fraction e->render.framegroupblend[0].lerp = 1; e->render.framegroupblend[1].lerp = 0; if (e->render.framegroupblend[0].start > e->render.framegroupblend[1].start) { // make sure frame lerp won't last longer than 100ms // (this mainly helps with models that use framegroups and // switch between them infrequently) float maxdelta = cl_lerpanim_maxdelta_server.value; if(e->render.model) if(e->render.model->animscenes) if(e->render.model->animscenes[e->render.framegroupblend[0].frame].framecount > 1 || e->render.model->animscenes[e->render.framegroupblend[1].frame].framecount > 1) maxdelta = cl_lerpanim_maxdelta_framegroups.value; maxdelta = max(maxdelta, cl.mtime[0] - cl.mtime[1]); e->render.framegroupblend[0].lerp = (cl.time - e->render.framegroupblend[0].start) / min(e->render.framegroupblend[0].start - e->render.framegroupblend[1].start, maxdelta); e->render.framegroupblend[0].lerp = bound(0, e->render.framegroupblend[0].lerp, 1); e->render.framegroupblend[1].lerp = 1 - e->render.framegroupblend[0].lerp; } } else { // begin a new frame lerp e->render.framegroupblend[1] = e->render.framegroupblend[0]; e->render.framegroupblend[1].lerp = 1; e->render.framegroupblend[0].frame = frame; e->render.framegroupblend[0].start = cl.time; e->render.framegroupblend[0].lerp = 0; } // set up the render matrix if (matrix) { // attached entity, this requires a matrix multiply (concat) // FIXME: e->render.scale should go away Matrix4x4_CreateFromQuakeEntity(&matrix2, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); // concat the matrices to make the entity relative to its tag Matrix4x4_Concat(&e->render.matrix, matrix, &matrix2); // get the origin from the new matrix Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); } else { // unattached entities are faster to process Matrix4x4_CreateFromQuakeEntity(&e->render.matrix, origin[0], origin[1], origin[2], angles[0], angles[1], angles[2], e->render.scale); } // tenebrae's sprites are all additive mode (weird) if (gamemode == GAME_TENEBRAE && e->render.model && e->render.model->type == mod_sprite) e->render.flags |= RENDER_ADDITIVE; // player model is only shown with chase_active on if (e->state_current.number == cl.viewentity) e->render.flags |= RENDER_EXTERIORMODEL; // either fullbright or lit if(!r_fullbright.integer) { if (!(e->render.effects & EF_FULLBRIGHT)) e->render.flags |= RENDER_LIGHT; else if(r_equalize_entities_fullbright.integer) e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; } // hide player shadow during intermission or nehahra movie if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (e->render.alpha >= 1) && !(e->render.flags & RENDER_VIEWMODEL) && (!(e->render.flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) e->render.flags |= RENDER_SHADOW; if (e->render.flags & RENDER_VIEWMODEL) e->render.flags |= RENDER_NOSELFSHADOW; if (e->render.effects & EF_NOSELFSHADOW) e->render.flags |= RENDER_NOSELFSHADOW; if (e->render.effects & EF_NODEPTHTEST) e->render.flags |= RENDER_NODEPTHTEST; if (e->render.effects & EF_ADDITIVE) e->render.flags |= RENDER_ADDITIVE; if (e->render.effects & EF_DOUBLESIDED) e->render.flags |= RENDER_DOUBLESIDED; if (e->render.effects & EF_DYNAMICMODELLIGHT) e->render.flags |= RENDER_DYNAMICMODELLIGHT; // make the other useful stuff e->render.allowdecals = true; CL_UpdateRenderEntity(&e->render); } // creates light and trails from an entity static void CL_UpdateNetworkEntityTrail(entity_t *e) { effectnameindex_t trailtype; vec3_t origin; // bmodels are treated specially since their origin is usually '0 0 0' and // their actual geometry is far from '0 0 0' if (e->render.model && e->render.model->soundfromcenter) { vec3_t o; VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); Matrix4x4_Transform(&e->render.matrix, o, origin); } else Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); // handle particle trails and such effects now that we know where this // entity is in the world... trailtype = EFFECT_NONE; // LordHavoc: if the entity has no effects, don't check each if (e->render.effects & (EF_BRIGHTFIELD | EF_FLAME | EF_STARDUST)) { if (e->render.effects & EF_BRIGHTFIELD) { if (IS_NEXUIZ_DERIVED(gamemode)) trailtype = EFFECT_TR_NEXUIZPLASMA; else CL_EntityParticles(e); } if (e->render.effects & EF_FLAME) CL_ParticleTrail(EFFECT_EF_FLAME, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL, 1); if (e->render.effects & EF_STARDUST) CL_ParticleTrail(EFFECT_EF_STARDUST, bound(0, cl.time - cl.oldtime, 0.1), origin, origin, vec3_origin, vec3_origin, NULL, 0, false, true, NULL, NULL, 1); } if (e->render.internaleffects & (INTEF_FLAG1QW | INTEF_FLAG2QW)) { // these are only set on player entities CL_AddQWCTFFlagModel(e, (e->render.internaleffects & INTEF_FLAG2QW) != 0); } // muzzleflash fades over time if (e->persistent.muzzleflash > 0) e->persistent.muzzleflash -= bound(0, cl.time - cl.oldtime, 0.1) * 20; // LordHavoc: if the entity has no effects, don't check each if (e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) { if (e->render.effects & EF_GIB) trailtype = EFFECT_TR_BLOOD; else if (e->render.effects & EF_ZOMGIB) trailtype = EFFECT_TR_SLIGHTBLOOD; else if (e->render.effects & EF_TRACER) trailtype = EFFECT_TR_WIZSPIKE; else if (e->render.effects & EF_TRACER2) trailtype = EFFECT_TR_KNIGHTSPIKE; else if (e->render.effects & EF_ROCKET) trailtype = EFFECT_TR_ROCKET; else if (e->render.effects & EF_GRENADE) { // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; } else if (e->render.effects & EF_TRACER3) trailtype = EFFECT_TR_VORESPIKE; } // do trails if (e->render.flags & RENDER_GLOWTRAIL) trailtype = EFFECT_TR_GLOWTRAIL; if (e->state_current.traileffectnum) trailtype = (effectnameindex_t)e->state_current.traileffectnum; // check if a trail is allowed (it is not after a teleport for example) if (trailtype && e->persistent.trail_allowed) { float len; vec3_t vel; VectorSubtract(e->state_current.origin, e->state_previous.origin, vel); len = e->state_current.time - e->state_previous.time; if (len > 0) len = 1.0f / len; VectorScale(vel, len, vel); // pass time as count so that trails that are time based (such as an emitter) will emit properly as long as they don't use trailspacing CL_ParticleTrail(trailtype, bound(0, cl.time - cl.oldtime, 0.1), e->persistent.trail_origin, origin, vel, vel, e, e->state_current.glowcolor, false, true, NULL, NULL, 1); } // now that the entity has survived one trail update it is allowed to // leave a real trail on later frames e->persistent.trail_allowed = true; VectorCopy(origin, e->persistent.trail_origin); } /* =============== CL_UpdateViewEntities =============== */ void CL_UpdateViewEntities(void) { int i; // update any RENDER_VIEWMODEL entities to use the new view matrix for (i = 1;i < cl.num_entities;i++) { if (cl.entities_active[i]) { entity_t *ent = cl.entities + i; if ((ent->render.flags & RENDER_VIEWMODEL) || ent->state_current.tagentity) CL_UpdateNetworkEntity(ent, 32, true); } } // and of course the engine viewmodel needs updating as well CL_UpdateNetworkEntity(&cl.viewent, 32, true); } /* =============== CL_UpdateNetworkCollisionEntities =============== */ static void CL_UpdateNetworkCollisionEntities(void) { entity_t *ent; int i; // start on the entity after the world cl.num_brushmodel_entities = 0; for (i = cl.maxclients + 1;i < cl.num_entities;i++) { if (cl.entities_active[i]) { ent = cl.entities + i; if (ent->state_current.active && ent->render.model && ent->render.model->name[0] == '*' && ent->render.model->TraceBox) { // do not interpolate the bmodels for this CL_UpdateNetworkEntity(ent, 32, false); cl.brushmodel_entities[cl.num_brushmodel_entities++] = i; } } } } /* =============== CL_UpdateNetworkEntities =============== */ static void CL_UpdateNetworkEntities(void) { entity_t *ent; int i; // start on the entity after the world for (i = 1;i < cl.num_entities;i++) { if (cl.entities_active[i]) { ent = cl.entities + i; if (ent->state_current.active) { CL_UpdateNetworkEntity(ent, 32, true); // view models should never create light/trails if (!(ent->render.flags & RENDER_VIEWMODEL)) CL_UpdateNetworkEntityTrail(ent); } else { R_DecalSystem_Reset(&ent->render.decalsystem); cl.entities_active[i] = false; } } } } static void CL_UpdateViewModel(void) { entity_t *ent; ent = &cl.viewent; ent->state_previous = ent->state_current; ent->state_current = defaultstate; ent->state_current.time = cl.time; ent->state_current.number = (unsigned short)-1; ent->state_current.active = true; ent->state_current.modelindex = cl.stats[STAT_WEAPON]; ent->state_current.frame = cl.stats[STAT_WEAPONFRAME]; ent->state_current.flags = RENDER_VIEWMODEL; if ((cl.stats[STAT_HEALTH] <= 0 && cl_deathnoviewmodel.integer) || cl.intermission) ent->state_current.modelindex = 0; else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) { if (gamemode == GAME_TRANSFUSION) ent->state_current.alpha = 128; else ent->state_current.modelindex = 0; } ent->state_current.alpha = cl.entities[cl.viewentity].state_current.alpha; ent->state_current.effects = EF_NOSHADOW | (cl.entities[cl.viewentity].state_current.effects & (EF_ADDITIVE | EF_FULLBRIGHT | EF_NODEPTHTEST | EF_NOGUNBOB)); // reset animation interpolation on weaponmodel if model changed if (ent->state_previous.modelindex != ent->state_current.modelindex) { ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; } CL_UpdateNetworkEntity(ent, 32, true); } // note this is a recursive function, but it can never get in a runaway loop (because of the delayedlink flags) static void CL_LinkNetworkEntity(entity_t *e) { effectnameindex_t trailtype; vec3_t origin; vec3_t dlightcolor; vec_t dlightradius; char vabuf[1024]; // skip inactive entities and world if (!e->state_current.active || e == cl.entities) return; if (e->state_current.tagentity) { // if the tag entity is currently impossible, skip it if (e->state_current.tagentity >= cl.num_entities) return; // if the tag entity is inactive, skip it if (!cl.entities[e->state_current.tagentity].state_current.active) { if(!cl.csqc_server2csqcentitynumber[e->state_current.tagentity]) return; if(!cl.csqcrenderentities[cl.csqc_server2csqcentitynumber[e->state_current.tagentity]].entitynumber) return; // if we get here, it's properly csqc networked and attached } } // create entity dlights associated with this entity if (e->render.model && e->render.model->soundfromcenter) { // bmodels are treated specially since their origin is usually '0 0 0' vec3_t o; VectorMAM(0.5f, e->render.model->normalmins, 0.5f, e->render.model->normalmaxs, o); Matrix4x4_Transform(&e->render.matrix, o, origin); } else Matrix4x4_OriginFromMatrix(&e->render.matrix, origin); trailtype = EFFECT_NONE; dlightradius = 0; dlightcolor[0] = 0; dlightcolor[1] = 0; dlightcolor[2] = 0; // LordHavoc: if the entity has no effects, don't check each if (e->render.effects & (EF_BRIGHTFIELD | EF_DIMLIGHT | EF_BRIGHTLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) { if (e->render.effects & EF_BRIGHTFIELD) { if (IS_NEXUIZ_DERIVED(gamemode)) trailtype = EFFECT_TR_NEXUIZPLASMA; } if (e->render.effects & EF_DIMLIGHT) { dlightradius = max(dlightradius, 200); dlightcolor[0] += 1.50f; dlightcolor[1] += 1.50f; dlightcolor[2] += 1.50f; } if (e->render.effects & EF_BRIGHTLIGHT) { dlightradius = max(dlightradius, 400); dlightcolor[0] += 3.00f; dlightcolor[1] += 3.00f; dlightcolor[2] += 3.00f; } // LordHavoc: more effects if (e->render.effects & EF_RED) // red { dlightradius = max(dlightradius, 200); dlightcolor[0] += 1.50f; dlightcolor[1] += 0.15f; dlightcolor[2] += 0.15f; } if (e->render.effects & EF_BLUE) // blue { dlightradius = max(dlightradius, 200); dlightcolor[0] += 0.15f; dlightcolor[1] += 0.15f; dlightcolor[2] += 1.50f; } if (e->render.effects & EF_FLAME) CL_ParticleTrail(EFFECT_EF_FLAME, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL, 1); if (e->render.effects & EF_STARDUST) CL_ParticleTrail(EFFECT_EF_STARDUST, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0, true, false, NULL, NULL, 1); } // muzzleflash fades over time, and is offset a bit if (e->persistent.muzzleflash > 0 && r_refdef.scene.numlights < MAX_DLIGHTS) { vec3_t v2; vec3_t color; trace_t trace; matrix4x4_t tempmatrix; Matrix4x4_Transform(&e->render.matrix, muzzleflashorigin, v2); trace = CL_TraceLine(origin, v2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, NULL, false, false); Matrix4x4_Normalize(&tempmatrix, &e->render.matrix); Matrix4x4_SetOrigin(&tempmatrix, trace.endpos[0], trace.endpos[1], trace.endpos[2]); Matrix4x4_Scale(&tempmatrix, 150, 1); VectorSet(color, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f, e->persistent.muzzleflash * 4.0f); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, color, -1, NULL, true, 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } // LordHavoc: if the model has no flags, don't check each if (e->render.model && e->render.effects && !(e->render.flags & RENDER_VIEWMODEL)) { if (e->render.effects & EF_GIB) trailtype = EFFECT_TR_BLOOD; else if (e->render.effects & EF_ZOMGIB) trailtype = EFFECT_TR_SLIGHTBLOOD; else if (e->render.effects & EF_TRACER) trailtype = EFFECT_TR_WIZSPIKE; else if (e->render.effects & EF_TRACER2) trailtype = EFFECT_TR_KNIGHTSPIKE; else if (e->render.effects & EF_ROCKET) trailtype = EFFECT_TR_ROCKET; else if (e->render.effects & EF_GRENADE) { // LordHavoc: e->render.alpha == -1 is for Nehahra dem compatibility (cigar smoke) trailtype = e->render.alpha == -1 ? EFFECT_TR_NEHAHRASMOKE : EFFECT_TR_GRENADE; } else if (e->render.effects & EF_TRACER3) trailtype = EFFECT_TR_VORESPIKE; } // LordHavoc: customizable glow if (e->state_current.glowsize) { // * 4 for the expansion from 0-255 to 0-1023 range, // / 255 to scale down byte colors dlightradius = max(dlightradius, e->state_current.glowsize * 4); VectorMA(dlightcolor, (1.0f / 255.0f), palette_rgb[e->state_current.glowcolor], dlightcolor); } // custom rtlight if ((e->state_current.lightpflags & PFLAGS_FULLDYNAMIC) && r_refdef.scene.numlights < MAX_DLIGHTS) { matrix4x4_t dlightmatrix; vec4_t light; VectorScale(e->state_current.light, (1.0f / 256.0f), light); light[3] = e->state_current.light[3]; if (light[0] == 0 && light[1] == 0 && light[2] == 0) VectorSet(light, 1, 1, 1); if (light[3] == 0) light[3] = 350; // FIXME: add ambient/diffuse/specular scales as an extension ontop of TENEBRAE_GFX_DLIGHTS? Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); Matrix4x4_Scale(&dlightmatrix, light[3], 1); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, light, e->state_current.lightstyle, e->state_current.skin > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", e->state_current.skin) : NULL, !(e->state_current.lightpflags & PFLAGS_NOSHADOW), (e->state_current.lightpflags & PFLAGS_CORONA) != 0, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } // make the glow dlight else if (dlightradius > 0 && (dlightcolor[0] || dlightcolor[1] || dlightcolor[2]) && !(e->render.flags & RENDER_VIEWMODEL) && r_refdef.scene.numlights < MAX_DLIGHTS) { matrix4x4_t dlightmatrix; Matrix4x4_Normalize(&dlightmatrix, &e->render.matrix); // hack to make glowing player light shine on their gun //if (e->state_current.number == cl.viewentity/* && !chase_active.integer*/) // Matrix4x4_AdjustOrigin(&dlightmatrix, 0, 0, 30); Matrix4x4_Scale(&dlightmatrix, dlightradius, 1); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &dlightmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } // do trail light if (e->render.flags & RENDER_GLOWTRAIL) trailtype = EFFECT_TR_GLOWTRAIL; if (e->state_current.traileffectnum) trailtype = (effectnameindex_t)e->state_current.traileffectnum; if (trailtype) CL_ParticleTrail(trailtype, 1, origin, origin, vec3_origin, vec3_origin, NULL, e->state_current.glowcolor, true, false, NULL, NULL, 1); // don't show entities with no modelindex (note: this still shows // entities which have a modelindex that resolved to a NULL model) if (e->render.model && !(e->render.effects & EF_NODRAW) && r_refdef.scene.numentities < r_refdef.scene.maxentities) r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; //if (cl.viewentity && e->state_current.number == cl.viewentity) // Matrix4x4_Print(&e->render.matrix); } static void CL_RelinkWorld(void) { entity_t *ent = &cl.entities[0]; // FIXME: this should be done at load ent->render.matrix = identitymatrix; ent->render.flags = RENDER_SHADOW; if (!r_fullbright.integer) ent->render.flags |= RENDER_LIGHT; VectorSet(ent->render.colormod, 1, 1, 1); VectorSet(ent->render.glowmod, 1, 1, 1); ent->render.allowdecals = true; CL_UpdateRenderEntity(&ent->render); r_refdef.scene.worldentity = &ent->render; r_refdef.scene.worldmodel = cl.worldmodel; // if the world is q2bsp, animate the textures if (ent->render.model && ent->render.model->brush.isq2bsp) ent->render.framegroupblend[0].frame = (int)(cl.time * 2.0f); } static void CL_RelinkStaticEntities(void) { int i; entity_t *e; for (i = 0, e = cl.static_entities;i < cl.num_static_entities && r_refdef.scene.numentities < r_refdef.scene.maxentities;i++, e++) { e->render.flags = 0; // if the model was not loaded when the static entity was created we // need to re-fetch the model pointer e->render.model = CL_GetModelByIndex(e->state_baseline.modelindex); // either fullbright or lit if(!r_fullbright.integer) { if (!(e->render.effects & EF_FULLBRIGHT)) e->render.flags |= RENDER_LIGHT; else if(r_equalize_entities_fullbright.integer) e->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; } // hide player shadow during intermission or nehahra movie if (!(e->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (e->render.alpha >= 1)) e->render.flags |= RENDER_SHADOW; VectorSet(e->render.colormod, 1, 1, 1); VectorSet(e->render.glowmod, 1, 1, 1); VM_FrameBlendFromFrameGroupBlend(e->render.frameblend, e->render.framegroupblend, e->render.model, cl.time); e->render.allowdecals = true; CL_UpdateRenderEntity(&e->render); r_refdef.scene.entities[r_refdef.scene.numentities++] = &e->render; } } /* =============== CL_RelinkEntities =============== */ static void CL_RelinkNetworkEntities(void) { entity_t *ent; int i; // start on the entity after the world for (i = 1;i < cl.num_entities;i++) { if (cl.entities_active[i]) { ent = cl.entities + i; if (ent->state_current.active) CL_LinkNetworkEntity(ent); else cl.entities_active[i] = false; } } } static void CL_RelinkEffects(void) { int i, intframe; cl_effect_t *e; entity_render_t *entrender; float frame; for (i = 0, e = cl.effects;i < cl.num_effects;i++, e++) { if (e->active) { frame = (cl.time - e->starttime) * e->framerate + e->startframe; intframe = (int)frame; if (intframe < 0 || intframe >= e->endframe) { memset(e, 0, sizeof(*e)); while (cl.num_effects > 0 && !cl.effects[cl.num_effects - 1].active) cl.num_effects--; continue; } if (intframe != e->frame) { e->frame = intframe; e->frame1time = e->frame2time; e->frame2time = cl.time; } // if we're drawing effects, get a new temp entity // (NewTempEntity adds it to the render entities list for us) if (r_draweffects.integer && (entrender = CL_NewTempEntity(e->starttime))) { // interpolation stuff entrender->framegroupblend[0].frame = intframe; entrender->framegroupblend[0].lerp = 1 - frame - intframe; entrender->framegroupblend[0].start = e->frame1time; if (intframe + 1 >= e->endframe) { entrender->framegroupblend[1].frame = 0; // disappear entrender->framegroupblend[1].lerp = 0; entrender->framegroupblend[1].start = 0; } else { entrender->framegroupblend[1].frame = intframe + 1; entrender->framegroupblend[1].lerp = frame - intframe; entrender->framegroupblend[1].start = e->frame2time; } // normal stuff entrender->model = CL_GetModelByIndex(e->modelindex); entrender->alpha = 1; VectorSet(entrender->colormod, 1, 1, 1); VectorSet(entrender->glowmod, 1, 1, 1); Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, e->origin[0], e->origin[1], e->origin[2], 0, 0, 0, 1); CL_UpdateRenderEntity(entrender); } } } } void CL_Beam_CalculatePositions(const beam_t *b, vec3_t start, vec3_t end) { VectorCopy(b->start, start); VectorCopy(b->end, end); // if coming from the player, update the start position if (b->entity == cl.viewentity) { if (cl_beams_quakepositionhack.integer && !chase_active.integer) { // LordHavoc: this is a stupid hack from Quake that makes your // lightning appear to come from your waist and cover less of your // view // in Quake this hack was applied to all players (causing the // infamous crotch-lightning), but in darkplaces and QuakeWorld it // only applies to your own lightning, and only in first person Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, start); } if (cl_beams_instantaimhack.integer) { vec3_t dir, localend; vec_t len; // LordHavoc: this updates the beam direction to match your // viewangles VectorSubtract(end, start, dir); len = VectorLength(dir); VectorNormalize(dir); VectorSet(localend, len, 0, 0); Matrix4x4_Transform(&r_refdef.view.matrix, localend, end); } } } void CL_RelinkBeams(void) { int i; beam_t *b; vec3_t dist, org, start, end; float d; entity_render_t *entrender; double yaw, pitch; float forward; matrix4x4_t tempmatrix; for (i = 0, b = cl.beams;i < cl.num_beams;i++, b++) { if (!b->model) continue; if (b->endtime < cl.time) { b->model = NULL; continue; } CL_Beam_CalculatePositions(b, start, end); if (b->lightning) { if (cl_beams_lightatend.integer && r_refdef.scene.numlights < MAX_DLIGHTS) { // FIXME: create a matrix from the beam start/end orientation vec3_t dlightcolor; VectorSet(dlightcolor, 0.3, 0.7, 1); Matrix4x4_CreateFromQuakeEntity(&tempmatrix, end[0], end[1], end[2], 0, 0, 0, 200); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, dlightcolor, -1, NULL, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } if (cl_beams_polygons.integer) continue; } // calculate pitch and yaw // (this is similar to the QuakeC builtin function vectoangles) VectorSubtract(end, start, dist); if (dist[1] == 0 && dist[0] == 0) { yaw = 0; if (dist[2] > 0) pitch = 90; else pitch = 270; } else { yaw = atan2(dist[1], dist[0]) * 180 / M_PI; if (yaw < 0) yaw += 360; forward = sqrt (dist[0]*dist[0] + dist[1]*dist[1]); pitch = atan2(dist[2], forward) * 180 / M_PI; if (pitch < 0) pitch += 360; } // add new entities for the lightning VectorCopy (start, org); d = VectorNormalizeLength(dist); while (d > 0) { entrender = CL_NewTempEntity (0); if (!entrender) return; //VectorCopy (org, ent->render.origin); entrender->model = b->model; //ent->render.effects = EF_FULLBRIGHT; //ent->render.angles[0] = pitch; //ent->render.angles[1] = yaw; //ent->render.angles[2] = rand()%360; Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, org[0], org[1], org[2], -pitch, yaw, lhrandom(0, 360), 1); CL_UpdateRenderEntity(entrender); VectorMA(org, 30, dist, org); d -= 30; } } while (cl.num_beams > 0 && !cl.beams[cl.num_beams - 1].model) cl.num_beams--; } static void CL_RelinkQWNails(void) { int i; vec_t *v; entity_render_t *entrender; for (i = 0;i < cl.qw_num_nails;i++) { v = cl.qw_nails[i]; // if we're drawing effects, get a new temp entity // (NewTempEntity adds it to the render entities list for us) if (!(entrender = CL_NewTempEntity(0))) continue; // normal stuff entrender->model = CL_GetModelByIndex(cl.qw_modelindex_spike); entrender->alpha = 1; VectorSet(entrender->colormod, 1, 1, 1); VectorSet(entrender->glowmod, 1, 1, 1); Matrix4x4_CreateFromQuakeEntity(&entrender->matrix, v[0], v[1], v[2], v[3], v[4], v[5], 1); CL_UpdateRenderEntity(entrender); } } static void CL_LerpPlayer(float frac) { int i; cl.viewzoom = cl.mviewzoom[1] + frac * (cl.mviewzoom[0] - cl.mviewzoom[1]); for (i = 0;i < 3;i++) { cl.punchangle[i] = cl.mpunchangle[1][i] + frac * (cl.mpunchangle[0][i] - cl.mpunchangle[1][i]); cl.punchvector[i] = cl.mpunchvector[1][i] + frac * (cl.mpunchvector[0][i] - cl.mpunchvector[1][i]); cl.velocity[i] = cl.mvelocity[1][i] + frac * (cl.mvelocity[0][i] - cl.mvelocity[1][i]); } // interpolate the angles if playing a demo or spectating someone if (cls.demoplayback || cl.fixangle[0]) { for (i = 0;i < 3;i++) { float d = cl.mviewangles[0][i] - cl.mviewangles[1][i]; if (d > 180) d -= 360; else if (d < -180) d += 360; cl.viewangles[i] = cl.mviewangles[1][i] + frac * d; } } } void CSQC_RelinkAllEntities (int drawmask) { // link stuff CL_RelinkWorld(); CL_RelinkStaticEntities(); CL_RelinkBeams(); CL_RelinkEffects(); // link stuff if (drawmask & ENTMASK_ENGINE) { CL_RelinkNetworkEntities(); if (drawmask & ENTMASK_ENGINEVIEWMODELS) CL_LinkNetworkEntity(&cl.viewent); // link gun model CL_RelinkQWNails(); } // update view blend V_CalcViewBlend(); } /* =============== CL_UpdateWorld Update client game world for a new frame =============== */ void CL_UpdateWorld(void) { r_refdef.scene.extraupdate = !r_speeds.integer; r_refdef.scene.numentities = 0; r_refdef.scene.numlights = 0; r_refdef.view.matrix = identitymatrix; r_refdef.view.quality = 1; cl.num_brushmodel_entities = 0; if (cls.state == ca_connected && cls.signon == SIGNONS) { // prepare for a new frame CL_LerpPlayer(CL_LerpPoint()); CL_DecayLightFlashes(); CL_ClearTempEntities(); V_DriftPitch(); V_FadeViewFlashs(); // if prediction is enabled we have to update all the collidable // network entities before the prediction code can be run CL_UpdateNetworkCollisionEntities(); // now update the player prediction CL_ClientMovement_Replay(); // update the player entity (which may be predicted) CL_UpdateNetworkEntity(cl.entities + cl.viewentity, 32, true); // now update the view (which depends on that player entity) V_CalcRefdef(); // now update all the network entities and create particle trails // (some entities may depend on the view) CL_UpdateNetworkEntities(); // update the engine-based viewmodel CL_UpdateViewModel(); CL_RelinkLightFlashes(); CSQC_RelinkAllEntities(ENTMASK_ENGINE | ENTMASK_ENGINEVIEWMODELS); // decals, particles, and explosions will be updated during rneder } r_refdef.scene.time = cl.time; } // LordHavoc: pausedemo command static void CL_PauseDemo_f (void) { cls.demopaused = !cls.demopaused; if (cls.demopaused) Con_Print("Demo paused\n"); else Con_Print("Demo unpaused\n"); } /* ====================== CL_Fog_f ====================== */ static void CL_Fog_f (void) { if (Cmd_Argc () == 1) { Con_Printf("\"fog\" is \"%f %f %f %f %f %f %f %f %f\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth); return; } FOG_clear(); // so missing values get good defaults if(Cmd_Argc() > 1) r_refdef.fog_density = atof(Cmd_Argv(1)); if(Cmd_Argc() > 2) r_refdef.fog_red = atof(Cmd_Argv(2)); if(Cmd_Argc() > 3) r_refdef.fog_green = atof(Cmd_Argv(3)); if(Cmd_Argc() > 4) r_refdef.fog_blue = atof(Cmd_Argv(4)); if(Cmd_Argc() > 5) r_refdef.fog_alpha = atof(Cmd_Argv(5)); if(Cmd_Argc() > 6) r_refdef.fog_start = atof(Cmd_Argv(6)); if(Cmd_Argc() > 7) r_refdef.fog_end = atof(Cmd_Argv(7)); if(Cmd_Argc() > 8) r_refdef.fog_height = atof(Cmd_Argv(8)); if(Cmd_Argc() > 9) r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); } /* ====================== CL_FogHeightTexture_f ====================== */ static void CL_Fog_HeightTexture_f (void) { if (Cmd_Argc () < 11) { Con_Printf("\"fog_heighttexture\" is \"%f %f %f %f %f %f %f %f %f %s\"\n", r_refdef.fog_density, r_refdef.fog_red, r_refdef.fog_green, r_refdef.fog_blue, r_refdef.fog_alpha, r_refdef.fog_start, r_refdef.fog_end, r_refdef.fog_height, r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); return; } FOG_clear(); // so missing values get good defaults r_refdef.fog_density = atof(Cmd_Argv(1)); r_refdef.fog_red = atof(Cmd_Argv(2)); r_refdef.fog_green = atof(Cmd_Argv(3)); r_refdef.fog_blue = atof(Cmd_Argv(4)); r_refdef.fog_alpha = atof(Cmd_Argv(5)); r_refdef.fog_start = atof(Cmd_Argv(6)); r_refdef.fog_end = atof(Cmd_Argv(7)); r_refdef.fog_height = atof(Cmd_Argv(8)); r_refdef.fog_fadedepth = atof(Cmd_Argv(9)); strlcpy(r_refdef.fog_height_texturename, Cmd_Argv(10), sizeof(r_refdef.fog_height_texturename)); } /* ==================== CL_TimeRefresh_f For program optimization ==================== */ static void CL_TimeRefresh_f (void) { int i; double timestart, timedelta; r_refdef.scene.extraupdate = false; timestart = Sys_DirtyTime(); for (i = 0;i < 128;i++) { Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, i / 128.0 * 360.0, 0, 1); r_refdef.view.quality = 1; CL_UpdateScreen(); } timedelta = Sys_DirtyTime() - timestart; Con_Printf("%f seconds (%f fps)\n", timedelta, 128/timedelta); } static void CL_AreaStats_f(void) { World_PrintAreaStats(&cl.world, "client"); } cl_locnode_t *CL_Locs_FindNearest(const vec3_t point) { int i; cl_locnode_t *loc; cl_locnode_t *best; vec3_t nearestpoint; vec_t dist, bestdist; best = NULL; bestdist = 0; for (loc = cl.locnodes;loc;loc = loc->next) { for (i = 0;i < 3;i++) nearestpoint[i] = bound(loc->mins[i], point[i], loc->maxs[i]); dist = VectorDistance2(nearestpoint, point); if (bestdist > dist || !best) { bestdist = dist; best = loc; if (bestdist < 1) break; } } return best; } void CL_Locs_FindLocationName(char *buffer, size_t buffersize, vec3_t point) { cl_locnode_t *loc; loc = CL_Locs_FindNearest(point); if (loc) strlcpy(buffer, loc->name, buffersize); else dpsnprintf(buffer, buffersize, "LOC=%.0f:%.0f:%.0f", point[0], point[1], point[2]); } static void CL_Locs_FreeNode(cl_locnode_t *node) { cl_locnode_t **pointer, **next; for (pointer = &cl.locnodes;*pointer;pointer = next) { next = &(*pointer)->next; if (*pointer == node) { *pointer = node->next; Mem_Free(node); return; } } Con_Printf("CL_Locs_FreeNode: no such node! (%p)\n", (void *)node); } static void CL_Locs_AddNode(vec3_t mins, vec3_t maxs, const char *name) { cl_locnode_t *node, **pointer; int namelen; if (!name) name = ""; namelen = (int)strlen(name); node = (cl_locnode_t *) Mem_Alloc(cls.levelmempool, sizeof(cl_locnode_t) + namelen + 1); VectorSet(node->mins, min(mins[0], maxs[0]), min(mins[1], maxs[1]), min(mins[2], maxs[2])); VectorSet(node->maxs, max(mins[0], maxs[0]), max(mins[1], maxs[1]), max(mins[2], maxs[2])); node->name = (char *)(node + 1); memcpy(node->name, name, namelen); node->name[namelen] = 0; // link it into the tail of the list to preserve the order for (pointer = &cl.locnodes;*pointer;pointer = &(*pointer)->next) ; *pointer = node; } static void CL_Locs_Add_f(void) { vec3_t mins, maxs; if (Cmd_Argc() != 5 && Cmd_Argc() != 8) { Con_Printf("usage: %s x y z[ x y z] name\n", Cmd_Argv(0)); return; } mins[0] = atof(Cmd_Argv(1)); mins[1] = atof(Cmd_Argv(2)); mins[2] = atof(Cmd_Argv(3)); if (Cmd_Argc() == 8) { maxs[0] = atof(Cmd_Argv(4)); maxs[1] = atof(Cmd_Argv(5)); maxs[2] = atof(Cmd_Argv(6)); CL_Locs_AddNode(mins, maxs, Cmd_Argv(7)); } else CL_Locs_AddNode(mins, mins, Cmd_Argv(4)); } static void CL_Locs_RemoveNearest_f(void) { cl_locnode_t *loc; loc = CL_Locs_FindNearest(r_refdef.view.origin); if (loc) CL_Locs_FreeNode(loc); else Con_Printf("no loc point or box found for your location\n"); } static void CL_Locs_Clear_f(void) { while (cl.locnodes) CL_Locs_FreeNode(cl.locnodes); } static void CL_Locs_Save_f(void) { cl_locnode_t *loc; qfile_t *outfile; char locfilename[MAX_QPATH]; if (!cl.locnodes) { Con_Printf("No loc points/boxes exist!\n"); return; } if (cls.state != ca_connected || !cl.worldmodel) { Con_Printf("No level loaded!\n"); return; } dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); outfile = FS_OpenRealFile(locfilename, "w", false); if (!outfile) return; // if any boxes are used then this is a proquake-format loc file, which // allows comments, so add some relevant information at the start for (loc = cl.locnodes;loc;loc = loc->next) if (!VectorCompare(loc->mins, loc->maxs)) break; if (loc) { FS_Printf(outfile, "// %s %s saved by %s\n// x,y,z,x,y,z,\"name\"\n\n", locfilename, Sys_TimeString("%Y-%m-%d"), engineversion); for (loc = cl.locnodes;loc;loc = loc->next) if (VectorCompare(loc->mins, loc->maxs)) break; if (loc) Con_Printf("Warning: writing loc file containing a mixture of qizmo-style points and proquake-style boxes may not work in qizmo or proquake!\n"); } for (loc = cl.locnodes;loc;loc = loc->next) { if (VectorCompare(loc->mins, loc->maxs)) { int len; const char *s; const char *in = loc->name; char name[MAX_INPUTLINE]; for (len = 0;len < (int)sizeof(name) - 1 && *in;) { if (*in == ' ') {s = "$loc_name_separator";in++;} else if (!strncmp(in, "SSG", 3)) {s = "$loc_name_ssg";in += 3;} else if (!strncmp(in, "NG", 2)) {s = "$loc_name_ng";in += 2;} else if (!strncmp(in, "SNG", 3)) {s = "$loc_name_sng";in += 3;} else if (!strncmp(in, "GL", 2)) {s = "$loc_name_gl";in += 2;} else if (!strncmp(in, "RL", 2)) {s = "$loc_name_rl";in += 2;} else if (!strncmp(in, "LG", 2)) {s = "$loc_name_lg";in += 2;} else if (!strncmp(in, "GA", 2)) {s = "$loc_name_ga";in += 2;} else if (!strncmp(in, "YA", 2)) {s = "$loc_name_ya";in += 2;} else if (!strncmp(in, "RA", 2)) {s = "$loc_name_ra";in += 2;} else if (!strncmp(in, "MEGA", 4)) {s = "$loc_name_mh";in += 4;} else s = NULL; if (s) { while (len < (int)sizeof(name) - 1 && *s) name[len++] = *s++; continue; } name[len++] = *in++; } name[len] = 0; FS_Printf(outfile, "%.0f %.0f %.0f %s\n", loc->mins[0]*8, loc->mins[1]*8, loc->mins[2]*8, name); } else FS_Printf(outfile, "%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,\"%s\"\n", loc->mins[0], loc->mins[1], loc->mins[2], loc->maxs[0], loc->maxs[1], loc->maxs[2], loc->name); } FS_Close(outfile); } void CL_Locs_Reload_f(void) { int i, linenumber, limit, len; const char *s; char *filedata, *text, *textend, *linestart, *linetext, *lineend; fs_offset_t filesize; vec3_t mins, maxs; char locfilename[MAX_QPATH]; char name[MAX_INPUTLINE]; if (cls.state != ca_connected || !cl.worldmodel) { Con_Printf("No level loaded!\n"); return; } CL_Locs_Clear_f(); // try maps/something.loc first (LordHavoc: where I think they should be) dpsnprintf(locfilename, sizeof(locfilename), "%s.loc", cl.worldnamenoextension); filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); if (!filedata) { // try proquake name as well (LordHavoc: I hate path mangling) dpsnprintf(locfilename, sizeof(locfilename), "locs/%s.loc", cl.worldbasename); filedata = (char *)FS_LoadFile(locfilename, cls.levelmempool, false, &filesize); if (!filedata) return; } text = filedata; textend = filedata + filesize; for (linenumber = 1;text < textend;linenumber++) { linestart = text; for (;text < textend && *text != '\r' && *text != '\n';text++) ; lineend = text; if (text + 1 < textend && *text == '\r' && text[1] == '\n') text++; if (text < textend) text++; // trim trailing whitespace while (lineend > linestart && ISWHITESPACE(lineend[-1])) lineend--; // trim leading whitespace while (linestart < lineend && ISWHITESPACE(*linestart)) linestart++; // check if this is a comment if (linestart + 2 <= lineend && !strncmp(linestart, "//", 2)) continue; linetext = linestart; limit = 3; for (i = 0;i < limit;i++) { if (linetext >= lineend) break; // note: a missing number is interpreted as 0 if (i < 3) mins[i] = atof(linetext); else maxs[i - 3] = atof(linetext); // now advance past the number while (linetext < lineend && !ISWHITESPACE(*linetext) && *linetext != ',') linetext++; // advance through whitespace if (linetext < lineend) { if (*linetext == ',') { linetext++; limit = 6; // note: comma can be followed by whitespace } if (ISWHITESPACE(*linetext)) { // skip whitespace while (linetext < lineend && ISWHITESPACE(*linetext)) linetext++; } } } // if this is a quoted name, remove the quotes if (i == 6) { if (linetext >= lineend || *linetext != '"') continue; // proquake location names are always quoted lineend--; linetext++; len = min(lineend - linetext, (int)sizeof(name) - 1); memcpy(name, linetext, len); name[len] = 0; // add the box to the list CL_Locs_AddNode(mins, maxs, name); } // if a point was parsed, it needs to be scaled down by 8 (since // point-based loc files were invented by a proxy which dealt // directly with quake protocol coordinates, which are *8), turn // it into a box else if (i == 3) { // interpret silly fuhquake macros for (len = 0;len < (int)sizeof(name) - 1 && linetext < lineend;) { if (*linetext == '$') { if (linetext + 18 <= lineend && !strncmp(linetext, "$loc_name_separator", 19)) {s = " ";linetext += 19;} else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_ssg", 13)) {s = "SSG";linetext += 13;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ng", 12)) {s = "NG";linetext += 12;} else if (linetext + 13 <= lineend && !strncmp(linetext, "$loc_name_sng", 13)) {s = "SNG";linetext += 13;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_gl", 12)) {s = "GL";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_rl", 12)) {s = "RL";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_lg", 12)) {s = "LG";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ga", 12)) {s = "GA";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ya", 12)) {s = "YA";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_ra", 12)) {s = "RA";linetext += 12;} else if (linetext + 12 <= lineend && !strncmp(linetext, "$loc_name_mh", 12)) {s = "MEGA";linetext += 12;} else s = NULL; if (s) { while (len < (int)sizeof(name) - 1 && *s) name[len++] = *s++; continue; } } name[len++] = *linetext++; } name[len] = 0; // add the point to the list VectorScale(mins, (1.0 / 8.0), mins); CL_Locs_AddNode(mins, mins, name); } else continue; } } /* =========== CL_Shutdown =========== */ void CL_Shutdown (void) { CL_Screen_Shutdown(); CL_Particles_Shutdown(); CL_Parse_Shutdown(); Mem_FreePool (&cls.permanentmempool); Mem_FreePool (&cls.levelmempool); } /* ================= CL_Init ================= */ void CL_Init (void) { cls.levelmempool = Mem_AllocPool("client (per-level memory)", 0, NULL); cls.permanentmempool = Mem_AllocPool("client (long term memory)", 0, NULL); memset(&r_refdef, 0, sizeof(r_refdef)); // max entities sent to renderer per frame r_refdef.scene.maxentities = MAX_EDICTS + 256 + 512; r_refdef.scene.entities = (entity_render_t **)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t *) * r_refdef.scene.maxentities); // max temp entities r_refdef.scene.maxtempentities = MAX_TEMPENTITIES; r_refdef.scene.tempentities = (entity_render_t *)Mem_Alloc(cls.permanentmempool, sizeof(entity_render_t) * r_refdef.scene.maxtempentities); CL_InitInput (); // // register our commands // Cvar_RegisterVariable (&cl_upspeed); Cvar_RegisterVariable (&cl_forwardspeed); Cvar_RegisterVariable (&cl_backspeed); Cvar_RegisterVariable (&cl_sidespeed); Cvar_RegisterVariable (&cl_movespeedkey); Cvar_RegisterVariable (&cl_yawspeed); Cvar_RegisterVariable (&cl_pitchspeed); Cvar_RegisterVariable (&cl_anglespeedkey); Cvar_RegisterVariable (&cl_shownet); Cvar_RegisterVariable (&cl_nolerp); Cvar_RegisterVariable (&cl_lerpexcess); Cvar_RegisterVariable (&cl_lerpanim_maxdelta_server); Cvar_RegisterVariable (&cl_lerpanim_maxdelta_framegroups); Cvar_RegisterVariable (&cl_deathfade); Cvar_RegisterVariable (&lookspring); Cvar_RegisterVariable (&lookstrafe); Cvar_RegisterVariable (&sensitivity); Cvar_RegisterVariable (&freelook); Cvar_RegisterVariable (&m_pitch); Cvar_RegisterVariable (&m_yaw); Cvar_RegisterVariable (&m_forward); Cvar_RegisterVariable (&m_side); Cvar_RegisterVariable (&cl_itembobspeed); Cvar_RegisterVariable (&cl_itembobheight); Cmd_AddCommand ("entities", CL_PrintEntities_f, "print information on network entities known to client"); Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server (or disconnect all clients if running a server)"); Cmd_AddCommand ("record", CL_Record_f, "record a demo"); Cmd_AddCommand ("stop", CL_Stop_f, "stop recording or playing a demo"); Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "watch a demo file"); Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "play back a demo as fast as possible and save statistics to benchmark.log"); // Support Client-side Model Index List Cmd_AddCommand ("cl_modelindexlist", CL_ModelIndexList_f, "list information on all models in the client modelindex"); // Support Client-side Sound Index List Cmd_AddCommand ("cl_soundindexlist", CL_SoundIndexList_f, "list all sounds in the client soundindex"); Cvar_RegisterVariable (&cl_autodemo); Cvar_RegisterVariable (&cl_autodemo_nameformat); Cvar_RegisterVariable (&cl_autodemo_delete); Cmd_AddCommand ("fog", CL_Fog_f, "set global fog parameters (density red green blue [alpha [mindist [maxdist [top [fadedepth]]]]])"); Cmd_AddCommand ("fog_heighttexture", CL_Fog_HeightTexture_f, "set global fog parameters (density red green blue alpha mindist maxdist top depth textures/mapname/fogheight.tga)"); // LordHavoc: added pausedemo Cmd_AddCommand ("pausedemo", CL_PauseDemo_f, "pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies)"); Cmd_AddCommand ("cl_areastats", CL_AreaStats_f, "prints statistics on entity culling during collision traces"); Cvar_RegisterVariable(&r_draweffects); Cvar_RegisterVariable(&cl_explosions_alpha_start); Cvar_RegisterVariable(&cl_explosions_alpha_end); Cvar_RegisterVariable(&cl_explosions_size_start); Cvar_RegisterVariable(&cl_explosions_size_end); Cvar_RegisterVariable(&cl_explosions_lifetime); Cvar_RegisterVariable(&cl_stainmaps); Cvar_RegisterVariable(&cl_stainmaps_clearonload); Cvar_RegisterVariable(&cl_beams_polygons); Cvar_RegisterVariable(&cl_beams_quakepositionhack); Cvar_RegisterVariable(&cl_beams_instantaimhack); Cvar_RegisterVariable(&cl_beams_lightatend); Cvar_RegisterVariable(&cl_noplayershadow); Cvar_RegisterVariable(&cl_dlights_decayradius); Cvar_RegisterVariable(&cl_dlights_decaybrightness); Cvar_RegisterVariable(&cl_prydoncursor); Cvar_RegisterVariable(&cl_prydoncursor_notrace); Cvar_RegisterVariable(&cl_deathnoviewmodel); // for QW connections Cvar_RegisterVariable(&qport); Cvar_SetValueQuick(&qport, (rand() * RAND_MAX + rand()) & 0xffff); Cmd_AddCommand("timerefresh", CL_TimeRefresh_f, "turn quickly and print rendering statistcs"); Cvar_RegisterVariable(&cl_locs_enable); Cvar_RegisterVariable(&cl_locs_show); Cmd_AddCommand("locs_add", CL_Locs_Add_f, "add a point or box location (usage: x y z[ x y z] \"name\", if two sets of xyz are supplied it is a box, otherwise point)"); Cmd_AddCommand("locs_removenearest", CL_Locs_RemoveNearest_f, "remove the nearest point or box (note: you need to be very near a box to remove it)"); Cmd_AddCommand("locs_clear", CL_Locs_Clear_f, "remove all loc points/boxes"); Cmd_AddCommand("locs_reload", CL_Locs_Reload_f, "reload .loc file for this map"); Cmd_AddCommand("locs_save", CL_Locs_Save_f, "save .loc file for this map containing currently defined points and boxes"); CL_Parse_Init(); CL_Particles_Init(); CL_Screen_Init(); CL_Video_Init(); } darkplaces/progsvm.h0000664000175000017500000011165313067716222014042 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* This is a try to make the vm more generic, it is mainly based on the progs.h file. For the license refer to progs.h. Generic means, less as possible hard-coded links with the other parts of the engine. This means no edict_engineprivate struct usage, etc. The code uses void pointers instead. */ #ifndef PROGSVM_H #define PROGSVM_H #include "pr_comp.h" // defs shared with qcc #include "progdefs.h" // generated by program cdefs #include "clprogdefs.h" // generated by program cdefs #ifndef DP_SMALLMEMORY #define PROFILING #endif typedef struct prvm_stack_s { int s; mfunction_t *f; double tprofile_acc; double profile_acc; double builtinsprofile_acc; } prvm_stack_t; typedef union prvm_eval_s { prvm_int_t string; prvm_vec_t _float; prvm_vec_t vector[3]; prvm_int_t function; prvm_int_t ivector[3]; prvm_int_t _int; prvm_int_t edict; } prvm_eval_t; typedef struct prvm_required_field_s { int type; const char *name; } prvm_required_field_t; // AK: I dont call it engine private cause it doesnt really belongs to the engine // it belongs to prvm. typedef struct prvm_edict_private_s { qboolean free; float freetime; // realtime of last change to "free" (i.e. also set on allocation) int mark; // used during leaktest (0 = unref, >0 = referenced); special values during server physics: #define PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN -1 #define PRVM_EDICT_MARK_SETORIGIN_CAUGHT -2 const char *allocation_origin; } prvm_edict_private_t; typedef struct prvm_edict_s { // engine-private fields (stored in dynamically resized array) //edict_engineprivate_t *e; union { prvm_edict_private_t *required; prvm_vec_t *fp; prvm_int_t *ip; // FIXME: this server pointer really means world, not server // (it is used by both server qc and client qc, but not menu qc) edict_engineprivate_t *server; // add other private structs as you desire // new structs have to start with the elements of prvm_edit_private_t // e.g. a new struct has to either look like this: // typedef struct server_edict_private_s { // prvm_edict_private_t base; // vec3_t moved_from; // vec3_t moved_fromangles; // ... } server_edict_private_t; // or: // typedef struct server_edict_private_s { // qboolean free; // float freetime; // vec3_t moved_from; // vec3_t moved_fromangles; // ... } server_edict_private_t; // However, the first one should be preferred. } priv; // QuakeC fields (stored in dynamically resized array) union { prvm_vec_t *fp; prvm_int_t *ip; // entvars_t *server; // cl_entvars_t *client; } fields; } prvm_edict_t; #define VMPOLYGONS_MAXPOINTS 64 typedef struct vmpolygons_triangle_s { rtexture_t *texture; int drawflag; qboolean hasalpha; unsigned short elements[3]; } vmpolygons_triangle_t; typedef struct vmpolygons_s { mempool_t *pool; qboolean initialized; int max_vertices; int num_vertices; float *data_vertex3f; float *data_color4f; float *data_texcoord2f; int max_triangles; int num_triangles; vmpolygons_triangle_t *data_triangles; unsigned short *data_sortedelement3s; qboolean begin_active; int begin_draw2d; rtexture_t *begin_texture; int begin_drawflag; int begin_vertices; float begin_vertex[VMPOLYGONS_MAXPOINTS][3]; float begin_color[VMPOLYGONS_MAXPOINTS][4]; float begin_texcoord[VMPOLYGONS_MAXPOINTS][2]; qboolean begin_texture_hasalpha; } vmpolygons_t; extern prvm_eval_t prvm_badvalue; #define PRVM_alledictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_alledictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_alledictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_alledictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_alledictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_allglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_allglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_allglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_allglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_allglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_allfunction(funcname) (prog->funcoffsets.funcname) #define PRVM_drawedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_drawedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_drawedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_drawedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_drawedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_drawglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_drawglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_drawglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_drawglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_drawglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_drawfunction(funcname) (prog->funcoffsets.funcname) #define PRVM_gameedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_gameedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_gameedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_gameedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_gameedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_gameglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_gameglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_gameglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_gameglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_gameglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_gamefunction(funcname) (prog->funcoffsets.funcname) #define PRVM_serveredictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_serveredictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_serveredictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_serveredictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_serveredictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_serverglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_serverglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_serverglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_serverglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_serverglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_serverfunction(funcname) (prog->funcoffsets.funcname) #define PRVM_clientedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_clientedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_clientedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_clientedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_clientedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_clientglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_clientglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_clientglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_clientglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_clientglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_clientfunction(funcname) (prog->funcoffsets.funcname) #define PRVM_menuedictfloat(ed, fieldname) (PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.fieldname)) #define PRVM_menuedictvector(ed, fieldname) (PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.fieldname)) #define PRVM_menuedictstring(ed, fieldname) (PRVM_EDICTFIELDSTRING(ed, prog->fieldoffsets.fieldname)) #define PRVM_menuedictedict(ed, fieldname) (PRVM_EDICTFIELDEDICT(ed, prog->fieldoffsets.fieldname)) #define PRVM_menuedictfunction(ed, fieldname) (PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.fieldname)) #define PRVM_menuglobalfloat(fieldname) (PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.fieldname)) #define PRVM_menuglobalvector(fieldname) (PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.fieldname)) #define PRVM_menuglobalstring(fieldname) (PRVM_GLOBALFIELDSTRING(prog->globaloffsets.fieldname)) #define PRVM_menuglobaledict(fieldname) (PRVM_GLOBALFIELDEDICT(prog->globaloffsets.fieldname)) #define PRVM_menuglobalfunction(fieldname) (PRVM_GLOBALFIELDFUNCTION(prog->globaloffsets.fieldname)) #define PRVM_menufunction(funcname) (prog->funcoffsets.funcname) #if 1 #define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)((ed)->fields.fp + (fieldoffset))) #define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->_float) #define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->vector) #define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->string) #define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->edict) #define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (PRVM_EDICTFIELDVALUE(ed, fieldoffset)->function) #define PRVM_GLOBALFIELDVALUE(fieldoffset) ((fieldoffset) < 0 ? Con_Printf("Invalid fieldoffset at %s:%i\n", __FILE__, __LINE__), &prvm_badvalue : (prvm_eval_t *)(prog->globals.fp + (fieldoffset))) #define PRVM_GLOBALFIELDFLOAT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->_float) #define PRVM_GLOBALFIELDVECTOR(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->vector) #define PRVM_GLOBALFIELDSTRING(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->string) #define PRVM_GLOBALFIELDEDICT(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->edict) #define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (PRVM_GLOBALFIELDVALUE(fieldoffset)->function) #else #define PRVM_EDICTFIELDVALUE(ed, fieldoffset) ((prvm_eval_t *)(ed->fields.fp + fieldoffset)) #define PRVM_EDICTFIELDFLOAT(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->_float) #define PRVM_EDICTFIELDVECTOR(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->vector) #define PRVM_EDICTFIELDSTRING(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->string) #define PRVM_EDICTFIELDEDICT(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->edict) #define PRVM_EDICTFIELDFUNCTION(ed, fieldoffset) (((prvm_eval_t *)(ed->fields.fp + fieldoffset))->function) #define PRVM_GLOBALFIELDVALUE(fieldoffset) ((prvm_eval_t *)(prog->globals.fp + fieldoffset)) #define PRVM_GLOBALFIELDFLOAT(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->_float) #define PRVM_GLOBALFIELDVECTOR(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->vector) #define PRVM_GLOBALFIELDSTRING(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->string) #define PRVM_GLOBALFIELDEDICT(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->edict) #define PRVM_GLOBALFIELDFUNCTION(fieldoffset) (((prvm_eval_t *)(prog->globals.fp + fieldoffset))->function) #endif //============================================================================ #define PRVM_OP_STATE 1 #ifdef DP_SMALLMEMORY #define PRVM_MAX_STACK_DEPTH 128 #define PRVM_LOCALSTACK_SIZE 2048 #define PRVM_MAX_OPENFILES 16 #define PRVM_MAX_OPENSEARCHES 8 #else #define PRVM_MAX_STACK_DEPTH 1024 #define PRVM_LOCALSTACK_SIZE 16384 #define PRVM_MAX_OPENFILES 256 #define PRVM_MAX_OPENSEARCHES 128 #endif struct prvm_prog_s; typedef void (*prvm_builtin_t) (struct prvm_prog_s *prog); // NOTE: field offsets use -1 for NULL typedef struct prvm_prog_fieldoffsets_s { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) int x; #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function } prvm_prog_fieldoffsets_t; // NOTE: global offsets use -1 for NULL typedef struct prvm_prog_globaloffsets_s { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) int x; #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function } prvm_prog_globaloffsets_t; // NOTE: function offsets use 0 for NULL typedef struct prvm_prog_funcoffsets_s { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) int x; #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function } prvm_prog_funcoffsets_t; // stringbuffer flags #define STRINGBUFFER_SAVED 1 // saved in savegames #define STRINGBUFFER_QCFLAGS 1 // allowed to be set by QC #define STRINGBUFFER_TEMP 128 // internal use ONLY typedef struct prvm_stringbuffer_s { int max_strings; int num_strings; char **strings; const char *origin; unsigned char flags; } prvm_stringbuffer_t; // [INIT] variables flagged with this token can be initialized by 'you' // NOTE: external code has to create and free the mempools but everything else is done by prvm ! typedef struct prvm_prog_s { double starttime; // system time when PRVM_Prog_Load was called double inittime; // system time when QC initialization code finished (any entity created before is not a leak) double profiletime; // system time when last PRVM_CallProfile was called (or PRVM_Prog_Load initially) mfunction_t *functions; int functions_covered; char *strings; int stringssize; ddef_t *fielddefs; ddef_t *globaldefs; mstatement_t *statements; int entityfields; // number of vec_t fields in progs (some variables are 3) int entityfieldsarea; // LordHavoc: equal to max_edicts * entityfields (for bounds checking) // loaded values from the disk format int progs_version; int progs_crc; int progs_numstatements; int progs_numglobaldefs; int progs_numfielddefs; int progs_numfunctions; int progs_numstrings; int progs_numglobals; int progs_entityfields; // real values in memory (some modified by loader) int numstatements; int numglobaldefs; int numfielddefs; int numfunctions; int numstrings; int numglobals; int *statement_linenums; // NULL if not available int *statement_columnnums; // NULL if not available double *statement_profile; // only incremented if prvm_statementprofiling is on int statements_covered; double *explicit_profile; // only incremented if prvm_statementprofiling is on int explicit_covered; int numexplicitcoveragestatements; union { prvm_vec_t *fp; prvm_int_t *ip; // globalvars_t *server; // cl_globalvars_t *client; } globals; int maxknownstrings; int numknownstrings; // this is updated whenever a string is removed or added // (simple optimization of the free string search) int firstfreeknownstring; const char **knownstrings; unsigned char *knownstrings_freeable; const char **knownstrings_origin; const char ***stringshash; memexpandablearray_t stringbuffersarray; // all memory allocations related to this vm_prog (code, edicts, strings) mempool_t *progs_mempool; // [INIT] prvm_builtin_t *builtins; // [INIT] int numbuiltins; // [INIT] int argc; int trace; int break_statement; int break_stack_index; int watch_global; etype_t watch_global_type; prvm_eval_t watch_global_value; int watch_edict; int watch_field; etype_t watch_field_type; prvm_eval_t watch_edictfield_value; mfunction_t *xfunction; int xstatement; // stacktrace writes into stack[MAX_STACK_DEPTH] // thus increase the array, so depth wont be overwritten prvm_stack_t stack[PRVM_MAX_STACK_DEPTH+1]; int depth; prvm_int_t localstack[PRVM_LOCALSTACK_SIZE]; int localstack_used; unsigned short filecrc; //============================================================================ // until this point everything also exists (with the pr_ prefix) in the old vm qfile_t *openfiles[PRVM_MAX_OPENFILES]; const char * openfiles_origin[PRVM_MAX_OPENFILES]; fssearch_t *opensearches[PRVM_MAX_OPENSEARCHES]; const char * opensearches_origin[PRVM_MAX_OPENSEARCHES]; skeleton_t *skeletons[MAX_EDICTS]; // buffer for storing all tempstrings created during one invocation of ExecuteProgram sizebuf_t tempstringsbuf; // LordHavoc: moved this here to clean up things that relied on prvm_prog_list too much // FIXME: make VM_CL_R_Polygon functions use Debug_Polygon functions? vmpolygons_t vmpolygons; // copies of some vars that were former read from sv int num_edicts; // number of edicts for which space has been (should be) allocated int max_edicts; // [INIT] // used instead of the constant MAX_EDICTS int limit_edicts; // [INIT] // number of reserved edicts (allocated from 1) int reserved_edicts; // [INIT] prvm_edict_t *edicts; prvm_vec_t *edictsfields; void *edictprivate; // size of the engine private struct int edictprivate_size; // [INIT] prvm_prog_fieldoffsets_t fieldoffsets; prvm_prog_globaloffsets_t globaloffsets; prvm_prog_funcoffsets_t funcoffsets; // allow writing to world entity fields, this is set by server init and // cleared before first server frame qboolean allowworldwrites; // name of the prog, e.g. "Server", "Client" or "Menu" (used for text output) const char *name; // [INIT] // flag - used to store general flags like PRVM_GE_SELF, etc. int flag; const char *extensionstring; // [INIT] qboolean loadintoworld; // [INIT] // used to indicate whether a prog is loaded qboolean loaded; qboolean leaktest_active; // translation buffer (only needs to be freed on unloading progs, type is private to prvm_edict.c) void *po; // printed together with backtraces const char *statestring; struct animatemodel_cache *animatemodel_cache; // prvm_builtin_mem_t *mem_list; // now passed as parameter of PRVM_LoadProgs // char **required_func; // int numrequiredfunc; //============================================================================ ddef_t *self; // if self != 0 then there is a global self //============================================================================ // function pointers void (*begin_increase_edicts)(struct prvm_prog_s *prog); // [INIT] used by PRVM_MEM_Increase_Edicts void (*end_increase_edicts)(struct prvm_prog_s *prog); // [INIT] void (*init_edict)(struct prvm_prog_s *prog, prvm_edict_t *edict); // [INIT] used by PRVM_ED_ClearEdict void (*free_edict)(struct prvm_prog_s *prog, prvm_edict_t *ed); // [INIT] used by PRVM_ED_Free void (*count_edicts)(struct prvm_prog_s *prog); // [INIT] used by PRVM_ED_Count_f qboolean (*load_edict)(struct prvm_prog_s *prog, prvm_edict_t *ent); // [INIT] used by PRVM_ED_LoadFromFile void (*init_cmd)(struct prvm_prog_s *prog); // [INIT] used by PRVM_InitProg void (*reset_cmd)(struct prvm_prog_s *prog); // [INIT] used by PRVM_ResetProg void (*error_cmd)(const char *format, ...) DP_FUNC_PRINTF(1); // [INIT] void (*ExecuteProgram)(struct prvm_prog_s *prog, func_t fnum, const char *errormessage); // pointer to one of the *VM_ExecuteProgram functions } prvm_prog_t; typedef enum prvm_progindex_e { PRVM_PROG_SERVER, PRVM_PROG_CLIENT, PRVM_PROG_MENU, PRVM_PROG_MAX } prvm_progindex_t; extern prvm_prog_t prvm_prog_list[PRVM_PROG_MAX]; prvm_prog_t *PRVM_ProgFromString(const char *str); prvm_prog_t *PRVM_FriendlyProgFromString(const char *str); // for console commands (prints error if name unknown and returns NULL, prints error if prog not loaded and returns NULL) #define PRVM_GetProg(n) (&prvm_prog_list[(n)]) #define PRVM_ProgLoaded(n) (PRVM_GetProg(n)->loaded) #define SVVM_prog (&prvm_prog_list[PRVM_PROG_SERVER]) #define CLVM_prog (&prvm_prog_list[PRVM_PROG_CLIENT]) #ifdef CONFIG_MENU #define MVM_prog (&prvm_prog_list[PRVM_PROG_MENU]) #endif //============================================================================ // prvm_cmds part extern prvm_builtin_t vm_sv_builtins[]; extern prvm_builtin_t vm_cl_builtins[]; extern prvm_builtin_t vm_m_builtins[]; extern const int vm_sv_numbuiltins; extern const int vm_cl_numbuiltins; extern const int vm_m_numbuiltins; extern const char * vm_sv_extensions; // client also uses this extern const char * vm_m_extensions; void SVVM_init_cmd(prvm_prog_t *prog); void SVVM_reset_cmd(prvm_prog_t *prog); void CLVM_init_cmd(prvm_prog_t *prog); void CLVM_reset_cmd(prvm_prog_t *prog); #ifdef CONFIG_MENU void MVM_init_cmd(prvm_prog_t *prog); void MVM_reset_cmd(prvm_prog_t *prog); #endif void VM_Cmd_Init(prvm_prog_t *prog); void VM_Cmd_Reset(prvm_prog_t *prog); //============================================================================ void PRVM_Init (void); #ifdef PROFILING void SVVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); #ifdef CONFIG_MENU void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); #endif #else #define SVVM_ExecuteProgram PRVM_ExecuteProgram #define CLVM_ExecuteProgram PRVM_ExecuteProgram #ifdef CONFIG_MENU #define MVM_ExecuteProgram PRVM_ExecuteProgram #endif void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage); #endif #define PRVM_Alloc(buffersize) Mem_Alloc(prog->progs_mempool, buffersize) #define PRVM_Free(buffer) Mem_Free(buffer) void PRVM_Profile (prvm_prog_t *prog, int maxfunctions, double mintime, int sortby); void PRVM_Profile_f (void); void PRVM_ChildProfile_f (void); void PRVM_CallProfile_f (void); void PRVM_PrintFunction_f (void); void PRVM_PrintState(prvm_prog_t *prog, int stack_index); void PRVM_Crash(prvm_prog_t *prog); void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize); const char *PRVM_AllocationOrigin(prvm_prog_t *prog); ddef_t *PRVM_ED_FindField(prvm_prog_t *prog, const char *name); ddef_t *PRVM_ED_FindGlobal(prvm_prog_t *prog, const char *name); mfunction_t *PRVM_ED_FindFunction(prvm_prog_t *prog, const char *name); int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *name); int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *name); func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *name); #define PRVM_ED_FindFieldOffset_FromStruct(st, field) prog->fieldoffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) #define PRVM_ED_FindGlobalOffset_FromStruct(st, field) prog->globaloffsets . field = ((int *)(&((st *)NULL)-> field ) - ((int *)NULL)) void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog); qboolean PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e); prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog); void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed); void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e); void PRVM_PrintFunctionStatements(prvm_prog_t *prog, const char *name); void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname); void PRVM_ED_Write(prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed); const char *PRVM_ED_ParseEdict(prvm_prog_t *prog, const char *data, prvm_edict_t *ent); void PRVM_ED_WriteGlobals(prvm_prog_t *prog, qfile_t *f); void PRVM_ED_ParseGlobals(prvm_prog_t *prog, const char *data); void PRVM_ED_LoadFromFile(prvm_prog_t *prog, const char *data); unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline); #define PRVM_EDICT(n) (((unsigned)(n) < (unsigned int)prog->max_edicts) ? (unsigned int)(n) : PRVM_EDICT_NUM_ERROR(prog, (unsigned int)(n), __FILE__, __LINE__)) #define PRVM_EDICT_NUM(n) (prog->edicts + PRVM_EDICT(n)) //int NUM_FOR_EDICT_ERROR(prvm_edict_t *e); #define PRVM_NUM_FOR_EDICT(e) ((int)((prvm_edict_t *)(e) - prog->edicts)) //int PRVM_NUM_FOR_EDICT(prvm_edict_t *e); #define PRVM_NEXT_EDICT(e) ((e) + 1) #define PRVM_EDICT_TO_PROG(e) (PRVM_NUM_FOR_EDICT(e)) //int PRVM_EDICT_TO_PROG(prvm_edict_t *e); #define PRVM_PROG_TO_EDICT(n) (PRVM_EDICT_NUM(n)) //prvm_edict_t *PRVM_PROG_TO_EDICT(int n); //============================================================================ #define PRVM_G_FLOAT(o) (prog->globals.fp[o]) #define PRVM_G_INT(o) (prog->globals.ip[o]) #define PRVM_G_EDICT(o) (PRVM_PROG_TO_EDICT(prog->globals.ip[o])) #define PRVM_G_EDICTNUM(o) PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(o)) #define PRVM_G_VECTOR(o) (&prog->globals.fp[o]) #define PRVM_G_STRING(o) (PRVM_GetString(prog, prog->globals.ip[o])) //#define PRVM_G_FUNCTION(prog, o) (prog->globals.ip[o]) // FIXME: make these go away? #define PRVM_E_FLOAT(e,o) (e->fields.fp[o]) #define PRVM_E_INT(e,o) (e->fields.ip[o]) //#define PRVM_E_VECTOR(e,o) (&(e->fields.fp[o])) #define PRVM_E_STRING(e,o) (PRVM_GetString(prog, e->fields.ip[o])) extern int prvm_type_size[8]; // for consistency : I think a goal of this sub-project is to // make the new vm mostly independent from the old one, thus if it's necessary, I copy everything void PRVM_Init_Exec(prvm_prog_t *prog); void PRVM_ED_PrintEdicts_f (void); void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname); const char *PRVM_GetString(prvm_prog_t *prog, int num); int PRVM_SetEngineString(prvm_prog_t *prog, const char *s); const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s); int PRVM_SetTempString(prvm_prog_t *prog, const char *s); int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer); void PRVM_FreeString(prvm_prog_t *prog, int num); ddef_t *PRVM_ED_FieldAtOfs(prvm_prog_t *prog, int ofs); qboolean PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash); char *PRVM_UglyValueString(prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength); char *PRVM_GlobalString(prvm_prog_t *prog, int ofs, char *line, size_t linelength); char *PRVM_GlobalStringNoContents(prvm_prog_t *prog, int ofs, char *line, size_t linelength); //============================================================================ /* Initializing a vm: Call InitProg with the num Set up the fields marked with [INIT] in the prog struct Load a program with LoadProgs */ // Load expects to be called right after Reset void PRVM_Prog_Init(prvm_prog_t *prog); void PRVM_Prog_Load(prvm_prog_t *prog, const char *filename, unsigned char *data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global); void PRVM_Prog_Reset(prvm_prog_t *prog); void PRVM_StackTrace(prvm_prog_t *prog); void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text); void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n); void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) DP_FUNC_PRINTF(2); void VM_GenerateFrameGroupBlend(prvm_prog_t *prog, framegroupblend_t *framegroupblend, const prvm_edict_t *ed); void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model, double curtime); void VM_UpdateEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend); void VM_RemoveEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed); void PRVM_ExplicitCoverageEvent(prvm_prog_t *prog, mfunction_t *func, int statement); #endif darkplaces/glquake.h0000664000175000017500000017763313067716220014006 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef GLQUAKE_H #define GLQUAKE_H #ifdef USE_GLES2 #ifdef __IPHONEOS__ #include #else #include #endif // used in R_SetupShader_Generic calls, not actually passed to GL #ifndef GL_MODULATE #define GL_MODULATE 0x2100 #define GL_DECAL 0x2101 #define GL_ADD 0x0104 #endif #endif // disable data conversion warnings #ifdef _MSC_VER #pragma warning(disable : 4310) // LordHavoc: MSVC++ 2008 x86: cast truncates constant value #pragma warning(disable : 4245) // LordHavoc: MSVC++ 2008 x86: 'initializing' : conversion from 'int' to 'unsigned char', signed/unsigned mismatch #pragma warning(disable : 4204) // LordHavoc: MSVC++ 2008 x86: nonstandard extension used : non-constant aggregate initializer //#pragma warning(disable : 4267) // LordHavoc: MSVC++ 2008 x64, conversion from 'size_t' to 'int', possible loss of data //#pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float //#pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float //#pragma warning(disable : 4706) // LordHavoc: MSVC++ 2008 x86, assignment within conditional expression //#pragma warning(disable : 4127) // LordHavoc: MSVC++ 2008 x86, conditional expression is constant //#pragma warning(disable : 4100) // LordHavoc: MSVC++ 2008 x86, unreferenced formal parameter //#pragma warning(disable : 4055) // LordHavoc: MSVC++ 2008 x86, 'type cast' from data pointer to function pointer //#pragma warning(disable : 4054) // LordHavoc: MSVC++ 2008 x86, 'type cast' from function pointer to data pointer #endif //==================================================== #ifndef USE_GLES2 // wgl uses APIENTRY #ifndef APIENTRY #define APIENTRY #endif // for platforms (wgl) that do not use GLAPIENTRY #ifndef GLAPIENTRY #define GLAPIENTRY APIENTRY #endif #ifndef GL_PROJECTION #include typedef unsigned int GLenum; typedef unsigned char GLboolean; typedef unsigned int GLbitfield; typedef void GLvoid; // 1-byte signed typedef signed char GLbyte; // 2-byte signed typedef short GLshort; // 4-byte signed typedef int GLint; // 1-byte unsigned typedef unsigned char GLubyte; // 2-byte unsigned typedef unsigned short GLushort; // 4-byte unsigned typedef unsigned int GLuint; // 4-byte signed typedef int GLsizei; // single precision float typedef float GLfloat; // single precision float in [0,1] typedef float GLclampf; // double precision float typedef double GLdouble; // double precision float in [0,1] typedef double GLclampd; // int whose size is the same as a pointer (?) typedef ptrdiff_t GLintptrARB; // int whose size is the same as a pointer (?) typedef ptrdiff_t GLsizeiptrARB; #define GL_STEREO 0x0C33 #define GL_MODELVIEW 0x1700 #define GL_PROJECTION 0x1701 #define GL_TEXTURE 0x1702 #define GL_MATRIX_MODE 0x0BA0 #define GL_MODELVIEW_MATRIX 0x0BA6 #define GL_PROJECTION_MATRIX 0x0BA7 #define GL_TEXTURE_MATRIX 0x0BA8 #define GL_DONT_CARE 0x1100 #define GL_FASTEST 0x1101 #define GL_NICEST 0x1102 #define GL_DEPTH_TEST 0x0B71 #define GL_CULL_FACE 0x0B44 #define GL_BLEND 0x0BE2 #define GL_ALPHA_TEST 0x0BC0 #define GL_ZERO 0x0 #define GL_ONE 0x1 #define GL_SRC_COLOR 0x0300 #define GL_ONE_MINUS_SRC_COLOR 0x0301 #define GL_DST_COLOR 0x0306 #define GL_ONE_MINUS_DST_COLOR 0x0307 #define GL_SRC_ALPHA 0x0302 #define GL_ONE_MINUS_SRC_ALPHA 0x0303 #define GL_DST_ALPHA 0x0304 #define GL_ONE_MINUS_DST_ALPHA 0x0305 #define GL_SRC_ALPHA_SATURATE 0x0308 #define GL_CONSTANT_COLOR 0x8001 #define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 #define GL_CONSTANT_ALPHA 0x8003 #define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 #define GL_TEXTURE_ENV 0x2300 #define GL_TEXTURE_ENV_MODE 0x2200 #define GL_TEXTURE_1D 0x0DE0 #define GL_TEXTURE_2D 0x0DE1 #define GL_TEXTURE_WRAP_S 0x2802 #define GL_TEXTURE_WRAP_T 0x2803 #define GL_TEXTURE_WRAP_R 0x8072 #define GL_TEXTURE_BORDER_COLOR 0x1004 #define GL_TEXTURE_MAG_FILTER 0x2800 #define GL_TEXTURE_MIN_FILTER 0x2801 #define GL_PACK_ALIGNMENT 0x0D05 #define GL_UNPACK_ALIGNMENT 0x0CF5 #define GL_TEXTURE_BINDING_1D 0x8068 #define GL_TEXTURE_BINDING_2D 0x8069 #define GL_TEXTURE_INTERNAL_FORMAT 0x1003 #define GL_TEXTURE_MIN_LOD 0x813A #define GL_TEXTURE_MAX_LOD 0x813B #define GL_TEXTURE_BASE_LEVEL 0x813C #define GL_TEXTURE_MAX_LEVEL 0x813D #define GL_NEAREST 0x2600 #define GL_LINEAR 0x2601 #define GL_NEAREST_MIPMAP_NEAREST 0x2700 #define GL_NEAREST_MIPMAP_LINEAR 0x2702 #define GL_LINEAR_MIPMAP_NEAREST 0x2701 #define GL_LINEAR_MIPMAP_LINEAR 0x2703 #define GL_LINE 0x1B01 #define GL_FILL 0x1B02 #define GL_ADD 0x0104 #define GL_DECAL 0x2101 #define GL_MODULATE 0x2100 #define GL_REPEAT 0x2901 #define GL_CLAMP 0x2900 #define GL_POINTS 0x0000 #define GL_LINES 0x0001 #define GL_LINE_LOOP 0x0002 #define GL_LINE_STRIP 0x0003 #define GL_TRIANGLES 0x0004 #define GL_TRIANGLE_STRIP 0x0005 #define GL_TRIANGLE_FAN 0x0006 #define GL_QUADS 0x0007 #define GL_QUAD_STRIP 0x0008 #define GL_POLYGON 0x0009 #define GL_FALSE 0x0 #define GL_TRUE 0x1 #define GL_BYTE 0x1400 #define GL_UNSIGNED_BYTE 0x1401 #define GL_SHORT 0x1402 #define GL_UNSIGNED_SHORT 0x1403 #define GL_INT 0x1404 #define GL_UNSIGNED_INT 0x1405 #define GL_FLOAT 0x1406 #define GL_DOUBLE 0x140A #define GL_2_BYTES 0x1407 #define GL_3_BYTES 0x1408 #define GL_4_BYTES 0x1409 #define GL_VERTEX_ARRAY 0x8074 #define GL_NORMAL_ARRAY 0x8075 #define GL_COLOR_ARRAY 0x8076 //#define GL_INDEX_ARRAY 0x8077 #define GL_TEXTURE_COORD_ARRAY 0x8078 //#define GL_EDGE_FLAG_ARRAY 0x8079 #define GL_NONE 0 #define GL_FRONT_LEFT 0x0400 #define GL_FRONT_RIGHT 0x0401 #define GL_BACK_LEFT 0x0402 #define GL_BACK_RIGHT 0x0403 #define GL_FRONT 0x0404 #define GL_BACK 0x0405 #define GL_LEFT 0x0406 #define GL_RIGHT 0x0407 #define GL_FRONT_AND_BACK 0x0408 #define GL_AUX0 0x0409 #define GL_AUX1 0x040A #define GL_AUX2 0x040B #define GL_AUX3 0x040C #define GL_VENDOR 0x1F00 #define GL_RENDERER 0x1F01 #define GL_VERSION 0x1F02 #define GL_EXTENSIONS 0x1F03 #define GL_NO_ERROR 0x0 #define GL_INVALID_VALUE 0x0501 #define GL_INVALID_ENUM 0x0500 #define GL_INVALID_OPERATION 0x0502 #define GL_STACK_OVERFLOW 0x0503 #define GL_STACK_UNDERFLOW 0x0504 #define GL_OUT_OF_MEMORY 0x0505 #define GL_DITHER 0x0BD0 #define GL_ALPHA 0x1906 #define GL_RGB 0x1907 #define GL_RGBA 0x1908 #define GL_MAX_TEXTURE_SIZE 0x0D33 #define GL_NEVER 0x0200 #define GL_LESS 0x0201 #define GL_EQUAL 0x0202 #define GL_LEQUAL 0x0203 #define GL_GREATER 0x0204 #define GL_NOTEQUAL 0x0205 #define GL_GEQUAL 0x0206 #define GL_ALWAYS 0x0207 #define GL_DEPTH_TEST 0x0B71 #define GL_RED_SCALE 0x0D14 #define GL_GREEN_SCALE 0x0D18 #define GL_BLUE_SCALE 0x0D1A #define GL_ALPHA_SCALE 0x0D1C #define GL_DEPTH_BUFFER_BIT 0x00000100 #define GL_ACCUM_BUFFER_BIT 0x00000200 #define GL_STENCIL_BUFFER_BIT 0x00000400 #define GL_COLOR_BUFFER_BIT 0x00004000 #define GL_STENCIL_TEST 0x0B90 #define GL_KEEP 0x1E00 #define GL_REPLACE 0x1E01 #define GL_INCR 0x1E02 #define GL_DECR 0x1E03 #define GL_POLYGON_OFFSET_FACTOR 0x8038 #define GL_POLYGON_OFFSET_UNITS 0x2A00 #define GL_POLYGON_OFFSET_POINT 0x2A01 #define GL_POLYGON_OFFSET_LINE 0x2A02 #define GL_POLYGON_OFFSET_FILL 0x8037 #define GL_POINT_SMOOTH 0x0B10 #define GL_LINE_SMOOTH 0x0B20 #define GL_POLYGON_SMOOTH 0x0B41 #define GL_POLYGON_STIPPLE 0x0B42 #define GL_CLIP_PLANE0 0x3000 #define GL_CLIP_PLANE1 0x3001 #define GL_CLIP_PLANE2 0x3002 #define GL_CLIP_PLANE3 0x3003 #define GL_CLIP_PLANE4 0x3004 #define GL_CLIP_PLANE5 0x3005 #define GL_DEPTH_COMPONENT 0x1902 #define GL_VIEWPORT 0x0BA2 #define GL_DRAW_BUFFER 0x0C01 #define GL_READ_BUFFER 0x0C02 #define GL_LUMINANCE 0x1909 #define GL_INTENSITY 0x8049 #endif //GL_EXT_texture_filter_anisotropic #ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF #endif // GL_ARB_depth_texture #ifndef GL_DEPTH_COMPONENT32_ARB #define GL_DEPTH_COMPONENT16_ARB 0x81A5 #define GL_DEPTH_COMPONENT24_ARB 0x81A6 #define GL_DEPTH_COMPONENT32_ARB 0x81A7 #define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A #define GL_DEPTH_TEXTURE_MODE_ARB 0x884B #endif // GL_ARB_shadow #ifndef GL_TEXTURE_COMPARE_MODE_ARB #define GL_TEXTURE_COMPARE_MODE_ARB 0x884C #define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D #define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E #endif // GL_ARB_multitexture extern void (GLAPIENTRY *qglMultiTexCoord1f) (GLenum, GLfloat); extern void (GLAPIENTRY *qglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); extern void (GLAPIENTRY *qglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); extern void (GLAPIENTRY *qglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); extern void (GLAPIENTRY *qglActiveTexture) (GLenum); extern void (GLAPIENTRY *qglClientActiveTexture) (GLenum); #ifndef GL_ACTIVE_TEXTURE #define GL_ACTIVE_TEXTURE 0x84E0 #define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 #define GL_MAX_TEXTURE_UNITS 0x84E2 #define GL_TEXTURE0 0x84C0 #define GL_TEXTURE1 0x84C1 #define GL_TEXTURE2 0x84C2 #define GL_TEXTURE3 0x84C3 #define GL_TEXTURE4 0x84C4 #define GL_TEXTURE5 0x84C5 #define GL_TEXTURE6 0x84C6 #define GL_TEXTURE7 0x84C7 #define GL_TEXTURE8 0x84C8 #define GL_TEXTURE9 0x84C9 #define GL_TEXTURE10 0x84CA #define GL_TEXTURE11 0x84CB #define GL_TEXTURE12 0x84CC #define GL_TEXTURE13 0x84CD #define GL_TEXTURE14 0x84CE #define GL_TEXTURE15 0x84CF #define GL_TEXTURE16 0x84D0 #define GL_TEXTURE17 0x84D1 #define GL_TEXTURE18 0x84D2 #define GL_TEXTURE19 0x84D3 #define GL_TEXTURE20 0x84D4 #define GL_TEXTURE21 0x84D5 #define GL_TEXTURE22 0x84D6 #define GL_TEXTURE23 0x84D7 #define GL_TEXTURE24 0x84D8 #define GL_TEXTURE25 0x84D9 #define GL_TEXTURE26 0x84DA #define GL_TEXTURE27 0x84DB #define GL_TEXTURE28 0x84DC #define GL_TEXTURE29 0x84DD #define GL_TEXTURE30 0x84DE #define GL_TEXTURE31 0x84DF #endif // GL_ARB_texture_env_combine #ifndef GL_COMBINE #define GL_COMBINE 0x8570 #define GL_COMBINE_RGB 0x8571 #define GL_COMBINE_ALPHA 0x8572 #define GL_SOURCE0_RGB 0x8580 #define GL_SOURCE1_RGB 0x8581 #define GL_SOURCE2_RGB 0x8582 #define GL_SOURCE0_ALPHA 0x8588 #define GL_SOURCE1_ALPHA 0x8589 #define GL_SOURCE2_ALPHA 0x858A #define GL_OPERAND0_RGB 0x8590 #define GL_OPERAND1_RGB 0x8591 #define GL_OPERAND2_RGB 0x8592 #define GL_OPERAND0_ALPHA 0x8598 #define GL_OPERAND1_ALPHA 0x8599 #define GL_OPERAND2_ALPHA 0x859A #define GL_RGB_SCALE 0x8573 #define GL_ADD_SIGNED 0x8574 #define GL_INTERPOLATE 0x8575 #define GL_SUBTRACT 0x84E7 #define GL_CONSTANT 0x8576 #define GL_PRIMARY_COLOR 0x8577 #define GL_PREVIOUS 0x8578 #endif #ifndef GL_MAX_ELEMENTS_VERTICES #define GL_MAX_ELEMENTS_VERTICES 0x80E8 #endif #ifndef GL_MAX_ELEMENTS_INDICES #define GL_MAX_ELEMENTS_INDICES 0x80E9 #endif #ifndef GL_TEXTURE_3D #define GL_PACK_SKIP_IMAGES 0x806B #define GL_PACK_IMAGE_HEIGHT 0x806C #define GL_UNPACK_SKIP_IMAGES 0x806D #define GL_UNPACK_IMAGE_HEIGHT 0x806E #define GL_TEXTURE_3D 0x806F #define GL_PROXY_TEXTURE_3D 0x8070 #define GL_TEXTURE_DEPTH 0x8071 #define GL_TEXTURE_WRAP_R 0x8072 #define GL_MAX_3D_TEXTURE_SIZE 0x8073 #define GL_TEXTURE_BINDING_3D 0x806A extern void (GLAPIENTRY *qglTexImage3D)(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); extern void (GLAPIENTRY *qglTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); extern void (GLAPIENTRY *qglCopyTexSubImage3D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); #endif #ifndef GL_TEXTURE_CUBE_MAP_POSITIVE_X #define GL_NORMAL_MAP 0x8511 #define GL_REFLECTION_MAP 0x8512 #define GL_TEXTURE_CUBE_MAP 0x8513 #define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 #define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 #define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 #define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A #define GL_PROXY_TEXTURE_CUBE_MAP 0x851B #define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C #endif #ifndef GL_DEPTH_COMPONENT16_ARB #define GL_DEPTH_COMPONENT16_ARB 0x81A5 #define GL_DEPTH_COMPONENT24_ARB 0x81A6 #define GL_DEPTH_COMPONENT32_ARB 0x81A7 #define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A #define GL_DEPTH_TEXTURE_MODE_ARB 0x884B #endif #ifndef GL_SCISSOR_TEST #define GL_SCISSOR_TEST 0x0C11 #define GL_SCISSOR_BOX 0x0C10 #endif // GL_SGIS_texture_edge_clamp or GL_EXT_texture_edge_clamp #ifndef GL_CLAMP_TO_EDGE #define GL_CLAMP_TO_EDGE 0x812F #endif //GL_ATI_separate_stencil #ifndef GL_STENCIL_BACK_FUNC #define GL_STENCIL_BACK_FUNC 0x8800 #define GL_STENCIL_BACK_FAIL 0x8801 #define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 #define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 #endif extern void (GLAPIENTRY *qglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); extern void (GLAPIENTRY *qglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); //GL_EXT_stencil_two_side #define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 #define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 extern void (GLAPIENTRY *qglActiveStencilFaceEXT)(GLenum); //GL_EXT_blend_minmax #ifndef GL_FUNC_ADD #define GL_FUNC_ADD 0x8006 // also supplied by GL_blend_subtract #define GL_MIN 0x8007 #define GL_MAX 0x8008 #define GL_BLEND_EQUATION 0x8009 // also supplied by GL_blend_subtract extern void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); // also supplied by GL_blend_subtract #endif //GL_EXT_blend_subtract #ifndef GL_FUNC_SUBTRACT #define GL_FUNC_SUBTRACT 0x800A #define GL_FUNC_REVERSE_SUBTRACT 0x800B extern void (GLAPIENTRY *qglBlendEquationEXT)(GLenum); // also supplied by GL_blend_subtract #endif //GL_ARB_texture_non_power_of_two //GL_ARB_vertex_buffer_object #ifndef GL_ARRAY_BUFFER #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #define GL_ARRAY_BUFFER_BINDING 0x8894 #define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 #define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 #define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 #define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 #define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 #define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A #define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B #define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C #define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D #define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E #define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F #define GL_STREAM_DRAW 0x88E0 #define GL_STREAM_READ 0x88E1 #define GL_STREAM_COPY 0x88E2 #define GL_STATIC_DRAW 0x88E4 #define GL_STATIC_READ 0x88E5 #define GL_STATIC_COPY 0x88E6 #define GL_DYNAMIC_DRAW 0x88E8 #define GL_DYNAMIC_READ 0x88E9 #define GL_DYNAMIC_COPY 0x88EA #define GL_READ_ONLY 0x88B8 #define GL_WRITE_ONLY 0x88B9 #define GL_READ_WRITE 0x88BA #define GL_BUFFER_SIZE 0x8764 #define GL_BUFFER_USAGE 0x8765 #define GL_BUFFER_ACCESS 0x88BB #define GL_BUFFER_MAPPED 0x88BC #define GL_BUFFER_MAP_POINTER 0x88BD #endif extern void (GLAPIENTRY *qglBindBufferARB) (GLenum target, GLuint buffer); extern void (GLAPIENTRY *qglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); extern void (GLAPIENTRY *qglGenBuffersARB) (GLsizei n, GLuint *buffers); extern GLboolean (GLAPIENTRY *qglIsBufferARB) (GLuint buffer); extern GLvoid* (GLAPIENTRY *qglMapBufferARB) (GLenum target, GLenum access); extern GLboolean (GLAPIENTRY *qglUnmapBufferARB) (GLenum target); extern void (GLAPIENTRY *qglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); extern void (GLAPIENTRY *qglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); //GL_ARB_framebuffer_object // (slight differences from GL_EXT_framebuffer_object as this integrates GL_EXT_packed_depth_stencil) #ifndef GL_FRAMEBUFFER #define GL_FRAMEBUFFER 0x8D40 #define GL_READ_FRAMEBUFFER 0x8CA8 #define GL_DRAW_FRAMEBUFFER 0x8CA9 #define GL_RENDERBUFFER 0x8D41 #define GL_STENCIL_INDEX1 0x8D46 #define GL_STENCIL_INDEX4 0x8D47 #define GL_STENCIL_INDEX8 0x8D48 #define GL_STENCIL_INDEX16 0x8D49 #define GL_RENDERBUFFER_WIDTH 0x8D42 #define GL_RENDERBUFFER_HEIGHT 0x8D43 #define GL_RENDERBUFFER_INTERNAL_FORMAT 0x8D44 #define GL_RENDERBUFFER_RED_SIZE 0x8D50 #define GL_RENDERBUFFER_GREEN_SIZE 0x8D51 #define GL_RENDERBUFFER_BLUE_SIZE 0x8D52 #define GL_RENDERBUFFER_ALPHA_SIZE 0x8D53 #define GL_RENDERBUFFER_DEPTH_SIZE 0x8D54 #define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55 #define GL_RENDERBUFFER_SAMPLES 0x8CAB #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE 0x8CD0 #define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME 0x8CD1 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL 0x8CD2 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE 0x8CD3 #define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER 0x8CD4 #define GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING 0x8210 #define GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE 0x8211 #define GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE 0x8212 #define GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE 0x8213 #define GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE 0x8214 #define GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE 0x8215 #define GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE 0x8216 #define GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE 0x8217 #define GL_SRGB 0x8C40 #define GL_UNSIGNED_NORMALIZED 0x8C17 #define GL_FRAMEBUFFER_DEFAULT 0x8218 #define GL_INDEX 0x8222 #define GL_COLOR_ATTACHMENT0 0x8CE0 #define GL_COLOR_ATTACHMENT1 0x8CE1 #define GL_COLOR_ATTACHMENT2 0x8CE2 #define GL_COLOR_ATTACHMENT3 0x8CE3 #define GL_COLOR_ATTACHMENT4 0x8CE4 #define GL_COLOR_ATTACHMENT5 0x8CE5 #define GL_COLOR_ATTACHMENT6 0x8CE6 #define GL_COLOR_ATTACHMENT7 0x8CE7 #define GL_COLOR_ATTACHMENT8 0x8CE8 #define GL_COLOR_ATTACHMENT9 0x8CE9 #define GL_COLOR_ATTACHMENT10 0x8CEA #define GL_COLOR_ATTACHMENT11 0x8CEB #define GL_COLOR_ATTACHMENT12 0x8CEC #define GL_COLOR_ATTACHMENT13 0x8CED #define GL_COLOR_ATTACHMENT14 0x8CEE #define GL_COLOR_ATTACHMENT15 0x8CEF #define GL_DEPTH_ATTACHMENT 0x8D00 #define GL_STENCIL_ATTACHMENT 0x8D20 #define GL_DEPTH_STENCIL_ATTACHMENT 0x821A #define GL_MAX_SAMPLES 0x8D57 #define GL_FRAMEBUFFER_COMPLETE 0x8CD5 #define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 #define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 #define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB #define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC #define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD #define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56 #define GL_FRAMEBUFFER_UNDEFINED 0x8219 #define GL_FRAMEBUFFER_BINDING 0x8CA6 // alias DRAW_FRAMEBUFFER_BINDING #define GL_DRAW_FRAMEBUFFER_BINDING 0x8CA6 #define GL_READ_FRAMEBUFFER_BINDING 0x8CAA #define GL_RENDERBUFFER_BINDING 0x8CA7 #define GL_MAX_COLOR_ATTACHMENTS 0x8CDF #define GL_MAX_RENDERBUFFER_SIZE 0x84E8 #define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 #define GL_DEPTH_STENCIL 0x84F9 #define GL_UNSIGNED_INT_24_8 0x84FA #define GL_DEPTH24_STENCIL8 0x88F0 #define GL_TEXTURE_STENCIL_SIZE 0x88F1 #endif extern GLboolean (GLAPIENTRY *qglIsRenderbuffer)(GLuint renderbuffer); extern GLvoid (GLAPIENTRY *qglBindRenderbuffer)(GLenum target, GLuint renderbuffer); extern GLvoid (GLAPIENTRY *qglDeleteRenderbuffers)(GLsizei n, const GLuint *renderbuffers); extern GLvoid (GLAPIENTRY *qglGenRenderbuffers)(GLsizei n, GLuint *renderbuffers); extern GLvoid (GLAPIENTRY *qglRenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); extern GLvoid (GLAPIENTRY *qglRenderbufferStorageMultisample)(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); extern GLvoid (GLAPIENTRY *qglGetRenderbufferParameteriv)(GLenum target, GLenum pname, GLint *params); extern GLboolean (GLAPIENTRY *qglIsFramebuffer)(GLuint framebuffer); extern GLvoid (GLAPIENTRY *qglBindFramebuffer)(GLenum target, GLuint framebuffer); extern GLvoid (GLAPIENTRY *qglDeleteFramebuffers)(GLsizei n, const GLuint *framebuffers); extern GLvoid (GLAPIENTRY *qglGenFramebuffers)(GLsizei n, GLuint *framebuffers); extern GLenum (GLAPIENTRY *qglCheckFramebufferStatus)(GLenum target); extern GLvoid (GLAPIENTRY *qglFramebufferTexture1D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); extern GLvoid (GLAPIENTRY *qglFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); extern GLvoid (GLAPIENTRY *qglFramebufferTexture3D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); extern GLvoid (GLAPIENTRY *qglFramebufferTextureLayer)(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); extern GLvoid (GLAPIENTRY *qglFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); extern GLvoid (GLAPIENTRY *qglGetFramebufferAttachmentParameteriv)(GLenum target, GLenum attachment, GLenum pname, GLint *params); extern GLvoid (GLAPIENTRY *qglBlitFramebuffer)(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); extern GLvoid (GLAPIENTRY *qglGenerateMipmap)(GLenum target); // GL_ARB_draw_buffers #ifndef GL_MAX_DRAW_BUFFERS_ARB #define GL_MAX_DRAW_BUFFERS_ARB 0x8824 #define GL_DRAW_BUFFER0_ARB 0x8825 #define GL_DRAW_BUFFER1_ARB 0x8826 #define GL_DRAW_BUFFER2_ARB 0x8827 #define GL_DRAW_BUFFER3_ARB 0x8828 #define GL_DRAW_BUFFER4_ARB 0x8829 #define GL_DRAW_BUFFER5_ARB 0x882A #define GL_DRAW_BUFFER6_ARB 0x882B #define GL_DRAW_BUFFER7_ARB 0x882C #define GL_DRAW_BUFFER8_ARB 0x882D #define GL_DRAW_BUFFER9_ARB 0x882E #define GL_DRAW_BUFFER10_ARB 0x882F #define GL_DRAW_BUFFER11_ARB 0x8830 #define GL_DRAW_BUFFER12_ARB 0x8831 #define GL_DRAW_BUFFER13_ARB 0x8832 #define GL_DRAW_BUFFER14_ARB 0x8833 #define GL_DRAW_BUFFER15_ARB 0x8834 #endif extern void (GLAPIENTRY *qglDrawBuffersARB)(GLsizei n, const GLenum *bufs); // GL_ARB_texture_float #ifndef GL_RGBA32F_ARB #define GL_RGBA32F_ARB 0x8814 #define GL_RGB32F_ARB 0x8815 #define GL_ALPHA32F_ARB 0x8816 #define GL_INTENSITY32F_ARB 0x8817 #define GL_LUMINANCE32F_ARB 0x8818 #define GL_LUMINANCE_ALPHA32F_ARB 0x8819 #define GL_RGBA16F_ARB 0x881A #define GL_RGB16F_ARB 0x881B #define GL_ALPHA16F_ARB 0x881C #define GL_INTENSITY16F_ARB 0x881D #define GL_LUMINANCE16F_ARB 0x881E #define GL_LUMINANCE_ALPHA16F_ARB 0x881F #endif // GL_ARB_half_float_pixel #ifndef GL_HALF_FLOAT_ARB typedef unsigned short GLhalfARB; #define GL_HALF_FLOAT_ARB 0x140B #endif // GL_EXT_texture_sRGB #ifndef GL_SRGB_EXT #define GL_SRGB_EXT 0x8C40 #define GL_SRGB8_EXT 0x8C41 #define GL_SRGB_ALPHA_EXT 0x8C42 #define GL_SRGB8_ALPHA8_EXT 0x8C43 #define GL_SLUMINANCE_ALPHA_EXT 0x8C44 #define GL_SLUMINANCE8_ALPHA8_EXT 0x8C45 #define GL_SLUMINANCE_EXT 0x8C46 #define GL_SLUMINANCE8_EXT 0x8C47 #define GL_COMPRESSED_SRGB_EXT 0x8C48 #define GL_COMPRESSED_SRGB_ALPHA_EXT 0x8C49 #define GL_COMPRESSED_SLUMINANCE_EXT 0x8C4A #define GL_COMPRESSED_SLUMINANCE_ALPHA_EXT 0x8C4B #define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E #define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F #endif // GL_ARB_uniform_buffer_object #ifndef GL_UNIFORM_BUFFER #define GL_UNIFORM_BUFFER 0x8A11 #define GL_UNIFORM_BUFFER_BINDING 0x8A28 #define GL_UNIFORM_BUFFER_START 0x8A29 #define GL_UNIFORM_BUFFER_SIZE 0x8A2A #define GL_MAX_VERTEX_UNIFORM_BLOCKS 0x8A2B #define GL_MAX_GEOMETRY_UNIFORM_BLOCKS 0x8A2C #define GL_MAX_FRAGMENT_UNIFORM_BLOCKS 0x8A2D #define GL_MAX_COMBINED_UNIFORM_BLOCKS 0x8A2E #define GL_MAX_UNIFORM_BUFFER_BINDINGS 0x8A2F #define GL_MAX_UNIFORM_BLOCK_SIZE 0x8A30 #define GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS 0x8A31 #define GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS 0x8A32 #define GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS 0x8A33 #define GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT 0x8A34 #define GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH 0x8A35 #define GL_ACTIVE_UNIFORM_BLOCKS 0x8A36 #define GL_UNIFORM_TYPE 0x8A37 #define GL_UNIFORM_SIZE 0x8A38 #define GL_UNIFORM_NAME_LENGTH 0x8A39 #define GL_UNIFORM_BLOCK_INDEX 0x8A3A #define GL_UNIFORM_OFFSET 0x8A3B #define GL_UNIFORM_ARRAY_STRIDE 0x8A3C #define GL_UNIFORM_MATRIX_STRIDE 0x8A3D #define GL_UNIFORM_IS_ROW_MAJOR 0x8A3E #define GL_UNIFORM_BLOCK_BINDING 0x8A3F #define GL_UNIFORM_BLOCK_DATA_SIZE 0x8A40 #define GL_UNIFORM_BLOCK_NAME_LENGTH 0x8A41 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS 0x8A42 #define GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES 0x8A43 #define GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 0x8A44 #define GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER 0x8A45 #define GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER 0x8A46 #define GL_INVALID_INDEX 0xFFFFFFFFu #endif extern void (GLAPIENTRY *qglGetUniformIndices)(GLuint program, GLsizei uniformCount, const char** uniformNames, GLuint* uniformIndices); extern void (GLAPIENTRY *qglGetActiveUniformsiv)(GLuint program, GLsizei uniformCount, const GLuint* uniformIndices, GLenum pname, GLint* params); extern void (GLAPIENTRY *qglGetActiveUniformName)(GLuint program, GLuint uniformIndex, GLsizei bufSize, GLsizei* length, char* uniformName); extern GLuint (GLAPIENTRY *qglGetUniformBlockIndex)(GLuint program, const char* uniformBlockName); extern void (GLAPIENTRY *qglGetActiveUniformBlockiv)(GLuint program, GLuint uniformBlockIndex, GLenum pname, GLint* params); extern void (GLAPIENTRY *qglGetActiveUniformBlockName)(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length, char* uniformBlockName); extern void (GLAPIENTRY *qglBindBufferRange)(GLenum target, GLuint index, GLuint buffer, GLintptrARB offset, GLsizeiptrARB size); extern void (GLAPIENTRY *qglBindBufferBase)(GLenum target, GLuint index, GLuint buffer); extern void (GLAPIENTRY *qglGetIntegeri_v)(GLenum target, GLuint index, GLint* data); extern void (GLAPIENTRY *qglUniformBlockBinding)(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding); extern void (GLAPIENTRY *qglScissor)(GLint x, GLint y, GLsizei width, GLsizei height); extern void (GLAPIENTRY *qglClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); extern void (GLAPIENTRY *qglClear)(GLbitfield mask); extern void (GLAPIENTRY *qglAlphaFunc)(GLenum func, GLclampf ref); extern void (GLAPIENTRY *qglBlendFunc)(GLenum sfactor, GLenum dfactor); extern void (GLAPIENTRY *qglCullFace)(GLenum mode); extern void (GLAPIENTRY *qglDrawBuffer)(GLenum mode); extern void (GLAPIENTRY *qglReadBuffer)(GLenum mode); extern void (GLAPIENTRY *qglEnable)(GLenum cap); extern void (GLAPIENTRY *qglDisable)(GLenum cap); extern GLboolean (GLAPIENTRY *qglIsEnabled)(GLenum cap); extern void (GLAPIENTRY *qglEnableClientState)(GLenum cap); extern void (GLAPIENTRY *qglDisableClientState)(GLenum cap); extern void (GLAPIENTRY *qglGetBooleanv)(GLenum pname, GLboolean *params); extern void (GLAPIENTRY *qglGetDoublev)(GLenum pname, GLdouble *params); extern void (GLAPIENTRY *qglGetFloatv)(GLenum pname, GLfloat *params); extern void (GLAPIENTRY *qglGetIntegerv)(GLenum pname, GLint *params); extern GLenum (GLAPIENTRY *qglGetError)(void); extern const GLubyte* (GLAPIENTRY *qglGetString)(GLenum name); extern void (GLAPIENTRY *qglFinish)(void); extern void (GLAPIENTRY *qglFlush)(void); extern void (GLAPIENTRY *qglClearDepth)(GLclampd depth); extern void (GLAPIENTRY *qglDepthFunc)(GLenum func); extern void (GLAPIENTRY *qglDepthMask)(GLboolean flag); extern void (GLAPIENTRY *qglDepthRange)(GLclampd near_val, GLclampd far_val); extern void (GLAPIENTRY *qglDepthRangef)(GLclampf near_val, GLclampf far_val); extern void (GLAPIENTRY *qglColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); extern void (GLAPIENTRY *qglDrawRangeElements)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); extern void (GLAPIENTRY *qglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); extern void (GLAPIENTRY *qglDrawArrays)(GLenum mode, GLint first, GLsizei count); extern void (GLAPIENTRY *qglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); extern void (GLAPIENTRY *qglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); extern void (GLAPIENTRY *qglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); extern void (GLAPIENTRY *qglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); extern void (GLAPIENTRY *qglArrayElement)(GLint i); extern void (GLAPIENTRY *qglColor4ub)(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); extern void (GLAPIENTRY *qglColor4f)(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); extern void (GLAPIENTRY *qglTexCoord1f)(GLfloat s); extern void (GLAPIENTRY *qglTexCoord2f)(GLfloat s, GLfloat t); extern void (GLAPIENTRY *qglTexCoord3f)(GLfloat s, GLfloat t, GLfloat r); extern void (GLAPIENTRY *qglTexCoord4f)(GLfloat s, GLfloat t, GLfloat r, GLfloat q); extern void (GLAPIENTRY *qglVertex2f)(GLfloat x, GLfloat y); extern void (GLAPIENTRY *qglVertex3f)(GLfloat x, GLfloat y, GLfloat z); extern void (GLAPIENTRY *qglVertex4f)(GLfloat x, GLfloat y, GLfloat z, GLfloat w); extern void (GLAPIENTRY *qglBegin)(GLenum mode); extern void (GLAPIENTRY *qglEnd)(void); extern void (GLAPIENTRY *qglMatrixMode)(GLenum mode); //extern void (GLAPIENTRY *qglOrtho)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); //extern void (GLAPIENTRY *qglFrustum)(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val); extern void (GLAPIENTRY *qglViewport)(GLint x, GLint y, GLsizei width, GLsizei height); //extern void (GLAPIENTRY *qglPushMatrix)(void); //extern void (GLAPIENTRY *qglPopMatrix)(void); extern void (GLAPIENTRY *qglLoadIdentity)(void); //extern void (GLAPIENTRY *qglLoadMatrixd)(const GLdouble *m); extern void (GLAPIENTRY *qglLoadMatrixf)(const GLfloat *m); //extern void (GLAPIENTRY *qglMultMatrixd)(const GLdouble *m); //extern void (GLAPIENTRY *qglMultMatrixf)(const GLfloat *m); //extern void (GLAPIENTRY *qglRotated)(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); //extern void (GLAPIENTRY *qglRotatef)(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); //extern void (GLAPIENTRY *qglScaled)(GLdouble x, GLdouble y, GLdouble z); //extern void (GLAPIENTRY *qglScalef)(GLfloat x, GLfloat y, GLfloat z); //extern void (GLAPIENTRY *qglTranslated)(GLdouble x, GLdouble y, GLdouble z); //extern void (GLAPIENTRY *qglTranslatef)(GLfloat x, GLfloat y, GLfloat z); extern void (GLAPIENTRY *qglReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); extern void (GLAPIENTRY *qglStencilFunc)(GLenum func, GLint ref, GLuint mask); extern void (GLAPIENTRY *qglStencilMask)(GLuint mask); extern void (GLAPIENTRY *qglStencilOp)(GLenum fail, GLenum zfail, GLenum zpass); extern void (GLAPIENTRY *qglClearStencil)(GLint s); extern void (GLAPIENTRY *qglTexEnvf)(GLenum target, GLenum pname, GLfloat param); extern void (GLAPIENTRY *qglTexEnvfv)(GLenum target, GLenum pname, const GLfloat *params); extern void (GLAPIENTRY *qglTexEnvi)(GLenum target, GLenum pname, GLint param); extern void (GLAPIENTRY *qglTexParameterf)(GLenum target, GLenum pname, GLfloat param); extern void (GLAPIENTRY *qglTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); extern void (GLAPIENTRY *qglTexParameteri)(GLenum target, GLenum pname, GLint param); extern void (GLAPIENTRY *qglGetTexParameterfv)(GLenum target, GLenum pname, GLfloat *params); extern void (GLAPIENTRY *qglGetTexParameteriv)(GLenum target, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetTexLevelParameterfv)(GLenum target, GLint level, GLenum pname, GLfloat *params); extern void (GLAPIENTRY *qglGetTexLevelParameteriv)(GLenum target, GLint level, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetTexImage)(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); extern void (GLAPIENTRY *qglHint)(GLenum target, GLenum mode); extern void (GLAPIENTRY *qglGenTextures)(GLsizei n, GLuint *textures); extern void (GLAPIENTRY *qglDeleteTextures)(GLsizei n, const GLuint *textures); extern void (GLAPIENTRY *qglBindTexture)(GLenum target, GLuint texture); //extern void (GLAPIENTRY *qglPrioritizeTextures)(GLsizei n, const GLuint *textures, const GLclampf *priorities); //extern GLboolean (GLAPIENTRY *qglAreTexturesResident)(GLsizei n, const GLuint *textures, GLboolean *residences); //extern GLboolean (GLAPIENTRY *qglIsTexture)(GLuint texture); //extern void (GLAPIENTRY *qglPixelStoref)(GLenum pname, GLfloat param); extern void (GLAPIENTRY *qglPixelStorei)(GLenum pname, GLint param); //extern void (GLAPIENTRY *qglTexImage1D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); extern void (GLAPIENTRY *qglTexImage2D)(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); //extern void (GLAPIENTRY *qglTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); extern void (GLAPIENTRY *qglTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); //extern void (GLAPIENTRY *qglCopyTexImage1D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); extern void (GLAPIENTRY *qglCopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); //extern void (GLAPIENTRY *qglCopyTexSubImage1D)(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); extern void (GLAPIENTRY *qglCopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); extern void (GLAPIENTRY *qglPolygonOffset)(GLfloat factor, GLfloat units); extern void (GLAPIENTRY *qglPolygonMode)(GLenum face, GLenum mode); //extern void (GLAPIENTRY *qglClipPlane)(GLenum plane, const GLdouble *equation); //extern void (GLAPIENTRY *qglGetClipPlane)(GLenum plane, GLdouble *equation); //[515]: added on 29.07.2005 extern void (GLAPIENTRY *qglLineWidth)(GLfloat width); extern void (GLAPIENTRY *qglPointSize)(GLfloat size); // GL 2.0 shader objects #ifndef GL_PROGRAM_OBJECT // 1-byte character string typedef char GLchar; #endif extern void (GLAPIENTRY *qglDeleteShader)(GLuint obj); extern void (GLAPIENTRY *qglDeleteProgram)(GLuint obj); //extern GLuint (GLAPIENTRY *qglGetHandle)(GLenum pname); extern void (GLAPIENTRY *qglDetachShader)(GLuint containerObj, GLuint attachedObj); extern GLuint (GLAPIENTRY *qglCreateShader)(GLenum shaderType); extern void (GLAPIENTRY *qglShaderSource)(GLuint shaderObj, GLsizei count, const GLchar **string, const GLint *length); extern void (GLAPIENTRY *qglCompileShader)(GLuint shaderObj); extern GLuint (GLAPIENTRY *qglCreateProgram)(void); extern void (GLAPIENTRY *qglAttachShader)(GLuint containerObj, GLuint obj); extern void (GLAPIENTRY *qglLinkProgram)(GLuint programObj); extern void (GLAPIENTRY *qglUseProgram)(GLuint programObj); extern void (GLAPIENTRY *qglValidateProgram)(GLuint programObj); extern void (GLAPIENTRY *qglUniform1f)(GLint location, GLfloat v0); extern void (GLAPIENTRY *qglUniform2f)(GLint location, GLfloat v0, GLfloat v1); extern void (GLAPIENTRY *qglUniform3f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); extern void (GLAPIENTRY *qglUniform4f)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); extern void (GLAPIENTRY *qglUniform1i)(GLint location, GLint v0); extern void (GLAPIENTRY *qglUniform2i)(GLint location, GLint v0, GLint v1); extern void (GLAPIENTRY *qglUniform3i)(GLint location, GLint v0, GLint v1, GLint v2); extern void (GLAPIENTRY *qglUniform4i)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); extern void (GLAPIENTRY *qglUniform1fv)(GLint location, GLsizei count, const GLfloat *value); extern void (GLAPIENTRY *qglUniform2fv)(GLint location, GLsizei count, const GLfloat *value); extern void (GLAPIENTRY *qglUniform3fv)(GLint location, GLsizei count, const GLfloat *value); extern void (GLAPIENTRY *qglUniform4fv)(GLint location, GLsizei count, const GLfloat *value); extern void (GLAPIENTRY *qglUniform1iv)(GLint location, GLsizei count, const GLint *value); extern void (GLAPIENTRY *qglUniform2iv)(GLint location, GLsizei count, const GLint *value); extern void (GLAPIENTRY *qglUniform3iv)(GLint location, GLsizei count, const GLint *value); extern void (GLAPIENTRY *qglUniform4iv)(GLint location, GLsizei count, const GLint *value); extern void (GLAPIENTRY *qglUniformMatrix2fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); extern void (GLAPIENTRY *qglUniformMatrix3fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); extern void (GLAPIENTRY *qglUniformMatrix4fv)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); extern void (GLAPIENTRY *qglGetShaderiv)(GLuint obj, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetProgramiv)(GLuint obj, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetShaderInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); extern void (GLAPIENTRY *qglGetProgramInfoLog)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *infoLog); extern void (GLAPIENTRY *qglGetAttachedShaders)(GLuint containerObj, GLsizei maxCount, GLsizei *count, GLuint *obj); extern GLint (GLAPIENTRY *qglGetUniformLocation)(GLuint programObj, const GLchar *name); extern void (GLAPIENTRY *qglGetActiveUniform)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); extern void (GLAPIENTRY *qglGetUniformfv)(GLuint programObj, GLint location, GLfloat *params); extern void (GLAPIENTRY *qglGetUniformiv)(GLuint programObj, GLint location, GLint *params); extern void (GLAPIENTRY *qglGetShaderSource)(GLuint obj, GLsizei maxLength, GLsizei *length, GLchar *source); extern void (GLAPIENTRY *qglPolygonStipple)(const GLubyte *mask); #ifndef GL_PROGRAM_OBJECT #define GL_PROGRAM_OBJECT 0x8B40 #define GL_DELETE_STATUS 0x8B80 #define GL_COMPILE_STATUS 0x8B81 #define GL_LINK_STATUS 0x8B82 #define GL_VALIDATE_STATUS 0x8B83 #define GL_INFO_LOG_LENGTH 0x8B84 #define GL_ATTACHED_SHADERS 0x8B85 #define GL_ACTIVE_UNIFORMS 0x8B86 #define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 #define GL_SHADER_SOURCE_LENGTH 0x8B88 #define GL_SHADER_OBJECT 0x8B48 #define GL_SHADER_TYPE 0x8B4F #define GL_FLOAT 0x1406 #define GL_FLOAT_VEC2 0x8B50 #define GL_FLOAT_VEC3 0x8B51 #define GL_FLOAT_VEC4 0x8B52 #define GL_INT 0x1404 #define GL_INT_VEC2 0x8B53 #define GL_INT_VEC3 0x8B54 #define GL_INT_VEC4 0x8B55 #define GL_BOOL 0x8B56 #define GL_BOOL_VEC2 0x8B57 #define GL_BOOL_VEC3 0x8B58 #define GL_BOOL_VEC4 0x8B59 #define GL_FLOAT_MAT2 0x8B5A #define GL_FLOAT_MAT3 0x8B5B #define GL_FLOAT_MAT4 0x8B5C #define GL_SAMPLER_1D 0x8B5D #define GL_SAMPLER_2D 0x8B5E #define GL_SAMPLER_3D 0x8B5F #define GL_SAMPLER_CUBE 0x8B60 #define GL_SAMPLER_1D_SHADOW 0x8B61 #define GL_SAMPLER_2D_SHADOW 0x8B62 #define GL_SAMPLER_2D_RECT 0x8B63 #define GL_SAMPLER_2D_RECT_SHADOW 0x8B64 #endif // GL 2.0 vertex shader extern void (GLAPIENTRY *qglVertexAttrib1f)(GLuint index, GLfloat v0); extern void (GLAPIENTRY *qglVertexAttrib1s)(GLuint index, GLshort v0); extern void (GLAPIENTRY *qglVertexAttrib1d)(GLuint index, GLdouble v0); extern void (GLAPIENTRY *qglVertexAttrib2f)(GLuint index, GLfloat v0, GLfloat v1); extern void (GLAPIENTRY *qglVertexAttrib2s)(GLuint index, GLshort v0, GLshort v1); extern void (GLAPIENTRY *qglVertexAttrib2d)(GLuint index, GLdouble v0, GLdouble v1); extern void (GLAPIENTRY *qglVertexAttrib3f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); extern void (GLAPIENTRY *qglVertexAttrib3s)(GLuint index, GLshort v0, GLshort v1, GLshort v2); extern void (GLAPIENTRY *qglVertexAttrib3d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2); extern void (GLAPIENTRY *qglVertexAttrib4f)(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); extern void (GLAPIENTRY *qglVertexAttrib4s)(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3); extern void (GLAPIENTRY *qglVertexAttrib4d)(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); extern void (GLAPIENTRY *qglVertexAttrib4Nub)(GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); extern void (GLAPIENTRY *qglVertexAttrib1fv)(GLuint index, const GLfloat *v); extern void (GLAPIENTRY *qglVertexAttrib1sv)(GLuint index, const GLshort *v); extern void (GLAPIENTRY *qglVertexAttrib1dv)(GLuint index, const GLdouble *v); extern void (GLAPIENTRY *qglVertexAttrib2fv)(GLuint index, const GLfloat *v); extern void (GLAPIENTRY *qglVertexAttrib2sv)(GLuint index, const GLshort *v); extern void (GLAPIENTRY *qglVertexAttrib2dv)(GLuint index, const GLdouble *v); extern void (GLAPIENTRY *qglVertexAttrib3fv)(GLuint index, const GLfloat *v); extern void (GLAPIENTRY *qglVertexAttrib3sv)(GLuint index, const GLshort *v); extern void (GLAPIENTRY *qglVertexAttrib3dv)(GLuint index, const GLdouble *v); extern void (GLAPIENTRY *qglVertexAttrib4fv)(GLuint index, const GLfloat *v); extern void (GLAPIENTRY *qglVertexAttrib4sv)(GLuint index, const GLshort *v); extern void (GLAPIENTRY *qglVertexAttrib4dv)(GLuint index, const GLdouble *v); extern void (GLAPIENTRY *qglVertexAttrib4iv)(GLuint index, const GLint *v); extern void (GLAPIENTRY *qglVertexAttrib4bv)(GLuint index, const GLbyte *v); extern void (GLAPIENTRY *qglVertexAttrib4ubv)(GLuint index, const GLubyte *v); extern void (GLAPIENTRY *qglVertexAttrib4usv)(GLuint index, const GLushort *v); extern void (GLAPIENTRY *qglVertexAttrib4uiv)(GLuint index, const GLuint *v); extern void (GLAPIENTRY *qglVertexAttrib4Nbv)(GLuint index, const GLbyte *v); extern void (GLAPIENTRY *qglVertexAttrib4Nsv)(GLuint index, const GLshort *v); extern void (GLAPIENTRY *qglVertexAttrib4Niv)(GLuint index, const GLint *v); extern void (GLAPIENTRY *qglVertexAttrib4Nubv)(GLuint index, const GLubyte *v); extern void (GLAPIENTRY *qglVertexAttrib4Nusv)(GLuint index, const GLushort *v); extern void (GLAPIENTRY *qglVertexAttrib4Nuiv)(GLuint index, const GLuint *v); extern void (GLAPIENTRY *qglVertexAttribPointer)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); extern void (GLAPIENTRY *qglEnableVertexAttribArray)(GLuint index); extern void (GLAPIENTRY *qglDisableVertexAttribArray)(GLuint index); extern void (GLAPIENTRY *qglBindAttribLocation)(GLuint programObj, GLuint index, const GLchar *name); extern void (GLAPIENTRY *qglBindFragDataLocation)(GLuint programObj, GLuint index, const GLchar *name); extern void (GLAPIENTRY *qglGetActiveAttrib)(GLuint programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLchar *name); extern GLint (GLAPIENTRY *qglGetAttribLocation)(GLuint programObj, const GLchar *name); extern void (GLAPIENTRY *qglGetVertexAttribdv)(GLuint index, GLenum pname, GLdouble *params); extern void (GLAPIENTRY *qglGetVertexAttribfv)(GLuint index, GLenum pname, GLfloat *params); extern void (GLAPIENTRY *qglGetVertexAttribiv)(GLuint index, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetVertexAttribPointerv)(GLuint index, GLenum pname, GLvoid **pointer); #ifndef GL_VERTEX_SHADER #define GL_VERTEX_SHADER 0x8B31 #define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A #define GL_MAX_VARYING_FLOATS 0x8B4B #define GL_MAX_VERTEX_ATTRIBS 0x8869 #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 #define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C #define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D #define GL_MAX_TEXTURE_COORDS 0x8871 #define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 #define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 #define GL_ACTIVE_ATTRIBUTES 0x8B89 #define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A #define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 #define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 #define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 #define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 #define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A #define GL_CURRENT_VERTEX_ATTRIB 0x8626 #define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 #define GL_FLOAT 0x1406 #define GL_FLOAT_VEC2 0x8B50 #define GL_FLOAT_VEC3 0x8B51 #define GL_FLOAT_VEC4 0x8B52 #define GL_FLOAT_MAT2 0x8B5A #define GL_FLOAT_MAT3 0x8B5B #define GL_FLOAT_MAT4 0x8B5C #endif // GL 2.0 fragment shader #ifndef GL_FRAGMENT_SHADER #define GL_FRAGMENT_SHADER 0x8B30 #define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 #define GL_MAX_TEXTURE_COORDS 0x8871 #define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 #define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B #endif // GL 2.0 shading language 100 #ifndef GL_SHADING_LANGUAGE_VERSION #define GL_SHADING_LANGUAGE_VERSION 0x8B8C #endif // GL_ARB_texture_compression extern void (GLAPIENTRY *qglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); extern void (GLAPIENTRY *qglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); //extern void (GLAPIENTRY *qglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); extern void (GLAPIENTRY *qglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); extern void (GLAPIENTRY *qglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); //extern void (GLAPIENTRY *qglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); extern void (GLAPIENTRY *qglGetCompressedTexImageARB)(GLenum target, GLint lod, void *img); #ifndef GL_COMPRESSED_RGB_ARB #define GL_COMPRESSED_ALPHA_ARB 0x84E9 #define GL_COMPRESSED_LUMINANCE_ARB 0x84EA #define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB #define GL_COMPRESSED_INTENSITY_ARB 0x84EC #define GL_COMPRESSED_RGB_ARB 0x84ED #define GL_COMPRESSED_RGBA_ARB 0x84EE #define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF #define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 #define GL_TEXTURE_COMPRESSED_ARB 0x86A1 #define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 #define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 #endif // GL_EXT_texture_compression_s3tc #ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 #endif // GL_ARB_occlusion_query extern void (GLAPIENTRY *qglGenQueriesARB)(GLsizei n, GLuint *ids); extern void (GLAPIENTRY *qglDeleteQueriesARB)(GLsizei n, const GLuint *ids); extern GLboolean (GLAPIENTRY *qglIsQueryARB)(GLuint qid); extern void (GLAPIENTRY *qglBeginQueryARB)(GLenum target, GLuint qid); extern void (GLAPIENTRY *qglEndQueryARB)(GLenum target); extern void (GLAPIENTRY *qglGetQueryivARB)(GLenum target, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetQueryObjectivARB)(GLuint qid, GLenum pname, GLint *params); extern void (GLAPIENTRY *qglGetQueryObjectuivARB)(GLuint qid, GLenum pname, GLuint *params); #ifndef GL_SAMPLES_PASSED_ARB #define GL_SAMPLES_PASSED_ARB 0x8914 #define GL_QUERY_COUNTER_BITS_ARB 0x8864 #define GL_CURRENT_QUERY_ARB 0x8865 #define GL_QUERY_RESULT_ARB 0x8866 #define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 #endif // GL_ARB_query_buffer_object #ifndef GL_QUERY_BUFFER_ARB #define GL_QUERY_BUFFER_ARB 0x9192 #define GL_QUERY_BUFFER_BINDING_ARB 0x9193 #define GL_QUERY_RESULT_NO_WAIT_ARB 0x9194 #define GL_QUERY_BUFFER_BARRIER_BIT_ARB 0x00008000 #endif // GL_EXT_bgr #define GL_BGR 0x80E0 // GL_EXT_bgra #define GL_BGRA 0x80E1 //GL_AMD_texture_texture4 //GL_ARB_texture_gather //GL_ARB_multisample #define GL_MULTISAMPLE_ARB 0x809D #define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E #define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F #define GL_SAMPLE_COVERAGE_ARB 0x80A0 #define GL_SAMPLE_BUFFERS_ARB 0x80A8 #define GL_SAMPLES_ARB 0x80A9 #define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA #define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB #define GL_MULTISAMPLE_BIT_ARB 0x20000000 extern void (GLAPIENTRY *qglSampleCoverageARB)(GLclampf value, GLboolean invert); extern void (GLAPIENTRY *qglPointSize)(GLfloat size); //GL_EXT_packed_depth_stencil #define GL_DEPTH_STENCIL_EXT 0x84F9 #define GL_UNSIGNED_INT_24_8_EXT 0x84FA #define GL_DEPTH24_STENCIL8_EXT 0x88F0 //GL_EXT_blend_func_separate #ifndef GL_BLEND_DST_RGB #define GL_BLEND_DST_RGB 0x80C8 #define GL_BLEND_SRC_RGB 0x80C9 #define GL_BLEND_DST_ALPHA 0x80CA #define GL_BLEND_SRC_ALPHA 0x80CB #endif extern void (GLAPIENTRY *qglBlendFuncSeparate)(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); #endif #define DEBUGGL #ifdef DEBUGGL #ifdef USE_GLES2 #define CHECKGLERROR {if (gl_paranoid.integer){if (gl_printcheckerror.integer) Con_Printf("CHECKGLERROR at %s:%d\n", __FILE__, __LINE__);gl_errornumber = glGetError();if (gl_errornumber) GL_PrintError(gl_errornumber, __FILE__, __LINE__);}} #else #define CHECKGLERROR {if (gl_paranoid.integer){if (gl_printcheckerror.integer) Con_Printf("CHECKGLERROR at %s:%d\n", __FILE__, __LINE__);gl_errornumber = qglGetError ? qglGetError() : 0;if (gl_errornumber) GL_PrintError(gl_errornumber, __FILE__, __LINE__);}} #endif extern int gl_errornumber; void GL_PrintError(int errornumber, const char *filename, int linenumber); #else #define CHECKGLERROR #endif #ifdef USE_GLES2 #define qglIsBufferARB glIsBuffer #define qglIsEnabled glIsEnabled #define qglIsFramebufferEXT glIsFramebuffer //#define qglIsQueryARB glIsQuery #define qglIsRenderbufferEXT glIsRenderbuffer //#define qglUnmapBufferARB glUnmapBuffer #define qglCheckFramebufferStatus glCheckFramebufferStatus #define qglGetError glGetError #define qglCreateProgram glCreateProgram #define qglCreateShader glCreateShader //#define qglGetHandleARB glGetHandle #define qglGetAttribLocation glGetAttribLocation #define qglGetUniformLocation glGetUniformLocation //#define qglMapBufferARB glMapBuffer #define qglGetString glGetString //#define qglActiveStencilFaceEXT glActiveStencilFace #define qglActiveTexture glActiveTexture #define qglAlphaFunc glAlphaFunc #define qglArrayElement glArrayElement #define qglAttachShader glAttachShader //#define qglBegin glBegin //#define qglBeginQueryARB glBeginQuery #define qglBindAttribLocation glBindAttribLocation //#define qglBindFragDataLocation glBindFragDataLocation #define qglBindBufferARB glBindBuffer #define qglBindFramebuffer glBindFramebuffer #define qglBindRenderbuffer glBindRenderbuffer #define qglBindTexture glBindTexture #define qglBlendEquationEXT glBlendEquation #define qglBlendFunc glBlendFunc #define qglBlendFuncSeparate glBlendFuncSeparate #define qglBufferDataARB glBufferData #define qglBufferSubDataARB glBufferSubData #define qglClear glClear #define qglClearColor glClearColor #define qglClearDepthf glClearDepthf #define qglClearStencil glClearStencil #define qglClientActiveTexture glClientActiveTexture #define qglColor4f glColor4f #define qglColor4ub glColor4ub #define qglColorMask glColorMask #define qglColorPointer glColorPointer #define qglCompileShader glCompileShader #define qglCompressedTexImage2DARB glCompressedTexImage2D #define qglCompressedTexImage3DARB glCompressedTexImage3D #define qglCompressedTexSubImage2DARB glCompressedTexSubImage2D #define qglCompressedTexSubImage3DARB glCompressedTexSubImage3D #define qglCopyTexImage2D glCopyTexImage2D #define qglCopyTexSubImage2D glCopyTexSubImage2D #define qglCopyTexSubImage3D glCopyTexSubImage3D #define qglCullFace glCullFace #define qglDeleteBuffersARB glDeleteBuffers #define qglDeleteFramebuffers glDeleteFramebuffers #define qglDeleteProgram glDeleteProgram #define qglDeleteShader glDeleteShader //#define qglDeleteQueriesARB glDeleteQueries #define qglDeleteRenderbuffers glDeleteRenderbuffers #define qglDeleteTextures glDeleteTextures #define qglDepthFunc glDepthFunc #define qglDepthMask glDepthMask #define qglDepthRangef glDepthRangef #define qglDetachShader glDetachShader #define qglDisable glDisable #define qglDisableClientState glDisableClientState #define qglDisableVertexAttribArray glDisableVertexAttribArray #define qglDrawArrays glDrawArrays //#define qglDrawBuffer glDrawBuffer //#define qglDrawBuffersARB glDrawBuffers #define qglDrawElements glDrawElements //#define qglDrawRangeElements glDrawRangeElements #define qglEnable glEnable #define qglEnableClientState glEnableClientState #define qglEnableVertexAttribArray glEnableVertexAttribArray //#define qglEnd glEnd //#define qglEndQueryARB glEndQuery #define qglFinish glFinish #define qglFlush glFlush #define qglFramebufferRenderbuffer glFramebufferRenderbuffer #define qglFramebufferTexture2D glFramebufferTexture2D #define qglFramebufferTexture3DEXT glFramebufferTexture3D #define qglGenBuffersARB glGenBuffers #define qglGenFramebuffers glGenFramebuffers //#define qglGenQueriesARB glGenQueries #define qglGenRenderbuffers glGenRenderbuffers #define qglGenTextures glGenTextures #define qglGenerateMipmapEXT glGenerateMipmap #define qglGetActiveAttrib glGetActiveAttrib #define qglGetActiveUniform glGetActiveUniform #define qglGetAttachedShaders glGetAttachedShaders #define qglGetBooleanv glGetBooleanv //#define qglGetCompressedTexImageARB glGetCompressedTexImage #define qglGetDoublev glGetDoublev #define qglGetFloatv glGetFloatv #define qglGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameteriv #define qglGetProgramInfoLog glGetProgramInfoLog #define qglGetShaderInfoLog glGetShaderInfoLog #define qglGetIntegerv glGetIntegerv #define qglGetShaderiv glGetShaderiv #define qglGetProgramiv glGetProgramiv //#define qglGetQueryObjectivARB glGetQueryObjectiv //#define qglGetQueryObjectuivARB glGetQueryObjectuiv //#define qglGetQueryivARB glGetQueryiv #define qglGetRenderbufferParameterivEXT glGetRenderbufferParameteriv #define qglGetShaderSource glGetShaderSource #define qglGetTexImage glGetTexImage #define qglGetTexLevelParameterfv glGetTexLevelParameterfv #define qglGetTexLevelParameteriv glGetTexLevelParameteriv #define qglGetTexParameterfv glGetTexParameterfv #define qglGetTexParameteriv glGetTexParameteriv #define qglGetUniformfv glGetUniformfv #define qglGetUniformiv glGetUniformiv #define qglHint glHint #define qglLineWidth glLineWidth #define qglLinkProgram glLinkProgram #define qglLoadIdentity glLoadIdentity #define qglLoadMatrixf glLoadMatrixf #define qglMatrixMode glMatrixMode #define qglMultiTexCoord1f glMultiTexCoord1f #define qglMultiTexCoord2f glMultiTexCoord2f #define qglMultiTexCoord3f glMultiTexCoord3f #define qglMultiTexCoord4f glMultiTexCoord4f #define qglNormalPointer glNormalPointer #define qglPixelStorei glPixelStorei #define qglPointSize glPointSize //#define qglPolygonMode glPolygonMode #define qglPolygonOffset glPolygonOffset //#define qglPolygonStipple glPolygonStipple #define qglReadBuffer glReadBuffer #define qglReadPixels glReadPixels #define qglRenderbufferStorage glRenderbufferStorage #define qglScissor glScissor #define qglShaderSource glShaderSource #define qglStencilFunc glStencilFunc #define qglStencilFuncSeparate glStencilFuncSeparate #define qglStencilMask glStencilMask #define qglStencilOp glStencilOp #define qglStencilOpSeparate glStencilOpSeparate #define qglTexCoord1f glTexCoord1f #define qglTexCoord2f glTexCoord2f #define qglTexCoord3f glTexCoord3f #define qglTexCoord4f glTexCoord4f #define qglTexCoordPointer glTexCoordPointer #define qglTexEnvf glTexEnvf #define qglTexEnvfv glTexEnvfv #define qglTexEnvi glTexEnvi #define qglTexImage2D glTexImage2D #define qglTexImage3D glTexImage3D #define qglTexParameterf glTexParameterf #define qglTexParameterfv glTexParameterfv #define qglTexParameteri glTexParameteri #define qglTexSubImage2D glTexSubImage2D #define qglTexSubImage3D glTexSubImage3D #define qglUniform1f glUniform1f #define qglUniform1fv glUniform1fv #define qglUniform1i glUniform1i #define qglUniform1iv glUniform1iv #define qglUniform2f glUniform2f #define qglUniform2fv glUniform2fv #define qglUniform2i glUniform2i #define qglUniform2iv glUniform2iv #define qglUniform3f glUniform3f #define qglUniform3fv glUniform3fv #define qglUniform3i glUniform3i #define qglUniform3iv glUniform3iv #define qglUniform4f glUniform4f #define qglUniform4fv glUniform4fv #define qglUniform4i glUniform4i #define qglUniform4iv glUniform4iv #define qglUniformMatrix2fv glUniformMatrix2fv #define qglUniformMatrix3fv glUniformMatrix3fv #define qglUniformMatrix4fv glUniformMatrix4fv #define qglUseProgram glUseProgram #define qglValidateProgram glValidateProgram #define qglVertex2f glVertex2f #define qglVertex3f glVertex3f #define qglVertex4f glVertex4f #define qglVertexAttribPointer glVertexAttribPointer #define qglVertexPointer glVertexPointer #define qglViewport glViewport #define qglVertexAttrib1f glVertexAttrib1f //#define qglVertexAttrib1s glVertexAttrib1s //#define qglVertexAttrib1d glVertexAttrib1d #define qglVertexAttrib2f glVertexAttrib2f //#define qglVertexAttrib2s glVertexAttrib2s //#define qglVertexAttrib2d glVertexAttrib2d #define qglVertexAttrib3f glVertexAttrib3f //#define qglVertexAttrib3s glVertexAttrib3s //#define qglVertexAttrib3d glVertexAttrib3d #define qglVertexAttrib4f glVertexAttrib4f //#define qglVertexAttrib4s glVertexAttrib4s //#define qglVertexAttrib4d glVertexAttrib4d //#define qglVertexAttrib4Nub glVertexAttrib4Nub #define qglVertexAttrib1fv glVertexAttrib1fv //#define qglVertexAttrib1sv glVertexAttrib1sv //#define qglVertexAttrib1dv glVertexAttrib1dv #define qglVertexAttrib2fv glVertexAttrib2fv //#define qglVertexAttrib2sv glVertexAttrib2sv //#define qglVertexAttrib2dv glVertexAttrib2dv #define qglVertexAttrib3fv glVertexAttrib3fv //#define qglVertexAttrib3sv glVertexAttrib3sv //#define qglVertexAttrib3dv glVertexAttrib3dv #define qglVertexAttrib4fv glVertexAttrib4fv //#define qglVertexAttrib4sv glVertexAttrib4sv //#define qglVertexAttrib4dv glVertexAttrib4dv //#define qglVertexAttrib4iv glVertexAttrib4iv //#define qglVertexAttrib4bv glVertexAttrib4bv //#define qglVertexAttrib4ubv glVertexAttrib4ubv //#define qglVertexAttrib4usv glVertexAttrib4usv //#define qglVertexAttrib4uiv glVertexAttrib4uiv //#define qglVertexAttrib4Nbv glVertexAttrib4Nbv //#define qglVertexAttrib4Nsv glVertexAttrib4Nsv //#define qglVertexAttrib4Niv glVertexAttrib4Niv //#define qglVertexAttrib4Nubv glVertexAttrib4Nubv //#define qglVertexAttrib4Nusv glVertexAttrib4Nusv //#define qglVertexAttrib4Nuiv glVertexAttrib4Nuiv //#define qglGetVertexAttribdv glGetVertexAttribdv #define qglGetVertexAttribfv glGetVertexAttribfv #define qglGetVertexAttribiv glGetVertexAttribiv #define qglGetVertexAttribPointerv glGetVertexAttribPointerv #endif #endif darkplaces/intoverflow.h0000664000175000017500000000227613067716220014721 0ustar kalevkalev#ifndef INTOVERFLOW_H #define INTOVERFLOW_H // simple safe library to handle integer overflows when doing buffer size calculations // Usage: // - calculate data size using INTOVERFLOW_??? macros // - compare: calculated-size <= INTOVERFLOW_NORMALIZE(buffersize) // Functionality: // - all overflows (values > INTOVERFLOW_MAX) and errors are mapped to INTOVERFLOW_MAX // - if any input of an operation is INTOVERFLOW_MAX, INTOVERFLOW_MAX will be returned // - otherwise, regular arithmetics apply #define INTOVERFLOW_MAX 2147483647 #define INTOVERFLOW_ADD(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX - (b)) ? ((a) + (b)) : INTOVERFLOW_MAX) #define INTOVERFLOW_SUB(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) <= (a)) ? ((a) - (b)) : INTOVERFLOW_MAX) #define INTOVERFLOW_MUL(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (a) < INTOVERFLOW_MAX / (b)) ? ((a) * (b)) : INTOVERFLOW_MAX) #define INTOVERFLOW_DIV(a,b) (((a) < INTOVERFLOW_MAX && (b) < INTOVERFLOW_MAX && (b) > 0) ? ((a) / (b)) : INTOVERFLOW_MAX) #define INTOVERFLOW_NORMALIZE(a) (((a) < INTOVERFLOW_MAX) ? (a) : (INTOVERFLOW_MAX - 1)) #endif darkplaces/COPYING0000664000175000017500000004311013067716216013222 0ustar kalevkalev GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. darkplaces/resource.h0000664000175000017500000000067313067716222014173 0ustar kalevkalev//{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by darkplaces.rc // #define IDI_ICON1 101 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif darkplaces/vid_agl.c0000664000175000017500000007567013067716222013755 0ustar kalevkalev/* vid_agl.c Mac OS X OpenGL and input module, using Carbon and AGL Copyright (C) 2005-2006 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "quakedef.h" #include "vid_agl_mackeys.h" // this is SDL/src/video/maccommon/SDL_mackeys.h #ifndef kCGLCEMPEngine #define kCGLCEMPEngine 313 #endif // Tell startup code that we have a client int cl_available = true; qboolean vid_supportrefreshrate = true; // AGL prototypes AGLPixelFormat (*qaglChoosePixelFormat) (const AGLDevice *gdevs, GLint ndev, const GLint *attribList); AGLContext (*qaglCreateContext) (AGLPixelFormat pix, AGLContext share); GLboolean (*qaglDestroyContext) (AGLContext ctx); void (*qaglDestroyPixelFormat) (AGLPixelFormat pix); const GLubyte* (*qaglErrorString) (GLenum code); GLenum (*qaglGetError) (void); GLboolean (*qaglSetCurrentContext) (AGLContext ctx); GLboolean (*qaglSetDrawable) (AGLContext ctx, AGLDrawable draw); GLboolean (*qaglSetFullScreen) (AGLContext ctx, GLsizei width, GLsizei height, GLsizei freq, GLint device); GLboolean (*qaglSetInteger) (AGLContext ctx, GLenum pname, const GLint *params); void (*qaglSwapBuffers) (AGLContext ctx); CGLError (*qCGLEnable) (CGLContextObj ctx, CGLContextEnable pname); CGLError (*qCGLDisable) (CGLContextObj ctx, CGLContextEnable pname); CGLContextObj (*qCGLGetCurrentContext) (void); static qboolean multithreadedgl; static qboolean mouse_avail = true; static qboolean vid_usingmouse = false; static qboolean vid_usinghidecursor = false; static qboolean vid_usingnoaccel = false; static qboolean vid_isfullscreen = false; static qboolean vid_usingvsync = false; static qboolean sound_active = true; static cvar_t apple_multithreadedgl = {CVAR_SAVE, "apple_multithreadedgl", "1", "makes use of a second thread for the OpenGL driver (if possible) rather than using the engine thread (note: this is done automatically on most other operating systems)"}; static cvar_t apple_mouse_noaccel = {CVAR_SAVE, "apple_mouse_noaccel", "1", "disables mouse acceleration while DarkPlaces is active"}; static AGLContext context; static WindowRef window; static double originalMouseSpeed = -1.0; io_connect_t IN_GetIOHandle(void) { io_connect_t iohandle = MACH_PORT_NULL; kern_return_t status; io_service_t iohidsystem = MACH_PORT_NULL; mach_port_t masterport; status = IOMasterPort(MACH_PORT_NULL, &masterport); if(status != KERN_SUCCESS) return 0; iohidsystem = IORegistryEntryFromPath(masterport, kIOServicePlane ":/IOResources/IOHIDSystem"); if(!iohidsystem) return 0; status = IOServiceOpen(iohidsystem, mach_task_self(), kIOHIDParamConnectType, &iohandle); IOObjectRelease(iohidsystem); return iohandle; } void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) { if (!mouse_avail || !window) relative = hidecursor = false; if (relative) { if(vid_usingmouse && (vid_usingnoaccel != !!apple_mouse_noaccel.integer)) VID_SetMouse(false, false, false); // ungrab first! if (!vid_usingmouse) { Rect winBounds; CGPoint winCenter; SelectWindow(window); // Put the mouse cursor at the center of the window GetWindowBounds (window, kWindowContentRgn, &winBounds); winCenter.x = (winBounds.left + winBounds.right) / 2; winCenter.y = (winBounds.top + winBounds.bottom) / 2; CGWarpMouseCursorPosition(winCenter); // Lock the mouse pointer at its current position CGAssociateMouseAndMouseCursorPosition(false); // Save the status of mouse acceleration originalMouseSpeed = -1.0; // in case of error if(apple_mouse_noaccel.integer) { io_connect_t mouseDev = IN_GetIOHandle(); if(mouseDev != 0) { if(IOHIDGetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), &originalMouseSpeed) == kIOReturnSuccess) { Con_DPrintf("previous mouse acceleration: %f\n", originalMouseSpeed); if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), -1.0) != kIOReturnSuccess) { Con_Print("Could not disable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } } else { Con_Print("Could not disable mouse acceleration (failed at IOHIDGetAccelerationWithKey).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } IOServiceClose(mouseDev); } else { Con_Print("Could not disable mouse acceleration (failed at IO_GetIOHandle).\n"); Cvar_SetValueQuick(&apple_mouse_noaccel, 0); } } vid_usingmouse = true; vid_usingnoaccel = !!apple_mouse_noaccel.integer; } } else { if (vid_usingmouse) { if(originalMouseSpeed != -1.0) { io_connect_t mouseDev = IN_GetIOHandle(); if(mouseDev != 0) { Con_DPrintf("restoring mouse acceleration to: %f\n", originalMouseSpeed); if(IOHIDSetAccelerationWithKey(mouseDev, CFSTR(kIOHIDMouseAccelerationType), originalMouseSpeed) != kIOReturnSuccess) Con_Print("Could not re-enable mouse acceleration (failed at IOHIDSetAccelerationWithKey).\n"); IOServiceClose(mouseDev); } else Con_Print("Could not re-enable mouse acceleration (failed at IO_GetIOHandle).\n"); } CGAssociateMouseAndMouseCursorPosition(true); vid_usingmouse = false; } } if (vid_usinghidecursor != hidecursor) { vid_usinghidecursor = hidecursor; if (hidecursor) CGDisplayHideCursor(CGMainDisplayID()); else CGDisplayShowCursor(CGMainDisplayID()); } } #define GAMMA_TABLE_SIZE 256 void VID_Finish (void) { qboolean vid_usevsync; // handle changes of the vsync option vid_usevsync = (vid_vsync.integer && !cls.timedemo); if (vid_usingvsync != vid_usevsync) { GLint sync = (vid_usevsync ? 1 : 0); if (qaglSetInteger(context, AGL_SWAP_INTERVAL, &sync) == GL_TRUE) { vid_usingvsync = vid_usevsync; Con_DPrintf("Vsync %s\n", vid_usevsync ? "activated" : "deactivated"); } else Con_Printf("ERROR: can't %s vsync\n", vid_usevsync ? "activate" : "deactivate"); } if (!vid_hidden) { if (r_speeds.integer == 2 || gl_finish.integer) GL_Finish(); qaglSwapBuffers(context); } VID_UpdateGamma(false, GAMMA_TABLE_SIZE); if (apple_multithreadedgl.integer) { if (!multithreadedgl) { if(qCGLGetCurrentContext && qCGLEnable && qCGLDisable) { CGLContextObj ctx = qCGLGetCurrentContext(); CGLError e = qCGLEnable(ctx, kCGLCEMPEngine); if(e == kCGLNoError) multithreadedgl = true; else { Con_Printf("WARNING: can't enable multithreaded GL, error %d\n", (int) e); Cvar_SetValueQuick(&apple_multithreadedgl, 0); } } else { Con_Printf("WARNING: can't enable multithreaded GL, CGL functions not present\n"); Cvar_SetValueQuick(&apple_multithreadedgl, 0); } } } else { if (multithreadedgl) { if(qCGLGetCurrentContext && qCGLEnable && qCGLDisable) { CGLContextObj ctx = qCGLGetCurrentContext(); qCGLDisable(ctx, kCGLCEMPEngine); multithreadedgl = false; } } } } int VID_SetGamma(unsigned short *ramps, int rampsize) { CGGammaValue table_red [GAMMA_TABLE_SIZE]; CGGammaValue table_green [GAMMA_TABLE_SIZE]; CGGammaValue table_blue [GAMMA_TABLE_SIZE]; int i; // Convert the unsigned short table into 3 float tables for (i = 0; i < rampsize; i++) table_red[i] = (float)ramps[i] / 65535.0f; for (i = 0; i < rampsize; i++) table_green[i] = (float)ramps[i + rampsize] / 65535.0f; for (i = 0; i < rampsize; i++) table_blue[i] = (float)ramps[i + 2 * rampsize] / 65535.0f; if (CGSetDisplayTransferByTable(CGMainDisplayID(), rampsize, table_red, table_green, table_blue) != CGDisplayNoErr) { Con_Print("VID_SetGamma: ERROR: CGSetDisplayTransferByTable failed!\n"); return false; } return true; } int VID_GetGamma(unsigned short *ramps, int rampsize) { CGGammaValue table_red [GAMMA_TABLE_SIZE]; CGGammaValue table_green [GAMMA_TABLE_SIZE]; CGGammaValue table_blue [GAMMA_TABLE_SIZE]; CGTableCount actualsize = 0; int i; // Get the gamma ramps from the system if (CGGetDisplayTransferByTable(CGMainDisplayID(), rampsize, table_red, table_green, table_blue, &actualsize) != CGDisplayNoErr) { Con_Print("VID_GetGamma: ERROR: CGGetDisplayTransferByTable failed!\n"); return false; } if (actualsize != (unsigned int)rampsize) { Con_Printf("VID_GetGamma: ERROR: invalid gamma table size (%u != %u)\n", actualsize, rampsize); return false; } // Convert the 3 float tables into 1 unsigned short table for (i = 0; i < rampsize; i++) ramps[i] = table_red[i] * 65535.0f; for (i = 0; i < rampsize; i++) ramps[i + rampsize] = table_green[i] * 65535.0f; for (i = 0; i < rampsize; i++) ramps[i + 2 * rampsize] = table_blue[i] * 65535.0f; return true; } void signal_handler(int sig) { Sys_PrintfToTerminal("Received signal %d, exiting...\n", sig); VID_RestoreSystemGamma(); Sys_Quit(1); } void InitSig(void) { signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGILL, signal_handler); signal(SIGTRAP, signal_handler); signal(SIGIOT, signal_handler); signal(SIGBUS, signal_handler); signal(SIGFPE, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGTERM, signal_handler); } void VID_Init(void) { InitSig(); // trap evil signals Cvar_RegisterVariable(&apple_multithreadedgl); Cvar_RegisterVariable(&apple_mouse_noaccel); // COMMANDLINEOPTION: Input: -nomouse disables mouse support (see also vid_mouse cvar) if (COM_CheckParm ("-nomouse")) mouse_avail = false; } static void *prjobj = NULL; static void *cglobj = NULL; static void GL_CloseLibrary(void) { if (cglobj) dlclose(cglobj); cglobj = NULL; if (prjobj) dlclose(prjobj); prjobj = NULL; gl_driver[0] = 0; gl_extensions = ""; gl_platform = ""; gl_platformextensions = ""; } static int GL_OpenLibrary(void) { const char *name = "/System/Library/Frameworks/AGL.framework/AGL"; const char *name2 = "/System/Library/Frameworks/OpenGL.framework/OpenGL"; Con_Printf("Loading OpenGL driver %s\n", name); GL_CloseLibrary(); if (!(prjobj = dlopen(name, RTLD_LAZY))) { Con_Printf("Unable to open symbol list for %s\n", name); return false; } strlcpy(gl_driver, name, sizeof(gl_driver)); Con_Printf("Loading OpenGL driver %s\n", name2); if (!(cglobj = dlopen(name2, RTLD_LAZY))) Con_Printf("Unable to open symbol list for %s; multithreaded GL disabled\n", name); return true; } void *GL_GetProcAddress(const char *name) { return dlsym(prjobj, name); } static void *CGL_GetProcAddress(const char *name) { if(!cglobj) return NULL; return dlsym(cglobj, name); } void VID_Shutdown(void) { if (context == NULL && window == NULL) return; VID_EnableJoystick(false); VID_SetMouse(false, false, false); VID_RestoreSystemGamma(); if (context != NULL) { qaglDestroyContext(context); context = NULL; } if (vid_isfullscreen) CGReleaseAllDisplays(); if (window != NULL) { DisposeWindow(window); window = NULL; } vid_hidden = true; vid_isfullscreen = false; GL_CloseLibrary(); Key_ClearStates (); } // Since the event handler can be called at any time, we store the events for later processing static qboolean AsyncEvent_Quitting = false; static qboolean AsyncEvent_Collapsed = false; static OSStatus MainWindowEventHandler (EventHandlerCallRef nextHandler, EventRef event, void *userData) { OSStatus err = noErr; switch (GetEventKind (event)) { case kEventWindowClosed: AsyncEvent_Quitting = true; break; // Docked (start) case kEventWindowCollapsing: AsyncEvent_Collapsed = true; break; // Undocked / restored (end) case kEventWindowExpanded: AsyncEvent_Collapsed = false; break; default: err = eventNotHandledErr; break; } return err; } static void VID_AppFocusChanged(qboolean windowIsActive) { if (vid_activewindow != windowIsActive) { vid_activewindow = windowIsActive; if (!vid_activewindow) VID_RestoreSystemGamma(); } if (windowIsActive || !snd_mutewhenidle.integer) { if (!sound_active) { S_UnblockSound (); sound_active = true; } } else { if (sound_active) { S_BlockSound (); sound_active = false; } } } static void VID_ProcessPendingAsyncEvents (void) { // Collapsed / expanded if (AsyncEvent_Collapsed != vid_hidden) { vid_hidden = !vid_hidden; VID_AppFocusChanged(!vid_hidden); } // Closed if (AsyncEvent_Quitting) Sys_Quit(0); } static void VID_BuildAGLAttrib(GLint *attrib, qboolean stencil, qboolean fullscreen, qboolean stereobuffer, int samples) { *attrib++ = AGL_RGBA; *attrib++ = AGL_RED_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = AGL_GREEN_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = AGL_BLUE_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = AGL_DOUBLEBUFFER; *attrib++ = AGL_DEPTH_SIZE;*attrib++ = stencil ? 24 : 16; // if stencil is enabled, ask for alpha too if (stencil) { *attrib++ = AGL_STENCIL_SIZE;*attrib++ = 8; *attrib++ = AGL_ALPHA_SIZE;*attrib++ = 8; } if (fullscreen) *attrib++ = AGL_FULLSCREEN; if (stereobuffer) *attrib++ = AGL_STEREO; #ifdef AGL_SAMPLE_BUFFERS_ARB #ifdef AGL_SAMPLES_ARB if (samples > 1) { *attrib++ = AGL_SAMPLE_BUFFERS_ARB; *attrib++ = 1; *attrib++ = AGL_SAMPLES_ARB; *attrib++ = samples; } #endif #endif *attrib++ = AGL_NONE; } qboolean VID_InitMode(viddef_mode_t *mode) { const EventTypeSpec winEvents[] = { { kEventClassWindow, kEventWindowClosed }, { kEventClassWindow, kEventWindowCollapsing }, { kEventClassWindow, kEventWindowExpanded }, }; OSStatus carbonError; Rect windowBounds; CFStringRef windowTitle; AGLPixelFormat pixelFormat; GLint attributes [32]; GLenum error; if (!GL_OpenLibrary()) { Con_Printf("Unable to load GL driver\n"); return false; } if ((qaglChoosePixelFormat = (AGLPixelFormat (*) (const AGLDevice *gdevs, GLint ndev, const GLint *attribList))GL_GetProcAddress("aglChoosePixelFormat")) == NULL || (qaglCreateContext = (AGLContext (*) (AGLPixelFormat pix, AGLContext share))GL_GetProcAddress("aglCreateContext")) == NULL || (qaglDestroyContext = (GLboolean (*) (AGLContext ctx))GL_GetProcAddress("aglDestroyContext")) == NULL || (qaglDestroyPixelFormat = (void (*) (AGLPixelFormat pix))GL_GetProcAddress("aglDestroyPixelFormat")) == NULL || (qaglErrorString = (const GLubyte* (*) (GLenum code))GL_GetProcAddress("aglErrorString")) == NULL || (qaglGetError = (GLenum (*) (void))GL_GetProcAddress("aglGetError")) == NULL || (qaglSetCurrentContext = (GLboolean (*) (AGLContext ctx))GL_GetProcAddress("aglSetCurrentContext")) == NULL || (qaglSetDrawable = (GLboolean (*) (AGLContext ctx, AGLDrawable draw))GL_GetProcAddress("aglSetDrawable")) == NULL || (qaglSetFullScreen = (GLboolean (*) (AGLContext ctx, GLsizei width, GLsizei height, GLsizei freq, GLint device))GL_GetProcAddress("aglSetFullScreen")) == NULL || (qaglSetInteger = (GLboolean (*) (AGLContext ctx, GLenum pname, const GLint *params))GL_GetProcAddress("aglSetInteger")) == NULL || (qaglSwapBuffers = (void (*) (AGLContext ctx))GL_GetProcAddress("aglSwapBuffers")) == NULL ) { Con_Printf("AGL functions not found\n"); ReleaseWindow(window); return false; } qCGLEnable = (CGLError (*) (CGLContextObj ctx, CGLContextEnable pname)) CGL_GetProcAddress("CGLEnable"); qCGLDisable = (CGLError (*) (CGLContextObj ctx, CGLContextEnable pname)) CGL_GetProcAddress("CGLDisable"); qCGLGetCurrentContext = (CGLContextObj (*) (void)) CGL_GetProcAddress("CGLGetCurrentContext"); if(!qCGLEnable || !qCGLDisable || !qCGLGetCurrentContext) Con_Printf("CGL functions not found; disabling multithreaded OpenGL\n"); // Ignore the events from the previous window AsyncEvent_Quitting = false; AsyncEvent_Collapsed = false; // Create the window, a bit towards the center of the screen windowBounds.left = 100; windowBounds.top = 100; windowBounds.right = mode->width + 100; windowBounds.bottom = mode->height + 100; carbonError = CreateNewWindow(kDocumentWindowClass, kWindowStandardFloatingAttributes | kWindowStandardHandlerAttribute, &windowBounds, &window); if (carbonError != noErr || window == NULL) { Con_Printf("Unable to create window (error %u)\n", (unsigned)carbonError); return false; } // Set the window title windowTitle = CFSTR("DarkPlaces AGL"); SetWindowTitleWithCFString(window, windowTitle); // Install the callback function for the window events we can't get // through ReceiveNextEvent (i.e. close, collapse, and expand) InstallWindowEventHandler (window, NewEventHandlerUPP (MainWindowEventHandler), GetEventTypeCount(winEvents), winEvents, window, NULL); // Create the desired attribute list VID_BuildAGLAttrib(attributes, mode->bitsperpixel == 32, mode->fullscreen, mode->stereobuffer, mode->samples); if (!mode->fullscreen) { // Output to Window pixelFormat = qaglChoosePixelFormat(NULL, 0, attributes); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglChoosePixelFormat FAILED: %s\n", (char *)qaglErrorString(error)); ReleaseWindow(window); return false; } } else // Output is fullScreen { CGDirectDisplayID mainDisplay; CFDictionaryRef refDisplayMode; GDHandle gdhDisplay; // Get the mainDisplay and set resolution to current mainDisplay = CGMainDisplayID(); CGDisplayCapture(mainDisplay); // TOCHECK: not sure whether or not it's necessary to change the resolution // "by hand", or if aglSetFullscreen does the job anyway refDisplayMode = CGDisplayBestModeForParametersAndRefreshRateWithProperty(mainDisplay, mode->bitsperpixel, mode->width, mode->height, mode->refreshrate, kCGDisplayModeIsSafeForHardware, NULL); CGDisplaySwitchToMode(mainDisplay, refDisplayMode); DMGetGDeviceByDisplayID((DisplayIDType)mainDisplay, &gdhDisplay, false); // Set pixel format with built attribs // Note: specifying a device is *required* for AGL_FullScreen pixelFormat = qaglChoosePixelFormat(&gdhDisplay, 1, attributes); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglChoosePixelFormat FAILED: %s\n", (char *)qaglErrorString(error)); ReleaseWindow(window); return false; } } // Create a context using the pform context = qaglCreateContext(pixelFormat, NULL); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglCreateContext FAILED: %s\n", (char *)qaglErrorString(error)); } // Make the context the current one ('enable' it) qaglSetCurrentContext(context); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglSetCurrentContext FAILED: %s\n", (char *)qaglErrorString(error)); ReleaseWindow(window); return false; } // Discard pform qaglDestroyPixelFormat(pixelFormat); // Attempt fullscreen if requested if (mode->fullscreen) { qaglSetFullScreen (context, mode->width, mode->height, mode->refreshrate, 0); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglSetFullScreen FAILED: %s\n", (char *)qaglErrorString(error)); return false; } } else { // Set Window as Drawable qaglSetDrawable(context, GetWindowPort(window)); error = qaglGetError(); if (error != AGL_NO_ERROR) { Con_Printf("qaglSetDrawable FAILED: %s\n", (char *)qaglErrorString(error)); ReleaseWindow(window); return false; } } if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) Sys_Error("glGetString not found in %s", gl_driver); gl_platformextensions = ""; gl_platform = "AGL"; multithreadedgl = false; vid_isfullscreen = mode->fullscreen; vid_usingmouse = false; vid_usinghidecursor = false; vid_hidden = false; vid_activewindow = true; sound_active = true; GL_Init(); SelectWindow(window); ShowWindow(window); return true; } static void Handle_KeyMod(UInt32 keymod) { const struct keymod_to_event_s { UInt32 keybit; keynum_t event; } keymod_events [] = { { cmdKey, K_AUX1 }, { shiftKey, K_SHIFT }, { alphaLock, K_CAPSLOCK }, { optionKey, K_ALT }, { controlKey, K_CTRL }, { kEventKeyModifierNumLockMask, K_NUMLOCK }, { kEventKeyModifierFnMask, K_AUX2 } }; static UInt32 prev_keymod = 0; unsigned int i; UInt32 modChanges; modChanges = prev_keymod ^ keymod; if (modChanges == 0) return; for (i = 0; i < sizeof(keymod_events) / sizeof(keymod_events[0]); i++) { UInt32 keybit = keymod_events[i].keybit; if ((modChanges & keybit) != 0) Key_Event(keymod_events[i].event, '\0', (keymod & keybit) != 0); } prev_keymod = keymod; } static void Handle_Key(unsigned char charcode, UInt32 mackeycode, qboolean keypressed) { unsigned int keycode = 0; char ascii = '\0'; switch (mackeycode) { case MK_ESCAPE: keycode = K_ESCAPE; break; case MK_F1: keycode = K_F1; break; case MK_F2: keycode = K_F2; break; case MK_F3: keycode = K_F3; break; case MK_F4: keycode = K_F4; break; case MK_F5: keycode = K_F5; break; case MK_F6: keycode = K_F6; break; case MK_F7: keycode = K_F7; break; case MK_F8: keycode = K_F8; break; case MK_F9: keycode = K_F9; break; case MK_F10: keycode = K_F10; break; case MK_F11: keycode = K_F11; break; case MK_F12: keycode = K_F12; break; case MK_SCROLLOCK: keycode = K_SCROLLOCK; break; case MK_PAUSE: keycode = K_PAUSE; break; case MK_BACKSPACE: keycode = K_BACKSPACE; break; case MK_INSERT: keycode = K_INS; break; case MK_HOME: keycode = K_HOME; break; case MK_PAGEUP: keycode = K_PGUP; break; case MK_NUMLOCK: keycode = K_NUMLOCK; break; case MK_KP_EQUALS: keycode = K_KP_EQUALS; break; case MK_KP_DIVIDE: keycode = K_KP_DIVIDE; break; case MK_KP_MULTIPLY: keycode = K_KP_MULTIPLY; break; case MK_TAB: keycode = K_TAB; break; case MK_DELETE: keycode = K_DEL; break; case MK_END: keycode = K_END; break; case MK_PAGEDOWN: keycode = K_PGDN; break; case MK_KP7: keycode = K_KP_7; break; case MK_KP8: keycode = K_KP_8; break; case MK_KP9: keycode = K_KP_9; break; case MK_KP_MINUS: keycode = K_KP_MINUS; break; case MK_CAPSLOCK: keycode = K_CAPSLOCK; break; case MK_RETURN: keycode = K_ENTER; break; case MK_KP4: keycode = K_KP_4; break; case MK_KP5: keycode = K_KP_5; break; case MK_KP6: keycode = K_KP_6; break; case MK_KP_PLUS: keycode = K_KP_PLUS; break; case MK_KP1: keycode = K_KP_1; break; case MK_KP2: keycode = K_KP_2; break; case MK_KP3: keycode = K_KP_3; break; case MK_KP_ENTER: case MK_IBOOK_ENTER: keycode = K_KP_ENTER; break; case MK_KP0: keycode = K_KP_0; break; case MK_KP_PERIOD: keycode = K_KP_PERIOD; break; default: switch(charcode) { case kUpArrowCharCode: keycode = K_UPARROW; break; case kLeftArrowCharCode: keycode = K_LEFTARROW; break; case kDownArrowCharCode: keycode = K_DOWNARROW; break; case kRightArrowCharCode: keycode = K_RIGHTARROW; break; case 0: case 191: // characters 0 and 191 are sent by the mouse buttons (?!) break; default: if ('A' <= charcode && charcode <= 'Z') { keycode = charcode + ('a' - 'A'); // lowercase it ascii = charcode; } else if (charcode >= 32) { keycode = charcode; ascii = charcode; } else Con_DPrintf(">> UNKNOWN char/keycode: %d/%u <<\n", charcode, (unsigned) mackeycode); } } if (keycode != 0) Key_Event(keycode, ascii, keypressed); } void Sys_SendKeyEvents(void) { EventRef theEvent; EventTargetRef theTarget; // Start by processing the asynchronous events we received since the previous frame VID_ProcessPendingAsyncEvents(); theTarget = GetEventDispatcherTarget(); while (ReceiveNextEvent(0, NULL, kEventDurationNoWait, true, &theEvent) == noErr) { UInt32 eventClass = GetEventClass(theEvent); UInt32 eventKind = GetEventKind(theEvent); switch (eventClass) { case kEventClassMouse: { EventMouseButton theButton; int key; switch (eventKind) { case kEventMouseDown: case kEventMouseUp: GetEventParameter(theEvent, kEventParamMouseButton, typeMouseButton, NULL, sizeof(theButton), NULL, &theButton); switch (theButton) { default: case kEventMouseButtonPrimary: key = K_MOUSE1; break; case kEventMouseButtonSecondary: key = K_MOUSE2; break; case kEventMouseButtonTertiary: key = K_MOUSE3; break; } Key_Event(key, '\0', eventKind == kEventMouseDown); break; // Note: These two events are mutual exclusives // Treat MouseDragged in the same statement, so we don't block MouseMoved while a mousebutton is held case kEventMouseMoved: case kEventMouseDragged: { HIPoint deltaPos; HIPoint windowPos; GetEventParameter(theEvent, kEventParamMouseDelta, typeHIPoint, NULL, sizeof(deltaPos), NULL, &deltaPos); GetEventParameter(theEvent, kEventParamWindowMouseLocation, typeHIPoint, NULL, sizeof(windowPos), NULL, &windowPos); if (vid_usingmouse) { in_mouse_x += deltaPos.x; in_mouse_y += deltaPos.y; } in_windowmouse_x = windowPos.x; in_windowmouse_y = windowPos.y; break; } case kEventMouseWheelMoved: { SInt32 delta; unsigned int wheelEvent; GetEventParameter(theEvent, kEventParamMouseWheelDelta, typeSInt32, NULL, sizeof(delta), NULL, &delta); wheelEvent = (delta > 0) ? K_MWHEELUP : K_MWHEELDOWN; Key_Event(wheelEvent, 0, true); Key_Event(wheelEvent, 0, false); break; } default: Con_Printf (">> kEventClassMouse (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); break; } } case kEventClassKeyboard: { char charcode; UInt32 keycode; switch (eventKind) { case kEventRawKeyDown: GetEventParameter(theEvent, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(charcode), NULL, &charcode); GetEventParameter(theEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(keycode), NULL, &keycode); Handle_Key(charcode, keycode, true); break; case kEventRawKeyRepeat: break; case kEventRawKeyUp: GetEventParameter(theEvent, kEventParamKeyMacCharCodes, typeChar, NULL, sizeof(charcode), NULL, &charcode); GetEventParameter(theEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof(keycode), NULL, &keycode); Handle_Key(charcode, keycode, false); break; case kEventRawKeyModifiersChanged: { UInt32 keymod = 0; GetEventParameter(theEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(keymod), NULL, &keymod); Handle_KeyMod(keymod); break; } case kEventHotKeyPressed: break; case kEventHotKeyReleased: break; case kEventMouseWheelMoved: break; default: Con_Printf (">> kEventClassKeyboard (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); break; } break; } case kEventClassTextInput: Con_Printf(">> kEventClassTextInput (%d) <<\n", (int)eventKind); break; case kEventClassApplication: switch (eventKind) { case kEventAppActivated : VID_AppFocusChanged(true); break; case kEventAppDeactivated: VID_AppFocusChanged(false); break; case kEventAppQuit: Sys_Quit(0); break; case kEventAppActiveWindowChanged: break; default: Con_Printf(">> kEventClassApplication (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); break; } break; case kEventClassAppleEvent: switch (eventKind) { case kEventAppleEvent : break; default: Con_Printf(">> kEventClassAppleEvent (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); break; } break; case kEventClassWindow: switch (eventKind) { case kEventWindowUpdate : break; default: Con_Printf(">> kEventClassWindow (UNKNOWN eventKind: %u) <<\n", (unsigned)eventKind); break; } break; case kEventClassControl: break; default: /*Con_Printf(">> UNKNOWN eventClass: %c%c%c%c, eventKind: %d <<\n", eventClass >> 24, (eventClass >> 16) & 0xFF, (eventClass >> 8) & 0xFF, eventClass & 0xFF, eventKind);*/ break; } SendEventToEventTarget (theEvent, theTarget); ReleaseEvent(theEvent); } } void VID_BuildJoyState(vid_joystate_t *joystate) { VID_Shared_BuildJoyState_Begin(joystate); VID_Shared_BuildJoyState_Finish(joystate); } void VID_EnableJoystick(qboolean enable) { int index = joy_enable.integer > 0 ? joy_index.integer : -1; qboolean success = false; int sharedcount = 0; sharedcount = VID_Shared_SetJoystick(index); if (index >= 0 && index < sharedcount) success = true; // update cvar containing count of XInput joysticks if (joy_detected.integer != sharedcount) Cvar_SetValueQuick(&joy_detected, sharedcount); Cvar_SetValueQuick(&joy_active, success ? 1 : 0); } void IN_Move (void) { vid_joystate_t joystate; VID_EnableJoystick(true); VID_BuildJoyState(&joystate); VID_ApplyJoyState(&joystate); } static bool GetDictionaryBoolean(CFDictionaryRef d, const void *key) { CFBooleanRef ref = (CFBooleanRef) CFDictionaryGetValue(d, key); if(ref) return CFBooleanGetValue(ref); return false; } long GetDictionaryLong(CFDictionaryRef d, const void *key) { long value = 0; CFNumberRef ref = (CFNumberRef) CFDictionaryGetValue(d, key); if(ref) CFNumberGetValue(ref, kCFNumberLongType, &value); return value; } vid_mode_t *VID_GetDesktopMode(void) { return NULL; // FIXME add desktopfullscreen } size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) { CGDirectDisplayID mainDisplay = CGMainDisplayID(); CFArrayRef vidmodes = CGDisplayAvailableModes(mainDisplay); CFDictionaryRef thismode; unsigned int n = CFArrayGetCount(vidmodes); unsigned int i; size_t k; k = 0; for(i = 0; i < n; ++i) { thismode = (CFDictionaryRef) CFArrayGetValueAtIndex(vidmodes, i); if(!GetDictionaryBoolean(thismode, kCGDisplayModeIsSafeForHardware)) continue; if(k >= maxcount) break; modes[k].width = GetDictionaryLong(thismode, kCGDisplayWidth); modes[k].height = GetDictionaryLong(thismode, kCGDisplayHeight); modes[k].bpp = GetDictionaryLong(thismode, kCGDisplayBitsPerPixel); modes[k].refreshrate = GetDictionaryLong(thismode, kCGDisplayRefreshRate); modes[k].pixelheight_num = 1; modes[k].pixelheight_denom = 1; // OS X doesn't expose this either ++k; } return k; } darkplaces/gl_rsurf.c0000664000175000017500000017363013067716220014164 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_surf.c: surface-related refresh code #include "quakedef.h" #include "r_shadow.h" #include "portals.h" #include "csprogs.h" #include "image.h" cvar_t r_ambient = {0, "r_ambient", "0", "brightens map, value is 0-128"}; cvar_t r_lockpvs = {0, "r_lockpvs", "0", "disables pvs switching, allows you to walk around and inspect what is visible from a given location in the map (anything not visible from your current location will not be drawn)"}; cvar_t r_lockvisibility = {0, "r_lockvisibility", "0", "disables visibility updates, allows you to walk around and inspect what is visible from a given viewpoint in the map (anything offscreen at the moment this is enabled will not be drawn)"}; cvar_t r_useportalculling = {0, "r_useportalculling", "2", "improve framerate with r_novis 1 by using portal culling - still not as good as compiled visibility data in the map, but it helps (a value of 2 forces use of this even with vis data, which improves framerates in maps without too much complexity, but hurts in extremely complex maps, which is why 2 is not the default mode)"}; cvar_t r_usesurfaceculling = {0, "r_usesurfaceculling", "1", "skip off-screen surfaces (1 = cull surfaces if the map is likely to benefit, 2 = always cull surfaces)"}; cvar_t r_q3bsp_renderskydepth = {0, "r_q3bsp_renderskydepth", "0", "draws sky depth masking in q3 maps (as in q1 maps), this means for example that sky polygons can hide other things"}; /* =============== R_BuildLightMap Combine and scale multiple lightmaps into the 8.8 format in blocklights =============== */ void R_BuildLightMap (const entity_render_t *ent, msurface_t *surface) { int smax, tmax, i, size, size3, maps, l; int *bl, scale; unsigned char *lightmap, *out, *stain; dp_model_t *model = ent->model; int *intblocklights; unsigned char *templight; smax = (surface->lightmapinfo->extents[0]>>4)+1; tmax = (surface->lightmapinfo->extents[1]>>4)+1; size = smax*tmax; size3 = size*3; r_refdef.stats[r_stat_lightmapupdatepixels] += size; r_refdef.stats[r_stat_lightmapupdates]++; if (cl.buildlightmapmemorysize < size*sizeof(int[3])) { cl.buildlightmapmemorysize = size*sizeof(int[3]); if (cl.buildlightmapmemory) Mem_Free(cl.buildlightmapmemory); cl.buildlightmapmemory = (unsigned char *) Mem_Alloc(cls.levelmempool, cl.buildlightmapmemorysize); } // these both point at the same buffer, templight is only used for final // processing and can replace the intblocklights data as it goes intblocklights = (int *)cl.buildlightmapmemory; templight = (unsigned char *)cl.buildlightmapmemory; // update cached lighting info model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = false; lightmap = surface->lightmapinfo->samples; // set to full bright if no light data bl = intblocklights; if (!model->brushq1.lightdata) { for (i = 0;i < size3;i++) bl[i] = 128*256; } else { // clear to no light memset(bl, 0, size3*sizeof(*bl)); // add all the lightmaps if (lightmap) for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3) for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size3;i++) bl[i] += lightmap[i] * scale; } stain = surface->lightmapinfo->stainsamples; bl = intblocklights; out = templight; // the >> 16 shift adjusts down 8 bits to account for the stainmap // scaling, and remaps the 0-65536 (2x overbright) to 0-256, it will // be doubled during rendering to achieve 2x overbright // (0 = 0.0, 128 = 1.0, 256 = 2.0) if (stain) { for (i = 0;i < size;i++, bl += 3, stain += 3, out += 4) { l = (bl[0] * stain[0]) >> 16;out[2] = min(l, 255); l = (bl[1] * stain[1]) >> 16;out[1] = min(l, 255); l = (bl[2] * stain[2]) >> 16;out[0] = min(l, 255); out[3] = 255; } } else { for (i = 0;i < size;i++, bl += 3, out += 4) { l = bl[0] >> 8;out[2] = min(l, 255); l = bl[1] >> 8;out[1] = min(l, 255); l = bl[2] >> 8;out[0] = min(l, 255); out[3] = 255; } } if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) Image_MakesRGBColorsFromLinear_Lightmap(templight, templight, size); R_UpdateTexture(surface->lightmaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); // update the surface's deluxemap if it has one if (surface->deluxemaptexture != r_texture_blanknormalmap) { vec3_t n; unsigned char *normalmap = surface->lightmapinfo->nmapsamples; lightmap = surface->lightmapinfo->samples; // clear to no normalmap bl = intblocklights; memset(bl, 0, size3*sizeof(*bl)); // add all the normalmaps if (lightmap && normalmap) { for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++, lightmap += size3, normalmap += size3) { for (scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[maps]], i = 0;i < size;i++) { // add the normalmap with weighting proportional to the style's lightmap intensity l = (int)(VectorLength(lightmap + i*3) * scale); bl[i*3+0] += ((int)normalmap[i*3+0] - 128) * l; bl[i*3+1] += ((int)normalmap[i*3+1] - 128) * l; bl[i*3+2] += ((int)normalmap[i*3+2] - 128) * l; } } } bl = intblocklights; out = templight; // we simply renormalize the weighted normals to get a valid deluxemap for (i = 0;i < size;i++, bl += 3, out += 4) { VectorCopy(bl, n); VectorNormalize(n); l = (int)(n[0] * 128 + 128);out[2] = bound(0, l, 255); l = (int)(n[1] * 128 + 128);out[1] = bound(0, l, 255); l = (int)(n[2] * 128 + 128);out[0] = bound(0, l, 255); out[3] = 255; } R_UpdateTexture(surface->deluxemaptexture, templight, surface->lightmapinfo->lightmaporigin[0], surface->lightmapinfo->lightmaporigin[1], 0, smax, tmax, 1); } } static void R_StainNode (mnode_t *node, dp_model_t *model, const vec3_t origin, float radius, const float fcolor[8]) { float ndist, a, ratio, maxdist, maxdist2, maxdist3, invradius, sdtable[256], td, dist2; msurface_t *surface, *endsurface; int i, s, t, smax, tmax, smax3, impacts, impactt, stained; unsigned char *bl; vec3_t impact; maxdist = radius * radius; invradius = 1.0f / radius; loc0: if (!node->plane) return; ndist = PlaneDiff(origin, node->plane); if (ndist > radius) { node = node->children[0]; goto loc0; } if (ndist < -radius) { node = node->children[1]; goto loc0; } dist2 = ndist * ndist; maxdist3 = maxdist - dist2; if (node->plane->type < 3) { VectorCopy(origin, impact); impact[node->plane->type] -= ndist; } else { impact[0] = origin[0] - node->plane->normal[0] * ndist; impact[1] = origin[1] - node->plane->normal[1] * ndist; impact[2] = origin[2] - node->plane->normal[2] * ndist; } for (surface = model->data_surfaces + node->firstsurface, endsurface = surface + node->numsurfaces;surface < endsurface;surface++) { if (surface->lightmapinfo->stainsamples) { smax = (surface->lightmapinfo->extents[0] >> 4) + 1; tmax = (surface->lightmapinfo->extents[1] >> 4) + 1; impacts = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3] - surface->lightmapinfo->texturemins[0]); impactt = (int)(DotProduct (impact, surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3] - surface->lightmapinfo->texturemins[1]); s = bound(0, impacts, smax * 16) - impacts; t = bound(0, impactt, tmax * 16) - impactt; i = (int)(s * s + t * t + dist2); if ((i > maxdist) || (smax > (int)(sizeof(sdtable)/sizeof(sdtable[0])))) // smax overflow fix from Andreas Dehmel continue; // reduce calculations for (s = 0, i = impacts; s < smax; s++, i -= 16) sdtable[s] = i * i + dist2; bl = surface->lightmapinfo->stainsamples; smax3 = smax * 3; stained = false; i = impactt; for (t = 0;t < tmax;t++, i -= 16) { td = i * i; // make sure some part of it is visible on this line if (td < maxdist3) { maxdist2 = maxdist - td; for (s = 0;s < smax;s++) { if (sdtable[s] < maxdist2) { ratio = lhrandom(0.0f, 1.0f); a = (fcolor[3] + ratio * fcolor[7]) * (1.0f - sqrt(sdtable[s] + td) * invradius); if (a >= (1.0f / 64.0f)) { if (a > 1) a = 1; bl[0] = (unsigned char) ((float) bl[0] + a * ((fcolor[0] + ratio * fcolor[4]) - (float) bl[0])); bl[1] = (unsigned char) ((float) bl[1] + a * ((fcolor[1] + ratio * fcolor[5]) - (float) bl[1])); bl[2] = (unsigned char) ((float) bl[2] + a * ((fcolor[2] + ratio * fcolor[6]) - (float) bl[2])); stained = true; } } bl += 3; } } else // skip line bl += smax3; } // force lightmap upload if (stained) model->brushq1.lightmapupdateflags[surface - model->data_surfaces] = true; } } if (node->children[0]->plane) { if (node->children[1]->plane) { R_StainNode(node->children[0], model, origin, radius, fcolor); node = node->children[1]; goto loc0; } else { node = node->children[0]; goto loc0; } } else if (node->children[1]->plane) { node = node->children[1]; goto loc0; } } void R_Stain (const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2) { int n; float fcolor[8]; entity_render_t *ent; dp_model_t *model; vec3_t org; if (r_refdef.scene.worldmodel == NULL || !r_refdef.scene.worldmodel->brush.data_nodes || !r_refdef.scene.worldmodel->brushq1.lightdata) return; fcolor[0] = cr1; fcolor[1] = cg1; fcolor[2] = cb1; fcolor[3] = ca1 * (1.0f / 64.0f); fcolor[4] = cr2 - cr1; fcolor[5] = cg2 - cg1; fcolor[6] = cb2 - cb1; fcolor[7] = (ca2 - ca1) * (1.0f / 64.0f); R_StainNode(r_refdef.scene.worldmodel->brush.data_nodes + r_refdef.scene.worldmodel->brushq1.hulls[0].firstclipnode, r_refdef.scene.worldmodel, origin, radius, fcolor); // look for embedded bmodels for (n = 0;n < cl.num_brushmodel_entities;n++) { ent = &cl.entities[cl.brushmodel_entities[n]].render; model = ent->model; if (model && model->name[0] == '*') { if (model->brush.data_nodes) { Matrix4x4_Transform(&ent->inversematrix, origin, org); R_StainNode(model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, model, org, radius, fcolor); } } } } /* ============================================================= BRUSH MODELS ============================================================= */ static void R_DrawPortal_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { // due to the hacky nature of this function's parameters, this is never // called with a batch, so numsurfaces is always 1, and the surfacelist // contains only a leaf number for coloring purposes const mportal_t *portal = (mportal_t *)ent; qboolean isvis; int i, numpoints; float *v; float vertex3f[POLYGONELEMENTS_MAXPOINTS*3]; CHECKGLERROR GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); GL_DepthTest(true); GL_CullFace(GL_NONE); R_EntityMatrix(&identitymatrix); numpoints = min(portal->numpoints, POLYGONELEMENTS_MAXPOINTS); // R_Mesh_ResetTextureState(); isvis = (portal->here->clusterindex >= 0 && portal->past->clusterindex >= 0 && portal->here->clusterindex != portal->past->clusterindex); i = surfacelist[0] >> 1; GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, isvis ? 0.125f : 0.03125f); for (i = 0, v = vertex3f;i < numpoints;i++, v += 3) VectorCopy(portal->points[i].position, v); R_Mesh_PrepareVertices_Generic_Arrays(numpoints, vertex3f, NULL, NULL); R_SetupShader_Generic_NoTexture(false, false); R_Mesh_Draw(0, numpoints, 0, numpoints - 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } // LordHavoc: this is just a nice debugging tool, very slow void R_DrawPortals(void) { int i, leafnum; mportal_t *portal; float center[3], f; dp_model_t *model = r_refdef.scene.worldmodel; if (model == NULL) return; for (leafnum = 0;leafnum < r_refdef.scene.worldmodel->brush.num_leafs;leafnum++) { if (r_refdef.viewcache.world_leafvisible[leafnum]) { //for (portalnum = 0, portal = model->brush.data_portals;portalnum < model->brush.num_portals;portalnum++, portal++) for (portal = r_refdef.scene.worldmodel->brush.data_leafs[leafnum].portals;portal;portal = portal->next) { if (portal->numpoints <= POLYGONELEMENTS_MAXPOINTS) if (!R_CullBox(portal->mins, portal->maxs)) { VectorClear(center); for (i = 0;i < portal->numpoints;i++) VectorAdd(center, portal->points[i].position, center); f = ixtable[portal->numpoints]; VectorScale(center, f, center); R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawPortal_Callback, (entity_render_t *)portal, leafnum, rsurface.rtlight); } } } } } static void R_View_WorldVisibility_CullSurfaces(void) { int surfaceindex; int surfaceindexstart; int surfaceindexend; unsigned char *surfacevisible; msurface_t *surfaces; dp_model_t *model = r_refdef.scene.worldmodel; if (!model) return; if (r_trippy.integer) return; if (r_usesurfaceculling.integer < 1) return; surfaceindexstart = model->firstmodelsurface; surfaceindexend = surfaceindexstart + model->nummodelsurfaces; surfaces = model->data_surfaces; surfacevisible = r_refdef.viewcache.world_surfacevisible; for (surfaceindex = surfaceindexstart;surfaceindex < surfaceindexend;surfaceindex++) if (surfacevisible[surfaceindex] && R_CullBox(surfaces[surfaceindex].mins, surfaces[surfaceindex].maxs)) surfacevisible[surfaceindex] = 0; } void R_View_WorldVisibility(qboolean forcenovis) { int i, j, *mark; mleaf_t *leaf; mleaf_t *viewleaf; dp_model_t *model = r_refdef.scene.worldmodel; if (!model) return; if (r_refdef.view.usecustompvs) { // clear the visible surface and leaf flags arrays memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); r_refdef.viewcache.world_novis = false; // simply cull each marked leaf to the frustum (view pyramid) for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) { // if leaf is in current pvs and on the screen, mark its surfaces if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) { r_refdef.stats[r_stat_world_leafs]++; r_refdef.viewcache.world_leafvisible[j] = true; if (leaf->numleafsurfaces) for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) r_refdef.viewcache.world_surfacevisible[*mark] = true; } } R_View_WorldVisibility_CullSurfaces(); return; } // if possible find the leaf the view origin is in viewleaf = model->brush.PointInLeaf ? model->brush.PointInLeaf(model, r_refdef.view.origin) : NULL; // if possible fetch the visible cluster bits if (!r_lockpvs.integer && model->brush.FatPVS) model->brush.FatPVS(model, r_refdef.view.origin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); if (!r_lockvisibility.integer) { // clear the visible surface and leaf flags arrays memset(r_refdef.viewcache.world_surfacevisible, 0, model->num_surfaces); memset(r_refdef.viewcache.world_leafvisible, 0, model->brush.num_leafs); r_refdef.viewcache.world_novis = false; // if floating around in the void (no pvs data available, and no // portals available), simply use all on-screen leafs. if (!viewleaf || viewleaf->clusterindex < 0 || forcenovis || r_trippy.integer) { // no visibility method: (used when floating around in the void) // simply cull each leaf to the frustum (view pyramid) // similar to quake's RecursiveWorldNode but without cache misses r_refdef.viewcache.world_novis = true; for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) { if (leaf->clusterindex < 0) continue; // if leaf is in current pvs and on the screen, mark its surfaces if (!R_CullBox(leaf->mins, leaf->maxs)) { r_refdef.stats[r_stat_world_leafs]++; r_refdef.viewcache.world_leafvisible[j] = true; if (leaf->numleafsurfaces) for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) r_refdef.viewcache.world_surfacevisible[*mark] = true; } } } // just check if each leaf in the PVS is on screen // (unless portal culling is enabled) else if (!model->brush.data_portals || r_useportalculling.integer < 1 || (r_useportalculling.integer < 2 && !r_novis.integer)) { // pvs method: // simply check if each leaf is in the Potentially Visible Set, // and cull to frustum (view pyramid) // similar to quake's RecursiveWorldNode but without cache misses for (j = 0, leaf = model->brush.data_leafs;j < model->brush.num_leafs;j++, leaf++) { if (leaf->clusterindex < 0) continue; // if leaf is in current pvs and on the screen, mark its surfaces if (CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex) && !R_CullBox(leaf->mins, leaf->maxs)) { r_refdef.stats[r_stat_world_leafs]++; r_refdef.viewcache.world_leafvisible[j] = true; if (leaf->numleafsurfaces) for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) r_refdef.viewcache.world_surfacevisible[*mark] = true; } } } // if desired use a recursive portal flow, culling each portal to // frustum and checking if the leaf the portal leads to is in the pvs else { int leafstackpos; mportal_t *p; mleaf_t *leafstack[8192]; vec3_t cullmins, cullmaxs; float cullbias = r_nearclip.value * 2.0f; // the nearclip plane can easily end up culling portals in certain perfectly-aligned views, causing view blackouts // simple-frustum portal method: // follows portals leading outward from viewleaf, does not venture // offscreen or into leafs that are not visible, faster than // Quake's RecursiveWorldNode and vastly better in unvised maps, // often culls some surfaces that pvs alone would miss // (such as a room in pvs that is hidden behind a wall, but the // passage leading to the room is off-screen) leafstack[0] = viewleaf; leafstackpos = 1; while (leafstackpos) { leaf = leafstack[--leafstackpos]; if (r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs]) continue; if (leaf->clusterindex < 0) continue; r_refdef.stats[r_stat_world_leafs]++; r_refdef.viewcache.world_leafvisible[leaf - model->brush.data_leafs] = true; // mark any surfaces bounding this leaf if (leaf->numleafsurfaces) for (i = 0, mark = leaf->firstleafsurface;i < leaf->numleafsurfaces;i++, mark++) r_refdef.viewcache.world_surfacevisible[*mark] = true; // follow portals into other leafs // the checks are: // the leaf has not been visited yet // and the leaf is visible in the pvs // the portal polygon's bounding box is on the screen for (p = leaf->portals;p;p = p->next) { r_refdef.stats[r_stat_world_portals]++; if (r_refdef.viewcache.world_leafvisible[p->past - model->brush.data_leafs]) continue; if (!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, p->past->clusterindex)) continue; cullmins[0] = p->mins[0] - cullbias; cullmins[1] = p->mins[1] - cullbias; cullmins[2] = p->mins[2] - cullbias; cullmaxs[0] = p->maxs[0] + cullbias; cullmaxs[1] = p->maxs[1] + cullbias; cullmaxs[2] = p->maxs[2] + cullbias; if (R_CullBox(cullmins, cullmaxs)) continue; if (leafstackpos >= (int)(sizeof(leafstack) / sizeof(leafstack[0]))) break; leafstack[leafstackpos++] = p->past; } } } } R_View_WorldVisibility_CullSurfaces(); } void R_Q1BSP_DrawSky(entity_render_t *ent) { if (ent->model == NULL) return; if (ent == r_refdef.scene.worldentity) R_DrawWorldSurfaces(true, true, false, false, false); else R_DrawModelSurfaces(ent, true, true, false, false, false); } void R_Q1BSP_DrawAddWaterPlanes(entity_render_t *ent) { int i, j, n, flagsmask; dp_model_t *model = ent->model; msurface_t *surfaces; if (model == NULL) return; if (ent == r_refdef.scene.worldentity) RSurf_ActiveWorldEntity(); else RSurf_ActiveModelEntity(ent, true, false, false); surfaces = model->data_surfaces; flagsmask = MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA; // add visible surfaces to draw list if (ent == r_refdef.scene.worldentity) { for (i = 0;i < model->nummodelsurfaces;i++) { j = model->sortedmodelsurfaces[i]; if (r_refdef.viewcache.world_surfacevisible[j]) if (surfaces[j].texture->basematerialflags & flagsmask) R_Water_AddWaterPlane(surfaces + j, 0); } } else { if(ent->entitynumber >= MAX_EDICTS) // && CL_VM_TransformView(ent->entitynumber - MAX_EDICTS, NULL, NULL, NULL)) n = ent->entitynumber; else n = 0; for (i = 0;i < model->nummodelsurfaces;i++) { j = model->sortedmodelsurfaces[i]; if (surfaces[j].texture->basematerialflags & flagsmask) R_Water_AddWaterPlane(surfaces + j, n); } } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } void R_Q1BSP_Draw(entity_render_t *ent) { dp_model_t *model = ent->model; if (model == NULL) return; if (ent == r_refdef.scene.worldentity) R_DrawWorldSurfaces(false, true, false, false, false); else R_DrawModelSurfaces(ent, false, true, false, false, false); } void R_Q1BSP_DrawDepth(entity_render_t *ent) { dp_model_t *model = ent->model; if (model == NULL || model->surfmesh.isanimated) return; GL_ColorMask(0,0,0,0); GL_Color(1,1,1,1); GL_DepthTest(true); GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(true); // R_Mesh_ResetTextureState(); if (ent == r_refdef.scene.worldentity) R_DrawWorldSurfaces(false, false, true, false, false); else R_DrawModelSurfaces(ent, false, false, true, false, false); GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); } void R_Q1BSP_DrawDebug(entity_render_t *ent) { if (ent->model == NULL) return; if (ent == r_refdef.scene.worldentity) R_DrawWorldSurfaces(false, false, false, true, false); else R_DrawModelSurfaces(ent, false, false, false, true, false); } void R_Q1BSP_DrawPrepass(entity_render_t *ent) { dp_model_t *model = ent->model; if (model == NULL) return; if (ent == r_refdef.scene.worldentity) R_DrawWorldSurfaces(false, true, false, false, true); else R_DrawModelSurfaces(ent, false, true, false, false, true); } typedef struct r_q1bsp_getlightinfo_s { dp_model_t *model; vec3_t relativelightorigin; float lightradius; int *outleaflist; unsigned char *outleafpvs; int outnumleafs; unsigned char *visitingleafpvs; int *outsurfacelist; unsigned char *outsurfacepvs; unsigned char *tempsurfacepvs; unsigned char *outshadowtrispvs; unsigned char *outlighttrispvs; int outnumsurfaces; vec3_t outmins; vec3_t outmaxs; vec3_t lightmins; vec3_t lightmaxs; const unsigned char *pvs; qboolean svbsp_active; qboolean svbsp_insertoccluder; int numfrustumplanes; const mplane_t *frustumplanes; } r_q1bsp_getlightinfo_t; #define GETLIGHTINFO_MAXNODESTACK 4096 static void R_Q1BSP_RecursiveGetLightInfo_BSP(r_q1bsp_getlightinfo_t *info, qboolean skipsurfaces) { // nodestack mnode_t *nodestack[GETLIGHTINFO_MAXNODESTACK]; int nodestackpos = 0; // node processing mplane_t *plane; mnode_t *node; int sides; // leaf processing mleaf_t *leaf; const msurface_t *surface; const msurface_t *surfaces = info->model->data_surfaces; int numleafsurfaces; int leafsurfaceindex; int surfaceindex; int triangleindex, t; int currentmaterialflags; qboolean castshadow; const int *e; const vec_t *v[3]; float v2[3][3]; qboolean insidebox; qboolean frontsidecasting = r_shadow_frontsidecasting.integer != 0; qboolean svbspactive = info->svbsp_active; qboolean svbspinsertoccluder = info->svbsp_insertoccluder; const int *leafsurfaceindices; qboolean addedtris; int i; mportal_t *portal; static float points[128][3]; // push the root node onto our nodestack nodestack[nodestackpos++] = info->model->brush.data_nodes; // we'll be done when the nodestack is empty while (nodestackpos) { // get a node from the stack to process node = nodestack[--nodestackpos]; // is it a node or a leaf? plane = node->plane; if (plane) { // node #if 0 if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) continue; #endif #if 0 if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) continue; #endif // axial planes can be processed much more quickly if (plane->type < 3) { // axial plane if (info->lightmins[plane->type] > plane->dist) nodestack[nodestackpos++] = node->children[0]; else if (info->lightmaxs[plane->type] < plane->dist) nodestack[nodestackpos++] = node->children[1]; else { // recurse front side first because the svbsp building prefers it if (info->relativelightorigin[plane->type] >= plane->dist) { if (nodestackpos < GETLIGHTINFO_MAXNODESTACK-1) nodestack[nodestackpos++] = node->children[0]; nodestack[nodestackpos++] = node->children[1]; } else { if (nodestackpos < GETLIGHTINFO_MAXNODESTACK-1) nodestack[nodestackpos++] = node->children[1]; nodestack[nodestackpos++] = node->children[0]; } } } else { // sloped plane sides = BoxOnPlaneSide(info->lightmins, info->lightmaxs, plane); switch (sides) { default: continue; // ERROR: NAN bounding box! case 1: nodestack[nodestackpos++] = node->children[0]; break; case 2: nodestack[nodestackpos++] = node->children[1]; break; case 3: // recurse front side first because the svbsp building prefers it if (PlaneDist(info->relativelightorigin, plane) >= 0) { if (nodestackpos < GETLIGHTINFO_MAXNODESTACK-1) nodestack[nodestackpos++] = node->children[0]; nodestack[nodestackpos++] = node->children[1]; } else { if (nodestackpos < GETLIGHTINFO_MAXNODESTACK-1) nodestack[nodestackpos++] = node->children[1]; nodestack[nodestackpos++] = node->children[0]; } break; } } } else { // leaf leaf = (mleaf_t *)node; #if 1 if (r_shadow_frontsidecasting.integer && info->pvs != NULL && !CHECKPVSBIT(info->pvs, leaf->clusterindex)) continue; #endif #if 1 if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) continue; #endif #if 1 if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) continue; #endif if (svbspactive) { // we can occlusion test the leaf by checking if all of its portals // are occluded (unless the light is in this leaf - but that was // already handled by the caller) for (portal = leaf->portals;portal;portal = portal->next) { for (i = 0;i < portal->numpoints;i++) VectorCopy(portal->points[i].position, points[i]); if (SVBSP_AddPolygon(&r_svbsp, portal->numpoints, points[0], false, NULL, NULL, 0) & 2) break; } if (leaf->portals && portal == NULL) continue; // no portals of this leaf visible } // add this leaf to the reduced light bounds info->outmins[0] = min(info->outmins[0], leaf->mins[0]); info->outmins[1] = min(info->outmins[1], leaf->mins[1]); info->outmins[2] = min(info->outmins[2], leaf->mins[2]); info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); // mark this leaf as being visible to the light if (info->outleafpvs) { int leafindex = leaf - info->model->brush.data_leafs; if (!CHECKPVSBIT(info->outleafpvs, leafindex)) { SETPVSBIT(info->outleafpvs, leafindex); info->outleaflist[info->outnumleafs++] = leafindex; } } // when using BIH, we skip the surfaces here if (skipsurfaces) continue; // iterate the surfaces linked by this leaf and check their triangles leafsurfaceindices = leaf->firstleafsurface; numleafsurfaces = leaf->numleafsurfaces; if (svbspinsertoccluder) { for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) { surfaceindex = leafsurfaceindices[leafsurfaceindex]; if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) continue; SETPVSBIT(info->outsurfacepvs, surfaceindex); surface = surfaces + surfaceindex; if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) continue; currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); if (!castshadow) continue; insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) { v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; VectorCopy(v[0], v2[0]); VectorCopy(v[1], v2[1]); VectorCopy(v[2], v2[2]); if (insidebox || TriangleBBoxOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); } } } else { for (leafsurfaceindex = 0;leafsurfaceindex < numleafsurfaces;leafsurfaceindex++) { surfaceindex = leafsurfaceindices[leafsurfaceindex]; if (CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) continue; SETPVSBIT(info->outsurfacepvs, surfaceindex); surface = surfaces + surfaceindex; if (!BoxesOverlap(info->lightmins, info->lightmaxs, surface->mins, surface->maxs)) continue; addedtris = false; currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); insidebox = BoxInsideBox(surface->mins, surface->maxs, info->lightmins, info->lightmaxs); for (triangleindex = 0, t = surface->num_firstshadowmeshtriangle, e = info->model->brush.shadowmesh->element3i + t * 3;triangleindex < surface->num_triangles;triangleindex++, t++, e += 3) { v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; VectorCopy(v[0], v2[0]); VectorCopy(v[1], v2[1]); VectorCopy(v[2], v2[2]); if (!insidebox && !TriangleBBoxOverlapsBox(v2[0], v2[1], v2[2], info->lightmins, info->lightmaxs)) continue; if (svbspactive && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) continue; // we don't omit triangles from lighting even if they are // backfacing, because when using shadowmapping they are often // not fully occluded on the horizon of an edge SETPVSBIT(info->outlighttrispvs, t); addedtris = true; if (castshadow) { if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) { // if the material is double sided we // can't cull by direction SETPVSBIT(info->outshadowtrispvs, t); } else if (frontsidecasting) { // front side casting occludes backfaces, // so they are completely useless as both // casters and lit polygons if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) SETPVSBIT(info->outshadowtrispvs, t); } else { // back side casting does not occlude // anything so we can't cull lit polygons if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) SETPVSBIT(info->outshadowtrispvs, t); } } } if (addedtris) info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; } } } } } static void R_Q1BSP_RecursiveGetLightInfo_BIH(r_q1bsp_getlightinfo_t *info, const bih_t *bih) { bih_leaf_t *leaf; bih_node_t *node; int nodenum; int axis; int surfaceindex; int t; int nodeleafindex; int currentmaterialflags; qboolean castshadow; msurface_t *surface; const int *e; const vec_t *v[3]; float v2[3][3]; int nodestack[GETLIGHTINFO_MAXNODESTACK]; int nodestackpos = 0; // note: because the BSP leafs are not in the BIH tree, the _BSP function // must be called to mark leafs visible for entity culling... // we start at the root node nodestack[nodestackpos++] = bih->rootnode; // we'll be done when the stack is empty while (nodestackpos) { // pop one off the stack to process nodenum = nodestack[--nodestackpos]; // node node = bih->nodes + nodenum; if (node->type == BIH_UNORDERED) { for (nodeleafindex = 0;nodeleafindex < BIH_MAXUNORDEREDCHILDREN && node->children[nodeleafindex] >= 0;nodeleafindex++) { leaf = bih->leafs + node->children[nodeleafindex]; if (leaf->type != BIH_RENDERTRIANGLE) continue; #if 1 if (!BoxesOverlap(info->lightmins, info->lightmaxs, leaf->mins, leaf->maxs)) continue; #endif #if 1 if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(leaf->mins, leaf->maxs, info->numfrustumplanes, info->frustumplanes)) continue; #endif surfaceindex = leaf->surfaceindex; surface = info->model->data_surfaces + surfaceindex; currentmaterialflags = R_GetCurrentTexture(surface->texture)->currentmaterialflags; castshadow = !(currentmaterialflags & MATERIALFLAG_NOSHADOW); t = leaf->itemindex + surface->num_firstshadowmeshtriangle - surface->num_firsttriangle; e = info->model->brush.shadowmesh->element3i + t * 3; v[0] = info->model->brush.shadowmesh->vertex3f + e[0] * 3; v[1] = info->model->brush.shadowmesh->vertex3f + e[1] * 3; v[2] = info->model->brush.shadowmesh->vertex3f + e[2] * 3; VectorCopy(v[0], v2[0]); VectorCopy(v[1], v2[1]); VectorCopy(v[2], v2[2]); if (info->svbsp_insertoccluder) { if (castshadow) SVBSP_AddPolygon(&r_svbsp, 3, v2[0], true, NULL, NULL, 0); continue; } if (info->svbsp_active && !(SVBSP_AddPolygon(&r_svbsp, 3, v2[0], false, NULL, NULL, 0) & 2)) continue; // we don't occlude triangles from lighting even // if they are backfacing, because when using // shadowmapping they are often not fully occluded // on the horizon of an edge SETPVSBIT(info->outlighttrispvs, t); if (castshadow) { if (currentmaterialflags & MATERIALFLAG_NOCULLFACE) { // if the material is double sided we // can't cull by direction SETPVSBIT(info->outshadowtrispvs, t); } else if (r_shadow_frontsidecasting.integer) { // front side casting occludes backfaces, // so they are completely useless as both // casters and lit polygons if (PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) SETPVSBIT(info->outshadowtrispvs, t); } else { // back side casting does not occlude // anything so we can't cull lit polygons if (!PointInfrontOfTriangle(info->relativelightorigin, v2[0], v2[1], v2[2])) SETPVSBIT(info->outshadowtrispvs, t); } } if (!CHECKPVSBIT(info->outsurfacepvs, surfaceindex)) { SETPVSBIT(info->outsurfacepvs, surfaceindex); info->outsurfacelist[info->outnumsurfaces++] = surfaceindex; } } } else { axis = node->type - BIH_SPLITX; #if 0 if (!BoxesOverlap(info->lightmins, info->lightmaxs, node->mins, node->maxs)) continue; #endif #if 0 if (!r_shadow_compilingrtlight && R_CullBoxCustomPlanes(node->mins, node->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) continue; #endif if (info->lightmins[axis] <= node->backmax) { if (info->lightmaxs[axis] >= node->frontmin && nodestackpos < GETLIGHTINFO_MAXNODESTACK-1) nodestack[nodestackpos++] = node->front; nodestack[nodestackpos++] = node->back; continue; } else if (info->lightmaxs[axis] >= node->frontmin) { nodestack[nodestackpos++] = node->front; continue; } else continue; // light falls between children, nothing here } } } static void R_Q1BSP_CallRecursiveGetLightInfo(r_q1bsp_getlightinfo_t *info, qboolean use_svbsp) { extern cvar_t r_shadow_usebihculling; if (use_svbsp) { float origin[3]; VectorCopy(info->relativelightorigin, origin); r_svbsp.maxnodes = max(r_svbsp.maxnodes, 1<<12); r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); info->svbsp_active = true; info->svbsp_insertoccluder = true; for (;;) { SVBSP_Init(&r_svbsp, origin, r_svbsp.maxnodes, r_svbsp.nodes); R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); // if that failed, retry with more nodes if (r_svbsp.ranoutofnodes) { // an upper limit is imposed if (r_svbsp.maxnodes >= 2<<22) break; r_svbsp.maxnodes *= 2; r_svbsp.nodes = (svbsp_node_t*) R_FrameData_Alloc(r_svbsp.maxnodes * sizeof(svbsp_node_t)); //Mem_Free(r_svbsp.nodes); //r_svbsp.nodes = (svbsp_node_t*) Mem_Alloc(tempmempool, r_svbsp.maxnodes * sizeof(svbsp_node_t)); } else break; } // now clear the visibility arrays because we need to redo it info->outnumleafs = 0; info->outnumsurfaces = 0; memset(info->outleafpvs, 0, (info->model->brush.num_leafs + 7) >> 3); memset(info->outsurfacepvs, 0, (info->model->nummodelsurfaces + 7) >> 3); if (info->model->brush.shadowmesh) memset(info->outshadowtrispvs, 0, (info->model->brush.shadowmesh->numtriangles + 7) >> 3); else memset(info->outshadowtrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); memset(info->outlighttrispvs, 0, (info->model->surfmesh.num_triangles + 7) >> 3); } else info->svbsp_active = false; // we HAVE to mark the leaf the light is in as lit, because portals are // irrelevant to a leaf that the light source is inside of // (and they are all facing away, too) { mnode_t *node = info->model->brush.data_nodes; mleaf_t *leaf; while (node->plane) node = node->children[(node->plane->type < 3 ? info->relativelightorigin[node->plane->type] : DotProduct(info->relativelightorigin,node->plane->normal)) < node->plane->dist]; leaf = (mleaf_t *)node; info->outmins[0] = min(info->outmins[0], leaf->mins[0]); info->outmins[1] = min(info->outmins[1], leaf->mins[1]); info->outmins[2] = min(info->outmins[2], leaf->mins[2]); info->outmaxs[0] = max(info->outmaxs[0], leaf->maxs[0]); info->outmaxs[1] = max(info->outmaxs[1], leaf->maxs[1]); info->outmaxs[2] = max(info->outmaxs[2], leaf->maxs[2]); if (info->outleafpvs) { int leafindex = leaf - info->model->brush.data_leafs; if (!CHECKPVSBIT(info->outleafpvs, leafindex)) { SETPVSBIT(info->outleafpvs, leafindex); info->outleaflist[info->outnumleafs++] = leafindex; } } } info->svbsp_insertoccluder = false; // use BIH culling on single leaf maps (generally this only happens if running a model as a map), otherwise use BSP culling to make use of vis data if (r_shadow_usebihculling.integer > 0 && (r_shadow_usebihculling.integer == 2 || info->model->brush.num_leafs == 1) && info->model->render_bih.leafs != NULL) { R_Q1BSP_RecursiveGetLightInfo_BSP(info, true); R_Q1BSP_RecursiveGetLightInfo_BIH(info, &info->model->render_bih); } else R_Q1BSP_RecursiveGetLightInfo_BSP(info, false); // we're using temporary framedata memory, so this pointer will be invalid soon, clear it r_svbsp.nodes = NULL; if (developer_extra.integer && use_svbsp) { Con_DPrintf("GetLightInfo: svbsp built with %i nodes, polygon stats:\n", r_svbsp.numnodes); Con_DPrintf("occluders: %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_occluders_accepted, r_svbsp.stat_occluders_rejected, r_svbsp.stat_occluders_fragments_accepted, r_svbsp.stat_occluders_fragments_rejected); Con_DPrintf("queries : %i accepted, %i rejected, %i fragments accepted, %i fragments rejected.\n", r_svbsp.stat_queries_accepted, r_svbsp.stat_queries_rejected, r_svbsp.stat_queries_fragments_accepted, r_svbsp.stat_queries_fragments_rejected); } } static msurface_t *r_q1bsp_getlightinfo_surfaces; static int R_Q1BSP_GetLightInfo_comparefunc(const void *ap, const void *bp) { int a = *(int*)ap; int b = *(int*)bp; const msurface_t *as = r_q1bsp_getlightinfo_surfaces + a; const msurface_t *bs = r_q1bsp_getlightinfo_surfaces + b; if (as->texture < bs->texture) return -1; if (as->texture > bs->texture) return 1; return a - b; } extern cvar_t r_shadow_sortsurfaces; void R_Q1BSP_GetLightInfo(entity_render_t *ent, vec3_t relativelightorigin, float lightradius, vec3_t outmins, vec3_t outmaxs, int *outleaflist, unsigned char *outleafpvs, int *outnumleafspointer, int *outsurfacelist, unsigned char *outsurfacepvs, int *outnumsurfacespointer, unsigned char *outshadowtrispvs, unsigned char *outlighttrispvs, unsigned char *visitingleafpvs, int numfrustumplanes, const mplane_t *frustumplanes) { r_q1bsp_getlightinfo_t info; VectorCopy(relativelightorigin, info.relativelightorigin); info.lightradius = lightradius; info.lightmins[0] = info.relativelightorigin[0] - info.lightradius; info.lightmins[1] = info.relativelightorigin[1] - info.lightradius; info.lightmins[2] = info.relativelightorigin[2] - info.lightradius; info.lightmaxs[0] = info.relativelightorigin[0] + info.lightradius; info.lightmaxs[1] = info.relativelightorigin[1] + info.lightradius; info.lightmaxs[2] = info.relativelightorigin[2] + info.lightradius; if (ent->model == NULL) { VectorCopy(info.lightmins, outmins); VectorCopy(info.lightmaxs, outmaxs); *outnumleafspointer = 0; *outnumsurfacespointer = 0; return; } info.model = ent->model; info.outleaflist = outleaflist; info.outleafpvs = outleafpvs; info.outnumleafs = 0; info.visitingleafpvs = visitingleafpvs; info.outsurfacelist = outsurfacelist; info.outsurfacepvs = outsurfacepvs; info.outshadowtrispvs = outshadowtrispvs; info.outlighttrispvs = outlighttrispvs; info.outnumsurfaces = 0; info.numfrustumplanes = numfrustumplanes; info.frustumplanes = frustumplanes; VectorCopy(info.relativelightorigin, info.outmins); VectorCopy(info.relativelightorigin, info.outmaxs); memset(visitingleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); memset(outleafpvs, 0, (info.model->brush.num_leafs + 7) >> 3); memset(outsurfacepvs, 0, (info.model->nummodelsurfaces + 7) >> 3); if (info.model->brush.shadowmesh) memset(outshadowtrispvs, 0, (info.model->brush.shadowmesh->numtriangles + 7) >> 3); else memset(outshadowtrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); memset(outlighttrispvs, 0, (info.model->surfmesh.num_triangles + 7) >> 3); if (info.model->brush.GetPVS && r_shadow_frontsidecasting.integer) info.pvs = info.model->brush.GetPVS(info.model, info.relativelightorigin); else info.pvs = NULL; RSurf_ActiveWorldEntity(); if (r_shadow_frontsidecasting.integer && r_shadow_compilingrtlight && r_shadow_realtime_world_compileportalculling.integer && info.model->brush.data_portals) { // use portal recursion for exact light volume culling, and exact surface checking Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, true, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); } else if (r_shadow_frontsidecasting.integer && r_shadow_realtime_dlight_portalculling.integer && info.model->brush.data_portals) { // use portal recursion for exact light volume culling, but not the expensive exact surface checking Portal_Visibility(info.model, info.relativelightorigin, info.outleaflist, info.outleafpvs, &info.outnumleafs, info.outsurfacelist, info.outsurfacepvs, &info.outnumsurfaces, NULL, 0, r_shadow_realtime_dlight_portalculling.integer >= 2, info.lightmins, info.lightmaxs, info.outmins, info.outmaxs, info.outshadowtrispvs, info.outlighttrispvs, info.visitingleafpvs); } else { // recurse the bsp tree, checking leafs and surfaces for visibility // optionally using svbsp for exact culling of compiled lights // (or if the user enables dlight svbsp culling, which is mostly for // debugging not actual use) R_Q1BSP_CallRecursiveGetLightInfo(&info, (r_shadow_compilingrtlight ? r_shadow_realtime_world_compilesvbsp.integer : r_shadow_realtime_dlight_svbspculling.integer) != 0); } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity // limit combined leaf box to light boundaries outmins[0] = max(info.outmins[0] - 1, info.lightmins[0]); outmins[1] = max(info.outmins[1] - 1, info.lightmins[1]); outmins[2] = max(info.outmins[2] - 1, info.lightmins[2]); outmaxs[0] = min(info.outmaxs[0] + 1, info.lightmaxs[0]); outmaxs[1] = min(info.outmaxs[1] + 1, info.lightmaxs[1]); outmaxs[2] = min(info.outmaxs[2] + 1, info.lightmaxs[2]); *outnumleafspointer = info.outnumleafs; *outnumsurfacespointer = info.outnumsurfaces; // now sort surfaces by texture for faster rendering r_q1bsp_getlightinfo_surfaces = info.model->data_surfaces; if (r_shadow_sortsurfaces.integer) qsort(info.outsurfacelist, info.outnumsurfaces, sizeof(*info.outsurfacelist), R_Q1BSP_GetLightInfo_comparefunc); } void R_Q1BSP_CompileShadowVolume(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) { dp_model_t *model = ent->model; msurface_t *surface; int surfacelistindex; float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; // if triangle neighbors are disabled, shadowvolumes are disabled if (!model->brush.shadowmesh->neighbor3i) return; r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { surface = model->data_surfaces + surfacelist[surfacelistindex]; if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) continue; R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs); } R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); r_shadow_compilingrtlight->static_meshchain_shadow_zfail = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, false, false, true); } extern cvar_t r_polygonoffset_submodel_factor; extern cvar_t r_polygonoffset_submodel_offset; void R_Q1BSP_DrawShadowVolume(entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const vec3_t lightmins, const vec3_t lightmaxs) { dp_model_t *model = ent->model; const msurface_t *surface; int modelsurfacelistindex; float projectdistance = relativelightdirection ? lightradius : lightradius + model->radius*2 + r_shadow_projectdistance.value; // check the box in modelspace, it was already checked in worldspace if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) return; R_FrameData_SetMark(); if (ent->model->brush.submodel) GL_PolygonOffset(r_refdef.shadowpolygonfactor + r_polygonoffset_submodel_factor.value, r_refdef.shadowpolygonoffset + r_polygonoffset_submodel_offset.value); if (model->brush.shadowmesh) { // if triangle neighbors are disabled, shadowvolumes are disabled if (!model->brush.shadowmesh->neighbor3i) return; R_Shadow_PrepareShadowMark(model->brush.shadowmesh->numtriangles); for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) { surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; if (R_GetCurrentTexture(surface->texture)->currentmaterialflags & MATERIALFLAG_NOSHADOW) continue; R_Shadow_MarkVolumeFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); } R_Shadow_VolumeFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, model->brush.shadowmesh->neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); } else { // if triangle neighbors are disabled, shadowvolumes are disabled if (!model->surfmesh.data_neighbor3i) return; projectdistance = lightradius + model->radius*2; R_Shadow_PrepareShadowMark(model->surfmesh.num_triangles); // identify lit faces within the bounding box for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) { surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; rsurface.texture = R_GetCurrentTexture(surface->texture); if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) continue; R_Shadow_MarkVolumeFromBox(surface->num_firsttriangle, surface->num_triangles, rsurface.modelvertex3f, rsurface.modelelement3i, relativelightorigin, relativelightdirection, lightmins, lightmaxs, surface->mins, surface->maxs); } R_Shadow_VolumeFromList(model->surfmesh.num_vertices, model->surfmesh.num_triangles, rsurface.modelvertex3f, model->surfmesh.data_element3i, model->surfmesh.data_neighbor3i, relativelightorigin, relativelightdirection, projectdistance, numshadowmark, shadowmarklist, ent->mins, ent->maxs); } if (ent->model->brush.submodel) GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset); R_FrameData_ReturnToMark(); } void R_Q1BSP_CompileShadowMap(entity_render_t *ent, vec3_t relativelightorigin, vec3_t relativelightdirection, float lightradius, int numsurfaces, const int *surfacelist) { dp_model_t *model = ent->model; msurface_t *surface; int surfacelistindex; int sidetotals[6] = { 0, 0, 0, 0, 0, 0 }, sidemasks = 0; int i; if (!model->brush.shadowmesh) return; r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Begin(r_main_mempool, 32768, 32768, NULL, NULL, NULL, false, false, true); R_Shadow_PrepareShadowSides(model->brush.shadowmesh->numtriangles); for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { surface = model->data_surfaces + surfacelist[surfacelistindex]; sidemasks |= R_Shadow_ChooseSidesFromBox(surface->num_firstshadowmeshtriangle, surface->num_triangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, &r_shadow_compilingrtlight->matrix_worldtolight, relativelightorigin, relativelightdirection, r_shadow_compilingrtlight->cullmins, r_shadow_compilingrtlight->cullmaxs, surface->mins, surface->maxs, surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW ? NULL : sidetotals); } R_Shadow_ShadowMapFromList(model->brush.shadowmesh->numverts, model->brush.shadowmesh->numtriangles, model->brush.shadowmesh->vertex3f, model->brush.shadowmesh->element3i, numshadowsides, sidetotals, shadowsides, shadowsideslist); r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap = Mod_ShadowMesh_Finish(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, false, false, true); r_shadow_compilingrtlight->static_shadowmap_receivers &= sidemasks; for(i = 0;i<6;i++) if(!sidetotals[i]) r_shadow_compilingrtlight->static_shadowmap_casters &= ~(1 << i); } #define RSURF_MAX_BATCHSURFACES 8192 static const msurface_t *batchsurfacelist[RSURF_MAX_BATCHSURFACES]; void R_Q1BSP_DrawShadowMap(int side, entity_render_t *ent, const vec3_t relativelightorigin, const vec3_t relativelightdirection, float lightradius, int modelnumsurfaces, const int *modelsurfacelist, const unsigned char *surfacesides, const vec3_t lightmins, const vec3_t lightmaxs) { dp_model_t *model = ent->model; const msurface_t *surface; int modelsurfacelistindex, batchnumsurfaces; // check the box in modelspace, it was already checked in worldspace if (!BoxesOverlap(model->normalmins, model->normalmaxs, lightmins, lightmaxs)) return; R_FrameData_SetMark(); // identify lit faces within the bounding box for (modelsurfacelistindex = 0;modelsurfacelistindex < modelnumsurfaces;modelsurfacelistindex++) { surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; if (surfacesides && !(surfacesides[modelsurfacelistindex] && (1 << side))) continue; rsurface.texture = R_GetCurrentTexture(surface->texture); if (rsurface.texture->currentmaterialflags & MATERIALFLAG_NOSHADOW) continue; if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) continue; r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += surface->num_triangles; r_refdef.stats[r_stat_lights_shadowtriangles] += surface->num_triangles; batchsurfacelist[0] = surface; batchnumsurfaces = 1; while(++modelsurfacelistindex < modelnumsurfaces && batchnumsurfaces < RSURF_MAX_BATCHSURFACES) { surface = model->data_surfaces + modelsurfacelist[modelsurfacelistindex]; if (surfacesides && !(surfacesides[modelsurfacelistindex] & (1 << side))) continue; if (surface->texture != batchsurfacelist[0]->texture) break; if (!BoxesOverlap(lightmins, lightmaxs, surface->mins, surface->maxs)) continue; r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += surface->num_triangles; r_refdef.stats[r_stat_lights_shadowtriangles] += surface->num_triangles; batchsurfacelist[batchnumsurfaces++] = surface; } --modelsurfacelistindex; GL_CullFace(rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE ? GL_NONE : r_refdef.view.cullface_back); RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, batchnumsurfaces, batchsurfacelist); R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); RSurf_DrawBatch(); } R_FrameData_ReturnToMark(); } #define BATCHSIZE 1024 static void R_Q1BSP_DrawLight_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i, j, endsurface; texture_t *t; const msurface_t *surface; R_FrameData_SetMark(); // note: in practice this never actually receives batches R_Shadow_RenderMode_Begin(); R_Shadow_RenderMode_ActiveLight(rtlight); R_Shadow_RenderMode_Lighting(false, true, false); R_Shadow_SetupEntityLight(ent); for (i = 0;i < numsurfaces;i = j) { j = i + 1; surface = rsurface.modelsurfaces + surfacelist[i]; t = surface->texture; rsurface.texture = R_GetCurrentTexture(t); endsurface = min(j + BATCHSIZE, numsurfaces); for (j = i;j < endsurface;j++) { surface = rsurface.modelsurfaces + surfacelist[j]; if (t != surface->texture) break; R_Shadow_RenderLighting(1, &surface); } } R_Shadow_RenderMode_End(); R_FrameData_ReturnToMark(); } extern qboolean r_shadow_usingdeferredprepass; void R_Q1BSP_DrawLight(entity_render_t *ent, int numsurfaces, const int *surfacelist, const unsigned char *lighttrispvs) { dp_model_t *model = ent->model; const msurface_t *surface; int i, k, kend, l, endsurface, batchnumsurfaces, texturenumsurfaces; const msurface_t **texturesurfacelist; texture_t *tex; CHECKGLERROR R_FrameData_SetMark(); // this is a double loop because non-visible surface skipping has to be // fast, and even if this is not the world model (and hence no visibility // checking) the input surface list and batch buffer are different formats // so some processing is necessary. (luckily models have few surfaces) for (i = 0;i < numsurfaces;) { batchnumsurfaces = 0; endsurface = min(i + RSURF_MAX_BATCHSURFACES, numsurfaces); if (ent == r_refdef.scene.worldentity) { for (;i < endsurface;i++) if (r_refdef.viewcache.world_surfacevisible[surfacelist[i]]) batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; } else { for (;i < endsurface;i++) batchsurfacelist[batchnumsurfaces++] = model->data_surfaces + surfacelist[i]; } if (!batchnumsurfaces) continue; for (k = 0;k < batchnumsurfaces;k = kend) { surface = batchsurfacelist[k]; tex = surface->texture; rsurface.texture = R_GetCurrentTexture(tex); // gather surfaces into a batch range for (kend = k;kend < batchnumsurfaces && tex == batchsurfacelist[kend]->texture;kend++) ; // now figure out what to do with this particular range of surfaces // VorteX: added MATERIALFLAG_NORTLIGHT if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WALL | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NORTLIGHT)) != MATERIALFLAG_WALL) continue; if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) continue; if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) { vec3_t tempcenter, center; for (l = k;l < kend;l++) { surface = batchsurfacelist[l]; if (r_transparent_sortsurfacesbynearest.integer) { tempcenter[0] = bound(surface->mins[0], rsurface.localvieworigin[0], surface->maxs[0]); tempcenter[1] = bound(surface->mins[1], rsurface.localvieworigin[1], surface->maxs[1]); tempcenter[2] = bound(surface->mins[2], rsurface.localvieworigin[2], surface->maxs[2]); } else { tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; } Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); if (ent->transparent_offset) // transparent offset { center[0] += r_refdef.view.forward[0]*ent->transparent_offset; center[1] += r_refdef.view.forward[1]*ent->transparent_offset; center[2] += r_refdef.view.forward[2]*ent->transparent_offset; } R_MeshQueue_AddTransparent((rsurface.entity->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : ((rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? TRANSPARENTSORT_HUD : rsurface.texture->transparentsort), center, R_Q1BSP_DrawLight_TransparentCallback, ent, surface - rsurface.modelsurfaces, rsurface.rtlight); } continue; } if (r_shadow_usingdeferredprepass) continue; texturenumsurfaces = kend - k; texturesurfacelist = batchsurfacelist + k; R_Shadow_RenderLighting(texturenumsurfaces, texturesurfacelist); } } R_FrameData_ReturnToMark(); } //Made by [515] static void R_ReplaceWorldTexture (void) { dp_model_t *m; texture_t *t; int i; const char *r, *newt; skinframe_t *skinframe; if (!r_refdef.scene.worldmodel) { Con_Printf("There is no worldmodel\n"); return; } m = r_refdef.scene.worldmodel; if(Cmd_Argc() < 2) { Con_Print("r_replacemaptexture - replaces texture\n"); Con_Print("r_replacemaptexture - switch back to default texture\n"); return; } if(!cl.islocalgame || !cl.worldmodel) { Con_Print("This command works only in singleplayer\n"); return; } r = Cmd_Argv(1); newt = Cmd_Argv(2); if(!newt[0]) newt = r; for(i=0,t=m->data_textures;inum_textures;i++,t++) { if(/*t->width && !strcasecmp(t->name, r)*/ matchpattern( t->name, r, true ) ) { if ((skinframe = R_SkinFrame_LoadExternal(newt, TEXF_MIPMAP | TEXF_ALPHA | TEXF_PICMIP, true))) { // t->skinframes[0] = skinframe; t->currentskinframe = skinframe; Con_Printf("%s replaced with %s\n", r, newt); } else { Con_Printf("%s was not found\n", newt); return; } } } } //Made by [515] static void R_ListWorldTextures (void) { dp_model_t *m; texture_t *t; int i; if (!r_refdef.scene.worldmodel) { Con_Printf("There is no worldmodel\n"); return; } m = r_refdef.scene.worldmodel; Con_Print("Worldmodel textures :\n"); for(i=0,t=m->data_textures;inum_textures;i++,t++) if (t->numskinframes) Con_Printf("%s\n", t->name); } #if 0 static void gl_surf_start(void) { } static void gl_surf_shutdown(void) { } static void gl_surf_newmap(void) { } #endif void GL_Surf_Init(void) { Cvar_RegisterVariable(&r_ambient); Cvar_RegisterVariable(&r_lockpvs); Cvar_RegisterVariable(&r_lockvisibility); Cvar_RegisterVariable(&r_useportalculling); Cvar_RegisterVariable(&r_usesurfaceculling); Cvar_RegisterVariable(&r_q3bsp_renderskydepth); Cmd_AddCommand ("r_replacemaptexture", R_ReplaceWorldTexture, "override a map texture for testing purposes"); Cmd_AddCommand ("r_listmaptextures", R_ListWorldTextures, "list all textures used by the current map"); //R_RegisterModule("GL_Surf", gl_surf_start, gl_surf_shutdown, gl_surf_newmap); } darkplaces/console.c0000664000175000017500000024041013067716216013777 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // console.c #if !defined(WIN32) || defined(__MINGW32__) # include #endif #include #include "quakedef.h" #include "thread.h" // for u8_encodech #include "ft2.h" float con_cursorspeed = 4; // lines up from bottom to display int con_backscroll; conbuffer_t con; void *con_mutex = NULL; #define CON_LINES(i) CONBUFFER_LINES(&con, i) #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con) #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con) cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"}; cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"}; cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"}; cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"}; cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"}; cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"}; cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"}; cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"}; cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"}; cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"}; cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"}; cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"}; cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"}; cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"}; cvar_t sys_specialcharactertranslation = {0, "sys_specialcharactertranslation", "1", "terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output)"}; #ifdef WIN32 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; #else cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"}; #endif cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"}; cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: " "0: add nothing after completion. " "1: add the last color after completion. " "2: add a quote when starting a quote instead of the color. " "4: will replace 1, will force color, even after a quote. " "8: ignore non-alphanumerics. " "16: ignore spaces. "}; #define NICKS_ADD_COLOR 1 #define NICKS_ADD_QUOTE 2 #define NICKS_FORCE_COLOR 4 #define NICKS_ALPHANUMERICS_ONLY 8 #define NICKS_NO_SPACES 16 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"}; cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"}; cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"}; cvar_t condump_stripcolors = {CVAR_SAVE, "condump_stripcolors", "0", "strip color codes from console dumps"}; int con_linewidth; int con_vislines; qboolean con_initialized; // used for server replies to rcon command lhnetsocket_t *rcon_redirect_sock = NULL; lhnetaddress_t *rcon_redirect_dest = NULL; int rcon_redirect_bufferpos = 0; char rcon_redirect_buffer[1400]; qboolean rcon_redirect_proquakeprotocol = false; // generic functions for console buffers void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool) { buf->active = true; buf->textsize = textsize; buf->text = (char *) Mem_Alloc(mempool, textsize); buf->maxlines = maxlines; buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines)); buf->lines_first = 0; buf->lines_count = 0; } /*! The translation table between the graphical font and plain ASCII --KB */ static char qfont_table[256] = { '\0', '#', '#', '#', '#', '.', '#', '#', '#', 9, 10, '#', ' ', 13, '.', '.', '[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '<', '=', '>', ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '<', '<', '=', '>', '#', '#', '.', '#', '#', '#', '#', ' ', '#', ' ', '>', '.', '.', '[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '<', '=', '>', ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '<' }; /* SanitizeString strips color tags from the string in and writes the result on string out */ static void SanitizeString(char *in, char *out) { while(*in) { if(*in == STRING_COLOR_TAG) { ++in; if(!*in) { out[0] = STRING_COLOR_TAG; out[1] = 0; return; } else if (*in >= '0' && *in <= '9') // ^[0-9] found { ++in; if(!*in) { *out = 0; return; } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9] continue; } else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found { if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) ) { in+=4; if (!*in) { *out = 0; return; } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb continue; } else in--; } else if (*in != STRING_COLOR_TAG) --in; } *out = qfont_table[*(unsigned char*)in]; ++in; ++out; } *out = 0; } /* ================ ConBuffer_Clear ================ */ void ConBuffer_Clear (conbuffer_t *buf) { buf->lines_count = 0; } /* ================ ConBuffer_Shutdown ================ */ void ConBuffer_Shutdown(conbuffer_t *buf) { buf->active = false; if (buf->text) Mem_Free(buf->text); if (buf->lines) Mem_Free(buf->lines); buf->text = NULL; buf->lines = NULL; } /* ================ ConBuffer_FixTimes Notifies the console code about the current time (and shifts back times of other entries when the time went backwards) ================ */ void ConBuffer_FixTimes(conbuffer_t *buf) { int i; if(buf->lines_count >= 1) { double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime; if(diff < 0) { for(i = 0; i < buf->lines_count; ++i) CONBUFFER_LINES(buf, i).addtime += diff; } } } /* ================ ConBuffer_DeleteLine Deletes the first line from the console history. ================ */ void ConBuffer_DeleteLine(conbuffer_t *buf) { if(buf->lines_count == 0) return; --buf->lines_count; buf->lines_first = (buf->lines_first + 1) % buf->maxlines; } /* ================ ConBuffer_DeleteLastLine Deletes the last line from the console history. ================ */ void ConBuffer_DeleteLastLine(conbuffer_t *buf) { if(buf->lines_count == 0) return; --buf->lines_count; } /* ================ ConBuffer_BytesLeft Checks if there is space for a line of the given length, and if yes, returns a pointer to the start of such a space, and NULL otherwise. ================ */ static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len) { if(len > buf->textsize) return NULL; if(buf->lines_count == 0) return buf->text; else { char *firstline_start = buf->lines[buf->lines_first].start; char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len; // the buffer is cyclic, so we first have two cases... if(firstline_start < lastline_onepastend) // buffer is contiguous { // put at end? if(len <= buf->text + buf->textsize - lastline_onepastend) return lastline_onepastend; // put at beginning? else if(len <= firstline_start - buf->text) return buf->text; else return NULL; } else // buffer has a contiguous hole { if(len <= firstline_start - lastline_onepastend) return lastline_onepastend; else return NULL; } } } /* ================ ConBuffer_AddLine Appends a given string as a new line to the console. ================ */ void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask) { char *putpos; con_lineinfo_t *p; // developer_memory 1 during shutdown prints while conbuffer_t is being freed if (!buf->active) return; ConBuffer_FixTimes(buf); if(len >= buf->textsize) { // line too large? // only display end of line. line += len - buf->textsize + 1; len = buf->textsize - 1; } while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines) ConBuffer_DeleteLine(buf); memcpy(putpos, line, len); putpos[len] = 0; ++buf->lines_count; //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST); p = &CONBUFFER_LINES_LAST(buf); p->start = putpos; p->len = len; p->addtime = cl.time; p->mask = mask; p->height = -1; // calculate when needed } int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start) { int i; if(start == -1) start = buf->lines_count; for(i = start - 1; i >= 0; --i) { con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); if((l->mask & mask_must) != mask_must) continue; if(l->mask & mask_mustnot) continue; return i; } return -1; } const char *ConBuffer_GetLine(conbuffer_t *buf, int i) { static char copybuf[MAX_INPUTLINE]; // client only con_lineinfo_t *l = &CONBUFFER_LINES(buf, i); size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1; strlcpy(copybuf, l->start, sz); return copybuf; } /* ============================================================================== LOGGING ============================================================================== */ /// \name Logging //@{ cvar_t log_file = {0, "log_file", "", "filename to log messages to"}; cvar_t log_file_stripcolors = {0, "log_file_stripcolors", "0", "strip color codes from log messages"}; cvar_t log_dest_udp = {0, "log_dest_udp", "", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"}; char log_dest_buffer[1400]; // UDP packet size_t log_dest_buffer_pos; unsigned int log_dest_buffer_appending; char crt_log_file [MAX_OSPATH] = ""; qfile_t* logfile = NULL; unsigned char* logqueue = NULL; size_t logq_ind = 0; size_t logq_size = 0; void Log_ConPrint (const char *msg); //@} static void Log_DestBuffer_Init(void) { memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print log_dest_buffer_pos = 5; } static void Log_DestBuffer_Flush_NoLock(void) { lhnetaddress_t log_dest_addr; lhnetsocket_t *log_dest_socket; const char *s = log_dest_udp.string; qboolean have_opened_temp_sockets = false; if(s) if(log_dest_buffer_pos > 5) { ++log_dest_buffer_appending; log_dest_buffer[log_dest_buffer_pos++] = 0; if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one { have_opened_temp_sockets = true; NetConn_OpenServerPorts(true); } while(COM_ParseToken_Console(&s)) if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000)) { log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr); if(!log_dest_socket) log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr); if(log_dest_socket) NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr); } if(have_opened_temp_sockets) NetConn_CloseServerPorts(); --log_dest_buffer_appending; } log_dest_buffer_pos = 0; } /* ==================== Log_DestBuffer_Flush ==================== */ void Log_DestBuffer_Flush(void) { if (con_mutex) Thread_LockMutex(con_mutex); Log_DestBuffer_Flush_NoLock(); if (con_mutex) Thread_UnlockMutex(con_mutex); } static const char* Log_Timestamp (const char *desc) { static char timestamp [128]; // init/shutdown only time_t crt_time; #if _MSC_VER >= 1400 struct tm crt_tm; #else struct tm *crt_tm; #endif char timestring [64]; // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993"); time (&crt_time); #if _MSC_VER >= 1400 localtime_s (&crt_tm, &crt_time); strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm); #else crt_tm = localtime (&crt_time); strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm); #endif if (desc != NULL) dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring); else dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring); return timestamp; } static void Log_Open (void) { if (logfile != NULL || log_file.string[0] == '\0') return; logfile = FS_OpenRealFile(log_file.string, "a", false); if (logfile != NULL) { strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file)); FS_Print (logfile, Log_Timestamp ("Log started")); } } /* ==================== Log_Close ==================== */ void Log_Close (void) { if (logfile == NULL) return; FS_Print (logfile, Log_Timestamp ("Log stopped")); FS_Print (logfile, "\n"); FS_Close (logfile); logfile = NULL; crt_log_file[0] = '\0'; } /* ==================== Log_Start ==================== */ void Log_Start (void) { size_t pos; size_t n; Log_Open (); // Dump the contents of the log queue into the log file and free it if (logqueue != NULL) { unsigned char *temp = logqueue; logqueue = NULL; if(logq_ind != 0) { if (logfile != NULL) FS_Write (logfile, temp, logq_ind); if(*log_dest_udp.string) { for(pos = 0; pos < logq_ind; ) { if(log_dest_buffer_pos == 0) Log_DestBuffer_Init(); n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos); memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n); log_dest_buffer_pos += n; Log_DestBuffer_Flush_NoLock(); pos += n; } } } Mem_Free (temp); logq_ind = 0; logq_size = 0; } } /* ================ Log_ConPrint ================ */ void Log_ConPrint (const char *msg) { static qboolean inprogress = false; // don't allow feedback loops with memory error reports if (inprogress) return; inprogress = true; // Until the host is completely initialized, we maintain a log queue // to store the messages, since the log can't be started before if (logqueue != NULL) { size_t remain = logq_size - logq_ind; size_t len = strlen (msg); // If we need to enlarge the log queue if (len > remain) { size_t factor = ((logq_ind + len) / logq_size) + 1; unsigned char* newqueue; logq_size *= factor; newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); memcpy (newqueue, logqueue, logq_ind); Mem_Free (logqueue); logqueue = newqueue; remain = logq_size - logq_ind; } memcpy (&logqueue[logq_ind], msg, len); logq_ind += len; inprogress = false; return; } // Check if log_file has changed if (strcmp (crt_log_file, log_file.string) != 0) { Log_Close (); Log_Open (); } // If a log file is available if (logfile != NULL) { if (log_file_stripcolors.integer) { // sanitize msg size_t len = strlen(msg); char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1); memcpy (sanitizedmsg, msg, len); SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work. FS_Print (logfile, sanitizedmsg); Mem_Free(sanitizedmsg); } else { FS_Print (logfile, msg); } } inprogress = false; } /* ================ Log_Printf ================ */ void Log_Printf (const char *logfilename, const char *fmt, ...) { qfile_t *file; file = FS_OpenRealFile(logfilename, "a", true); if (file != NULL) { va_list argptr; va_start (argptr, fmt); FS_VPrintf (file, fmt, argptr); va_end (argptr); FS_Close (file); } } /* ============================================================================== CONSOLE ============================================================================== */ /* ================ Con_ToggleConsole_f ================ */ void Con_ToggleConsole_f (void) { if (COM_CheckParm ("-noconsole")) if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER)) return; // only allow the key bind to turn off console // toggle the 'user wants console' bit key_consoleactive ^= KEY_CONSOLEACTIVE_USER; Con_ClearNotify(); } /* ================ Con_ClearNotify ================ */ void Con_ClearNotify (void) { int i; for(i = 0; i < CON_LINES_COUNT; ++i) if(!(CON_LINES(i).mask & CON_MASK_CHAT)) CON_LINES(i).mask |= CON_MASK_HIDENOTIFY; } /* ================ Con_MessageMode_f ================ */ static void Con_MessageMode_f (void) { key_dest = key_message; chat_mode = 0; // "say" if(Cmd_Argc() > 1) { dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); chat_bufferlen = (unsigned int)strlen(chat_buffer); } } /* ================ Con_MessageMode2_f ================ */ static void Con_MessageMode2_f (void) { key_dest = key_message; chat_mode = 1; // "say_team" if(Cmd_Argc() > 1) { dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); chat_bufferlen = (unsigned int)strlen(chat_buffer); } } /* ================ Con_CommandMode_f ================ */ static void Con_CommandMode_f (void) { key_dest = key_message; if(Cmd_Argc() > 1) { dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args()); chat_bufferlen = (unsigned int)strlen(chat_buffer); } chat_mode = -1; // command } /* ================ Con_CheckResize ================ */ void Con_CheckResize (void) { int i, width; float f; f = bound(1, con_textsize.value, 128); if(f != con_textsize.value) Cvar_SetValueQuick(&con_textsize, f); width = (int)floor(vid_conwidth.value / con_textsize.value); width = bound(1, width, con.textsize/4); // FIXME uses con in a non abstracted way if (width == con_linewidth) return; con_linewidth = width; for(i = 0; i < CON_LINES_COUNT; ++i) CON_LINES(i).height = -1; // recalculate when next needed Con_ClearNotify(); con_backscroll = 0; } //[515]: the simplest command ever //LordHavoc: not so simple after I made it print usage... static void Con_Maps_f (void) { if (Cmd_Argc() > 2) { Con_Printf("usage: maps [mapnameprefix]\n"); return; } else if (Cmd_Argc() == 2) GetMapList(Cmd_Argv(1), NULL, 0); else GetMapList("", NULL, 0); } static void Con_ConDump_f (void) { int i; qfile_t *file; if (Cmd_Argc() != 2) { Con_Printf("usage: condump \n"); return; } file = FS_OpenRealFile(Cmd_Argv(1), "w", false); if (!file) { Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1)); return; } if (con_mutex) Thread_LockMutex(con_mutex); for(i = 0; i < CON_LINES_COUNT; ++i) { if (condump_stripcolors.integer) { // sanitize msg size_t len = CON_LINES(i).len; char* sanitizedmsg = (char*)Mem_Alloc(tempmempool, len + 1); memcpy (sanitizedmsg, CON_LINES(i).start, len); SanitizeString(sanitizedmsg, sanitizedmsg); // SanitizeString's in pointer is always ahead of the out pointer, so this should work. FS_Write(file, sanitizedmsg, strlen(sanitizedmsg)); Mem_Free(sanitizedmsg); } else { FS_Write(file, CON_LINES(i).start, CON_LINES(i).len); } FS_Write(file, "\n", 1); } if (con_mutex) Thread_UnlockMutex(con_mutex); FS_Close(file); } void Con_Clear_f (void) { if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_Clear(&con); if (con_mutex) Thread_UnlockMutex(con_mutex); } /* ================ Con_Init ================ */ void Con_Init (void) { con_linewidth = 80; ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool); if (Thread_HasThreads()) con_mutex = Thread_CreateMutex(); // Allocate a log queue, this will be freed after configs are parsed logq_size = MAX_INPUTLINE; logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size); logq_ind = 0; Cvar_RegisterVariable (&sys_colortranslation); Cvar_RegisterVariable (&sys_specialcharactertranslation); Cvar_RegisterVariable (&log_file); Cvar_RegisterVariable (&log_file_stripcolors); Cvar_RegisterVariable (&log_dest_udp); // support for the classic Quake option // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file if (COM_CheckParm ("-condebug") != 0) Cvar_SetQuick (&log_file, "qconsole.log"); // register our cvars Cvar_RegisterVariable (&con_chat); Cvar_RegisterVariable (&con_chatpos); Cvar_RegisterVariable (&con_chatrect_x); Cvar_RegisterVariable (&con_chatrect_y); Cvar_RegisterVariable (&con_chatrect); Cvar_RegisterVariable (&con_chatsize); Cvar_RegisterVariable (&con_chattime); Cvar_RegisterVariable (&con_chatwidth); Cvar_RegisterVariable (&con_notify); Cvar_RegisterVariable (&con_notifyalign); Cvar_RegisterVariable (&con_notifysize); Cvar_RegisterVariable (&con_notifytime); Cvar_RegisterVariable (&con_textsize); Cvar_RegisterVariable (&con_chatsound); // --blub Cvar_RegisterVariable (&con_nickcompletion); Cvar_RegisterVariable (&con_nickcompletion_flags); Cvar_RegisterVariable (&con_completion_playdemo); // *.dem Cvar_RegisterVariable (&con_completion_timedemo); // *.dem Cvar_RegisterVariable (&con_completion_exec); // *.cfg Cvar_RegisterVariable (&condump_stripcolors); // register our commands Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console"); Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone"); Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team"); Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command"); Cmd_AddCommand ("clear", Con_Clear_f, "clear console history"); Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps"); Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)"); con_initialized = true; Con_DPrint("Console initialized.\n"); } void Con_Shutdown (void) { if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_Shutdown(&con); if (con_mutex) Thread_UnlockMutex(con_mutex); if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL; } /* ================ Con_PrintToHistory Handles cursor positioning, line wrapping, etc All console printing must go through this in order to be displayed If no console is visible, the notify window will pop up. ================ */ static void Con_PrintToHistory(const char *txt, int mask) { // process: // \n goes to next line // \r deletes current line and makes a new one static int cr_pending = 0; static char buf[CON_TEXTSIZE]; // con_mutex static int bufpos = 0; if(!con.text) // FIXME uses a non-abstracted property of con return; for(; *txt; ++txt) { if(cr_pending) { ConBuffer_DeleteLastLine(&con); cr_pending = 0; } switch(*txt) { case 0: break; case '\r': ConBuffer_AddLine(&con, buf, bufpos, mask); bufpos = 0; cr_pending = 1; break; case '\n': ConBuffer_AddLine(&con, buf, bufpos, mask); bufpos = 0; break; default: buf[bufpos++] = *txt; if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con { ConBuffer_AddLine(&con, buf, bufpos, mask); bufpos = 0; } break; } } } void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol) { rcon_redirect_sock = sock; rcon_redirect_dest = dest; rcon_redirect_proquakeprotocol = proquakeprotocol; if (rcon_redirect_proquakeprotocol) { // reserve space for the packet header rcon_redirect_buffer[0] = 0; rcon_redirect_buffer[1] = 0; rcon_redirect_buffer[2] = 0; rcon_redirect_buffer[3] = 0; // this is a reply to a CCREQ_RCON rcon_redirect_buffer[4] = (unsigned char)CCREP_RCON; } else memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print rcon_redirect_bufferpos = 5; } static void Con_Rcon_Redirect_Flush(void) { if(rcon_redirect_sock) { rcon_redirect_buffer[rcon_redirect_bufferpos] = 0; if (rcon_redirect_proquakeprotocol) { // update the length in the packet header StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK)); } NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest); } memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print rcon_redirect_bufferpos = 5; rcon_redirect_proquakeprotocol = false; } void Con_Rcon_Redirect_End(void) { Con_Rcon_Redirect_Flush(); rcon_redirect_dest = NULL; rcon_redirect_sock = NULL; } void Con_Rcon_Redirect_Abort(void) { rcon_redirect_dest = NULL; rcon_redirect_sock = NULL; } /* ================ Con_Rcon_AddChar ================ */ /// Adds a character to the rcon buffer. static void Con_Rcon_AddChar(int c) { if(log_dest_buffer_appending) return; ++log_dest_buffer_appending; // if this print is in response to an rcon command, add the character // to the rcon redirect buffer if (rcon_redirect_dest) { rcon_redirect_buffer[rcon_redirect_bufferpos++] = c; if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1) Con_Rcon_Redirect_Flush(); } else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way { if(log_dest_buffer_pos == 0) Log_DestBuffer_Init(); log_dest_buffer[log_dest_buffer_pos++] = c; if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero Log_DestBuffer_Flush_NoLock(); } else log_dest_buffer_pos = 0; --log_dest_buffer_appending; } /** * Convert an RGB color to its nearest quake color. * I'll cheat on this a bit by translating the colors to HSV first, * S and V decide if it's black or white, otherwise, H will decide the * actual color. * @param _r Red (0-255) * @param _g Green (0-255) * @param _b Blue (0-255) * @return A quake color character. */ static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b) { float r = ((float)_r)/255.0; float g = ((float)_g)/255.0; float b = ((float)_b)/255.0; float min = min(r, min(g, b)); float max = max(r, max(g, b)); int h; ///< Hue angle [0,360] float s; ///< Saturation [0,1] float v = max; ///< In HSV v == max [0,1] if(max == min) s = 0; else s = 1.0 - (min/max); // Saturation threshold. We now say 0.2 is the minimum value for a color! if(s < 0.2) { // If the value is less than half, return a black color code. // Otherwise return a white one. if(v < 0.5) return '0'; return '7'; } // Let's get the hue angle to define some colors: if(max == min) h = 0; else if(max == r) h = (int)(60.0 * (g-b)/(max-min))%360; else if(max == g) h = (int)(60.0 * (b-r)/(max-min) + 120); else // if(max == b) redundant check h = (int)(60.0 * (r-g)/(max-min) + 240); if(h < 36) // *red* to orange return '1'; else if(h < 80) // orange over *yellow* to evilish-bright-green return '3'; else if(h < 150) // evilish-bright-green over *green* to ugly bright blue return '2'; else if(h < 200) // ugly bright blue over *bright blue* to darkish blue return '5'; else if(h < 270) // darkish blue over *dark blue* to cool purple return '4'; else if(h < 330) // cool purple over *purple* to ugly swiny red return '6'; else // ugly red to red closes the circly return '1'; } /* ================ Con_MaskPrint ================ */ extern cvar_t timestamps; extern cvar_t timeformat; extern qboolean sys_nostdout; void Con_MaskPrint(int additionalmask, const char *msg) { static int mask = 0; static int index = 0; static char line[MAX_INPUTLINE]; if (con_mutex) Thread_LockMutex(con_mutex); for (;*msg;msg++) { Con_Rcon_AddChar(*msg); // if this is the beginning of a new line, print timestamp if (index == 0) { const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : ""; // reset the color // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7! line[index++] = STRING_COLOR_TAG; // assert( STRING_COLOR_DEFAULT < 10 ) line[index++] = STRING_COLOR_DEFAULT + '0'; // special color codes for chat messages must always come first // for Con_PrintToHistory to work properly if (*msg == 1 || *msg == 2 || *msg == 3) { // play talk wav if (*msg == 1) { if (con_chatsound.value) { if(IS_NEXUIZ_DERIVED(gamemode)) { if(msg[1] == '\r' && cl.foundtalk2wav) S_LocalSound ("sound/misc/talk2.wav"); else S_LocalSound ("sound/misc/talk.wav"); } else { if (msg[1] == '(' && cl.foundtalk2wav) S_LocalSound ("sound/misc/talk2.wav"); else S_LocalSound ("sound/misc/talk.wav"); } } } // Send to chatbox for say/tell (1) and messages (3) // 3 is just so that a message can be sent to the chatbox without a sound. if (*msg == 1 || *msg == 3) mask = CON_MASK_CHAT; line[index++] = STRING_COLOR_TAG; line[index++] = '3'; msg++; Con_Rcon_AddChar(*msg); } // store timestamp for (;*timestamp;index++, timestamp++) if (index < (int)sizeof(line) - 2) line[index] = *timestamp; // add the mask mask |= additionalmask; } // append the character line[index++] = *msg; // if this is a newline character, we have a complete line to print if (*msg == '\n' || index >= (int)sizeof(line) / 2) { // terminate the line line[index] = 0; // send to log file Log_ConPrint(line); // send to scrollable buffer if (con_initialized && cls.state != ca_dedicated) { Con_PrintToHistory(line, mask); } // send to terminal or dedicated server window if (!sys_nostdout) if (developer.integer || !(mask & CON_MASK_DEVELOPER)) { if(sys_specialcharactertranslation.integer) { char *p; const char *q; p = line; while(*p) { int ch = u8_getchar(p, &q); if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20) { *p = qfont_table[ch - 0xE000]; if(q > p+1) memmove(p+1, q, strlen(q)+1); p = p + 1; } else p = p + (q - p); } } if(sys_colortranslation.integer == 1) // ANSI { static char printline[MAX_INPUTLINE * 4 + 3]; // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end // a newline can transform into four bytes, but then prevents the three extra bytes from appearing int lastcolor = 0; const char *in; char *out; int color; for(in = line, out = printline; *in; ++in) { switch(*in) { case STRING_COLOR_TAG: if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) { char r = tolower(in[2]); char g = tolower(in[3]); char b = tolower(in[4]); // it's a hex digit already, so the else part needs no check --blub if(isdigit(r)) r -= '0'; else r -= 87; if(isdigit(g)) g -= '0'; else g -= 87; if(isdigit(b)) b -= '0'; else b -= 87; color = Sys_Con_NearestColor(r * 17, g * 17, b * 17); in += 3; // 3 only, the switch down there does the fourth } else color = in[1]; switch(color) { case STRING_COLOR_TAG: ++in; *out++ = STRING_COLOR_TAG; break; case '0': case '7': // normal color ++in; if(lastcolor == 0) break; else lastcolor = 0; *out++ = 0x1B; *out++ = '['; *out++ = 'm'; break; case '1': // light red ++in; if(lastcolor == 1) break; else lastcolor = 1; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm'; break; case '2': // light green ++in; if(lastcolor == 2) break; else lastcolor = 2; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm'; break; case '3': // yellow ++in; if(lastcolor == 3) break; else lastcolor = 3; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm'; break; case '4': // light blue ++in; if(lastcolor == 4) break; else lastcolor = 4; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm'; break; case '5': // light cyan ++in; if(lastcolor == 5) break; else lastcolor = 5; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm'; break; case '6': // light magenta ++in; if(lastcolor == 6) break; else lastcolor = 6; *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm'; break; // 7 handled above case '8': case '9': // bold normal color ++in; if(lastcolor == 8) break; else lastcolor = 8; *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm'; break; default: *out++ = STRING_COLOR_TAG; break; } break; case '\n': if(lastcolor != 0) { *out++ = 0x1B; *out++ = '['; *out++ = 'm'; lastcolor = 0; } *out++ = *in; break; default: *out++ = *in; break; } } if(lastcolor != 0) { *out++ = 0x1B; *out++ = '['; *out++ = 'm'; } *out++ = 0; Sys_PrintToTerminal(printline); } else if(sys_colortranslation.integer == 2) // Quake { Sys_PrintToTerminal(line); } else // strip { static char printline[MAX_INPUTLINE]; // it can only get shorter here const char *in; char *out; for(in = line, out = printline; *in; ++in) { switch(*in) { case STRING_COLOR_TAG: switch(in[1]) { case STRING_COLOR_RGB_TAG_CHAR: if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) ) { in+=4; break; } *out++ = STRING_COLOR_TAG; *out++ = STRING_COLOR_RGB_TAG_CHAR; ++in; break; case STRING_COLOR_TAG: ++in; *out++ = STRING_COLOR_TAG; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': ++in; break; default: *out++ = STRING_COLOR_TAG; break; } break; default: *out++ = *in; break; } } *out++ = 0; Sys_PrintToTerminal(printline); } } // empty the line buffer index = 0; mask = 0; } } if (con_mutex) Thread_UnlockMutex(con_mutex); } /* ================ Con_MaskPrintf ================ */ void Con_MaskPrintf(int mask, const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); Con_MaskPrint(mask, msg); } /* ================ Con_Print ================ */ void Con_Print(const char *msg) { Con_MaskPrint(CON_MASK_PRINT, msg); } /* ================ Con_Printf ================ */ void Con_Printf(const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); Con_MaskPrint(CON_MASK_PRINT, msg); } /* ================ Con_DPrint ================ */ void Con_DPrint(const char *msg) { if(developer.integer < 0) // at 0, we still add to the buffer but hide return; Con_MaskPrint(CON_MASK_DEVELOPER, msg); } /* ================ Con_DPrintf ================ */ void Con_DPrintf(const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; if(developer.integer < 0) // at 0, we still add to the buffer but hide return; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); Con_MaskPrint(CON_MASK_DEVELOPER, msg); } /* ============================================================================== DRAWING ============================================================================== */ /* ================ Con_DrawInput The input line scrolls horizontally if typing goes beyond the right edge Modified by EvilTypeGuy eviltypeguy@qeradiant.com ================ */ static void Con_DrawInput (void) { int y; int i; char text[sizeof(key_line)+5+1]; // space for ^^xRGB too float x, xo; size_t len_out; int col_out; if (!key_consoleactive) return; // don't draw anything strlcpy(text, key_line, sizeof(text)); // Advanced Console Editing by Radix radix@planetquake.com // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com y = (int)strlen(text); // make the color code visible when the cursor is inside it if(text[key_linepos] != 0) { for(i=1; i < 5 && key_linepos - i > 0; ++i) if(text[key_linepos-i] == STRING_COLOR_TAG) { int caret_pos, ofs = 0; caret_pos = key_linepos - i; if(i == 1 && text[caret_pos+1] == STRING_COLOR_TAG) ofs = 1; else if(i == 1 && isdigit(text[caret_pos+1])) ofs = 2; else if(text[caret_pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(text[caret_pos+2]) && isxdigit(text[caret_pos+3]) && isxdigit(text[caret_pos+4])) ofs = 5; if(ofs && (size_t)(y + ofs + 1) < sizeof(text)) { int carets = 1; while(caret_pos - carets >= 1 && text[caret_pos - carets] == STRING_COLOR_TAG) ++carets; if(carets & 1) { // str^2ing (displayed as string) --> str^2^^2ing (displayed as str^2ing) // str^^ing (displayed as str^ing) --> str^^^^ing (displayed as str^^ing) memmove(&text[caret_pos + ofs + 1], &text[caret_pos], y - caret_pos); text[caret_pos + ofs] = STRING_COLOR_TAG; y += ofs + 1; text[y] = 0; } } break; } } len_out = key_linepos; col_out = -1; xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000); x = vid_conwidth.value * 0.95 - xo; // scroll if(x >= 0) x = 0; // draw it DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE ); // draw a cursor on top of this if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible { if (!utf8_enable.integer) { text[0] = 11 + 130 * key_insert; // either solid or triangle facing right text[1] = 0; } else { size_t len; const char *curbuf; char charbuf16[16]; curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16); memcpy(text, curbuf, len); text[len] = 0; } DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE); } } typedef struct { dp_font_t *font; float alignment; // 0 = left, 0.5 = center, 1 = right float fontsize; float x; float y; float width; float ymin, ymax; const char *continuationString; // PRIVATE: int colorindex; // init to -1 } con_text_info_t; static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth) { con_text_info_t *ti = (con_text_info_t *) passthrough; if(w == NULL) { ti->colorindex = -1; return ti->fontsize * ti->font->maxwidth; } if(maxWidth >= 0) return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char else if(maxWidth == -1) return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font); else { Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth); // Note: this is NOT a Con_Printf, as it could print recursively return 0; } } static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) { (void) passthrough; (void) line; (void) length; (void) width; (void) isContinuation; return 1; } static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation) { con_text_info_t *ti = (con_text_info_t *) passthrough; if(ti->y < ti->ymin - 0.001) (void) 0; else if(ti->y > ti->ymax - ti->fontsize + 0.001) (void) 0; else { int x = (int) (ti->x + (ti->width - width) * ti->alignment); if(isContinuation && *ti->continuationString) x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font); if(length > 0) DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font); } ti->y += ti->fontsize; return 1; } static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString) { int i; int lines = 0; int maxlines = (int) floor(height / fontsize + 0.01f); int startidx; int nskip = 0; int continuationWidth = 0; size_t len; double t = cl.time; // saved so it won't change con_text_info_t ti; ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY; ti.fontsize = fontsize; ti.alignment = alignment_x; ti.width = width; ti.ymin = y; ti.ymax = y + height; ti.continuationString = continuationString; len = 0; Con_WordWidthFunc(&ti, NULL, &len, -1); len = strlen(continuationString); continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &len, -1); // first find the first line to draw by backwards iterating and word wrapping to find their length... startidx = CON_LINES_COUNT; for(i = CON_LINES_COUNT - 1; i >= 0; --i) { con_lineinfo_t *l = &CON_LINES(i); int mylines; if((l->mask & mask_must) != mask_must) continue; if(l->mask & mask_mustnot) continue; if(maxage && (l->addtime < t - maxage)) continue; // WE FOUND ONE! // Calculate its actual height... mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti); if(lines + mylines >= maxlines) { nskip = lines + mylines - maxlines; lines = maxlines; startidx = i; break; } lines += mylines; startidx = i; } // then center according to the calculated amount of lines... ti.x = x; ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize; // then actually draw for(i = startidx; i < CON_LINES_COUNT; ++i) { con_lineinfo_t *l = &CON_LINES(i); if((l->mask & mask_must) != mask_must) continue; if(l->mask & mask_mustnot) continue; if(maxage && (l->addtime < t - maxage)) continue; COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); } return lines; } /* ================ Con_DrawNotify Draws the last few lines of output transparently over the game top ================ */ void Con_DrawNotify (void) { float x, v, xr; float chatstart, notifystart, inputsize, height; float align; char temptext[MAX_INPUTLINE]; int numChatlines; int chatpos; if (con_mutex) Thread_LockMutex(con_mutex); ConBuffer_FixTimes(&con); numChatlines = con_chat.integer; chatpos = con_chatpos.integer; if (con_notify.integer < 0) Cvar_SetValueQuick(&con_notify, 0); if (gamemode == GAME_TRANSFUSION) v = 8; // vertical offset else v = 0; // GAME_NEXUIZ: center, otherwise left justify align = con_notifyalign.value; if(!*con_notifyalign.string) // empty string, evaluated to 0 above { if(IS_OLDNEXUIZ_DERIVED(gamemode)) align = 0.5; } if(numChatlines || !con_chatrect.integer) { if(chatpos == 0) { // first chat, input line, then notify chatstart = v; notifystart = v + (numChatlines + 1) * con_chatsize.value; } else if(chatpos > 0) { // first notify, then (chatpos-1) empty lines, then chat, then input notifystart = v; chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value; } else // if(chatpos < 0) { // first notify, then much space, then chat, then input, then -chatpos-1 empty lines notifystart = v; chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value; } } else { // just notify and input notifystart = v; chatstart = 0; // shut off gcc warning } v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, ""); if(con_chatrect.integer) { x = con_chatrect_x.value * vid_conwidth.value; v = con_chatrect_y.value * vid_conheight.value; } else { x = 0; if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong v = chatstart; } height = numChatlines * con_chatsize.value; if(numChatlines) { Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, "^3 ... "); v += height; } if (key_dest == key_message) { //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on } int colorindex = -1; const char *cursor; char charbuf16[16]; cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16); // LordHavoc: speedup, and other improvements if (chat_mode < 0) dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor); else if(chat_mode) dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor); else dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor); // FIXME word wrap inputsize = (numChatlines ? con_chatsize : con_notifysize).value; xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT); x = min(xr, x); DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT); } if (con_mutex) Thread_UnlockMutex(con_mutex); } /* ================ Con_LineHeight Returns the height of a given console line; calculates it if necessary. ================ */ static int Con_LineHeight(int lineno) { con_lineinfo_t *li = &CON_LINES(lineno); if(li->height == -1) { float width = vid_conwidth.value; con_text_info_t ti; ti.fontsize = con_textsize.value; ti.font = FONT_CONSOLE; li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL); } return li->height; } /* ================ Con_DrawConsoleLine Draws a line of the console; returns its height in lines. If alpha is 0, the line is not drawn, but still wrapped and its height returned. ================ */ static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax) { float width = vid_conwidth.value; con_text_info_t ti; con_lineinfo_t *li = &CON_LINES(lineno); if((li->mask & mask_must) != mask_must) return 0; if((li->mask & mask_mustnot) != 0) return 0; ti.continuationString = ""; ti.alignment = 0; ti.fontsize = con_textsize.value; ti.font = FONT_CONSOLE; ti.x = 0; ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize; ti.ymin = ymin; ti.ymax = ymax; ti.width = width; return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti); } /* ================ Con_LastVisibleLine Calculates the last visible line index and how much to show of it based on con_backscroll. ================ */ static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast) { int lines_seen = 0; int i; if(con_backscroll < 0) con_backscroll = 0; *last = 0; // now count until we saw con_backscroll actual lines for(i = CON_LINES_COUNT - 1; i >= 0; --i) if((CON_LINES(i).mask & mask_must) == mask_must) if((CON_LINES(i).mask & mask_mustnot) == 0) { int h = Con_LineHeight(i); // line is the last visible line? *last = i; if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll) { *limitlast = lines_seen + h - con_backscroll; return; } lines_seen += h; } // if we get here, no line was on screen - scroll so that one line is // visible then. con_backscroll = lines_seen - 1; *limitlast = 1; } /* ================ Con_DrawConsole Draws the console with the solid background The typing input line at the bottom should only be drawn if typing is allowed ================ */ void Con_DrawConsole (int lines) { float alpha, alpha0; double sx, sy; int mask_must = 0; int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER; cachepic_t *conbackpic; if (lines <= 0) return; if (con_mutex) Thread_LockMutex(con_mutex); if (con_backscroll < 0) con_backscroll = 0; con_vislines = lines; r_draw2d_force = true; // draw the background alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game if((alpha = alpha0 * scr_conalphafactor.value) > 0) { sx = scr_conscroll_x.value; sy = scr_conscroll_y.value; conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL; sx *= realtime; sy *= realtime; sx -= floor(sx); sy -= floor(sy); if (conbackpic && conbackpic->tex != r_texture_notexture) DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0); else DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0); } if((alpha = alpha0 * scr_conalpha2factor.value) > 0) { sx = scr_conscroll2_x.value; sy = scr_conscroll2_y.value; conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); sx *= realtime; sy *= realtime; sx -= floor(sx); sy -= floor(sy); if(conbackpic && conbackpic->tex != r_texture_notexture) DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0); } if((alpha = alpha0 * scr_conalpha3factor.value) > 0) { sx = scr_conscroll3_x.value; sy = scr_conscroll3_y.value; conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0); sx *= realtime; sy *= realtime; sx -= floor(sx); sy -= floor(sy); if(conbackpic && conbackpic->tex != r_texture_notexture) DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer, 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha, 0); } DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE); // draw the text #if 0 { int i; int count = CON_LINES_COUNT; float ymax = con_vislines - 2 * con_textsize.value; float y = ymax + con_textsize.value * con_backscroll; for (i = 0;i < count && y >= 0;i++) y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value; // fix any excessive scrollback for the next frame if (i >= count && y >= 0) { con_backscroll -= (int)(y / con_textsize.value); if (con_backscroll < 0) con_backscroll = 0; } } #else if(CON_LINES_COUNT > 0) { int i, last, limitlast; float y; float ymax = con_vislines - 2 * con_textsize.value; Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast); y = ymax - con_textsize.value; if(limitlast) y += (CON_LINES(last).height - limitlast) * con_textsize.value; i = last; for(;;) { y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value; if(i == 0) break; // top of console buffer if(y < 0) break; // top of console window limitlast = 0; --i; } } #endif // draw the input prompt, user text, and cursor if desired Con_DrawInput (); r_draw2d_force = false; if (con_mutex) Thread_UnlockMutex(con_mutex); } /* GetMapList Made by [515] Prints not only map filename, but also its format (q1/q2/q3/hl) and even its message */ //[515]: here is an ugly hack.. two gotos... oh my... *but it works* //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing //LordHavoc: added .ent file loading, and redesigned error handling to still try the .ent file even if the map format is not recognized, this also eliminated one goto //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups... qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength) { fssearch_t *t; char message[1024]; int i, k, max, p, o, min; unsigned char *len; qfile_t *f; unsigned char buf[1024]; dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s); t = FS_Search(message, 1, true); if(!t) return false; if (t->numfilenames > 1) Con_Printf("^1 %i maps found :\n", t->numfilenames); len = (unsigned char *)Z_Malloc(t->numfilenames); min = 666; for(max=i=0;inumfilenames;i++) { k = (int)strlen(t->filenames[i]); k -= 9; if(max < k) max = k; else if(min > k) min = k; len[i] = k; } o = (int)strlen(s); for(i=0;inumfilenames;i++) { int lumpofs = 0, lumplen = 0; char *entities = NULL; const char *data = NULL; char keyname[64]; char entfilename[MAX_QPATH]; char desc[64]; desc[0] = 0; strlcpy(message, "^1ERROR: open failed^7", sizeof(message)); p = 0; f = FS_OpenVirtualFile(t->filenames[i], true); if(f) { strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message)); memset(buf, 0, 1024); FS_Read(f, buf, 1024); if (!memcmp(buf, "IBSP", 4)) { p = LittleLong(((int *)buf)[1]); if (p == Q3BSPVERSION) { q3dheader_t *header = (q3dheader_t *)buf; lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs); lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen); dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p); } else if (p == Q2BSPVERSION) { q2dheader_t *header = (q2dheader_t *)buf; lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs); lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen); dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p); } else dpsnprintf(desc, sizeof(desc), "IBSP%i", p); } else if (BuffLittleLong(buf) == BSPVERSION) { lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); dpsnprintf(desc, sizeof(desc), "BSP29"); } else if (BuffLittleLong(buf) == 30) { lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); dpsnprintf(desc, sizeof(desc), "BSPHL"); } else if (!memcmp(buf, "BSP2", 4)) { lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); dpsnprintf(desc, sizeof(desc), "BSP2"); } else if (!memcmp(buf, "2PSB", 4)) { lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES); lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4); dpsnprintf(desc, sizeof(desc), "BSP2RMQe"); } else { dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf)); } strlcpy(entfilename, t->filenames[i], sizeof(entfilename)); memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5); entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL); if (!entities && lumplen >= 10) { FS_Seek(f, lumpofs, SEEK_SET); entities = (char *)Z_Malloc(lumplen + 1); FS_Read(f, entities, lumplen); } if (entities) { // if there are entities to parse, a missing message key just // means there is no title, so clear the message string now message[0] = 0; data = entities; for (;;) { int l; if (!COM_ParseToken_Simple(&data, false, false, true)) break; if (com_token[0] == '{') continue; if (com_token[0] == '}') break; // skip leading whitespace for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++); for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++) keyname[l] = com_token[k+l]; keyname[l] = 0; if (!COM_ParseToken_Simple(&data, false, false, true)) break; if (developer_extra.integer) Con_DPrintf("key: %s %s\n", keyname, com_token); if (!strcmp(keyname, "message")) { // get the message contents strlcpy(message, com_token, sizeof(message)); break; } } } } if (entities) Z_Free(entities); if(f) FS_Close(f); *(t->filenames[i]+len[i]+5) = 0; Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message); } Con_Print("\n"); for(p=o;pfilenames[0]+5+p); if(k == 0) goto endcomplete; for(i=1;inumfilenames;i++) if(*(t->filenames[i]+5+p) != k) goto endcomplete; } endcomplete: if(p > o && completedname && completednamebufferlength > 0) { memset(completedname, 0, completednamebufferlength); memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1)); } Z_Free(len); FS_FreeSearch(t); return p > o; } /* Con_DisplayList New function for tab-completion system Added by EvilTypeGuy MEGA Thanks to Taniwha */ void Con_DisplayList(const char **list) { int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4); const char **walk = list; while (*walk) { len = (int)strlen(*walk); if (len > maxlen) maxlen = len; walk++; } maxlen += 1; while (*list) { len = (int)strlen(*list); if (pos + maxlen >= width) { Con_Print("\n"); pos = 0; } Con_Print(*list); for (i = 0; i < (maxlen - len); i++) Con_Print(" "); pos += maxlen; list++; } if (pos) Con_Print("\n\n"); } // Now it becomes TRICKY :D --blub static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches. // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there... static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys static int Nicks_matchpos; // co against <<:BLASTER:>> is true!? static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len) { while(a_len) { if(tolower(*a) == tolower(*b)) { if(*a == 0) return 0; --a_len; ++a; ++b; continue; } if(!*a) return -1; if(!*b) return 1; if(*a == ' ') return (*a < *b) ? -1 : 1; if(*b == ' ') ++b; else return (*a < *b) ? -1 : 1; } return 0; } static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len) { char space_char; if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)) { if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) return Nicks_strncasecmp_nospaces(a, b, a_len); return strncasecmp(a, b, a_len); } space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // ignore non alphanumerics of B // if A contains a non-alphanumeric, B must contain it as well though! while(a_len) { qboolean alnum_a, alnum_b; if(tolower(*a) == tolower(*b)) { if(*a == 0) // end of both strings, they're equal return 0; --a_len; ++a; ++b; continue; } // not equal, end of one string? if(!*a) return -1; if(!*b) return 1; // ignore non alphanumerics alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char); alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char); if(!alnum_a) // b must contain this return (*a < *b) ? -1 : 1; if(!alnum_b) ++b; // otherwise, both are alnum, they're just not equal, return the appropriate number else return (*a < *b) ? -1 : 1; } return 0; } /* Nicks_CompleteCountPossible Count the number of possible nicks to complete */ static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon) { char name[128]; int i, p; int match; int spos; int count = 0; if(!con_nickcompletion.integer) return 0; // changed that to 1 if(!line[0])// || !line[1]) // we want at least... 2 written characters return 0; for(i = 0; i < cl.maxclients; ++i) { p = i; if(!cl.scores[p].name[0]) continue; SanitizeString(cl.scores[p].name, name); //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name); if(!name[0]) continue; match = -1; spos = pos - 1; // no need for a minimum of characters :) while(spos >= 0) { if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'') { if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start { --spos; continue; } } if(isCon && spos == 0) break; if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0) match = spos; --spos; } if(match < 0) continue; //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name); strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count])); // the sanitized list strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count])); if(!count) { Nicks_matchpos = match; } Nicks_offset[count] = s - (&line[match]); //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]); ++count; } return count; } static void Cmd_CompleteNicksPrint(int count) { int i; for(i = 0; i < count; ++i) Con_Printf("%s\n", Nicks_list[i]); } static void Nicks_CutMatchesNormal(int count) { // cut match 0 down to the longest possible completion int i; unsigned int c, l; c = (unsigned int)strlen(Nicks_sanlist[0]) - 1; for(i = 1; i < count; ++i) { l = (unsigned int)strlen(Nicks_sanlist[i]) - 1; if(l < c) c = l; for(l = 0; l <= c; ++l) if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l])) { c = l-1; break; } } Nicks_sanlist[0][c+1] = 0; //Con_Printf("List0: %s\n", Nicks_sanlist[0]); } static unsigned int Nicks_strcleanlen(const char *s) { unsigned int l = 0; while(*s) { if( (*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || (*s >= '0' && *s <= '9') || *s == ' ') ++l; ++s; } return l; } static void Nicks_CutMatchesAlphaNumeric(int count) { // cut match 0 down to the longest possible completion int i; unsigned int c, l; char tempstr[sizeof(Nicks_sanlist[0])]; char *a, *b; char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces c = (unsigned int)strlen(Nicks_sanlist[0]); for(i = 0, l = 0; i < (int)c; ++i) { if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') || (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') || (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED { tempstr[l++] = Nicks_sanlist[0][i]; } } tempstr[l] = 0; for(i = 1; i < count; ++i) { a = tempstr; b = Nicks_sanlist[i]; while(1) { if(!*a) break; if(!*b) { *a = 0; break; } if(tolower(*a) == tolower(*b)) { ++a; ++b; continue; } if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char) { // b is alnum, so cut *a = 0; break; } ++b; } } // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit Nicks_CutMatchesNormal(count); //if(!Nicks_sanlist[0][0]) if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) { // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0])); } } static void Nicks_CutMatchesNoSpaces(int count) { // cut match 0 down to the longest possible completion int i; unsigned int c, l; char tempstr[sizeof(Nicks_sanlist[0])]; char *a, *b; c = (unsigned int)strlen(Nicks_sanlist[0]); for(i = 0, l = 0; i < (int)c; ++i) { if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied { tempstr[l++] = Nicks_sanlist[0][i]; } } tempstr[l] = 0; for(i = 1; i < count; ++i) { a = tempstr; b = Nicks_sanlist[i]; while(1) { if(!*a) break; if(!*b) { *a = 0; break; } if(tolower(*a) == tolower(*b)) { ++a; ++b; continue; } if(*b != ' ') { *a = 0; break; } ++b; } } // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit Nicks_CutMatchesNormal(count); //if(!Nicks_sanlist[0][0]) //Con_Printf("TS: %s\n", tempstr); if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr)) { // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0])); } } static void Nicks_CutMatches(int count) { if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY) Nicks_CutMatchesAlphaNumeric(count); else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES) Nicks_CutMatchesNoSpaces(count); else Nicks_CutMatchesNormal(count); } static const char **Nicks_CompleteBuildList(int count) { const char **buf; int bpos = 0; // the list is freed by Con_CompleteCommandLine, so create a char** buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *)); for(; bpos < count; ++bpos) buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos]; Nicks_CutMatches(count); buf[bpos] = NULL; return buf; } /* Nicks_AddLastColor Restores the previous used color, after the autocompleted name. */ static int Nicks_AddLastColor(char *buffer, int pos) { qboolean quote_added = false; int match; int color = STRING_COLOR_DEFAULT + '0'; char r = 0, g = 0, b = 0; if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"') { // we'll have to add a quote :) buffer[pos++] = '\"'; quote_added = true; } if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR) { // add color when no quote was added, or when flags &4? // find last color for(match = Nicks_matchpos-1; match >= 0; --match) { if(buffer[match] == STRING_COLOR_TAG) { if( isdigit(buffer[match+1]) ) { color = buffer[match+1]; break; } else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR) { if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) ) { r = buffer[match+2]; g = buffer[match+3]; b = buffer[match+4]; color = -1; break; } } } } if(!quote_added) { if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4 pos -= 2; else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) ) pos -= 5; } buffer[pos++] = STRING_COLOR_TAG; if (color == -1) { buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR; buffer[pos++] = r; buffer[pos++] = g; buffer[pos++] = b; } else buffer[pos++] = color; } return pos; } int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos) { int n; /*if(!con_nickcompletion.integer) return; is tested in Nicks_CompletionCountPossible */ n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false); if(n == 1) { size_t len; char *msg; msg = Nicks_list[0]; len = min(size - Nicks_matchpos - 3, strlen(msg)); memcpy(&buffer[Nicks_matchpos], msg, len); if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0 len = (int)Nicks_AddLastColor(buffer, Nicks_matchpos+(int)len); buffer[len++] = ' '; buffer[len] = 0; return (int)len; } else if(n > 1) { int len; char *msg; Con_Printf("\n%i possible nicks:\n", n); Cmd_CompleteNicksPrint(n); Nicks_CutMatches(n); msg = Nicks_sanlist[0]; len = (int)min(size - Nicks_matchpos, strlen(msg)); memcpy(&buffer[Nicks_matchpos], msg, len); buffer[Nicks_matchpos + len] = 0; //pos += len; return Nicks_matchpos + len; } return pos; } /* Con_CompleteCommandLine New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha Enhanced to tab-complete map names by [515] */ void Con_CompleteCommandLine (void) { const char *cmd = ""; char *s; const char **list[4] = {0, 0, 0, 0}; char s2[512]; char command[512]; int c, v, a, i, cmd_len, pos, k; int n; // nicks --blub const char *space, *patterns; char vabuf[1024]; //find what we want to complete pos = key_linepos; while(--pos) { k = key_line[pos]; if(k == '\"' || k == ';' || k == ' ' || k == '\'') break; } pos++; s = key_line + pos; strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor key_line[key_linepos] = 0; //hide them space = strchr(key_line + 1, ' '); if(space && pos == (space - key_line) + 1) { strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line))); patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this? if(patterns && !*patterns) patterns = NULL; // get rid of the empty string if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map"))) { //maps search char t[MAX_QPATH]; if (GetMapList(s, t, sizeof(t))) { // first move the cursor key_linepos += (int)strlen(t) - (int)strlen(s); // and now do the actual work *s = 0; strlcat(key_line, t, MAX_INPUTLINE); strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor // and fix the cursor if(key_linepos > (int) strlen(key_line)) key_linepos = (int) strlen(key_line); } return; } else { if(patterns) { char t[MAX_QPATH]; stringlist_t resultbuf, dirbuf; // Usage: // // store completion patterns (space separated) for command foo in con_completion_foo // set con_completion_foo "foodata/*.foodefault *.foo" // foo // // Note: patterns with slash are always treated as absolute // patterns; patterns without slash search in the innermost // directory the user specified. There is no way to "complete into" // a directory as of now, as directories seem to be unknown to the // FS subsystem. // // Examples: // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm" // set con_completion_playdemo "*.dem" // set con_completion_play "*.wav *.ogg" // // TODO somehow add support for directories; these shall complete // to their name + an appended slash. stringlistinit(&resultbuf); stringlistinit(&dirbuf); while(COM_ParseToken_Simple(&patterns, false, false, true)) { fssearch_t *search; if(strchr(com_token, '/')) { search = FS_Search(com_token, true, true); } else { const char *slash = strrchr(s, '/'); if(slash) { strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash strlcat(t, com_token, sizeof(t)); search = FS_Search(t, true, true); } else search = FS_Search(com_token, true, true); } if(search) { for(i = 0; i < search->numfilenames; ++i) if(!strncmp(search->filenames[i], s, strlen(s))) if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE) stringlistappend(&resultbuf, search->filenames[i]); FS_FreeSearch(search); } } // In any case, add directory names { fssearch_t *search; const char *slash = strrchr(s, '/'); if(slash) { strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash strlcat(t, "*", sizeof(t)); search = FS_Search(t, true, true); } else search = FS_Search("*", true, true); if(search) { for(i = 0; i < search->numfilenames; ++i) if(!strncmp(search->filenames[i], s, strlen(s))) if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY) stringlistappend(&dirbuf, search->filenames[i]); FS_FreeSearch(search); } } if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0) { const char *p, *q; unsigned int matchchars; if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1) { dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]); } else if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0) { dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]); } else { stringlistsort(&resultbuf, true); // dirbuf is already sorted Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings); for(i = 0; i < dirbuf.numstrings; ++i) { Con_Printf("^4%s^7/\n", dirbuf.strings[i]); } for(i = 0; i < resultbuf.numstrings; ++i) { Con_Printf("%s\n", resultbuf.strings[i]); } matchchars = sizeof(t) - 1; if(resultbuf.numstrings > 0) { p = resultbuf.strings[0]; q = resultbuf.strings[resultbuf.numstrings - 1]; for(; *p && *p == *q; ++p, ++q); matchchars = (unsigned int)(p - resultbuf.strings[0]); } if(dirbuf.numstrings > 0) { p = dirbuf.strings[0]; q = dirbuf.strings[dirbuf.numstrings - 1]; for(; *p && *p == *q; ++p, ++q); matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0])); } // now p points to the first non-equal character, or to the end // of resultbuf.strings[0]. We want to append the characters // from resultbuf.strings[0] to (not including) p as these are // the unique prefix strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t))); } // first move the cursor key_linepos += (int)strlen(t) - (int)strlen(s); // and now do the actual work *s = 0; strlcat(key_line, t, MAX_INPUTLINE); strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor // and fix the cursor if(key_linepos > (int) strlen(key_line)) key_linepos = (int) strlen(key_line); } stringlistfreecontents(&resultbuf); stringlistfreecontents(&dirbuf); return; // bail out, when we complete for a command that wants a file name } } } // Count number of possible matches and print them c = Cmd_CompleteCountPossible(s); if (c) { Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":"); Cmd_CompleteCommandPrint(s); } v = Cvar_CompleteCountPossible(s); if (v) { Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":"); Cvar_CompleteCvarPrint(s); } a = Cmd_CompleteAliasCountPossible(s); if (a) { Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":"); Cmd_CompleteAliasPrint(s); } n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true); if (n) { Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":"); Cmd_CompleteNicksPrint(n); } if (!(c + v + a + n)) // No possible matches { if(s2[0]) strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos); return; } if (c) cmd = *(list[0] = Cmd_CompleteBuildList(s)); if (v) cmd = *(list[1] = Cvar_CompleteBuildList(s)); if (a) cmd = *(list[2] = Cmd_CompleteAliasBuildList(s)); if (n) cmd = *(list[3] = Nicks_CompleteBuildList(n)); for (cmd_len = (int)strlen(s);;cmd_len++) { const char **l; for (i = 0; i < 3; i++) if (list[i]) for (l = list[i];*l;l++) if ((*l)[cmd_len] != cmd[cmd_len]) goto done; // all possible matches share this character, so we continue... if (!cmd[cmd_len]) { // if all matches ended at the same position, stop // (this means there is only one match) break; } } done: // prevent a buffer overrun by limiting cmd_len according to remaining space cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos); if (cmd) { key_linepos = pos; memcpy(&key_line[key_linepos], cmd, cmd_len); key_linepos += cmd_len; // if there is only one match, add a space after it if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1) { if(n) { // was a nick, might have an offset, and needs colors ;) --blub key_linepos = pos - Nicks_offset[0]; cmd_len = (int)strlen(Nicks_list[0]); cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos); memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len); key_linepos += cmd_len; if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0 key_linepos = Nicks_AddLastColor(key_line, key_linepos); } key_line[key_linepos++] = ' '; } } // use strlcat to avoid a buffer overrun key_line[key_linepos] = 0; strlcat(key_line, s2, sizeof(key_line)); // free the command, cvar, and alias lists for (i = 0; i < 4; i++) if (list[i]) Mem_Free((void *)list[i]); } darkplaces/darkplaces-dedicated-vs2012.vcxproj0000664000175000017500000004214713067716216020565 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {389AE334-D907-4069-90B3-F0551B3EFDE9} darkplacesdedicated Win32Proj darkplaces-dedicated-vs2012 Application v110 MultiByte true Application v110 MultiByte Application v110 MultiByte true Application v110 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Console MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Console MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX64 darkplaces/snd_3dras.h0000664000175000017500000000335613067716222014225 0ustar kalevkalev//BSD #ifndef SND_3DRAS_H #define SND_3DRAS_H #include "sound.h" #define DEFAULT_SOUND_PACKET_VOLUME 255 #define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 #define CHANNELFLAG_NONE 0 #define CHANNELFLAG_FORCELOOP (1 << 0) // force looping even if the sound is not looped #define CHANNELFLAG_LOCALSOUND (1 << 1) // INTERNAL USE. Not settable by S_SetChannelFlag #define CHANNELFLAG_PAUSED (1 << 2) #define CHANNELFLAG_FULLVOLUME (1 << 3) // isn't affected by the general volume #define SFXFLAG_NONE 0 //#define SFXFLAG_FILEMISSING (1 << 0) // wasn't able to load the associated sound file #define SFXFLAG_SERVERSOUND (1 << 1) // the sfx is part of the server precache list //#define SFXFLAG_STREAMED (1 << 2) // informative only. You shouldn't need to know that #define SFXFLAG_PERMANENTLOCK (1 << 3) // can never be freed (ex: used by the client code) typedef struct channel_s{ struct channel_s* next; void* rasptr;//Sound Event // This is also used to indicate a unused slot (when it's pointing to 0) int entnum;// to allow overriding a specific sound int entchannel; unsigned int id; } channel_t; typedef struct entnum_s{ struct entnum_s *next; int entnum; vec3_t lastloc; //Since DP has no way of tracking the deletion, we will use this instead (great jumps indicate teleport or new ent void *rasptr;//Sound Source // This is also used to indicate a unused slot (when it's pointing to 0) } entnum_t; struct sfx_s{ struct sfx_s *next; char name[MAX_QPATH]; void* rasptr; //Sound Data// The sound data allocated in the lib int locks; unsigned int flags; // cf SFXFLAG_* defines //unsigned int loopstart; // in sample frames. equals total_length if not looped //unsigned int total_length; // in sample frames }; #endif darkplaces/common.h0000664000175000017500000003655013067716216013642 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef COMMON_H #define COMMON_H /// MSVC has a different name for several standard functions #ifdef WIN32 # define strcasecmp _stricmp # define strncasecmp _strnicmp #endif // Create our own define for Mac OS X #if defined(__APPLE__) && defined(__MACH__) # define MACOSX #endif #ifdef SUNOS #include ///< Needed for FNDELAY #endif //============================================================================ typedef struct sizebuf_s { qboolean allowoverflow; ///< if false, do a Sys_Error qboolean overflowed; ///< set to true if the buffer size failed unsigned char *data; int maxsize; int cursize; int readcount; qboolean badread; // set if a read goes beyond end of message } sizebuf_t; void SZ_Clear (sizebuf_t *buf); unsigned char *SZ_GetSpace (sizebuf_t *buf, int length); void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length); void SZ_HexDumpToConsole(const sizebuf_t *buf); void Com_HexDumpToConsole(const unsigned char *data, int size); unsigned short CRC_Block(const unsigned char *data, size_t size); unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size); // for hash lookup functions that use strcasecmp for comparison unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence); // these are actually md4sum (mdfour.c) unsigned Com_BlockChecksum (void *buffer, int length); void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf); void COM_Init_Commands(void); //============================================================================ // Endianess handling //============================================================================ // check mem_bigendian if you need to know the system byte order /*! \name Byte order functions. * @{ */ // unaligned memory access crashes on some platform, so always read bytes... #define BigShort(l) BuffBigShort((unsigned char *)&(l)) #define LittleShort(l) BuffLittleShort((unsigned char *)&(l)) #define BigLong(l) BuffBigLong((unsigned char *)&(l)) #define LittleLong(l) BuffLittleLong((unsigned char *)&(l)) #define BigFloat(l) BuffBigFloat((unsigned char *)&(l)) #define LittleFloat(l) BuffLittleFloat((unsigned char *)&(l)) /// Extract a big endian 32bit float from the given \p buffer. float BuffBigFloat (const unsigned char *buffer); /// Extract a big endian 32bit int from the given \p buffer. int BuffBigLong (const unsigned char *buffer); /// Extract a big endian 16bit short from the given \p buffer. short BuffBigShort (const unsigned char *buffer); /// Extract a little endian 32bit float from the given \p buffer. float BuffLittleFloat (const unsigned char *buffer); /// Extract a little endian 32bit int from the given \p buffer. int BuffLittleLong (const unsigned char *buffer); /// Extract a little endian 16bit short from the given \p buffer. short BuffLittleShort (const unsigned char *buffer); /// Encode a big endian 32bit int to the given \p buffer void StoreBigLong (unsigned char *buffer, unsigned int i); /// Encode a big endian 16bit int to the given \p buffer void StoreBigShort (unsigned char *buffer, unsigned short i); /// Encode a little endian 32bit int to the given \p buffer void StoreLittleLong (unsigned char *buffer, unsigned int i); /// Encode a little endian 16bit int to the given \p buffer void StoreLittleShort (unsigned char *buffer, unsigned short i); //@} //============================================================================ // these versions are purely for internal use, never sent in network protocol // (use Protocol_EnumForNumber and Protocol_NumberToEnum to convert) typedef enum protocolversion_e { PROTOCOL_UNKNOWN, PROTOCOL_DARKPLACES7, ///< added QuakeWorld-style movement protocol to allow more consistent prediction PROTOCOL_DARKPLACES6, ///< various changes PROTOCOL_DARKPLACES5, ///< uses EntityFrame5 entity snapshot encoder/decoder which is based on a Tribes networking article at http://www.garagegames.com/articles/networking1/ PROTOCOL_DARKPLACES4, ///< various changes PROTOCOL_DARKPLACES3, ///< uses EntityFrame4 entity snapshot encoder/decoder which is broken, this attempted to do partial snapshot updates on a QuakeWorld-like protocol, but it is broken and impossible to fix PROTOCOL_DARKPLACES2, ///< various changes PROTOCOL_DARKPLACES1, ///< uses EntityFrame entity snapshot encoder/decoder which is a QuakeWorld-like entity snapshot delta compression method PROTOCOL_QUAKEDP, ///< darkplaces extended quake protocol (used by TomazQuake and others), backwards compatible as long as no extended features are used PROTOCOL_NEHAHRAMOVIE, ///< Nehahra movie protocol, a big nasty hack dating back to early days of the Quake Standards Group (but only ever used by neh_gl.exe), this is potentially backwards compatible with quake protocol as long as no extended features are used (but in actuality the neh_gl.exe which wrote this protocol ALWAYS wrote the extended information) PROTOCOL_QUAKE, ///< quake (aka netquake/normalquake/nq) protocol PROTOCOL_QUAKEWORLD, ///< quakeworld protocol PROTOCOL_NEHAHRABJP, ///< same as QUAKEDP but with 16bit modelindex PROTOCOL_NEHAHRABJP2, ///< same as NEHAHRABJP but with 16bit soundindex PROTOCOL_NEHAHRABJP3 ///< same as NEHAHRABJP2 but with some changes } protocolversion_t; /*! \name Message IO functions. * Handles byte ordering and avoids alignment errors * @{ */ void MSG_InitReadBuffer (sizebuf_t *buf, unsigned char *data, int size); void MSG_WriteChar (sizebuf_t *sb, int c); void MSG_WriteByte (sizebuf_t *sb, int c); void MSG_WriteShort (sizebuf_t *sb, int c); void MSG_WriteLong (sizebuf_t *sb, int c); void MSG_WriteFloat (sizebuf_t *sb, vec_t f); void MSG_WriteString (sizebuf_t *sb, const char *s); void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s); void MSG_WriteAngle8i (sizebuf_t *sb, vec_t f); void MSG_WriteAngle16i (sizebuf_t *sb, vec_t f); void MSG_WriteAngle32f (sizebuf_t *sb, vec_t f); void MSG_WriteCoord13i (sizebuf_t *sb, vec_t f); void MSG_WriteCoord16i (sizebuf_t *sb, vec_t f); void MSG_WriteCoord32f (sizebuf_t *sb, vec_t f); void MSG_WriteCoord (sizebuf_t *sb, vec_t f, protocolversion_t protocol); void MSG_WriteVector (sizebuf_t *sb, const vec3_t v, protocolversion_t protocol); void MSG_WriteAngle (sizebuf_t *sb, vec_t f, protocolversion_t protocol); void MSG_BeginReading (sizebuf_t *sb); int MSG_ReadLittleShort (sizebuf_t *sb); int MSG_ReadBigShort (sizebuf_t *sb); int MSG_ReadLittleLong (sizebuf_t *sb); int MSG_ReadBigLong (sizebuf_t *sb); float MSG_ReadLittleFloat (sizebuf_t *sb); float MSG_ReadBigFloat (sizebuf_t *sb); char *MSG_ReadString (sizebuf_t *sb, char *string, size_t maxstring); int MSG_ReadBytes (sizebuf_t *sb, int numbytes, unsigned char *out); #define MSG_ReadChar(sb) ((sb)->readcount >= (sb)->cursize ? ((sb)->badread = true, -1) : (signed char)(sb)->data[(sb)->readcount++]) #define MSG_ReadByte(sb) ((sb)->readcount >= (sb)->cursize ? ((sb)->badread = true, -1) : (unsigned char)(sb)->data[(sb)->readcount++]) #define MSG_ReadShort MSG_ReadLittleShort #define MSG_ReadLong MSG_ReadLittleLong #define MSG_ReadFloat MSG_ReadLittleFloat float MSG_ReadAngle8i (sizebuf_t *sb); float MSG_ReadAngle16i (sizebuf_t *sb); float MSG_ReadAngle32f (sizebuf_t *sb); float MSG_ReadCoord13i (sizebuf_t *sb); float MSG_ReadCoord16i (sizebuf_t *sb); float MSG_ReadCoord32f (sizebuf_t *sb); float MSG_ReadCoord (sizebuf_t *sb, protocolversion_t protocol); void MSG_ReadVector (sizebuf_t *sb, vec3_t v, protocolversion_t protocol); float MSG_ReadAngle (sizebuf_t *sb, protocolversion_t protocol); //@} //============================================================================ typedef float (*COM_WordWidthFunc_t) (void *passthrough, const char *w, size_t *length, float maxWidth); // length is updated to the longest fitting string into maxWidth; if maxWidth < 0, all characters are used and length is used as is typedef int (*COM_LineProcessorFunc) (void *passthrough, const char *line, size_t length, float width, qboolean isContination); int COM_Wordwrap(const char *string, size_t length, float continuationSize, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL); extern char com_token[MAX_INPUTLINE]; int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments); int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline); int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline); int COM_ParseToken_Console(const char **datapointer); extern int com_argc; extern const char **com_argv; extern int com_selffd; int COM_CheckParm (const char *parm); void COM_Init (void); void COM_Shutdown (void); void COM_InitGameType (void); char *va(char *buf, size_t buflen, const char *format, ...) DP_FUNC_PRINTF(3); // does a varargs printf into provided buffer, returns buffer (so it can be called in-line unlike dpsnprintf) // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead #ifdef snprintf # undef snprintf #endif #define snprintf DO_NOT_USE_SNPRINTF__USE_DPSNPRINTF #ifdef vsnprintf # undef vsnprintf #endif #define vsnprintf DO_NOT_USE_VSNPRINTF__USE_DPVSNPRINTF // dpsnprintf and dpvsnprintf // return the number of printed characters, excluding the final '\0' // or return -1 if the buffer isn't big enough to contain the entire string. // buffer is ALWAYS null-terminated extern int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) DP_FUNC_PRINTF(3); extern int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args); // A bunch of functions are forbidden for security reasons (and also to please MSVS 2005, for some of them) // LordHavoc: added #undef lines here to avoid warnings in Linux #undef strcat #define strcat DO_NOT_USE_STRCAT__USE_STRLCAT_OR_MEMCPY #undef strncat #define strncat DO_NOT_USE_STRNCAT__USE_STRLCAT_OR_MEMCPY #undef strcpy #define strcpy DO_NOT_USE_STRCPY__USE_STRLCPY_OR_MEMCPY #undef strncpy #define strncpy DO_NOT_USE_STRNCPY__USE_STRLCPY_OR_MEMCPY //#undef sprintf //#define sprintf DO_NOT_USE_SPRINTF__USE_DPSNPRINTF //============================================================================ extern struct cvar_s registered; extern struct cvar_s cmdline; typedef enum userdirmode_e { USERDIRMODE_NOHOME, // basedir only USERDIRMODE_HOME, // Windows basedir, general POSIX (~/.) USERDIRMODE_MYGAMES, // pre-Vista (My Documents/My Games/), general POSIX (~/.) USERDIRMODE_SAVEDGAMES, // Vista (%USERPROFILE%/Saved Games/), OSX (~/Library/Application Support/), Linux (~/.config) USERDIRMODE_COUNT } userdirmode_t; typedef enum gamemode_e { GAME_NORMAL, GAME_HIPNOTIC, GAME_ROGUE, GAME_QUOTH, GAME_NEHAHRA, GAME_NEXUIZ, GAME_XONOTIC, GAME_TRANSFUSION, GAME_GOODVSBAD2, GAME_TEU, GAME_BATTLEMECH, GAME_ZYMOTIC, GAME_SETHERAL, GAME_TENEBRAE, // full of evil hackery GAME_NEOTERIC, GAME_OPENQUARTZ, //this game sucks GAME_PRYDON, GAME_DELUXEQUAKE, GAME_THEHUNTED, GAME_DEFEATINDETAIL2, GAME_DARSANA, GAME_CONTAGIONTHEORY, GAME_EDU2P, GAME_PROPHECY, GAME_BLOODOMNICIDE, GAME_STEELSTORM, // added by motorsep GAME_STEELSTORM2, // added by motorsep GAME_SSAMMO, // added by motorsep GAME_STEELSTORMREVENANTS, // added by motorsep 07/19/2015 GAME_TOMESOFMEPHISTOPHELES, // added by motorsep GAME_STRAPBOMB, // added by motorsep for Urre GAME_MOONHELM, GAME_VORETOURNAMENT, GAME_COUNT } gamemode_t; // Master switch for some hacks/changes that eventually should become cvars. #define IS_NEXUIZ_DERIVED(g) ((g) == GAME_NEXUIZ || (g) == GAME_XONOTIC || (g) == GAME_VORETOURNAMENT) // Pre-csqcmodels era. #define IS_OLDNEXUIZ_DERIVED(g) ((g) == GAME_NEXUIZ || (g) == GAME_VORETOURNAMENT) extern gamemode_t gamemode; extern const char *gamename; extern const char *gamenetworkfiltername; extern const char *gamedirname1; extern const char *gamedirname2; extern const char *gamescreenshotname; extern const char *gameuserdirname; extern char com_modname[MAX_OSPATH]; void COM_ChangeGameTypeForGameDirs(void); void COM_ToLowerString (const char *in, char *out, size_t size_out); void COM_ToUpperString (const char *in, char *out, size_t size_out); int COM_StringBeginsWith(const char *s, const char *match); int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix); size_t COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets); void COM_ToLowerString (const char *in, char *out, size_t size_out); void COM_ToUpperString (const char *in, char *out, size_t size_out); typedef struct stringlist_s { /// maxstrings changes as needed, causing reallocation of strings[] array int maxstrings; int numstrings; char **strings; } stringlist_t; int matchpattern(const char *in, const char *pattern, int caseinsensitive); int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one); void stringlistinit(stringlist_t *list); void stringlistfreecontents(stringlist_t *list); void stringlistappend(stringlist_t *list, const char *text); void stringlistsort(stringlist_t *list, qboolean uniq); void listdirectory(stringlist_t *list, const char *basepath, const char *path); char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength); void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value); void InfoString_Print(char *buffer); // strlcat and strlcpy, from OpenBSD // Most (all?) BSDs already have them #if defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(MACOSX) # define HAVE_STRLCAT 1 # define HAVE_STRLCPY 1 #endif #ifndef HAVE_STRLCAT /*! * Appends src to string dst of size siz (unlike strncat, siz is the * full size of dst, not space left). At most siz-1 characters * will be copied. Always NUL terminates (unless siz <= strlen(dst)). * Returns strlen(src) + MIN(siz, strlen(initial dst)). * If retval >= siz, truncation occurred. */ size_t strlcat(char *dst, const char *src, size_t siz); #endif // #ifndef HAVE_STRLCAT #ifndef HAVE_STRLCPY /*! * Copy src to string dst of size siz. At most siz-1 characters * will be copied. Always NUL terminates (unless siz == 0). * Returns strlen(src); if retval >= siz, truncation occurred. */ size_t strlcpy(char *dst, const char *src, size_t siz); #endif // #ifndef HAVE_STRLCPY void FindFraction(double val, int *num, int *denom, int denomMax); // decodes XPM file to XPM array (as if #include'd) char **XPM_DecodeString(const char *in); size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen); #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #endif darkplaces/vid_glx.c0000664000175000017500000014417313067716222013777 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if !defined(__APPLE__) && !defined(__MACH__) && !defined(SUNOS) //#define USEDGA #endif #include #include #include #include #include #include // TODO possibly ifdef this out on non-supporting systems... Solaris (as always)? #include #include "quakedef.h" #include "dpsoftrast.h" #include #include #include #include #ifdef USEDGA #include #endif #include #include #include #include // get the Uchar type #include "utf8lib.h" #include "image.h" #include "nexuiz.xpm" #include "darkplaces.xpm" // Tell startup code that we have a client int cl_available = true; // note: if we used the XRandR extension we could support refresh rates qboolean vid_supportrefreshrate = false; //GLX prototypes XVisualInfo *(GLAPIENTRY *qglXChooseVisual)(Display *dpy, int screen, int *attribList); GLXContext (GLAPIENTRY *qglXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); void (GLAPIENTRY *qglXDestroyContext)(Display *dpy, GLXContext ctx); Bool (GLAPIENTRY *qglXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); void (GLAPIENTRY *qglXSwapBuffers)(Display *dpy, GLXDrawable drawable); const char *(GLAPIENTRY *qglXQueryExtensionsString)(Display *dpy, int screen); //GLX_ARB_get_proc_address void *(GLAPIENTRY *qglXGetProcAddressARB)(const GLubyte *procName); static dllfunction_t getprocaddressfuncs[] = { {"glXGetProcAddressARB", (void **) &qglXGetProcAddressARB}, {NULL, NULL} }; //GLX_SGI_swap_control GLint (GLAPIENTRY *qglXSwapIntervalSGI)(GLint interval); static dllfunction_t swapcontrolfuncs[] = { {"glXSwapIntervalSGI", (void **) &qglXSwapIntervalSGI}, {NULL, NULL} }; static Display *vidx11_display = NULL; static int vidx11_screen; static Window win, root; static GLXContext ctx = NULL; static GC vidx11_gc = NULL; static XImage *vidx11_ximage[2] = { NULL, NULL }; static int vidx11_ximage_pos = 0; static XShmSegmentInfo vidx11_shminfo[2]; static int vidx11_shmevent = -1; static int vidx11_shmwait = 0; // number of frames outstanding Atom wm_delete_window_atom; Atom net_wm_state_atom; Atom net_wm_state_hidden_atom; Atom net_wm_state_fullscreen_atom; Atom net_wm_icon; Atom cardinal; #define KEY_MASK (KeyPressMask | KeyReleaseMask) #define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \ PointerMotionMask | ButtonMotionMask) #define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | \ StructureNotifyMask | FocusChangeMask | EnterWindowMask | \ LeaveWindowMask) static qboolean mouse_avail = true; static qboolean vid_usingmousegrab = false; static qboolean vid_usingmouse = false; static qboolean vid_usinghidecursor = false; static qboolean vid_usingvsync = false; static qboolean vid_usevsync = false; static qboolean vid_x11_hardwaregammasupported = false; #ifdef USEDGA static qboolean vid_x11_dgasupported = false; #endif static int vid_x11_gammarampsize = 0; #ifdef USEDGA cvar_t vid_dgamouse = {CVAR_SAVE, "vid_dgamouse", "0", "make use of DGA mouse input"}; static qboolean vid_usingdgamouse = false; #endif qboolean vidmode_ext = false; static int win_x, win_y; static XF86VidModeModeInfo init_vidmode, game_vidmode; static qboolean vid_isfullscreen = false; static qboolean vid_isvidmodefullscreen = false; static qboolean vid_isdesktopfullscreen = false; static qboolean vid_isoverrideredirect = false; static vid_mode_t desktop_mode; static Visual *vidx11_visual; static Colormap vidx11_colormap; /*-----------------------------------------------------------------------*/ // extern long keysym2ucs(KeySym keysym); // LordHavoc: suppress warning just in this case, it's not worth having a header file for this... static void DP_Xutf8LookupString(XKeyEvent * ev, Uchar *uch, KeySym * keysym_return, Status * status_return) { int rc; KeySym keysym; int codepoint; char buffer[64]; int nbytes = sizeof(buffer); rc = XLookupString(ev, buffer, nbytes, &keysym, NULL); if (rc > 0) { codepoint = buffer[0] & 0xFF; } else { codepoint = keysym2ucs(keysym); } if (codepoint < 0) { if (keysym == None) { *status_return = XLookupNone; } else { *status_return = XLookupKeySym; *keysym_return = keysym; } *uch = 0; return; } *uch = codepoint; if (keysym != None) { *keysym_return = keysym; *status_return = XLookupBoth; } else { *status_return = XLookupChars; } } static int XLateKey(XKeyEvent *ev, Uchar *ascii) { int key = 0; //char buf[64]; KeySym keysym, shifted; Status status; keysym = XLookupKeysym (ev, 0); DP_Xutf8LookupString(ev, ascii, &shifted, &status); switch(keysym) { case XK_KP_Page_Up: key = K_KP_PGUP; break; case XK_Page_Up: key = K_PGUP; break; case XK_KP_Page_Down: key = K_KP_PGDN; break; case XK_Page_Down: key = K_PGDN; break; case XK_KP_Home: key = K_KP_HOME; break; case XK_Home: key = K_HOME; break; case XK_KP_End: key = K_KP_END; break; case XK_End: key = K_END; break; case XK_KP_Left: key = K_KP_LEFTARROW; break; case XK_Left: key = K_LEFTARROW; break; case XK_KP_Right: key = K_KP_RIGHTARROW; break; case XK_Right: key = K_RIGHTARROW; break; case XK_KP_Down: key = K_KP_DOWNARROW; break; case XK_Down: key = K_DOWNARROW; break; case XK_KP_Up: key = K_KP_UPARROW; break; case XK_Up: key = K_UPARROW; break; case XK_Escape: key = K_ESCAPE; break; case XK_KP_Enter: key = K_KP_ENTER; break; case XK_Return: key = K_ENTER; break; case XK_Tab: key = K_TAB; break; case XK_F1: key = K_F1; break; case XK_F2: key = K_F2; break; case XK_F3: key = K_F3; break; case XK_F4: key = K_F4; break; case XK_F5: key = K_F5; break; case XK_F6: key = K_F6; break; case XK_F7: key = K_F7; break; case XK_F8: key = K_F8; break; case XK_F9: key = K_F9; break; case XK_F10: key = K_F10; break; case XK_F11: key = K_F11; break; case XK_F12: key = K_F12; break; case XK_BackSpace: key = K_BACKSPACE; break; case XK_KP_Delete: key = K_KP_DEL; break; case XK_Delete: key = K_DEL; break; case XK_Pause: key = K_PAUSE; break; case XK_Shift_L: case XK_Shift_R: key = K_SHIFT; break; case XK_Execute: case XK_Control_L: case XK_Control_R: key = K_CTRL; break; case XK_Alt_L: case XK_Meta_L: case XK_ISO_Level3_Shift: case XK_Alt_R: case XK_Meta_R: key = K_ALT; break; case XK_KP_Begin: key = K_KP_5; break; case XK_Insert:key = K_INS; break; case XK_KP_Insert: key = K_KP_INS; break; case XK_KP_Multiply: key = K_KP_MULTIPLY; break; case XK_KP_Add: key = K_KP_PLUS; break; case XK_KP_Subtract: key = K_KP_MINUS; break; case XK_KP_Divide: key = K_KP_SLASH; break; case XK_Num_Lock: key = K_NUMLOCK; break; case XK_Caps_Lock: key = K_CAPSLOCK; break; case XK_Scroll_Lock: key = K_SCROLLOCK; break; case XK_asciicircum: *ascii = key = '^'; break; // for some reason, XLookupString returns "" on this one for Grunt|2 case XK_section: *ascii = key = '~'; break; default: if (keysym < 32) break; if (keysym >= 'A' && keysym <= 'Z') key = keysym - 'A' + 'a'; else key = keysym; break; } return key; } static Cursor CreateNullCursor(Display *display, Window root) { Pixmap cursormask; XGCValues xgc; GC gc; XColor dummycolour; Cursor cursor; cursormask = XCreatePixmap(display, root, 1, 1, 1); xgc.function = GXclear; gc = XCreateGC(display, cursormask, GCFunction, &xgc); XFillRectangle(display, cursormask, gc, 0, 0, 1, 1); dummycolour.pixel = 0; dummycolour.red = 0; dummycolour.flags = 04; cursor = XCreatePixmapCursor(display, cursormask, cursormask, &dummycolour,&dummycolour, 0,0); XFreePixmap(display,cursormask); XFreeGC(display,gc); return cursor; } void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) { static int originalmouseparms_num; static int originalmouseparms_denom; static int originalmouseparms_threshold; static qboolean restore_spi; #ifdef USEDGA qboolean usedgamouse; #endif if (!vidx11_display || !win) return; if (relative) fullscreengrab = true; if (!mouse_avail) fullscreengrab = relative = hidecursor = false; #ifdef USEDGA usedgamouse = relative && vid_dgamouse.integer; if (!vid_x11_dgasupported) usedgamouse = false; if (fullscreengrab && vid_usingmouse && (vid_usingdgamouse != usedgamouse)) VID_SetMouse(false, false, false); // ungrab first! #endif if (vid_usingmousegrab != fullscreengrab) { vid_usingmousegrab = fullscreengrab; cl_ignoremousemoves = 2; if (fullscreengrab) { XGrabPointer(vidx11_display, win, True, 0, GrabModeAsync, GrabModeAsync, win, None, CurrentTime); if (vid_grabkeyboard.integer || vid_isoverrideredirect) XGrabKeyboard(vidx11_display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime); } else { XUngrabPointer(vidx11_display, CurrentTime); XUngrabKeyboard(vidx11_display, CurrentTime); } } if (relative) { if (!vid_usingmouse) { XWindowAttributes attribs_1; XSetWindowAttributes attribs_2; XGetWindowAttributes(vidx11_display, win, &attribs_1); attribs_2.event_mask = attribs_1.your_event_mask | KEY_MASK | MOUSE_MASK; XChangeWindowAttributes(vidx11_display, win, CWEventMask, &attribs_2); #ifdef USEDGA vid_usingdgamouse = usedgamouse; if (usedgamouse) { XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), XF86DGADirectMouse); XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); } else #endif XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2); // COMMANDLINEOPTION: X11 Input: -noforcemparms disables setting of mouse parameters (not used with DGA, windows only) #ifdef USEDGA if (!COM_CheckParm ("-noforcemparms") && !usedgamouse) #else if (!COM_CheckParm ("-noforcemparms")) #endif { XGetPointerControl(vidx11_display, &originalmouseparms_num, &originalmouseparms_denom, &originalmouseparms_threshold); XChangePointerControl (vidx11_display, true, false, 1, 1, -1); // TODO maybe change threshold here, or remove this comment restore_spi = true; } else restore_spi = false; cl_ignoremousemoves = 2; vid_usingmouse = true; } } else { if (vid_usingmouse) { #ifdef USEDGA if (vid_usingdgamouse) XF86DGADirectVideo(vidx11_display, DefaultScreen(vidx11_display), 0); vid_usingdgamouse = false; #endif cl_ignoremousemoves = 2; if (restore_spi) XChangePointerControl (vidx11_display, true, true, originalmouseparms_num, originalmouseparms_denom, originalmouseparms_threshold); restore_spi = false; vid_usingmouse = false; } } if (vid_usinghidecursor != hidecursor) { vid_usinghidecursor = hidecursor; if (hidecursor) XDefineCursor(vidx11_display, win, CreateNullCursor(vidx11_display, win)); else XUndefineCursor(vidx11_display, win); } } static keynum_t buttonremap[18] = { K_MOUSE1, K_MOUSE3, K_MOUSE2, K_MWHEELUP, K_MWHEELDOWN, K_MOUSE4, K_MOUSE5, K_MOUSE6, K_MOUSE7, K_MOUSE8, K_MOUSE9, K_MOUSE10, K_MOUSE11, K_MOUSE12, K_MOUSE13, K_MOUSE14, K_MOUSE15, K_MOUSE16, }; static qboolean BuildXImages(int w, int h) { int i; if(DefaultDepth(vidx11_display, vidx11_screen) != 32 && DefaultDepth(vidx11_display, vidx11_screen) != 24) { Con_Printf("Sorry, we only support 24bpp and 32bpp modes\n"); VID_Shutdown(); return false; } // match to dpsoftrast's specs if(vidx11_visual->red_mask != 0x00FF0000) { Con_Printf("Sorry, we only support BGR visuals\n"); VID_Shutdown(); return false; } if(vidx11_visual->green_mask != 0x0000FF00) { Con_Printf("Sorry, we only support BGR visuals\n"); VID_Shutdown(); return false; } if(vidx11_visual->blue_mask != 0x000000FF) { Con_Printf("Sorry, we only support BGR visuals\n"); VID_Shutdown(); return false; } if(vidx11_shmevent >= 0) { for(i = 0; i < 2; ++i) { vidx11_shminfo[i].shmid = -1; vidx11_ximage[i] = XShmCreateImage(vidx11_display, vidx11_visual, DefaultDepth(vidx11_display, vidx11_screen), ZPixmap, NULL, &vidx11_shminfo[i], w, h); if(!vidx11_ximage[i]) { Con_Printf("Failed to get an XImage segment\n"); VID_Shutdown(); return false; } if(vidx11_ximage[i]->bytes_per_line != w * 4) { Con_Printf("Sorry, we only support linear pixel layout\n"); VID_Shutdown(); return false; } vidx11_shminfo[i].shmid = shmget(IPC_PRIVATE, vidx11_ximage[i]->bytes_per_line * vidx11_ximage[i]->height, IPC_CREAT|0777); if(vidx11_shminfo[i].shmid < 0) { Con_Printf("Failed to get a shm segment\n"); VID_Shutdown(); return false; } vidx11_shminfo[i].shmaddr = vidx11_ximage[i]->data = shmat(vidx11_shminfo[i].shmid, NULL, 0); if(!vidx11_shminfo[i].shmaddr) { Con_Printf("Failed to get a shm segment addresst\n"); VID_Shutdown(); return false; } vidx11_shminfo[i].readOnly = True; XShmAttach(vidx11_display, &vidx11_shminfo[i]); } } else { for(i = 0; i < 1; ++i) // we only need one buffer if we don't use Xshm { char *p = calloc(4, w * h); vidx11_shminfo[i].shmid = -1; vidx11_ximage[i] = XCreateImage(vidx11_display, vidx11_visual, DefaultDepth(vidx11_display, vidx11_screen), ZPixmap, 0, (char*)p, w, h, 8, 0); if(!vidx11_ximage[i]) { Con_Printf("Failed to get an XImage segment\n"); VID_Shutdown(); return false; } if(vidx11_ximage[i]->bytes_per_line != w * 4) { Con_Printf("Sorry, we only support linear pixel layout\n"); VID_Shutdown(); return false; } } } return true; } static void DestroyXImages(void) { int i; for(i = 0; i < 2; ++i) { if(vidx11_shminfo[i].shmid >= 0) { XShmDetach(vidx11_display, &vidx11_shminfo[i]); XDestroyImage(vidx11_ximage[i]); vidx11_ximage[i] = NULL; shmdt(vidx11_shminfo[i].shmaddr); shmctl(vidx11_shminfo[i].shmid, IPC_RMID, 0); vidx11_shminfo[i].shmid = -1; } if(vidx11_ximage[i]) XDestroyImage(vidx11_ximage[i]); vidx11_ximage[i] = 0; } } static int in_mouse_x_save = 0, in_mouse_y_save = 0; static void HandleEvents(void) { XEvent event; int key; Uchar unicode; qboolean dowarp = false; if (!vidx11_display) return; in_mouse_x += in_mouse_x_save; in_mouse_y += in_mouse_y_save; in_mouse_x_save = 0; in_mouse_y_save = 0; while (XPending(vidx11_display)) { XNextEvent(vidx11_display, &event); switch (event.type) { case KeyPress: // key pressed key = XLateKey (&event.xkey, &unicode); Key_Event(key, unicode, true); break; case KeyRelease: // key released key = XLateKey (&event.xkey, &unicode); Key_Event(key, unicode, false); break; case MotionNotify: // mouse moved if (vid_usingmouse) { #ifdef USEDGA if (vid_usingdgamouse) { in_mouse_x += event.xmotion.x_root; in_mouse_y += event.xmotion.y_root; } else #endif { if (!event.xmotion.send_event) { in_mouse_x += event.xmotion.x - in_windowmouse_x; in_mouse_y += event.xmotion.y - in_windowmouse_y; //if (abs(vid.width/2 - event.xmotion.x) + abs(vid.height/2 - event.xmotion.y)) if (vid_stick_mouse.integer || abs(vid.width/2 - event.xmotion.x) > vid.width / 4 || abs(vid.height/2 - event.xmotion.y) > vid.height / 4) dowarp = true; } } } in_windowmouse_x = event.xmotion.x; in_windowmouse_y = event.xmotion.y; break; case ButtonPress: // mouse button pressed if (event.xbutton.button <= 18) Key_Event(buttonremap[event.xbutton.button - 1], 0, true); else Con_Printf("HandleEvents: ButtonPress gave value %d, 1-18 expected\n", event.xbutton.button); break; case ButtonRelease: // mouse button released if (event.xbutton.button <= 18) Key_Event(buttonremap[event.xbutton.button - 1], 0, false); else Con_Printf("HandleEvents: ButtonRelease gave value %d, 1-18 expected\n", event.xbutton.button); break; case CreateNotify: // window created win_x = event.xcreatewindow.x; win_y = event.xcreatewindow.y; break; case ConfigureNotify: // window changed size/location win_x = event.xconfigure.x; win_y = event.xconfigure.y; // HACK on X11, we just request fullscreen mode, but // cannot guess what the window manager will do for us // exactly. That is why we read back the resolution we // actually got here. if(vid_isdesktopfullscreen) { desktop_mode.width = event.xconfigure.width; desktop_mode.height = event.xconfigure.height; } if((vid_resizable.integer < 2 || vid_isdesktopfullscreen) && (vid.width != event.xconfigure.width || vid.height != event.xconfigure.height)) { vid.width = event.xconfigure.width; vid.height = event.xconfigure.height; if(vid_isdesktopfullscreen) Con_Printf("NetWM fullscreen: actually using resolution %dx%d\n", vid.width, vid.height); else Con_DPrintf("Updating to ConfigureNotify resolution %dx%d\n", vid.width, vid.height); if(vid.renderpath == RENDERPATH_SOFT) { DPSOFTRAST_Flush(); if(vid.softdepthpixels) free(vid.softdepthpixels); DestroyXImages(); XSync(vidx11_display, False); if(!BuildXImages(vid.width, vid.height)) return; XSync(vidx11_display, False); vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; vid.softdepthpixels = (unsigned int *)calloc(4, vid.width * vid.height); } } break; case DestroyNotify: // window has been destroyed Sys_Quit(0); break; case ClientMessage: // window manager messages if ((event.xclient.format == 32) && ((unsigned int)event.xclient.data.l[0] == wm_delete_window_atom)) Sys_Quit(0); break; case MapNotify: if (vid_isoverrideredirect) break; // window restored vid_hidden = false; VID_RestoreSystemGamma(); if(vid_isvidmodefullscreen) { // set our video mode XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &game_vidmode); // Move the viewport to top left XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); } if(vid_isdesktopfullscreen) { // make sure it's fullscreen XEvent event; event.type = ClientMessage; event.xclient.serial = 0; event.xclient.send_event = True; event.xclient.message_type = net_wm_state_atom; event.xclient.window = win; event.xclient.format = 32; event.xclient.data.l[0] = 1; event.xclient.data.l[1] = net_wm_state_fullscreen_atom; event.xclient.data.l[2] = 0; event.xclient.data.l[3] = 1; event.xclient.data.l[4] = 0; XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); } dowarp = true; break; case UnmapNotify: if (vid_isoverrideredirect) break; // window iconified/rolledup/whatever vid_hidden = true; VID_RestoreSystemGamma(); if(vid_isvidmodefullscreen) XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode); break; case FocusIn: if (vid_isoverrideredirect) break; // window is now the input focus vid_activewindow = true; break; case FocusOut: if (vid_isoverrideredirect) break; if(vid_isdesktopfullscreen && event.xfocus.mode == NotifyNormal) { // iconify netwm fullscreen window when it loses focus // when the user selects it in the taskbar, the window manager will map it again and send MapNotify XEvent event; event.type = ClientMessage; event.xclient.serial = 0; event.xclient.send_event = True; event.xclient.message_type = net_wm_state_atom; event.xclient.window = win; event.xclient.format = 32; event.xclient.data.l[0] = 1; event.xclient.data.l[1] = net_wm_state_hidden_atom; event.xclient.data.l[2] = 0; event.xclient.data.l[3] = 1; event.xclient.data.l[4] = 0; XSendEvent(vidx11_display, root, False, SubstructureRedirectMask | SubstructureNotifyMask, &event); } // window is no longer the input focus vid_activewindow = false; VID_RestoreSystemGamma(); break; case EnterNotify: // mouse entered window break; case LeaveNotify: // mouse left window break; default: if(vidx11_shmevent >= 0 && event.type == vidx11_shmevent) --vidx11_shmwait; break; } } if (dowarp) { /* move the mouse to the window center again */ // we'll catch the warp motion by its send_event flag, updating the // stored mouse position without adding any delta motion XEvent event; event.type = MotionNotify; event.xmotion.display = vidx11_display; event.xmotion.window = win; event.xmotion.x = vid.width / 2; event.xmotion.y = vid.height / 2; XSendEvent(vidx11_display, win, False, PointerMotionMask, &event); XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2); } } static void *prjobj = NULL; static void GL_CloseLibrary(void) { if (prjobj) dlclose(prjobj); prjobj = NULL; gl_driver[0] = 0; qglXGetProcAddressARB = NULL; gl_extensions = ""; gl_platform = ""; gl_platformextensions = ""; } static int GL_OpenLibrary(const char *name) { Con_Printf("Loading OpenGL driver %s\n", name); GL_CloseLibrary(); if (!(prjobj = dlopen(name, RTLD_LAZY | RTLD_GLOBAL))) { Con_Printf("Unable to open symbol list for %s\n", name); return false; } strlcpy(gl_driver, name, sizeof(gl_driver)); return true; } void *GL_GetProcAddress(const char *name) { void *p = NULL; if (qglXGetProcAddressARB != NULL) p = (void *) qglXGetProcAddressARB((GLubyte *)name); if (p == NULL) p = (void *) dlsym(prjobj, name); return p; } void VID_Shutdown(void) { if (!vidx11_display) return; VID_EnableJoystick(false); VID_SetMouse(false, false, false); VID_RestoreSystemGamma(); // FIXME: glXDestroyContext here? if (vid_isvidmodefullscreen) XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, &init_vidmode); if(vidx11_gc) XFreeGC(vidx11_display, vidx11_gc); vidx11_gc = NULL; DestroyXImages(); vidx11_shmevent = -1; vid.softpixels = NULL; if (vid.softdepthpixels) free(vid.softdepthpixels); vid.softdepthpixels = NULL; if (win) XDestroyWindow(vidx11_display, win); XCloseDisplay(vidx11_display); vid_hidden = true; vid_isfullscreen = false; vid_isdesktopfullscreen = false; vid_isvidmodefullscreen = false; vid_isoverrideredirect = false; vidx11_display = NULL; win = 0; ctx = NULL; GL_CloseLibrary(); Key_ClearStates (); } static void signal_handler(int sig) { Con_Printf("Received signal %d, exiting...\n", sig); VID_RestoreSystemGamma(); Sys_Quit(1); } static void InitSig(void) { signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGILL, signal_handler); signal(SIGTRAP, signal_handler); signal(SIGIOT, signal_handler); signal(SIGBUS, signal_handler); signal(SIGFPE, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGTERM, signal_handler); } void VID_Finish (void) { vid_usevsync = vid_vsync.integer && !cls.timedemo && qglXSwapIntervalSGI; switch(vid.renderpath) { case RENDERPATH_SOFT: if(vidx11_shmevent >= 0) { vidx11_ximage_pos = !vidx11_ximage_pos; vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); ++vidx11_shmwait; XShmPutImage(vidx11_display, win, vidx11_gc, vidx11_ximage[!vidx11_ximage_pos], 0, 0, 0, 0, vid.width, vid.height, True); // save mouse motion so we can deal with it later in_mouse_x = 0; in_mouse_y = 0; while(vidx11_shmwait > 1) HandleEvents(); in_mouse_x_save += in_mouse_x; in_mouse_y_save += in_mouse_y; in_mouse_x = 0; in_mouse_y = 0; } else { // no buffer switching here, we just flush the renderer DPSOFTRAST_Finish(); XPutImage(vidx11_display, win, vidx11_gc, vidx11_ximage[vidx11_ximage_pos], 0, 0, 0, 0, vid.width, vid.height); } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (vid_usingvsync != vid_usevsync) { vid_usingvsync = vid_usevsync; if (qglXSwapIntervalSGI && qglXSwapIntervalSGI (vid_usevsync)) Con_Print("glXSwapIntervalSGI didn't accept the vid_vsync change, it will take effect on next vid_restart (GLX_SGI_swap_control does not allow turning off vsync)\n"); } if (!vid_hidden) { CHECKGLERROR if (r_speeds.integer == 2 || gl_finish.integer) GL_Finish(); qglXSwapBuffers(vidx11_display, win);CHECKGLERROR } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; } if (vid_x11_hardwaregammasupported) VID_UpdateGamma(false, vid_x11_gammarampsize); } int VID_SetGamma(unsigned short *ramps, int rampsize) { return XF86VidModeSetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2); } int VID_GetGamma(unsigned short *ramps, int rampsize) { return XF86VidModeGetGammaRamp(vidx11_display, vidx11_screen, rampsize, ramps, ramps + rampsize, ramps + rampsize*2); } void VID_Init(void) { #ifdef USEDGA Cvar_RegisterVariable (&vid_dgamouse); #endif Cvar_RegisterVariable (&vid_desktopfullscreen); InitSig(); // trap evil signals // COMMANDLINEOPTION: Input: -nomouse disables mouse support (see also vid_mouse cvar) if (COM_CheckParm ("-nomouse")) mouse_avail = false; vidx11_shminfo[0].shmid = -1; vidx11_shminfo[1].shmid = -1; } static void VID_BuildGLXAttrib(int *attrib, qboolean stencil, qboolean stereobuffer, int samples) { *attrib++ = GLX_RGBA; *attrib++ = GLX_RED_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = GLX_GREEN_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = GLX_BLUE_SIZE;*attrib++ = stencil ? 8 : 5; *attrib++ = GLX_DOUBLEBUFFER; *attrib++ = GLX_DEPTH_SIZE;*attrib++ = stencil ? 24 : 16; // if stencil is enabled, ask for alpha too if (stencil) { *attrib++ = GLX_STENCIL_SIZE;*attrib++ = 8; *attrib++ = GLX_ALPHA_SIZE;*attrib++ = 8; } if (stereobuffer) *attrib++ = GLX_STEREO; if (samples > 1) { *attrib++ = GLX_SAMPLE_BUFFERS_ARB; *attrib++ = 1; *attrib++ = GLX_SAMPLES_ARB; *attrib++ = samples; } *attrib++ = None; } static qboolean VID_InitModeSoft(viddef_mode_t *mode) { int i, j; XSetWindowAttributes attr; XClassHint *clshints; XWMHints *wmhints; XSizeHints *szhints; unsigned long mask; int MajorVersion, MinorVersion; char *xpm; char **idata; unsigned char *data; XGCValues gcval; const char *dpyname; char vabuf[1024]; vid_isfullscreen = false; vid_isdesktopfullscreen = false; vid_isvidmodefullscreen = false; vid_isoverrideredirect = false; if (!(vidx11_display = XOpenDisplay(NULL))) { Con_Print("Couldn't open the X display\n"); return false; } dpyname = XDisplayName(NULL); // LordHavoc: making the close button on a window do the right thing // seems to involve this mess, sigh... wm_delete_window_atom = XInternAtom(vidx11_display, "WM_DELETE_WINDOW", false); net_wm_state_atom = XInternAtom(vidx11_display, "_NET_WM_STATE", false); net_wm_state_fullscreen_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_FULLSCREEN", false); net_wm_state_hidden_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_HIDDEN", false); net_wm_icon = XInternAtom(vidx11_display, "_NET_WM_ICON", false); cardinal = XInternAtom(vidx11_display, "CARDINAL", false); // make autorepeat send keypress/keypress/.../keyrelease instead of intervening keyrelease XkbSetDetectableAutoRepeat(vidx11_display, true, NULL); vidx11_screen = DefaultScreen(vidx11_display); root = RootWindow(vidx11_display, vidx11_screen); desktop_mode.width = DisplayWidth(vidx11_display, vidx11_screen); desktop_mode.height = DisplayHeight(vidx11_display, vidx11_screen); desktop_mode.bpp = DefaultDepth(vidx11_display, vidx11_screen); desktop_mode.refreshrate = 60; // FIXME desktop_mode.pixelheight_num = 1; // FIXME desktop_mode.pixelheight_denom = 1; // FIXME // Get video mode list MajorVersion = MinorVersion = 0; if (!XF86VidModeQueryVersion(vidx11_display, &MajorVersion, &MinorVersion)) vidmode_ext = false; else { Con_DPrintf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion); vidmode_ext = true; } if (mode->fullscreen) { if(vid_desktopfullscreen.integer) { // TODO detect WM support vid_isdesktopfullscreen = true; vid_isfullscreen = true; // width and height will be filled in later Con_DPrintf("Using NetWM fullscreen mode\n"); } if(!vid_isfullscreen && vidmode_ext) { int best_fit, best_dist, dist, x, y; // Are we going fullscreen? If so, let's change video mode XF86VidModeModeLine *current_vidmode; XF86VidModeModeInfo **vidmodes; int num_vidmodes; // This nice hack comes from the SDL source code current_vidmode = (XF86VidModeModeLine*)((char*)&init_vidmode + sizeof(init_vidmode.dotclock)); XF86VidModeGetModeLine(vidx11_display, vidx11_screen, (int*)&init_vidmode.dotclock, current_vidmode); XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); best_dist = 0; best_fit = -1; for (i = 0; i < num_vidmodes; i++) { if (mode->width > vidmodes[i]->hdisplay || mode->height > vidmodes[i]->vdisplay) continue; x = mode->width - vidmodes[i]->hdisplay; y = mode->height - vidmodes[i]->vdisplay; dist = (x * x) + (y * y); if (best_fit == -1 || dist < best_dist) { best_dist = dist; best_fit = i; } } if (best_fit != -1) { // LordHavoc: changed from ActualWidth/ActualHeight =, // to width/height =, so the window will take the full area of // the mode chosen mode->width = vidmodes[best_fit]->hdisplay; mode->height = vidmodes[best_fit]->vdisplay; // change to the mode XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, vidmodes[best_fit]); memcpy(&game_vidmode, vidmodes[best_fit], sizeof(game_vidmode)); vid_isvidmodefullscreen = true; vid_isfullscreen = true; // Move the viewport to top left XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); Con_DPrintf("Using XVidMode fullscreen mode at %dx%d\n", mode->width, mode->height); } free(vidmodes); } if(!vid_isfullscreen) { // sorry, no FS available // use the full desktop resolution vid_isfullscreen = true; // width and height will be filled in later mode->width = DisplayWidth(vidx11_display, vidx11_screen); mode->height = DisplayHeight(vidx11_display, vidx11_screen); Con_DPrintf("Using X11 fullscreen mode at %dx%d\n", mode->width, mode->height); } } // LordHavoc: save the visual for use in gamma ramp settings later vidx11_visual = DefaultVisual(vidx11_display, vidx11_screen); /* window attributes */ attr.background_pixel = 0; attr.border_pixel = 0; // LordHavoc: save the colormap for later, too vidx11_colormap = attr.colormap = XCreateColormap(vidx11_display, root, vidx11_visual, AllocNone); attr.event_mask = X_MASK; if (mode->fullscreen) { if(vid_isdesktopfullscreen) { mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask; attr.backing_store = NotUseful; attr.save_under = False; } else { mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect; attr.override_redirect = True; attr.backing_store = NotUseful; attr.save_under = False; vid_isoverrideredirect = true; // so it knows to grab } } else { mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; } win = XCreateWindow(vidx11_display, root, 0, 0, mode->width, mode->height, 0, CopyFromParent, InputOutput, vidx11_visual, mask, &attr); data = loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); if(data) { // use _NET_WM_ICON too static long netwm_icon[MAX_NETWM_ICON]; int pos = 0; int i = 1; while(data) { if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) { netwm_icon[pos++] = image_width; netwm_icon[pos++] = image_height; for(i = 0; i < image_height; ++i) for(j = 0; j < image_width; ++j) netwm_icon[pos++] = BuffLittleLong(&data[(i*image_width+j)*4]); } else { Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); } ++i; Mem_Free(data); data = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "darkplaces-icon%d", i), false, false, false, NULL); } XChangeProperty(vidx11_display, win, net_wm_icon, cardinal, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); } // fallthrough for old window managers xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); idata = NULL; if(xpm) idata = XPM_DecodeString(xpm); if(!idata) idata = ENGINE_ICON; wmhints = XAllocWMHints(); if(XpmCreatePixmapFromData(vidx11_display, win, idata, &wmhints->icon_pixmap, &wmhints->icon_mask, NULL) == XpmSuccess) wmhints->flags |= IconPixmapHint | IconMaskHint; if(xpm) Mem_Free(xpm); clshints = XAllocClassHint(); clshints->res_name = strdup(gamename); clshints->res_class = strdup("DarkPlaces"); szhints = XAllocSizeHints(); if(vid_resizable.integer == 0 && !vid_isdesktopfullscreen) { szhints->min_width = szhints->max_width = mode->width; szhints->min_height = szhints->max_height = mode->height; szhints->flags |= PMinSize | PMaxSize; } XmbSetWMProperties(vidx11_display, win, gamename, gamename, (char **) com_argv, com_argc, szhints, wmhints, clshints); // strdup() allocates using malloc(), should be freed with free() free(clshints->res_name); free(clshints->res_class); XFree(clshints); XFree(wmhints); XFree(szhints); //XStoreName(vidx11_display, win, gamename); XMapWindow(vidx11_display, win); XSetWMProtocols(vidx11_display, win, &wm_delete_window_atom, 1); if (vid_isoverrideredirect) { XMoveWindow(vidx11_display, win, 0, 0); XRaiseWindow(vidx11_display, win); XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); XFlush(vidx11_display); } if(vid_isvidmodefullscreen) { // Move the viewport to top left XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); } //XSync(vidx11_display, False); // COMMANDLINEOPTION: Unix GLX: -noshm disables XShm extensioon if(dpyname && dpyname[0] == ':' && dpyname[1] && (dpyname[2] < '0' || dpyname[2] > '9') && !COM_CheckParm("-noshm") && XShmQueryExtension(vidx11_display)) { Con_Printf("Using XShm\n"); vidx11_shmevent = XShmGetEventBase(vidx11_display) + ShmCompletion; } else { Con_Printf("Not using XShm\n"); vidx11_shmevent = -1; } BuildXImages(mode->width, mode->height); vidx11_ximage_pos = 0; vid.softpixels = (unsigned int *) vidx11_ximage[vidx11_ximage_pos]->data; vidx11_shmwait = 0; vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); memset(&gcval, 0, sizeof(gcval)); vidx11_gc = XCreateGC(vidx11_display, win, 0, &gcval); if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid.softpixels, (unsigned int *)vid.softdepthpixels) < 0) { Con_Printf("Failed to initialize software rasterizer\n"); VID_Shutdown(); return false; } XSync(vidx11_display, False); vid_usingmousegrab = false; vid_usingmouse = false; vid_usinghidecursor = false; vid_usingvsync = false; vid_hidden = false; vid_activewindow = true; vid_x11_hardwaregammasupported = XF86VidModeGetGammaRampSize(vidx11_display, vidx11_screen, &vid_x11_gammarampsize) != 0; #ifdef USEDGA vid_x11_dgasupported = XF86DGAQueryVersion(vidx11_display, &MajorVersion, &MinorVersion); if (!vid_x11_dgasupported) Con_Print( "Failed to detect XF86DGA Mouse extension\n" ); #endif VID_Soft_SharedSetup(); return true; } static qboolean VID_InitModeGL(viddef_mode_t *mode) { int i, j; int attrib[32]; XSetWindowAttributes attr; XClassHint *clshints; XWMHints *wmhints; XSizeHints *szhints; unsigned long mask; XVisualInfo *visinfo; int MajorVersion, MinorVersion; const char *drivername; char *xpm; char **idata; unsigned char *data; char vabuf[1024]; vid_isfullscreen = false; vid_isdesktopfullscreen = false; vid_isvidmodefullscreen = false; vid_isoverrideredirect = false; #if defined(__APPLE__) && defined(__MACH__) drivername = "/usr/X11R6/lib/libGL.1.dylib"; #else drivername = "libGL.so.1"; #endif // COMMANDLINEOPTION: Linux GLX: -gl_driver selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it // COMMANDLINEOPTION: BSD GLX: -gl_driver selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it // LordHavoc: although this works on MacOSX, it's useless there (as there is only one system libGL) i = COM_CheckParm("-gl_driver"); if (i && i < com_argc - 1) drivername = com_argv[i + 1]; if (!GL_OpenLibrary(drivername)) { Con_Printf("Unable to load GL driver \"%s\"\n", drivername); return false; } if (!(vidx11_display = XOpenDisplay(NULL))) { Con_Print("Couldn't open the X display\n"); return false; } // LordHavoc: making the close button on a window do the right thing // seems to involve this mess, sigh... wm_delete_window_atom = XInternAtom(vidx11_display, "WM_DELETE_WINDOW", false); net_wm_state_atom = XInternAtom(vidx11_display, "_NET_WM_STATE", false); net_wm_state_fullscreen_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_FULLSCREEN", false); net_wm_state_hidden_atom = XInternAtom(vidx11_display, "_NET_WM_STATE_HIDDEN", false); net_wm_icon = XInternAtom(vidx11_display, "_NET_WM_ICON", false); cardinal = XInternAtom(vidx11_display, "CARDINAL", false); // make autorepeat send keypress/keypress/.../keyrelease instead of intervening keyrelease XkbSetDetectableAutoRepeat(vidx11_display, true, NULL); vidx11_screen = DefaultScreen(vidx11_display); root = RootWindow(vidx11_display, vidx11_screen); desktop_mode.width = DisplayWidth(vidx11_display, vidx11_screen); desktop_mode.height = DisplayHeight(vidx11_display, vidx11_screen); desktop_mode.bpp = DefaultDepth(vidx11_display, vidx11_screen); desktop_mode.refreshrate = 60; // FIXME desktop_mode.pixelheight_num = 1; // FIXME desktop_mode.pixelheight_denom = 1; // FIXME // Get video mode list MajorVersion = MinorVersion = 0; if (!XF86VidModeQueryVersion(vidx11_display, &MajorVersion, &MinorVersion)) vidmode_ext = false; else { Con_DPrintf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion); vidmode_ext = true; } if ((qglXChooseVisual = (XVisualInfo *(GLAPIENTRY *)(Display *dpy, int screen, int *attribList))GL_GetProcAddress("glXChooseVisual")) == NULL || (qglXCreateContext = (GLXContext (GLAPIENTRY *)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct))GL_GetProcAddress("glXCreateContext")) == NULL || (qglXDestroyContext = (void (GLAPIENTRY *)(Display *dpy, GLXContext ctx))GL_GetProcAddress("glXDestroyContext")) == NULL || (qglXMakeCurrent = (Bool (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable, GLXContext ctx))GL_GetProcAddress("glXMakeCurrent")) == NULL || (qglXSwapBuffers = (void (GLAPIENTRY *)(Display *dpy, GLXDrawable drawable))GL_GetProcAddress("glXSwapBuffers")) == NULL || (qglXQueryExtensionsString = (const char *(GLAPIENTRY *)(Display *dpy, int screen))GL_GetProcAddress("glXQueryExtensionsString")) == NULL) { Con_Printf("glX functions not found in %s\n", gl_driver); return false; } VID_BuildGLXAttrib(attrib, mode->bitsperpixel == 32, mode->stereobuffer, mode->samples); visinfo = qglXChooseVisual(vidx11_display, vidx11_screen, attrib); if (!visinfo) { Con_Print("Couldn't get an RGB, Double-buffered, Depth visual\n"); return false; } if (mode->fullscreen) { if(vid_desktopfullscreen.integer) { // TODO detect WM support vid_isdesktopfullscreen = true; vid_isfullscreen = true; // width and height will be filled in later Con_DPrintf("Using NetWM fullscreen mode\n"); } if(!vid_isfullscreen && vidmode_ext) { int best_fit, best_dist, dist, x, y; // Are we going fullscreen? If so, let's change video mode XF86VidModeModeLine *current_vidmode; XF86VidModeModeInfo **vidmodes; int num_vidmodes; // This nice hack comes from the SDL source code current_vidmode = (XF86VidModeModeLine*)((char*)&init_vidmode + sizeof(init_vidmode.dotclock)); XF86VidModeGetModeLine(vidx11_display, vidx11_screen, (int*)&init_vidmode.dotclock, current_vidmode); XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); best_dist = 0; best_fit = -1; for (i = 0; i < num_vidmodes; i++) { if (mode->width > vidmodes[i]->hdisplay || mode->height > vidmodes[i]->vdisplay) continue; x = mode->width - vidmodes[i]->hdisplay; y = mode->height - vidmodes[i]->vdisplay; dist = (x * x) + (y * y); if (best_fit == -1 || dist < best_dist) { best_dist = dist; best_fit = i; } } if (best_fit != -1) { // LordHavoc: changed from ActualWidth/ActualHeight =, // to width/height =, so the window will take the full area of // the mode chosen mode->width = vidmodes[best_fit]->hdisplay; mode->height = vidmodes[best_fit]->vdisplay; // change to the mode XF86VidModeSwitchToMode(vidx11_display, vidx11_screen, vidmodes[best_fit]); memcpy(&game_vidmode, vidmodes[best_fit], sizeof(game_vidmode)); vid_isvidmodefullscreen = true; vid_isfullscreen = true; // Move the viewport to top left XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); Con_DPrintf("Using XVidMode fullscreen mode at %dx%d\n", mode->width, mode->height); } free(vidmodes); } if(!vid_isfullscreen) { // sorry, no FS available // use the full desktop resolution vid_isfullscreen = true; // width and height will be filled in later mode->width = DisplayWidth(vidx11_display, vidx11_screen); mode->height = DisplayHeight(vidx11_display, vidx11_screen); Con_DPrintf("Using X11 fullscreen mode at %dx%d\n", mode->width, mode->height); } } // LordHavoc: save the visual for use in gamma ramp settings later vidx11_visual = visinfo->visual; /* window attributes */ attr.background_pixel = 0; attr.border_pixel = 0; // LordHavoc: save the colormap for later, too vidx11_colormap = attr.colormap = XCreateColormap(vidx11_display, root, visinfo->visual, AllocNone); attr.event_mask = X_MASK; if (mode->fullscreen) { if(vid_isdesktopfullscreen) { mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask; attr.backing_store = NotUseful; attr.save_under = False; } else { mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | CWEventMask | CWOverrideRedirect; attr.override_redirect = True; attr.backing_store = NotUseful; attr.save_under = False; vid_isoverrideredirect = true; // so it knows to grab } } else { mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask; } win = XCreateWindow(vidx11_display, root, 0, 0, mode->width, mode->height, 0, visinfo->depth, InputOutput, visinfo->visual, mask, &attr); data = loadimagepixelsbgra("darkplaces-icon", false, false, false, NULL); if(data) { // use _NET_WM_ICON too static long netwm_icon[MAX_NETWM_ICON]; int pos = 0; int i = 1; while(data) { if(pos + 2 * image_width * image_height < MAX_NETWM_ICON) { netwm_icon[pos++] = image_width; netwm_icon[pos++] = image_height; for(i = 0; i < image_height; ++i) for(j = 0; j < image_width; ++j) netwm_icon[pos++] = BuffLittleLong(&data[(i*image_width+j)*4]); } else { Con_Printf("Skipping NETWM icon #%d because there is no space left\n", i); } ++i; Mem_Free(data); data = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "darkplaces-icon%d", i), false, false, false, NULL); } XChangeProperty(vidx11_display, win, net_wm_icon, cardinal, 32, PropModeReplace, (const unsigned char *) netwm_icon, pos); } // fallthrough for old window managers xpm = (char *) FS_LoadFile("darkplaces-icon.xpm", tempmempool, false, NULL); idata = NULL; if(xpm) idata = XPM_DecodeString(xpm); if(!idata) idata = ENGINE_ICON; wmhints = XAllocWMHints(); if(XpmCreatePixmapFromData(vidx11_display, win, idata, &wmhints->icon_pixmap, &wmhints->icon_mask, NULL) == XpmSuccess) wmhints->flags |= IconPixmapHint | IconMaskHint; if(xpm) Mem_Free(xpm); clshints = XAllocClassHint(); clshints->res_name = strdup(gamename); clshints->res_class = strdup("DarkPlaces"); szhints = XAllocSizeHints(); if(vid_resizable.integer == 0 && !vid_isdesktopfullscreen) { szhints->min_width = szhints->max_width = mode->width; szhints->min_height = szhints->max_height = mode->height; szhints->flags |= PMinSize | PMaxSize; } XmbSetWMProperties(vidx11_display, win, gamename, gamename, (char **) com_argv, com_argc, szhints, wmhints, clshints); // strdup() allocates using malloc(), should be freed with free() free(clshints->res_name); free(clshints->res_class); XFree(clshints); XFree(wmhints); XFree(szhints); //XStoreName(vidx11_display, win, gamename); XMapWindow(vidx11_display, win); XSetWMProtocols(vidx11_display, win, &wm_delete_window_atom, 1); if (vid_isoverrideredirect) { XMoveWindow(vidx11_display, win, 0, 0); XRaiseWindow(vidx11_display, win); XWarpPointer(vidx11_display, None, win, 0, 0, 0, 0, 0, 0); XFlush(vidx11_display); } if(vid_isvidmodefullscreen) { // Move the viewport to top left XF86VidModeSetViewPort(vidx11_display, vidx11_screen, 0, 0); } //XSync(vidx11_display, False); ctx = qglXCreateContext(vidx11_display, visinfo, NULL, True); XFree(visinfo); // glXChooseVisual man page says to use XFree to free visinfo if (!ctx) { Con_Printf ("glXCreateContext failed\n"); return false; } if (!qglXMakeCurrent(vidx11_display, win, ctx)) { Con_Printf ("glXMakeCurrent failed\n"); return false; } XSync(vidx11_display, False); if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) { Con_Printf ("glGetString not found in %s\n", gl_driver); return false; } gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); gl_platform = "GLX"; gl_platformextensions = qglXQueryExtensionsString(vidx11_display, vidx11_screen); // COMMANDLINEOPTION: Linux GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) // COMMANDLINEOPTION: BSD GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) // COMMANDLINEOPTION: MacOSX GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) GL_CheckExtension("GLX_ARB_get_proc_address", getprocaddressfuncs, "-nogetprocaddress", false); // COMMANDLINEOPTION: Linux GLX: -novideosync disables GLX_SGI_swap_control // COMMANDLINEOPTION: BSD GLX: -novideosync disables GLX_SGI_swap_control // COMMANDLINEOPTION: MacOSX GLX: -novideosync disables GLX_SGI_swap_control GL_CheckExtension("GLX_SGI_swap_control", swapcontrolfuncs, "-novideosync", false); vid_usingmousegrab = false; vid_usingmouse = false; vid_usinghidecursor = false; vid_usingvsync = false; vid_hidden = false; vid_activewindow = true; vid_x11_hardwaregammasupported = XF86VidModeGetGammaRampSize(vidx11_display, vidx11_screen, &vid_x11_gammarampsize) != 0; #ifdef USEDGA vid_x11_dgasupported = XF86DGAQueryVersion(vidx11_display, &MajorVersion, &MinorVersion); if (!vid_x11_dgasupported) Con_Print( "Failed to detect XF86DGA Mouse extension\n" ); #endif GL_Init(); return true; } qboolean VID_InitMode(viddef_mode_t *mode) { #ifdef SSE_POSSIBLE if (vid_soft.integer) return VID_InitModeSoft(mode); else #endif return VID_InitModeGL(mode); } void Sys_SendKeyEvents(void) { static qboolean sound_active = true; // enable/disable sound on focus gain/loss if ((!vid_hidden && vid_activewindow) || !snd_mutewhenidle.integer) { if (!sound_active) { S_UnblockSound (); sound_active = true; } } else { if (sound_active) { S_BlockSound (); sound_active = false; } } HandleEvents(); } void VID_BuildJoyState(vid_joystate_t *joystate) { VID_Shared_BuildJoyState_Begin(joystate); VID_Shared_BuildJoyState_Finish(joystate); } void VID_EnableJoystick(qboolean enable) { int index = joy_enable.integer > 0 ? joy_index.integer : -1; qboolean success = false; int sharedcount = 0; sharedcount = VID_Shared_SetJoystick(index); if (index >= 0 && index < sharedcount) success = true; // update cvar containing count of XInput joysticks if (joy_detected.integer != sharedcount) Cvar_SetValueQuick(&joy_detected, sharedcount); Cvar_SetValueQuick(&joy_active, success ? 1 : 0); } void IN_Move (void) { vid_joystate_t joystate; VID_EnableJoystick(true); VID_BuildJoyState(&joystate); VID_ApplyJoyState(&joystate); } vid_mode_t *VID_GetDesktopMode(void) { return &desktop_mode; } size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) { if(vidmode_ext) { int i, bpp; size_t k; XF86VidModeModeInfo **vidmodes; int num_vidmodes; XF86VidModeGetAllModeLines(vidx11_display, vidx11_screen, &num_vidmodes, &vidmodes); k = 0; for (i = 0; i < num_vidmodes; i++) { if(k >= maxcount) break; // we don't get bpp info, so let's just assume all of 8, 15, 16, 24, 32 work for(bpp = 8; bpp <= 32; bpp = ((bpp == 8) ? 15 : (bpp & 0xF8) + 8)) { if(k >= maxcount) break; modes[k].width = vidmodes[i]->hdisplay; modes[k].height = vidmodes[i]->vdisplay; modes[k].bpp = 8; if(vidmodes[i]->dotclock && vidmodes[i]->htotal && vidmodes[i]->vtotal) modes[k].refreshrate = vidmodes[i]->dotclock / vidmodes[i]->htotal / vidmodes[i]->vtotal; else modes[k].refreshrate = 60; modes[k].pixelheight_num = 1; modes[k].pixelheight_denom = 1; // xvidmode does not provide this ++k; } } // manpage of XF86VidModeGetAllModeLines says it should be freed by the caller XFree(vidmodes); return k; } return 0; // FIXME implement this } darkplaces/spritegn.h0000664000175000017500000000632213067716222014174 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // // spritegn.h: header file for sprite generation program // // ********************************************************** // * This file must be identical in the spritegen directory * // * and in the Quake directory, because it's used to * // * pass data from one to the other via .spr files. * // ********************************************************** #ifndef SPRITEGEN_H #define SPRITEGEN_H //------------------------------------------------------- // This program generates .spr sprite package files. // The format of the files is as follows: // // dsprite_t file header structure // // // dspriteframe_t frame header structure // sprite bitmap // // dspriteframe_t frame header structure // sprite bitmap // //------------------------------------------------------- #define SPRITE_VERSION 1 #define SPRITEHL_VERSION 2 #define SPRITE32_VERSION 32 #define SPRITE2_VERSION 2 typedef struct dsprite_s { int ident; int version; int type; float boundingradius; int width; int height; int numframes; float beamlength; synctype_t synctype; } dsprite_t; typedef struct dspritehl_s { int ident; int version; int type; int rendermode; float boundingradius; int width; int height; int numframes; float beamlength; synctype_t synctype; } dspritehl_t; typedef struct dsprite2frame_s { int width, height; int origin_x, origin_y; // raster coordinates inside pic char name[64]; // name of pcx file } dsprite2frame_t; typedef struct dsprite2_s { int ident; int version; int numframes; dsprite2frame_t frames[1]; // variable sized } dsprite2_t; #define SPR_VP_PARALLEL_UPRIGHT 0 #define SPR_FACING_UPRIGHT 1 #define SPR_VP_PARALLEL 2 #define SPR_ORIENTED 3 #define SPR_VP_PARALLEL_ORIENTED 4 #define SPR_LABEL 5 #define SPR_LABEL_SCALE 6 #define SPR_OVERHEAD 7 #define SPRHL_OPAQUE 0 #define SPRHL_ADDITIVE 1 #define SPRHL_INDEXALPHA 2 #define SPRHL_ALPHATEST 3 typedef struct dspriteframe_s { int origin[2]; int width; int height; } dspriteframe_t; typedef struct dspritegroup_s { int numframes; } dspritegroup_t; typedef struct dspriteinterval_s { float interval; } dspriteinterval_t; typedef enum spriteframetype_e { SPR_SINGLE=0, SPR_GROUP } spriteframetype_t; typedef struct dspriteframetype_s { spriteframetype_t type; } dspriteframetype_t; #endif darkplaces/cmd.c0000664000175000017500000015733113067716216013111 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cmd.c -- Quake script command processing module #include "quakedef.h" #include "thread.h" typedef struct cmdalias_s { struct cmdalias_s *next; char name[MAX_ALIAS_NAME]; char *value; qboolean initstate; // indicates this command existed at init char *initialvalue; // backup copy of value at init } cmdalias_t; static cmdalias_t *cmd_alias; static qboolean cmd_wait; static mempool_t *cmd_mempool; static char cmd_tokenizebuffer[CMD_TOKENIZELENGTH]; static int cmd_tokenizebufferpos = 0; //============================================================================= /* ============ Cmd_Wait_f Causes execution of the remainder of the command buffer to be delayed until next frame. This allows commands like: bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2" ============ */ static void Cmd_Wait_f (void) { cmd_wait = true; } typedef struct cmddeferred_s { struct cmddeferred_s *next; char *value; double delay; } cmddeferred_t; static cmddeferred_t *cmd_deferred_list = NULL; /* ============ Cmd_Defer_f Cause a command to be executed after a delay. ============ */ static void Cmd_Defer_f (void) { if(Cmd_Argc() == 1) { cmddeferred_t *next = cmd_deferred_list; if(!next) Con_Printf("No commands are pending.\n"); while(next) { Con_Printf("-> In %9.2f: %s\n", next->delay, next->value); next = next->next; } } else if(Cmd_Argc() == 2 && !strcasecmp("clear", Cmd_Argv(1))) { while(cmd_deferred_list) { cmddeferred_t *cmd = cmd_deferred_list; cmd_deferred_list = cmd->next; Mem_Free(cmd->value); Mem_Free(cmd); } } else if(Cmd_Argc() == 3) { const char *value = Cmd_Argv(2); cmddeferred_t *defcmd = (cmddeferred_t*)Mem_Alloc(tempmempool, sizeof(*defcmd)); size_t len = strlen(value); defcmd->delay = atof(Cmd_Argv(1)); defcmd->value = (char*)Mem_Alloc(tempmempool, len+1); memcpy(defcmd->value, value, len+1); defcmd->next = NULL; if(cmd_deferred_list) { cmddeferred_t *next = cmd_deferred_list; while(next->next) next = next->next; next->next = defcmd; } else cmd_deferred_list = defcmd; /* Stupid me... this changes the order... so commands with the same delay go blub :S defcmd->next = cmd_deferred_list; cmd_deferred_list = defcmd;*/ } else { Con_Printf("usage: defer \n" " defer clear\n"); return; } } /* ============ Cmd_Centerprint_f Print something to the center of the screen using SCR_Centerprint ============ */ static void Cmd_Centerprint_f (void) { char msg[MAX_INPUTLINE]; unsigned int i, c, p; c = Cmd_Argc(); if(c >= 2) { strlcpy(msg, Cmd_Argv(1), sizeof(msg)); for(i = 2; i < c; ++i) { strlcat(msg, " ", sizeof(msg)); strlcat(msg, Cmd_Argv(i), sizeof(msg)); } c = (unsigned int)strlen(msg); for(p = 0, i = 0; i < c; ++i) { if(msg[i] == '\\') { if(msg[i+1] == 'n') msg[p++] = '\n'; else if(msg[i+1] == '\\') msg[p++] = '\\'; else { msg[p++] = '\\'; msg[p++] = msg[i+1]; } ++i; } else { msg[p++] = msg[i]; } } msg[p] = '\0'; SCR_CenterPrint(msg); } } /* ============================================================================= COMMAND BUFFER ============================================================================= */ static sizebuf_t cmd_text; static unsigned char cmd_text_buf[CMDBUFSIZE]; void *cmd_text_mutex = NULL; /* ============ Cbuf_AddText Adds command text at the end of the buffer ============ */ void Cbuf_AddText (const char *text) { int l; l = (int)strlen(text); Cbuf_LockThreadMutex(); if (cmd_text.cursize + l >= cmd_text.maxsize) Con_Print("Cbuf_AddText: overflow\n"); else SZ_Write(&cmd_text, (const unsigned char *)text, l); Cbuf_UnlockThreadMutex(); } /* ============ Cbuf_InsertText Adds command text immediately after the current command Adds a \n to the text FIXME: actually change the command buffer to do less copying ============ */ void Cbuf_InsertText (const char *text) { size_t l = strlen(text); Cbuf_LockThreadMutex(); // we need to memmove the existing text and stuff this in before it... if (cmd_text.cursize + l >= (size_t)cmd_text.maxsize) Con_Print("Cbuf_InsertText: overflow\n"); else { // we don't have a SZ_Prepend, so... memmove(cmd_text.data + l, cmd_text.data, cmd_text.cursize); cmd_text.cursize += (int)l; memcpy(cmd_text.data, text, l); } Cbuf_UnlockThreadMutex(); } /* ============ Cbuf_Execute_Deferred --blub ============ */ static void Cbuf_Execute_Deferred (void) { static double oldrealtime = 0; cmddeferred_t *cmd, *prev; double eat; if (realtime - oldrealtime < 0 || realtime - oldrealtime > 1800) oldrealtime = realtime; eat = realtime - oldrealtime; if (eat < (1.0 / 120.0)) return; oldrealtime = realtime; prev = NULL; cmd = cmd_deferred_list; while(cmd) { cmd->delay -= eat; if(cmd->delay <= 0) { Cbuf_AddText(cmd->value); Cbuf_AddText(";\n"); Mem_Free(cmd->value); if(prev) { prev->next = cmd->next; Mem_Free(cmd); cmd = prev->next; } else { cmd_deferred_list = cmd->next; Mem_Free(cmd); cmd = cmd_deferred_list; } continue; } prev = cmd; cmd = cmd->next; } } /* ============ Cbuf_Execute ============ */ static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ); void Cbuf_Execute (void) { int i; char *text; char line[MAX_INPUTLINE]; char preprocessed[MAX_INPUTLINE]; char *firstchar; qboolean quotes; char *comment; // LordHavoc: making sure the tokenizebuffer doesn't get filled up by repeated crashes cmd_tokenizebufferpos = 0; while (cmd_text.cursize) { // find a \n or ; line break text = (char *)cmd_text.data; quotes = false; comment = NULL; for (i=0 ; i < cmd_text.cursize ; i++) { if(!comment) { if (text[i] == '"') quotes = !quotes; if(quotes) { // make sure i doesn't get > cursize which causes a negative // size in memmove, which is fatal --blub if (i < (cmd_text.cursize-1) && (text[i] == '\\' && (text[i+1] == '"' || text[i+1] == '\\'))) i++; } else { if(text[i] == '/' && text[i + 1] == '/' && (i == 0 || ISWHITESPACE(text[i-1]))) comment = &text[i]; if(text[i] == ';') break; // don't break if inside a quoted string or comment } } if (text[i] == '\r' || text[i] == '\n') break; } // better than CRASHING on overlong input lines that may SOMEHOW enter the buffer if(i >= MAX_INPUTLINE) { Con_Printf("Warning: console input buffer had an overlong line. Ignored.\n"); line[0] = 0; } else { memcpy (line, text, comment ? (comment - text) : i); line[comment ? (comment - text) : i] = 0; } // delete the text from the command buffer and move remaining commands down // this is necessary because commands (exec, alias) can insert data at the // beginning of the text buffer if (i == cmd_text.cursize) cmd_text.cursize = 0; else { i++; cmd_text.cursize -= i; memmove (cmd_text.data, text+i, cmd_text.cursize); } // execute the command line firstchar = line; while(*firstchar && ISWHITESPACE(*firstchar)) ++firstchar; if( (strncmp(firstchar, "alias", 5) || !ISWHITESPACE(firstchar[5])) && (strncmp(firstchar, "bind", 4) || !ISWHITESPACE(firstchar[4])) && (strncmp(firstchar, "in_bind", 7) || !ISWHITESPACE(firstchar[7])) ) { if(Cmd_PreprocessString( line, preprocessed, sizeof(preprocessed), NULL )) Cmd_ExecuteString (preprocessed, src_command, false); } else { Cmd_ExecuteString (line, src_command, false); } if (cmd_wait) { // skip out while text still remains in buffer, leaving it // for next frame cmd_wait = false; break; } } } void Cbuf_Frame(void) { Cbuf_Execute_Deferred(); if (cmd_text.cursize) { SV_LockThreadMutex(); Cbuf_Execute(); SV_UnlockThreadMutex(); } } /* ============================================================================== SCRIPT COMMANDS ============================================================================== */ /* =============== Cmd_StuffCmds_f Adds command line parameters as script statements Commands lead with a +, and continue until a - or another + quake +prog jctest.qp +cmd amlev1 quake -nosound +cmd amlev1 =============== */ qboolean host_stuffcmdsrun = false; static void Cmd_StuffCmds_f (void) { int i, j, l; // this is for all commandline options combined (and is bounds checked) char build[MAX_INPUTLINE]; if (Cmd_Argc () != 1) { Con_Print("stuffcmds : execute command line parameters\n"); return; } // no reason to run the commandline arguments twice if (host_stuffcmdsrun) return; host_stuffcmdsrun = true; build[0] = 0; l = 0; for (i = 0;i < com_argc;i++) { if (com_argv[i] && com_argv[i][0] == '+' && (com_argv[i][1] < '0' || com_argv[i][1] > '9') && l + strlen(com_argv[i]) - 1 <= sizeof(build) - 1) { j = 1; while (com_argv[i][j]) build[l++] = com_argv[i][j++]; i++; for (;i < com_argc;i++) { if (!com_argv[i]) continue; if ((com_argv[i][0] == '+' || com_argv[i][0] == '-') && (com_argv[i][1] < '0' || com_argv[i][1] > '9')) break; if (l + strlen(com_argv[i]) + 4 > sizeof(build) - 1) break; build[l++] = ' '; if (strchr(com_argv[i], ' ')) build[l++] = '\"'; for (j = 0;com_argv[i][j];j++) build[l++] = com_argv[i][j]; if (strchr(com_argv[i], ' ')) build[l++] = '\"'; } build[l++] = '\n'; i--; } } // now terminate the combined string and prepend it to the command buffer // we already reserved space for the terminator build[l++] = 0; Cbuf_InsertText (build); } static void Cmd_Exec(const char *filename) { char *f; size_t filenameLen = strlen(filename); qboolean isdefaultcfg = !strcmp(filename, "default.cfg") || (filenameLen >= 12 && !strcmp(filename + filenameLen - 12, "/default.cfg")); if (!strcmp(filename, "config.cfg")) { filename = CONFIGFILENAME; if (COM_CheckParm("-noconfig")) return; // don't execute config.cfg } f = (char *)FS_LoadFile (filename, tempmempool, false, NULL); if (!f) { Con_Printf("couldn't exec %s\n",filename); return; } Con_Printf("execing %s\n",filename); // if executing default.cfg for the first time, lock the cvar defaults // it may seem backwards to insert this text BEFORE the default.cfg // but Cbuf_InsertText inserts before, so this actually ends up after it. if (isdefaultcfg) Cbuf_InsertText("\ncvar_lockdefaults\n"); // insert newline after the text to make sure the last line is terminated (some text editors omit the trailing newline) // (note: insertion order here is backwards from execution order, so this adds it after the text, by calling it before...) Cbuf_InsertText ("\n"); Cbuf_InsertText (f); Mem_Free(f); if (isdefaultcfg) { // special defaults for specific games go here, these execute before default.cfg // Nehahra pushable crates malfunction in some levels if this is on // Nehahra NPC AI is confused by blowupfallenzombies switch(gamemode) { case GAME_NORMAL: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 0\n" "sv_gameplayfix_findradiusdistancetobox 0\n" "sv_gameplayfix_grenadebouncedownslopes 0\n" "sv_gameplayfix_slidemoveprojectiles 0\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" "sv_gameplayfix_setmodelrealbox 0\n" "sv_gameplayfix_droptofloorstartsolid 0\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" "sv_gameplayfix_noairborncorpse 0\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" "sv_gameplayfix_easierwaterjump 0\n" "sv_gameplayfix_delayprojectiles 0\n" "sv_gameplayfix_multiplethinksperframe 0\n" "sv_gameplayfix_fixedcheckwatertransition 0\n" "sv_gameplayfix_q1bsptracelinereportstexture 0\n" "sv_gameplayfix_swiminbmodels 0\n" "sv_gameplayfix_downtracesupportsongroundflag 0\n" "sys_ticrate 0.01388889\n" "r_shadow_gloss 1\n" "r_shadow_bumpscale_basetexture 0\n" ); break; case GAME_NEHAHRA: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 0\n" "sv_gameplayfix_findradiusdistancetobox 0\n" "sv_gameplayfix_grenadebouncedownslopes 0\n" "sv_gameplayfix_slidemoveprojectiles 0\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" "sv_gameplayfix_setmodelrealbox 0\n" "sv_gameplayfix_droptofloorstartsolid 0\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" "sv_gameplayfix_noairborncorpse 0\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" "sv_gameplayfix_easierwaterjump 0\n" "sv_gameplayfix_delayprojectiles 0\n" "sv_gameplayfix_multiplethinksperframe 0\n" "sv_gameplayfix_fixedcheckwatertransition 0\n" "sv_gameplayfix_q1bsptracelinereportstexture 0\n" "sv_gameplayfix_swiminbmodels 0\n" "sv_gameplayfix_downtracesupportsongroundflag 0\n" "sys_ticrate 0.01388889\n" "r_shadow_gloss 1\n" "r_shadow_bumpscale_basetexture 0\n" ); break; // hipnotic mission pack has issues in their 'friendly monster' ai, which seem to attempt to attack themselves for some reason when findradius() returns non-solid entities. // hipnotic mission pack has issues with bobbing water entities 'jittering' between different heights on alternate frames at the default 0.0138889 ticrate, 0.02 avoids this issue // hipnotic mission pack has issues in their proximity mine sticking code, which causes them to bounce off. case GAME_HIPNOTIC: case GAME_QUOTH: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 0\n" "sv_gameplayfix_findradiusdistancetobox 0\n" "sv_gameplayfix_grenadebouncedownslopes 0\n" "sv_gameplayfix_slidemoveprojectiles 0\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" "sv_gameplayfix_setmodelrealbox 0\n" "sv_gameplayfix_droptofloorstartsolid 0\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" "sv_gameplayfix_noairborncorpse 0\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" "sv_gameplayfix_easierwaterjump 0\n" "sv_gameplayfix_delayprojectiles 0\n" "sv_gameplayfix_multiplethinksperframe 0\n" "sv_gameplayfix_fixedcheckwatertransition 0\n" "sv_gameplayfix_q1bsptracelinereportstexture 0\n" "sv_gameplayfix_swiminbmodels 0\n" "sv_gameplayfix_downtracesupportsongroundflag 0\n" "sys_ticrate 0.02\n" "r_shadow_gloss 1\n" "r_shadow_bumpscale_basetexture 0\n" ); break; // rogue mission pack has a guardian boss that does not wake up if findradius returns one of the entities around its spawn area case GAME_ROGUE: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 0\n" "sv_gameplayfix_findradiusdistancetobox 0\n" "sv_gameplayfix_grenadebouncedownslopes 0\n" "sv_gameplayfix_slidemoveprojectiles 0\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" "sv_gameplayfix_setmodelrealbox 0\n" "sv_gameplayfix_droptofloorstartsolid 0\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" "sv_gameplayfix_noairborncorpse 0\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" "sv_gameplayfix_easierwaterjump 0\n" "sv_gameplayfix_delayprojectiles 0\n" "sv_gameplayfix_multiplethinksperframe 0\n" "sv_gameplayfix_fixedcheckwatertransition 0\n" "sv_gameplayfix_q1bsptracelinereportstexture 0\n" "sv_gameplayfix_swiminbmodels 0\n" "sv_gameplayfix_downtracesupportsongroundflag 0\n" "sys_ticrate 0.01388889\n" "r_shadow_gloss 1\n" "r_shadow_bumpscale_basetexture 0\n" ); break; case GAME_TENEBRAE: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 0\n" "sv_gameplayfix_findradiusdistancetobox 0\n" "sv_gameplayfix_grenadebouncedownslopes 0\n" "sv_gameplayfix_slidemoveprojectiles 0\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 0\n" "sv_gameplayfix_setmodelrealbox 0\n" "sv_gameplayfix_droptofloorstartsolid 0\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 0\n" "sv_gameplayfix_noairborncorpse 0\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 0\n" "sv_gameplayfix_easierwaterjump 0\n" "sv_gameplayfix_delayprojectiles 0\n" "sv_gameplayfix_multiplethinksperframe 0\n" "sv_gameplayfix_fixedcheckwatertransition 0\n" "sv_gameplayfix_q1bsptracelinereportstexture 0\n" "sv_gameplayfix_swiminbmodels 0\n" "sv_gameplayfix_downtracesupportsongroundflag 0\n" "sys_ticrate 0.01388889\n" "r_shadow_gloss 2\n" "r_shadow_bumpscale_basetexture 4\n" ); break; case GAME_NEXUIZ: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 1\n" "sv_gameplayfix_findradiusdistancetobox 1\n" "sv_gameplayfix_grenadebouncedownslopes 1\n" "sv_gameplayfix_slidemoveprojectiles 1\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" "sv_gameplayfix_setmodelrealbox 1\n" "sv_gameplayfix_droptofloorstartsolid 1\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" "sv_gameplayfix_noairborncorpse 1\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" "sv_gameplayfix_easierwaterjump 1\n" "sv_gameplayfix_delayprojectiles 1\n" "sv_gameplayfix_multiplethinksperframe 1\n" "sv_gameplayfix_fixedcheckwatertransition 1\n" "sv_gameplayfix_q1bsptracelinereportstexture 1\n" "sv_gameplayfix_swiminbmodels 1\n" "sv_gameplayfix_downtracesupportsongroundflag 1\n" "sys_ticrate 0.01388889\n" "sv_gameplayfix_q2airaccelerate 1\n" "sv_gameplayfix_stepmultipletimes 1\n" ); break; // Steel Storm: Burning Retribution csqc misinterprets CSQC_InputEvent if type is a value other than 0 or 1 case GAME_STEELSTORM: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 1\n" "sv_gameplayfix_findradiusdistancetobox 1\n" "sv_gameplayfix_grenadebouncedownslopes 1\n" "sv_gameplayfix_slidemoveprojectiles 1\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" "sv_gameplayfix_setmodelrealbox 1\n" "sv_gameplayfix_droptofloorstartsolid 1\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" "sv_gameplayfix_noairborncorpse 1\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" "sv_gameplayfix_easierwaterjump 1\n" "sv_gameplayfix_delayprojectiles 1\n" "sv_gameplayfix_multiplethinksperframe 1\n" "sv_gameplayfix_fixedcheckwatertransition 1\n" "sv_gameplayfix_q1bsptracelinereportstexture 1\n" "sv_gameplayfix_swiminbmodels 1\n" "sv_gameplayfix_downtracesupportsongroundflag 1\n" "sys_ticrate 0.01388889\n" "cl_csqc_generatemousemoveevents 0\n" ); break; default: Cbuf_InsertText("\n" "sv_gameplayfix_blowupfallenzombies 1\n" "sv_gameplayfix_findradiusdistancetobox 1\n" "sv_gameplayfix_grenadebouncedownslopes 1\n" "sv_gameplayfix_slidemoveprojectiles 1\n" "sv_gameplayfix_upwardvelocityclearsongroundflag 1\n" "sv_gameplayfix_setmodelrealbox 1\n" "sv_gameplayfix_droptofloorstartsolid 1\n" "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect 1\n" "sv_gameplayfix_noairborncorpse 1\n" "sv_gameplayfix_noairborncorpse_allowsuspendeditems 1\n" "sv_gameplayfix_easierwaterjump 1\n" "sv_gameplayfix_delayprojectiles 1\n" "sv_gameplayfix_multiplethinksperframe 1\n" "sv_gameplayfix_fixedcheckwatertransition 1\n" "sv_gameplayfix_q1bsptracelinereportstexture 1\n" "sv_gameplayfix_swiminbmodels 1\n" "sv_gameplayfix_downtracesupportsongroundflag 1\n" "sys_ticrate 0.01388889\n" ); break; } } } /* =============== Cmd_Exec_f =============== */ static void Cmd_Exec_f (void) { fssearch_t *s; int i; if (Cmd_Argc () != 2) { Con_Print("exec : execute a script file\n"); return; } s = FS_Search(Cmd_Argv(1), true, true); if(!s || !s->numfilenames) { Con_Printf("couldn't exec %s\n",Cmd_Argv(1)); return; } for(i = 0; i < s->numfilenames; ++i) Cmd_Exec(s->filenames[i]); FS_FreeSearch(s); } /* =============== Cmd_Echo_f Just prints the rest of the line to the console =============== */ static void Cmd_Echo_f (void) { int i; for (i=1 ; i - toggles between 0 and 1\n toggle - toggles between 0 and \n toggle [string 1] [string 2]...[string n] - cycles through all strings\n"); else { // Correct Arguments Specified // Acquire Potential CVar cvar_t* cvCVar = Cvar_FindVar( Cmd_Argv(1) ); if(cvCVar != NULL) { // Valid CVar if(nNumArgs == 2) { // Default Usage if(cvCVar->integer) Cvar_SetValueQuick(cvCVar, 0); else Cvar_SetValueQuick(cvCVar, 1); } else if(nNumArgs == 3) { // 0 and Specified Usage if(cvCVar->integer == atoi(Cmd_Argv(2) ) ) // CVar is Specified Value; // Reset to 0 Cvar_SetValueQuick(cvCVar, 0); else if(cvCVar->integer == 0) // CVar is 0; Specify Value Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); else // CVar does not match; Reset to 0 Cvar_SetValueQuick(cvCVar, 0); } else { // Variable Values Specified int nCnt; int bFound = 0; for(nCnt = 2; nCnt < nNumArgs; nCnt++) { // Cycle through Values if( strcmp(cvCVar->string, Cmd_Argv(nCnt) ) == 0) { // Current Value Located; Increment to Next if( (nCnt + 1) == nNumArgs) // Max Value Reached; Reset Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); else // Next Value Cvar_SetQuick(cvCVar, Cmd_Argv(nCnt + 1) ); // End Loop nCnt = nNumArgs; // Assign Found bFound = 1; } } if(!bFound) // Value not Found; Reset to Original Cvar_SetQuick(cvCVar, Cmd_Argv(2) ); } } else { // Invalid CVar Con_Printf("ERROR : CVar '%s' not found\n", Cmd_Argv(1) ); } } } /* =============== Cmd_Alias_f Creates a new command that executes a command string (possibly ; seperated) =============== */ static void Cmd_Alias_f (void) { cmdalias_t *a; char cmd[MAX_INPUTLINE]; int i, c; const char *s; size_t alloclen; if (Cmd_Argc() == 1) { Con_Print("Current alias commands:\n"); for (a = cmd_alias ; a ; a=a->next) Con_Printf("%s : %s", a->name, a->value); return; } s = Cmd_Argv(1); if (strlen(s) >= MAX_ALIAS_NAME) { Con_Print("Alias name is too long\n"); return; } // if the alias already exists, reuse it for (a = cmd_alias ; a ; a=a->next) { if (!strcmp(s, a->name)) { Z_Free (a->value); break; } } if (!a) { cmdalias_t *prev, *current; a = (cmdalias_t *)Z_Malloc (sizeof(cmdalias_t)); strlcpy (a->name, s, sizeof (a->name)); // insert it at the right alphanumeric position for( prev = NULL, current = cmd_alias ; current && strcmp( current->name, a->name ) < 0 ; prev = current, current = current->next ) ; if( prev ) { prev->next = a; } else { cmd_alias = a; } a->next = current; } // copy the rest of the command line cmd[0] = 0; // start out with a null string c = Cmd_Argc(); for (i=2 ; i < c ; i++) { if (i != 2) strlcat (cmd, " ", sizeof (cmd)); strlcat (cmd, Cmd_Argv(i), sizeof (cmd)); } strlcat (cmd, "\n", sizeof (cmd)); alloclen = strlen (cmd) + 1; if(alloclen >= 2) cmd[alloclen - 2] = '\n'; // to make sure a newline is appended even if too long a->value = (char *)Z_Malloc (alloclen); memcpy (a->value, cmd, alloclen); } /* =============== Cmd_UnAlias_f Remove existing aliases. =============== */ static void Cmd_UnAlias_f (void) { cmdalias_t *a, *p; int i; const char *s; if(Cmd_Argc() == 1) { Con_Print("unalias: Usage: unalias alias1 [alias2 ...]\n"); return; } for(i = 1; i < Cmd_Argc(); ++i) { s = Cmd_Argv(i); p = NULL; for(a = cmd_alias; a; p = a, a = a->next) { if(!strcmp(s, a->name)) { if (a->initstate) // we can not remove init aliases continue; if(a == cmd_alias) cmd_alias = a->next; if(p) p->next = a->next; Z_Free(a->value); Z_Free(a); break; } } if(!a) Con_Printf("unalias: %s alias not found\n", s); } } /* ============================================================================= COMMAND EXECUTION ============================================================================= */ typedef struct cmd_function_s { struct cmd_function_s *next; const char *name; const char *description; xcommand_t consolefunction; xcommand_t clientfunction; qboolean csqcfunc; qboolean initstate; // indicates this command existed at init } cmd_function_t; static int cmd_argc; static const char *cmd_argv[MAX_ARGS]; static const char *cmd_null_string = ""; static const char *cmd_args; cmd_source_t cmd_source; static cmd_function_t *cmd_functions; // possible commands to execute static const char *Cmd_GetDirectCvarValue(const char *varname, cmdalias_t *alias, qboolean *is_multiple) { cvar_t *cvar; long argno; char *endptr; static char vabuf[1024]; // cmd_mutex if(is_multiple) *is_multiple = false; if(!varname || !*varname) return NULL; if(alias) { if(!strcmp(varname, "*")) { if(is_multiple) *is_multiple = true; return Cmd_Args(); } else if(!strcmp(varname, "#")) { return va(vabuf, sizeof(vabuf), "%d", Cmd_Argc()); } else if(varname[strlen(varname) - 1] == '-') { argno = strtol(varname, &endptr, 10); if(endptr == varname + strlen(varname) - 1) { // whole string is a number, apart from the - const char *p = Cmd_Args(); for(; argno > 1; --argno) if(!COM_ParseToken_Console(&p)) break; if(p) { if(is_multiple) *is_multiple = true; // kill pre-argument whitespace for (;*p && ISWHITESPACE(*p);p++) ; return p; } } } else { argno = strtol(varname, &endptr, 10); if(*endptr == 0) { // whole string is a number // NOTE: we already made sure we don't have an empty cvar name! if(argno >= 0 && argno < Cmd_Argc()) return Cmd_Argv(argno); } } } if((cvar = Cvar_FindVar(varname)) && !(cvar->flags & CVAR_PRIVATE)) return cvar->string; return NULL; } qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes) { qboolean quote_quot = !!strchr(quoteset, '"'); qboolean quote_backslash = !!strchr(quoteset, '\\'); qboolean quote_dollar = !!strchr(quoteset, '$'); if(putquotes) { if(outlen <= 2) { *out++ = 0; return false; } *out++ = '"'; --outlen; --outlen; } while(*in) { if(*in == '"' && quote_quot) { if(outlen <= 2) goto fail; *out++ = '\\'; --outlen; *out++ = '"'; --outlen; } else if(*in == '\\' && quote_backslash) { if(outlen <= 2) goto fail; *out++ = '\\'; --outlen; *out++ = '\\'; --outlen; } else if(*in == '$' && quote_dollar) { if(outlen <= 2) goto fail; *out++ = '$'; --outlen; *out++ = '$'; --outlen; } else { if(outlen <= 1) goto fail; *out++ = *in; --outlen; } ++in; } if(putquotes) *out++ = '"'; *out++ = 0; return true; fail: if(putquotes) *out++ = '"'; *out++ = 0; return false; } static const char *Cmd_GetCvarValue(const char *var, size_t varlen, cmdalias_t *alias) { static char varname[MAX_INPUTLINE]; // cmd_mutex static char varval[MAX_INPUTLINE]; // cmd_mutex const char *varstr = NULL; char *varfunc; qboolean required = false; qboolean optional = false; static char asis[] = "asis"; // just to suppress const char warnings if(varlen >= MAX_INPUTLINE) varlen = MAX_INPUTLINE - 1; memcpy(varname, var, varlen); varname[varlen] = 0; varfunc = strchr(varname, ' '); if(varfunc) { *varfunc = 0; ++varfunc; } if(*var == 0) { // empty cvar name? if(alias) Con_Printf("Warning: Could not expand $ in alias %s\n", alias->name); else Con_Printf("Warning: Could not expand $\n"); return "$"; } if(varfunc) { char *p; // ? means optional while((p = strchr(varfunc, '?'))) { optional = true; memmove(p, p+1, strlen(p)); // with final NUL } // ! means required while((p = strchr(varfunc, '!'))) { required = true; memmove(p, p+1, strlen(p)); // with final NUL } // kill spaces while((p = strchr(varfunc, ' '))) { memmove(p, p+1, strlen(p)); // with final NUL } // if no function is left, NULL it if(!*varfunc) varfunc = NULL; } if(varname[0] == '$') varstr = Cmd_GetDirectCvarValue(Cmd_GetDirectCvarValue(varname + 1, alias, NULL), alias, NULL); else { qboolean is_multiple = false; // Exception: $* and $n- don't use the quoted form by default varstr = Cmd_GetDirectCvarValue(varname, alias, &is_multiple); if(is_multiple) if(!varfunc) varfunc = asis; } if(!varstr) { if(required) { if(alias) Con_Printf("Error: Could not expand $%s in alias %s\n", varname, alias->name); else Con_Printf("Error: Could not expand $%s\n", varname); return NULL; } else if(optional) { return ""; } else { if(alias) Con_Printf("Warning: Could not expand $%s in alias %s\n", varname, alias->name); else Con_Printf("Warning: Could not expand $%s\n", varname); dpsnprintf(varval, sizeof(varval), "$%s", varname); return varval; } } if(!varfunc || !strcmp(varfunc, "q")) // note: quoted form is default, use "asis" to override! { // quote it so it can be used inside double quotes // we just need to replace " by \", and of course, double backslashes Cmd_QuoteString(varval, sizeof(varval), varstr, "\"\\", false); return varval; } else if(!strcmp(varfunc, "asis")) { return varstr; } else Con_Printf("Unknown variable function %s\n", varfunc); return varstr; } /* Cmd_PreprocessString Preprocesses strings and replaces $*, $param#, $cvar accordingly. Also strips comments. */ static qboolean Cmd_PreprocessString( const char *intext, char *outtext, unsigned maxoutlen, cmdalias_t *alias ) { const char *in; size_t eat, varlen; unsigned outlen; const char *val; // don't crash if there's no room in the outtext buffer if( maxoutlen == 0 ) { return false; } maxoutlen--; // because of \0 in = intext; outlen = 0; while( *in && outlen < maxoutlen ) { if( *in == '$' ) { // this is some kind of expansion, see what comes after the $ in++; // The console does the following preprocessing: // // - $$ is transformed to a single dollar sign. // - $var or ${var} are expanded to the contents of the named cvar, // with quotation marks and backslashes quoted so it can safely // be used inside quotation marks (and it should always be used // that way) // - ${var asis} inserts the cvar value as is, without doing this // quoting // - ${var ?} silently expands to the empty string if // $var does not exist // - ${var !} fails expansion and executes nothing if // $var does not exist // - prefix the cvar name with a dollar sign to do indirection; // for example, if $x has the value timelimit, ${$x} will return // the value of $timelimit // - when expanding an alias, the special variable name $* refers // to all alias parameters, and a number refers to that numbered // alias parameter, where the name of the alias is $0, the first // parameter is $1 and so on; as a special case, $* inserts all // parameters, without extra quoting, so one can use $* to just // pass all parameters around. All parameters starting from $n // can be referred to as $n- (so $* is equivalent to $1-). // - ${* q} and ${n- q} force quoting anyway // // Note: when expanding an alias, cvar expansion is done in the SAME step // as alias expansion so that alias parameters or cvar values containing // dollar signs have no unwanted bad side effects. However, this needs to // be accounted for when writing complex aliases. For example, // alias foo "set x NEW; echo $x" // actually expands to // "set x NEW; echo OLD" // and will print OLD! To work around this, use a second alias: // alias foo "set x NEW; foo2" // alias foo2 "echo $x" // // Also note: lines starting with alias are exempt from cvar expansion. // If you want cvar expansion, write "alias" instead: // // set x 1 // alias foo "echo $x" // "alias" bar "echo $x" // set x 2 // // foo will print 2, because the variable $x will be expanded when the alias // gets expanded. bar will print 1, because the variable $x was expanded // at definition time. foo can be equivalently defined as // // "alias" foo "echo $$x" // // because at definition time, $$ will get replaced to a single $. if( *in == '$' ) { val = "$"; eat = 1; } else if(*in == '{') { varlen = strcspn(in + 1, "}"); if(in[varlen + 1] == '}') { val = Cmd_GetCvarValue(in + 1, varlen, alias); if(!val) return false; eat = varlen + 2; } else { // ran out of data? val = NULL; eat = varlen + 1; } } else { varlen = strspn(in, "#*0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"); val = Cmd_GetCvarValue(in, varlen, alias); if(!val) return false; eat = varlen; } if(val) { // insert the cvar value while(*val && outlen < maxoutlen) outtext[outlen++] = *val++; in += eat; } else { // copy the unexpanded text outtext[outlen++] = '$'; while(eat && outlen < maxoutlen) { outtext[outlen++] = *in++; --eat; } } } else outtext[outlen++] = *in++; } outtext[outlen] = 0; return true; } /* ============ Cmd_ExecuteAlias Called for aliases and fills in the alias into the cbuffer ============ */ static void Cmd_ExecuteAlias (cmdalias_t *alias) { static char buffer[ MAX_INPUTLINE ]; // cmd_mutex static char buffer2[ MAX_INPUTLINE ]; // cmd_mutex qboolean ret = Cmd_PreprocessString( alias->value, buffer, sizeof(buffer) - 2, alias ); if(!ret) return; // insert at start of command buffer, so that aliases execute in order // (fixes bug introduced by Black on 20050705) // Note: Cbuf_PreprocessString will be called on this string AGAIN! So we // have to make sure that no second variable expansion takes place, otherwise // alias parameters containing dollar signs can have bad effects. Cmd_QuoteString(buffer2, sizeof(buffer2), buffer, "$", false); Cbuf_InsertText( buffer2 ); } /* ======== Cmd_List CmdList Added by EvilTypeGuy eviltypeguy@qeradiant.com Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ ======== */ static void Cmd_List_f (void) { cmd_function_t *cmd; const char *partial; size_t len; int count; qboolean ispattern; if (Cmd_Argc() > 1) { partial = Cmd_Argv (1); len = strlen(partial); ispattern = (strchr(partial, '*') || strchr(partial, '?')); } else { partial = NULL; len = 0; ispattern = false; } count = 0; for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (partial && (ispattern ? !matchpattern_with_separator(cmd->name, partial, false, "", false) : strncmp(partial, cmd->name, len))) continue; Con_Printf("%s : %s\n", cmd->name, cmd->description); count++; } if (len) { if(ispattern) Con_Printf("%i Command%s matching \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); else Con_Printf("%i Command%s beginning with \"%s\"\n\n", count, (count > 1) ? "s" : "", partial); } else Con_Printf("%i Command%s\n\n", count, (count > 1) ? "s" : ""); } static void Cmd_Apropos_f(void) { cmd_function_t *cmd; cvar_t *cvar; cmdalias_t *alias; const char *partial; int count; qboolean ispattern; char vabuf[1024]; if (Cmd_Argc() > 1) partial = Cmd_Args(); else { Con_Printf("usage: apropos \n"); return; } ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); if(!ispattern) partial = va(vabuf, sizeof(vabuf), "*%s*", partial); count = 0; for (cvar = cvar_vars; cvar; cvar = cvar->next) { if (!matchpattern_with_separator(cvar->name, partial, true, "", false)) if (!matchpattern_with_separator(cvar->description, partial, true, "", false)) continue; Con_Printf ("cvar ^3%s^7 is \"%s\" [\"%s\"] %s\n", cvar->name, cvar->string, cvar->defstring, cvar->description); count++; } for (cmd = cmd_functions; cmd; cmd = cmd->next) { if (!matchpattern_with_separator(cmd->name, partial, true, "", false)) if (!matchpattern_with_separator(cmd->description, partial, true, "", false)) continue; Con_Printf("command ^2%s^7: %s\n", cmd->name, cmd->description); count++; } for (alias = cmd_alias; alias; alias = alias->next) { // procede here a bit differently as an alias value always got a final \n if (!matchpattern_with_separator(alias->name, partial, true, "", false)) if (!matchpattern_with_separator(alias->value, partial, true, "\n", false)) // when \n is as separator wildcards don't match it continue; Con_Printf("alias ^5%s^7: %s", alias->name, alias->value); // do not print an extra \n count++; } Con_Printf("%i result%s\n\n", count, (count > 1) ? "s" : ""); } /* ============ Cmd_Init ============ */ void Cmd_Init (void) { cmd_mempool = Mem_AllocPool("commands", 0, NULL); // space for commands and script files cmd_text.data = cmd_text_buf; cmd_text.maxsize = sizeof(cmd_text_buf); cmd_text.cursize = 0; if (Thread_HasThreads()) cmd_text_mutex = Thread_CreateMutex(); } void Cmd_Init_Commands (void) { // // register our commands // Cmd_AddCommand ("stuffcmds",Cmd_StuffCmds_f, "execute commandline parameters (must be present in quake.rc script)"); Cmd_AddCommand ("exec",Cmd_Exec_f, "execute a script file"); Cmd_AddCommand ("echo",Cmd_Echo_f, "print a message to the console (useful in scripts)"); Cmd_AddCommand ("alias",Cmd_Alias_f, "create a script function (parameters are passed in as $X (being X a number), $* for all parameters, $X- for all parameters starting from $X). Without arguments show the list of all alias"); Cmd_AddCommand ("unalias",Cmd_UnAlias_f, "remove an alias"); Cmd_AddCommand ("cmd", Cmd_ForwardToServer, "send a console commandline to the server (used by some mods)"); Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for next rendered frame"); Cmd_AddCommand ("set", Cvar_Set_f, "create or change the value of a console variable"); Cmd_AddCommand ("seta", Cvar_SetA_f, "create or change the value of a console variable that will be saved to config.cfg"); Cmd_AddCommand ("unset", Cvar_Del_f, "delete a cvar (does not work for static ones like _cl_name, or read-only ones)"); #ifdef FILLALLCVARSWITHRUBBISH Cmd_AddCommand ("fillallcvarswithrubbish", Cvar_FillAll_f, "fill all cvars with a specified number of characters to provoke buffer overruns"); #endif /* FILLALLCVARSWITHRUBBISH */ // 2000-01-09 CmdList, CvarList commands By Matthias "Maddes" Buecher // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com Cmd_AddCommand ("cmdlist", Cmd_List_f, "lists all console commands beginning with the specified prefix or matching the specified wildcard pattern"); Cmd_AddCommand ("cvarlist", Cvar_List_f, "lists all console variables beginning with the specified prefix or matching the specified wildcard pattern"); Cmd_AddCommand ("apropos", Cmd_Apropos_f, "lists all console variables/commands/aliases containing the specified string in the name or description"); Cmd_AddCommand ("cvar_lockdefaults", Cvar_LockDefaults_f, "stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg"); Cmd_AddCommand ("cvar_resettodefaults_all", Cvar_ResetToDefaults_All_f, "sets all cvars to their locked default values"); Cmd_AddCommand ("cvar_resettodefaults_nosaveonly", Cvar_ResetToDefaults_NoSaveOnly_f, "sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg)"); Cmd_AddCommand ("cvar_resettodefaults_saveonly", Cvar_ResetToDefaults_SaveOnly_f, "sets all saved cvars to their locked default values (variables that will be saved to config.cfg)"); Cmd_AddCommand ("cprint", Cmd_Centerprint_f, "print something at the screen center"); Cmd_AddCommand ("defer", Cmd_Defer_f, "execute a command in the future"); // DRESK - 5/14/06 // Support Doom3-style Toggle Command Cmd_AddCommand( "toggle", Cmd_Toggle_f, "toggles a console variable's values (use for more info)"); } /* ============ Cmd_Shutdown ============ */ void Cmd_Shutdown(void) { if (cmd_text_mutex) { // we usually have this locked when we get here from Host_Quit_f Cbuf_UnlockThreadMutex(); Thread_DestroyMutex(cmd_text_mutex); } cmd_text_mutex = NULL; Mem_FreePool(&cmd_mempool); } /* ============ Cmd_Argc ============ */ int Cmd_Argc (void) { return cmd_argc; } /* ============ Cmd_Argv ============ */ const char *Cmd_Argv (int arg) { if (arg >= cmd_argc ) return cmd_null_string; return cmd_argv[arg]; } /* ============ Cmd_Args ============ */ const char *Cmd_Args (void) { return cmd_args; } /* ============ Cmd_TokenizeString Parses the given string into command line tokens. ============ */ // AK: This function should only be called from ExcuteString because the current design is a bit of an hack static void Cmd_TokenizeString (const char *text) { int l; cmd_argc = 0; cmd_args = NULL; while (1) { // skip whitespace up to a /n while (*text && ISWHITESPACE(*text) && *text != '\r' && *text != '\n') text++; // line endings: // UNIX: \n // Mac: \r // Windows: \r\n if (*text == '\n' || *text == '\r') { // a newline separates commands in the buffer if (*text == '\r' && text[1] == '\n') text++; text++; break; } if (!*text) return; if (cmd_argc == 1) cmd_args = text; if (!COM_ParseToken_Console(&text)) return; if (cmd_argc < MAX_ARGS) { l = (int)strlen(com_token) + 1; if (cmd_tokenizebufferpos + l > CMD_TOKENIZELENGTH) { Con_Printf("Cmd_TokenizeString: ran out of %i character buffer space for command arguements\n", CMD_TOKENIZELENGTH); break; } memcpy (cmd_tokenizebuffer + cmd_tokenizebufferpos, com_token, l); cmd_argv[cmd_argc] = cmd_tokenizebuffer + cmd_tokenizebufferpos; cmd_tokenizebufferpos += l; cmd_argc++; } } } /* ============ Cmd_AddCommand ============ */ void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description) { cmd_function_t *cmd; cmd_function_t *prev, *current; // fail if the command is a variable name if (Cvar_FindVar( cmd_name )) { Con_Printf("Cmd_AddCommand: %s already defined as a var\n", cmd_name); return; } // fail if the command already exists for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { if (!strcmp (cmd_name, cmd->name)) { if (consolefunction || clientfunction) { Con_Printf("Cmd_AddCommand: %s already defined\n", cmd_name); return; } else //[515]: csqc { cmd->csqcfunc = true; return; } } } cmd = (cmd_function_t *)Mem_Alloc(cmd_mempool, sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->consolefunction = consolefunction; cmd->clientfunction = clientfunction; cmd->description = description; if(!consolefunction && !clientfunction) //[515]: csqc cmd->csqcfunc = true; cmd->next = cmd_functions; // insert it at the right alphanumeric position for( prev = NULL, current = cmd_functions ; current && strcmp( current->name, cmd->name ) < 0 ; prev = current, current = current->next ) ; if( prev ) { prev->next = cmd; } else { cmd_functions = cmd; } cmd->next = current; } void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description) { Cmd_AddCommand_WithClientCommand (cmd_name, function, NULL, description); } /* ============ Cmd_Exists ============ */ qboolean Cmd_Exists (const char *cmd_name) { cmd_function_t *cmd; for (cmd=cmd_functions ; cmd ; cmd=cmd->next) if (!strcmp (cmd_name,cmd->name)) return true; return false; } /* ============ Cmd_CompleteCommand ============ */ const char *Cmd_CompleteCommand (const char *partial) { cmd_function_t *cmd; size_t len; len = strlen(partial); if (!len) return NULL; // check functions for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp(partial, cmd->name, len)) return cmd->name; return NULL; } /* Cmd_CompleteCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ int Cmd_CompleteCountPossible (const char *partial) { cmd_function_t *cmd; size_t len; int h; h = 0; len = strlen(partial); if (!len) return 0; // Loop through the command list and count all partial matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp(partial, cmd->name, len)) h++; return h; } /* Cmd_CompleteBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char **Cmd_CompleteBuildList (const char *partial) { cmd_function_t *cmd; size_t len = 0; size_t bpos = 0; size_t sizeofbuf = (Cmd_CompleteCountPossible (partial) + 1) * sizeof (const char *); const char **buf; len = strlen(partial); buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); // Loop through the alias list and print all matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp(partial, cmd->name, len)) buf[bpos++] = cmd->name; buf[bpos] = NULL; return buf; } // written by LordHavoc void Cmd_CompleteCommandPrint (const char *partial) { cmd_function_t *cmd; size_t len = strlen(partial); // Loop through the command list and print all matches for (cmd = cmd_functions; cmd; cmd = cmd->next) if (!strncasecmp(partial, cmd->name, len)) Con_Printf("^2%s^7: %s\n", cmd->name, cmd->description); } /* Cmd_CompleteAlias New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char *Cmd_CompleteAlias (const char *partial) { cmdalias_t *alias; size_t len; len = strlen(partial); if (!len) return NULL; // Check functions for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) return alias->name; return NULL; } // written by LordHavoc void Cmd_CompleteAliasPrint (const char *partial) { cmdalias_t *alias; size_t len = strlen(partial); // Loop through the alias list and print all matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) Con_Printf("^5%s^7: %s", alias->name, alias->value); } /* Cmd_CompleteAliasCountPossible New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ int Cmd_CompleteAliasCountPossible (const char *partial) { cmdalias_t *alias; size_t len; int h; h = 0; len = strlen(partial); if (!len) return 0; // Loop through the command list and count all partial matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) h++; return h; } /* Cmd_CompleteAliasBuildList New function for tab-completion system Added by EvilTypeGuy Thanks to Fett erich@heintz.com Thanks to taniwha */ const char **Cmd_CompleteAliasBuildList (const char *partial) { cmdalias_t *alias; size_t len = 0; size_t bpos = 0; size_t sizeofbuf = (Cmd_CompleteAliasCountPossible (partial) + 1) * sizeof (const char *); const char **buf; len = strlen(partial); buf = (const char **)Mem_Alloc(tempmempool, sizeofbuf + sizeof (const char *)); // Loop through the alias list and print all matches for (alias = cmd_alias; alias; alias = alias->next) if (!strncasecmp(partial, alias->name, len)) buf[bpos++] = alias->name; buf[bpos] = NULL; return buf; } void Cmd_ClearCsqcFuncs (void) { cmd_function_t *cmd; for (cmd=cmd_functions ; cmd ; cmd=cmd->next) cmd->csqcfunc = false; } /* ============ Cmd_ExecuteString A complete command line has been parsed, so try to execute it FIXME: lookupnoadd the token to speed search? ============ */ void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex) { int oldpos; int found; cmd_function_t *cmd; cmdalias_t *a; if (lockmutex) Cbuf_LockThreadMutex(); oldpos = cmd_tokenizebufferpos; cmd_source = src; found = false; Cmd_TokenizeString (text); // execute the command line if (!Cmd_Argc()) goto done; // no tokens // check functions for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { if (!strcasecmp (cmd_argv[0],cmd->name)) { if (cmd->csqcfunc && CL_VM_ConsoleCommand (text)) //[515]: csqc goto done; switch (src) { case src_command: if (cmd->consolefunction) cmd->consolefunction (); else if (cmd->clientfunction) { if (cls.state == ca_connected) { // forward remote commands to the server for execution Cmd_ForwardToServer(); } else Con_Printf("Can not send command \"%s\", not connected.\n", Cmd_Argv(0)); } else Con_Printf("Command \"%s\" can not be executed\n", Cmd_Argv(0)); found = true; goto command_found; case src_client: if (cmd->clientfunction) { cmd->clientfunction (); goto done; } break; } break; } } command_found: // if it's a client command and no command was found, say so. if (cmd_source == src_client) { Con_Printf("player \"%s\" tried to %s\n", host_client->name, text); goto done; } // check alias for (a=cmd_alias ; a ; a=a->next) { if (!strcasecmp (cmd_argv[0], a->name)) { Cmd_ExecuteAlias(a); goto done; } } if(found) // if the command was hooked and found, all is good goto done; // check cvars if (!Cvar_Command () && host_framecount > 0) Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0)); done: cmd_tokenizebufferpos = oldpos; if (lockmutex) Cbuf_UnlockThreadMutex(); } /* =================== Cmd_ForwardStringToServer Sends an entire command string over to the server, unprocessed =================== */ void Cmd_ForwardStringToServer (const char *s) { char temp[128]; if (cls.state != ca_connected) { Con_Printf("Can't \"%s\", not connected\n", s); return; } if (!cls.netcon) return; // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my // attention, it has been eradicated from here, its only (former) use in // all of darkplaces. if (cls.protocol == PROTOCOL_QUAKEWORLD) MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); else MSG_WriteByte(&cls.netcon->message, clc_stringcmd); if ((!strncmp(s, "say ", 4) || !strncmp(s, "say_team ", 9)) && cl_locs_enable.integer) { // say/say_team commands can replace % character codes with status info while (*s) { if (*s == '%' && s[1]) { // handle proquake message macros temp[0] = 0; switch (s[1]) { case 'l': // current location CL_Locs_FindLocationName(temp, sizeof(temp), cl.movement_origin); break; case 'h': // current health dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_HEALTH]); break; case 'a': // current armor dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ARMOR]); break; case 'x': // current rockets dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_ROCKETS]); break; case 'c': // current cells dpsnprintf(temp, sizeof(temp), "%i", cl.stats[STAT_CELLS]); break; // silly proquake macros case 'd': // loc at last death CL_Locs_FindLocationName(temp, sizeof(temp), cl.lastdeathorigin); break; case 't': // current time dpsnprintf(temp, sizeof(temp), "%.0f:%.0f", floor(cl.time / 60), cl.time - floor(cl.time / 60) * 60); break; case 'r': // rocket launcher status ("I have RL", "I need rockets", "I need RL") if (!(cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER)) dpsnprintf(temp, sizeof(temp), "I need RL"); else if (!cl.stats[STAT_ROCKETS]) dpsnprintf(temp, sizeof(temp), "I need rockets"); else dpsnprintf(temp, sizeof(temp), "I have RL"); break; case 'p': // powerup status (outputs "quad" "pent" and "eyes" according to status) if (cl.stats[STAT_ITEMS] & IT_QUAD) { if (temp[0]) strlcat(temp, " ", sizeof(temp)); strlcat(temp, "quad", sizeof(temp)); } if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) { if (temp[0]) strlcat(temp, " ", sizeof(temp)); strlcat(temp, "pent", sizeof(temp)); } if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) { if (temp[0]) strlcat(temp, " ", sizeof(temp)); strlcat(temp, "eyes", sizeof(temp)); } break; case 'w': // weapon status (outputs "SSG:NG:SNG:GL:RL:LG" with the text between : characters omitted if you lack the weapon) if (cl.stats[STAT_ITEMS] & IT_SUPER_SHOTGUN) strlcat(temp, "SSG", sizeof(temp)); strlcat(temp, ":", sizeof(temp)); if (cl.stats[STAT_ITEMS] & IT_NAILGUN) strlcat(temp, "NG", sizeof(temp)); strlcat(temp, ":", sizeof(temp)); if (cl.stats[STAT_ITEMS] & IT_SUPER_NAILGUN) strlcat(temp, "SNG", sizeof(temp)); strlcat(temp, ":", sizeof(temp)); if (cl.stats[STAT_ITEMS] & IT_GRENADE_LAUNCHER) strlcat(temp, "GL", sizeof(temp)); strlcat(temp, ":", sizeof(temp)); if (cl.stats[STAT_ITEMS] & IT_ROCKET_LAUNCHER) strlcat(temp, "RL", sizeof(temp)); strlcat(temp, ":", sizeof(temp)); if (cl.stats[STAT_ITEMS] & IT_LIGHTNING) strlcat(temp, "LG", sizeof(temp)); break; default: // not a recognized macro, print it as-is... temp[0] = s[0]; temp[1] = s[1]; temp[2] = 0; break; } // write the resulting text SZ_Write(&cls.netcon->message, (unsigned char *)temp, (int)strlen(temp)); s += 2; continue; } MSG_WriteByte(&cls.netcon->message, *s); s++; } MSG_WriteByte(&cls.netcon->message, 0); } else // any other command is passed on as-is SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1); } /* =================== Cmd_ForwardToServer Sends the entire command line over to the server =================== */ void Cmd_ForwardToServer (void) { const char *s; char vabuf[1024]; if (!strcasecmp(Cmd_Argv(0), "cmd")) { // we want to strip off "cmd", so just send the args s = Cmd_Argc() > 1 ? Cmd_Args() : ""; } else { // we need to keep the command name, so send Cmd_Argv(0), a space and then Cmd_Args() s = va(vabuf, sizeof(vabuf), "%s %s", Cmd_Argv(0), Cmd_Argc() > 1 ? Cmd_Args() : ""); } // don't send an empty forward message if the user tries "cmd" by itself if (!s || !*s) return; Cmd_ForwardStringToServer(s); } /* ================ Cmd_CheckParm Returns the position (1 to argc-1) in the command's argument list where the given parameter apears, or 0 if not present ================ */ int Cmd_CheckParm (const char *parm) { int i; if (!parm) { Con_Printf ("Cmd_CheckParm: NULL"); return 0; } for (i = 1; i < Cmd_Argc (); i++) if (!strcasecmp (parm, Cmd_Argv (i))) return i; return 0; } void Cmd_SaveInitState(void) { cmd_function_t *f; cmdalias_t *a; for (f = cmd_functions;f;f = f->next) f->initstate = true; for (a = cmd_alias;a;a = a->next) { a->initstate = true; a->initialvalue = Mem_strdup(zonemempool, a->value); } Cvar_SaveInitState(); } void Cmd_RestoreInitState(void) { cmd_function_t *f, **fp; cmdalias_t *a, **ap; for (fp = &cmd_functions;(f = *fp);) { if (f->initstate) fp = &f->next; else { // destroy this command, it didn't exist at init Con_DPrintf("Cmd_RestoreInitState: Destroying command %s\n", f->name); *fp = f->next; Z_Free(f); } } for (ap = &cmd_alias;(a = *ap);) { if (a->initstate) { // restore this alias, it existed at init if (strcmp(a->value ? a->value : "", a->initialvalue ? a->initialvalue : "")) { Con_DPrintf("Cmd_RestoreInitState: Restoring alias %s\n", a->name); if (a->value) Z_Free(a->value); a->value = Mem_strdup(zonemempool, a->initialvalue); } ap = &a->next; } else { // free this alias, it didn't exist at init... Con_DPrintf("Cmd_RestoreInitState: Destroying alias %s\n", a->name); *ap = a->next; if (a->value) Z_Free(a->value); Z_Free(a); } } Cvar_RestoreInitState(); } darkplaces/model_zymotic.h0000664000175000017500000000413413067716222015216 0ustar kalevkalev #ifndef MODEL_ZYMOTIC_H #define MODEL_ZYMOTIC_H typedef struct zymlump_s { int start; int length; } zymlump_t; typedef struct zymtype1header_s { char id[12]; // "ZYMOTICMODEL", length 12, no termination int type; // 0 (vertex morph) 1 (skeletal pose) or 2 (skeletal scripted) int filesize; // size of entire model file float mins[3], maxs[3], radius; // for clipping uses int numverts; int numtris; int numshaders; int numbones; // this may be zero in the vertex morph format (undecided) int numscenes; // 0 in skeletal scripted models // skeletal pose header // lump offsets are relative to the file zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) zymlump_t lump_poses; // float pose[numposes][numbones][6]; // animation data zymlump_t lump_bones; // zymbone_t bone[numbones]; zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct zymlump_t lump_texcoords; // float texcoords[numvertices][2]; zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation } zymtype1header_t; #define ZYMBONEFLAG_SHARED 1 typedef struct zymbone_s { char name[32]; int flags; int parent; // parent bone number } zymbone_t; // normally the scene will loop, if this is set it will stay on the final frame #define ZYMSCENEFLAG_NOLOOP 1 typedef struct zymscene_s { char name[32]; float mins[3], maxs[3], radius; // for clipping float framerate; // the scene will animate at this framerate (in frames per second) int flags; int start, length; // range of poses } zymscene_t; typedef struct zymvertex_s { int bonenum; float origin[3]; } zymvertex_t; #endif darkplaces/utf8lib.h0000664000175000017500000000567313067716222013726 0ustar kalevkalev/* * UTF-8 utility functions for DarkPlaces */ #ifndef UTF8LIB_H__ #define UTF8LIB_H__ #include "qtypes.h" // types for unicode strings // let them be 32 bit for now // normally, whcar_t is 16 or 32 bit, 16 on linux I think, 32 on haiku and maybe windows #ifdef _MSC_VER typedef __int32 U_int32; #else #include #include typedef int32_t U_int32; #endif // Uchar, a wide character typedef U_int32 Uchar; // Initialize UTF8, this registers cvars which allows for UTF8 to be disabled // completely. // When UTF8 is disabled, every u8_ function will work exactly as you'd expect // a non-utf8 version to work: u8_strlen() will wrap to strlen() // u8_byteofs() and u8_charidx() will simply return whatever is passed as index parameter // u8_getchar() will will just return the next byte, u8_fromchar will write one byte, ... extern cvar_t utf8_enable; void u8_Init(void); size_t u8_strlen(const char*); size_t u8_strnlen(const char*, size_t); int u8_byteofs(const char*, size_t, size_t*); int u8_charidx(const char*, size_t, size_t*); size_t u8_bytelen(const char*, size_t); size_t u8_prevbyte(const char*, size_t); Uchar u8_getchar_utf8_enabled(const char*, const char**); Uchar u8_getnchar_utf8_enabled(const char*, const char**, size_t); int u8_fromchar(Uchar, char*, size_t); size_t u8_mbstowcs(Uchar *, const char *, size_t); size_t u8_wcstombs(char*, const Uchar*, size_t); size_t u8_COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid); // returns a static buffer, use this for inlining char *u8_encodech(Uchar ch, size_t*, char*buf16); size_t u8_strpad(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth); size_t u8_strpad_colorcodes(char *out, size_t outsize, const char *in, qboolean leftalign, size_t minwidth, size_t maxwidth); /* Careful: if we disable utf8 but not freetype, we wish to see freetype chars * for normal letters. So use E000+x for special chars, but leave the freetype stuff for the * rest: */ extern Uchar u8_quake2utf8map[256]; // these defines get a bit tricky, as c and e may be aliased to the same variable #define u8_getchar(c,e) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,e) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]])) #define u8_getchar_noendptr(c) (utf8_enable.integer ? u8_getchar_utf8_enabled(c,NULL) : (u8_quake2utf8map[(unsigned char)*(c)])) #define u8_getchar_check(c,e) ((e) ? u8_getchar((c),(e)) : u8_getchar_noendptr((c))) #define u8_getnchar(c,e,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,e,n) : ((n) <= 0 ? ((*(e) = c), 0) : (u8_quake2utf8map[(unsigned char)(*(e) = (c) + 1)[-1]]))) #define u8_getnchar_noendptr(c,n) (utf8_enable.integer ? u8_getnchar_utf8_enabled(c,NULL,n) : ((n) <= 0 ? 0 : (u8_quake2utf8map[(unsigned char)*(c)]))) #define u8_getnchar_check(c,e,n) ((e) ? u8_getchar((c),(e),(n)) : u8_getchar_noendptr((c),(n))) Uchar u8_toupper(Uchar ch); Uchar u8_tolower(Uchar ch); #endif // UTF8LIB_H__ darkplaces/image.h0000664000175000017500000000765313067716220013431 0ustar kalevkalev #ifndef IMAGE_H #define IMAGE_H extern int image_width, image_height; // swizzle components (even converting number of components) and flip images // (warning: input must be different than output due to non-linear read/write) // (tip: component indices can contain values | 0x80000000 to tell it to // store them directly into output, so 255 | 0x80000000 would write 255) void Image_CopyMux(unsigned char *outpixels, const unsigned char *inpixels, int inputwidth, int inputheight, qboolean inputflipx, qboolean inputflipy, qboolean inputflipdiagonal, int numoutputcomponents, int numinputcomponents, int *outputinputcomponentindices); // applies gamma correction to RGB pixels, in can be the same as out void Image_GammaRemapRGB(const unsigned char *in, unsigned char *out, int pixels, const unsigned char *gammar, const unsigned char *gammag, const unsigned char *gammab); // converts 8bit image data to BGRA, in can not be the same as out void Image_Copy8bitBGRA(const unsigned char *in, unsigned char *out, int pixels, const unsigned int *pal); void Image_StripImageExtension (const char *in, char *out, size_t size_out); // called by conchars.tga loader in gl_draw.c, otherwise private unsigned char *LoadTGA_BGRA (const unsigned char *f, int filesize, int *miplevel); // loads a texture, as pixel data unsigned char *loadimagepixelsbgra (const char *filename, qboolean complain, qboolean allowFixtrans, qboolean convertsRGB, int *miplevel); // loads an 8bit pcx image into a 296x194x8bit buffer, with cropping as needed qboolean LoadPCX_QWSkin(const unsigned char *f, int filesize, unsigned char *pixels, int outwidth, int outheight); // loads the palette from an 8bit pcx image into your provided array qboolean LoadPCX_PaletteOnly(const unsigned char *f, int filesize, unsigned char *palette768b); // get the metadata from a Quake2 wal file qboolean LoadWAL_GetMetadata(const unsigned char *f, int filesize, int *retwidth, int *retheight, int *retflags, int *retvalue, int *retcontents, char *retanimname32c); // loads a texture, as a texture rtexture_t *loadtextureimage (rtexturepool_t *pool, const char *filename, qboolean complain, int flags, qboolean allowFixtrans, qboolean sRGB); // writes an upside down BGR image into a TGA qboolean Image_WriteTGABGR_preflipped (const char *filename, int width, int height, const unsigned char *data); // writes a BGRA image into a TGA file qboolean Image_WriteTGABGRA (const char *filename, int width, int height, const unsigned char *data); // resizes the image (in can not be the same as out) void Image_Resample32(const void *indata, int inwidth, int inheight, int indepth, void *outdata, int outwidth, int outheight, int outdepth, int quality); // scales the image down by a power of 2 (in can be the same as out) void Image_MipReduce32(const unsigned char *in, unsigned char *out, int *width, int *height, int *depth, int destwidth, int destheight, int destdepth); void Image_HeightmapToNormalmap_BGRA(const unsigned char *inpixels, unsigned char *outpixels, int width, int height, int clamp, float bumpscale); // console command to fix the colors of transparent pixels (to prevent weird borders) void Image_FixTransparentPixels_f(void); extern cvar_t r_fixtrans_auto; #define Image_LinearFloatFromsRGBFloat(c) (((c) <= 0.04045f) ? (c) * (1.0f / 12.92f) : (float)pow(((c) + 0.055f)*(1.0f/1.055f), 2.4f)) #define Image_sRGBFloatFromLinearFloat(c) (((c) < 0.0031308f) ? (c) * 12.92f : 1.055f * (float)pow((c), 1.0f/2.4f) - 0.055f) #define Image_LinearFloatFromsRGB(c) Image_LinearFloatFromsRGBFloat((c) * (1.0f / 255.0f)) #define Image_sRGBFloatFromLinear(c) Image_sRGBFloatFromLinearFloat((c) * (1.0f / 255.0f)) #define Image_sRGBFloatFromLinear_Lightmap(c) Image_sRGBFloatFromLinearFloat((c) * (2.0f / 255.0f)) * 0.5f void Image_MakeLinearColorsFromsRGB(unsigned char *pout, const unsigned char *pin, int numpixels); void Image_MakesRGBColorsFromLinear_Lightmap(unsigned char *pout, const unsigned char *pin, int numpixels); #endif darkplaces/bih.c0000664000175000017500000001453113067716216013102 0ustar kalevkalev // This code written in 2010 by Forest Hale (darkplacesengine gmail com), and placed into public domain. #include #include #include "bih.h" static int BIH_BuildNode(bih_t *bih, int numchildren, int *leaflist, float *totalmins, float *totalmaxs) { int i; int j; int longestaxis; int axis = 0; int nodenum; int front = 0; int back = 0; bih_node_t *node; bih_leaf_t *child; float splitdist; float d; float mins[3]; float maxs[3]; float size[3]; float frontmins[3]; float frontmaxs[3]; float backmins[3]; float backmaxs[3]; // calculate bounds of children child = bih->leafs + leaflist[0]; mins[0] = child->mins[0]; mins[1] = child->mins[1]; mins[2] = child->mins[2]; maxs[0] = child->maxs[0]; maxs[1] = child->maxs[1]; maxs[2] = child->maxs[2]; for (i = 1;i < numchildren;i++) { child = bih->leafs + leaflist[i]; if (mins[0] > child->mins[0]) mins[0] = child->mins[0]; if (mins[1] > child->mins[1]) mins[1] = child->mins[1]; if (mins[2] > child->mins[2]) mins[2] = child->mins[2]; if (maxs[0] < child->maxs[0]) maxs[0] = child->maxs[0]; if (maxs[1] < child->maxs[1]) maxs[1] = child->maxs[1]; if (maxs[2] < child->maxs[2]) maxs[2] = child->maxs[2]; } size[0] = maxs[0] - mins[0]; size[1] = maxs[1] - mins[1]; size[2] = maxs[2] - mins[2]; // provide bounds to caller totalmins[0] = mins[0]; totalmins[1] = mins[1]; totalmins[2] = mins[2]; totalmaxs[0] = maxs[0]; totalmaxs[1] = maxs[1]; totalmaxs[2] = maxs[2]; // if we run out of nodes it's the caller's fault, but don't crash if (bih->numnodes == bih->maxnodes) { if (!bih->error) bih->error = BIHERROR_OUT_OF_NODES; return 0; } nodenum = bih->numnodes++; node = bih->nodes + nodenum; // store bounds for node node->mins[0] = mins[0]; node->mins[1] = mins[1]; node->mins[2] = mins[2]; node->maxs[0] = maxs[0]; node->maxs[1] = maxs[1]; node->maxs[2] = maxs[2]; node->front = 0; node->back = 0; node->frontmin = 0; node->backmax = 0; memset(node->children, -1, sizeof(node->children)); // check if there are few enough children to store an unordered node if (numchildren <= BIH_MAXUNORDEREDCHILDREN) { node->type = BIH_UNORDERED; for (j = 0;j < numchildren;j++) node->children[j] = leaflist[j]; return nodenum; } // pick longest axis longestaxis = 0; if (size[0] < size[1]) longestaxis = 1; if (size[longestaxis] < size[2]) longestaxis = 2; // iterate possible split axis choices, starting with the longest axis, if // all fail it means all children have the same bounds and we simply split // the list in half because each node can only have two children. for (j = 0;j < 3;j++) { // pick an axis axis = (longestaxis + j) % 3; // sort children into front and back lists splitdist = (node->mins[axis] + node->maxs[axis]) * 0.5f; front = 0; back = 0; for (i = 0;i < numchildren;i++) { child = bih->leafs + leaflist[i]; d = (child->mins[axis] + child->maxs[axis]) * 0.5f; if (d < splitdist) bih->leafsortscratch[back++] = leaflist[i]; else leaflist[front++] = leaflist[i]; } // now copy the back ones into the space made in the leaflist for them if (back) memcpy(leaflist + front, bih->leafsortscratch, back*sizeof(leaflist[0])); // if both sides have some children, it's good enough for us. if (front && back) break; } if (j == 3) { // somewhat common case: no good choice, divide children arbitrarily axis = 0; back = numchildren >> 1; front = numchildren - back; } // we now have front and back children divided in leaflist... node->type = (bih_nodetype_t)((int)BIH_SPLITX + axis); node->front = BIH_BuildNode(bih, front, leaflist, frontmins, frontmaxs); node->frontmin = frontmins[axis]; node->back = BIH_BuildNode(bih, back, leaflist + front, backmins, backmaxs); node->backmax = backmaxs[axis]; return nodenum; } int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch) { int i; memset(bih, 0, sizeof(*bih)); bih->numleafs = numleafs; bih->leafs = leafs; bih->leafsort = temp_leafsort; bih->leafsortscratch = temp_leafsortscratch; bih->numnodes = 0; bih->maxnodes = maxnodes; bih->nodes = nodes; // clear things we intend to rebuild memset(bih->nodes, 0, sizeof(bih->nodes[0]) * bih->maxnodes); for (i = 0;i < bih->numleafs;i++) bih->leafsort[i] = i; bih->rootnode = BIH_BuildNode(bih, bih->numleafs, bih->leafsort, bih->mins, bih->maxs); return bih->error; } static void BIH_GetTriangleListForBox_Node(const bih_t *bih, int nodenum, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, int *numtrianglespointer, const float *mins, const float *maxs) { int axis; bih_node_t *node; bih_leaf_t *leaf; for(;;) { node = bih->nodes + nodenum; // check if this is an unordered node (which holds an array of leaf numbers) if (node->type == BIH_UNORDERED) { for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) { leaf = bih->leafs + node->children[axis]; if (mins[0] > leaf->maxs[0] || maxs[0] < leaf->mins[0] || mins[1] > leaf->maxs[1] || maxs[1] < leaf->mins[1] || mins[2] > leaf->maxs[2] || maxs[2] < leaf->mins[2]) continue; switch(leaf->type) { case BIH_RENDERTRIANGLE: if (*numtrianglespointer >= maxtriangles) { ++*numtrianglespointer; // so the caller can detect overflow break; } if(trianglelist_surf) trianglelist_surf[*numtrianglespointer] = leaf->surfaceindex; trianglelist_idx[*numtrianglespointer] = leaf->itemindex; ++*numtrianglespointer; break; default: break; } } return; } // splitting node axis = node->type - BIH_SPLITX; if (mins[axis] < node->backmax) { if (maxs[axis] > node->frontmin) BIH_GetTriangleListForBox_Node(bih, node->front, maxtriangles, trianglelist_idx, trianglelist_surf, numtrianglespointer, mins, maxs); nodenum = node->back; continue; } if (maxs[axis] > node->frontmin) { nodenum = node->front; continue; } // fell between the child groups, nothing here return; } } int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs) { int numtriangles = 0; BIH_GetTriangleListForBox_Node(bih, bih->rootnode, maxtriangles, trianglelist_idx, trianglelist_surf, &numtriangles, mins, maxs); return numtriangles; } darkplaces/snd_bsd.c0000664000175000017500000001232713067716222013752 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include #include #ifndef SUNOS # include #endif #include #include #ifndef SUNOS # include #endif #include #include "snd_main.h" static int audio_fd = -1; /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { unsigned int i; const char *snddev; audio_info_t info; // Open the audio device #ifdef _PATH_SOUND snddev = _PATH_SOUND; #elif defined(SUNOS) snddev = "/dev/audio"; #else snddev = "/dev/sound"; #endif audio_fd = open (snddev, O_WRONLY | O_NDELAY | O_NONBLOCK); if (audio_fd < 0) { Con_Printf("Can't open the sound device (%s)\n", snddev); return false; } AUDIO_INITINFO (&info); #ifdef AUMODE_PLAY // NetBSD / OpenBSD info.mode = AUMODE_PLAY; #endif info.play.sample_rate = requested->speed; info.play.channels = requested->channels; info.play.precision = requested->width * 8; if (requested->width == 1) #ifdef SUNOS info.play.encoding = AUDIO_ENCODING_LINEAR8; #else info.play.encoding = AUDIO_ENCODING_ULINEAR; #endif else #ifdef SUNOS info.play.encoding = AUDIO_ENCODING_LINEAR; #else if (mem_bigendian) info.play.encoding = AUDIO_ENCODING_SLINEAR_BE; else info.play.encoding = AUDIO_ENCODING_SLINEAR_LE; #endif if (ioctl (audio_fd, AUDIO_SETINFO, &info) != 0) { Con_Printf("Can't set up the sound device (%s)\n", snddev); return false; } // TODO: check the parameters with AUDIO_GETINFO // TODO: check AUDIO_ENCODINGFLAG_EMULATED with AUDIO_GETENC snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); return true; } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown (void) { if (audio_fd >= 0) { close(audio_fd); audio_fd = -1; } if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { unsigned int startoffset, factor, limit, nbframes; int written; if (audio_fd < 0 || snd_renderbuffer->startframe == snd_renderbuffer->endframe) return; startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; limit = snd_renderbuffer->maxframes - startoffset; nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; if (nbframes > limit) { written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], limit * factor); if (written < 0) { Con_Printf("SndSys_Submit: audio write returned %d!\n", written); return; } if (written % factor != 0) Sys_Error("SndSys_Submit: nb of bytes written (%d) isn't aligned to a frame sample!\n", written); snd_renderbuffer->startframe += written / factor; if ((unsigned int)written < limit * factor) { Con_Printf("SndSys_Submit: audio can't keep up! (%u < %u)\n", written, limit * factor); return; } nbframes -= limit; startoffset = 0; } written = write (audio_fd, &snd_renderbuffer->ring[startoffset * factor], nbframes * factor); if (written < 0) { Con_Printf("SndSys_Submit: audio write returned %d!\n", written); return; } snd_renderbuffer->startframe += written / factor; } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { audio_info_t info; if (ioctl (audio_fd, AUDIO_GETINFO, &info) < 0) { Con_Print("Error: can't get audio info\n"); SndSys_Shutdown (); return 0; } return info.play.samples; } /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { // Nothing to do return true; } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { // Nothing to do } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/netconn.c0000775000175000017500000043471713067716222014020 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2002 Mathieu Olivier Copyright (C) 2003 Forest Hale This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "thread.h" #include "lhnet.h" // for secure rcon authentication #include "hmac.h" #include "mdfour.h" #include #define QWMASTER_PORT 27000 #define DPMASTER_PORT 27950 // note this defaults on for dedicated servers, off for listen servers cvar_t sv_public = {0, "sv_public", "0", "1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect; -3: already block at getchallenge level"}; cvar_t sv_public_rejectreason = {0, "sv_public_rejectreason", "The server is closing.", "Rejection reason for connects when sv_public is -2"}; static cvar_t sv_heartbeatperiod = {CVAR_SAVE, "sv_heartbeatperiod", "120", "how often to send heartbeat in seconds (only used if sv_public is 1)"}; extern cvar_t sv_status_privacy; static cvar_t sv_masters [] = { {CVAR_SAVE, "sv_master1", "", "user-chosen master server 1"}, {CVAR_SAVE, "sv_master2", "", "user-chosen master server 2"}, {CVAR_SAVE, "sv_master3", "", "user-chosen master server 3"}, {CVAR_SAVE, "sv_master4", "", "user-chosen master server 4"}, {0, "sv_masterextra1", "ghdigital.com", "ghdigital.com - default master server 1 (admin: LordHavoc)"}, // admin: LordHavoc {0, "sv_masterextra2", "dpmaster.deathmask.net", "dpmaster.deathmask.net - default master server 2 (admin: Willis)"}, // admin: Willis {0, "sv_masterextra3", "dpmaster.tchr.no", "dpmaster.tchr.no - default master server 3 (admin: tChr)"}, // admin: tChr {0, NULL, NULL, NULL} }; #ifdef CONFIG_MENU static cvar_t sv_qwmasters [] = { {CVAR_SAVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"}, {CVAR_SAVE, "sv_qwmaster2", "", "user-chosen qwmaster server 2"}, {CVAR_SAVE, "sv_qwmaster3", "", "user-chosen qwmaster server 3"}, {CVAR_SAVE, "sv_qwmaster4", "", "user-chosen qwmaster server 4"}, {0, "sv_qwmasterextra1", "master.quakeservers.net:27000", "Global master server. (admin: unknown)"}, {0, "sv_qwmasterextra2", "asgaard.morphos-team.net:27000", "Global master server. (admin: unknown)"}, {0, "sv_qwmasterextra3", "qwmaster.ocrana.de:27000", "German master server. (admin: unknown)"}, {0, "sv_qwmasterextra4", "qwmaster.fodquake.net:27000", "Global master server. (admin: unknown)"}, {0, NULL, NULL, NULL} }; #endif static double nextheartbeattime = 0; sizebuf_t cl_message; sizebuf_t sv_message; static unsigned char cl_message_buf[NET_MAXMESSAGE]; static unsigned char sv_message_buf[NET_MAXMESSAGE]; char cl_readstring[MAX_INPUTLINE]; char sv_readstring[MAX_INPUTLINE]; cvar_t net_test = {0, "net_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"}; cvar_t net_usesizelimit = {0, "net_usesizelimit", "2", "use packet size limiting (0: never, 1: in non-CSQC mode, 2: always)"}; cvar_t net_burstreserve = {0, "net_burstreserve", "0.3", "how much of the burst time to reserve for packet size spikes"}; cvar_t net_messagetimeout = {0, "net_messagetimeout","300", "drops players who have not sent any packets for this many seconds"}; cvar_t net_connecttimeout = {0, "net_connecttimeout","15", "after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods). Must be above 10 seconds."}; cvar_t net_connectfloodblockingtimeout = {0, "net_connectfloodblockingtimeout", "5", "when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods). Note that this does not include retries from the same IP; these are handled earlier and let in."}; cvar_t net_challengefloodblockingtimeout = {0, "net_challengefloodblockingtimeout", "0.5", "when a challenge packet is received, it will block all future challenge packets from that IP address for this many seconds (cuts down on challenge floods). DarkPlaces clients retry once per second, so this should be <= 1. Failure here may lead to connect attempts failing."}; cvar_t net_getstatusfloodblockingtimeout = {0, "net_getstatusfloodblockingtimeout", "1", "when a getstatus packet is received, it will block all future getstatus packets from that IP address for this many seconds (cuts down on getstatus floods). DarkPlaces retries every 4 seconds, and qstat retries once per second, so this should be <= 1. Failure here may lead to server not showing up in the server list."}; cvar_t net_sourceaddresscheck = {0, "net_sourceaddresscheck", "1", "compare the source IP address for replies (more secure, may break some bad multihoming setups"}; cvar_t hostname = {CVAR_SAVE, "hostname", "UNNAMED", "server message to show in server browser"}; cvar_t developer_networking = {0, "developer_networking", "0", "prints all received and sent packets (recommended only for debugging)"}; cvar_t cl_netlocalping = {0, "cl_netlocalping","0", "lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings)"}; static cvar_t cl_netpacketloss_send = {0, "cl_netpacketloss_send","0", "drops this percentage of outgoing packets, useful for testing network protocol robustness (jerky movement, prediction errors, etc)"}; static cvar_t cl_netpacketloss_receive = {0, "cl_netpacketloss_receive","0", "drops this percentage of incoming packets, useful for testing network protocol robustness (jerky movement, effects failing to start, sounds failing to play, etc)"}; static cvar_t net_slist_queriespersecond = {0, "net_slist_queriespersecond", "20", "how many server information requests to send per second"}; static cvar_t net_slist_queriesperframe = {0, "net_slist_queriesperframe", "4", "maximum number of server information requests to send each rendered frame (guards against low framerates causing problems)"}; static cvar_t net_slist_timeout = {0, "net_slist_timeout", "4", "how long to listen for a server information response before giving up"}; static cvar_t net_slist_pause = {0, "net_slist_pause", "0", "when set to 1, the server list won't update until it is set back to 0"}; static cvar_t net_slist_maxtries = {0, "net_slist_maxtries", "3", "how many times to ask the same server for information (more times gives better ping reports but takes longer)"}; static cvar_t net_slist_favorites = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "net_slist_favorites", "", "contains a list of IP addresses and ports to always query explicitly"}; static cvar_t net_tos_dscp = {CVAR_SAVE, "net_tos_dscp", "32", "DiffServ Codepoint for network sockets (may need game restart to apply)"}; static cvar_t gameversion = {0, "gameversion", "0", "version of game data (mod-specific) to be sent to querying clients"}; static cvar_t gameversion_min = {0, "gameversion_min", "-1", "minimum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; static cvar_t gameversion_max = {0, "gameversion_max", "-1", "maximum version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible; if -1, gameversion is used alone"}; static cvar_t rcon_restricted_password = {CVAR_PRIVATE, "rcon_restricted_password", "", "password to authenticate rcon commands in restricted mode; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; static cvar_t rcon_restricted_commands = {0, "rcon_restricted_commands", "", "allowed commands for rcon when the restricted mode password was used"}; static cvar_t rcon_secure_maxdiff = {0, "rcon_secure_maxdiff", "5", "maximum time difference between rcon request and server system clock (to protect against replay attack)"}; extern cvar_t rcon_secure; extern cvar_t rcon_secure_challengetimeout; double masterquerytime = -1000; int masterquerycount = 0; int masterreplycount = 0; int serverquerycount = 0; int serverreplycount = 0; challenge_t challenges[MAX_CHALLENGES]; #ifdef CONFIG_MENU /// this is only false if there are still servers left to query static qboolean serverlist_querysleep = true; static qboolean serverlist_paused = false; /// this is pushed a second or two ahead of realtime whenever a master server /// reply is received, to avoid issuing queries while master replies are still /// flooding in (which would make a mess of the ping times) static double serverlist_querywaittime = 0; #endif static int cl_numsockets; static lhnetsocket_t *cl_sockets[16]; static int sv_numsockets; static lhnetsocket_t *sv_sockets[16]; netconn_t *netconn_list = NULL; mempool_t *netconn_mempool = NULL; void *netconn_mutex = NULL; cvar_t cl_netport = {0, "cl_port", "0", "forces client to use chosen port number if not 0"}; cvar_t sv_netport = {0, "port", "26000", "server port for players to connect to"}; cvar_t net_address = {0, "net_address", "", "network address to open ipv4 ports on (if empty, use default interfaces)"}; cvar_t net_address_ipv6 = {0, "net_address_ipv6", "", "network address to open ipv6 ports on (if empty, use default interfaces)"}; char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; int cl_net_extresponse_count = 0; int cl_net_extresponse_last = 0; char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; int sv_net_extresponse_count = 0; int sv_net_extresponse_last = 0; #ifdef CONFIG_MENU // ServerList interface serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; serverlist_infofield_t serverlist_sortbyfield; int serverlist_sortflags; int serverlist_viewcount = 0; unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; int serverlist_maxcachecount = 0; int serverlist_cachecount = 0; serverlist_entry_t *serverlist_cache = NULL; qboolean serverlist_consoleoutput; static int nFavorites = 0; static lhnetaddress_t favorites[MAX_FAVORITESERVERS]; static int nFavorites_idfp = 0; static char favorites_idfp[MAX_FAVORITESERVERS][FP64_SIZE+1]; void NetConn_UpdateFavorites(void) { const char *p; nFavorites = 0; nFavorites_idfp = 0; p = net_slist_favorites.string; while((size_t) nFavorites < sizeof(favorites) / sizeof(*favorites) && COM_ParseToken_Console(&p)) { if(com_token[0] != '[' && strlen(com_token) == FP64_SIZE && !strchr(com_token, '.')) // currently 44 bytes, longest possible IPv6 address: 39 bytes, so this works // (if v6 address contains port, it must start with '[') { strlcpy(favorites_idfp[nFavorites_idfp], com_token, sizeof(favorites_idfp[nFavorites_idfp])); ++nFavorites_idfp; } else { if(LHNETADDRESS_FromString(&favorites[nFavorites], com_token, 26000)) ++nFavorites; } } } /// helper function to insert a value into the viewset /// spare entries will be removed static void _ServerList_ViewList_Helper_InsertBefore( int index, serverlist_entry_t *entry ) { int i; if( serverlist_viewcount < SERVERLIST_VIEWLISTSIZE ) { i = serverlist_viewcount++; } else { i = SERVERLIST_VIEWLISTSIZE - 1; } for( ; i > index ; i-- ) serverlist_viewlist[ i ] = serverlist_viewlist[ i - 1 ]; serverlist_viewlist[index] = (int)(entry - serverlist_cache); } /// we suppose serverlist_viewcount to be valid, ie > 0 static void _ServerList_ViewList_Helper_Remove( int index ) { serverlist_viewcount--; for( ; index < serverlist_viewcount ; index++ ) serverlist_viewlist[index] = serverlist_viewlist[index + 1]; } /// \returns true if A should be inserted before B static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_entry_t *B ) { int result = 0; // > 0 if for numbers A > B and for text if A < B if( serverlist_sortflags & SLSF_CATEGORIES ) { result = A->info.category - B->info.category; if (result != 0) return result < 0; } if( serverlist_sortflags & SLSF_FAVORITES ) { if(A->info.isfavorite != B->info.isfavorite) return A->info.isfavorite; } switch( serverlist_sortbyfield ) { case SLIF_PING: result = A->info.ping - B->info.ping; break; case SLIF_MAXPLAYERS: result = A->info.maxplayers - B->info.maxplayers; break; case SLIF_NUMPLAYERS: result = A->info.numplayers - B->info.numplayers; break; case SLIF_NUMBOTS: result = A->info.numbots - B->info.numbots; break; case SLIF_NUMHUMANS: result = A->info.numhumans - B->info.numhumans; break; case SLIF_FREESLOTS: result = A->info.freeslots - B->info.freeslots; break; case SLIF_PROTOCOL: result = A->info.protocol - B->info.protocol; break; case SLIF_CNAME: result = strcmp( B->info.cname, A->info.cname ); break; case SLIF_GAME: result = strcasecmp( B->info.game, A->info.game ); break; case SLIF_MAP: result = strcasecmp( B->info.map, A->info.map ); break; case SLIF_MOD: result = strcasecmp( B->info.mod, A->info.mod ); break; case SLIF_NAME: result = strcasecmp( B->info.name, A->info.name ); break; case SLIF_QCSTATUS: result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though break; case SLIF_CATEGORY: result = A->info.category - B->info.category; break; case SLIF_ISFAVORITE: result = !!B->info.isfavorite - !!A->info.isfavorite; break; default: Con_DPrint( "_ServerList_Entry_Compare: Bad serverlist_sortbyfield!\n" ); break; } if (result != 0) { if( serverlist_sortflags & SLSF_DESCENDING ) return result > 0; else return result < 0; } // if the chosen sort key is identical, sort by index // (makes this a stable sort, so that later replies from servers won't // shuffle the servers around when they have the same ping) return A < B; } static qboolean _ServerList_CompareInt( int A, serverlist_maskop_t op, int B ) { // This should actually be done with some intermediate and end-of-function return switch( op ) { case SLMO_LESS: return A < B; case SLMO_LESSEQUAL: return A <= B; case SLMO_EQUAL: return A == B; case SLMO_GREATER: return A > B; case SLMO_NOTEQUAL: return A != B; case SLMO_GREATEREQUAL: case SLMO_CONTAINS: case SLMO_NOTCONTAIN: case SLMO_STARTSWITH: case SLMO_NOTSTARTSWITH: return A >= B; default: Con_DPrint( "_ServerList_CompareInt: Bad op!\n" ); return false; } } static qboolean _ServerList_CompareStr( const char *A, serverlist_maskop_t op, const char *B ) { int i; char bufferA[ 1400 ], bufferB[ 1400 ]; // should be more than enough COM_StringDecolorize(A, 0, bufferA, sizeof(bufferA), false); for (i = 0;i < (int)sizeof(bufferA)-1 && bufferA[i];i++) bufferA[i] = (bufferA[i] >= 'A' && bufferA[i] <= 'Z') ? (bufferA[i] + 'a' - 'A') : bufferA[i]; bufferA[i] = 0; for (i = 0;i < (int)sizeof(bufferB)-1 && B[i];i++) bufferB[i] = (B[i] >= 'A' && B[i] <= 'Z') ? (B[i] + 'a' - 'A') : B[i]; bufferB[i] = 0; // Same here, also using an intermediate & final return would be more appropriate // A info B mask switch( op ) { case SLMO_CONTAINS: return *bufferB && !!strstr( bufferA, bufferB ); // we want a real bool case SLMO_NOTCONTAIN: return !*bufferB || !strstr( bufferA, bufferB ); case SLMO_STARTSWITH: //Con_Printf("startsWith: %s %s\n", bufferA, bufferB); return *bufferB && !memcmp(bufferA, bufferB, strlen(bufferB)); case SLMO_NOTSTARTSWITH: return !*bufferB || memcmp(bufferA, bufferB, strlen(bufferB)); case SLMO_LESS: return strcmp( bufferA, bufferB ) < 0; case SLMO_LESSEQUAL: return strcmp( bufferA, bufferB ) <= 0; case SLMO_EQUAL: return strcmp( bufferA, bufferB ) == 0; case SLMO_GREATER: return strcmp( bufferA, bufferB ) > 0; case SLMO_NOTEQUAL: return strcmp( bufferA, bufferB ) != 0; case SLMO_GREATEREQUAL: return strcmp( bufferA, bufferB ) >= 0; default: Con_DPrint( "_ServerList_CompareStr: Bad op!\n" ); return false; } } static qboolean _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info_t *info ) { if( !_ServerList_CompareInt( info->ping, mask->tests[SLIF_PING], mask->info.ping ) ) return false; if( !_ServerList_CompareInt( info->maxplayers, mask->tests[SLIF_MAXPLAYERS], mask->info.maxplayers ) ) return false; if( !_ServerList_CompareInt( info->numplayers, mask->tests[SLIF_NUMPLAYERS], mask->info.numplayers ) ) return false; if( !_ServerList_CompareInt( info->numbots, mask->tests[SLIF_NUMBOTS], mask->info.numbots ) ) return false; if( !_ServerList_CompareInt( info->numhumans, mask->tests[SLIF_NUMHUMANS], mask->info.numhumans ) ) return false; if( !_ServerList_CompareInt( info->freeslots, mask->tests[SLIF_FREESLOTS], mask->info.freeslots ) ) return false; if( !_ServerList_CompareInt( info->protocol, mask->tests[SLIF_PROTOCOL], mask->info.protocol )) return false; if( *mask->info.cname && !_ServerList_CompareStr( info->cname, mask->tests[SLIF_CNAME], mask->info.cname ) ) return false; if( *mask->info.game && !_ServerList_CompareStr( info->game, mask->tests[SLIF_GAME], mask->info.game ) ) return false; if( *mask->info.mod && !_ServerList_CompareStr( info->mod, mask->tests[SLIF_MOD], mask->info.mod ) ) return false; if( *mask->info.map && !_ServerList_CompareStr( info->map, mask->tests[SLIF_MAP], mask->info.map ) ) return false; if( *mask->info.name && !_ServerList_CompareStr( info->name, mask->tests[SLIF_NAME], mask->info.name ) ) return false; if( *mask->info.qcstatus && !_ServerList_CompareStr( info->qcstatus, mask->tests[SLIF_QCSTATUS], mask->info.qcstatus ) ) return false; if( *mask->info.players && !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) ) return false; if( !_ServerList_CompareInt( info->category, mask->tests[SLIF_CATEGORY], mask->info.category ) ) return false; if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite )) return false; return true; } static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) { int start, end, mid, i; lhnetaddress_t addr; // reject incompatible servers if( entry->info.gameversion != gameversion.integer && !( gameversion_min.integer >= 0 // min/max range set by user/mod? && gameversion_max.integer >= 0 && gameversion_min.integer <= entry->info.gameversion // version of server in min/max range? && gameversion_max.integer >= entry->info.gameversion ) ) return; // refresh the "favorite" status entry->info.isfavorite = false; if(LHNETADDRESS_FromString(&addr, entry->info.cname, 26000)) { char idfp[FP64_SIZE+1]; for(i = 0; i < nFavorites; ++i) { if(LHNETADDRESS_Compare(&addr, &favorites[i]) == 0) { entry->info.isfavorite = true; break; } } if(Crypto_RetrieveHostKey(&addr, 0, NULL, 0, idfp, sizeof(idfp), NULL, NULL)) { for(i = 0; i < nFavorites_idfp; ++i) { if(!strcmp(idfp, favorites_idfp[i])) { entry->info.isfavorite = true; break; } } } } // refresh the "category" entry->info.category = MR_GetServerListEntryCategory(entry); // FIXME: change this to be more readable (...) // now check whether it passes through the masks for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ ) if( !_ServerList_Entry_Mask( &serverlist_andmasks[start], &entry->info ) ) return; for( start = 0 ; start < SERVERLIST_ORMASKCOUNT && serverlist_ormasks[start].active ; start++ ) if( _ServerList_Entry_Mask( &serverlist_ormasks[start], &entry->info ) ) break; if( start == SERVERLIST_ORMASKCOUNT || (start > 0 && !serverlist_ormasks[start].active) ) return; if( !serverlist_viewcount ) { _ServerList_ViewList_Helper_InsertBefore( 0, entry ); return; } // ok, insert it, we just need to find out where exactly: // two special cases // check whether to insert it as new first item if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(0) ) ) { _ServerList_ViewList_Helper_InsertBefore( 0, entry ); return; } // check whether to insert it as new last item else if( !_ServerList_Entry_Compare( entry, ServerList_GetViewEntry(serverlist_viewcount - 1) ) ) { _ServerList_ViewList_Helper_InsertBefore( serverlist_viewcount, entry ); return; } start = 0; end = serverlist_viewcount - 1; while( end > start + 1 ) { mid = (start + end) / 2; // test the item that lies in the middle between start and end if( _ServerList_Entry_Compare( entry, ServerList_GetViewEntry(mid) ) ) // the item has to be in the upper half end = mid; else // the item has to be in the lower half start = mid; } _ServerList_ViewList_Helper_InsertBefore( start + 1, entry ); } static void ServerList_ViewList_Remove( serverlist_entry_t *entry ) { int i; for( i = 0; i < serverlist_viewcount; i++ ) { if (ServerList_GetViewEntry(i) == entry) { _ServerList_ViewList_Helper_Remove(i); break; } } } void ServerList_RebuildViewList(void) { int i; serverlist_viewcount = 0; for( i = 0 ; i < serverlist_cachecount ; i++ ) { serverlist_entry_t *entry = &serverlist_cache[i]; // also display entries that are currently being refreshed [11/8/2007 Black] if( entry->query == SQS_QUERIED || entry->query == SQS_REFRESHING ) ServerList_ViewList_Insert( entry ); } } void ServerList_ResetMasks(void) { int i; memset( &serverlist_andmasks, 0, sizeof( serverlist_andmasks ) ); memset( &serverlist_ormasks, 0, sizeof( serverlist_ormasks ) ); // numbots needs to be compared to -1 to always succeed for(i = 0; i < SERVERLIST_ANDMASKCOUNT; ++i) serverlist_andmasks[i].info.numbots = -1; for(i = 0; i < SERVERLIST_ORMASKCOUNT; ++i) serverlist_ormasks[i].info.numbots = -1; } void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer) { int i; int numplayers = 0, maxplayers = 0; for (i = 0;i < serverlist_cachecount;i++) { if (serverlist_cache[i].query == SQS_QUERIED) { numplayers += serverlist_cache[i].info.numhumans; maxplayers += serverlist_cache[i].info.maxplayers; } } *numplayerspointer = numplayers; *maxplayerspointer = maxplayers; } #if 0 static void _ServerList_Test(void) { int i; if (serverlist_maxcachecount <= 1024) { serverlist_maxcachecount = 1024; serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); } for( i = 0 ; i < 1024 ; i++ ) { memset( &serverlist_cache[serverlist_cachecount], 0, sizeof( serverlist_entry_t ) ); serverlist_cache[serverlist_cachecount].info.ping = 1000 + 1024 - i; dpsnprintf( serverlist_cache[serverlist_cachecount].info.name, sizeof(serverlist_cache[serverlist_cachecount].info.name), "Black's ServerList Test %i", i ); serverlist_cache[serverlist_cachecount].finished = true; dpsnprintf( serverlist_cache[serverlist_cachecount].line1, sizeof(serverlist_cache[serverlist_cachecount].info.line1), "%i %s", serverlist_cache[serverlist_cachecount].info.ping, serverlist_cache[serverlist_cachecount].info.name ); ServerList_ViewList_Insert( &serverlist_cache[serverlist_cachecount] ); serverlist_cachecount++; } } #endif void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput) { masterquerytime = realtime; masterquerycount = 0; masterreplycount = 0; if( resetcache ) { serverquerycount = 0; serverreplycount = 0; serverlist_cachecount = 0; serverlist_viewcount = 0; serverlist_maxcachecount = 0; serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); } else { // refresh all entries int n; for( n = 0 ; n < serverlist_cachecount ; n++ ) { serverlist_entry_t *entry = &serverlist_cache[ n ]; entry->query = SQS_REFRESHING; entry->querycounter = 0; } } serverlist_consoleoutput = consoleoutput; //_ServerList_Test(); NetConn_QueryMasters(querydp, queryqw); } #endif // rest int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress) { int length; int i; if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) Thread_LockMutex(netconn_mutex); length = LHNET_Read(mysocket, data, maxlength, peeraddress); if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) Thread_UnlockMutex(netconn_mutex); if (length == 0) return 0; if (cl_netpacketloss_receive.integer) for (i = 0;i < cl_numsockets;i++) if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_receive.integer) return 0; if (developer_networking.integer) { char addressstring[128], addressstring2[128]; LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); if (length > 0) { LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i from %s:\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length, addressstring2); Com_HexDumpToConsole((unsigned char *)data, length); } else Con_Printf("LHNET_Read(%p (%s), %p, %i, %p) = %i\n", (void *)mysocket, addressstring, (void *)data, maxlength, (void *)peeraddress, length); } return length; } int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress) { int ret; int i; if (cl_netpacketloss_send.integer) for (i = 0;i < cl_numsockets;i++) if (cl_sockets[i] == mysocket && (rand() % 100) < cl_netpacketloss_send.integer) return length; if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) Thread_LockMutex(netconn_mutex); ret = LHNET_Write(mysocket, data, length, peeraddress); if (mysocket->address.addresstype == LHNETADDRESSTYPE_LOOP && netconn_mutex) Thread_UnlockMutex(netconn_mutex); if (developer_networking.integer) { char addressstring[128], addressstring2[128]; LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), addressstring, sizeof(addressstring), true); LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); Con_Printf("LHNET_Write(%p (%s), %p, %i, %p (%s)) = %i%s\n", (void *)mysocket, addressstring, (void *)data, length, (void *)peeraddress, addressstring2, length, ret == length ? "" : " (ERROR)"); Com_HexDumpToConsole((unsigned char *)data, length); } return ret; } int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress) { // note this does not include the trailing NULL because we add that in the parser return NetConn_Write(mysocket, string, (int)strlen(string), peeraddress); } qboolean NetConn_CanSend(netconn_t *conn) { conn->outgoing_packetcounter = (conn->outgoing_packetcounter + 1) % NETGRAPH_PACKETS; conn->outgoing_netgraph[conn->outgoing_packetcounter].time = realtime; conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes = NETGRAPH_NOPACKET; conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes = NETGRAPH_NOPACKET; conn->outgoing_netgraph[conn->outgoing_packetcounter].cleartime = conn->cleartime; if (realtime > conn->cleartime) return true; else { conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_CHOKEDPACKET; return false; } } static void NetConn_UpdateCleartime(double *cleartime, int rate, int burstsize, int len) { double bursttime = burstsize / (double)rate; // delay later packets to obey rate limit if (*cleartime < realtime - bursttime) *cleartime = realtime - bursttime; *cleartime = *cleartime + len / (double)rate; // limit bursts to one packet in size ("dialup mode" emulating old behaviour) if (net_test.integer) { if (*cleartime < realtime) *cleartime = realtime; } } static int NetConn_AddCryptoFlag(crypto_t *crypto) { // HACK: if an encrypted connection is used, randomly set some unused // flags. When AES encryption is enabled, that will make resends differ // from the original, so that e.g. substring filters in a router/IPS // are unlikely to match a second time. See also "startkeylogger". int flag = 0; if (crypto->authenticated) { // Let's always set at least one of the bits. int r = rand() % 7 + 1; if (r & 1) flag |= NETFLAG_CRYPTO0; if (r & 2) flag |= NETFLAG_CRYPTO1; if (r & 4) flag |= NETFLAG_CRYPTO2; } return flag; } int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, int burstsize, qboolean quakesignon_suppressreliables) { int totallen = 0; unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; // if this packet was supposedly choked, but we find ourselves sending one // anyway, make sure the size counting starts at zero // (this mostly happens on level changes and disconnects and such) if (conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes == NETGRAPH_CHOKEDPACKET) conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes = NETGRAPH_NOPACKET; conn->outgoing_netgraph[conn->outgoing_packetcounter].cleartime = conn->cleartime; if (protocol == PROTOCOL_QUAKEWORLD) { int packetLen; qboolean sendreliable; // note that it is ok to send empty messages to the qw server, // otherwise it won't respond to us at all sendreliable = false; // if the remote side dropped the last reliable message, resend it if (conn->qw.incoming_acknowledged > conn->qw.last_reliable_sequence && conn->qw.incoming_reliable_acknowledged != conn->qw.reliable_sequence) sendreliable = true; // if the reliable transmit buffer is empty, copy the current message out if (!conn->sendMessageLength && conn->message.cursize) { memcpy (conn->sendMessage, conn->message.data, conn->message.cursize); conn->sendMessageLength = conn->message.cursize; SZ_Clear(&conn->message); // clear the message buffer conn->qw.reliable_sequence ^= 1; sendreliable = true; } // outgoing unreliable packet number, and outgoing reliable packet number (0 or 1) StoreLittleLong(sendbuffer, conn->outgoing_unreliable_sequence | (((unsigned int)sendreliable)<<31)); // last received unreliable packet number, and last received reliable packet number (0 or 1) StoreLittleLong(sendbuffer + 4, conn->qw.incoming_sequence | (((unsigned int)conn->qw.incoming_reliable_sequence)<<31)); packetLen = 8; conn->outgoing_unreliable_sequence++; // client sends qport in every packet if (conn == cls.netcon) { *((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport); packetLen += 2; // also update cls.qw_outgoing_sequence cls.qw_outgoing_sequence = conn->outgoing_unreliable_sequence; } if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400) { Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize); return -1; } conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; // add the reliable message if there is one if (sendreliable) { conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += conn->sendMessageLength + 28; memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength); packetLen += conn->sendMessageLength; conn->qw.last_reliable_sequence = conn->outgoing_unreliable_sequence; } // add the unreliable message if possible if (packetLen + data->cursize <= 1400) { conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += data->cursize + 28; memcpy(sendbuffer + packetLen, data->data, data->cursize); packetLen += data->cursize; } NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress); conn->packetsSent++; conn->unreliableMessagesSent++; totallen += packetLen + 28; } else { unsigned int packetLen; unsigned int dataLen; unsigned int eom; const void *sendme; size_t sendmelen; // if a reliable message fragment has been lost, send it again if (conn->sendMessageLength && (realtime - conn->lastSendTime) > 1.0) { if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) { dataLen = conn->sendMessageLength; eom = NETFLAG_EOM; } else { dataLen = MAX_PACKETFRAGMENT; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto))); StoreBigLong(sendbuffer + 4, conn->nq.sendSequence - 1); memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); if (sendme && NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress) == (int)sendmelen) { conn->lastSendTime = realtime; conn->packetsReSent++; } totallen += (int)sendmelen + 28; } // if we have a new reliable message to send, do so if (!conn->sendMessageLength && conn->message.cursize && !quakesignon_suppressreliables) { if (conn->message.cursize > (int)sizeof(conn->sendMessage)) { Con_Printf("NetConn_SendUnreliableMessage: reliable message too big (%u > %u)\n", conn->message.cursize, (int)sizeof(conn->sendMessage)); conn->message.overflowed = true; return -1; } if (developer_networking.integer && conn == cls.netcon) { Con_Print("client sending reliable message to server:\n"); SZ_HexDumpToConsole(&conn->message); } memcpy(conn->sendMessage, conn->message.data, conn->message.cursize); conn->sendMessageLength = conn->message.cursize; SZ_Clear(&conn->message); if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) { dataLen = conn->sendMessageLength; eom = NETFLAG_EOM; } else { dataLen = MAX_PACKETFRAGMENT; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto))); StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); conn->nq.sendSequence++; conn->outgoing_netgraph[conn->outgoing_packetcounter].reliablebytes += packetLen + 28; sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); if(sendme) NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress); conn->lastSendTime = realtime; conn->packetsSent++; conn->reliableMessagesSent++; totallen += (int)sendmelen + 28; } // if we have an unreliable message to send, do so if (data->cursize) { packetLen = NET_HEADERSIZE + data->cursize; if (packetLen > (int)sizeof(sendbuffer)) { Con_Printf("NetConn_SendUnreliableMessage: message too big %u\n", data->cursize); return -1; } StoreBigLong(sendbuffer, packetLen | NETFLAG_UNRELIABLE | NetConn_AddCryptoFlag(&conn->crypto)); StoreBigLong(sendbuffer + 4, conn->outgoing_unreliable_sequence); memcpy(sendbuffer + NET_HEADERSIZE, data->data, data->cursize); conn->outgoing_unreliable_sequence++; conn->outgoing_netgraph[conn->outgoing_packetcounter].unreliablebytes += packetLen + 28; sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); if(sendme) NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress); conn->packetsSent++; conn->unreliableMessagesSent++; totallen += (int)sendmelen + 28; } } NetConn_UpdateCleartime(&conn->cleartime, rate, burstsize, totallen); return 0; } qboolean NetConn_HaveClientPorts(void) { return !!cl_numsockets; } qboolean NetConn_HaveServerPorts(void) { return !!sv_numsockets; } void NetConn_CloseClientPorts(void) { for (;cl_numsockets > 0;cl_numsockets--) if (cl_sockets[cl_numsockets - 1]) LHNET_CloseSocket(cl_sockets[cl_numsockets - 1]); } static void NetConn_OpenClientPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport) { lhnetaddress_t address; lhnetsocket_t *s; int success; char addressstring2[1024]; if (addressstring && addressstring[0]) success = LHNETADDRESS_FromString(&address, addressstring, defaultport); else success = LHNETADDRESS_FromPort(&address, addresstype, defaultport); if (success) { if ((s = LHNET_OpenSocket_Connectionless(&address))) { cl_sockets[cl_numsockets++] = s; LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); if (addresstype != LHNETADDRESSTYPE_LOOP) Con_Printf("Client opened a socket on address %s\n", addressstring2); } else { LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); Con_Printf("Client failed to open a socket on address %s\n", addressstring2); } } else Con_Printf("Client unable to parse address %s\n", addressstring); } void NetConn_OpenClientPorts(void) { int port; NetConn_CloseClientPorts(); SV_LockThreadMutex(); // FIXME recursive? Crypto_LoadKeys(); // client sockets SV_UnlockThreadMutex(); port = bound(0, cl_netport.integer, 65535); if (cl_netport.integer != port) Cvar_SetValueQuick(&cl_netport, port); if(port == 0) Con_Printf("Client using an automatically assigned port\n"); else Con_Printf("Client using port %i\n", port); NetConn_OpenClientPort(NULL, LHNETADDRESSTYPE_LOOP, 2); NetConn_OpenClientPort(net_address.string, LHNETADDRESSTYPE_INET4, port); #ifndef NOSUPPORTIPV6 NetConn_OpenClientPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port); #endif } void NetConn_CloseServerPorts(void) { for (;sv_numsockets > 0;sv_numsockets--) if (sv_sockets[sv_numsockets - 1]) LHNET_CloseSocket(sv_sockets[sv_numsockets - 1]); } static qboolean NetConn_OpenServerPort(const char *addressstring, lhnetaddresstype_t addresstype, int defaultport, int range) { lhnetaddress_t address; lhnetsocket_t *s; int port; char addressstring2[1024]; int success; for (port = defaultport; port <= defaultport + range; port++) { if (addressstring && addressstring[0]) success = LHNETADDRESS_FromString(&address, addressstring, port); else success = LHNETADDRESS_FromPort(&address, addresstype, port); if (success) { if ((s = LHNET_OpenSocket_Connectionless(&address))) { sv_sockets[sv_numsockets++] = s; LHNETADDRESS_ToString(LHNET_AddressFromSocket(s), addressstring2, sizeof(addressstring2), true); if (addresstype != LHNETADDRESSTYPE_LOOP) Con_Printf("Server listening on address %s\n", addressstring2); return true; } else { LHNETADDRESS_ToString(&address, addressstring2, sizeof(addressstring2), true); Con_Printf("Server failed to open socket on address %s\n", addressstring2); } } else { Con_Printf("Server unable to parse address %s\n", addressstring); // if it cant parse one address, it wont be able to parse another for sure return false; } } return false; } void NetConn_OpenServerPorts(int opennetports) { int port; NetConn_CloseServerPorts(); SV_LockThreadMutex(); // FIXME recursive? Crypto_LoadKeys(); // server sockets SV_UnlockThreadMutex(); NetConn_UpdateSockets(); port = bound(0, sv_netport.integer, 65535); if (port == 0) port = 26000; Con_Printf("Server using port %i\n", port); if (sv_netport.integer != port) Cvar_SetValueQuick(&sv_netport, port); if (cls.state != ca_dedicated) NetConn_OpenServerPort(NULL, LHNETADDRESSTYPE_LOOP, 1, 1); if (opennetports) { #ifndef NOSUPPORTIPV6 qboolean ip4success = NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); NetConn_OpenServerPort(net_address_ipv6.string, LHNETADDRESSTYPE_INET6, port, ip4success ? 1 : 100); #else NetConn_OpenServerPort(net_address.string, LHNETADDRESSTYPE_INET4, port, 100); #endif } if (sv_numsockets == 0) Host_Error("NetConn_OpenServerPorts: unable to open any ports!"); } lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address) { int i, a = LHNETADDRESS_GetAddressType(address); for (i = 0;i < cl_numsockets;i++) if (cl_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == a) return cl_sockets[i]; return NULL; } lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address) { int i, a = LHNETADDRESS_GetAddressType(address); for (i = 0;i < sv_numsockets;i++) if (sv_sockets[i] && LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(sv_sockets[i])) == a) return sv_sockets[i]; return NULL; } netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress) { netconn_t *conn; conn = (netconn_t *)Mem_Alloc(netconn_mempool, sizeof(*conn)); conn->mysocket = mysocket; conn->peeraddress = *peeraddress; conn->lastMessageTime = realtime; conn->message.data = conn->messagedata; conn->message.maxsize = sizeof(conn->messagedata); conn->message.cursize = 0; // LordHavoc: (inspired by ProQuake) use a short connect timeout to // reduce effectiveness of connection request floods conn->timeout = realtime + net_connecttimeout.value; LHNETADDRESS_ToString(&conn->peeraddress, conn->address, sizeof(conn->address), true); conn->next = netconn_list; netconn_list = conn; return conn; } void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength); void NetConn_Close(netconn_t *conn) { netconn_t *c; // remove connection from list // allow the client to reconnect immediately NetConn_ClearFlood(&(conn->peeraddress), sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0])); if (conn == netconn_list) netconn_list = conn->next; else { for (c = netconn_list;c;c = c->next) { if (c->next == conn) { c->next = conn->next; break; } } // not found in list, we'll avoid crashing here... if (!c) return; } // free connection Mem_Free(conn); } static int clientport = -1; static int clientport2 = -1; static int hostport = -1; void NetConn_UpdateSockets(void) { int i, j; // TODO add logic to automatically close sockets if needed LHNET_DefaultDSCP(net_tos_dscp.integer); if (cls.state != ca_dedicated) { if (clientport2 != cl_netport.integer) { clientport2 = cl_netport.integer; if (cls.state == ca_connected) Con_Print("Changing \"cl_port\" will not take effect until you reconnect.\n"); } if (cls.state == ca_disconnected && clientport != clientport2) { clientport = clientport2; NetConn_CloseClientPorts(); } if (cl_numsockets == 0) NetConn_OpenClientPorts(); } if (hostport != sv_netport.integer) { hostport = sv_netport.integer; if (sv.active) Con_Print("Changing \"port\" will not take effect until \"map\" command is executed.\n"); } for (j = 0;j < MAX_RCONS;j++) { i = (cls.rcon_ringpos + j + 1) % MAX_RCONS; if(cls.rcon_commands[i][0]) { if(realtime > cls.rcon_timeout[i]) { char s[128]; LHNETADDRESS_ToString(&cls.rcon_addresses[i], s, sizeof(s), true); Con_Printf("rcon to %s (for command %s) failed: challenge request timed out\n", s, cls.rcon_commands[i]); cls.rcon_commands[i][0] = 0; --cls.rcon_trying; break; } } } } static int NetConn_ReceivedMessage(netconn_t *conn, const unsigned char *data, size_t length, protocolversion_t protocol, double newtimeout) { int originallength = (int)length; unsigned char sendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; unsigned char cryptosendbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; unsigned char cryptoreadbuffer[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; if (length < 8) return 0; if (protocol == PROTOCOL_QUAKEWORLD) { unsigned int sequence, sequence_ack; qboolean reliable_ack, reliable_message; int count; //int qport; sequence = LittleLong(*((int *)(data + 0))); sequence_ack = LittleLong(*((int *)(data + 4))); data += 8; length -= 8; if (conn != cls.netcon) { // server only if (length < 2) return 0; // TODO: use qport to identify that this client really is who they say they are? (and elsewhere in the code to identify the connection without a port match?) //qport = LittleShort(*((int *)(data + 8))); data += 2; length -= 2; } conn->packetsReceived++; reliable_message = (sequence >> 31) != 0; reliable_ack = (sequence_ack >> 31) != 0; sequence &= ~(1<<31); sequence_ack &= ~(1<<31); if (sequence <= conn->qw.incoming_sequence) { //Con_DPrint("Got a stale datagram\n"); return 0; } count = sequence - (conn->qw.incoming_sequence + 1); if (count > 0) { conn->droppedDatagrams += count; //Con_DPrintf("Dropped %u datagram(s)\n", count); // If too may packets have been dropped, only write the // last NETGRAPH_PACKETS ones to the netgraph. Why? // Because there's no point in writing more than // these as the netgraph is going to be full anyway. if (count > NETGRAPH_PACKETS) count = NETGRAPH_PACKETS; while (count--) { conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime; conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; } } conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime; conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28); // limit bursts to one packet in size ("dialup mode" emulating old behaviour) if (net_test.integer) { if (conn->cleartime < realtime) conn->cleartime = realtime; } if (reliable_ack == conn->qw.reliable_sequence) { // received, now we will be able to send another reliable message conn->sendMessageLength = 0; conn->reliableMessagesReceived++; } conn->qw.incoming_sequence = sequence; if (conn == cls.netcon) cls.qw_incoming_sequence = conn->qw.incoming_sequence; conn->qw.incoming_acknowledged = sequence_ack; conn->qw.incoming_reliable_acknowledged = reliable_ack; if (reliable_message) conn->qw.incoming_reliable_sequence ^= 1; conn->lastMessageTime = realtime; conn->timeout = realtime + newtimeout; conn->unreliableMessagesReceived++; if (conn == cls.netcon) { SZ_Clear(&cl_message); SZ_Write(&cl_message, data, (int)length); MSG_BeginReading(&cl_message); } else { SZ_Clear(&sv_message); SZ_Write(&sv_message, data, (int)length); MSG_BeginReading(&sv_message); } return 2; } else { unsigned int count; unsigned int flags; unsigned int sequence; size_t qlength; const void *sendme; size_t sendmelen; originallength = (int)length; data = (const unsigned char *) Crypto_DecryptPacket(&conn->crypto, data, length, cryptoreadbuffer, &length, sizeof(cryptoreadbuffer)); if(!data) return 0; if(length < 8) return 0; qlength = (unsigned int)BuffBigLong(data); flags = qlength & ~NETFLAG_LENGTH_MASK; qlength &= NETFLAG_LENGTH_MASK; // control packets were already handled if (!(flags & NETFLAG_CTL) && qlength == length) { sequence = BuffBigLong(data + 4); conn->packetsReceived++; data += 8; length -= 8; if (flags & NETFLAG_UNRELIABLE) { if (sequence >= conn->nq.unreliableReceiveSequence) { if (sequence > conn->nq.unreliableReceiveSequence) { count = sequence - conn->nq.unreliableReceiveSequence; conn->droppedDatagrams += count; //Con_DPrintf("Dropped %u datagram(s)\n", count); // If too may packets have been dropped, only write the // last NETGRAPH_PACKETS ones to the netgraph. Why? // Because there's no point in writing more than // these as the netgraph is going to be full anyway. if (count > NETGRAPH_PACKETS) count = NETGRAPH_PACKETS; while (count--) { conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime; conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = NETGRAPH_LOSTPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; } } conn->incoming_packetcounter = (conn->incoming_packetcounter + 1) % NETGRAPH_PACKETS; conn->incoming_netgraph[conn->incoming_packetcounter].time = realtime; conn->incoming_netgraph[conn->incoming_packetcounter].cleartime = conn->incoming_cleartime; conn->incoming_netgraph[conn->incoming_packetcounter].unreliablebytes = originallength + 28; conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes = NETGRAPH_NOPACKET; conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes = NETGRAPH_NOPACKET; NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28); conn->nq.unreliableReceiveSequence = sequence + 1; conn->lastMessageTime = realtime; conn->timeout = realtime + newtimeout; conn->unreliableMessagesReceived++; if (length > 0) { if (conn == cls.netcon) { SZ_Clear(&cl_message); SZ_Write(&cl_message, data, (int)length); MSG_BeginReading(&cl_message); } else { SZ_Clear(&sv_message); SZ_Write(&sv_message, data, (int)length); MSG_BeginReading(&sv_message); } return 2; } } //else // Con_DPrint("Got a stale datagram\n"); return 1; } else if (flags & NETFLAG_ACK) { conn->incoming_netgraph[conn->incoming_packetcounter].ackbytes += originallength + 28; NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28); if (sequence == (conn->nq.sendSequence - 1)) { if (sequence == conn->nq.ackSequence) { conn->nq.ackSequence++; if (conn->nq.ackSequence != conn->nq.sendSequence) Con_DPrint("ack sequencing error\n"); conn->lastMessageTime = realtime; conn->timeout = realtime + newtimeout; if (conn->sendMessageLength > MAX_PACKETFRAGMENT) { unsigned int packetLen; unsigned int dataLen; unsigned int eom; conn->sendMessageLength -= MAX_PACKETFRAGMENT; memmove(conn->sendMessage, conn->sendMessage+MAX_PACKETFRAGMENT, conn->sendMessageLength); if (conn->sendMessageLength <= MAX_PACKETFRAGMENT) { dataLen = conn->sendMessageLength; eom = NETFLAG_EOM; } else { dataLen = MAX_PACKETFRAGMENT; eom = 0; } packetLen = NET_HEADERSIZE + dataLen; StoreBigLong(sendbuffer, packetLen | (NETFLAG_DATA | eom | NetConn_AddCryptoFlag(&conn->crypto))); StoreBigLong(sendbuffer + 4, conn->nq.sendSequence); memcpy(sendbuffer + NET_HEADERSIZE, conn->sendMessage, dataLen); conn->nq.sendSequence++; sendme = Crypto_EncryptPacket(&conn->crypto, &sendbuffer, packetLen, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); if (sendme && NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress) == (int)sendmelen) { conn->lastSendTime = realtime; conn->packetsSent++; } } else conn->sendMessageLength = 0; } //else // Con_DPrint("Duplicate ACK received\n"); } //else // Con_DPrint("Stale ACK received\n"); return 1; } else if (flags & NETFLAG_DATA) { unsigned char temppacket[8]; conn->incoming_netgraph[conn->incoming_packetcounter].reliablebytes += originallength + 28; NetConn_UpdateCleartime(&conn->incoming_cleartime, cl_rate.integer, cl_rate_burstsize.integer, originallength + 28); conn->outgoing_netgraph[conn->outgoing_packetcounter].ackbytes += 8 + 28; StoreBigLong(temppacket, 8 | NETFLAG_ACK | NetConn_AddCryptoFlag(&conn->crypto)); StoreBigLong(temppacket + 4, sequence); sendme = Crypto_EncryptPacket(&conn->crypto, temppacket, 8, &cryptosendbuffer, &sendmelen, sizeof(cryptosendbuffer)); if(sendme) NetConn_Write(conn->mysocket, sendme, (int)sendmelen, &conn->peeraddress); if (sequence == conn->nq.receiveSequence) { conn->lastMessageTime = realtime; conn->timeout = realtime + newtimeout; conn->nq.receiveSequence++; if( conn->receiveMessageLength + length <= (int)sizeof( conn->receiveMessage ) ) { memcpy(conn->receiveMessage + conn->receiveMessageLength, data, length); conn->receiveMessageLength += (int)length; } else { Con_Printf( "Reliable message (seq: %i) too big for message buffer!\n" "Dropping the message!\n", sequence ); conn->receiveMessageLength = 0; return 1; } if (flags & NETFLAG_EOM) { conn->reliableMessagesReceived++; length = conn->receiveMessageLength; conn->receiveMessageLength = 0; if (length > 0) { if (conn == cls.netcon) { SZ_Clear(&cl_message); SZ_Write(&cl_message, conn->receiveMessage, (int)length); MSG_BeginReading(&cl_message); } else { SZ_Clear(&sv_message); SZ_Write(&sv_message, conn->receiveMessage, (int)length); MSG_BeginReading(&sv_message); } return 2; } } } else conn->receivedDuplicateCount++; return 1; } } } return 0; } static void NetConn_ConnectionEstablished(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, protocolversion_t initialprotocol) { crypto_t *crypto; cls.connect_trying = false; #ifdef CONFIG_MENU M_Update_Return_Reason(""); #endif // the connection request succeeded, stop current connection and set up a new connection CL_Disconnect(); // if we're connecting to a remote server, shut down any local server if (LHNETADDRESS_GetAddressType(peeraddress) != LHNETADDRESSTYPE_LOOP && sv.active) { SV_LockThreadMutex(); Host_ShutdownServer (); SV_UnlockThreadMutex(); } // allocate a net connection to keep track of things cls.netcon = NetConn_Open(mysocket, peeraddress); crypto = &cls.netcon->crypto; if(cls.crypto.authenticated) { Crypto_FinishInstance(crypto, &cls.crypto); Con_Printf("%s connection to %s has been established: server is %s@%s%.*s, I am %.*s@%s%.*s\n", crypto->use_aes ? "Encrypted" : "Authenticated", cls.netcon->address, crypto->server_idfp[0] ? crypto->server_idfp : "-", (crypto->server_issigned || !crypto->server_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-", crypto_keyfp_recommended_length, crypto->client_idfp[0] ? crypto->client_idfp : "-", (crypto->client_issigned || !crypto->client_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-" ); } Con_Printf("Connection accepted to %s\n", cls.netcon->address); key_dest = key_game; #ifdef CONFIG_MENU m_state = m_none; #endif cls.demonum = -1; // not in the demo loop now cls.state = ca_connected; cls.signon = 0; // need all the signon messages before playing cls.protocol = initialprotocol; // reset move sequence numbering on this new connection cls.servermovesequence = 0; if (cls.protocol == PROTOCOL_QUAKEWORLD) Cmd_ForwardStringToServer("new"); if (cls.protocol == PROTOCOL_QUAKE) { // write a keepalive (clc_nop) as it seems to greatly improve the // chances of connecting to a netquake server sizebuf_t msg; unsigned char buf[4]; memset(&msg, 0, sizeof(msg)); msg.data = buf; msg.maxsize = sizeof(buf); MSG_WriteChar(&msg, clc_nop); NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, 0, false); } } int NetConn_IsLocalGame(void) { if (cls.state == ca_connected && sv.active && cl.maxclients == 1) return true; return false; } #ifdef CONFIG_MENU static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring) { int n; int pingtime; serverlist_entry_t *entry = NULL; // search the cache for this server and update it for (n = 0;n < serverlist_cachecount;n++) { entry = &serverlist_cache[ n ]; if (!strcmp(addressstring, entry->info.cname)) break; } if (n == serverlist_cachecount) { // LAN search doesnt require an answer from the master server so we wont // know the ping nor will it be initialized already... // find a slot if (serverlist_cachecount == SERVERLIST_TOTALSIZE) return -1; if (serverlist_maxcachecount <= serverlist_cachecount) { serverlist_maxcachecount += 64; serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); } entry = &serverlist_cache[n]; memset(entry, 0, sizeof(*entry)); // store the data the engine cares about (address and ping) strlcpy(entry->info.cname, addressstring, sizeof(entry->info.cname)); entry->info.ping = 100000; entry->querytime = realtime; // if not in the slist menu we should print the server to console if (serverlist_consoleoutput) Con_Printf("querying %s\n", addressstring); ++serverlist_cachecount; } // if this is the first reply from this server, count it as having replied pingtime = (int)((realtime - entry->querytime) * 1000.0 + 0.5); pingtime = bound(0, pingtime, 9999); if (entry->query == SQS_REFRESHING) { entry->info.ping = pingtime; entry->query = SQS_QUERIED; } else { // convert to unsigned to catch the -1 // I still dont like this but its better than the old 10000 magic ping number - as in easier to type and read :( [11/8/2007 Black] entry->info.ping = min((unsigned) entry->info.ping, (unsigned) pingtime); serverreplycount++; } // other server info is updated by the caller return n; } static void NetConn_ClientParsePacket_ServerList_UpdateCache(int n) { serverlist_entry_t *entry = &serverlist_cache[n]; serverlist_info_t *info = &entry->info; // update description strings for engine menu and console output dpsnprintf(entry->line1, sizeof(serverlist_cache[n].line1), "^%c%5d^7 ^%c%3u^7/%3u %-65.65s", info->ping >= 300 ? '1' : (info->ping >= 200 ? '3' : '7'), (int)info->ping, ((info->numhumans > 0 && info->numhumans < info->maxplayers) ? (info->numhumans >= 4 ? '7' : '3') : '1'), info->numplayers, info->maxplayers, info->name); dpsnprintf(entry->line2, sizeof(serverlist_cache[n].line2), "^4%-21.21s %-19.19s ^%c%-17.17s^4 %-20.20s", info->cname, info->game, ( info->gameversion != gameversion.integer && !( gameversion_min.integer >= 0 // min/max range set by user/mod? && gameversion_max.integer >= 0 && gameversion_min.integer <= info->gameversion // version of server in min/max range? && gameversion_max.integer >= info->gameversion ) ) ? '1' : '4', info->mod, info->map); if (entry->query == SQS_QUERIED) { if(!serverlist_paused) ServerList_ViewList_Remove(entry); } // if not in the slist menu we should print the server to console (if wanted) else if( serverlist_consoleoutput ) Con_Printf("%s\n%s\n", serverlist_cache[n].line1, serverlist_cache[n].line2); // and finally, update the view set if(!serverlist_paused) ServerList_ViewList_Insert( entry ); // update the entry's state serverlist_cache[n].query = SQS_QUERIED; } // returns true, if it's sensible to continue the processing static qboolean NetConn_ClientParsePacket_ServerList_PrepareQuery( int protocol, const char *ipstring, qboolean isfavorite ) { int n; serverlist_entry_t *entry; // ignore the rest of the message if the serverlist is full if( serverlist_cachecount == SERVERLIST_TOTALSIZE ) return false; // also ignore it if we have already queried it (other master server response) for( n = 0 ; n < serverlist_cachecount ; n++ ) if( !strcmp( ipstring, serverlist_cache[ n ].info.cname ) ) break; if( n < serverlist_cachecount ) { // the entry has already been queried once or return true; } if (serverlist_maxcachecount <= n) { serverlist_maxcachecount += 64; serverlist_cache = (serverlist_entry_t *)Mem_Realloc(netconn_mempool, (void *)serverlist_cache, sizeof(serverlist_entry_t) * serverlist_maxcachecount); } entry = &serverlist_cache[n]; memset(entry, 0, sizeof(*entry)); entry->protocol = protocol; // store the data the engine cares about (address and ping) strlcpy (entry->info.cname, ipstring, sizeof(entry->info.cname)); entry->info.isfavorite = isfavorite; // no, then reset the ping right away entry->info.ping = -1; // we also want to increase the serverlist_cachecount then serverlist_cachecount++; serverquerycount++; entry->query = SQS_QUERYING; return true; } static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *senderaddress, const unsigned char *data, int length, qboolean isextended) { masterreplycount++; if (serverlist_consoleoutput) Con_Printf("received DarkPlaces %sserver list...\n", isextended ? "extended " : ""); while (length >= 7) { char ipstring [128]; // IPv4 address if (data[0] == '\\') { unsigned short port = data[5] * 256 + data[6]; if (port != 0 && (data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF || data[4] != 0xFF)) dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%hu", data[1], data[2], data[3], data[4], port); // move on to next address in packet data += 7; length -= 7; } // IPv6 address else if (data[0] == '/' && isextended && length >= 19) { unsigned short port = data[17] * 256 + data[18]; if (port != 0) { #ifdef WHY_JUST_WHY const char *ifname; char ifnamebuf[16]; /// \TODO: make some basic checks of the IP address (broadcast, ...) ifname = LHNETADDRESS_GetInterfaceName(senderaddress, ifnamebuf, sizeof(ifnamebuf)); if (ifname != NULL) { dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x%%%s]:%hu", (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], ifname, port); } else #endif { dpsnprintf (ipstring, sizeof (ipstring), "[%x:%x:%x:%x:%x:%x:%x:%x]:%hu", (data[1] << 8) | data[2], (data[3] << 8) | data[4], (data[5] << 8) | data[6], (data[7] << 8) | data[8], (data[9] << 8) | data[10], (data[11] << 8) | data[12], (data[13] << 8) | data[14], (data[15] << 8) | data[16], port); } } // move on to next address in packet data += 19; length -= 19; } else { Con_Print("Error while parsing the server list\n"); break; } if (serverlist_consoleoutput && developer_networking.integer) Con_Printf("Requesting info from DarkPlaces server %s\n", ipstring); if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, ipstring, false ) ) { break; } } // begin or resume serverlist queries serverlist_querysleep = false; serverlist_querywaittime = realtime + 3; } #endif static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) { qboolean fromserver; int ret, c; char *string, addressstring2[128]; char stringbuf[16384]; char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; size_t sendlength; #ifdef CONFIG_MENU char infostringvalue[MAX_INPUTLINE]; char ipstring[32]; const char *s; #endif // quakeworld ingame packet fromserver = cls.netcon && mysocket == cls.netcon->mysocket && !LHNETADDRESS_Compare(&cls.netcon->peeraddress, peeraddress); // convert the address to a string incase we need it LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) { // received a command string - strip off the packaging and put it // into our string buffer with NULL termination data += 4; length -= 4; length = min(length, (int)sizeof(stringbuf) - 1); memcpy(stringbuf, data, length); stringbuf[length] = 0; string = stringbuf; if (developer_networking.integer) { Con_Printf("NetConn_ClientParsePacket: %s sent us a command:\n", addressstring2); Com_HexDumpToConsole(data, length); } sendlength = sizeof(senddata) - 4; switch(Crypto_ClientParsePacket(string, length, senddata+4, &sendlength, peeraddress)) { case CRYPTO_NOMATCH: // nothing to do break; case CRYPTO_MATCH: if(sendlength) { memcpy(senddata, "\377\377\377\377", 4); NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress); } break; case CRYPTO_DISCARD: if(sendlength) { memcpy(senddata, "\377\377\377\377", 4); NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress); } return true; break; case CRYPTO_REPLACE: string = senddata+4; length = (int)sendlength; break; } if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.rcon_trying) { int i = 0, j; for (j = 0;j < MAX_RCONS;j++) { // note: this value from i is used outside the loop too... i = (cls.rcon_ringpos + j) % MAX_RCONS; if(cls.rcon_commands[i][0]) if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[i])) break; } if (j < MAX_RCONS) { char buf[1500]; char argbuf[1500]; const char *e; int n; dpsnprintf(argbuf, sizeof(argbuf), "%s %s", string + 10, cls.rcon_commands[i]); memcpy(buf, "\377\377\377\377srcon HMAC-MD4 CHALLENGE ", 29); e = strchr(rcon_password.string, ' '); n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 29), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n)) { int k; buf[45] = ' '; strlcpy(buf + 46, argbuf, sizeof(buf) - 46); NetConn_Write(mysocket, buf, 46 + (int)strlen(buf + 46), peeraddress); cls.rcon_commands[i][0] = 0; --cls.rcon_trying; for (k = 0;k < MAX_RCONS;k++) if(cls.rcon_commands[k][0]) if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[k])) break; if(k < MAX_RCONS) { int l; NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", peeraddress); // extend the timeout on other requests as we asked for a challenge for (l = 0;l < MAX_RCONS;l++) if(cls.rcon_commands[l][0]) if (!LHNETADDRESS_Compare(peeraddress, &cls.rcon_addresses[l])) cls.rcon_timeout[l] = realtime + rcon_secure_challengetimeout.value; } return true; // we used up the challenge, so we can't use this oen for connecting now anyway } } } if (length >= 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) { // darkplaces or quake3 char protocolnames[1400]; Con_DPrintf("\"%s\" received, sending connect request back to %s\n", string, addressstring2); if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("challenge message from wrong server %s\n", addressstring2); return true; } Protocol_Names(protocolnames, sizeof(protocolnames)); #ifdef CONFIG_MENU M_Update_Return_Reason("Got challenge response"); #endif // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); // TODO: add userinfo stuff here instead of using NQ commands? memcpy(senddata, "\377\377\377\377", 4); dpsnprintf(senddata+4, sizeof(senddata)-4, "connect\\protocol\\darkplaces 3\\protocols\\%s%s\\challenge\\%s", protocolnames, cls.connect_userinfo, string + 10); NetConn_WriteString(mysocket, senddata, peeraddress); return true; } if (length == 6 && !memcmp(string, "accept", 6) && cls.connect_trying) { // darkplaces or quake3 if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("accept message from wrong server %s\n", addressstring2); return true; } #ifdef CONFIG_MENU M_Update_Return_Reason("Accepted"); #endif NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_DARKPLACES3); return true; } if (length > 7 && !memcmp(string, "reject ", 7) && cls.connect_trying) { char rejectreason[128]; if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("reject message from wrong server %s\n", addressstring2); return true; } cls.connect_trying = false; string += 7; length = min(length - 7, (int)sizeof(rejectreason) - 1); memcpy(rejectreason, string, length); rejectreason[length] = 0; #ifdef CONFIG_MENU M_Update_Return_Reason(rejectreason); #endif return true; } #ifdef CONFIG_MENU if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15)) { serverlist_info_t *info; char *p; int n; string += 15; // search the cache for this server and update it n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); if (n < 0) return true; info = &serverlist_cache[n].info; info->game[0] = 0; info->mod[0] = 0; info->map[0] = 0; info->name[0] = 0; info->qcstatus[0] = 0; info->players[0] = 0; info->protocol = -1; info->numplayers = 0; info->numbots = -1; info->maxplayers = 0; info->gameversion = 0; p = strchr(string, '\n'); if(p) { *p = 0; // cut off the string there ++p; } else Con_Printf("statusResponse without players block?\n"); if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game)); if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map )); if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name)); if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s); if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s); if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s); if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s); if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s); if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); if (p != NULL) strlcpy(info->players, p, sizeof(info->players)); info->numhumans = info->numplayers - max(0, info->numbots); info->freeslots = info->maxplayers - info->numplayers; NetConn_ClientParsePacket_ServerList_UpdateCache(n); return true; } if (length >= 13 && !memcmp(string, "infoResponse\x0A", 13)) { serverlist_info_t *info; int n; string += 13; // search the cache for this server and update it n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); if (n < 0) return true; info = &serverlist_cache[n].info; info->game[0] = 0; info->mod[0] = 0; info->map[0] = 0; info->name[0] = 0; info->qcstatus[0] = 0; info->players[0] = 0; info->protocol = -1; info->numplayers = 0; info->numbots = -1; info->maxplayers = 0; info->gameversion = 0; if ((s = InfoString_GetValue(string, "gamename" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->game, s, sizeof (info->game)); if ((s = InfoString_GetValue(string, "modname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod )); if ((s = InfoString_GetValue(string, "mapname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map )); if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name)); if ((s = InfoString_GetValue(string, "protocol" , infostringvalue, sizeof(infostringvalue))) != NULL) info->protocol = atoi(s); if ((s = InfoString_GetValue(string, "clients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numplayers = atoi(s); if ((s = InfoString_GetValue(string, "bots" , infostringvalue, sizeof(infostringvalue))) != NULL) info->numbots = atoi(s); if ((s = InfoString_GetValue(string, "sv_maxclients", infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s); if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s); if ((s = InfoString_GetValue(string, "qcstatus" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->qcstatus, s, sizeof(info->qcstatus)); info->numhumans = info->numplayers - max(0, info->numbots); info->freeslots = info->maxplayers - info->numplayers; NetConn_ClientParsePacket_ServerList_UpdateCache(n); return true; } if (!strncmp(string, "getserversResponse\\", 19) && serverlist_cachecount < SERVERLIST_TOTALSIZE) { // Extract the IP addresses data += 18; length -= 18; NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, false); return true; } if (!strncmp(string, "getserversExtResponse", 21) && serverlist_cachecount < SERVERLIST_TOTALSIZE) { // Extract the IP addresses data += 21; length -= 21; NetConn_ClientParsePacket_ServerList_ParseDPList(peeraddress, data, length, true); return true; } if (!memcmp(string, "d\n", 2) && serverlist_cachecount < SERVERLIST_TOTALSIZE) { // Extract the IP addresses data += 2; length -= 2; masterreplycount++; if (serverlist_consoleoutput) Con_Printf("received QuakeWorld server list from %s...\n", addressstring2); while (length >= 6 && (data[0] != 0xFF || data[1] != 0xFF || data[2] != 0xFF || data[3] != 0xFF) && data[4] * 256 + data[5] != 0) { dpsnprintf (ipstring, sizeof (ipstring), "%u.%u.%u.%u:%u", data[0], data[1], data[2], data[3], data[4] * 256 + data[5]); if (serverlist_consoleoutput && developer_networking.integer) Con_Printf("Requesting info from QuakeWorld server %s\n", ipstring); if( !NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, ipstring, false ) ) { break; } // move on to next address in packet data += 6; length -= 6; } // begin or resume serverlist queries serverlist_querysleep = false; serverlist_querywaittime = realtime + 3; return true; } #endif if (!strncmp(string, "extResponse ", 12)) { ++cl_net_extresponse_count; if(cl_net_extresponse_count > NET_EXTRESPONSE_MAX) cl_net_extresponse_count = NET_EXTRESPONSE_MAX; cl_net_extresponse_last = (cl_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; dpsnprintf(cl_net_extresponse[cl_net_extresponse_last], sizeof(cl_net_extresponse[cl_net_extresponse_last]), "\"%s\" %s", addressstring2, string + 12); return true; } if (!strncmp(string, "ping", 4)) { if (developer_extra.integer) Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); return true; } if (!strncmp(string, "ack", 3)) return true; // QuakeWorld compatibility if (length > 1 && string[0] == 'c' && (string[1] == '-' || (string[1] >= '0' && string[1] <= '9')) && cls.connect_trying) { // challenge message if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("c message from wrong server %s\n", addressstring2); return true; } Con_Printf("challenge %s received, sending QuakeWorld connect request back to %s\n", string + 1, addressstring2); #ifdef CONFIG_MENU M_Update_Return_Reason("Got QuakeWorld challenge response"); #endif cls.qw_qport = qport.integer; // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); memcpy(senddata, "\377\377\377\377", 4); dpsnprintf(senddata+4, sizeof(senddata)-4, "connect %i %i %i \"%s%s\"\n", 28, cls.qw_qport, atoi(string + 1), cls.userinfo, cls.connect_userinfo); NetConn_WriteString(mysocket, senddata, peeraddress); return true; } if (length >= 1 && string[0] == 'j' && cls.connect_trying) { // accept message if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("j message from wrong server %s\n", addressstring2); return true; } #ifdef CONFIG_MENU M_Update_Return_Reason("QuakeWorld Accepted"); #endif NetConn_ConnectionEstablished(mysocket, peeraddress, PROTOCOL_QUAKEWORLD); return true; } if (length > 2 && !memcmp(string, "n\\", 2)) { #ifdef CONFIG_MENU serverlist_info_t *info; int n; // qw server status if (serverlist_consoleoutput && developer_networking.integer >= 2) Con_Printf("QW server status from server at %s:\n%s\n", addressstring2, string + 1); string += 1; // search the cache for this server and update it n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); if (n < 0) return true; info = &serverlist_cache[n].info; strlcpy(info->game, "QuakeWorld", sizeof(info->game)); if ((s = InfoString_GetValue(string, "*gamedir" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->mod , s, sizeof (info->mod ));else info->mod[0] = 0; if ((s = InfoString_GetValue(string, "map" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->map , s, sizeof (info->map ));else info->map[0] = 0; if ((s = InfoString_GetValue(string, "hostname" , infostringvalue, sizeof(infostringvalue))) != NULL) strlcpy(info->name, s, sizeof (info->name));else info->name[0] = 0; info->protocol = 0; info->numplayers = 0; // updated below info->numhumans = 0; // updated below if ((s = InfoString_GetValue(string, "maxclients" , infostringvalue, sizeof(infostringvalue))) != NULL) info->maxplayers = atoi(s);else info->maxplayers = 0; if ((s = InfoString_GetValue(string, "gameversion" , infostringvalue, sizeof(infostringvalue))) != NULL) info->gameversion = atoi(s);else info->gameversion = 0; // count active players on server // (we could gather more info, but we're just after the number) s = strchr(string, '\n'); if (s) { s++; while (s < string + length) { for (;s < string + length && *s != '\n';s++) ; if (s >= string + length) break; info->numplayers++; info->numhumans++; s++; } } NetConn_ClientParsePacket_ServerList_UpdateCache(n); #endif return true; } if (string[0] == 'n') { // qw print command, used by rcon replies too if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address) && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) { Con_DPrintf("n message from wrong server %s\n", addressstring2); return true; } Con_Printf("QW print command from server at %s:\n%s\n", addressstring2, string + 1); } // we may not have liked the packet, but it was a command packet, so // we're done processing this packet now return true; } // quakeworld ingame packet if (fromserver && cls.protocol == PROTOCOL_QUAKEWORLD && length >= 8 && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) { ret = 0; CL_ParseServerMessage(); return ret; } // netquake control packets, supported for compatibility only if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED) { #ifdef CONFIG_MENU int n; serverlist_info_t *info; #endif data += 4; length -= 4; SZ_Clear(&cl_message); SZ_Write(&cl_message, data, length); MSG_BeginReading(&cl_message); c = MSG_ReadByte(&cl_message); switch (c) { case CCREP_ACCEPT: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREP_ACCEPT from %s.\n", addressstring2); if (cls.connect_trying) { lhnetaddress_t clientportaddress; if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) { Con_DPrintf("CCREP_ACCEPT message from wrong server %s\n", addressstring2); break; } clientportaddress = *peeraddress; LHNETADDRESS_SetPort(&clientportaddress, MSG_ReadLong(&cl_message)); // extra ProQuake stuff if (length >= 6) cls.proquake_servermod = MSG_ReadByte(&cl_message); // MOD_PROQUAKE else cls.proquake_servermod = 0; if (length >= 7) cls.proquake_serverversion = MSG_ReadByte(&cl_message); // version * 10 else cls.proquake_serverversion = 0; if (length >= 8) cls.proquake_serverflags = MSG_ReadByte(&cl_message); // flags (mainly PQF_CHEATFREE) else cls.proquake_serverflags = 0; if (cls.proquake_servermod == 1) Con_Printf("Connected to ProQuake %.1f server, enabling precise aim\n", cls.proquake_serverversion / 10.0f); // update the server IP in the userinfo (QW servers expect this, and it is used by the reconnect command) InfoString_SetValue(cls.userinfo, sizeof(cls.userinfo), "*ip", addressstring2); #ifdef CONFIG_MENU M_Update_Return_Reason("Accepted"); #endif NetConn_ConnectionEstablished(mysocket, &clientportaddress, PROTOCOL_QUAKE); } break; case CCREP_REJECT: if (developer_extra.integer) { Con_DPrintf("CCREP_REJECT message from wrong server %s\n", addressstring2); break; } if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) break; cls.connect_trying = false; #ifdef CONFIG_MENU M_Update_Return_Reason((char *)MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); #endif break; case CCREP_SERVER_INFO: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2); #ifdef CONFIG_MENU // LordHavoc: because the quake server may report weird addresses // we just ignore it and keep the real address MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); // search the cache for this server and update it n = NetConn_ClientParsePacket_ServerList_ProcessReply(addressstring2); if (n < 0) break; info = &serverlist_cache[n].info; strlcpy(info->game, "Quake", sizeof(info->game)); strlcpy(info->mod , "", sizeof(info->mod)); // mod name is not specified strlcpy(info->name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->name)); strlcpy(info->map , MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(info->map)); info->numplayers = MSG_ReadByte(&cl_message); info->maxplayers = MSG_ReadByte(&cl_message); info->protocol = MSG_ReadByte(&cl_message); NetConn_ClientParsePacket_ServerList_UpdateCache(n); #endif break; case CCREP_RCON: // RocketGuy: ProQuake rcon support if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.rcon_address)) { Con_DPrintf("CCREP_RCON message from wrong server %s\n", addressstring2); break; } if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREP_RCON from %s.\n", addressstring2); Con_Printf("%s\n", MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); break; case CCREP_PLAYER_INFO: // we got a CCREP_PLAYER_INFO?? //if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: received CCREP_PLAYER_INFO from %s.\n", addressstring2); break; case CCREP_RULE_INFO: // we got a CCREP_RULE_INFO?? //if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: received CCREP_RULE_INFO from %s.\n", addressstring2); break; default: break; } SZ_Clear(&cl_message); // we may not have liked the packet, but it was a valid control // packet, so we're done processing this packet now return true; } ret = 0; if (fromserver && length >= (int)NET_HEADERSIZE && (ret = NetConn_ReceivedMessage(cls.netcon, data, length, cls.protocol, net_messagetimeout.value)) == 2) CL_ParseServerMessage(); return ret; } #ifdef CONFIG_MENU void NetConn_QueryQueueFrame(void) { int index; int queries; int maxqueries; double timeouttime; static double querycounter = 0; if(!net_slist_pause.integer && serverlist_paused) ServerList_RebuildViewList(); serverlist_paused = net_slist_pause.integer != 0; if (serverlist_querysleep) return; // apply a cool down time after master server replies, // to avoid messing up the ping times on the servers if (serverlist_querywaittime > realtime) return; // each time querycounter reaches 1.0 issue a query querycounter += cl.realframetime * net_slist_queriespersecond.value; maxqueries = (int)querycounter; maxqueries = bound(0, maxqueries, net_slist_queriesperframe.integer); querycounter -= maxqueries; if( maxqueries == 0 ) { return; } // scan serverlist and issue queries as needed serverlist_querysleep = true; timeouttime = realtime - net_slist_timeout.value; for( index = 0, queries = 0 ; index < serverlist_cachecount && queries < maxqueries ; index++ ) { serverlist_entry_t *entry = &serverlist_cache[ index ]; if( entry->query != SQS_QUERYING && entry->query != SQS_REFRESHING ) { continue; } serverlist_querysleep = false; if( entry->querycounter != 0 && entry->querytime > timeouttime ) { continue; } if( entry->querycounter != (unsigned) net_slist_maxtries.integer ) { lhnetaddress_t address; int socket; LHNETADDRESS_FromString(&address, entry->info.cname, 0); if (entry->protocol == PROTOCOL_QUAKEWORLD) { for (socket = 0; socket < cl_numsockets ; socket++) NetConn_WriteString(cl_sockets[socket], "\377\377\377\377status\n", &address); } else { for (socket = 0; socket < cl_numsockets ; socket++) NetConn_WriteString(cl_sockets[socket], "\377\377\377\377getstatus", &address); } // update the entry fields entry->querytime = realtime; entry->querycounter++; // if not in the slist menu we should print the server to console if (serverlist_consoleoutput) Con_Printf("querying %25s (%i. try)\n", entry->info.cname, entry->querycounter); queries++; } else { // have we tried to refresh this server? if( entry->query == SQS_REFRESHING ) { // yes, so update the reply count (since its not responding anymore) serverreplycount--; if(!serverlist_paused) ServerList_ViewList_Remove(entry); } entry->query = SQS_TIMEDOUT; } } } #endif void NetConn_ClientFrame(void) { int i, length; lhnetaddress_t peeraddress; unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; NetConn_UpdateSockets(); if (cls.connect_trying && cls.connect_nextsendtime < realtime) { #ifdef CONFIG_MENU if (cls.connect_remainingtries == 0) M_Update_Return_Reason("Connect: Waiting 10 seconds for reply"); #endif cls.connect_nextsendtime = realtime + 1; cls.connect_remainingtries--; if (cls.connect_remainingtries <= -10) { cls.connect_trying = false; #ifdef CONFIG_MENU M_Update_Return_Reason("Connect: Failed"); #endif return; } // try challenge first (newer DP server or QW) NetConn_WriteString(cls.connect_mysocket, "\377\377\377\377getchallenge", &cls.connect_address); // then try netquake as a fallback (old server, or netquake) SZ_Clear(&cl_message); // save space for the header, filled in later MSG_WriteLong(&cl_message, 0); MSG_WriteByte(&cl_message, CCREQ_CONNECT); MSG_WriteString(&cl_message, "QUAKE"); MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION); // extended proquake stuff MSG_WriteByte(&cl_message, 1); // mod = MOD_PROQUAKE // this version matches ProQuake 3.40, the first version to support // the NAT fix, and it only supports the NAT fix for ProQuake 3.40 or // higher clients, so we pretend we are that version... MSG_WriteByte(&cl_message, 34); // version * 10 MSG_WriteByte(&cl_message, 0); // flags MSG_WriteLong(&cl_message, 0); // password // write the packetsize now... StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(cls.connect_mysocket, cl_message.data, cl_message.cursize, &cls.connect_address); SZ_Clear(&cl_message); } for (i = 0;i < cl_numsockets;i++) { while (cl_sockets[i] && (length = NetConn_Read(cl_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) { // R_TimeReport("clientreadnetwork"); NetConn_ClientParsePacket(cl_sockets[i], readbuffer, length, &peeraddress); // R_TimeReport("clientparsepacket"); } } #ifdef CONFIG_MENU NetConn_QueryQueueFrame(); #endif if (cls.netcon && realtime > cls.netcon->timeout && !sv.active) { Con_Print("Connection timed out\n"); CL_Disconnect(); SV_LockThreadMutex(); Host_ShutdownServer (); SV_UnlockThreadMutex(); } } static void NetConn_BuildChallengeString(char *buffer, int bufferlength) { int i; char c; for (i = 0;i < bufferlength - 1;i++) { do { c = rand () % (127 - 33) + 33; } while (c == '\\' || c == ';' || c == '"' || c == '%' || c == '/'); buffer[i] = c; } buffer[i] = 0; } /// (div0) build the full response only if possible; better a getinfo response than no response at all if getstatus won't fit static qboolean NetConn_BuildStatusResponse(const char* challenge, char* out_msg, size_t out_size, qboolean fullstatus) { prvm_prog_t *prog = SVVM_prog; char qcstatus[256]; unsigned int nb_clients = 0, nb_bots = 0, i; int length; char teambuf[3]; const char *crypto_idstring; const char *worldstatusstr; // How many clients are there? for (i = 0;i < (unsigned int)svs.maxclients;i++) { if (svs.clients[i].active) { nb_clients++; if (!svs.clients[i].netconnection) nb_bots++; } } *qcstatus = 0; worldstatusstr = PRVM_GetString(prog, PRVM_serverglobalstring(worldstatus)); if(worldstatusstr && *worldstatusstr) { char *p; const char *q; p = qcstatus; for(q = worldstatusstr; *q && (size_t)(p - qcstatus) < (sizeof(qcstatus) - 1); ++q) if(*q != '\\' && *q != '\n') *p++ = *q; *p = 0; } /// \TODO: we should add more information for the full status string crypto_idstring = Crypto_GetInfoResponseDataString(); length = dpsnprintf(out_msg, out_size, "\377\377\377\377%s\x0A" "\\gamename\\%s\\modname\\%s\\gameversion\\%d\\sv_maxclients\\%d" "\\clients\\%d\\bots\\%d\\mapname\\%s\\hostname\\%s\\protocol\\%d" "%s%s" "%s%s" "%s%s" "%s", fullstatus ? "statusResponse" : "infoResponse", gamenetworkfiltername, com_modname, gameversion.integer, svs.maxclients, nb_clients, nb_bots, sv.worldbasename, hostname.string, NET_PROTOCOL_VERSION, *qcstatus ? "\\qcstatus\\" : "", qcstatus, challenge ? "\\challenge\\" : "", challenge ? challenge : "", crypto_idstring ? "\\d0_blind_id\\" : "", crypto_idstring ? crypto_idstring : "", fullstatus ? "\n" : ""); // Make sure it fits in the buffer if (length < 0) goto bad; if (fullstatus) { char *ptr; int left; int savelength; savelength = length; ptr = out_msg + length; left = (int)out_size - length; for (i = 0;i < (unsigned int)svs.maxclients;i++) { client_t *client = &svs.clients[i]; if (client->active) { int nameind, cleanind, pingvalue; char curchar; char cleanname [sizeof(client->name)]; const char *statusstr; prvm_edict_t *ed; // Remove all characters '"' and '\' in the player name nameind = 0; cleanind = 0; do { curchar = client->name[nameind++]; if (curchar != '"' && curchar != '\\') { cleanname[cleanind++] = curchar; if (cleanind == sizeof(cleanname) - 1) break; } } while (curchar != '\0'); cleanname[cleanind] = 0; // cleanind is always a valid index even at this point pingvalue = (int)(client->ping * 1000.0f); if(client->netconnection) pingvalue = bound(1, pingvalue, 9999); else pingvalue = 0; *qcstatus = 0; ed = PRVM_EDICT_NUM(i + 1); statusstr = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus)); if(statusstr && *statusstr) { char *p; const char *q; p = qcstatus; for(q = statusstr; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) *p++ = *q; *p = 0; } if (IS_NEXUIZ_DERIVED(gamemode) && (teamplay.integer > 0)) { if(client->frags == -666) // spectator strlcpy(teambuf, " 0", sizeof(teambuf)); else if(client->colors == 0x44) // red team strlcpy(teambuf, " 1", sizeof(teambuf)); else if(client->colors == 0xDD) // blue team strlcpy(teambuf, " 2", sizeof(teambuf)); else if(client->colors == 0xCC) // yellow team strlcpy(teambuf, " 3", sizeof(teambuf)); else if(client->colors == 0x99) // pink team strlcpy(teambuf, " 4", sizeof(teambuf)); else strlcpy(teambuf, " 0", sizeof(teambuf)); } else *teambuf = 0; // note: team number is inserted according to SoF2 protocol if(*qcstatus) length = dpsnprintf(ptr, left, "%s %d%s \"%s\"\n", qcstatus, pingvalue, teambuf, cleanname); else length = dpsnprintf(ptr, left, "%d %d%s \"%s\"\n", client->frags, pingvalue, teambuf, cleanname); if(length < 0) { // out of space? // turn it into an infoResponse! out_msg[savelength] = 0; memcpy(out_msg + 4, "infoResponse\x0A", 13); memmove(out_msg + 17, out_msg + 19, savelength - 19); break; } left -= length; ptr += length; } } } return true; bad: return false; } static qboolean NetConn_PreventFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength, double floodtime, qboolean renew) { size_t floodslotnum, bestfloodslotnum; double bestfloodtime; lhnetaddress_t noportpeeraddress; // see if this is a connect flood noportpeeraddress = *peeraddress; LHNETADDRESS_SetPort(&noportpeeraddress, 0); bestfloodslotnum = 0; bestfloodtime = floodlist[bestfloodslotnum].lasttime; for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++) { if (bestfloodtime >= floodlist[floodslotnum].lasttime) { bestfloodtime = floodlist[floodslotnum].lasttime; bestfloodslotnum = floodslotnum; } if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0) { // this address matches an ongoing flood address if (realtime < floodlist[floodslotnum].lasttime + floodtime) { if(renew) { // renew the ban on this address so it does not expire // until the flood has subsided floodlist[floodslotnum].lasttime = realtime; } //Con_Printf("Flood detected!\n"); return true; } // the flood appears to have subsided, so allow this bestfloodslotnum = floodslotnum; // reuse the same slot break; } } // begin a new timeout on this address floodlist[bestfloodslotnum].address = noportpeeraddress; floodlist[bestfloodslotnum].lasttime = realtime; //Con_Printf("Flood detection initiated!\n"); return false; } void NetConn_ClearFlood(lhnetaddress_t *peeraddress, server_floodaddress_t *floodlist, size_t floodlength) { size_t floodslotnum; lhnetaddress_t noportpeeraddress; // see if this is a connect flood noportpeeraddress = *peeraddress; LHNETADDRESS_SetPort(&noportpeeraddress, 0); for (floodslotnum = 0;floodslotnum < floodlength;floodslotnum++) { if (floodlist[floodslotnum].lasttime && LHNETADDRESS_Compare(&noportpeeraddress, &floodlist[floodslotnum].address) == 0) { // this address matches an ongoing flood address // remove the ban floodlist[floodslotnum].address.addresstype = LHNETADDRESSTYPE_NONE; floodlist[floodslotnum].lasttime = 0; //Con_Printf("Flood cleared!\n"); } } } typedef qboolean (*rcon_matchfunc_t) (lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen); static qboolean hmac_mdfour_time_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) { char mdfourbuf[16]; long t1, t2; t1 = (long) time(NULL); t2 = strtol(s, NULL, 0); if(abs(t1 - t2) > rcon_secure_maxdiff.integer) return false; if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password))) return false; return !memcmp(mdfourbuf, hash, 16); } static qboolean hmac_mdfour_challenge_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) { char mdfourbuf[16]; int i; if(slen < (int)(sizeof(challenges[0].string)) - 1) return false; // validate the challenge for (i = 0;i < MAX_CHALLENGES;i++) if(challenges[i].time > 0) if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strncmp(challenges[i].string, s, sizeof(challenges[0].string) - 1)) break; // if the challenge is not recognized, drop the packet if (i == MAX_CHALLENGES) return false; if(!HMAC_MDFOUR_16BYTES((unsigned char *) mdfourbuf, (unsigned char *) s, slen, (unsigned char *) password, (int)strlen(password))) return false; if(memcmp(mdfourbuf, hash, 16)) return false; // unmark challenge to prevent replay attacks challenges[i].time = 0; return true; } static qboolean plaintext_matching(lhnetaddress_t *peeraddress, const char *password, const char *hash, const char *s, int slen) { return !strcmp(password, hash); } /// returns a string describing the user level, or NULL for auth failure static const char *RCon_Authenticate(lhnetaddress_t *peeraddress, const char *password, const char *s, const char *endpos, rcon_matchfunc_t comparator, const char *cs, int cslen) { const char *text, *userpass_start, *userpass_end, *userpass_startpass; static char buf[MAX_INPUTLINE]; qboolean hasquotes; qboolean restricted = false; qboolean have_usernames = false; static char vabuf[1024]; userpass_start = rcon_password.string; while((userpass_end = strchr(userpass_start, ' '))) { have_usernames = true; strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); if(buf[0]) if(comparator(peeraddress, buf, password, cs, cslen)) goto allow; userpass_start = userpass_end + 1; } if(userpass_start[0]) { userpass_end = userpass_start + strlen(userpass_start); if(comparator(peeraddress, userpass_start, password, cs, cslen)) goto allow; } restricted = true; have_usernames = false; userpass_start = rcon_restricted_password.string; while((userpass_end = strchr(userpass_start, ' '))) { have_usernames = true; strlcpy(buf, userpass_start, ((size_t)(userpass_end-userpass_start) >= sizeof(buf)) ? (int)(sizeof(buf)) : (int)(userpass_end-userpass_start+1)); if(buf[0]) if(comparator(peeraddress, buf, password, cs, cslen)) goto check; userpass_start = userpass_end + 1; } if(userpass_start[0]) { userpass_end = userpass_start + strlen(userpass_start); if(comparator(peeraddress, userpass_start, password, cs, cslen)) goto check; } return NULL; // DENIED check: for(text = s; text != endpos; ++text) if((signed char) *text > 0 && ((signed char) *text < (signed char) ' ' || *text == ';')) return NULL; // block possible exploits against the parser/alias expansion while(s != endpos) { size_t l = strlen(s); if(l) { hasquotes = (strchr(s, '"') != NULL); // sorry, we can't allow these substrings in wildcard expressions, // as they can mess with the argument counts text = rcon_restricted_commands.string; while(COM_ParseToken_Console(&text)) { // com_token now contains a pattern to check for... if(strchr(com_token, '*') || strchr(com_token, '?')) // wildcard expression, * can only match a SINGLE argument { if(!hasquotes) if(matchpattern_with_separator(s, com_token, true, " ", true)) // note how we excluded tab, newline etc. above goto match; } else if(strchr(com_token, ' ')) // multi-arg expression? must match in whole { if(!strcmp(com_token, s)) goto match; } else // single-arg expression? must match the beginning of the command { if(!strcmp(com_token, s)) goto match; if(!memcmp(va(vabuf, sizeof(vabuf), "%s ", com_token), s, strlen(com_token) + 1)) goto match; } } // if we got here, nothing matched! return NULL; } match: s += l + 1; } allow: userpass_startpass = strchr(userpass_start, ':'); if(have_usernames && userpass_startpass && userpass_startpass < userpass_end) return va(vabuf, sizeof(vabuf), "%srcon (username %.*s)", restricted ? "restricted " : "", (int)(userpass_startpass-userpass_start), userpass_start); return va(vabuf, sizeof(vabuf), "%srcon", restricted ? "restricted " : ""); } static void RCon_Execute(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress, const char *addressstring2, const char *userlevel, const char *s, const char *endpos, qboolean proquakeprotocol) { if(userlevel) { // looks like a legitimate rcon command with the correct password const char *s_ptr = s; Con_Printf("server received %s command from %s: ", userlevel, host_client ? host_client->name : addressstring2); while(s_ptr != endpos) { size_t l = strlen(s_ptr); if(l) Con_Printf(" %s;", s_ptr); s_ptr += l + 1; } Con_Printf("\n"); if (!host_client || !host_client->netconnection || LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) Con_Rcon_Redirect_Init(mysocket, peeraddress, proquakeprotocol); while(s != endpos) { size_t l = strlen(s); if(l) { client_t *host_client_save = host_client; Cmd_ExecuteString(s, src_command, true); host_client = host_client_save; // in case it is a command that changes host_client (like restart) } s += l + 1; } Con_Rcon_Redirect_End(); } else { Con_Printf("server denied rcon access to %s\n", host_client ? host_client->name : addressstring2); } } static int NetConn_ServerParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) { int i, ret, clientnum, best; double besttime; char *string, response[1400], addressstring2[128]; static char stringbuf[16384]; // server only qboolean islocal = (LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP); char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; size_t sendlength, response_len; char infostringvalue[MAX_INPUTLINE]; if (!sv.active) return false; // convert the address to a string incase we need it LHNETADDRESS_ToString(peeraddress, addressstring2, sizeof(addressstring2), true); // see if we can identify the sender as a local player // (this is necessary for rcon to send a reliable reply if the client is // actually on the server, not sending remotely) for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if (host_client->netconnection && host_client->netconnection->mysocket == mysocket && !LHNETADDRESS_Compare(&host_client->netconnection->peeraddress, peeraddress)) break; if (i == svs.maxclients) host_client = NULL; if (length >= 5 && data[0] == 255 && data[1] == 255 && data[2] == 255 && data[3] == 255) { // received a command string - strip off the packaging and put it // into our string buffer with NULL termination data += 4; length -= 4; length = min(length, (int)sizeof(stringbuf) - 1); memcpy(stringbuf, data, length); stringbuf[length] = 0; string = stringbuf; if (developer_extra.integer) { Con_Printf("NetConn_ServerParsePacket: %s sent us a command:\n", addressstring2); Com_HexDumpToConsole(data, length); } sendlength = sizeof(senddata) - 4; switch(Crypto_ServerParsePacket(string, length, senddata+4, &sendlength, peeraddress)) { case CRYPTO_NOMATCH: // nothing to do break; case CRYPTO_MATCH: if(sendlength) { memcpy(senddata, "\377\377\377\377", 4); NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress); } break; case CRYPTO_DISCARD: if(sendlength) { memcpy(senddata, "\377\377\377\377", 4); NetConn_Write(mysocket, senddata, (int)sendlength+4, peeraddress); } return true; break; case CRYPTO_REPLACE: string = senddata+4; length = (int)sendlength; break; } if (length >= 12 && !memcmp(string, "getchallenge", 12) && (islocal || sv_public.integer > -3)) { for (i = 0, best = 0, besttime = realtime;i < MAX_CHALLENGES;i++) { if(challenges[i].time > 0) if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address)) break; if (besttime > challenges[i].time) besttime = challenges[best = i].time; } // if we did not find an exact match, choose the oldest and // update address and string if (i == MAX_CHALLENGES) { i = best; challenges[i].address = *peeraddress; NetConn_BuildChallengeString(challenges[i].string, sizeof(challenges[i].string)); } else { // flood control: drop if requesting challenge too often if(challenges[i].time > realtime - net_challengefloodblockingtimeout.value) return true; } challenges[i].time = realtime; // send the challenge memcpy(response, "\377\377\377\377", 4); dpsnprintf(response+4, sizeof(response)-4, "challenge %s", challenges[i].string); response_len = strlen(response) + 1; Crypto_ServerAppendToChallenge(string, length, response, &response_len, sizeof(response)); NetConn_Write(mysocket, response, (int)response_len, peeraddress); return true; } if (length > 8 && !memcmp(string, "connect\\", 8)) { char *s; client_t *client; crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); string += 7; length -= 7; if(crypto && crypto->authenticated) { // no need to check challenge if(crypto_developer.integer) { Con_Printf("%s connection to %s is being established: client is %s@%s%.*s, I am %.*s@%s%.*s\n", crypto->use_aes ? "Encrypted" : "Authenticated", addressstring2, crypto->client_idfp[0] ? crypto->client_idfp : "-", (crypto->client_issigned || !crypto->client_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, crypto->client_keyfp[0] ? crypto->client_keyfp : "-", crypto_keyfp_recommended_length, crypto->server_idfp[0] ? crypto->server_idfp : "-", (crypto->server_issigned || !crypto->server_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, crypto->server_keyfp[0] ? crypto->server_keyfp : "-" ); } } else { if ((s = InfoString_GetValue(string, "challenge", infostringvalue, sizeof(infostringvalue)))) { // validate the challenge for (i = 0;i < MAX_CHALLENGES;i++) if(challenges[i].time > 0) if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strcmp(challenges[i].string, s)) break; // if the challenge is not recognized, drop the packet if (i == MAX_CHALLENGES) return true; } } if((s = InfoString_GetValue(string, "message", infostringvalue, sizeof(infostringvalue)))) Con_DPrintf("Connecting client %s sent us the message: %s\n", addressstring2, s); if(!(islocal || sv_public.integer > -2)) { if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"reject %s\" to %s.\n", sv_public_rejectreason.string, addressstring2); memcpy(response, "\377\377\377\377", 4); dpsnprintf(response+4, sizeof(response)-4, "reject %s", sv_public_rejectreason.string); NetConn_WriteString(mysocket, response, peeraddress); return true; } // check engine protocol if(!(s = InfoString_GetValue(string, "protocol", infostringvalue, sizeof(infostringvalue))) || strcmp(s, "darkplaces 3")) { if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"reject Wrong game protocol.\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377reject Wrong game protocol.", peeraddress); return true; } // see if this is a duplicate connection request or a disconnected // client who is rejoining to the same client slot for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) { if (client->netconnection && LHNETADDRESS_Compare(peeraddress, &client->netconnection->peeraddress) == 0) { // this is a known client... if(crypto && crypto->authenticated) { // reject if changing key! if(client->netconnection->crypto.authenticated) { if( strcmp(client->netconnection->crypto.client_idfp, crypto->client_idfp) || strcmp(client->netconnection->crypto.server_idfp, crypto->server_idfp) || strcmp(client->netconnection->crypto.client_keyfp, crypto->client_keyfp) || strcmp(client->netconnection->crypto.server_keyfp, crypto->server_keyfp) ) { if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to change key of crypto.\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to change key of crypto.", peeraddress); return true; } } } else { // reject if downgrading! if(client->netconnection->crypto.authenticated) { if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"reject Attempt to downgrade crypto.\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377reject Attempt to downgrade crypto.", peeraddress); return true; } } if (client->begun) { // client crashed and is coming back, // keep their stuff intact if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); if(crypto && crypto->authenticated) Crypto_FinishInstance(&client->netconnection->crypto, crypto); SV_SendServerinfo(client); } else { // client is still trying to connect, // so we send a duplicate reply if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending duplicate accept to %s.\n", addressstring2); if(crypto && crypto->authenticated) Crypto_FinishInstance(&client->netconnection->crypto, crypto); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); } return true; } } if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true)) return true; // find an empty client slot for this new client for (clientnum = 0, client = svs.clients;clientnum < svs.maxclients;clientnum++, client++) { netconn_t *conn; if (!client->active && (conn = NetConn_Open(mysocket, peeraddress))) { // allocated connection if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"accept\" to %s.\n", conn->address); NetConn_WriteString(mysocket, "\377\377\377\377accept", peeraddress); // now set up the client if(crypto && crypto->authenticated) Crypto_FinishInstance(&conn->crypto, crypto); SV_ConnectClient(clientnum, conn); NetConn_Heartbeat(1); return true; } } // no empty slots found - server is full if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending \"reject Server is full.\" to %s.\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377reject Server is full.", peeraddress); return true; } if (length >= 7 && !memcmp(string, "getinfo", 7) && (islocal || sv_public.integer > -1)) { const char *challenge = NULL; if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) return true; // If there was a challenge in the getinfo message if (length > 8 && string[7] == ' ') challenge = string + 8; if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), false)) { if (developer_extra.integer) Con_DPrintf("Sending reply to master %s - %s\n", addressstring2, response); NetConn_WriteString(mysocket, response, peeraddress); } return true; } if (length >= 9 && !memcmp(string, "getstatus", 9) && (islocal || sv_public.integer > -1)) { const char *challenge = NULL; if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) return true; // If there was a challenge in the getinfo message if (length > 10 && string[9] == ' ') challenge = string + 10; if (NetConn_BuildStatusResponse(challenge, response, sizeof(response), true)) { if (developer_extra.integer) Con_DPrintf("Sending reply to client %s - %s\n", addressstring2, response); NetConn_WriteString(mysocket, response, peeraddress); } return true; } if (length >= 37 && !memcmp(string, "srcon HMAC-MD4 TIME ", 20)) { char *password = string + 20; char *timeval = string + 37; char *s = strchr(timeval, ' '); char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it const char *userlevel; if(rcon_secure.integer > 1) return true; if(!s) return true; // invalid packet ++s; userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_time_matching, timeval, endpos - timeval - 1); // not including the appended \0 into the HMAC RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); return true; } if (length >= 42 && !memcmp(string, "srcon HMAC-MD4 CHALLENGE ", 25)) { char *password = string + 25; char *challenge = string + 42; char *s = strchr(challenge, ' '); char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it const char *userlevel; if(!s) return true; // invalid packet ++s; userlevel = RCon_Authenticate(peeraddress, password, s, endpos, hmac_mdfour_challenge_matching, challenge, endpos - challenge - 1); // not including the appended \0 into the HMAC RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); return true; } if (length >= 5 && !memcmp(string, "rcon ", 5)) { int j; char *s = string + 5; char *endpos = string + length + 1; // one behind the NUL, so adding strlen+1 will eventually reach it char password[64]; if(rcon_secure.integer > 0) return true; for (j = 0;!ISWHITESPACE(*s);s++) if (j < (int)sizeof(password) - 1) password[j++] = *s; if(ISWHITESPACE(*s) && s != endpos) // skip leading ugly space ++s; password[j] = 0; if (!ISWHITESPACE(password[0])) { const char *userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, false); } return true; } if (!strncmp(string, "extResponse ", 12)) { ++sv_net_extresponse_count; if(sv_net_extresponse_count > NET_EXTRESPONSE_MAX) sv_net_extresponse_count = NET_EXTRESPONSE_MAX; sv_net_extresponse_last = (sv_net_extresponse_last + 1) % NET_EXTRESPONSE_MAX; dpsnprintf(sv_net_extresponse[sv_net_extresponse_last], sizeof(sv_net_extresponse[sv_net_extresponse_last]), "'%s' %s", addressstring2, string + 12); return true; } if (!strncmp(string, "ping", 4)) { if (developer_extra.integer) Con_DPrintf("Received ping from %s, sending ack\n", addressstring2); NetConn_WriteString(mysocket, "\377\377\377\377ack", peeraddress); return true; } if (!strncmp(string, "ack", 3)) return true; // we may not have liked the packet, but it was a command packet, so // we're done processing this packet now return true; } // netquake control packets, supported for compatibility only, and only // when running game protocols that are normally served via this connection // protocol // (this protects more modern protocols against being used for // Quake packet flood Denial Of Service attacks) if (length >= 5 && (i = BuffBigLong(data)) && (i & (~NETFLAG_LENGTH_MASK)) == (int)NETFLAG_CTL && (i & NETFLAG_LENGTH_MASK) == length && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) && !ENCRYPTION_REQUIRED) { int c; int protocolnumber; const char *protocolname; client_t *knownclient; client_t *newclient; data += 4; length -= 4; SZ_Clear(&sv_message); SZ_Write(&sv_message, data, length); MSG_BeginReading(&sv_message); c = MSG_ReadByte(&sv_message); switch (c) { case CCREQ_CONNECT: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_CONNECT from %s.\n", addressstring2); if(!(islocal || sv_public.integer > -2)) { if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"%s\" to %s.\n", sv_public_rejectreason.string, addressstring2); SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_REJECT); MSG_WriteUnterminatedString(&sv_message, sv_public_rejectreason.string); MSG_WriteString(&sv_message, "\n"); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); break; } protocolname = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); protocolnumber = MSG_ReadByte(&sv_message); if (strcmp(protocolname, "QUAKE") || protocolnumber != NET_PROTOCOL_VERSION) { if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Incompatible version.\" to %s.\n", addressstring2); SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_REJECT); MSG_WriteString(&sv_message, "Incompatible version.\n"); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); break; } // see if this connect request comes from a known client for (clientnum = 0, knownclient = svs.clients;clientnum < svs.maxclients;clientnum++, knownclient++) { if (knownclient->netconnection && LHNETADDRESS_Compare(peeraddress, &knownclient->netconnection->peeraddress) == 0) { // this is either a duplicate connection request // or coming back from a timeout // (if so, keep their stuff intact) crypto_t *crypto = Crypto_ServerGetInstance(peeraddress); if((crypto && crypto->authenticated) || knownclient->netconnection->crypto.authenticated) { if (developer_extra.integer) Con_Printf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Attempt to downgrade crypto.\" to %s.\n", addressstring2); SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_REJECT); MSG_WriteString(&sv_message, "Attempt to downgrade crypto.\n"); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); return true; } // send a reply if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending duplicate CCREP_ACCEPT to %s.\n", addressstring2); SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_ACCEPT); MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(knownclient->netconnection->mysocket))); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); // if client is already spawned, re-send the // serverinfo message as they'll need it to play if (knownclient->begun) SV_SendServerinfo(knownclient); return true; } } // this is a new client, check for connection flood if (NetConn_PreventFlood(peeraddress, sv.connectfloodaddresses, sizeof(sv.connectfloodaddresses) / sizeof(sv.connectfloodaddresses[0]), net_connectfloodblockingtimeout.value, true)) break; // find a slot for the new client for (clientnum = 0, newclient = svs.clients;clientnum < svs.maxclients;clientnum++, newclient++) { netconn_t *conn; if (!newclient->active && (newclient->netconnection = conn = NetConn_Open(mysocket, peeraddress)) != NULL) { // connect to the client // everything is allocated, just fill in the details strlcpy (conn->address, addressstring2, sizeof (conn->address)); if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_ACCEPT to %s.\n", addressstring2); // send back the info about the server connection SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_ACCEPT); MSG_WriteLong(&sv_message, LHNETADDRESS_GetPort(LHNET_AddressFromSocket(conn->mysocket))); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); // now set up the client struct SV_ConnectClient(clientnum, conn); NetConn_Heartbeat(1); return true; } } if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_REJECT \"Server is full.\" to %s.\n", addressstring2); // no room; try to let player know SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_REJECT); MSG_WriteString(&sv_message, "Server is full.\n"); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); break; case CCREQ_SERVER_INFO: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_SERVER_INFO from %s.\n", addressstring2); if(!(islocal || sv_public.integer > -1)) break; if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) break; if (sv.active && !strcmp(MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), "QUAKE")) { int numclients; char myaddressstring[128]; if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: sending CCREP_SERVER_INFO to %s.\n", addressstring2); SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_SERVER_INFO); LHNETADDRESS_ToString(LHNET_AddressFromSocket(mysocket), myaddressstring, sizeof(myaddressstring), true); MSG_WriteString(&sv_message, myaddressstring); MSG_WriteString(&sv_message, hostname.string); MSG_WriteString(&sv_message, sv.name); // How many clients are there? for (i = 0, numclients = 0;i < svs.maxclients;i++) if (svs.clients[i].active) numclients++; MSG_WriteByte(&sv_message, numclients); MSG_WriteByte(&sv_message, svs.maxclients); MSG_WriteByte(&sv_message, NET_PROTOCOL_VERSION); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); } break; case CCREQ_PLAYER_INFO: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_PLAYER_INFO from %s.\n", addressstring2); if(!(islocal || sv_public.integer > -1)) break; if (NetConn_PreventFlood(peeraddress, sv.getstatusfloodaddresses, sizeof(sv.getstatusfloodaddresses) / sizeof(sv.getstatusfloodaddresses[0]), net_getstatusfloodblockingtimeout.value, false)) break; if (sv.active) { int playerNumber, activeNumber, clientNumber; client_t *client; playerNumber = MSG_ReadByte(&sv_message); activeNumber = -1; for (clientNumber = 0, client = svs.clients; clientNumber < svs.maxclients; clientNumber++, client++) if (client->active && ++activeNumber == playerNumber) break; if (clientNumber != svs.maxclients) { SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_PLAYER_INFO); MSG_WriteByte(&sv_message, playerNumber); MSG_WriteString(&sv_message, client->name); MSG_WriteLong(&sv_message, client->colors); MSG_WriteLong(&sv_message, client->frags); MSG_WriteLong(&sv_message, (int)(realtime - client->connecttime)); if(sv_status_privacy.integer) MSG_WriteString(&sv_message, client->netconnection ? "hidden" : "botclient"); else MSG_WriteString(&sv_message, client->netconnection ? client->netconnection->address : "botclient"); StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); } } break; case CCREQ_RULE_INFO: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RULE_INFO from %s.\n", addressstring2); if(!(islocal || sv_public.integer > -1)) break; // no flood check here, as it only returns one cvar for one cvar and clients may iterate quickly if (sv.active) { char *prevCvarName; cvar_t *var; // find the search start location prevCvarName = MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)); var = Cvar_FindVarAfter(prevCvarName, CVAR_NOTIFY); // send the response SZ_Clear(&sv_message); // save space for the header, filled in later MSG_WriteLong(&sv_message, 0); MSG_WriteByte(&sv_message, CCREP_RULE_INFO); if (var) { MSG_WriteString(&sv_message, var->name); MSG_WriteString(&sv_message, var->string); } StoreBigLong(sv_message.data, NETFLAG_CTL | (sv_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, sv_message.data, sv_message.cursize, peeraddress); SZ_Clear(&sv_message); } break; case CCREQ_RCON: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREQ_RCON from %s.\n", addressstring2); if (sv.active && !rcon_secure.integer) { char password[2048]; char cmd[2048]; char *s; char *endpos; const char *userlevel; strlcpy(password, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(password)); strlcpy(cmd, MSG_ReadString(&sv_message, sv_readstring, sizeof(sv_readstring)), sizeof(cmd)); s = cmd; endpos = cmd + strlen(cmd) + 1; // one behind the NUL, so adding strlen+1 will eventually reach it userlevel = RCon_Authenticate(peeraddress, password, s, endpos, plaintext_matching, NULL, 0); RCon_Execute(mysocket, peeraddress, addressstring2, userlevel, s, endpos, true); return true; } break; default: break; } SZ_Clear(&sv_message); // we may not have liked the packet, but it was a valid control // packet, so we're done processing this packet now return true; } if (host_client) { if ((ret = NetConn_ReceivedMessage(host_client->netconnection, data, length, sv.protocol, host_client->begun ? net_messagetimeout.value : net_connecttimeout.value)) == 2) { SV_ReadClientMessage(); return ret; } } return 0; } void NetConn_ServerFrame(void) { int i, length; lhnetaddress_t peeraddress; unsigned char readbuffer[NET_HEADERSIZE+NET_MAXMESSAGE]; for (i = 0;i < sv_numsockets;i++) while (sv_sockets[i] && (length = NetConn_Read(sv_sockets[i], readbuffer, sizeof(readbuffer), &peeraddress)) > 0) NetConn_ServerParsePacket(sv_sockets[i], readbuffer, length, &peeraddress); for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { // never timeout loopback connections if (host_client->netconnection && realtime > host_client->netconnection->timeout && LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) != LHNETADDRESSTYPE_LOOP) { Con_Printf("Client \"%s\" connection timed out\n", host_client->name); SV_DropClient(false); } } } void NetConn_SleepMicroseconds(int microseconds) { LHNET_SleepUntilPacket_Microseconds(microseconds); } #ifdef CONFIG_MENU void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) { int i, j; int masternum; lhnetaddress_t masteraddress; lhnetaddress_t broadcastaddress; char request[256]; if (serverlist_cachecount >= SERVERLIST_TOTALSIZE) return; // 26000 is the default quake server port, servers on other ports will not // be found // note this is IPv4-only, I doubt there are IPv6-only LANs out there LHNETADDRESS_FromString(&broadcastaddress, "255.255.255.255", 26000); if (querydp) { for (i = 0;i < cl_numsockets;i++) { if (cl_sockets[i]) { const char *cmdname, *extraoptions; int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) { // search LAN for Quake servers SZ_Clear(&cl_message); // save space for the header, filled in later MSG_WriteLong(&cl_message, 0); MSG_WriteByte(&cl_message, CCREQ_SERVER_INFO); MSG_WriteString(&cl_message, "QUAKE"); MSG_WriteByte(&cl_message, NET_PROTOCOL_VERSION); StoreBigLong(cl_message.data, NETFLAG_CTL | (cl_message.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(cl_sockets[i], cl_message.data, cl_message.cursize, &broadcastaddress); SZ_Clear(&cl_message); // search LAN for DarkPlaces servers NetConn_WriteString(cl_sockets[i], "\377\377\377\377getstatus", &broadcastaddress); } // build the getservers message to send to the dpmaster master servers if (LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])) == LHNETADDRESSTYPE_INET6) { cmdname = "getserversExt"; extraoptions = " ipv4 ipv6"; // ask for IPv4 and IPv6 servers } else { cmdname = "getservers"; extraoptions = ""; } memcpy(request, "\377\377\377\377", 4); dpsnprintf(request+4, sizeof(request)-4, "%s %s %u empty full%s", cmdname, gamenetworkfiltername, NET_PROTOCOL_VERSION, extraoptions); // search internet for (masternum = 0;sv_masters[masternum].name;masternum++) { if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == af) { masterquerycount++; NetConn_WriteString(cl_sockets[i], request, &masteraddress); } } // search favorite servers for(j = 0; j < nFavorites; ++j) { if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) { if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_DARKPLACES7, request, true ); } } } } } // only query QuakeWorld servers when the user wants to if (queryqw) { for (i = 0;i < cl_numsockets;i++) { if (cl_sockets[i]) { int af = LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i])); if(LHNETADDRESS_GetAddressType(&broadcastaddress) == af) { // search LAN for QuakeWorld servers NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &broadcastaddress); // build the getservers message to send to the qwmaster master servers // note this has no -1 prefix, and the trailing nul byte is sent dpsnprintf(request, sizeof(request), "c\n"); } // search internet for (masternum = 0;sv_qwmasters[masternum].name;masternum++) { if (sv_qwmasters[masternum].string && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]))) { if (m_state != m_slist) { char lookupstring[128]; LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true); Con_Printf("Querying master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string); } masterquerycount++; NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress); } } // search favorite servers for(j = 0; j < nFavorites; ++j) { if(LHNETADDRESS_GetAddressType(&favorites[j]) == af) { if(LHNETADDRESS_ToString(&favorites[j], request, sizeof(request), true)) { NetConn_WriteString(cl_sockets[i], "\377\377\377\377status\n", &favorites[j]); NetConn_ClientParsePacket_ServerList_PrepareQuery( PROTOCOL_QUAKEWORLD, request, true ); } } } } } } if (!masterquerycount) { Con_Print("Unable to query master servers, no suitable network sockets active.\n"); M_Update_Return_Reason("No network"); } } #endif void NetConn_Heartbeat(int priority) { lhnetaddress_t masteraddress; int masternum; lhnetsocket_t *mysocket; // if it's a state change (client connected), limit next heartbeat to no // more than 30 sec in the future if (priority == 1 && nextheartbeattime > realtime + 30.0) nextheartbeattime = realtime + 30.0; // limit heartbeatperiod to 30 to 270 second range, // lower limit is to avoid abusing master servers with excess traffic, // upper limit is to avoid timing out on the master server (which uses // 300 sec timeout) if (sv_heartbeatperiod.value < 30) Cvar_SetValueQuick(&sv_heartbeatperiod, 30); if (sv_heartbeatperiod.value > 270) Cvar_SetValueQuick(&sv_heartbeatperiod, 270); // make advertising optional and don't advertise singleplayer games, and // only send a heartbeat as often as the admin wants if (sv.active && sv_public.integer > 0 && svs.maxclients >= 2 && (priority > 1 || realtime > nextheartbeattime)) { nextheartbeattime = realtime + sv_heartbeatperiod.value; for (masternum = 0;sv_masters[masternum].name;masternum++) if (sv_masters[masternum].string && sv_masters[masternum].string[0] && LHNETADDRESS_FromString(&masteraddress, sv_masters[masternum].string, DPMASTER_PORT) && (mysocket = NetConn_ChooseServerSocketForAddress(&masteraddress))) NetConn_WriteString(mysocket, "\377\377\377\377heartbeat DarkPlaces\x0A", &masteraddress); } } static void Net_Heartbeat_f(void) { if (sv.active) NetConn_Heartbeat(2); else Con_Print("No server running, can not heartbeat to master server.\n"); } static void PrintStats(netconn_t *conn) { if ((cls.state == ca_connected && cls.protocol == PROTOCOL_QUAKEWORLD) || (sv.active && sv.protocol == PROTOCOL_QUAKEWORLD)) Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->outgoing_unreliable_sequence, conn->qw.incoming_sequence); else Con_Printf("address=%21s canSend=%u sendSeq=%6u recvSeq=%6u\n", conn->address, !conn->sendMessageLength, conn->nq.sendSequence, conn->nq.receiveSequence); Con_Printf("unreliable messages sent = %i\n", conn->unreliableMessagesSent); Con_Printf("unreliable messages recv = %i\n", conn->unreliableMessagesReceived); Con_Printf("reliable messages sent = %i\n", conn->reliableMessagesSent); Con_Printf("reliable messages received = %i\n", conn->reliableMessagesReceived); Con_Printf("packetsSent = %i\n", conn->packetsSent); Con_Printf("packetsReSent = %i\n", conn->packetsReSent); Con_Printf("packetsReceived = %i\n", conn->packetsReceived); Con_Printf("receivedDuplicateCount = %i\n", conn->receivedDuplicateCount); Con_Printf("droppedDatagrams = %i\n", conn->droppedDatagrams); } void Net_Stats_f(void) { netconn_t *conn; Con_Print("connections =\n"); for (conn = netconn_list;conn;conn = conn->next) PrintStats(conn); } #ifdef CONFIG_MENU void Net_Refresh_f(void) { if (m_state != m_slist) { Con_Print("Sending new requests to master servers\n"); ServerList_QueryList(false, true, false, true); Con_Print("Listening for replies...\n"); } else ServerList_QueryList(false, true, false, false); } void Net_Slist_f(void) { ServerList_ResetMasks(); serverlist_sortbyfield = SLIF_PING; serverlist_sortflags = 0; if (m_state != m_slist) { Con_Print("Sending requests to master servers\n"); ServerList_QueryList(true, true, false, true); Con_Print("Listening for replies...\n"); } else ServerList_QueryList(true, true, false, false); } void Net_SlistQW_f(void) { ServerList_ResetMasks(); serverlist_sortbyfield = SLIF_PING; serverlist_sortflags = 0; if (m_state != m_slist) { Con_Print("Sending requests to master servers\n"); ServerList_QueryList(true, false, true, true); serverlist_consoleoutput = true; Con_Print("Listening for replies...\n"); } else ServerList_QueryList(true, false, true, false); } #endif void NetConn_Init(void) { int i; lhnetaddress_t tempaddress; netconn_mempool = Mem_AllocPool("network connections", 0, NULL); Cmd_AddCommand("net_stats", Net_Stats_f, "print network statistics"); #ifdef CONFIG_MENU Cmd_AddCommand("net_slist", Net_Slist_f, "query dp master servers and print all server information"); Cmd_AddCommand("net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information"); Cmd_AddCommand("net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information"); #endif Cmd_AddCommand("heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)"); Cvar_RegisterVariable(&net_test); Cvar_RegisterVariable(&net_usesizelimit); Cvar_RegisterVariable(&net_burstreserve); Cvar_RegisterVariable(&rcon_restricted_password); Cvar_RegisterVariable(&rcon_restricted_commands); Cvar_RegisterVariable(&rcon_secure_maxdiff); Cvar_RegisterVariable(&net_slist_queriespersecond); Cvar_RegisterVariable(&net_slist_queriesperframe); Cvar_RegisterVariable(&net_slist_timeout); Cvar_RegisterVariable(&net_slist_maxtries); Cvar_RegisterVariable(&net_slist_favorites); Cvar_RegisterVariable(&net_slist_pause); if(LHNET_DefaultDSCP(-1) >= 0) // register cvar only if supported Cvar_RegisterVariable(&net_tos_dscp); Cvar_RegisterVariable(&net_messagetimeout); Cvar_RegisterVariable(&net_connecttimeout); Cvar_RegisterVariable(&net_connectfloodblockingtimeout); Cvar_RegisterVariable(&net_challengefloodblockingtimeout); Cvar_RegisterVariable(&net_getstatusfloodblockingtimeout); Cvar_RegisterVariable(&net_sourceaddresscheck); Cvar_RegisterVariable(&cl_netlocalping); Cvar_RegisterVariable(&cl_netpacketloss_send); Cvar_RegisterVariable(&cl_netpacketloss_receive); Cvar_RegisterVariable(&hostname); Cvar_RegisterVariable(&developer_networking); Cvar_RegisterVariable(&cl_netport); Cvar_RegisterVariable(&sv_netport); Cvar_RegisterVariable(&net_address); Cvar_RegisterVariable(&net_address_ipv6); Cvar_RegisterVariable(&sv_public); Cvar_RegisterVariable(&sv_public_rejectreason); Cvar_RegisterVariable(&sv_heartbeatperiod); for (i = 0;sv_masters[i].name;i++) Cvar_RegisterVariable(&sv_masters[i]); Cvar_RegisterVariable(&gameversion); Cvar_RegisterVariable(&gameversion_min); Cvar_RegisterVariable(&gameversion_max); // COMMANDLINEOPTION: Server: -ip sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically. if ((i = COM_CheckParm("-ip")) && i + 1 < com_argc) { if (LHNETADDRESS_FromString(&tempaddress, com_argv[i + 1], 0) == 1) { Con_Printf("-ip option used, setting net_address to \"%s\"\n", com_argv[i + 1]); Cvar_SetQuick(&net_address, com_argv[i + 1]); } else Con_Printf("-ip option used, but unable to parse the address \"%s\"\n", com_argv[i + 1]); } // COMMANDLINEOPTION: Server: -port sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine if (((i = COM_CheckParm("-port")) || (i = COM_CheckParm("-ipport")) || (i = COM_CheckParm("-udpport"))) && i + 1 < com_argc) { i = atoi(com_argv[i + 1]); if (i >= 0 && i < 65536) { Con_Printf("-port option used, setting port cvar to %i\n", i); Cvar_SetValueQuick(&sv_netport, i); } else Con_Printf("-port option used, but %i is not a valid port number\n", i); } cl_numsockets = 0; sv_numsockets = 0; cl_message.data = cl_message_buf; cl_message.maxsize = sizeof(cl_message_buf); cl_message.cursize = 0; sv_message.data = sv_message_buf; sv_message.maxsize = sizeof(sv_message_buf); sv_message.cursize = 0; LHNET_Init(); if (Thread_HasThreads()) netconn_mutex = Thread_CreateMutex(); } void NetConn_Shutdown(void) { NetConn_CloseClientPorts(); NetConn_CloseServerPorts(); LHNET_Shutdown(); if (netconn_mutex) Thread_DestroyMutex(netconn_mutex); netconn_mutex = NULL; } darkplaces/darkplaces-sdl-vs2012.vcxproj0000664000175000017500000004305213067716220017430 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51} darkplacessdl Win32Proj darkplaces-sdl-vs2012 Application v110 MultiByte true Application v110 MultiByte Application v110 MultiByte true Application v110 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 darkplaces/gl_backend.h0000664000175000017500000002003713067716220014407 0ustar kalevkalev #ifndef GL_BACKEND_H #define GL_BACKEND_H extern r_viewport_t gl_viewport; extern matrix4x4_t gl_modelmatrix; extern matrix4x4_t gl_viewmatrix; extern matrix4x4_t gl_modelviewmatrix; extern matrix4x4_t gl_projectionmatrix; extern matrix4x4_t gl_modelviewprojectionmatrix; extern float gl_modelview16f[16]; extern float gl_modelviewprojection16f[16]; extern qboolean gl_modelmatrixchanged; extern cvar_t gl_vbo_dynamicvertex; extern cvar_t gl_vbo_dynamicindex; #define POLYGONELEMENTS_MAXPOINTS 258 extern int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; extern unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; #define QUADELEMENTS_MAXQUADS 128 extern int quadelement3i[QUADELEMENTS_MAXQUADS*6]; extern unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out); qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor); void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float zNear, float zFar, const float *nearplane); void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, float zFar, const float *nearplane); void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float zNear, const float *nearplane); void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane); void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane); void R_SetViewport(const r_viewport_t *v); void R_GetViewport(r_viewport_t *v); void GL_Finish(void); void GL_BlendFunc(int blendfunc1, int blendfunc2); void GL_BlendEquationSubtract(qboolean negated); void GL_DepthMask(int state); void GL_DepthTest(int state); void GL_DepthFunc(int state); void GL_DepthRange(float nearfrac, float farfrac); void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask); void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask); void GL_PolygonOffset(float planeoffset, float depthoffset); void GL_CullFace(int state); void GL_AlphaTest(int state); void GL_AlphaToCoverage(qboolean state); void GL_ColorMask(int r, int g, int b, int a); void GL_Color(float cr, float cg, float cb, float ca); void GL_ActiveTexture(unsigned int num); void GL_ClientActiveTexture(unsigned int num); void GL_Scissor(int x, int y, int width, int height); void GL_ScissorTest(int state); void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue); void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels); int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); void R_Mesh_DestroyFramebufferObject(int fbo); void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4); unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list); void GL_Backend_FreeProgram(unsigned int prog); extern cvar_t gl_paranoid; extern cvar_t gl_printcheckerror; // adds console variables and registers the render module (only call from GL_Init) void gl_backend_init(void); // starts mesh rendering for the frame void R_Mesh_Start(void); // ends mesh rendering for the frame // (only valid after R_Mesh_Start) void R_Mesh_Finish(void); // vertex buffer and index buffer creation/updating/freeing r_meshbuffer_t *R_Mesh_CreateMeshBuffer(const void *data, size_t size, const char *name, qboolean isindexbuffer, qboolean isuniformbuffer, qboolean isdynamic, qboolean isindex16); void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size, qboolean subdata, size_t offset); void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer); void GL_Mesh_ListVBOs(qboolean printeach); void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *buffer, int bufferoffset); r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices); qboolean R_Mesh_PrepareVertices_Generic_Unlock(void); void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f); void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset); r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices); qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void); // if this returns false, you need to prepare the mesh again! void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f); void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *buffer, int bufferoffset); // sets up the requested vertex transform matrix void R_EntityMatrix(const matrix4x4_t *matrix); // sets the vertex array pointer void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); // sets the color array pointer (GL_Color only works when this is NULL) void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); // sets the texcoord array pointer for an array unit, if GL_UNSIGNED_BYTE | 0x80000000 is specified it will be an unnormalized type (integer values) void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset); // returns current texture bound to this identifier int R_Mesh_TexBound(unsigned int unitnum, int id); // copies a section of the framebuffer to a 2D texture void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height); // bind a given texture to a given image unit void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex); // sets the texcoord matrix for a texenv unit, can be NULL or blank (will use identity) void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix); // sets the combine state for a texenv unit void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale); // set up a blank texture state (unbinds all textures, texcoord pointers, and resets combine settings) void R_Mesh_ResetTextureState(void); // before a texture is freed, make sure there are no references to it void R_Mesh_ClearBindingsForTexture(int texnum); // renders a mesh void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, int element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, int element3s_bufferoffset); // saves a section of the rendered frame to a .tga or .jpg file qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha); // used by R_Envmap_f and internally in backend, clears the frame void R_ClearScreen(qboolean fogcolor); #endif darkplaces/conproc.c0000664000175000017500000001563113067716216014005 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // conproc.c #include "quakedef.h" #include #include #include "conproc.h" HANDLE heventDone; HANDLE hfileBuffer; HANDLE heventChildSend; HANDLE heventParentSend; HANDLE hStdout; HANDLE hStdin; DWORD RequestProc (DWORD dwNichts); LPVOID GetMappedBuffer (HANDLE hfileBuffer); void ReleaseMappedBuffer (LPVOID pBuffer); BOOL GetScreenBufferLines (int *piLines); BOOL SetScreenBufferLines (int iLines); BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine); BOOL WriteText (LPCTSTR szText); int CharToCode (int c); BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy); void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild) { DWORD dwID; // ignore if we don't have all the events. if (!hFile || !heventParent || !heventChild) return; hfileBuffer = hFile; heventParentSend = heventParent; heventChildSend = heventChild; // so we'll know when to go away. heventDone = CreateEvent (NULL, false, false, NULL); if (!heventDone) { Con_Print("Couldn't create heventDone\n"); return; } if (!CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) RequestProc, 0, 0, &dwID)) { CloseHandle (heventDone); Con_Print("Couldn't create QHOST thread\n"); return; } // save off the input/output handles. hStdout = GetStdHandle (STD_OUTPUT_HANDLE); hStdin = GetStdHandle (STD_INPUT_HANDLE); // force 80 character width, at least 25 character height SetConsoleCXCY (hStdout, 80, 25); } void DeinitConProc (void) { if (heventDone) SetEvent (heventDone); } DWORD RequestProc (DWORD dwNichts) { int *pBuffer; DWORD dwRet; HANDLE heventWait[2]; int iBeginLine, iEndLine; heventWait[0] = heventParentSend; heventWait[1] = heventDone; while (1) { dwRet = WaitForMultipleObjects (2, heventWait, false, INFINITE); // heventDone fired, so we're exiting. if (dwRet == WAIT_OBJECT_0 + 1) break; pBuffer = (int *) GetMappedBuffer (hfileBuffer); // hfileBuffer is invalid. Just leave. if (!pBuffer) { Con_Print("Invalid hfileBuffer\n"); break; } switch (pBuffer[0]) { case CCOM_WRITE_TEXT: // Param1 : Text pBuffer[0] = WriteText ((LPCTSTR) (pBuffer + 1)); break; case CCOM_GET_TEXT: // Param1 : Begin line // Param2 : End line iBeginLine = pBuffer[1]; iEndLine = pBuffer[2]; pBuffer[0] = ReadText ((LPTSTR) (pBuffer + 1), iBeginLine, iEndLine); break; case CCOM_GET_SCR_LINES: // No params pBuffer[0] = GetScreenBufferLines (&pBuffer[1]); break; case CCOM_SET_SCR_LINES: // Param1 : Number of lines pBuffer[0] = SetScreenBufferLines (pBuffer[1]); break; } ReleaseMappedBuffer (pBuffer); SetEvent (heventChildSend); } return 0; } LPVOID GetMappedBuffer (HANDLE hfileBuffer) { LPVOID pBuffer; pBuffer = MapViewOfFile (hfileBuffer, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); return pBuffer; } void ReleaseMappedBuffer (LPVOID pBuffer) { UnmapViewOfFile (pBuffer); } BOOL GetScreenBufferLines (int *piLines) { CONSOLE_SCREEN_BUFFER_INFO info; BOOL bRet; bRet = GetConsoleScreenBufferInfo (hStdout, &info); if (bRet) *piLines = info.dwSize.Y; return bRet; } BOOL SetScreenBufferLines (int iLines) { return SetConsoleCXCY (hStdout, 80, iLines); } BOOL ReadText (LPTSTR pszText, int iBeginLine, int iEndLine) { COORD coord; DWORD dwRead; BOOL bRet; coord.X = 0; coord.Y = iBeginLine; bRet = ReadConsoleOutputCharacter( hStdout, pszText, 80 * (iEndLine - iBeginLine + 1), coord, &dwRead); // Make sure it's null terminated. if (bRet) pszText[dwRead] = '\0'; return bRet; } BOOL WriteText (LPCTSTR szText) { DWORD dwWritten; INPUT_RECORD rec; char upper, *sz; sz = (LPTSTR) szText; while (*sz) { // 13 is the code for a carriage return (\n) instead of 10. if (*sz == 10) *sz = 13; upper = toupper(*sz); rec.EventType = KEY_EVENT; rec.Event.KeyEvent.bKeyDown = true; rec.Event.KeyEvent.wRepeatCount = 1; rec.Event.KeyEvent.wVirtualKeyCode = upper; rec.Event.KeyEvent.wVirtualScanCode = CharToCode (*sz); rec.Event.KeyEvent.uChar.AsciiChar = *sz; rec.Event.KeyEvent.uChar.UnicodeChar = *sz; rec.Event.KeyEvent.dwControlKeyState = isupper(*sz) ? 0x80 : 0x0; WriteConsoleInput( hStdin, &rec, 1, &dwWritten); rec.Event.KeyEvent.bKeyDown = false; WriteConsoleInput( hStdin, &rec, 1, &dwWritten); sz++; } return true; } int CharToCode (int c) { char upper; upper = toupper(c); switch (c) { case 13: return 28; default: break; } if (isalpha(c)) return (30 + upper - 65); if (isdigit(c)) return (1 + upper - 47); return c; } BOOL SetConsoleCXCY(HANDLE hStdout, int cx, int cy) { CONSOLE_SCREEN_BUFFER_INFO info; COORD coordMax; coordMax = GetLargestConsoleWindowSize(hStdout); if (cy > coordMax.Y) cy = coordMax.Y; if (cx > coordMax.X) cx = coordMax.X; if (!GetConsoleScreenBufferInfo(hStdout, &info)) return false; // height info.srWindow.Left = 0; info.srWindow.Right = info.dwSize.X - 1; info.srWindow.Top = 0; info.srWindow.Bottom = cy - 1; if (cy < info.dwSize.Y) { if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) return false; info.dwSize.Y = cy; if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) return false; } else if (cy > info.dwSize.Y) { info.dwSize.Y = cy; if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) return false; if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) return false; } if (!GetConsoleScreenBufferInfo(hStdout, &info)) return false; // width info.srWindow.Left = 0; info.srWindow.Right = cx - 1; info.srWindow.Top = 0; info.srWindow.Bottom = info.dwSize.Y - 1; if (cx < info.dwSize.X) { if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) return false; info.dwSize.X = cx; if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) return false; } else if (cx > info.dwSize.X) { info.dwSize.X = cx; if (!SetConsoleScreenBufferSize(hStdout, info.dwSize)) return false; if (!SetConsoleWindowInfo(hStdout, true, &info.srWindow)) return false; } return true; } darkplaces/r_sky.c0000664000175000017500000002620413067716222013464 0ustar kalevkalev #include "quakedef.h" #include "image.h" // FIXME: fix skybox after vid_restart cvar_t r_sky = {CVAR_SAVE, "r_sky", "1", "enables sky rendering (black otherwise)"}; cvar_t r_skyscroll1 = {CVAR_SAVE, "r_skyscroll1", "1", "speed at which upper clouds layer scrolls in quake sky"}; cvar_t r_skyscroll2 = {CVAR_SAVE, "r_skyscroll2", "2", "speed at which lower clouds layer scrolls in quake sky"}; int skyrenderlater; int skyrendermasked; static int skyrendersphere; static int skyrenderbox; static rtexturepool_t *skytexturepool; static char skyname[MAX_QPATH]; static matrix4x4_t skymatrix; static matrix4x4_t skyinversematrix; typedef struct suffixinfo_s { const char *suffix; qboolean flipx, flipy, flipdiagonal; } suffixinfo_t; static const suffixinfo_t suffix[3][6] = { { {"px", false, false, false}, {"nx", false, false, false}, {"py", false, false, false}, {"ny", false, false, false}, {"pz", false, false, false}, {"nz", false, false, false} }, { {"posx", false, false, false}, {"negx", false, false, false}, {"posy", false, false, false}, {"negy", false, false, false}, {"posz", false, false, false}, {"negz", false, false, false} }, { {"rt", false, false, true}, {"lf", true, true, true}, {"bk", false, true, false}, {"ft", true, false, false}, {"up", false, false, true}, {"dn", false, false, true} } }; static skinframe_t *skyboxskinframe[6]; void R_SkyStartFrame(void) { skyrendersphere = false; skyrenderbox = false; skyrendermasked = false; // for depth-masked sky, we need to know whether any sky was rendered skyrenderlater = false; if (r_sky.integer) { if (skyboxskinframe[0] || skyboxskinframe[1] || skyboxskinframe[2] || skyboxskinframe[3] || skyboxskinframe[4] || skyboxskinframe[5]) skyrenderbox = true; else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.solidskyskinframe) skyrendersphere = true; skyrendermasked = true; } } /* ================== R_SetSkyBox ================== */ static void R_UnloadSkyBox(void) { int i; int c = 0; for (i = 0;i < 6;i++) { if (skyboxskinframe[i]) { // TODO: make a R_SkinFrame_Purge for single skins... c++; } skyboxskinframe[i] = NULL; } if (c && developer_loading.integer) Con_Printf("unloading skybox\n"); } static int R_LoadSkyBox(void) { int i, j, success; int indices[4] = {0,1,2,3}; char name[MAX_INPUTLINE]; unsigned char *image_buffer; unsigned char *temp; char vabuf[1024]; R_UnloadSkyBox(); if (!skyname[0]) return true; for (j=0; j<3; j++) { success = 0; for (i=0; i<6; i++) { if (dpsnprintf(name, sizeof(name), "%s_%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) { if (dpsnprintf(name, sizeof(name), "%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) { if (dpsnprintf(name, sizeof(name), "env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) { if (dpsnprintf(name, sizeof(name), "gfx/env/%s%s", skyname, suffix[j][i].suffix) < 0 || !(image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) continue; } } } temp = (unsigned char *)Mem_Alloc(tempmempool, image_width*image_height*4); Image_CopyMux (temp, image_buffer, image_width, image_height, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, indices); skyboxskinframe[i] = R_SkinFrame_LoadInternalBGRA(va(vabuf, sizeof(vabuf), "skyboxside%d", i), TEXF_CLAMP | (gl_texturecompression_sky.integer ? TEXF_COMPRESS : 0), temp, image_width, image_height, vid.sRGB3D); Mem_Free(image_buffer); Mem_Free(temp); success++; } if (success) break; } if (j == 3) return false; if (developer_loading.integer) Con_Printf("loading skybox \"%s\"\n", name); return true; } int R_SetSkyBox(const char *sky) { if (strcmp(sky, skyname) == 0) // no change return true; if (strlen(sky) > 1000) { Con_Printf("sky name too long (%i, max is 1000)\n", (int)strlen(sky)); return false; } strlcpy(skyname, sky, sizeof(skyname)); return R_LoadSkyBox(); } // LordHavoc: added LoadSky console command static void LoadSky_f (void) { switch (Cmd_Argc()) { case 1: if (skyname[0]) Con_Printf("current sky: %s\n", skyname); else Con_Print("no skybox has been set\n"); break; case 2: if (R_SetSkyBox(Cmd_Argv(1))) { if (skyname[0]) Con_Printf("skybox set to %s\n", skyname); else Con_Print("skybox disabled\n"); } else Con_Printf("failed to load skybox %s\n", Cmd_Argv(1)); break; default: Con_Print("usage: loadsky skyname\n"); break; } } static const float skyboxvertex3f[6*4*3] = { // skyside[0] 16, -16, 16, 16, -16, -16, 16, 16, -16, 16, 16, 16, // skyside[1] -16, 16, 16, -16, 16, -16, -16, -16, -16, -16, -16, 16, // skyside[2] 16, 16, 16, 16, 16, -16, -16, 16, -16, -16, 16, 16, // skyside[3] -16, -16, 16, -16, -16, -16, 16, -16, -16, 16, -16, 16, // skyside[4] -16, -16, 16, 16, -16, 16, 16, 16, 16, -16, 16, 16, // skyside[5] 16, -16, -16, -16, -16, -16, -16, 16, -16, 16, 16, -16 }; static const float skyboxtexcoord2f[6*4*2] = { // skyside[0] 0, 1, 1, 1, 1, 0, 0, 0, // skyside[1] 1, 0, 0, 0, 0, 1, 1, 1, // skyside[2] 1, 1, 1, 0, 0, 0, 0, 1, // skyside[3] 0, 0, 0, 1, 1, 1, 1, 0, // skyside[4] 0, 1, 1, 1, 1, 0, 0, 0, // skyside[5] 0, 1, 1, 1, 1, 0, 0, 0 }; static const int skyboxelement3i[6*2*3] = { // skyside[3] 0, 1, 2, 0, 2, 3, // skyside[1] 4, 5, 6, 4, 6, 7, // skyside[0] 8, 9, 10, 8, 10, 11, // skyside[2] 12, 13, 14, 12, 14, 15, // skyside[4] 16, 17, 18, 16, 18, 19, // skyside[5] 20, 21, 22, 20, 22, 23 }; static const unsigned short skyboxelement3s[6*2*3] = { // skyside[3] 0, 1, 2, 0, 2, 3, // skyside[1] 4, 5, 6, 4, 6, 7, // skyside[0] 8, 9, 10, 8, 10, 11, // skyside[2] 12, 13, 14, 12, 14, 15, // skyside[4] 16, 17, 18, 16, 18, 19, // skyside[5] 20, 21, 22, 20, 22, 23 }; static void R_SkyBox(void) { int i; RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, 6*4, skyboxvertex3f, skyboxtexcoord2f, NULL, NULL, NULL, NULL, 6*2, skyboxelement3i, skyboxelement3s, false, false); for (i = 0;i < 6;i++) if(skyboxskinframe[i]) R_DrawCustomSurface(skyboxskinframe[i], &identitymatrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST, i*4, 4, i*2, 2, false, false); } #define skygridx 32 #define skygridx1 (skygridx + 1) #define skygridxrecip (1.0f / (skygridx)) #define skygridy 32 #define skygridy1 (skygridy + 1) #define skygridyrecip (1.0f / (skygridy)) #define skysphere_numverts (skygridx1 * skygridy1) #define skysphere_numtriangles (skygridx * skygridy * 2) static float skysphere_vertex3f[skysphere_numverts * 3]; static float skysphere_texcoord2f[skysphere_numverts * 2]; static int skysphere_element3i[skysphere_numtriangles * 3]; static unsigned short skysphere_element3s[skysphere_numtriangles * 3]; static void skyspherecalc(void) { int i, j; unsigned short *e; float a, b, x, ax, ay, v[3], length, *vertex3f, *texcoord2f; float dx, dy, dz; dx = 16.0f; dy = 16.0f; dz = 16.0f / 3.0f; vertex3f = skysphere_vertex3f; texcoord2f = skysphere_texcoord2f; for (j = 0;j <= skygridy;j++) { a = j * skygridyrecip; ax = cos(a * M_PI * 2); ay = -sin(a * M_PI * 2); for (i = 0;i <= skygridx;i++) { b = i * skygridxrecip; x = cos((b + 0.5) * M_PI); v[0] = ax*x * dx; v[1] = ay*x * dy; v[2] = -sin((b + 0.5) * M_PI) * dz; length = 3.0f / sqrt(v[0]*v[0]+v[1]*v[1]+(v[2]*v[2]*9)); *texcoord2f++ = v[0] * length; *texcoord2f++ = v[1] * length; *vertex3f++ = v[0]; *vertex3f++ = v[1]; *vertex3f++ = v[2]; } } e = skysphere_element3s; for (j = 0;j < skygridy;j++) { for (i = 0;i < skygridx;i++) { *e++ = j * skygridx1 + i; *e++ = j * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i; *e++ = j * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i + 1; *e++ = (j + 1) * skygridx1 + i; } } for (i = 0;i < skysphere_numtriangles*3;i++) skysphere_element3i[i] = skysphere_element3s[i]; } static void R_SkySphere(void) { double speedscale; static qboolean skysphereinitialized = false; matrix4x4_t scroll1matrix, scroll2matrix; if (!skysphereinitialized) { skysphereinitialized = true; skyspherecalc(); } // wrap the scroll values just to be extra kind to float accuracy // scroll speed for upper layer speedscale = r_refdef.scene.time*r_skyscroll1.value*8.0/128.0; speedscale -= floor(speedscale); Matrix4x4_CreateTranslate(&scroll1matrix, speedscale, speedscale, 0); // scroll speed for lower layer (transparent layer) speedscale = r_refdef.scene.time*r_skyscroll2.value*8.0/128.0; speedscale -= floor(speedscale); Matrix4x4_CreateTranslate(&scroll2matrix, speedscale, speedscale, 0); RSurf_ActiveCustomEntity(&skymatrix, &skyinversematrix, 0, 0, 1, 1, 1, 1, skysphere_numverts, skysphere_vertex3f, skysphere_texcoord2f, NULL, NULL, NULL, NULL, skysphere_numtriangles, skysphere_element3i, skysphere_element3s, false, false); R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.solidskyskinframe, &scroll1matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST , 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); R_DrawCustomSurface(r_refdef.scene.worldmodel->brush.alphaskyskinframe, &scroll2matrix, MATERIALFLAG_SKY | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED, 0, skysphere_numverts, 0, skysphere_numtriangles, false, false); } void R_Sky(void) { Matrix4x4_CreateFromQuakeEntity(&skymatrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], 0, 0, 0, r_refdef.farclip * (0.5f / 16.0f)); Matrix4x4_Invert_Simple(&skyinversematrix, &skymatrix); if (skyrendersphere) { // this does not modify depth buffer R_SkySphere(); } else if (skyrenderbox) { // this does not modify depth buffer R_SkyBox(); } /* this will be skyroom someday else { // this modifies the depth buffer so we have to clear it afterward //R_SkyRoom(); // clear the depthbuffer that was used while rendering the skyroom //GL_Clear(GL_DEPTH_BUFFER_BIT); } */ } //=============================================================== void R_ResetSkyBox(void) { R_UnloadSkyBox(); skyname[0] = 0; R_LoadSkyBox(); } static void r_sky_start(void) { skytexturepool = R_AllocTexturePool(); R_LoadSkyBox(); } static void r_sky_shutdown(void) { R_UnloadSkyBox(); R_FreeTexturePool(&skytexturepool); } static void r_sky_newmap(void) { } void R_Sky_Init(void) { Cmd_AddCommand ("loadsky", &LoadSky_f, "load a skybox by basename (for example loadsky mtnsun_ loads mtnsun_ft.tga and so on)"); Cvar_RegisterVariable (&r_sky); Cvar_RegisterVariable (&r_skyscroll1); Cvar_RegisterVariable (&r_skyscroll2); memset(&skyboxskinframe, 0, sizeof(skyboxskinframe)); skyname[0] = 0; R_RegisterModule("R_Sky", r_sky_start, r_sky_shutdown, r_sky_newmap, NULL, NULL); } darkplaces/snd_null.c0000775000175000017500000000665313067716222014164 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // snd_null.c -- include this instead of all the other snd_* files to have // no sound code whatsoever #include "quakedef.h" cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "1", "master volume"}; cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; void S_Init (void) { Cvar_RegisterVariable(&bgmvolume); Cvar_RegisterVariable(&mastervolume); Cvar_RegisterVariable(&volume); Cvar_RegisterVariable(&snd_staticvolume); Cvar_RegisterVariable(&snd_initialized); Cvar_RegisterVariable(&snd_mutewhenidle); } void S_Terminate (void) { } void S_Startup (void) { } void S_Shutdown (void) { } void S_ClearUsed (void) { } void S_PurgeUnused (void) { } void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) { } int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) { return -1; } int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed) { return -1; } void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx) { } qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value) { return false; } void S_StopSound (int entnum, int entchannel) { } void S_PauseGameSounds (qboolean toggle) { } void S_SetChannelVolume (unsigned int ch_ind, float fvol) { } sfx_t *S_PrecacheSound (const char *sample, qboolean complain, qboolean levelsound) { return NULL; } float S_SoundLength(const char *name) { return -1; } qboolean S_IsSoundPrecached (const sfx_t *sfx) { return false; } void S_UnloadAllSounds_f (void) { } sfx_t *S_FindName (const char *name) { return NULL; } void S_Update(const matrix4x4_t *matrix) { } void S_StopAllSounds (void) { } void S_ExtraUpdate (void) { } qboolean S_LocalSound (const char *s) { return false; } void S_BlockSound (void) { } void S_UnblockSound (void) { } int S_GetSoundRate(void) { return 0; } int S_GetSoundChannels(void) { return 0; } float S_GetChannelPosition (unsigned int ch_ind) { return -1; } float S_GetEntChannelPosition(int entnum, int entchannel) { return -1; } void SndSys_SendKeyEvents(void) { } darkplaces/world.c0000664000175000017500000044146113067716222013472 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // world.c -- world query functions #include "quakedef.h" #include "clvm_cmds.h" #include "cl_collision.h" /* entities never clip against themselves, or their owner line of sight checks trace->inopen and trace->inwater, but bullets don't */ static void World_Physics_Init(void); void World_Init(void) { Collision_Init(); World_Physics_Init(); } static void World_Physics_Shutdown(void); void World_Shutdown(void) { World_Physics_Shutdown(); } static void World_Physics_Start(world_t *world); void World_Start(world_t *world) { World_Physics_Start(world); } static void World_Physics_End(world_t *world); void World_End(world_t *world) { World_Physics_End(world); } //============================================================================ /// World_ClearLink is used for new headnodes void World_ClearLink (link_t *l) { l->entitynumber = 0; l->prev = l->next = l; } void World_RemoveLink (link_t *l) { l->next->prev = l->prev; l->prev->next = l->next; } void World_InsertLinkBefore (link_t *l, link_t *before, int entitynumber) { l->entitynumber = entitynumber; l->next = before; l->prev = before->prev; l->prev->next = l; l->next->prev = l; } /* =============================================================================== ENTITY AREA CHECKING =============================================================================== */ void World_PrintAreaStats(world_t *world, const char *worldname) { Con_Printf("%s areagrid check stats: %d calls %d nodes (%f per call) %d entities (%f per call)\n", worldname, world->areagrid_stats_calls, world->areagrid_stats_nodechecks, (double) world->areagrid_stats_nodechecks / (double) world->areagrid_stats_calls, world->areagrid_stats_entitychecks, (double) world->areagrid_stats_entitychecks / (double) world->areagrid_stats_calls); world->areagrid_stats_calls = 0; world->areagrid_stats_nodechecks = 0; world->areagrid_stats_entitychecks = 0; } /* =============== World_SetSize =============== */ void World_SetSize(world_t *world, const char *filename, const vec3_t mins, const vec3_t maxs, prvm_prog_t *prog) { int i; strlcpy(world->filename, filename, sizeof(world->filename)); VectorCopy(mins, world->mins); VectorCopy(maxs, world->maxs); world->prog = prog; // the areagrid_marknumber is not allowed to be 0 if (world->areagrid_marknumber < 1) world->areagrid_marknumber = 1; // choose either the world box size, or a larger box to ensure the grid isn't too fine world->areagrid_size[0] = max(world->maxs[0] - world->mins[0], AREA_GRID * sv_areagrid_mingridsize.value); world->areagrid_size[1] = max(world->maxs[1] - world->mins[1], AREA_GRID * sv_areagrid_mingridsize.value); world->areagrid_size[2] = max(world->maxs[2] - world->mins[2], AREA_GRID * sv_areagrid_mingridsize.value); // figure out the corners of such a box, centered at the center of the world box world->areagrid_mins[0] = (world->mins[0] + world->maxs[0] - world->areagrid_size[0]) * 0.5f; world->areagrid_mins[1] = (world->mins[1] + world->maxs[1] - world->areagrid_size[1]) * 0.5f; world->areagrid_mins[2] = (world->mins[2] + world->maxs[2] - world->areagrid_size[2]) * 0.5f; world->areagrid_maxs[0] = (world->mins[0] + world->maxs[0] + world->areagrid_size[0]) * 0.5f; world->areagrid_maxs[1] = (world->mins[1] + world->maxs[1] + world->areagrid_size[1]) * 0.5f; world->areagrid_maxs[2] = (world->mins[2] + world->maxs[2] + world->areagrid_size[2]) * 0.5f; // now calculate the actual useful info from that VectorNegate(world->areagrid_mins, world->areagrid_bias); world->areagrid_scale[0] = AREA_GRID / world->areagrid_size[0]; world->areagrid_scale[1] = AREA_GRID / world->areagrid_size[1]; world->areagrid_scale[2] = AREA_GRID / world->areagrid_size[2]; World_ClearLink(&world->areagrid_outside); for (i = 0;i < AREA_GRIDNODES;i++) World_ClearLink(&world->areagrid[i]); if (developer_extra.integer) Con_DPrintf("areagrid settings: divisions %ix%ix1 : box %f %f %f : %f %f %f size %f %f %f grid %f %f %f (mingrid %f)\n", AREA_GRID, AREA_GRID, world->areagrid_mins[0], world->areagrid_mins[1], world->areagrid_mins[2], world->areagrid_maxs[0], world->areagrid_maxs[1], world->areagrid_maxs[2], world->areagrid_size[0], world->areagrid_size[1], world->areagrid_size[2], 1.0f / world->areagrid_scale[0], 1.0f / world->areagrid_scale[1], 1.0f / world->areagrid_scale[2], sv_areagrid_mingridsize.value); } /* =============== World_UnlinkAll =============== */ void World_UnlinkAll(world_t *world) { prvm_prog_t *prog = world->prog; int i; link_t *grid; // unlink all entities one by one grid = &world->areagrid_outside; while (grid->next != grid) World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); for (i = 0, grid = world->areagrid;i < AREA_GRIDNODES;i++, grid++) while (grid->next != grid) World_UnlinkEdict(PRVM_EDICT_NUM(grid->next->entitynumber)); } /* =============== =============== */ void World_UnlinkEdict(prvm_edict_t *ent) { int i; for (i = 0;i < ENTITYGRIDAREAS;i++) { if (ent->priv.server->areagrid[i].prev) { World_RemoveLink (&ent->priv.server->areagrid[i]); ent->priv.server->areagrid[i].prev = ent->priv.server->areagrid[i].next = NULL; } } } int World_EntitiesInBox(world_t *world, const vec3_t requestmins, const vec3_t requestmaxs, int maxlist, prvm_edict_t **list) { prvm_prog_t *prog = world->prog; int numlist; link_t *grid; link_t *l; prvm_edict_t *ent; vec3_t paddedmins, paddedmaxs; int igrid[3], igridmins[3], igridmaxs[3]; // LordHavoc: discovered this actually causes its own bugs (dm6 teleporters being too close to info_teleport_destination) //VectorSet(paddedmins, requestmins[0] - 1.0f, requestmins[1] - 1.0f, requestmins[2] - 1.0f); //VectorSet(paddedmaxs, requestmaxs[0] + 1.0f, requestmaxs[1] + 1.0f, requestmaxs[2] + 1.0f); VectorCopy(requestmins, paddedmins); VectorCopy(requestmaxs, paddedmaxs); // FIXME: if areagrid_marknumber wraps, all entities need their // ent->priv.server->areagridmarknumber reset world->areagrid_stats_calls++; world->areagrid_marknumber++; igridmins[0] = (int) floor((paddedmins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); igridmins[1] = (int) floor((paddedmins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); //igridmins[2] = (int) ((paddedmins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); igridmaxs[0] = (int) floor((paddedmaxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; igridmaxs[1] = (int) floor((paddedmaxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; //igridmaxs[2] = (int) ((paddedmaxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; igridmins[0] = max(0, igridmins[0]); igridmins[1] = max(0, igridmins[1]); //igridmins[2] = max(0, igridmins[2]); igridmaxs[0] = min(AREA_GRID, igridmaxs[0]); igridmaxs[1] = min(AREA_GRID, igridmaxs[1]); //igridmaxs[2] = min(AREA_GRID, igridmaxs[2]); // paranoid debugging //VectorSet(igridmins, 0, 0, 0);VectorSet(igridmaxs, AREA_GRID, AREA_GRID, AREA_GRID); numlist = 0; // add entities not linked into areagrid because they are too big or // outside the grid bounds if (world->areagrid_outside.next) { grid = &world->areagrid_outside; for (l = grid->next;l != grid;l = l->next) { ent = PRVM_EDICT_NUM(l->entitynumber); if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) { ent->priv.server->areagridmarknumber = world->areagrid_marknumber; if (!ent->priv.server->free && BoxesOverlap(paddedmins, paddedmaxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) { if (numlist < maxlist) list[numlist] = ent; numlist++; } world->areagrid_stats_entitychecks++; } } } // add grid linked entities for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) { grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++) { if (grid->next) { for (l = grid->next;l != grid;l = l->next) { ent = PRVM_EDICT_NUM(l->entitynumber); if (ent->priv.server->areagridmarknumber != world->areagrid_marknumber) { ent->priv.server->areagridmarknumber = world->areagrid_marknumber; if (!ent->priv.server->free && BoxesOverlap(paddedmins, paddedmaxs, ent->priv.server->areamins, ent->priv.server->areamaxs)) { if (numlist < maxlist) list[numlist] = ent; numlist++; } //Con_Printf("%d %f %f %f %f %f %f : %d : %f %f %f %f %f %f\n", BoxesOverlap(mins, maxs, ent->priv.server->areamins, ent->priv.server->areamaxs), ent->priv.server->areamins[0], ent->priv.server->areamins[1], ent->priv.server->areamins[2], ent->priv.server->areamaxs[0], ent->priv.server->areamaxs[1], ent->priv.server->areamaxs[2], PRVM_NUM_FOR_EDICT(ent), mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); } world->areagrid_stats_entitychecks++; } } } } return numlist; } static void World_LinkEdict_AreaGrid(world_t *world, prvm_edict_t *ent) { prvm_prog_t *prog = world->prog; link_t *grid; int igrid[3], igridmins[3], igridmaxs[3], gridnum, entitynumber = PRVM_NUM_FOR_EDICT(ent); if (entitynumber <= 0 || entitynumber >= prog->max_edicts || PRVM_EDICT_NUM(entitynumber) != ent) { Con_Printf ("World_LinkEdict_AreaGrid: invalid edict %p (edicts is %p, edict compared to prog->edicts is %i)\n", (void *)ent, (void *)prog->edicts, entitynumber); return; } igridmins[0] = (int) floor((ent->priv.server->areamins[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]); igridmins[1] = (int) floor((ent->priv.server->areamins[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]); //igridmins[2] = (int) floor((ent->priv.server->areamins[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]); igridmaxs[0] = (int) floor((ent->priv.server->areamaxs[0] + world->areagrid_bias[0]) * world->areagrid_scale[0]) + 1; igridmaxs[1] = (int) floor((ent->priv.server->areamaxs[1] + world->areagrid_bias[1]) * world->areagrid_scale[1]) + 1; //igridmaxs[2] = (int) floor((ent->priv.server->areamaxs[2] + world->areagrid_bias[2]) * world->areagrid_scale[2]) + 1; if (igridmins[0] < 0 || igridmaxs[0] > AREA_GRID || igridmins[1] < 0 || igridmaxs[1] > AREA_GRID || ((igridmaxs[0] - igridmins[0]) * (igridmaxs[1] - igridmins[1])) > ENTITYGRIDAREAS) { // wow, something outside the grid, store it as such World_InsertLinkBefore (&ent->priv.server->areagrid[0], &world->areagrid_outside, entitynumber); return; } gridnum = 0; for (igrid[1] = igridmins[1];igrid[1] < igridmaxs[1];igrid[1]++) { grid = world->areagrid + igrid[1] * AREA_GRID + igridmins[0]; for (igrid[0] = igridmins[0];igrid[0] < igridmaxs[0];igrid[0]++, grid++, gridnum++) World_InsertLinkBefore (&ent->priv.server->areagrid[gridnum], grid, entitynumber); } } /* =============== World_LinkEdict =============== */ void World_LinkEdict(world_t *world, prvm_edict_t *ent, const vec3_t mins, const vec3_t maxs) { prvm_prog_t *prog = world->prog; // unlink from old position first if (ent->priv.server->areagrid[0].prev) World_UnlinkEdict(ent); // don't add the world if (ent == prog->edicts) return; // don't add free entities if (ent->priv.server->free) return; VectorCopy(mins, ent->priv.server->areamins); VectorCopy(maxs, ent->priv.server->areamaxs); World_LinkEdict_AreaGrid(world, ent); } //============================================================================ // physics engine support //============================================================================ #ifdef USEODE cvar_t physics_ode_quadtree_depth = {0, "physics_ode_quadtree_depth","5", "desired subdivision level of quadtree culling space"}; cvar_t physics_ode_allowconvex = {0, "physics_ode_allowconvex", "0", "allow usage of Convex Hull primitive type on trimeshes that have custom 'collisionconvex' mesh. If disabled, trimesh primitive type are used."}; cvar_t physics_ode_contactsurfacelayer = {0, "physics_ode_contactsurfacelayer","1", "allows objects to overlap this many units to reduce jitter"}; cvar_t physics_ode_worldstep_iterations = {0, "physics_ode_worldstep_iterations", "20", "parameter to dWorldQuickStep"}; cvar_t physics_ode_contact_mu = {0, "physics_ode_contact_mu", "1", "contact solver mu parameter - friction pyramid approximation 1 (see ODE User Guide)"}; cvar_t physics_ode_contact_erp = {0, "physics_ode_contact_erp", "0.96", "contact solver erp parameter - Error Restitution Percent (see ODE User Guide)"}; cvar_t physics_ode_contact_cfm = {0, "physics_ode_contact_cfm", "0", "contact solver cfm parameter - Constraint Force Mixing (see ODE User Guide)"}; cvar_t physics_ode_contact_maxpoints = {0, "physics_ode_contact_maxpoints", "16", "maximal number of contact points between 2 objects, higher = stable (and slower), can be up to 32"}; cvar_t physics_ode_world_erp = {0, "physics_ode_world_erp", "-1", "world solver erp parameter - Error Restitution Percent (see ODE User Guide); use defaults when set to -1"}; cvar_t physics_ode_world_cfm = {0, "physics_ode_world_cfm", "-1", "world solver cfm parameter - Constraint Force Mixing (see ODE User Guide); not touched when -1"}; cvar_t physics_ode_world_damping = {0, "physics_ode_world_damping", "1", "enabled damping scale (see ODE User Guide), this scales all damping values, be aware that behavior depends of step type"}; cvar_t physics_ode_world_damping_linear = {0, "physics_ode_world_damping_linear", "0.01", "world linear damping scale (see ODE User Guide); use defaults when set to -1"}; cvar_t physics_ode_world_damping_linear_threshold = {0, "physics_ode_world_damping_linear_threshold", "0.1", "world linear damping threshold (see ODE User Guide); use defaults when set to -1"}; cvar_t physics_ode_world_damping_angular = {0, "physics_ode_world_damping_angular", "0.05", "world angular damping scale (see ODE User Guide); use defaults when set to -1"}; cvar_t physics_ode_world_damping_angular_threshold = {0, "physics_ode_world_damping_angular_threshold", "0.1", "world angular damping threshold (see ODE User Guide); use defaults when set to -1"}; cvar_t physics_ode_world_gravitymod = {0, "physics_ode_world_gravitymod", "1", "multiplies gravity got from sv_gravity, this may be needed to tweak if strong damping is used"}; cvar_t physics_ode_iterationsperframe = {0, "physics_ode_iterationsperframe", "1", "divisor for time step, runs multiple physics steps per frame"}; cvar_t physics_ode_constantstep = {0, "physics_ode_constantstep", "0", "use constant step instead of variable step which tends to increase stability, if set to 1 uses sys_ticrate, instead uses it's own value"}; cvar_t physics_ode_autodisable = {0, "physics_ode_autodisable", "1", "automatic disabling of objects which dont move for long period of time, makes object stacking a lot faster"}; cvar_t physics_ode_autodisable_steps = {0, "physics_ode_autodisable_steps", "10", "how many steps object should be dormant to be autodisabled"}; cvar_t physics_ode_autodisable_time = {0, "physics_ode_autodisable_time", "0", "how many seconds object should be dormant to be autodisabled"}; cvar_t physics_ode_autodisable_threshold_linear = {0, "physics_ode_autodisable_threshold_linear", "0.6", "body will be disabled if it's linear move below this value"}; cvar_t physics_ode_autodisable_threshold_angular = {0, "physics_ode_autodisable_threshold_angular", "6", "body will be disabled if it's angular move below this value"}; cvar_t physics_ode_autodisable_threshold_samples = {0, "physics_ode_autodisable_threshold_samples", "5", "average threshold with this number of samples"}; cvar_t physics_ode_movelimit = {0, "physics_ode_movelimit", "0.5", "clamp velocity if a single move would exceed this percentage of object thickness, to prevent flying through walls, be aware that behavior depends of step type"}; cvar_t physics_ode_spinlimit = {0, "physics_ode_spinlimit", "10000", "reset spin velocity if it gets too large"}; cvar_t physics_ode_trick_fixnan = {0, "physics_ode_trick_fixnan", "1", "engine trick that checks and fixes NaN velocity/origin/angles on objects, a value of 2 makes console prints on each fix"}; cvar_t physics_ode_printstats = {0, "physics_ode_printstats", "0", "print ODE stats each frame"}; cvar_t physics_ode = {0, "physics_ode", "0", "run ODE physics (VERY experimental and potentially buggy)"}; // LordHavoc: this large chunk of definitions comes from the ODE library // include files. #ifdef LINK_TO_LIBODE #include "ode/ode.h" #else #ifdef WINAPI // ODE does not use WINAPI #define ODE_API #else #define ODE_API #endif // note: dynamic builds of ODE tend to be double precision, this is not used // for static builds typedef double dReal; typedef dReal dVector3[4]; typedef dReal dVector4[4]; typedef dReal dMatrix3[4*3]; typedef dReal dMatrix4[4*4]; typedef dReal dMatrix6[8*6]; typedef dReal dQuaternion[4]; struct dxWorld; /* dynamics world */ struct dxSpace; /* collision space */ struct dxBody; /* rigid body (dynamics object) */ struct dxGeom; /* geometry (collision object) */ struct dxJoint; struct dxJointNode; struct dxJointGroup; struct dxTriMeshData; #define dInfinity 3.402823466e+38f typedef struct dxWorld *dWorldID; typedef struct dxSpace *dSpaceID; typedef struct dxBody *dBodyID; typedef struct dxGeom *dGeomID; typedef struct dxJoint *dJointID; typedef struct dxJointGroup *dJointGroupID; typedef struct dxTriMeshData *dTriMeshDataID; typedef struct dJointFeedback { dVector3 f1; /* force applied to body 1 */ dVector3 t1; /* torque applied to body 1 */ dVector3 f2; /* force applied to body 2 */ dVector3 t2; /* torque applied to body 2 */ } dJointFeedback; typedef enum dJointType { dJointTypeNone = 0, dJointTypeBall, dJointTypeHinge, dJointTypeSlider, dJointTypeContact, dJointTypeUniversal, dJointTypeHinge2, dJointTypeFixed, dJointTypeNull, dJointTypeAMotor, dJointTypeLMotor, dJointTypePlane2D, dJointTypePR, dJointTypePU, dJointTypePiston } dJointType; #define D_ALL_PARAM_NAMES(start) \ /* parameters for limits and motors */ \ dParamLoStop = start, \ dParamHiStop, \ dParamVel, \ dParamFMax, \ dParamFudgeFactor, \ dParamBounce, \ dParamCFM, \ dParamStopERP, \ dParamStopCFM, \ /* parameters for suspension */ \ dParamSuspensionERP, \ dParamSuspensionCFM, \ dParamERP, \ #define D_ALL_PARAM_NAMES_X(start,x) \ /* parameters for limits and motors */ \ dParamLoStop ## x = start, \ dParamHiStop ## x, \ dParamVel ## x, \ dParamFMax ## x, \ dParamFudgeFactor ## x, \ dParamBounce ## x, \ dParamCFM ## x, \ dParamStopERP ## x, \ dParamStopCFM ## x, \ /* parameters for suspension */ \ dParamSuspensionERP ## x, \ dParamSuspensionCFM ## x, \ dParamERP ## x, enum { D_ALL_PARAM_NAMES(0) D_ALL_PARAM_NAMES_X(0x100,2) D_ALL_PARAM_NAMES_X(0x200,3) /* add a multiple of this constant to the basic parameter numbers to get * the parameters for the second, third etc axes. */ dParamGroup=0x100 }; typedef struct dMass { dReal mass; dVector3 c; dMatrix3 I; } dMass; enum { dContactMu2 = 0x001, dContactFDir1 = 0x002, dContactBounce = 0x004, dContactSoftERP = 0x008, dContactSoftCFM = 0x010, dContactMotion1 = 0x020, dContactMotion2 = 0x040, dContactMotionN = 0x080, dContactSlip1 = 0x100, dContactSlip2 = 0x200, dContactApprox0 = 0x0000, dContactApprox1_1 = 0x1000, dContactApprox1_2 = 0x2000, dContactApprox1 = 0x3000 }; typedef struct dSurfaceParameters { /* must always be defined */ int mode; dReal mu; /* only defined if the corresponding flag is set in mode */ dReal mu2; dReal bounce; dReal bounce_vel; dReal soft_erp; dReal soft_cfm; dReal motion1,motion2,motionN; dReal slip1,slip2; } dSurfaceParameters; typedef struct dContactGeom { dVector3 pos; ///< contact position dVector3 normal; ///< normal vector dReal depth; ///< penetration depth dGeomID g1,g2; ///< the colliding geoms int side1,side2; ///< (to be documented) } dContactGeom; typedef struct dContact { dSurfaceParameters surface; dContactGeom geom; dVector3 fdir1; } dContact; typedef void dNearCallback (void *data, dGeomID o1, dGeomID o2); // SAP // Order XZY or ZXY usually works best, if your Y is up. #define dSAP_AXES_XYZ ((0)|(1<<2)|(2<<4)) #define dSAP_AXES_XZY ((0)|(2<<2)|(1<<4)) #define dSAP_AXES_YXZ ((1)|(0<<2)|(2<<4)) #define dSAP_AXES_YZX ((1)|(2<<2)|(0<<4)) #define dSAP_AXES_ZXY ((2)|(0<<2)|(1<<4)) #define dSAP_AXES_ZYX ((2)|(1<<2)|(0<<4)) const char* (ODE_API *dGetConfiguration)(void); int (ODE_API *dCheckConfiguration)( const char* token ); int (ODE_API *dInitODE)(void); //int (ODE_API *dInitODE2)(unsigned int uiInitFlags); //int (ODE_API *dAllocateODEDataForThread)(unsigned int uiAllocateFlags); //void (ODE_API *dCleanupODEAllDataForThread)(void); void (ODE_API *dCloseODE)(void); //int (ODE_API *dMassCheck)(const dMass *m); //void (ODE_API *dMassSetZero)(dMass *); //void (ODE_API *dMassSetParameters)(dMass *, dReal themass, dReal cgx, dReal cgy, dReal cgz, dReal I11, dReal I22, dReal I33, dReal I12, dReal I13, dReal I23); //void (ODE_API *dMassSetSphere)(dMass *, dReal density, dReal radius); void (ODE_API *dMassSetSphereTotal)(dMass *, dReal total_mass, dReal radius); //void (ODE_API *dMassSetCapsule)(dMass *, dReal density, int direction, dReal radius, dReal length); void (ODE_API *dMassSetCapsuleTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); //void (ODE_API *dMassSetCylinder)(dMass *, dReal density, int direction, dReal radius, dReal length); void (ODE_API *dMassSetCylinderTotal)(dMass *, dReal total_mass, int direction, dReal radius, dReal length); //void (ODE_API *dMassSetBox)(dMass *, dReal density, dReal lx, dReal ly, dReal lz); void (ODE_API *dMassSetBoxTotal)(dMass *, dReal total_mass, dReal lx, dReal ly, dReal lz); //void (ODE_API *dMassSetTrimesh)(dMass *, dReal density, dGeomID g); //void (ODE_API *dMassSetTrimeshTotal)(dMass *m, dReal total_mass, dGeomID g); //void (ODE_API *dMassAdjust)(dMass *, dReal newmass); //void (ODE_API *dMassTranslate)(dMass *, dReal x, dReal y, dReal z); //void (ODE_API *dMassRotate)(dMass *, const dMatrix3 R); //void (ODE_API *dMassAdd)(dMass *a, const dMass *b); // dWorldID (ODE_API *dWorldCreate)(void); void (ODE_API *dWorldDestroy)(dWorldID world); void (ODE_API *dWorldSetGravity)(dWorldID, dReal x, dReal y, dReal z); void (ODE_API *dWorldGetGravity)(dWorldID, dVector3 gravity); void (ODE_API *dWorldSetERP)(dWorldID, dReal erp); //dReal (ODE_API *dWorldGetERP)(dWorldID); void (ODE_API *dWorldSetCFM)(dWorldID, dReal cfm); //dReal (ODE_API *dWorldGetCFM)(dWorldID); //void (ODE_API *dWorldStep)(dWorldID, dReal stepsize); //void (ODE_API *dWorldImpulseToForce)(dWorldID, dReal stepsize, dReal ix, dReal iy, dReal iz, dVector3 force); void (ODE_API *dWorldQuickStep)(dWorldID w, dReal stepsize); void (ODE_API *dWorldSetQuickStepNumIterations)(dWorldID, int num); //int (ODE_API *dWorldGetQuickStepNumIterations)(dWorldID); //void (ODE_API *dWorldSetQuickStepW)(dWorldID, dReal over_relaxation); //dReal (ODE_API *dWorldGetQuickStepW)(dWorldID); //void (ODE_API *dWorldSetContactMaxCorrectingVel)(dWorldID, dReal vel); //dReal (ODE_API *dWorldGetContactMaxCorrectingVel)(dWorldID); void (ODE_API *dWorldSetContactSurfaceLayer)(dWorldID, dReal depth); //dReal (ODE_API *dWorldGetContactSurfaceLayer)(dWorldID); //void (ODE_API *dWorldStepFast1)(dWorldID, dReal stepsize, int maxiterations); //void (ODE_API *dWorldSetAutoEnableDepthSF1)(dWorldID, int autoEnableDepth); //int (ODE_API *dWorldGetAutoEnableDepthSF1)(dWorldID); //dReal (ODE_API *dWorldGetAutoDisableLinearThreshold)(dWorldID); void (ODE_API *dWorldSetAutoDisableLinearThreshold)(dWorldID, dReal linear_threshold); //dReal (ODE_API *dWorldGetAutoDisableAngularThreshold)(dWorldID); void (ODE_API *dWorldSetAutoDisableAngularThreshold)(dWorldID, dReal angular_threshold); //dReal (ODE_API *dWorldGetAutoDisableLinearAverageThreshold)(dWorldID); //void (ODE_API *dWorldSetAutoDisableLinearAverageThreshold)(dWorldID, dReal linear_average_threshold); //dReal (ODE_API *dWorldGetAutoDisableAngularAverageThreshold)(dWorldID); //void (ODE_API *dWorldSetAutoDisableAngularAverageThreshold)(dWorldID, dReal angular_average_threshold); //int (ODE_API *dWorldGetAutoDisableAverageSamplesCount)(dWorldID); void (ODE_API *dWorldSetAutoDisableAverageSamplesCount)(dWorldID, unsigned int average_samples_count ); //int (ODE_API *dWorldGetAutoDisableSteps)(dWorldID); void (ODE_API *dWorldSetAutoDisableSteps)(dWorldID, int steps); //dReal (ODE_API *dWorldGetAutoDisableTime)(dWorldID); void (ODE_API *dWorldSetAutoDisableTime)(dWorldID, dReal time); //int (ODE_API *dWorldGetAutoDisableFlag)(dWorldID); void (ODE_API *dWorldSetAutoDisableFlag)(dWorldID, int do_auto_disable); //dReal (ODE_API *dWorldGetLinearDampingThreshold)(dWorldID w); void (ODE_API *dWorldSetLinearDampingThreshold)(dWorldID w, dReal threshold); //dReal (ODE_API *dWorldGetAngularDampingThreshold)(dWorldID w); void (ODE_API *dWorldSetAngularDampingThreshold)(dWorldID w, dReal threshold); //dReal (ODE_API *dWorldGetLinearDamping)(dWorldID w); void (ODE_API *dWorldSetLinearDamping)(dWorldID w, dReal scale); //dReal (ODE_API *dWorldGetAngularDamping)(dWorldID w); void (ODE_API *dWorldSetAngularDamping)(dWorldID w, dReal scale); //void (ODE_API *dWorldSetDamping)(dWorldID w, dReal linear_scale, dReal angular_scale); //dReal (ODE_API *dWorldGetMaxAngularSpeed)(dWorldID w); //void (ODE_API *dWorldSetMaxAngularSpeed)(dWorldID w, dReal max_speed); //dReal (ODE_API *dBodyGetAutoDisableLinearThreshold)(dBodyID); //void (ODE_API *dBodySetAutoDisableLinearThreshold)(dBodyID, dReal linear_average_threshold); //dReal (ODE_API *dBodyGetAutoDisableAngularThreshold)(dBodyID); //void (ODE_API *dBodySetAutoDisableAngularThreshold)(dBodyID, dReal angular_average_threshold); //int (ODE_API *dBodyGetAutoDisableAverageSamplesCount)(dBodyID); //void (ODE_API *dBodySetAutoDisableAverageSamplesCount)(dBodyID, unsigned int average_samples_count); //int (ODE_API *dBodyGetAutoDisableSteps)(dBodyID); //void (ODE_API *dBodySetAutoDisableSteps)(dBodyID, int steps); //dReal (ODE_API *dBodyGetAutoDisableTime)(dBodyID); //void (ODE_API *dBodySetAutoDisableTime)(dBodyID, dReal time); //int (ODE_API *dBodyGetAutoDisableFlag)(dBodyID); //void (ODE_API *dBodySetAutoDisableFlag)(dBodyID, int do_auto_disable); //void (ODE_API *dBodySetAutoDisableDefaults)(dBodyID); //dWorldID (ODE_API *dBodyGetWorld)(dBodyID); dBodyID (ODE_API *dBodyCreate)(dWorldID); void (ODE_API *dBodyDestroy)(dBodyID); void (ODE_API *dBodySetData)(dBodyID, void *data); void * (ODE_API *dBodyGetData)(dBodyID); void (ODE_API *dBodySetPosition)(dBodyID, dReal x, dReal y, dReal z); void (ODE_API *dBodySetRotation)(dBodyID, const dMatrix3 R); //void (ODE_API *dBodySetQuaternion)(dBodyID, const dQuaternion q); void (ODE_API *dBodySetLinearVel)(dBodyID, dReal x, dReal y, dReal z); void (ODE_API *dBodySetAngularVel)(dBodyID, dReal x, dReal y, dReal z); const dReal * (ODE_API *dBodyGetPosition)(dBodyID); //void (ODE_API *dBodyCopyPosition)(dBodyID body, dVector3 pos); const dReal * (ODE_API *dBodyGetRotation)(dBodyID); //void (ODE_API *dBodyCopyRotation)(dBodyID, dMatrix3 R); //const dReal * (ODE_API *dBodyGetQuaternion)(dBodyID); //void (ODE_API *dBodyCopyQuaternion)(dBodyID body, dQuaternion quat); const dReal * (ODE_API *dBodyGetLinearVel)(dBodyID); const dReal * (ODE_API *dBodyGetAngularVel)(dBodyID); void (ODE_API *dBodySetMass)(dBodyID, const dMass *mass); //void (ODE_API *dBodyGetMass)(dBodyID, dMass *mass); void (ODE_API *dBodyAddForce)(dBodyID, dReal fx, dReal fy, dReal fz); void (ODE_API *dBodyAddTorque)(dBodyID, dReal fx, dReal fy, dReal fz); //void (ODE_API *dBodyAddRelForce)(dBodyID, dReal fx, dReal fy, dReal fz); //void (ODE_API *dBodyAddRelTorque)(dBodyID, dReal fx, dReal fy, dReal fz); void (ODE_API *dBodyAddForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); //void (ODE_API *dBodyAddForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); //void (ODE_API *dBodyAddRelForceAtPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); //void (ODE_API *dBodyAddRelForceAtRelPos)(dBodyID, dReal fx, dReal fy, dReal fz, dReal px, dReal py, dReal pz); //const dReal * (ODE_API *dBodyGetForce)(dBodyID); //const dReal * (ODE_API *dBodyGetTorque)(dBodyID); //void (ODE_API *dBodySetForce)(dBodyID b, dReal x, dReal y, dReal z); //void (ODE_API *dBodySetTorque)(dBodyID b, dReal x, dReal y, dReal z); //void (ODE_API *dBodyGetRelPointPos)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodyGetRelPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodyGetPointVel)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodyGetPosRelPoint)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodyVectorToWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodyVectorFromWorld)(dBodyID, dReal px, dReal py, dReal pz, dVector3 result); //void (ODE_API *dBodySetFiniteRotationMode)(dBodyID, int mode); //void (ODE_API *dBodySetFiniteRotationAxis)(dBodyID, dReal x, dReal y, dReal z); //int (ODE_API *dBodyGetFiniteRotationMode)(dBodyID); //void (ODE_API *dBodyGetFiniteRotationAxis)(dBodyID, dVector3 result); int (ODE_API *dBodyGetNumJoints)(dBodyID b); dJointID (ODE_API *dBodyGetJoint)(dBodyID, int index); //void (ODE_API *dBodySetDynamic)(dBodyID); //void (ODE_API *dBodySetKinematic)(dBodyID); //int (ODE_API *dBodyIsKinematic)(dBodyID); void (ODE_API *dBodyEnable)(dBodyID); void (ODE_API *dBodyDisable)(dBodyID); int (ODE_API *dBodyIsEnabled)(dBodyID); void (ODE_API *dBodySetGravityMode)(dBodyID b, int mode); int (ODE_API *dBodyGetGravityMode)(dBodyID b); //void (*dBodySetMovedCallback)(dBodyID b, void(ODE_API *callback)(dBodyID)); //dGeomID (ODE_API *dBodyGetFirstGeom)(dBodyID b); //dGeomID (ODE_API *dBodyGetNextGeom)(dGeomID g); //void (ODE_API *dBodySetDampingDefaults)(dBodyID b); //dReal (ODE_API *dBodyGetLinearDamping)(dBodyID b); //void (ODE_API *dBodySetLinearDamping)(dBodyID b, dReal scale); //dReal (ODE_API *dBodyGetAngularDamping)(dBodyID b); //void (ODE_API *dBodySetAngularDamping)(dBodyID b, dReal scale); //void (ODE_API *dBodySetDamping)(dBodyID b, dReal linear_scale, dReal angular_scale); //dReal (ODE_API *dBodyGetLinearDampingThreshold)(dBodyID b); //void (ODE_API *dBodySetLinearDampingThreshold)(dBodyID b, dReal threshold); //dReal (ODE_API *dBodyGetAngularDampingThreshold)(dBodyID b); //void (ODE_API *dBodySetAngularDampingThreshold)(dBodyID b, dReal threshold); //dReal (ODE_API *dBodyGetMaxAngularSpeed)(dBodyID b); //void (ODE_API *dBodySetMaxAngularSpeed)(dBodyID b, dReal max_speed); //int (ODE_API *dBodyGetGyroscopicMode)(dBodyID b); //void (ODE_API *dBodySetGyroscopicMode)(dBodyID b, int enabled); dJointID (ODE_API *dJointCreateBall)(dWorldID, dJointGroupID); dJointID (ODE_API *dJointCreateHinge)(dWorldID, dJointGroupID); dJointID (ODE_API *dJointCreateSlider)(dWorldID, dJointGroupID); dJointID (ODE_API *dJointCreateContact)(dWorldID, dJointGroupID, const dContact *); dJointID (ODE_API *dJointCreateHinge2)(dWorldID, dJointGroupID); dJointID (ODE_API *dJointCreateUniversal)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreatePR)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreatePU)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreatePiston)(dWorldID, dJointGroupID); dJointID (ODE_API *dJointCreateFixed)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreateNull)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreateAMotor)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreateLMotor)(dWorldID, dJointGroupID); //dJointID (ODE_API *dJointCreatePlane2D)(dWorldID, dJointGroupID); void (ODE_API *dJointDestroy)(dJointID); dJointGroupID (ODE_API *dJointGroupCreate)(int max_size); void (ODE_API *dJointGroupDestroy)(dJointGroupID); void (ODE_API *dJointGroupEmpty)(dJointGroupID); //int (ODE_API *dJointGetNumBodies)(dJointID); void (ODE_API *dJointAttach)(dJointID, dBodyID body1, dBodyID body2); //void (ODE_API *dJointEnable)(dJointID); //void (ODE_API *dJointDisable)(dJointID); //int (ODE_API *dJointIsEnabled)(dJointID); void (ODE_API *dJointSetData)(dJointID, void *data); void * (ODE_API *dJointGetData)(dJointID); //dJointType (ODE_API *dJointGetType)(dJointID); dBodyID (ODE_API *dJointGetBody)(dJointID, int index); //void (ODE_API *dJointSetFeedback)(dJointID, dJointFeedback *); //dJointFeedback *(ODE_API *dJointGetFeedback)(dJointID); void (ODE_API *dJointSetBallAnchor)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetBallAnchor2)(dJointID, dReal x, dReal y, dReal z); void (ODE_API *dJointSetBallParam)(dJointID, int parameter, dReal value); void (ODE_API *dJointSetHingeAnchor)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetHingeAnchorDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); void (ODE_API *dJointSetHingeAxis)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetHingeAxisOffset)(dJointID j, dReal x, dReal y, dReal z, dReal angle); void (ODE_API *dJointSetHingeParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddHingeTorque)(dJointID joint, dReal torque); void (ODE_API *dJointSetSliderAxis)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetSliderAxisDelta)(dJointID, dReal x, dReal y, dReal z, dReal ax, dReal ay, dReal az); void (ODE_API *dJointSetSliderParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddSliderForce)(dJointID joint, dReal force); void (ODE_API *dJointSetHinge2Anchor)(dJointID, dReal x, dReal y, dReal z); void (ODE_API *dJointSetHinge2Axis1)(dJointID, dReal x, dReal y, dReal z); void (ODE_API *dJointSetHinge2Axis2)(dJointID, dReal x, dReal y, dReal z); void (ODE_API *dJointSetHinge2Param)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddHinge2Torques)(dJointID joint, dReal torque1, dReal torque2); void (ODE_API *dJointSetUniversalAnchor)(dJointID, dReal x, dReal y, dReal z); void (ODE_API *dJointSetUniversalAxis1)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetUniversalAxis1Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); void (ODE_API *dJointSetUniversalAxis2)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetUniversalAxis2Offset)(dJointID, dReal x, dReal y, dReal z, dReal offset1, dReal offset2); void (ODE_API *dJointSetUniversalParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddUniversalTorques)(dJointID joint, dReal torque1, dReal torque2); //void (ODE_API *dJointSetPRAnchor)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPRAxis1)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPRAxis2)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPRParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddPRTorque)(dJointID j, dReal torque); //void (ODE_API *dJointSetPUAnchor)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPUAnchorOffset)(dJointID, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); //void (ODE_API *dJointSetPUAxis1)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPUAxis2)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPUAxis3)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPUAxisP)(dJointID id, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPUParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddPUTorque)(dJointID j, dReal torque); //void (ODE_API *dJointSetPistonAnchor)(dJointID, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetPistonAnchorOffset)(dJointID j, dReal x, dReal y, dReal z, dReal dx, dReal dy, dReal dz); //void (ODE_API *dJointSetPistonParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointAddPistonForce)(dJointID joint, dReal force); //void (ODE_API *dJointSetFixed)(dJointID); //void (ODE_API *dJointSetFixedParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointSetAMotorNumAxes)(dJointID, int num); //void (ODE_API *dJointSetAMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetAMotorAngle)(dJointID, int anum, dReal angle); //void (ODE_API *dJointSetAMotorParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointSetAMotorMode)(dJointID, int mode); //void (ODE_API *dJointAddAMotorTorques)(dJointID, dReal torque1, dReal torque2, dReal torque3); //void (ODE_API *dJointSetLMotorNumAxes)(dJointID, int num); //void (ODE_API *dJointSetLMotorAxis)(dJointID, int anum, int rel, dReal x, dReal y, dReal z); //void (ODE_API *dJointSetLMotorParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointSetPlane2DXParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointSetPlane2DYParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointSetPlane2DAngleParam)(dJointID, int parameter, dReal value); //void (ODE_API *dJointGetBallAnchor)(dJointID, dVector3 result); //void (ODE_API *dJointGetBallAnchor2)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetBallParam)(dJointID, int parameter); //void (ODE_API *dJointGetHingeAnchor)(dJointID, dVector3 result); //void (ODE_API *dJointGetHingeAnchor2)(dJointID, dVector3 result); //void (ODE_API *dJointGetHingeAxis)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetHingeParam)(dJointID, int parameter); //dReal (ODE_API *dJointGetHingeAngle)(dJointID); //dReal (ODE_API *dJointGetHingeAngleRate)(dJointID); //dReal (ODE_API *dJointGetSliderPosition)(dJointID); //dReal (ODE_API *dJointGetSliderPositionRate)(dJointID); //void (ODE_API *dJointGetSliderAxis)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetSliderParam)(dJointID, int parameter); //void (ODE_API *dJointGetHinge2Anchor)(dJointID, dVector3 result); //void (ODE_API *dJointGetHinge2Anchor2)(dJointID, dVector3 result); //void (ODE_API *dJointGetHinge2Axis1)(dJointID, dVector3 result); //void (ODE_API *dJointGetHinge2Axis2)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetHinge2Param)(dJointID, int parameter); //dReal (ODE_API *dJointGetHinge2Angle1)(dJointID); //dReal (ODE_API *dJointGetHinge2Angle1Rate)(dJointID); //dReal (ODE_API *dJointGetHinge2Angle2Rate)(dJointID); //void (ODE_API *dJointGetUniversalAnchor)(dJointID, dVector3 result); //void (ODE_API *dJointGetUniversalAnchor2)(dJointID, dVector3 result); //void (ODE_API *dJointGetUniversalAxis1)(dJointID, dVector3 result); //void (ODE_API *dJointGetUniversalAxis2)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetUniversalParam)(dJointID, int parameter); //void (ODE_API *dJointGetUniversalAngles)(dJointID, dReal *angle1, dReal *angle2); //dReal (ODE_API *dJointGetUniversalAngle1)(dJointID); //dReal (ODE_API *dJointGetUniversalAngle2)(dJointID); //dReal (ODE_API *dJointGetUniversalAngle1Rate)(dJointID); //dReal (ODE_API *dJointGetUniversalAngle2Rate)(dJointID); //void (ODE_API *dJointGetPRAnchor)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetPRPosition)(dJointID); //dReal (ODE_API *dJointGetPRPositionRate)(dJointID); //dReal (ODE_API *dJointGetPRAngle)(dJointID); //dReal (ODE_API *dJointGetPRAngleRate)(dJointID); //void (ODE_API *dJointGetPRAxis1)(dJointID, dVector3 result); //void (ODE_API *dJointGetPRAxis2)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetPRParam)(dJointID, int parameter); //void (ODE_API *dJointGetPUAnchor)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetPUPosition)(dJointID); //dReal (ODE_API *dJointGetPUPositionRate)(dJointID); //void (ODE_API *dJointGetPUAxis1)(dJointID, dVector3 result); //void (ODE_API *dJointGetPUAxis2)(dJointID, dVector3 result); //void (ODE_API *dJointGetPUAxis3)(dJointID, dVector3 result); //void (ODE_API *dJointGetPUAxisP)(dJointID id, dVector3 result); //void (ODE_API *dJointGetPUAngles)(dJointID, dReal *angle1, dReal *angle2); //dReal (ODE_API *dJointGetPUAngle1)(dJointID); //dReal (ODE_API *dJointGetPUAngle1Rate)(dJointID); //dReal (ODE_API *dJointGetPUAngle2)(dJointID); //dReal (ODE_API *dJointGetPUAngle2Rate)(dJointID); //dReal (ODE_API *dJointGetPUParam)(dJointID, int parameter); //dReal (ODE_API *dJointGetPistonPosition)(dJointID); //dReal (ODE_API *dJointGetPistonPositionRate)(dJointID); //dReal (ODE_API *dJointGetPistonAngle)(dJointID); //dReal (ODE_API *dJointGetPistonAngleRate)(dJointID); //void (ODE_API *dJointGetPistonAnchor)(dJointID, dVector3 result); //void (ODE_API *dJointGetPistonAnchor2)(dJointID, dVector3 result); //void (ODE_API *dJointGetPistonAxis)(dJointID, dVector3 result); //dReal (ODE_API *dJointGetPistonParam)(dJointID, int parameter); //int (ODE_API *dJointGetAMotorNumAxes)(dJointID); //void (ODE_API *dJointGetAMotorAxis)(dJointID, int anum, dVector3 result); //int (ODE_API *dJointGetAMotorAxisRel)(dJointID, int anum); //dReal (ODE_API *dJointGetAMotorAngle)(dJointID, int anum); //dReal (ODE_API *dJointGetAMotorAngleRate)(dJointID, int anum); //dReal (ODE_API *dJointGetAMotorParam)(dJointID, int parameter); //int (ODE_API *dJointGetAMotorMode)(dJointID); //int (ODE_API *dJointGetLMotorNumAxes)(dJointID); //void (ODE_API *dJointGetLMotorAxis)(dJointID, int anum, dVector3 result); //dReal (ODE_API *dJointGetLMotorParam)(dJointID, int parameter); //dReal (ODE_API *dJointGetFixedParam)(dJointID, int parameter); //dJointID (ODE_API *dConnectingJoint)(dBodyID, dBodyID); //int (ODE_API *dConnectingJointList)(dBodyID, dBodyID, dJointID*); int (ODE_API *dAreConnected)(dBodyID, dBodyID); int (ODE_API *dAreConnectedExcluding)(dBodyID body1, dBodyID body2, int joint_type); // dSpaceID (ODE_API *dSimpleSpaceCreate)(dSpaceID space); dSpaceID (ODE_API *dHashSpaceCreate)(dSpaceID space); dSpaceID (ODE_API *dQuadTreeSpaceCreate)(dSpaceID space, const dVector3 Center, const dVector3 Extents, int Depth); //dSpaceID (ODE_API *dSweepAndPruneSpaceCreate)( dSpaceID space, int axisorder ); void (ODE_API *dSpaceDestroy)(dSpaceID); //void (ODE_API *dHashSpaceSetLevels)(dSpaceID space, int minlevel, int maxlevel); //void (ODE_API *dHashSpaceGetLevels)(dSpaceID space, int *minlevel, int *maxlevel); //void (ODE_API *dSpaceSetCleanup)(dSpaceID space, int mode); //int (ODE_API *dSpaceGetCleanup)(dSpaceID space); //void (ODE_API *dSpaceSetSublevel)(dSpaceID space, int sublevel); //int (ODE_API *dSpaceGetSublevel)(dSpaceID space); //void (ODE_API *dSpaceSetManualCleanup)(dSpaceID space, int mode); //int (ODE_API *dSpaceGetManualCleanup)(dSpaceID space); //void (ODE_API *dSpaceAdd)(dSpaceID, dGeomID); //void (ODE_API *dSpaceRemove)(dSpaceID, dGeomID); //int (ODE_API *dSpaceQuery)(dSpaceID, dGeomID); //void (ODE_API *dSpaceClean)(dSpaceID); //int (ODE_API *dSpaceGetNumGeoms)(dSpaceID); //dGeomID (ODE_API *dSpaceGetGeom)(dSpaceID, int i); //int (ODE_API *dSpaceGetClass)(dSpaceID space); // void (ODE_API *dGeomDestroy)(dGeomID geom); void (ODE_API *dGeomSetData)(dGeomID geom, void* data); void * (ODE_API *dGeomGetData)(dGeomID geom); void (ODE_API *dGeomSetBody)(dGeomID geom, dBodyID body); dBodyID (ODE_API *dGeomGetBody)(dGeomID geom); void (ODE_API *dGeomSetPosition)(dGeomID geom, dReal x, dReal y, dReal z); void (ODE_API *dGeomSetRotation)(dGeomID geom, const dMatrix3 R); //void (ODE_API *dGeomSetQuaternion)(dGeomID geom, const dQuaternion Q); //const dReal * (ODE_API *dGeomGetPosition)(dGeomID geom); //void (ODE_API *dGeomCopyPosition)(dGeomID geom, dVector3 pos); //const dReal * (ODE_API *dGeomGetRotation)(dGeomID geom); //void (ODE_API *dGeomCopyRotation)(dGeomID geom, dMatrix3 R); //void (ODE_API *dGeomGetQuaternion)(dGeomID geom, dQuaternion result); //void (ODE_API *dGeomGetAABB)(dGeomID geom, dReal aabb[6]); int (ODE_API *dGeomIsSpace)(dGeomID geom); //dSpaceID (ODE_API *dGeomGetSpace)(dGeomID); //int (ODE_API *dGeomGetClass)(dGeomID geom); //void (ODE_API *dGeomSetCategoryBits)(dGeomID geom, unsigned long bits); //void (ODE_API *dGeomSetCollideBits)(dGeomID geom, unsigned long bits); //unsigned long (ODE_API *dGeomGetCategoryBits)(dGeomID); //unsigned long (ODE_API *dGeomGetCollideBits)(dGeomID); //void (ODE_API *dGeomEnable)(dGeomID geom); //void (ODE_API *dGeomDisable)(dGeomID geom); //int (ODE_API *dGeomIsEnabled)(dGeomID geom); //void (ODE_API *dGeomSetOffsetPosition)(dGeomID geom, dReal x, dReal y, dReal z); //void (ODE_API *dGeomSetOffsetRotation)(dGeomID geom, const dMatrix3 R); //void (ODE_API *dGeomSetOffsetQuaternion)(dGeomID geom, const dQuaternion Q); //void (ODE_API *dGeomSetOffsetWorldPosition)(dGeomID geom, dReal x, dReal y, dReal z); //void (ODE_API *dGeomSetOffsetWorldRotation)(dGeomID geom, const dMatrix3 R); //void (ODE_API *dGeomSetOffsetWorldQuaternion)(dGeomID geom, const dQuaternion); //void (ODE_API *dGeomClearOffset)(dGeomID geom); //int (ODE_API *dGeomIsOffset)(dGeomID geom); //const dReal * (ODE_API *dGeomGetOffsetPosition)(dGeomID geom); //void (ODE_API *dGeomCopyOffsetPosition)(dGeomID geom, dVector3 pos); //const dReal * (ODE_API *dGeomGetOffsetRotation)(dGeomID geom); //void (ODE_API *dGeomCopyOffsetRotation)(dGeomID geom, dMatrix3 R); //void (ODE_API *dGeomGetOffsetQuaternion)(dGeomID geom, dQuaternion result); int (ODE_API *dCollide)(dGeomID o1, dGeomID o2, int flags, dContactGeom *contact, int skip); // void (ODE_API *dSpaceCollide)(dSpaceID space, void *data, dNearCallback *callback); void (ODE_API *dSpaceCollide2)(dGeomID space1, dGeomID space2, void *data, dNearCallback *callback); // dGeomID (ODE_API *dCreateSphere)(dSpaceID space, dReal radius); //void (ODE_API *dGeomSphereSetRadius)(dGeomID sphere, dReal radius); //dReal (ODE_API *dGeomSphereGetRadius)(dGeomID sphere); //dReal (ODE_API *dGeomSpherePointDepth)(dGeomID sphere, dReal x, dReal y, dReal z); // dGeomID (ODE_API *dCreateConvex)(dSpaceID space, dReal *_planes, unsigned int _planecount, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); //void (ODE_API *dGeomSetConvex)(dGeomID g, dReal *_planes, unsigned int _count, dReal *_points, unsigned int _pointcount,unsigned int *_polygons); // dGeomID (ODE_API *dCreateBox)(dSpaceID space, dReal lx, dReal ly, dReal lz); //void (ODE_API *dGeomBoxSetLengths)(dGeomID box, dReal lx, dReal ly, dReal lz); //void (ODE_API *dGeomBoxGetLengths)(dGeomID box, dVector3 result); //dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); //dReal (ODE_API *dGeomBoxPointDepth)(dGeomID box, dReal x, dReal y, dReal z); // //dGeomID (ODE_API *dCreatePlane)(dSpaceID space, dReal a, dReal b, dReal c, dReal d); //void (ODE_API *dGeomPlaneSetParams)(dGeomID plane, dReal a, dReal b, dReal c, dReal d); //void (ODE_API *dGeomPlaneGetParams)(dGeomID plane, dVector4 result); //dReal (ODE_API *dGeomPlanePointDepth)(dGeomID plane, dReal x, dReal y, dReal z); // dGeomID (ODE_API *dCreateCapsule)(dSpaceID space, dReal radius, dReal length); //void (ODE_API *dGeomCapsuleSetParams)(dGeomID ccylinder, dReal radius, dReal length); //void (ODE_API *dGeomCapsuleGetParams)(dGeomID ccylinder, dReal *radius, dReal *length); //dReal (ODE_API *dGeomCapsulePointDepth)(dGeomID ccylinder, dReal x, dReal y, dReal z); // dGeomID (ODE_API *dCreateCylinder)(dSpaceID space, dReal radius, dReal length); //void (ODE_API *dGeomCylinderSetParams)(dGeomID cylinder, dReal radius, dReal length); //void (ODE_API *dGeomCylinderGetParams)(dGeomID cylinder, dReal *radius, dReal *length); // //dGeomID (ODE_API *dCreateRay)(dSpaceID space, dReal length); //void (ODE_API *dGeomRaySetLength)(dGeomID ray, dReal length); //dReal (ODE_API *dGeomRayGetLength)(dGeomID ray); //void (ODE_API *dGeomRaySet)(dGeomID ray, dReal px, dReal py, dReal pz, dReal dx, dReal dy, dReal dz); //void (ODE_API *dGeomRayGet)(dGeomID ray, dVector3 start, dVector3 dir); // dGeomID (ODE_API *dCreateGeomTransform)(dSpaceID space); void (ODE_API *dGeomTransformSetGeom)(dGeomID g, dGeomID obj); //dGeomID (ODE_API *dGeomTransformGetGeom)(dGeomID g); void (ODE_API *dGeomTransformSetCleanup)(dGeomID g, int mode); //int (ODE_API *dGeomTransformGetCleanup)(dGeomID g); //void (ODE_API *dGeomTransformSetInfo)(dGeomID g, int mode); //int (ODE_API *dGeomTransformGetInfo)(dGeomID g); enum { TRIMESH_FACE_NORMALS }; typedef int dTriCallback(dGeomID TriMesh, dGeomID RefObject, int TriangleIndex); typedef void dTriArrayCallback(dGeomID TriMesh, dGeomID RefObject, const int* TriIndices, int TriCount); typedef int dTriRayCallback(dGeomID TriMesh, dGeomID Ray, int TriangleIndex, dReal u, dReal v); typedef int dTriTriMergeCallback(dGeomID TriMesh, int FirstTriangleIndex, int SecondTriangleIndex); dTriMeshDataID (ODE_API *dGeomTriMeshDataCreate)(void); void (ODE_API *dGeomTriMeshDataDestroy)(dTriMeshDataID g); //void (ODE_API *dGeomTriMeshDataSet)(dTriMeshDataID g, int data_id, void* in_data); //void* (ODE_API *dGeomTriMeshDataGet)(dTriMeshDataID g, int data_id); //void (*dGeomTriMeshSetLastTransform)( (ODE_API *dGeomID g, dMatrix4 last_trans ); //dReal* (*dGeomTriMeshGetLastTransform)( (ODE_API *dGeomID g ); void (ODE_API *dGeomTriMeshDataBuildSingle)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); //void (ODE_API *dGeomTriMeshDataBuildSingle1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); //void (ODE_API *dGeomTriMeshDataBuildDouble)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride); //void (ODE_API *dGeomTriMeshDataBuildDouble1)(dTriMeshDataID g, const void* Vertices, int VertexStride, int VertexCount, const void* Indices, int IndexCount, int TriStride, const void* Normals); //void (ODE_API *dGeomTriMeshDataBuildSimple)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount); //void (ODE_API *dGeomTriMeshDataBuildSimple1)(dTriMeshDataID g, const dReal* Vertices, int VertexCount, const dTriIndex* Indices, int IndexCount, const int* Normals); //void (ODE_API *dGeomTriMeshDataPreprocess)(dTriMeshDataID g); //void (ODE_API *dGeomTriMeshDataGetBuffer)(dTriMeshDataID g, unsigned char** buf, int* bufLen); //void (ODE_API *dGeomTriMeshDataSetBuffer)(dTriMeshDataID g, unsigned char* buf); //void (ODE_API *dGeomTriMeshSetCallback)(dGeomID g, dTriCallback* Callback); //dTriCallback* (ODE_API *dGeomTriMeshGetCallback)(dGeomID g); //void (ODE_API *dGeomTriMeshSetArrayCallback)(dGeomID g, dTriArrayCallback* ArrayCallback); //dTriArrayCallback* (ODE_API *dGeomTriMeshGetArrayCallback)(dGeomID g); //void (ODE_API *dGeomTriMeshSetRayCallback)(dGeomID g, dTriRayCallback* Callback); //dTriRayCallback* (ODE_API *dGeomTriMeshGetRayCallback)(dGeomID g); //void (ODE_API *dGeomTriMeshSetTriMergeCallback)(dGeomID g, dTriTriMergeCallback* Callback); //dTriTriMergeCallback* (ODE_API *dGeomTriMeshGetTriMergeCallback)(dGeomID g); dGeomID (ODE_API *dCreateTriMesh)(dSpaceID space, dTriMeshDataID Data, dTriCallback* Callback, dTriArrayCallback* ArrayCallback, dTriRayCallback* RayCallback); //void (ODE_API *dGeomTriMeshSetData)(dGeomID g, dTriMeshDataID Data); //dTriMeshDataID (ODE_API *dGeomTriMeshGetData)(dGeomID g); //void (ODE_API *dGeomTriMeshEnableTC)(dGeomID g, int geomClass, int enable); //int (ODE_API *dGeomTriMeshIsTCEnabled)(dGeomID g, int geomClass); //void (ODE_API *dGeomTriMeshClearTCCache)(dGeomID g); //dTriMeshDataID (ODE_API *dGeomTriMeshGetTriMeshDataID)(dGeomID g); //void (ODE_API *dGeomTriMeshGetTriangle)(dGeomID g, int Index, dVector3* v0, dVector3* v1, dVector3* v2); //void (ODE_API *dGeomTriMeshGetPoint)(dGeomID g, int Index, dReal u, dReal v, dVector3 Out); //int (ODE_API *dGeomTriMeshGetTriangleCount )(dGeomID g); //void (ODE_API *dGeomTriMeshDataUpdate)(dTriMeshDataID g); static dllfunction_t odefuncs[] = { {"dGetConfiguration", (void **) &dGetConfiguration}, {"dCheckConfiguration", (void **) &dCheckConfiguration}, {"dInitODE", (void **) &dInitODE}, // {"dInitODE2", (void **) &dInitODE2}, // {"dAllocateODEDataForThread", (void **) &dAllocateODEDataForThread}, // {"dCleanupODEAllDataForThread", (void **) &dCleanupODEAllDataForThread}, {"dCloseODE", (void **) &dCloseODE}, // {"dMassCheck", (void **) &dMassCheck}, // {"dMassSetZero", (void **) &dMassSetZero}, // {"dMassSetParameters", (void **) &dMassSetParameters}, // {"dMassSetSphere", (void **) &dMassSetSphere}, {"dMassSetSphereTotal", (void **) &dMassSetSphereTotal}, // {"dMassSetCapsule", (void **) &dMassSetCapsule}, {"dMassSetCapsuleTotal", (void **) &dMassSetCapsuleTotal}, // {"dMassSetCylinder", (void **) &dMassSetCylinder}, {"dMassSetCylinderTotal", (void **) &dMassSetCylinderTotal}, // {"dMassSetBox", (void **) &dMassSetBox}, {"dMassSetBoxTotal", (void **) &dMassSetBoxTotal}, // {"dMassSetTrimesh", (void **) &dMassSetTrimesh}, // {"dMassSetTrimeshTotal", (void **) &dMassSetTrimeshTotal}, // {"dMassAdjust", (void **) &dMassAdjust}, // {"dMassTranslate", (void **) &dMassTranslate}, // {"dMassRotate", (void **) &dMassRotate}, // {"dMassAdd", (void **) &dMassAdd}, {"dWorldCreate", (void **) &dWorldCreate}, {"dWorldDestroy", (void **) &dWorldDestroy}, {"dWorldSetGravity", (void **) &dWorldSetGravity}, {"dWorldGetGravity", (void **) &dWorldGetGravity}, {"dWorldSetERP", (void **) &dWorldSetERP}, // {"dWorldGetERP", (void **) &dWorldGetERP}, {"dWorldSetCFM", (void **) &dWorldSetCFM}, // {"dWorldGetCFM", (void **) &dWorldGetCFM}, // {"dWorldStep", (void **) &dWorldStep}, // {"dWorldImpulseToForce", (void **) &dWorldImpulseToForce}, {"dWorldQuickStep", (void **) &dWorldQuickStep}, {"dWorldSetQuickStepNumIterations", (void **) &dWorldSetQuickStepNumIterations}, // {"dWorldGetQuickStepNumIterations", (void **) &dWorldGetQuickStepNumIterations}, // {"dWorldSetQuickStepW", (void **) &dWorldSetQuickStepW}, // {"dWorldGetQuickStepW", (void **) &dWorldGetQuickStepW}, // {"dWorldSetContactMaxCorrectingVel", (void **) &dWorldSetContactMaxCorrectingVel}, // {"dWorldGetContactMaxCorrectingVel", (void **) &dWorldGetContactMaxCorrectingVel}, {"dWorldSetContactSurfaceLayer", (void **) &dWorldSetContactSurfaceLayer}, // {"dWorldGetContactSurfaceLayer", (void **) &dWorldGetContactSurfaceLayer}, // {"dWorldStepFast1", (void **) &dWorldStepFast1}, // {"dWorldSetAutoEnableDepthSF1", (void **) &dWorldSetAutoEnableDepthSF1}, // {"dWorldGetAutoEnableDepthSF1", (void **) &dWorldGetAutoEnableDepthSF1}, // {"dWorldGetAutoDisableLinearThreshold", (void **) &dWorldGetAutoDisableLinearThreshold}, {"dWorldSetAutoDisableLinearThreshold", (void **) &dWorldSetAutoDisableLinearThreshold}, // {"dWorldGetAutoDisableAngularThreshold", (void **) &dWorldGetAutoDisableAngularThreshold}, {"dWorldSetAutoDisableAngularThreshold", (void **) &dWorldSetAutoDisableAngularThreshold}, // {"dWorldGetAutoDisableLinearAverageThreshold", (void **) &dWorldGetAutoDisableLinearAverageThreshold}, // {"dWorldSetAutoDisableLinearAverageThreshold", (void **) &dWorldSetAutoDisableLinearAverageThreshold}, // {"dWorldGetAutoDisableAngularAverageThreshold", (void **) &dWorldGetAutoDisableAngularAverageThreshold}, // {"dWorldSetAutoDisableAngularAverageThreshold", (void **) &dWorldSetAutoDisableAngularAverageThreshold}, // {"dWorldGetAutoDisableAverageSamplesCount", (void **) &dWorldGetAutoDisableAverageSamplesCount}, {"dWorldSetAutoDisableAverageSamplesCount", (void **) &dWorldSetAutoDisableAverageSamplesCount}, // {"dWorldGetAutoDisableSteps", (void **) &dWorldGetAutoDisableSteps}, {"dWorldSetAutoDisableSteps", (void **) &dWorldSetAutoDisableSteps}, // {"dWorldGetAutoDisableTime", (void **) &dWorldGetAutoDisableTime}, {"dWorldSetAutoDisableTime", (void **) &dWorldSetAutoDisableTime}, // {"dWorldGetAutoDisableFlag", (void **) &dWorldGetAutoDisableFlag}, {"dWorldSetAutoDisableFlag", (void **) &dWorldSetAutoDisableFlag}, // {"dWorldGetLinearDampingThreshold", (void **) &dWorldGetLinearDampingThreshold}, {"dWorldSetLinearDampingThreshold", (void **) &dWorldSetLinearDampingThreshold}, // {"dWorldGetAngularDampingThreshold", (void **) &dWorldGetAngularDampingThreshold}, {"dWorldSetAngularDampingThreshold", (void **) &dWorldSetAngularDampingThreshold}, // {"dWorldGetLinearDamping", (void **) &dWorldGetLinearDamping}, {"dWorldSetLinearDamping", (void **) &dWorldSetLinearDamping}, // {"dWorldGetAngularDamping", (void **) &dWorldGetAngularDamping}, {"dWorldSetAngularDamping", (void **) &dWorldSetAngularDamping}, // {"dWorldSetDamping", (void **) &dWorldSetDamping}, // {"dWorldGetMaxAngularSpeed", (void **) &dWorldGetMaxAngularSpeed}, // {"dWorldSetMaxAngularSpeed", (void **) &dWorldSetMaxAngularSpeed}, // {"dBodyGetAutoDisableLinearThreshold", (void **) &dBodyGetAutoDisableLinearThreshold}, // {"dBodySetAutoDisableLinearThreshold", (void **) &dBodySetAutoDisableLinearThreshold}, // {"dBodyGetAutoDisableAngularThreshold", (void **) &dBodyGetAutoDisableAngularThreshold}, // {"dBodySetAutoDisableAngularThreshold", (void **) &dBodySetAutoDisableAngularThreshold}, // {"dBodyGetAutoDisableAverageSamplesCount", (void **) &dBodyGetAutoDisableAverageSamplesCount}, // {"dBodySetAutoDisableAverageSamplesCount", (void **) &dBodySetAutoDisableAverageSamplesCount}, // {"dBodyGetAutoDisableSteps", (void **) &dBodyGetAutoDisableSteps}, // {"dBodySetAutoDisableSteps", (void **) &dBodySetAutoDisableSteps}, // {"dBodyGetAutoDisableTime", (void **) &dBodyGetAutoDisableTime}, // {"dBodySetAutoDisableTime", (void **) &dBodySetAutoDisableTime}, // {"dBodyGetAutoDisableFlag", (void **) &dBodyGetAutoDisableFlag}, // {"dBodySetAutoDisableFlag", (void **) &dBodySetAutoDisableFlag}, // {"dBodySetAutoDisableDefaults", (void **) &dBodySetAutoDisableDefaults}, // {"dBodyGetWorld", (void **) &dBodyGetWorld}, {"dBodyCreate", (void **) &dBodyCreate}, {"dBodyDestroy", (void **) &dBodyDestroy}, {"dBodySetData", (void **) &dBodySetData}, {"dBodyGetData", (void **) &dBodyGetData}, {"dBodySetPosition", (void **) &dBodySetPosition}, {"dBodySetRotation", (void **) &dBodySetRotation}, // {"dBodySetQuaternion", (void **) &dBodySetQuaternion}, {"dBodySetLinearVel", (void **) &dBodySetLinearVel}, {"dBodySetAngularVel", (void **) &dBodySetAngularVel}, {"dBodyGetPosition", (void **) &dBodyGetPosition}, // {"dBodyCopyPosition", (void **) &dBodyCopyPosition}, {"dBodyGetRotation", (void **) &dBodyGetRotation}, // {"dBodyCopyRotation", (void **) &dBodyCopyRotation}, // {"dBodyGetQuaternion", (void **) &dBodyGetQuaternion}, // {"dBodyCopyQuaternion", (void **) &dBodyCopyQuaternion}, {"dBodyGetLinearVel", (void **) &dBodyGetLinearVel}, {"dBodyGetAngularVel", (void **) &dBodyGetAngularVel}, {"dBodySetMass", (void **) &dBodySetMass}, // {"dBodyGetMass", (void **) &dBodyGetMass}, {"dBodyAddForce", (void **) &dBodyAddForce}, {"dBodyAddTorque", (void **) &dBodyAddTorque}, // {"dBodyAddRelForce", (void **) &dBodyAddRelForce}, // {"dBodyAddRelTorque", (void **) &dBodyAddRelTorque}, {"dBodyAddForceAtPos", (void **) &dBodyAddForceAtPos}, // {"dBodyAddForceAtRelPos", (void **) &dBodyAddForceAtRelPos}, // {"dBodyAddRelForceAtPos", (void **) &dBodyAddRelForceAtPos}, // {"dBodyAddRelForceAtRelPos", (void **) &dBodyAddRelForceAtRelPos}, // {"dBodyGetForce", (void **) &dBodyGetForce}, // {"dBodyGetTorque", (void **) &dBodyGetTorque}, // {"dBodySetForce", (void **) &dBodySetForce}, // {"dBodySetTorque", (void **) &dBodySetTorque}, // {"dBodyGetRelPointPos", (void **) &dBodyGetRelPointPos}, // {"dBodyGetRelPointVel", (void **) &dBodyGetRelPointVel}, // {"dBodyGetPointVel", (void **) &dBodyGetPointVel}, // {"dBodyGetPosRelPoint", (void **) &dBodyGetPosRelPoint}, // {"dBodyVectorToWorld", (void **) &dBodyVectorToWorld}, // {"dBodyVectorFromWorld", (void **) &dBodyVectorFromWorld}, // {"dBodySetFiniteRotationMode", (void **) &dBodySetFiniteRotationMode}, // {"dBodySetFiniteRotationAxis", (void **) &dBodySetFiniteRotationAxis}, // {"dBodyGetFiniteRotationMode", (void **) &dBodyGetFiniteRotationMode}, // {"dBodyGetFiniteRotationAxis", (void **) &dBodyGetFiniteRotationAxis}, {"dBodyGetNumJoints", (void **) &dBodyGetNumJoints}, {"dBodyGetJoint", (void **) &dBodyGetJoint}, // {"dBodySetDynamic", (void **) &dBodySetDynamic}, // {"dBodySetKinematic", (void **) &dBodySetKinematic}, // {"dBodyIsKinematic", (void **) &dBodyIsKinematic}, {"dBodyEnable", (void **) &dBodyEnable}, {"dBodyDisable", (void **) &dBodyDisable}, {"dBodyIsEnabled", (void **) &dBodyIsEnabled}, {"dBodySetGravityMode", (void **) &dBodySetGravityMode}, {"dBodyGetGravityMode", (void **) &dBodyGetGravityMode}, // {"dBodySetMovedCallback", (void **) &dBodySetMovedCallback}, // {"dBodyGetFirstGeom", (void **) &dBodyGetFirstGeom}, // {"dBodyGetNextGeom", (void **) &dBodyGetNextGeom}, // {"dBodySetDampingDefaults", (void **) &dBodySetDampingDefaults}, // {"dBodyGetLinearDamping", (void **) &dBodyGetLinearDamping}, // {"dBodySetLinearDamping", (void **) &dBodySetLinearDamping}, // {"dBodyGetAngularDamping", (void **) &dBodyGetAngularDamping}, // {"dBodySetAngularDamping", (void **) &dBodySetAngularDamping}, // {"dBodySetDamping", (void **) &dBodySetDamping}, // {"dBodyGetLinearDampingThreshold", (void **) &dBodyGetLinearDampingThreshold}, // {"dBodySetLinearDampingThreshold", (void **) &dBodySetLinearDampingThreshold}, // {"dBodyGetAngularDampingThreshold", (void **) &dBodyGetAngularDampingThreshold}, // {"dBodySetAngularDampingThreshold", (void **) &dBodySetAngularDampingThreshold}, // {"dBodyGetMaxAngularSpeed", (void **) &dBodyGetMaxAngularSpeed}, // {"dBodySetMaxAngularSpeed", (void **) &dBodySetMaxAngularSpeed}, // {"dBodyGetGyroscopicMode", (void **) &dBodyGetGyroscopicMode}, // {"dBodySetGyroscopicMode", (void **) &dBodySetGyroscopicMode}, {"dJointCreateBall", (void **) &dJointCreateBall}, {"dJointCreateHinge", (void **) &dJointCreateHinge}, {"dJointCreateSlider", (void **) &dJointCreateSlider}, {"dJointCreateContact", (void **) &dJointCreateContact}, {"dJointCreateHinge2", (void **) &dJointCreateHinge2}, {"dJointCreateUniversal", (void **) &dJointCreateUniversal}, // {"dJointCreatePR", (void **) &dJointCreatePR}, // {"dJointCreatePU", (void **) &dJointCreatePU}, // {"dJointCreatePiston", (void **) &dJointCreatePiston}, {"dJointCreateFixed", (void **) &dJointCreateFixed}, // {"dJointCreateNull", (void **) &dJointCreateNull}, // {"dJointCreateAMotor", (void **) &dJointCreateAMotor}, // {"dJointCreateLMotor", (void **) &dJointCreateLMotor}, // {"dJointCreatePlane2D", (void **) &dJointCreatePlane2D}, {"dJointDestroy", (void **) &dJointDestroy}, {"dJointGroupCreate", (void **) &dJointGroupCreate}, {"dJointGroupDestroy", (void **) &dJointGroupDestroy}, {"dJointGroupEmpty", (void **) &dJointGroupEmpty}, // {"dJointGetNumBodies", (void **) &dJointGetNumBodies}, {"dJointAttach", (void **) &dJointAttach}, // {"dJointEnable", (void **) &dJointEnable}, // {"dJointDisable", (void **) &dJointDisable}, // {"dJointIsEnabled", (void **) &dJointIsEnabled}, {"dJointSetData", (void **) &dJointSetData}, {"dJointGetData", (void **) &dJointGetData}, // {"dJointGetType", (void **) &dJointGetType}, {"dJointGetBody", (void **) &dJointGetBody}, // {"dJointSetFeedback", (void **) &dJointSetFeedback}, // {"dJointGetFeedback", (void **) &dJointGetFeedback}, {"dJointSetBallAnchor", (void **) &dJointSetBallAnchor}, // {"dJointSetBallAnchor2", (void **) &dJointSetBallAnchor2}, {"dJointSetBallParam", (void **) &dJointSetBallParam}, {"dJointSetHingeAnchor", (void **) &dJointSetHingeAnchor}, // {"dJointSetHingeAnchorDelta", (void **) &dJointSetHingeAnchorDelta}, {"dJointSetHingeAxis", (void **) &dJointSetHingeAxis}, // {"dJointSetHingeAxisOffset", (void **) &dJointSetHingeAxisOffset}, {"dJointSetHingeParam", (void **) &dJointSetHingeParam}, // {"dJointAddHingeTorque", (void **) &dJointAddHingeTorque}, {"dJointSetSliderAxis", (void **) &dJointSetSliderAxis}, // {"dJointSetSliderAxisDelta", (void **) &dJointSetSliderAxisDelta}, {"dJointSetSliderParam", (void **) &dJointSetSliderParam}, // {"dJointAddSliderForce", (void **) &dJointAddSliderForce}, {"dJointSetHinge2Anchor", (void **) &dJointSetHinge2Anchor}, {"dJointSetHinge2Axis1", (void **) &dJointSetHinge2Axis1}, {"dJointSetHinge2Axis2", (void **) &dJointSetHinge2Axis2}, {"dJointSetHinge2Param", (void **) &dJointSetHinge2Param}, // {"dJointAddHinge2Torques", (void **) &dJointAddHinge2Torques}, {"dJointSetUniversalAnchor", (void **) &dJointSetUniversalAnchor}, {"dJointSetUniversalAxis1", (void **) &dJointSetUniversalAxis1}, // {"dJointSetUniversalAxis1Offset", (void **) &dJointSetUniversalAxis1Offset}, {"dJointSetUniversalAxis2", (void **) &dJointSetUniversalAxis2}, // {"dJointSetUniversalAxis2Offset", (void **) &dJointSetUniversalAxis2Offset}, {"dJointSetUniversalParam", (void **) &dJointSetUniversalParam}, // {"dJointAddUniversalTorques", (void **) &dJointAddUniversalTorques}, // {"dJointSetPRAnchor", (void **) &dJointSetPRAnchor}, // {"dJointSetPRAxis1", (void **) &dJointSetPRAxis1}, // {"dJointSetPRAxis2", (void **) &dJointSetPRAxis2}, // {"dJointSetPRParam", (void **) &dJointSetPRParam}, // {"dJointAddPRTorque", (void **) &dJointAddPRTorque}, // {"dJointSetPUAnchor", (void **) &dJointSetPUAnchor}, // {"dJointSetPUAnchorOffset", (void **) &dJointSetPUAnchorOffset}, // {"dJointSetPUAxis1", (void **) &dJointSetPUAxis1}, // {"dJointSetPUAxis2", (void **) &dJointSetPUAxis2}, // {"dJointSetPUAxis3", (void **) &dJointSetPUAxis3}, // {"dJointSetPUAxisP", (void **) &dJointSetPUAxisP}, // {"dJointSetPUParam", (void **) &dJointSetPUParam}, // {"dJointAddPUTorque", (void **) &dJointAddPUTorque}, // {"dJointSetPistonAnchor", (void **) &dJointSetPistonAnchor}, // {"dJointSetPistonAnchorOffset", (void **) &dJointSetPistonAnchorOffset}, // {"dJointSetPistonParam", (void **) &dJointSetPistonParam}, // {"dJointAddPistonForce", (void **) &dJointAddPistonForce}, // {"dJointSetFixed", (void **) &dJointSetFixed}, // {"dJointSetFixedParam", (void **) &dJointSetFixedParam}, // {"dJointSetAMotorNumAxes", (void **) &dJointSetAMotorNumAxes}, // {"dJointSetAMotorAxis", (void **) &dJointSetAMotorAxis}, // {"dJointSetAMotorAngle", (void **) &dJointSetAMotorAngle}, // {"dJointSetAMotorParam", (void **) &dJointSetAMotorParam}, // {"dJointSetAMotorMode", (void **) &dJointSetAMotorMode}, // {"dJointAddAMotorTorques", (void **) &dJointAddAMotorTorques}, // {"dJointSetLMotorNumAxes", (void **) &dJointSetLMotorNumAxes}, // {"dJointSetLMotorAxis", (void **) &dJointSetLMotorAxis}, // {"dJointSetLMotorParam", (void **) &dJointSetLMotorParam}, // {"dJointSetPlane2DXParam", (void **) &dJointSetPlane2DXParam}, // {"dJointSetPlane2DYParam", (void **) &dJointSetPlane2DYParam}, // {"dJointSetPlane2DAngleParam", (void **) &dJointSetPlane2DAngleParam}, // {"dJointGetBallAnchor", (void **) &dJointGetBallAnchor}, // {"dJointGetBallAnchor2", (void **) &dJointGetBallAnchor2}, // {"dJointGetBallParam", (void **) &dJointGetBallParam}, // {"dJointGetHingeAnchor", (void **) &dJointGetHingeAnchor}, // {"dJointGetHingeAnchor2", (void **) &dJointGetHingeAnchor2}, // {"dJointGetHingeAxis", (void **) &dJointGetHingeAxis}, // {"dJointGetHingeParam", (void **) &dJointGetHingeParam}, // {"dJointGetHingeAngle", (void **) &dJointGetHingeAngle}, // {"dJointGetHingeAngleRate", (void **) &dJointGetHingeAngleRate}, // {"dJointGetSliderPosition", (void **) &dJointGetSliderPosition}, // {"dJointGetSliderPositionRate", (void **) &dJointGetSliderPositionRate}, // {"dJointGetSliderAxis", (void **) &dJointGetSliderAxis}, // {"dJointGetSliderParam", (void **) &dJointGetSliderParam}, // {"dJointGetHinge2Anchor", (void **) &dJointGetHinge2Anchor}, // {"dJointGetHinge2Anchor2", (void **) &dJointGetHinge2Anchor2}, // {"dJointGetHinge2Axis1", (void **) &dJointGetHinge2Axis1}, // {"dJointGetHinge2Axis2", (void **) &dJointGetHinge2Axis2}, // {"dJointGetHinge2Param", (void **) &dJointGetHinge2Param}, // {"dJointGetHinge2Angle1", (void **) &dJointGetHinge2Angle1}, // {"dJointGetHinge2Angle1Rate", (void **) &dJointGetHinge2Angle1Rate}, // {"dJointGetHinge2Angle2Rate", (void **) &dJointGetHinge2Angle2Rate}, // {"dJointGetUniversalAnchor", (void **) &dJointGetUniversalAnchor}, // {"dJointGetUniversalAnchor2", (void **) &dJointGetUniversalAnchor2}, // {"dJointGetUniversalAxis1", (void **) &dJointGetUniversalAxis1}, // {"dJointGetUniversalAxis2", (void **) &dJointGetUniversalAxis2}, // {"dJointGetUniversalParam", (void **) &dJointGetUniversalParam}, // {"dJointGetUniversalAngles", (void **) &dJointGetUniversalAngles}, // {"dJointGetUniversalAngle1", (void **) &dJointGetUniversalAngle1}, // {"dJointGetUniversalAngle2", (void **) &dJointGetUniversalAngle2}, // {"dJointGetUniversalAngle1Rate", (void **) &dJointGetUniversalAngle1Rate}, // {"dJointGetUniversalAngle2Rate", (void **) &dJointGetUniversalAngle2Rate}, // {"dJointGetPRAnchor", (void **) &dJointGetPRAnchor}, // {"dJointGetPRPosition", (void **) &dJointGetPRPosition}, // {"dJointGetPRPositionRate", (void **) &dJointGetPRPositionRate}, // {"dJointGetPRAngle", (void **) &dJointGetPRAngle}, // {"dJointGetPRAngleRate", (void **) &dJointGetPRAngleRate}, // {"dJointGetPRAxis1", (void **) &dJointGetPRAxis1}, // {"dJointGetPRAxis2", (void **) &dJointGetPRAxis2}, // {"dJointGetPRParam", (void **) &dJointGetPRParam}, // {"dJointGetPUAnchor", (void **) &dJointGetPUAnchor}, // {"dJointGetPUPosition", (void **) &dJointGetPUPosition}, // {"dJointGetPUPositionRate", (void **) &dJointGetPUPositionRate}, // {"dJointGetPUAxis1", (void **) &dJointGetPUAxis1}, // {"dJointGetPUAxis2", (void **) &dJointGetPUAxis2}, // {"dJointGetPUAxis3", (void **) &dJointGetPUAxis3}, // {"dJointGetPUAxisP", (void **) &dJointGetPUAxisP}, // {"dJointGetPUAngles", (void **) &dJointGetPUAngles}, // {"dJointGetPUAngle1", (void **) &dJointGetPUAngle1}, // {"dJointGetPUAngle1Rate", (void **) &dJointGetPUAngle1Rate}, // {"dJointGetPUAngle2", (void **) &dJointGetPUAngle2}, // {"dJointGetPUAngle2Rate", (void **) &dJointGetPUAngle2Rate}, // {"dJointGetPUParam", (void **) &dJointGetPUParam}, // {"dJointGetPistonPosition", (void **) &dJointGetPistonPosition}, // {"dJointGetPistonPositionRate", (void **) &dJointGetPistonPositionRate}, // {"dJointGetPistonAngle", (void **) &dJointGetPistonAngle}, // {"dJointGetPistonAngleRate", (void **) &dJointGetPistonAngleRate}, // {"dJointGetPistonAnchor", (void **) &dJointGetPistonAnchor}, // {"dJointGetPistonAnchor2", (void **) &dJointGetPistonAnchor2}, // {"dJointGetPistonAxis", (void **) &dJointGetPistonAxis}, // {"dJointGetPistonParam", (void **) &dJointGetPistonParam}, // {"dJointGetAMotorNumAxes", (void **) &dJointGetAMotorNumAxes}, // {"dJointGetAMotorAxis", (void **) &dJointGetAMotorAxis}, // {"dJointGetAMotorAxisRel", (void **) &dJointGetAMotorAxisRel}, // {"dJointGetAMotorAngle", (void **) &dJointGetAMotorAngle}, // {"dJointGetAMotorAngleRate", (void **) &dJointGetAMotorAngleRate}, // {"dJointGetAMotorParam", (void **) &dJointGetAMotorParam}, // {"dJointGetAMotorMode", (void **) &dJointGetAMotorMode}, // {"dJointGetLMotorNumAxes", (void **) &dJointGetLMotorNumAxes}, // {"dJointGetLMotorAxis", (void **) &dJointGetLMotorAxis}, // {"dJointGetLMotorParam", (void **) &dJointGetLMotorParam}, // {"dJointGetFixedParam", (void **) &dJointGetFixedParam}, // {"dConnectingJoint", (void **) &dConnectingJoint}, // {"dConnectingJointList", (void **) &dConnectingJointList}, {"dAreConnected", (void **) &dAreConnected}, {"dAreConnectedExcluding", (void **) &dAreConnectedExcluding}, {"dSimpleSpaceCreate", (void **) &dSimpleSpaceCreate}, {"dHashSpaceCreate", (void **) &dHashSpaceCreate}, {"dQuadTreeSpaceCreate", (void **) &dQuadTreeSpaceCreate}, // {"dSweepAndPruneSpaceCreate", (void **) &dSweepAndPruneSpaceCreate}, {"dSpaceDestroy", (void **) &dSpaceDestroy}, // {"dHashSpaceSetLevels", (void **) &dHashSpaceSetLevels}, // {"dHashSpaceGetLevels", (void **) &dHashSpaceGetLevels}, // {"dSpaceSetCleanup", (void **) &dSpaceSetCleanup}, // {"dSpaceGetCleanup", (void **) &dSpaceGetCleanup}, // {"dSpaceSetSublevel", (void **) &dSpaceSetSublevel}, // {"dSpaceGetSublevel", (void **) &dSpaceGetSublevel}, // {"dSpaceSetManualCleanup", (void **) &dSpaceSetManualCleanup}, // {"dSpaceGetManualCleanup", (void **) &dSpaceGetManualCleanup}, // {"dSpaceAdd", (void **) &dSpaceAdd}, // {"dSpaceRemove", (void **) &dSpaceRemove}, // {"dSpaceQuery", (void **) &dSpaceQuery}, // {"dSpaceClean", (void **) &dSpaceClean}, // {"dSpaceGetNumGeoms", (void **) &dSpaceGetNumGeoms}, // {"dSpaceGetGeom", (void **) &dSpaceGetGeom}, // {"dSpaceGetClass", (void **) &dSpaceGetClass}, {"dGeomDestroy", (void **) &dGeomDestroy}, {"dGeomSetData", (void **) &dGeomSetData}, {"dGeomGetData", (void **) &dGeomGetData}, {"dGeomSetBody", (void **) &dGeomSetBody}, {"dGeomGetBody", (void **) &dGeomGetBody}, {"dGeomSetPosition", (void **) &dGeomSetPosition}, {"dGeomSetRotation", (void **) &dGeomSetRotation}, // {"dGeomSetQuaternion", (void **) &dGeomSetQuaternion}, // {"dGeomGetPosition", (void **) &dGeomGetPosition}, // {"dGeomCopyPosition", (void **) &dGeomCopyPosition}, // {"dGeomGetRotation", (void **) &dGeomGetRotation}, // {"dGeomCopyRotation", (void **) &dGeomCopyRotation}, // {"dGeomGetQuaternion", (void **) &dGeomGetQuaternion}, // {"dGeomGetAABB", (void **) &dGeomGetAABB}, {"dGeomIsSpace", (void **) &dGeomIsSpace}, // {"dGeomGetSpace", (void **) &dGeomGetSpace}, // {"dGeomGetClass", (void **) &dGeomGetClass}, // {"dGeomSetCategoryBits", (void **) &dGeomSetCategoryBits}, // {"dGeomSetCollideBits", (void **) &dGeomSetCollideBits}, // {"dGeomGetCategoryBits", (void **) &dGeomGetCategoryBits}, // {"dGeomGetCollideBits", (void **) &dGeomGetCollideBits}, // {"dGeomEnable", (void **) &dGeomEnable}, // {"dGeomDisable", (void **) &dGeomDisable}, // {"dGeomIsEnabled", (void **) &dGeomIsEnabled}, // {"dGeomSetOffsetPosition", (void **) &dGeomSetOffsetPosition}, // {"dGeomSetOffsetRotation", (void **) &dGeomSetOffsetRotation}, // {"dGeomSetOffsetQuaternion", (void **) &dGeomSetOffsetQuaternion}, // {"dGeomSetOffsetWorldPosition", (void **) &dGeomSetOffsetWorldPosition}, // {"dGeomSetOffsetWorldRotation", (void **) &dGeomSetOffsetWorldRotation}, // {"dGeomSetOffsetWorldQuaternion", (void **) &dGeomSetOffsetWorldQuaternion}, // {"dGeomClearOffset", (void **) &dGeomClearOffset}, // {"dGeomIsOffset", (void **) &dGeomIsOffset}, // {"dGeomGetOffsetPosition", (void **) &dGeomGetOffsetPosition}, // {"dGeomCopyOffsetPosition", (void **) &dGeomCopyOffsetPosition}, // {"dGeomGetOffsetRotation", (void **) &dGeomGetOffsetRotation}, // {"dGeomCopyOffsetRotation", (void **) &dGeomCopyOffsetRotation}, // {"dGeomGetOffsetQuaternion", (void **) &dGeomGetOffsetQuaternion}, {"dCollide", (void **) &dCollide}, {"dSpaceCollide", (void **) &dSpaceCollide}, {"dSpaceCollide2", (void **) &dSpaceCollide2}, {"dCreateSphere", (void **) &dCreateSphere}, // {"dGeomSphereSetRadius", (void **) &dGeomSphereSetRadius}, // {"dGeomSphereGetRadius", (void **) &dGeomSphereGetRadius}, // {"dGeomSpherePointDepth", (void **) &dGeomSpherePointDepth}, {"dCreateConvex", (void **) &dCreateConvex}, // {"dGeomSetConvex", (void **) &dGeomSetConvex}, {"dCreateBox", (void **) &dCreateBox}, // {"dGeomBoxSetLengths", (void **) &dGeomBoxSetLengths}, // {"dGeomBoxGetLengths", (void **) &dGeomBoxGetLengths}, // {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, // {"dGeomBoxPointDepth", (void **) &dGeomBoxPointDepth}, // {"dCreatePlane", (void **) &dCreatePlane}, // {"dGeomPlaneSetParams", (void **) &dGeomPlaneSetParams}, // {"dGeomPlaneGetParams", (void **) &dGeomPlaneGetParams}, // {"dGeomPlanePointDepth", (void **) &dGeomPlanePointDepth}, {"dCreateCapsule", (void **) &dCreateCapsule}, // {"dGeomCapsuleSetParams", (void **) &dGeomCapsuleSetParams}, // {"dGeomCapsuleGetParams", (void **) &dGeomCapsuleGetParams}, // {"dGeomCapsulePointDepth", (void **) &dGeomCapsulePointDepth}, {"dCreateCylinder", (void **) &dCreateCylinder}, // {"dGeomCylinderSetParams", (void **) &dGeomCylinderSetParams}, // {"dGeomCylinderGetParams", (void **) &dGeomCylinderGetParams}, // {"dCreateRay", (void **) &dCreateRay}, // {"dGeomRaySetLength", (void **) &dGeomRaySetLength}, // {"dGeomRayGetLength", (void **) &dGeomRayGetLength}, // {"dGeomRaySet", (void **) &dGeomRaySet}, // {"dGeomRayGet", (void **) &dGeomRayGet}, {"dCreateGeomTransform", (void **) &dCreateGeomTransform}, {"dGeomTransformSetGeom", (void **) &dGeomTransformSetGeom}, // {"dGeomTransformGetGeom", (void **) &dGeomTransformGetGeom}, {"dGeomTransformSetCleanup", (void **) &dGeomTransformSetCleanup}, // {"dGeomTransformGetCleanup", (void **) &dGeomTransformGetCleanup}, // {"dGeomTransformSetInfo", (void **) &dGeomTransformSetInfo}, // {"dGeomTransformGetInfo", (void **) &dGeomTransformGetInfo}, {"dGeomTriMeshDataCreate", (void **) &dGeomTriMeshDataCreate}, {"dGeomTriMeshDataDestroy", (void **) &dGeomTriMeshDataDestroy}, // {"dGeomTriMeshDataSet", (void **) &dGeomTriMeshDataSet}, // {"dGeomTriMeshDataGet", (void **) &dGeomTriMeshDataGet}, // {"dGeomTriMeshSetLastTransform", (void **) &dGeomTriMeshSetLastTransform}, // {"dGeomTriMeshGetLastTransform", (void **) &dGeomTriMeshGetLastTransform}, {"dGeomTriMeshDataBuildSingle", (void **) &dGeomTriMeshDataBuildSingle}, // {"dGeomTriMeshDataBuildSingle1", (void **) &dGeomTriMeshDataBuildSingle1}, // {"dGeomTriMeshDataBuildDouble", (void **) &dGeomTriMeshDataBuildDouble}, // {"dGeomTriMeshDataBuildDouble1", (void **) &dGeomTriMeshDataBuildDouble1}, // {"dGeomTriMeshDataBuildSimple", (void **) &dGeomTriMeshDataBuildSimple}, // {"dGeomTriMeshDataBuildSimple1", (void **) &dGeomTriMeshDataBuildSimple1}, // {"dGeomTriMeshDataPreprocess", (void **) &dGeomTriMeshDataPreprocess}, // {"dGeomTriMeshDataGetBuffer", (void **) &dGeomTriMeshDataGetBuffer}, // {"dGeomTriMeshDataSetBuffer", (void **) &dGeomTriMeshDataSetBuffer}, // {"dGeomTriMeshSetCallback", (void **) &dGeomTriMeshSetCallback}, // {"dGeomTriMeshGetCallback", (void **) &dGeomTriMeshGetCallback}, // {"dGeomTriMeshSetArrayCallback", (void **) &dGeomTriMeshSetArrayCallback}, // {"dGeomTriMeshGetArrayCallback", (void **) &dGeomTriMeshGetArrayCallback}, // {"dGeomTriMeshSetRayCallback", (void **) &dGeomTriMeshSetRayCallback}, // {"dGeomTriMeshGetRayCallback", (void **) &dGeomTriMeshGetRayCallback}, // {"dGeomTriMeshSetTriMergeCallback", (void **) &dGeomTriMeshSetTriMergeCallback}, // {"dGeomTriMeshGetTriMergeCallback", (void **) &dGeomTriMeshGetTriMergeCallback}, {"dCreateTriMesh", (void **) &dCreateTriMesh}, // {"dGeomTriMeshSetData", (void **) &dGeomTriMeshSetData}, // {"dGeomTriMeshGetData", (void **) &dGeomTriMeshGetData}, // {"dGeomTriMeshEnableTC", (void **) &dGeomTriMeshEnableTC}, // {"dGeomTriMeshIsTCEnabled", (void **) &dGeomTriMeshIsTCEnabled}, // {"dGeomTriMeshClearTCCache", (void **) &dGeomTriMeshClearTCCache}, // {"dGeomTriMeshGetTriMeshDataID", (void **) &dGeomTriMeshGetTriMeshDataID}, // {"dGeomTriMeshGetTriangle", (void **) &dGeomTriMeshGetTriangle}, // {"dGeomTriMeshGetPoint", (void **) &dGeomTriMeshGetPoint}, // {"dGeomTriMeshGetTriangleCount", (void **) &dGeomTriMeshGetTriangleCount}, // {"dGeomTriMeshDataUpdate", (void **) &dGeomTriMeshDataUpdate}, {NULL, NULL} }; // Handle for ODE DLL dllhandle_t ode_dll = NULL; #endif #endif static void World_Physics_Init(void) { #ifdef USEODE #ifndef LINK_TO_LIBODE const char* dllnames [] = { # if defined(WIN32) "libode3.dll", "libode2.dll", "libode1.dll", # elif defined(MACOSX) "libode.3.dylib", "libode.2.dylib", "libode.1.dylib", # else "libode.so.3", "libode.so.2", "libode.so.1", # endif NULL }; #endif Cvar_RegisterVariable(&physics_ode_quadtree_depth); Cvar_RegisterVariable(&physics_ode_contactsurfacelayer); Cvar_RegisterVariable(&physics_ode_worldstep_iterations); Cvar_RegisterVariable(&physics_ode_contact_mu); Cvar_RegisterVariable(&physics_ode_contact_erp); Cvar_RegisterVariable(&physics_ode_contact_cfm); Cvar_RegisterVariable(&physics_ode_contact_maxpoints); Cvar_RegisterVariable(&physics_ode_world_erp); Cvar_RegisterVariable(&physics_ode_world_cfm); Cvar_RegisterVariable(&physics_ode_world_damping); Cvar_RegisterVariable(&physics_ode_world_damping_linear); Cvar_RegisterVariable(&physics_ode_world_damping_linear_threshold); Cvar_RegisterVariable(&physics_ode_world_damping_angular); Cvar_RegisterVariable(&physics_ode_world_damping_angular_threshold); Cvar_RegisterVariable(&physics_ode_world_gravitymod); Cvar_RegisterVariable(&physics_ode_iterationsperframe); Cvar_RegisterVariable(&physics_ode_constantstep); Cvar_RegisterVariable(&physics_ode_movelimit); Cvar_RegisterVariable(&physics_ode_spinlimit); Cvar_RegisterVariable(&physics_ode_trick_fixnan); Cvar_RegisterVariable(&physics_ode_autodisable); Cvar_RegisterVariable(&physics_ode_autodisable_steps); Cvar_RegisterVariable(&physics_ode_autodisable_time); Cvar_RegisterVariable(&physics_ode_autodisable_threshold_linear); Cvar_RegisterVariable(&physics_ode_autodisable_threshold_angular); Cvar_RegisterVariable(&physics_ode_autodisable_threshold_samples); Cvar_RegisterVariable(&physics_ode_printstats); Cvar_RegisterVariable(&physics_ode_allowconvex); Cvar_RegisterVariable(&physics_ode); #ifndef LINK_TO_LIBODE // Load the DLL if (Sys_LoadLibrary (dllnames, &ode_dll, odefuncs)) #endif { dInitODE(); // dInitODE2(0); #ifndef LINK_TO_LIBODE # ifdef dSINGLE if (!dCheckConfiguration("ODE_single_precision")) # else if (!dCheckConfiguration("ODE_double_precision")) # endif { # ifdef dSINGLE Con_Printf("ODE library not compiled for single precision - incompatible! Not using ODE physics.\n"); # else Con_Printf("ODE library not compiled for double precision - incompatible! Not using ODE physics.\n"); # endif Sys_UnloadLibrary(&ode_dll); ode_dll = NULL; } else { # ifdef dSINGLE Con_Printf("ODE library loaded with single precision.\n"); # else Con_Printf("ODE library loaded with double precision.\n"); # endif Con_Printf("ODE configuration list: %s\n", dGetConfiguration()); } #endif } #endif } static void World_Physics_Shutdown(void) { #ifdef USEODE #ifndef LINK_TO_LIBODE if (ode_dll) #endif { dCloseODE(); #ifndef LINK_TO_LIBODE Sys_UnloadLibrary(&ode_dll); ode_dll = NULL; #endif } #endif } #ifdef USEODE static void World_Physics_UpdateODE(world_t *world) { dWorldID odeworld; odeworld = (dWorldID)world->physics.ode_world; // ERP and CFM if (physics_ode_world_erp.value >= 0) dWorldSetERP(odeworld, physics_ode_world_erp.value); if (physics_ode_world_cfm.value >= 0) dWorldSetCFM(odeworld, physics_ode_world_cfm.value); // Damping if (physics_ode_world_damping.integer) { dWorldSetLinearDamping(odeworld, (physics_ode_world_damping_linear.value >= 0) ? (physics_ode_world_damping_linear.value * physics_ode_world_damping.value) : 0); dWorldSetLinearDampingThreshold(odeworld, (physics_ode_world_damping_linear_threshold.value >= 0) ? (physics_ode_world_damping_linear_threshold.value * physics_ode_world_damping.value) : 0); dWorldSetAngularDamping(odeworld, (physics_ode_world_damping_angular.value >= 0) ? (physics_ode_world_damping_angular.value * physics_ode_world_damping.value) : 0); dWorldSetAngularDampingThreshold(odeworld, (physics_ode_world_damping_angular_threshold.value >= 0) ? (physics_ode_world_damping_angular_threshold.value * physics_ode_world_damping.value) : 0); } else { dWorldSetLinearDamping(odeworld, 0); dWorldSetLinearDampingThreshold(odeworld, 0); dWorldSetAngularDamping(odeworld, 0); dWorldSetAngularDampingThreshold(odeworld, 0); } // Autodisable dWorldSetAutoDisableFlag(odeworld, (physics_ode_autodisable.integer) ? 1 : 0); if (physics_ode_autodisable.integer) { dWorldSetAutoDisableSteps(odeworld, bound(1, physics_ode_autodisable_steps.integer, 100)); dWorldSetAutoDisableTime(odeworld, physics_ode_autodisable_time.value); dWorldSetAutoDisableAverageSamplesCount(odeworld, bound(1, physics_ode_autodisable_threshold_samples.integer, 100)); dWorldSetAutoDisableLinearThreshold(odeworld, physics_ode_autodisable_threshold_linear.value); dWorldSetAutoDisableAngularThreshold(odeworld, physics_ode_autodisable_threshold_angular.value); } } static void World_Physics_EnableODE(world_t *world) { dVector3 center, extents; if (world->physics.ode) return; #ifndef LINK_TO_LIBODE if (!ode_dll) return; #endif world->physics.ode = true; VectorMAM(0.5f, world->mins, 0.5f, world->maxs, center); VectorSubtract(world->maxs, center, extents); world->physics.ode_world = dWorldCreate(); world->physics.ode_space = dQuadTreeSpaceCreate(NULL, center, extents, bound(1, physics_ode_quadtree_depth.integer, 10)); world->physics.ode_contactgroup = dJointGroupCreate(0); World_Physics_UpdateODE(world); } #endif static void World_Physics_Start(world_t *world) { #ifdef USEODE if (world->physics.ode) return; World_Physics_EnableODE(world); #endif } static void World_Physics_End(world_t *world) { #ifdef USEODE if (world->physics.ode) { dWorldDestroy((dWorldID)world->physics.ode_world); dSpaceDestroy((dSpaceID)world->physics.ode_space); dJointGroupDestroy((dJointGroupID)world->physics.ode_contactgroup); world->physics.ode = false; } #endif } void World_Physics_RemoveJointFromEntity(world_t *world, prvm_edict_t *ed) { ed->priv.server->ode_joint_type = 0; #ifdef USEODE if(ed->priv.server->ode_joint) dJointDestroy((dJointID)ed->priv.server->ode_joint); ed->priv.server->ode_joint = NULL; #endif } void World_Physics_RemoveFromEntity(world_t *world, prvm_edict_t *ed) { edict_odefunc_t *f, *nf; // entity is not physics controlled, free any physics data ed->priv.server->ode_physics = false; #ifdef USEODE if (ed->priv.server->ode_geom) dGeomDestroy((dGeomID)ed->priv.server->ode_geom); ed->priv.server->ode_geom = NULL; if (ed->priv.server->ode_body) { dJointID j; dBodyID b1, b2; prvm_edict_t *ed2; while(dBodyGetNumJoints((dBodyID)ed->priv.server->ode_body)) { j = dBodyGetJoint((dBodyID)ed->priv.server->ode_body, 0); ed2 = (prvm_edict_t *) dJointGetData(j); b1 = dJointGetBody(j, 0); b2 = dJointGetBody(j, 1); if(b1 == (dBodyID)ed->priv.server->ode_body) { b1 = 0; ed2->priv.server->ode_joint_enemy = 0; } if(b2 == (dBodyID)ed->priv.server->ode_body) { b2 = 0; ed2->priv.server->ode_joint_aiment = 0; } dJointAttach(j, b1, b2); } dBodyDestroy((dBodyID)ed->priv.server->ode_body); } ed->priv.server->ode_body = NULL; #endif if (ed->priv.server->ode_vertex3f) Mem_Free(ed->priv.server->ode_vertex3f); ed->priv.server->ode_vertex3f = NULL; ed->priv.server->ode_numvertices = 0; if (ed->priv.server->ode_element3i) Mem_Free(ed->priv.server->ode_element3i); ed->priv.server->ode_element3i = NULL; ed->priv.server->ode_numtriangles = 0; if(ed->priv.server->ode_massbuf) Mem_Free(ed->priv.server->ode_massbuf); ed->priv.server->ode_massbuf = NULL; // clear functions stack for(f = ed->priv.server->ode_func; f; f = nf) { nf = f->next; Mem_Free(f); } ed->priv.server->ode_func = NULL; } void World_Physics_ApplyCmd(prvm_edict_t *ed, edict_odefunc_t *f) { #ifdef USEODE dBodyID body = (dBodyID)ed->priv.server->ode_body; switch(f->type) { case ODEFUNC_ENABLE: dBodyEnable(body); break; case ODEFUNC_DISABLE: dBodyDisable(body); break; case ODEFUNC_FORCE: dBodyEnable(body); dBodyAddForceAtPos(body, f->v1[0], f->v1[1], f->v1[2], f->v2[0], f->v2[1], f->v2[2]); break; case ODEFUNC_TORQUE: dBodyEnable(body); dBodyAddTorque(body, f->v1[0], f->v1[1], f->v1[2]); break; default: break; } #endif } #ifdef USEODE static void World_Physics_Frame_BodyToEntity(world_t *world, prvm_edict_t *ed) { prvm_prog_t *prog = world->prog; const dReal *avel; const dReal *o; const dReal *r; // for some reason dBodyGetRotation returns a [3][4] matrix const dReal *vel; dBodyID body = (dBodyID)ed->priv.server->ode_body; int movetype; matrix4x4_t bodymatrix; matrix4x4_t entitymatrix; vec3_t angles; vec3_t avelocity; vec3_t forward, left, up; vec3_t origin; vec3_t spinvelocity; vec3_t velocity; int jointtype; if (!body) return; movetype = (int)PRVM_gameedictfloat(ed, movetype); if (movetype != MOVETYPE_PHYSICS) { jointtype = (int)PRVM_gameedictfloat(ed, jointtype); switch(jointtype) { // TODO feed back data from physics case JOINTTYPE_POINT: break; case JOINTTYPE_HINGE: break; case JOINTTYPE_SLIDER: break; case JOINTTYPE_UNIVERSAL: break; case JOINTTYPE_HINGE2: break; case JOINTTYPE_FIXED: break; } return; } // store the physics engine data into the entity o = dBodyGetPosition(body); r = dBodyGetRotation(body); vel = dBodyGetLinearVel(body); avel = dBodyGetAngularVel(body); VectorCopy(o, origin); forward[0] = r[0]; forward[1] = r[4]; forward[2] = r[8]; left[0] = r[1]; left[1] = r[5]; left[2] = r[9]; up[0] = r[2]; up[1] = r[6]; up[2] = r[10]; VectorCopy(vel, velocity); VectorCopy(avel, spinvelocity); Matrix4x4_FromVectors(&bodymatrix, forward, left, up, origin); Matrix4x4_Concat(&entitymatrix, &bodymatrix, &ed->priv.server->ode_offsetimatrix); Matrix4x4_ToVectors(&entitymatrix, forward, left, up, origin); AnglesFromVectors(angles, forward, up, false); VectorSet(avelocity, RAD2DEG(spinvelocity[PITCH]), RAD2DEG(spinvelocity[ROLL]), RAD2DEG(spinvelocity[YAW])); { float pitchsign = 1; if(prog == SVVM_prog) // FIXME some better way? { pitchsign = SV_GetPitchSign(prog, ed); } else if(prog == CLVM_prog) { pitchsign = CL_GetPitchSign(prog, ed); } angles[PITCH] *= pitchsign; avelocity[PITCH] *= pitchsign; } VectorCopy(origin, PRVM_gameedictvector(ed, origin)); VectorCopy(velocity, PRVM_gameedictvector(ed, velocity)); //VectorCopy(forward, PRVM_gameedictvector(ed, axis_forward)); //VectorCopy(left, PRVM_gameedictvector(ed, axis_left)); //VectorCopy(up, PRVM_gameedictvector(ed, axis_up)); //VectorCopy(spinvelocity, PRVM_gameedictvector(ed, spinvelocity)); VectorCopy(angles, PRVM_gameedictvector(ed, angles)); VectorCopy(avelocity, PRVM_gameedictvector(ed, avelocity)); // values for BodyFromEntity to check if the qc modified anything later VectorCopy(origin, ed->priv.server->ode_origin); VectorCopy(velocity, ed->priv.server->ode_velocity); VectorCopy(angles, ed->priv.server->ode_angles); VectorCopy(avelocity, ed->priv.server->ode_avelocity); ed->priv.server->ode_gravity = dBodyGetGravityMode(body) != 0; if(prog == SVVM_prog) // FIXME some better way? { SV_LinkEdict(ed); SV_LinkEdict_TouchAreaGrid(ed); } } static void World_Physics_Frame_ForceFromEntity(world_t *world, prvm_edict_t *ed) { prvm_prog_t *prog = world->prog; int forcetype = 0, movetype = 0, enemy = 0; vec3_t movedir, origin; movetype = (int)PRVM_gameedictfloat(ed, movetype); forcetype = (int)PRVM_gameedictfloat(ed, forcetype); if (movetype == MOVETYPE_PHYSICS) forcetype = FORCETYPE_NONE; // can't have both if (!forcetype) return; enemy = PRVM_gameedictedict(ed, enemy); if (enemy <= 0 || enemy >= prog->num_edicts || prog->edicts[enemy].priv.required->free || prog->edicts[enemy].priv.server->ode_body == 0) return; VectorCopy(PRVM_gameedictvector(ed, movedir), movedir); VectorCopy(PRVM_gameedictvector(ed, origin), origin); dBodyEnable((dBodyID)prog->edicts[enemy].priv.server->ode_body); switch(forcetype) { case FORCETYPE_FORCE: if (movedir[0] || movedir[1] || movedir[2]) dBodyAddForce((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2]); break; case FORCETYPE_FORCEATPOS: if (movedir[0] || movedir[1] || movedir[2]) dBodyAddForceAtPos((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2], origin[0], origin[1], origin[2]); break; case FORCETYPE_TORQUE: if (movedir[0] || movedir[1] || movedir[2]) dBodyAddTorque((dBodyID)prog->edicts[enemy].priv.server->ode_body, movedir[0], movedir[1], movedir[2]); break; case FORCETYPE_NONE: default: // bad force break; } } static void World_Physics_Frame_JointFromEntity(world_t *world, prvm_edict_t *ed) { prvm_prog_t *prog = world->prog; dJointID j = 0; dBodyID b1 = 0; dBodyID b2 = 0; int movetype = 0; int jointtype = 0; int enemy = 0, aiment = 0; vec3_t origin, velocity, angles, forward, left, up, movedir; vec_t CFM, ERP, FMax, Stop, Vel; movetype = (int)PRVM_gameedictfloat(ed, movetype); jointtype = (int)PRVM_gameedictfloat(ed, jointtype); VectorClear(origin); VectorClear(velocity); VectorClear(angles); VectorClear(movedir); enemy = PRVM_gameedictedict(ed, enemy); aiment = PRVM_gameedictedict(ed, aiment); VectorCopy(PRVM_gameedictvector(ed, origin), origin); VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); VectorCopy(PRVM_gameedictvector(ed, angles), angles); VectorCopy(PRVM_gameedictvector(ed, movedir), movedir); if(movetype == MOVETYPE_PHYSICS) jointtype = JOINTTYPE_NONE; // can't have both if(enemy <= 0 || enemy >= prog->num_edicts || prog->edicts[enemy].priv.required->free || prog->edicts[enemy].priv.server->ode_body == 0) enemy = 0; if(aiment <= 0 || aiment >= prog->num_edicts || prog->edicts[aiment].priv.required->free || prog->edicts[aiment].priv.server->ode_body == 0) aiment = 0; // see http://www.ode.org/old_list_archives/2006-January/017614.html // we want to set ERP? make it fps independent and work like a spring constant // note: if movedir[2] is 0, it becomes ERP = 1, CFM = 1.0 / (H * K) if(movedir[0] > 0 && movedir[1] > 0) { float K = movedir[0]; float D = movedir[1]; float R = 2.0 * D * sqrt(K); // we assume D is premultiplied by sqrt(sprungMass) CFM = 1.0 / (world->physics.ode_step * K + R); // always > 0 ERP = world->physics.ode_step * K * CFM; Vel = 0; FMax = 0; Stop = movedir[2]; } else if(movedir[1] < 0) { CFM = 0; ERP = 0; Vel = movedir[0]; FMax = -movedir[1]; // TODO do we need to multiply with world.physics.ode_step? Stop = movedir[2] > 0 ? movedir[2] : dInfinity; } else // movedir[0] > 0, movedir[1] == 0 or movedir[0] < 0, movedir[1] >= 0 { CFM = 0; ERP = 0; Vel = 0; FMax = 0; Stop = dInfinity; } if(jointtype == ed->priv.server->ode_joint_type && VectorCompare(origin, ed->priv.server->ode_joint_origin) && VectorCompare(velocity, ed->priv.server->ode_joint_velocity) && VectorCompare(angles, ed->priv.server->ode_joint_angles) && enemy == ed->priv.server->ode_joint_enemy && aiment == ed->priv.server->ode_joint_aiment && VectorCompare(movedir, ed->priv.server->ode_joint_movedir)) return; // nothing to do AngleVectorsFLU(angles, forward, left, up); switch(jointtype) { case JOINTTYPE_POINT: j = dJointCreateBall((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_HINGE: j = dJointCreateHinge((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_SLIDER: j = dJointCreateSlider((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_UNIVERSAL: j = dJointCreateUniversal((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_HINGE2: j = dJointCreateHinge2((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_FIXED: j = dJointCreateFixed((dWorldID)world->physics.ode_world, 0); break; case JOINTTYPE_NONE: default: // no joint j = 0; break; } if(ed->priv.server->ode_joint) { //Con_Printf("deleted old joint %i\n", (int) (ed - prog->edicts)); dJointAttach((dJointID)ed->priv.server->ode_joint, 0, 0); dJointDestroy((dJointID)ed->priv.server->ode_joint); } ed->priv.server->ode_joint = (void *) j; ed->priv.server->ode_joint_type = jointtype; ed->priv.server->ode_joint_enemy = enemy; ed->priv.server->ode_joint_aiment = aiment; VectorCopy(origin, ed->priv.server->ode_joint_origin); VectorCopy(velocity, ed->priv.server->ode_joint_velocity); VectorCopy(angles, ed->priv.server->ode_joint_angles); VectorCopy(movedir, ed->priv.server->ode_joint_movedir); if(j) { //Con_Printf("made new joint %i\n", (int) (ed - prog->edicts)); dJointSetData(j, (void *) ed); if(enemy) b1 = (dBodyID)prog->edicts[enemy].priv.server->ode_body; if(aiment) b2 = (dBodyID)prog->edicts[aiment].priv.server->ode_body; dJointAttach(j, b1, b2); switch(jointtype) { case JOINTTYPE_POINT: dJointSetBallAnchor(j, origin[0], origin[1], origin[2]); break; case JOINTTYPE_HINGE: dJointSetHingeAnchor(j, origin[0], origin[1], origin[2]); dJointSetHingeAxis(j, forward[0], forward[1], forward[2]); dJointSetHingeParam(j, dParamFMax, FMax); dJointSetHingeParam(j, dParamHiStop, Stop); dJointSetHingeParam(j, dParamLoStop, -Stop); dJointSetHingeParam(j, dParamStopCFM, CFM); dJointSetHingeParam(j, dParamStopERP, ERP); dJointSetHingeParam(j, dParamVel, Vel); break; case JOINTTYPE_SLIDER: dJointSetSliderAxis(j, forward[0], forward[1], forward[2]); dJointSetSliderParam(j, dParamFMax, FMax); dJointSetSliderParam(j, dParamHiStop, Stop); dJointSetSliderParam(j, dParamLoStop, -Stop); dJointSetSliderParam(j, dParamStopCFM, CFM); dJointSetSliderParam(j, dParamStopERP, ERP); dJointSetSliderParam(j, dParamVel, Vel); break; case JOINTTYPE_UNIVERSAL: dJointSetUniversalAnchor(j, origin[0], origin[1], origin[2]); dJointSetUniversalAxis1(j, forward[0], forward[1], forward[2]); dJointSetUniversalAxis2(j, up[0], up[1], up[2]); dJointSetUniversalParam(j, dParamFMax, FMax); dJointSetUniversalParam(j, dParamHiStop, Stop); dJointSetUniversalParam(j, dParamLoStop, -Stop); dJointSetUniversalParam(j, dParamStopCFM, CFM); dJointSetUniversalParam(j, dParamStopERP, ERP); dJointSetUniversalParam(j, dParamVel, Vel); dJointSetUniversalParam(j, dParamFMax2, FMax); dJointSetUniversalParam(j, dParamHiStop2, Stop); dJointSetUniversalParam(j, dParamLoStop2, -Stop); dJointSetUniversalParam(j, dParamStopCFM2, CFM); dJointSetUniversalParam(j, dParamStopERP2, ERP); dJointSetUniversalParam(j, dParamVel2, Vel); break; case JOINTTYPE_HINGE2: dJointSetHinge2Anchor(j, origin[0], origin[1], origin[2]); dJointSetHinge2Axis1(j, forward[0], forward[1], forward[2]); dJointSetHinge2Axis2(j, velocity[0], velocity[1], velocity[2]); dJointSetHinge2Param(j, dParamFMax, FMax); dJointSetHinge2Param(j, dParamHiStop, Stop); dJointSetHinge2Param(j, dParamLoStop, -Stop); dJointSetHinge2Param(j, dParamStopCFM, CFM); dJointSetHinge2Param(j, dParamStopERP, ERP); dJointSetHinge2Param(j, dParamVel, Vel); dJointSetHinge2Param(j, dParamFMax2, FMax); dJointSetHinge2Param(j, dParamHiStop2, Stop); dJointSetHinge2Param(j, dParamLoStop2, -Stop); dJointSetHinge2Param(j, dParamStopCFM2, CFM); dJointSetHinge2Param(j, dParamStopERP2, ERP); dJointSetHinge2Param(j, dParamVel2, Vel); break; case JOINTTYPE_FIXED: break; case 0: default: Sys_Error("what? but above the joint was valid...\n"); break; } #undef SETPARAMS } } // test convex geometry data // planes for a cube, these should coincide with the dReal test_convex_planes[] = { 1.0f ,0.0f ,0.0f ,2.25f, 0.0f ,1.0f ,0.0f ,2.25f, 0.0f ,0.0f ,1.0f ,2.25f, -1.0f,0.0f ,0.0f ,2.25f, 0.0f ,-1.0f,0.0f ,2.25f, 0.0f ,0.0f ,-1.0f,2.25f }; const unsigned int test_convex_planecount = 6; // points for a cube dReal test_convex_points[] = { 2.25f,2.25f,2.25f, // point 0 -2.25f,2.25f,2.25f, // point 1 2.25f,-2.25f,2.25f, // point 2 -2.25f,-2.25f,2.25f, // point 3 2.25f,2.25f,-2.25f, // point 4 -2.25f,2.25f,-2.25f, // point 5 2.25f,-2.25f,-2.25f, // point 6 -2.25f,-2.25f,-2.25f, // point 7 }; const unsigned int test_convex_pointcount = 8; // polygons for a cube (6 squares), index unsigned int test_convex_polygons[] = { 4,0,2,6,4, // positive X 4,1,0,4,5, // positive Y 4,0,1,3,2, // positive Z 4,3,1,5,7, // negative X 4,2,3,7,6, // negative Y 4,5,4,6,7, // negative Z }; static void World_Physics_Frame_BodyFromEntity(world_t *world, prvm_edict_t *ed) { prvm_prog_t *prog = world->prog; const float *iv; const int *ie; dBodyID body; dMass mass; const dReal *ovelocity, *ospinvelocity; void *dataID; dp_model_t *model; float *ov; int *oe; int axisindex; int modelindex = 0; int movetype = MOVETYPE_NONE; int numtriangles; int numvertices; int solid = SOLID_NOT, geomtype = 0; int triangleindex; int vertexindex; mempool_t *mempool; qboolean modified = false; vec3_t angles; vec3_t avelocity; vec3_t entmaxs; vec3_t entmins; vec3_t forward; vec3_t geomcenter; vec3_t geomsize; vec3_t left; vec3_t origin; vec3_t spinvelocity; vec3_t up; vec3_t velocity; vec_t f; vec_t length; vec_t massval = 1.0f; vec_t movelimit; vec_t radius; vec3_t scale; vec_t spinlimit; vec_t test; qboolean gravity; qboolean geom_modified = false; edict_odefunc_t *func, *nextf; dReal *planes, *planesData, *pointsData; unsigned int *polygons, *polygonsData, polyvert; qboolean *mapped, *used, convex_compatible; int numplanes = 0, numpoints = 0, i; #ifndef LINK_TO_LIBODE if (!ode_dll) return; #endif VectorClear(entmins); VectorClear(entmaxs); solid = (int)PRVM_gameedictfloat(ed, solid); geomtype = (int)PRVM_gameedictfloat(ed, geomtype); movetype = (int)PRVM_gameedictfloat(ed, movetype); // support scale and q3map/radiant's modelscale_vec if (PRVM_gameedictvector(ed, modelscale_vec)[0] != 0.0 || PRVM_gameedictvector(ed, modelscale_vec)[1] != 0.0 || PRVM_gameedictvector(ed, modelscale_vec)[2] != 0.0) VectorCopy(PRVM_gameedictvector(ed, modelscale_vec), scale); else if (PRVM_gameedictfloat(ed, scale)) VectorSet(scale, PRVM_gameedictfloat(ed, scale), PRVM_gameedictfloat(ed, scale), PRVM_gameedictfloat(ed, scale)); else VectorSet(scale, 1.0f, 1.0f, 1.0f); modelindex = 0; if (PRVM_gameedictfloat(ed, mass)) massval = PRVM_gameedictfloat(ed, mass); if (movetype != MOVETYPE_PHYSICS) massval = 1.0f; mempool = prog->progs_mempool; model = NULL; if (!geomtype) { // VorteX: keep support for deprecated solid fields to not break mods if (solid == SOLID_PHYSICS_TRIMESH || solid == SOLID_BSP) geomtype = GEOMTYPE_TRIMESH; else if (solid == SOLID_NOT || solid == SOLID_TRIGGER) geomtype = GEOMTYPE_NONE; else if (solid == SOLID_PHYSICS_SPHERE) geomtype = GEOMTYPE_SPHERE; else if (solid == SOLID_PHYSICS_CAPSULE) geomtype = GEOMTYPE_CAPSULE; else if (solid == SOLID_PHYSICS_CYLINDER) geomtype = GEOMTYPE_CYLINDER; else if (solid == SOLID_PHYSICS_BOX) geomtype = GEOMTYPE_BOX; else geomtype = GEOMTYPE_BOX; } if (geomtype == GEOMTYPE_TRIMESH) { modelindex = (int)PRVM_gameedictfloat(ed, modelindex); if (world == &sv.world) model = SV_GetModelByIndex(modelindex); else if (world == &cl.world) model = CL_GetModelByIndex(modelindex); else model = NULL; if (model) { entmins[0] = model->normalmins[0] * scale[0]; entmins[1] = model->normalmins[1] * scale[1]; entmins[2] = model->normalmins[2] * scale[2]; entmaxs[0] = model->normalmaxs[0] * scale[0]; entmaxs[1] = model->normalmaxs[1] * scale[1]; entmaxs[2] = model->normalmaxs[2] * scale[2]; geom_modified = !VectorCompare(ed->priv.server->ode_scale, scale) || ed->priv.server->ode_modelindex != modelindex; } else { Con_Printf("entity %i (classname %s) has no model\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); geomtype = GEOMTYPE_BOX; VectorCopy(PRVM_gameedictvector(ed, mins), entmins); VectorCopy(PRVM_gameedictvector(ed, maxs), entmaxs); modelindex = 0; geom_modified = !VectorCompare(ed->priv.server->ode_mins, entmins) || !VectorCompare(ed->priv.server->ode_maxs, entmaxs); } } else if (geomtype && geomtype != GEOMTYPE_NONE) { VectorCopy(PRVM_gameedictvector(ed, mins), entmins); VectorCopy(PRVM_gameedictvector(ed, maxs), entmaxs); geom_modified = !VectorCompare(ed->priv.server->ode_mins, entmins) || !VectorCompare(ed->priv.server->ode_maxs, entmaxs); } else { // geometry type not set, falling back if (ed->priv.server->ode_physics) World_Physics_RemoveFromEntity(world, ed); return; } VectorSubtract(entmaxs, entmins, geomsize); if (VectorLength2(geomsize) == 0) { // we don't allow point-size physics objects... if (ed->priv.server->ode_physics) World_Physics_RemoveFromEntity(world, ed); return; } // get friction ed->priv.server->ode_friction = PRVM_gameedictfloat(ed, friction) ? PRVM_gameedictfloat(ed, friction) : 1.0f; // check if we need to create or replace the geom if (!ed->priv.server->ode_physics || ed->priv.server->ode_mass != massval || geom_modified) { modified = true; World_Physics_RemoveFromEntity(world, ed); ed->priv.server->ode_physics = true; VectorMAM(0.5f, entmins, 0.5f, entmaxs, geomcenter); if (PRVM_gameedictvector(ed, massofs)) VectorCopy(geomcenter, PRVM_gameedictvector(ed, massofs)); // check geomsize if (geomsize[0] * geomsize[1] * geomsize[2] == 0) { if (movetype == MOVETYPE_PHYSICS) Con_Printf("entity %i (classname %s) .mass * .size_x * .size_y * .size_z == 0\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); VectorSet(geomsize, 1.0f, 1.0f, 1.0f); } // greate geom switch(geomtype) { case GEOMTYPE_TRIMESH: // add an optimized mesh to the model containing only the SUPERCONTENTS_SOLID surfaces if (!model->brush.collisionmesh) Mod_CreateCollisionMesh(model); if (!model->brush.collisionmesh) { Con_Printf("entity %i (classname %s) has no geometry\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname))); goto treatasbox; } // check if trimesh can be defined with convex convex_compatible = false; for (i = 0;i < model->nummodelsurfaces;i++) { if (!strcmp(((msurface_t *)(model->data_surfaces + model->firstmodelsurface + i))->texture->name, "collisionconvex")) { convex_compatible = true; break; } } // ODE requires persistent mesh storage, so we need to copy out // the data from the model because renderer restarts could free it // during the game, additionally we need to flip the triangles... // note: ODE does preprocessing of the mesh for culling, removing // concave edges, etc., so this is not a lightweight operation ed->priv.server->ode_numvertices = numvertices = model->brush.collisionmesh->numverts; ed->priv.server->ode_vertex3f = (float *)Mem_Alloc(mempool, numvertices * sizeof(float[3])); // VorteX: rebuild geomsize based on entity's collision mesh, honor scale VectorSet(entmins, 0, 0, 0); VectorSet(entmaxs, 0, 0, 0); for (vertexindex = 0, ov = ed->priv.server->ode_vertex3f, iv = model->brush.collisionmesh->vertex3f;vertexindex < numvertices;vertexindex++, ov += 3, iv += 3) { ov[0] = iv[0] * scale[0]; ov[1] = iv[1] * scale[1]; ov[2] = iv[2] * scale[2]; entmins[0] = min(entmins[0], ov[0]); entmins[1] = min(entmins[1], ov[1]); entmins[2] = min(entmins[2], ov[2]); entmaxs[0] = max(entmaxs[0], ov[0]); entmaxs[1] = max(entmaxs[1], ov[1]); entmaxs[2] = max(entmaxs[2], ov[2]); } if (!PRVM_gameedictvector(ed, massofs)) VectorMAM(0.5f, entmins, 0.5f, entmaxs, geomcenter); for (vertexindex = 0, ov = ed->priv.server->ode_vertex3f, iv = model->brush.collisionmesh->vertex3f;vertexindex < numvertices;vertexindex++, ov += 3, iv += 3) { ov[0] = ov[0] - geomcenter[0]; ov[1] = ov[1] - geomcenter[1]; ov[2] = ov[2] - geomcenter[2]; } VectorSubtract(entmaxs, entmins, geomsize); if (VectorLength2(geomsize) == 0) { if (movetype == MOVETYPE_PHYSICS) Con_Printf("entity %i collision mesh has null geomsize\n", PRVM_NUM_FOR_EDICT(ed)); VectorSet(geomsize, 1.0f, 1.0f, 1.0f); } ed->priv.server->ode_numtriangles = numtriangles = model->brush.collisionmesh->numtriangles; ed->priv.server->ode_element3i = (int *)Mem_Alloc(mempool, numtriangles * sizeof(int[3])); //memcpy(ed->priv.server->ode_element3i, model->brush.collisionmesh->element3i, ed->priv.server->ode_numtriangles * sizeof(int[3])); for (triangleindex = 0, oe = ed->priv.server->ode_element3i, ie = model->brush.collisionmesh->element3i;triangleindex < numtriangles;triangleindex++, oe += 3, ie += 3) { oe[0] = ie[2]; oe[1] = ie[1]; oe[2] = ie[0]; } // create geom Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); if (!convex_compatible || !physics_ode_allowconvex.integer) { // trimesh dataID = dGeomTriMeshDataCreate(); dGeomTriMeshDataBuildSingle((dTriMeshDataID)dataID, (void*)ed->priv.server->ode_vertex3f, sizeof(float[3]), ed->priv.server->ode_numvertices, ed->priv.server->ode_element3i, ed->priv.server->ode_numtriangles*3, sizeof(int[3])); ed->priv.server->ode_geom = (void *)dCreateTriMesh((dSpaceID)world->physics.ode_space, (dTriMeshDataID)dataID, NULL, NULL, NULL); dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); } else { // VorteX: this code is unfinished in two ways // - no duplicate vertex merging are done // - triangles that shares same edge and havee sam plane are not merget into poly // so, currently it only works for geosphere meshes with no UV Con_Printf("Build convex hull for model %s...\n", model->name); // build convex geometry from trimesh data // this ensures that trimesh's triangles can form correct convex geometry // not many of error checking is performed // ODE's conve hull data consist of: // planes : an array of planes in the form: normal X, normal Y, normal Z, distance // points : an array of points X,Y,Z // polygons: an array of indices to the points of each polygon,it should be the number of vertices // followed by that amount of indices to "points" in counter clockwise order polygonsData = polygons = (unsigned int *)Mem_Alloc(mempool, numtriangles*sizeof(int)*4); planesData = planes = (dReal *)Mem_Alloc(mempool, numtriangles*sizeof(dReal)*4); mapped = (qboolean *)Mem_Alloc(mempool, numvertices*sizeof(qboolean)); used = (qboolean *)Mem_Alloc(mempool, numtriangles*sizeof(qboolean)); memset(mapped, 0, numvertices*sizeof(qboolean)); memset(used, 0, numtriangles*sizeof(qboolean)); numplanes = numpoints = polyvert = 0; // build convex hull // todo: merge duplicated verts here Con_Printf("Building...\n"); iv = ed->priv.server->ode_vertex3f; for (triangleindex = 0; triangleindex < numtriangles; triangleindex++) { // already formed a polygon? if (used[triangleindex]) continue; // init polygon // switch clockwise->counterclockwise ie = &model->brush.collisionmesh->element3i[triangleindex*3]; used[triangleindex] = true; TriangleNormal(&iv[ie[0]*3], &iv[ie[1]*3], &iv[ie[2]*3], planes); VectorNormalize(planes); polygons[0] = 3; polygons[3] = (unsigned int)ie[0]; mapped[polygons[3]] = true; polygons[2] = (unsigned int)ie[1]; mapped[polygons[2]] = true; polygons[1] = (unsigned int)ie[2]; mapped[polygons[1]] = true; // now find and include concave triangles for (i = triangleindex; i < numtriangles; i++) { if (used[i]) continue; // should share at least 2 vertexes for (polyvert = 1; polyvert <= polygons[0]; polyvert++) { // todo: merge in triangles that shares an edge and have same plane here } } // add polygon to overall stats planes[3] = DotProduct(&iv[polygons[1]*3], planes); polygons += (polygons[0]+1); planes += 4; numplanes++; } Mem_Free(used); // save points for (vertexindex = 0, numpoints = 0; vertexindex < numvertices; vertexindex++) if (mapped[vertexindex]) numpoints++; pointsData = (dReal *)Mem_Alloc(mempool, numpoints*sizeof(dReal)*3 + numplanes*sizeof(dReal)*4); // planes is appended for (vertexindex = 0, numpoints = 0; vertexindex < numvertices; vertexindex++) { if (mapped[vertexindex]) { VectorCopy(&iv[vertexindex*3], &pointsData[numpoints*3]); numpoints++; } } Mem_Free(mapped); Con_Printf("Points: \n"); for (i = 0; i < (int)numpoints; i++) Con_Printf("%3i: %3.1f %3.1f %3.1f\n", i, pointsData[i*3], pointsData[i*3+1], pointsData[i*3+2]); // save planes planes = planesData; planesData = pointsData + numpoints*3; memcpy(planesData, planes, numplanes*sizeof(dReal)*4); Mem_Free(planes); Con_Printf("planes...\n"); for (i = 0; i < numplanes; i++) Con_Printf("%3i: %1.1f %1.1f %1.1f %1.1f\n", i, planesData[i*4], planesData[i*4 + 1], planesData[i*4 + 2], planesData[i*4 + 3]); // save polygons polyvert = polygons - polygonsData; polygons = polygonsData; polygonsData = (unsigned int *)Mem_Alloc(mempool, polyvert*sizeof(int)); memcpy(polygonsData, polygons, polyvert*sizeof(int)); Mem_Free(polygons); Con_Printf("Polygons: \n"); polygons = polygonsData; for (i = 0; i < numplanes; i++) { Con_Printf("%3i : %i ", i, polygons[0]); for (triangleindex = 1; triangleindex <= (int)polygons[0]; triangleindex++) Con_Printf("%3i ", polygons[triangleindex]); polygons += (polygons[0]+1); Con_Printf("\n"); } Mem_Free(ed->priv.server->ode_element3i); ed->priv.server->ode_element3i = (int *)polygonsData; Mem_Free(ed->priv.server->ode_vertex3f); ed->priv.server->ode_vertex3f = (float *)pointsData; // check for properly build polygons by calculating the determinant of the 3x3 matrix composed of the first 3 points in the polygon // this code is picked from ODE Source Con_Printf("Check...\n"); polygons = polygonsData; for (i = 0; i < numplanes; i++) { if((pointsData[(polygons[1]*3)+0]*pointsData[(polygons[2]*3)+1]*pointsData[(polygons[3]*3)+2] + pointsData[(polygons[1]*3)+1]*pointsData[(polygons[2]*3)+2]*pointsData[(polygons[3]*3)+0] + pointsData[(polygons[1]*3)+2]*pointsData[(polygons[2]*3)+0]*pointsData[(polygons[3]*3)+1] - pointsData[(polygons[1]*3)+2]*pointsData[(polygons[2]*3)+1]*pointsData[(polygons[3]*3)+0] - pointsData[(polygons[1]*3)+1]*pointsData[(polygons[2]*3)+0]*pointsData[(polygons[3]*3)+2] - pointsData[(polygons[1]*3)+0]*pointsData[(polygons[2]*3)+2]*pointsData[(polygons[3]*3)+1]) < 0) Con_Printf("WARNING: Polygon %d is not defined counterclockwise\n", i); if (planesData[(i*4)+3] < 0) Con_Printf("WARNING: Plane %d does not contain the origin\n", i); polygons += (*polygons + 1); } // create geom Con_Printf("Create geom...\n"); ed->priv.server->ode_geom = (void *)dCreateConvex((dSpaceID)world->physics.ode_space, planesData, numplanes, pointsData, numpoints, polygonsData); dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); Con_Printf("Done!\n"); } break; case GEOMTYPE_BOX: treatasbox: Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); ed->priv.server->ode_geom = (void *)dCreateBox((dSpaceID)world->physics.ode_space, geomsize[0], geomsize[1], geomsize[2]); dMassSetBoxTotal(&mass, massval, geomsize[0], geomsize[1], geomsize[2]); break; case GEOMTYPE_SPHERE: Matrix4x4_CreateTranslate(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2]); ed->priv.server->ode_geom = (void *)dCreateSphere((dSpaceID)world->physics.ode_space, geomsize[0] * 0.5f); dMassSetSphereTotal(&mass, massval, geomsize[0] * 0.5f); break; case GEOMTYPE_CAPSULE: axisindex = 0; if (geomsize[axisindex] < geomsize[1]) axisindex = 1; if (geomsize[axisindex] < geomsize[2]) axisindex = 2; // the qc gives us 3 axis radius, the longest axis is the capsule // axis, since ODE doesn't like this idea we have to create a // capsule which uses the standard orientation, and apply a // transform to it if (axisindex == 0) { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); radius = min(geomsize[1], geomsize[2]) * 0.5f; } else if (axisindex == 1) { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); radius = min(geomsize[0], geomsize[2]) * 0.5f; } else { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); radius = min(geomsize[0], geomsize[1]) * 0.5f; } length = geomsize[axisindex] - radius*2; // because we want to support more than one axisindex, we have to // create a transform, and turn on its cleanup setting (which will // cause the child to be destroyed when it is destroyed) ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); dMassSetCapsuleTotal(&mass, massval, axisindex+1, radius, length); break; case GEOMTYPE_CAPSULE_X: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); radius = min(geomsize[1], geomsize[2]) * 0.5f; length = geomsize[0] - radius*2; // check if length is not enough, reduce radius then if (length <= 0) { radius -= (1 - length)*0.5; length = 1; } ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); dMassSetCapsuleTotal(&mass, massval, 1, radius, length); break; case GEOMTYPE_CAPSULE_Y: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); radius = min(geomsize[0], geomsize[2]) * 0.5f; length = geomsize[1] - radius*2; // check if length is not enough, reduce radius then if (length <= 0) { radius -= (1 - length)*0.5; length = 1; } ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); dMassSetCapsuleTotal(&mass, massval, 2, radius, length); break; case GEOMTYPE_CAPSULE_Z: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); radius = min(geomsize[1], geomsize[0]) * 0.5f; length = geomsize[2] - radius*2; // check if length is not enough, reduce radius then if (length <= 0) { radius -= (1 - length)*0.5; length = 1; } ed->priv.server->ode_geom = (void *)dCreateCapsule((dSpaceID)world->physics.ode_space, radius, length); dMassSetCapsuleTotal(&mass, massval, 3, radius, length); break; case GEOMTYPE_CYLINDER: axisindex = 0; if (geomsize[axisindex] < geomsize[1]) axisindex = 1; if (geomsize[axisindex] < geomsize[2]) axisindex = 2; // the qc gives us 3 axis radius, the longest axis is the capsule // axis, since ODE doesn't like this idea we have to create a // capsule which uses the standard orientation, and apply a // transform to it if (axisindex == 0) { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); radius = min(geomsize[1], geomsize[2]) * 0.5f; } else if (axisindex == 1) { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); radius = min(geomsize[0], geomsize[2]) * 0.5f; } else { Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); radius = min(geomsize[0], geomsize[1]) * 0.5f; } length = geomsize[axisindex]; // check if length is not enough, reduce radius then if (length <= 0) { radius -= (1 - length)*0.5; length = 1; } ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); dMassSetCylinderTotal(&mass, massval, axisindex+1, radius, length); break; case GEOMTYPE_CYLINDER_X: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 90, 1); radius = min(geomsize[1], geomsize[2]) * 0.5f; length = geomsize[0]; ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); dMassSetCylinderTotal(&mass, massval, 1, radius, length); break; case GEOMTYPE_CYLINDER_Y: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 90, 0, 0, 1); radius = min(geomsize[0], geomsize[2]) * 0.5f; length = geomsize[1]; ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); dMassSetCylinderTotal(&mass, massval, 2, radius, length); break; case GEOMTYPE_CYLINDER_Z: Matrix4x4_CreateFromQuakeEntity(&ed->priv.server->ode_offsetmatrix, geomcenter[0], geomcenter[1], geomcenter[2], 0, 0, 0, 1); radius = min(geomsize[0], geomsize[1]) * 0.5f; length = geomsize[2]; ed->priv.server->ode_geom = (void *)dCreateCylinder((dSpaceID)world->physics.ode_space, radius, length); dMassSetCylinderTotal(&mass, massval, 3, radius, length); break; default: Sys_Error("World_Physics_BodyFromEntity: unrecognized geomtype value %i was accepted by filter\n", solid); // this goto only exists to prevent warnings from the compiler // about uninitialized variables (mass), while allowing it to // catch legitimate uninitialized variable warnings goto treatasbox; } ed->priv.server->ode_mass = massval; ed->priv.server->ode_modelindex = modelindex; VectorCopy(entmins, ed->priv.server->ode_mins); VectorCopy(entmaxs, ed->priv.server->ode_maxs); VectorCopy(scale, ed->priv.server->ode_scale); ed->priv.server->ode_movelimit = min(geomsize[0], min(geomsize[1], geomsize[2])); Matrix4x4_Invert_Simple(&ed->priv.server->ode_offsetimatrix, &ed->priv.server->ode_offsetmatrix); ed->priv.server->ode_massbuf = Mem_Alloc(mempool, sizeof(mass)); memcpy(ed->priv.server->ode_massbuf, &mass, sizeof(dMass)); } if (ed->priv.server->ode_geom) dGeomSetData((dGeomID)ed->priv.server->ode_geom, (void*)ed); if (movetype == MOVETYPE_PHYSICS && ed->priv.server->ode_geom) { // entity is dynamic if (ed->priv.server->ode_body == NULL) { ed->priv.server->ode_body = (void *)(body = dBodyCreate((dWorldID)world->physics.ode_world)); dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); dBodySetData(body, (void*)ed); dBodySetMass(body, (dMass *) ed->priv.server->ode_massbuf); modified = true; } } else { // entity is deactivated if (ed->priv.server->ode_body != NULL) { if(ed->priv.server->ode_geom) dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); dBodyDestroy((dBodyID) ed->priv.server->ode_body); ed->priv.server->ode_body = NULL; modified = true; } } // get current data from entity VectorClear(origin); VectorClear(velocity); //VectorClear(forward); //VectorClear(left); //VectorClear(up); //VectorClear(spinvelocity); VectorClear(angles); VectorClear(avelocity); gravity = true; VectorCopy(PRVM_gameedictvector(ed, origin), origin); VectorCopy(PRVM_gameedictvector(ed, velocity), velocity); //VectorCopy(PRVM_gameedictvector(ed, axis_forward), forward); //VectorCopy(PRVM_gameedictvector(ed, axis_left), left); //VectorCopy(PRVM_gameedictvector(ed, axis_up), up); //VectorCopy(PRVM_gameedictvector(ed, spinvelocity), spinvelocity); VectorCopy(PRVM_gameedictvector(ed, angles), angles); VectorCopy(PRVM_gameedictvector(ed, avelocity), avelocity); if (PRVM_gameedictfloat(ed, gravity) != 0.0f && PRVM_gameedictfloat(ed, gravity) < 0.5f) gravity = false; if (ed == prog->edicts) gravity = false; // compatibility for legacy entities //if (!VectorLength2(forward) || solid == SOLID_BSP) { float pitchsign = 1; vec3_t qangles, qavelocity; VectorCopy(angles, qangles); VectorCopy(avelocity, qavelocity); if(prog == SVVM_prog) // FIXME some better way? { pitchsign = SV_GetPitchSign(prog, ed); } else if(prog == CLVM_prog) { pitchsign = CL_GetPitchSign(prog, ed); } qangles[PITCH] *= pitchsign; qavelocity[PITCH] *= pitchsign; AngleVectorsFLU(qangles, forward, left, up); // convert single-axis rotations in avelocity to spinvelocity // FIXME: untested math - check signs VectorSet(spinvelocity, DEG2RAD(qavelocity[PITCH]), DEG2RAD(qavelocity[ROLL]), DEG2RAD(qavelocity[YAW])); } // compatibility for legacy entities switch (solid) { case SOLID_BBOX: case SOLID_SLIDEBOX: case SOLID_CORPSE: VectorSet(forward, 1, 0, 0); VectorSet(left, 0, 1, 0); VectorSet(up, 0, 0, 1); VectorSet(spinvelocity, 0, 0, 0); break; } // we must prevent NANs... if (physics_ode_trick_fixnan.integer) { test = VectorLength2(origin) + VectorLength2(forward) + VectorLength2(left) + VectorLength2(up) + VectorLength2(velocity) + VectorLength2(spinvelocity); if (VEC_IS_NAN(test)) { modified = true; //Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .axis_forward = '%f %f %f' .axis_left = '%f %f %f' .axis_up = %f %f %f' .spinvelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], forward[0], forward[1], forward[2], left[0], left[1], left[2], up[0], up[1], up[2], spinvelocity[0], spinvelocity[1], spinvelocity[2]); if (physics_ode_trick_fixnan.integer >= 2) Con_Printf("Fixing NAN values on entity %i : .classname = \"%s\" .origin = '%f %f %f' .velocity = '%f %f %f' .angles = '%f %f %f' .avelocity = '%f %f %f'\n", PRVM_NUM_FOR_EDICT(ed), PRVM_GetString(prog, PRVM_gameedictstring(ed, classname)), origin[0], origin[1], origin[2], velocity[0], velocity[1], velocity[2], angles[0], angles[1], angles[2], avelocity[0], avelocity[1], avelocity[2]); test = VectorLength2(origin); if (VEC_IS_NAN(test)) VectorClear(origin); test = VectorLength2(forward) * VectorLength2(left) * VectorLength2(up); if (VEC_IS_NAN(test)) { VectorSet(angles, 0, 0, 0); VectorSet(forward, 1, 0, 0); VectorSet(left, 0, 1, 0); VectorSet(up, 0, 0, 1); } test = VectorLength2(velocity); if (VEC_IS_NAN(test)) VectorClear(velocity); test = VectorLength2(spinvelocity); if (VEC_IS_NAN(test)) { VectorClear(avelocity); VectorClear(spinvelocity); } } } // check if the qc edited any position data if (!VectorCompare(origin, ed->priv.server->ode_origin) || !VectorCompare(velocity, ed->priv.server->ode_velocity) || !VectorCompare(angles, ed->priv.server->ode_angles) || !VectorCompare(avelocity, ed->priv.server->ode_avelocity) || gravity != ed->priv.server->ode_gravity) modified = true; // store the qc values into the physics engine body = (dBodyID)ed->priv.server->ode_body; if (modified && ed->priv.server->ode_geom) { dVector3 r[3]; matrix4x4_t entitymatrix; matrix4x4_t bodymatrix; #if 0 Con_Printf("entity %i got changed by QC\n", (int) (ed - prog->edicts)); if(!VectorCompare(origin, ed->priv.server->ode_origin)) Con_Printf(" origin: %f %f %f -> %f %f %f\n", ed->priv.server->ode_origin[0], ed->priv.server->ode_origin[1], ed->priv.server->ode_origin[2], origin[0], origin[1], origin[2]); if(!VectorCompare(velocity, ed->priv.server->ode_velocity)) Con_Printf(" velocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_velocity[0], ed->priv.server->ode_velocity[1], ed->priv.server->ode_velocity[2], velocity[0], velocity[1], velocity[2]); if(!VectorCompare(angles, ed->priv.server->ode_angles)) Con_Printf(" angles: %f %f %f -> %f %f %f\n", ed->priv.server->ode_angles[0], ed->priv.server->ode_angles[1], ed->priv.server->ode_angles[2], angles[0], angles[1], angles[2]); if(!VectorCompare(avelocity, ed->priv.server->ode_avelocity)) Con_Printf(" avelocity: %f %f %f -> %f %f %f\n", ed->priv.server->ode_avelocity[0], ed->priv.server->ode_avelocity[1], ed->priv.server->ode_avelocity[2], avelocity[0], avelocity[1], avelocity[2]); if(gravity != ed->priv.server->ode_gravity) Con_Printf(" gravity: %i -> %i\n", ed->priv.server->ode_gravity, gravity); #endif // values for BodyFromEntity to check if the qc modified anything later VectorCopy(origin, ed->priv.server->ode_origin); VectorCopy(velocity, ed->priv.server->ode_velocity); VectorCopy(angles, ed->priv.server->ode_angles); VectorCopy(avelocity, ed->priv.server->ode_avelocity); ed->priv.server->ode_gravity = gravity; Matrix4x4_FromVectors(&entitymatrix, forward, left, up, origin); Matrix4x4_Concat(&bodymatrix, &entitymatrix, &ed->priv.server->ode_offsetmatrix); Matrix4x4_ToVectors(&bodymatrix, forward, left, up, origin); r[0][0] = forward[0]; r[1][0] = forward[1]; r[2][0] = forward[2]; r[0][1] = left[0]; r[1][1] = left[1]; r[2][1] = left[2]; r[0][2] = up[0]; r[1][2] = up[1]; r[2][2] = up[2]; if (body) { if (movetype == MOVETYPE_PHYSICS) { dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); dBodySetPosition(body, origin[0], origin[1], origin[2]); dBodySetRotation(body, r[0]); dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); dBodySetGravityMode(body, gravity); } else { dGeomSetBody((dGeomID)ed->priv.server->ode_geom, body); dBodySetPosition(body, origin[0], origin[1], origin[2]); dBodySetRotation(body, r[0]); dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); dBodySetGravityMode(body, gravity); dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); } } else { // no body... then let's adjust the parameters of the geom directly dGeomSetBody((dGeomID)ed->priv.server->ode_geom, 0); // just in case we previously HAD a body (which should never happen) dGeomSetPosition((dGeomID)ed->priv.server->ode_geom, origin[0], origin[1], origin[2]); dGeomSetRotation((dGeomID)ed->priv.server->ode_geom, r[0]); } } if (body) { // limit movement speed to prevent missed collisions at high speed ovelocity = dBodyGetLinearVel(body); ospinvelocity = dBodyGetAngularVel(body); movelimit = ed->priv.server->ode_movelimit * world->physics.ode_movelimit; test = VectorLength2(ovelocity); if (test > movelimit*movelimit) { // scale down linear velocity to the movelimit // scale down angular velocity the same amount for consistency f = movelimit / sqrt(test); VectorScale(ovelocity, f, velocity); VectorScale(ospinvelocity, f, spinvelocity); dBodySetLinearVel(body, velocity[0], velocity[1], velocity[2]); dBodySetAngularVel(body, spinvelocity[0], spinvelocity[1], spinvelocity[2]); } // make sure the angular velocity is not exploding spinlimit = physics_ode_spinlimit.value; test = VectorLength2(ospinvelocity); if (test > spinlimit) { dBodySetAngularVel(body, 0, 0, 0); } // apply functions and clear stack for(func = ed->priv.server->ode_func; func; func = nextf) { nextf = func->next; World_Physics_ApplyCmd(ed, func); Mem_Free(func); } ed->priv.server->ode_func = NULL; } } #define MAX_CONTACTS 32 static void nearCallback (void *data, dGeomID o1, dGeomID o2) { world_t *world = (world_t *)data; prvm_prog_t *prog = world->prog; dContact contact[MAX_CONTACTS]; // max contacts per collision pair int b1enabled = 0, b2enabled = 0; dBodyID b1, b2; dJointID c; int i; int numcontacts; float bouncefactor1 = 0.0f; float bouncestop1 = 60.0f / 800.0f; float bouncefactor2 = 0.0f; float bouncestop2 = 60.0f / 800.0f; float erp; dVector3 grav; prvm_edict_t *ed1, *ed2; if (dGeomIsSpace(o1) || dGeomIsSpace(o2)) { // colliding a space with something dSpaceCollide2(o1, o2, data, &nearCallback); // Note we do not want to test intersections within a space, // only between spaces. //if (dGeomIsSpace(o1)) dSpaceCollide(o1, data, &nearCallback); //if (dGeomIsSpace(o2)) dSpaceCollide(o2, data, &nearCallback); return; } b1 = dGeomGetBody(o1); if (b1) b1enabled = dBodyIsEnabled(b1); b2 = dGeomGetBody(o2); if (b2) b2enabled = dBodyIsEnabled(b2); // at least one object has to be using MOVETYPE_PHYSICS and should be enabled or we just don't care if (!b1enabled && !b2enabled) return; // exit without doing anything if the two bodies are connected by a joint if (b1 && b2 && dAreConnectedExcluding(b1, b2, dJointTypeContact)) return; ed1 = (prvm_edict_t *) dGeomGetData(o1); if(ed1 && ed1->priv.server->free) ed1 = NULL; if(ed1) { bouncefactor1 = PRVM_gameedictfloat(ed1, bouncefactor); bouncestop1 = PRVM_gameedictfloat(ed1, bouncestop); if (!bouncestop1) bouncestop1 = 60.0f / 800.0f; } ed2 = (prvm_edict_t *) dGeomGetData(o2); if(ed2 && ed2->priv.server->free) ed2 = NULL; if(ed2) { bouncefactor2 = PRVM_gameedictfloat(ed2, bouncefactor); bouncestop2 = PRVM_gameedictfloat(ed2, bouncestop); if (!bouncestop2) bouncestop2 = 60.0f / 800.0f; } if(prog == SVVM_prog) { if(ed1 && PRVM_serveredictfunction(ed1, touch)) { SV_LinkEdict_TouchAreaGrid_Call(ed1, ed2 ? ed2 : prog->edicts); } if(ed2 && PRVM_serveredictfunction(ed2, touch)) { SV_LinkEdict_TouchAreaGrid_Call(ed2, ed1 ? ed1 : prog->edicts); } } // merge bounce factors and bounce stop if(bouncefactor2 > 0) { if(bouncefactor1 > 0) { // TODO possibly better logic to merge bounce factor data? if(bouncestop2 < bouncestop1) bouncestop1 = bouncestop2; if(bouncefactor2 > bouncefactor1) bouncefactor1 = bouncefactor2; } else { bouncestop1 = bouncestop2; bouncefactor1 = bouncefactor2; } } dWorldGetGravity((dWorldID)world->physics.ode_world, grav); bouncestop1 *= fabs(grav[2]); // get erp // select object that moves faster ang get it's erp erp = (VectorLength2(PRVM_gameedictvector(ed1, velocity)) > VectorLength2(PRVM_gameedictvector(ed2, velocity))) ? PRVM_gameedictfloat(ed1, erp) : PRVM_gameedictfloat(ed2, erp); // get max contact points for this collision numcontacts = (int)PRVM_gameedictfloat(ed1, maxcontacts); if (!numcontacts) numcontacts = physics_ode_contact_maxpoints.integer; if (PRVM_gameedictfloat(ed2, maxcontacts)) numcontacts = max(numcontacts, (int)PRVM_gameedictfloat(ed2, maxcontacts)); else numcontacts = max(numcontacts, physics_ode_contact_maxpoints.integer); // generate contact points between the two non-space geoms numcontacts = dCollide(o1, o2, min(MAX_CONTACTS, numcontacts), &(contact[0].geom), sizeof(contact[0])); // add these contact points to the simulation for (i = 0;i < numcontacts;i++) { contact[i].surface.mode = (physics_ode_contact_mu.value != -1 ? dContactApprox1 : 0) | (physics_ode_contact_erp.value != -1 ? dContactSoftERP : 0) | (physics_ode_contact_cfm.value != -1 ? dContactSoftCFM : 0) | (bouncefactor1 > 0 ? dContactBounce : 0); contact[i].surface.mu = physics_ode_contact_mu.value * ed1->priv.server->ode_friction * ed2->priv.server->ode_friction; contact[i].surface.soft_erp = physics_ode_contact_erp.value + erp; contact[i].surface.soft_cfm = physics_ode_contact_cfm.value; contact[i].surface.bounce = bouncefactor1; contact[i].surface.bounce_vel = bouncestop1; c = dJointCreateContact((dWorldID)world->physics.ode_world, (dJointGroupID)world->physics.ode_contactgroup, contact + i); dJointAttach(c, b1, b2); } } #endif void World_Physics_Frame(world_t *world, double frametime, double gravity) { #ifdef USEODE prvm_prog_t *prog = world->prog; double tdelta, tdelta2, tdelta3, simulationtime, collisiontime; tdelta = Sys_DirtyTime(); if (world->physics.ode && physics_ode.integer) { int i; prvm_edict_t *ed; if (!physics_ode_constantstep.value) { world->physics.ode_iterations = bound(1, physics_ode_iterationsperframe.integer, 1000); world->physics.ode_step = frametime / world->physics.ode_iterations; } else { world->physics.ode_time += frametime; // step size if (physics_ode_constantstep.value > 0 && physics_ode_constantstep.value < 1) world->physics.ode_step = physics_ode_constantstep.value; else world->physics.ode_step = sys_ticrate.value; if (world->physics.ode_time > 0.2f) world->physics.ode_time = world->physics.ode_step; // set number of iterations to process world->physics.ode_iterations = 0; while(world->physics.ode_time >= world->physics.ode_step) { world->physics.ode_iterations++; world->physics.ode_time -= world->physics.ode_step; } } world->physics.ode_movelimit = physics_ode_movelimit.value / world->physics.ode_step; World_Physics_UpdateODE(world); // copy physics properties from entities to physics engine if (prog) { for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) if (!prog->edicts[i].priv.required->free) World_Physics_Frame_BodyFromEntity(world, ed); // oh, and it must be called after all bodies were created for (i = 0, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) if (!prog->edicts[i].priv.required->free) World_Physics_Frame_JointFromEntity(world, ed); } tdelta2 = Sys_DirtyTime(); collisiontime = 0; for (i = 0;i < world->physics.ode_iterations;i++) { // set the gravity dWorldSetGravity((dWorldID)world->physics.ode_world, 0, 0, -gravity * physics_ode_world_gravitymod.value); // set the tolerance for closeness of objects dWorldSetContactSurfaceLayer((dWorldID)world->physics.ode_world, max(0, physics_ode_contactsurfacelayer.value)); // run collisions for the current world state, creating JointGroup tdelta3 = Sys_DirtyTime(); dSpaceCollide((dSpaceID)world->physics.ode_space, (void *)world, nearCallback); collisiontime += (Sys_DirtyTime() - tdelta3)*10000; // apply forces if (prog) { int j; for (j = 0, ed = prog->edicts + j;j < prog->num_edicts;j++, ed++) if (!prog->edicts[j].priv.required->free) World_Physics_Frame_ForceFromEntity(world, ed); } // run physics (move objects, calculate new velocities) // be sure not to pass 0 as step time because that causes an ODE error dWorldSetQuickStepNumIterations((dWorldID)world->physics.ode_world, bound(1, physics_ode_worldstep_iterations.integer, 200)); if (world->physics.ode_step > 0) dWorldQuickStep((dWorldID)world->physics.ode_world, world->physics.ode_step); // clear the JointGroup now that we're done with it dJointGroupEmpty((dJointGroupID)world->physics.ode_contactgroup); } simulationtime = (Sys_DirtyTime() - tdelta2)*10000; // copy physics properties from physics engine to entities and do some stats if (prog) { for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) if (!prog->edicts[i].priv.required->free) World_Physics_Frame_BodyToEntity(world, ed); // print stats if (physics_ode_printstats.integer) { dBodyID body; world->physics.ode_numobjects = 0; world->physics.ode_activeovjects = 0; for (i = 1, ed = prog->edicts + i;i < prog->num_edicts;i++, ed++) { if (prog->edicts[i].priv.required->free) continue; body = (dBodyID)prog->edicts[i].priv.server->ode_body; if (!body) continue; world->physics.ode_numobjects++; if (dBodyIsEnabled(body)) world->physics.ode_activeovjects++; } Con_Printf("ODE Stats(%s): %i iterations, %3.01f (%3.01f collision) %3.01f total : %i objects %i active %i disabled\n", prog->name, world->physics.ode_iterations, simulationtime, collisiontime, (Sys_DirtyTime() - tdelta)*10000, world->physics.ode_numobjects, world->physics.ode_activeovjects, (world->physics.ode_numobjects - world->physics.ode_activeovjects)); } } } #endif } darkplaces/thread_sdl.c0000664000175000017500000001052213067716222014442 0ustar kalevkalev#include #include #include "quakedef.h" #include "thread.h" int Thread_Init(void) { #ifdef THREADDISABLE Con_Printf("Threading disabled in this build\n"); #endif return 0; } void Thread_Shutdown(void) { } qboolean Thread_HasThreads(void) { #ifdef THREADDISABLE return false; #else return true; #endif } void *_Thread_CreateMutex(const char *filename, int fileline) { void *mutex = SDL_CreateMutex(); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex create %s:%i\n" , mutex, filename, fileline); #endif return mutex; } void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex destroy %s:%i\n", mutex, filename, fileline); #endif SDL_DestroyMutex((SDL_mutex *)mutex); } int _Thread_LockMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex lock %s:%i\n" , mutex, filename, fileline); #endif return SDL_LockMutex((SDL_mutex *)mutex); } int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex unlock %s:%i\n" , mutex, filename, fileline); #endif return SDL_UnlockMutex((SDL_mutex *)mutex); } void *_Thread_CreateCond(const char *filename, int fileline) { void *cond = (void *)SDL_CreateCond(); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond create %s:%i\n" , cond, filename, fileline); #endif return cond; } void _Thread_DestroyCond(void *cond, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond destroy %s:%i\n" , cond, filename, fileline); #endif SDL_DestroyCond((SDL_cond *)cond); } int _Thread_CondSignal(void *cond, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond signal %s:%i\n" , cond, filename, fileline); #endif return SDL_CondSignal((SDL_cond *)cond); } int _Thread_CondBroadcast(void *cond, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond broadcast %s:%i\n" , cond, filename, fileline); #endif return SDL_CondBroadcast((SDL_cond *)cond); } int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond wait %s:%i\n" , cond, filename, fileline); #endif return SDL_CondWait((SDL_cond *)cond, (SDL_mutex *)mutex); } void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline) { #if SDL_MAJOR_VERSION == 1 void *thread = (void *)SDL_CreateThread(fn, data); #else void *thread = (void *)SDL_CreateThread(fn, filename, data); #endif #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread create %s:%i\n" , thread, filename, fileline); #endif return thread; } int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline) { int status = retval; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread wait %s:%i\n" , thread, filename, fileline); #endif SDL_WaitThread((SDL_Thread *)thread, &status); return status; } // standard barrier implementation using conds and mutexes // see: http://www.howforge.com/implementing-barrier-in-pthreads typedef struct { unsigned int needed; unsigned int called; void *mutex; void *cond; } barrier_t; void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) Z_Malloc(sizeof(barrier_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); #endif b->needed = count; b->called = 0; b->mutex = Thread_CreateMutex(); b->cond = Thread_CreateCond(); return (void *) b; } void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); #endif Thread_DestroyMutex(b->mutex); Thread_DestroyCond(b->cond); } void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); #endif Thread_LockMutex(b->mutex); b->called++; if (b->called == b->needed) { b->called = 0; Thread_CondBroadcast(b->cond); } else { do { Thread_CondWait(b->cond, b->mutex); } while(b->called); } Thread_UnlockMutex(b->mutex); } darkplaces/clvm_cmds.h0000664000175000017500000000134113067716216014307 0ustar kalevkalev#ifndef __CLVM_CMDS_H__ #define __CLVM_CMDS_H__ /* These are VM built-ins that originate in the client-side programs support but are reused by the other programs (usually the menu). */ void VM_CL_setmodel (void); void VM_CL_precache_model (void); void VM_CL_setorigin (void); void VM_CL_R_AddDynamicLight (void); void VM_CL_R_ClearScene (void); void VM_CL_R_AddEntities (void); void VM_CL_R_AddEntity (void); void VM_CL_R_SetView (void); void VM_CL_R_RenderScene (void); void VM_CL_R_LoadWorldModel (void); void VM_CL_R_PolygonBegin (void); void VM_CL_R_PolygonVertex (void); void VM_CL_R_PolygonEnd (void); void VM_CL_setattachment(void); void VM_CL_gettagindex(void); void VM_CL_gettaginfo(void); #endif /* __CLVM_CMDS_H__ */ darkplaces/snd_sdl.c0000664000175000017500000001473213067716222013766 0ustar kalevkalev/* Copyright (C) 2004 Andreas Kirsch This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include "quakedef.h" #include "snd_main.h" static unsigned int sdlaudiotime = 0; // Note: SDL calls SDL_LockAudio() right before this function, so no need to lock the audio data here static void Buffer_Callback (void *userdata, Uint8 *stream, int len) { unsigned int factor, RequestedFrames, MaxFrames, FrameCount; unsigned int StartOffset, EndOffset; factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width; if ((unsigned int)len % factor != 0) Sys_Error("SDL sound: invalid buffer length passed to Buffer_Callback (%d bytes)\n", len); RequestedFrames = (unsigned int)len / factor; if (SndSys_LockRenderBuffer()) { if (snd_usethreadedmixing) { S_MixToBuffer(stream, RequestedFrames); if (snd_blocked) memset(stream, snd_renderbuffer->format.width == 1 ? 0x80 : 0, len); SndSys_UnlockRenderBuffer(); return; } // Transfert up to a chunk of samples from snd_renderbuffer to stream MaxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe; if (MaxFrames > RequestedFrames) FrameCount = RequestedFrames; else FrameCount = MaxFrames; StartOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; EndOffset = (snd_renderbuffer->startframe + FrameCount) % snd_renderbuffer->maxframes; if (StartOffset > EndOffset) // if the buffer wraps { unsigned int PartialLength1, PartialLength2; PartialLength1 = (snd_renderbuffer->maxframes - StartOffset) * factor; memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], PartialLength1); PartialLength2 = FrameCount * factor - PartialLength1; memcpy(&stream[PartialLength1], &snd_renderbuffer->ring[0], PartialLength2); } else memcpy(stream, &snd_renderbuffer->ring[StartOffset * factor], FrameCount * factor); snd_renderbuffer->startframe += FrameCount; if (FrameCount < RequestedFrames && developer_insane.integer && vid_activewindow) Con_DPrintf("SDL sound: %u sample frames missing\n", RequestedFrames - FrameCount); sdlaudiotime += RequestedFrames; SndSys_UnlockRenderBuffer(); } } /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { unsigned int buffersize; SDL_AudioSpec wantspec; SDL_AudioSpec obtainspec; snd_threaded = false; Con_DPrint ("SndSys_Init: using the SDL module\n"); // Init the SDL Audio subsystem if( SDL_InitSubSystem( SDL_INIT_AUDIO ) ) { Con_Print( "Initializing the SDL Audio subsystem failed!\n" ); return false; } buffersize = (unsigned int)ceil((double)requested->speed / 25.0); // 2048 bytes on 24kHz to 48kHz // Init the SDL Audio subsystem wantspec.callback = Buffer_Callback; wantspec.userdata = NULL; wantspec.freq = requested->speed; wantspec.format = ((requested->width == 1) ? AUDIO_U8 : AUDIO_S16SYS); wantspec.channels = requested->channels; wantspec.samples = CeilPowerOf2(buffersize); // needs to be a power of 2 on some platforms. Con_Printf("Wanted audio Specification:\n" "\tChannels : %i\n" "\tFormat : 0x%X\n" "\tFrequency : %i\n" "\tSamples : %i\n", wantspec.channels, wantspec.format, wantspec.freq, wantspec.samples); if( SDL_OpenAudio( &wantspec, &obtainspec ) ) { Con_Printf( "Failed to open the audio device! (%s)\n", SDL_GetError() ); return false; } Con_Printf("Obtained audio specification:\n" "\tChannels : %i\n" "\tFormat : 0x%X\n" "\tFrequency : %i\n" "\tSamples : %i\n", obtainspec.channels, obtainspec.format, obtainspec.freq, obtainspec.samples); // If we haven't obtained what we wanted if (wantspec.freq != obtainspec.freq || wantspec.format != obtainspec.format || wantspec.channels != obtainspec.channels) { SDL_CloseAudio(); // Pass the obtained format as a suggested format if (suggested != NULL) { suggested->speed = obtainspec.freq; // FIXME: check the format more carefully. There are plenty of unsupported cases suggested->width = ((obtainspec.format == AUDIO_U8) ? 1 : 2); suggested->channels = obtainspec.channels; } return false; } snd_threaded = true; snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); sdlaudiotime = 0; SDL_PauseAudio( false ); return true; } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown(void) { SDL_CloseAudio(); if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { // Nothing to do here (this sound module is callback-based) } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { return sdlaudiotime; } /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { SDL_LockAudio(); return true; } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { SDL_UnlockAudio(); } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/thread_win.c0000664000175000017500000001576113067716222014467 0ustar kalevkalev#include "quakedef.h" #include "thread.h" #include int Thread_Init(void) { #ifdef THREADDISABLE Con_Printf("Threading disabled in this build\n"); #endif return 0; } void Thread_Shutdown(void) { } qboolean Thread_HasThreads(void) { #ifdef THREADDISABLE return false; #else return true; #endif } void *_Thread_CreateMutex(const char *filename, int fileline) { void *mutex = (void *)CreateMutex(NULL, FALSE, NULL); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex create %s:%i\n" , mutex, filename, fileline); #endif return mutex; } void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex destroy %s:%i\n", mutex, filename, fileline); #endif CloseHandle(mutex); } int _Thread_LockMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex lock %s:%i\n" , mutex, filename, fileline); #endif return (WaitForSingleObject(mutex, INFINITE) == WAIT_FAILED) ? -1 : 0; } int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline) { #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex unlock %s:%i\n" , mutex, filename, fileline); #endif return (ReleaseMutex(mutex) == FALSE) ? -1 : 0; } typedef struct thread_semaphore_s { HANDLE semaphore; volatile LONG value; } thread_semaphore_t; static thread_semaphore_t *Thread_CreateSemaphore(unsigned int v) { thread_semaphore_t *s = (thread_semaphore_t *)calloc(sizeof(*s), 1); s->semaphore = CreateSemaphore(NULL, v, 32768, NULL); s->value = v; return s; } static void Thread_DestroySemaphore(thread_semaphore_t *s) { CloseHandle(s->semaphore); free(s); } static int Thread_WaitSemaphore(thread_semaphore_t *s, unsigned int msec) { int r = WaitForSingleObject(s->semaphore, msec); if (r == WAIT_OBJECT_0) { InterlockedDecrement(&s->value); return 0; } if (r == WAIT_TIMEOUT) return 1; return -1; } static int Thread_PostSemaphore(thread_semaphore_t *s) { InterlockedIncrement(&s->value); if (ReleaseSemaphore(s->semaphore, 1, NULL)) return 0; InterlockedDecrement(&s->value); return -1; } typedef struct thread_cond_s { HANDLE mutex; int waiting; int signals; thread_semaphore_t *sem; thread_semaphore_t *done; } thread_cond_t; void *_Thread_CreateCond(const char *filename, int fileline) { thread_cond_t *c = (thread_cond_t *)calloc(sizeof(*c), 1); c->mutex = CreateMutex(NULL, FALSE, NULL); c->sem = Thread_CreateSemaphore(0); c->done = Thread_CreateSemaphore(0); c->waiting = 0; c->signals = 0; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond create %s:%i\n" , c, filename, fileline); #endif return c; } void _Thread_DestroyCond(void *cond, const char *filename, int fileline) { thread_cond_t *c = (thread_cond_t *)cond; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond destroy %s:%i\n" , cond, filename, fileline); #endif Thread_DestroySemaphore(c->sem); Thread_DestroySemaphore(c->done); CloseHandle(c->mutex); } int _Thread_CondSignal(void *cond, const char *filename, int fileline) { thread_cond_t *c = (thread_cond_t *)cond; int n; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond signal %s:%i\n" , cond, filename, fileline); #endif WaitForSingleObject(c->mutex, INFINITE); n = c->waiting - c->signals; if (n > 0) { c->signals++; Thread_PostSemaphore(c->sem); } ReleaseMutex(c->mutex); if (n > 0) Thread_WaitSemaphore(c->done, INFINITE); return 0; } int _Thread_CondBroadcast(void *cond, const char *filename, int fileline) { thread_cond_t *c = (thread_cond_t *)cond; int i = 0; int n = 0; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond broadcast %s:%i\n" , cond, filename, fileline); #endif WaitForSingleObject(c->mutex, INFINITE); n = c->waiting - c->signals; if (n > 0) { c->signals += n; for (i = 0;i < n;i++) Thread_PostSemaphore(c->sem); } ReleaseMutex(c->mutex); for (i = 0;i < n;i++) Thread_WaitSemaphore(c->done, INFINITE); return 0; } int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline) { thread_cond_t *c = (thread_cond_t *)cond; int waitresult; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond wait %s:%i\n" , cond, filename, fileline); #endif WaitForSingleObject(c->mutex, INFINITE); c->waiting++; ReleaseMutex(c->mutex); ReleaseMutex(mutex); waitresult = Thread_WaitSemaphore(c->sem, INFINITE); WaitForSingleObject(c->mutex, INFINITE); if (c->signals > 0) { if (waitresult > 0) Thread_WaitSemaphore(c->sem, INFINITE); Thread_PostSemaphore(c->done); c->signals--; } c->waiting--; ReleaseMutex(c->mutex); WaitForSingleObject(mutex, INFINITE); return waitresult; } typedef struct threadwrapper_s { HANDLE handle; unsigned int threadid; int result; int (*fn)(void *); void *data; } threadwrapper_t; unsigned int __stdcall Thread_WrapperFunc(void *d) { threadwrapper_t *w = (threadwrapper_t *)d; w->result = w->fn(w->data); _endthreadex(w->result); return w->result; } void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline) { threadwrapper_t *w = (threadwrapper_t *)calloc(sizeof(*w), 1); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread create %s:%i\n" , w, filename, fileline); #endif w->fn = fn; w->data = data; w->threadid = 0; w->result = 0; w->handle = (HANDLE)_beginthreadex(NULL, 0, Thread_WrapperFunc, (void *)w, 0, &w->threadid); return (void *)w; } int _Thread_WaitThread(void *d, int retval, const char *filename, int fileline) { threadwrapper_t *w = (threadwrapper_t *)d; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread wait %s:%i\n" , w, filename, fileline); #endif WaitForSingleObject(w->handle, INFINITE); CloseHandle(w->handle); retval = w->result; free(w); return retval; } // standard barrier implementation using conds and mutexes // see: http://www.howforge.com/implementing-barrier-in-pthreads typedef struct { unsigned int needed; unsigned int called; void *mutex; void *cond; } barrier_t; void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) Z_Malloc(sizeof(barrier_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); #endif b->needed = count; b->called = 0; b->mutex = Thread_CreateMutex(); b->cond = Thread_CreateCond(); return (void *) b; } void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); #endif Thread_DestroyMutex(b->mutex); Thread_DestroyCond(b->cond); } void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); #endif Thread_LockMutex(b->mutex); b->called++; if (b->called == b->needed) { b->called = 0; Thread_CondBroadcast(b->cond); } else { do { Thread_CondWait(b->cond, b->mutex); } while(b->called); } Thread_UnlockMutex(b->mutex); } darkplaces/lhnet.c0000664000175000017500000012107313067716220013445 0ustar kalevkalev // Written by Forest Hale 2003-06-15 and placed into public domain. #ifdef WIN32 #ifdef _MSC_VER #pragma comment(lib, "ws2_32.lib") #endif # ifndef NOSUPPORTIPV6 // Windows XP or higher is required for getaddrinfo, but the inclusion of wspiapi provides fallbacks for older versions # define _WIN32_WINNT 0x0501 # endif # include # include # ifdef USE_WSPIAPI_H # include # endif #endif #ifndef STANDALONETEST #include "quakedef.h" #endif #include #include #include #include #ifndef WIN32 #include #include #include #include #include #include #include #include #ifndef NOSUPPORTIPV6 #include #endif #endif #ifdef __MORPHOS__ #include #endif // for Z_Malloc/Z_Free in quake #ifndef STANDALONETEST #include "zone.h" #include "sys.h" #include "netconn.h" #else #define Con_Print printf #define Con_Printf printf #define Z_Malloc malloc #define Z_Free free #endif #include "lhnet.h" #if defined(WIN32) // as of Visual Studio 2015, EWOULDBLOCK and ECONNREFUSED are real things, with different values than we want when talking to WinSock, so we have to undef them here or change the rest of the code. #undef EWOULDBLOCK #undef ECONNREFUSED #define EWOULDBLOCK WSAEWOULDBLOCK #define ECONNREFUSED WSAECONNREFUSED #define SOCKETERRNO WSAGetLastError() #define IOC_VENDOR 0x18000000 #define _WSAIOW(x,y) (IOC_IN|(x)|(y)) #define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR,12) #define SOCKLEN_T int #elif defined(__MORPHOS__) #define ioctlsocket IoctlSocket #define closesocket CloseSocket #define SOCKETERRNO Errno() #define SOCKLEN_T int #else #define ioctlsocket ioctl #define closesocket close #define SOCKETERRNO errno #define SOCKLEN_T socklen_t #endif #ifdef MSG_DONTWAIT #define LHNET_RECVFROM_FLAGS MSG_DONTWAIT #define LHNET_SENDTO_FLAGS 0 #else #define LHNET_RECVFROM_FLAGS 0 #define LHNET_SENDTO_FLAGS 0 #endif typedef struct lhnetaddressnative_s { lhnetaddresstype_t addresstype; int port; union { struct sockaddr sock; struct sockaddr_in in; #ifndef NOSUPPORTIPV6 struct sockaddr_in6 in6; #endif } addr; } lhnetaddressnative_t; // to make LHNETADDRESS_FromString resolve repeated hostnames faster, cache them #define MAX_NAMECACHE 64 static struct namecache_s { lhnetaddressnative_t address; double expirationtime; char name[64]; } namecache[MAX_NAMECACHE]; static int namecacheposition = 0; int LHNETADDRESS_FromPort(lhnetaddress_t *vaddress, lhnetaddresstype_t addresstype, int port) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; if (!address) return 0; switch(addresstype) { default: break; case LHNETADDRESSTYPE_LOOP: // local:port (loopback) memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_LOOP; address->port = port; return 1; case LHNETADDRESSTYPE_INET4: // 0.0.0.0:port (INADDR_ANY, binds to all interfaces) memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_INET4; address->port = port; address->addr.in.sin_family = AF_INET; address->addr.in.sin_port = htons((unsigned short)port); return 1; #ifndef NOSUPPORTIPV6 case LHNETADDRESSTYPE_INET6: // [0:0:0:0:0:0:0:0]:port (IN6ADDR_ANY, binds to all interfaces) memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_INET6; address->port = port; address->addr.in6.sin6_family = AF_INET6; address->addr.in6.sin6_port = htons((unsigned short)port); return 1; #endif } return 0; } #ifndef NOSUPPORTIPV6 static int LHNETADDRESS_Resolve(lhnetaddressnative_t *address, const char *name, int port) { char port_buff [16]; struct addrinfo hints; struct addrinfo* addrinf; int err; dpsnprintf (port_buff, sizeof (port_buff), "%d", port); port_buff[sizeof (port_buff) - 1] = '\0'; memset(&hints, 0, sizeof (hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; //hints.ai_flags = AI_PASSIVE; err = getaddrinfo(name, port_buff, &hints, &addrinf); if (err != 0 || addrinf == NULL) return 0; if (addrinf->ai_addr->sa_family != AF_INET6 && addrinf->ai_addr->sa_family != AF_INET) { freeaddrinfo (addrinf); return 0; } // great it worked if (addrinf->ai_addr->sa_family == AF_INET6) { address->addresstype = LHNETADDRESSTYPE_INET6; memcpy(&address->addr.in6, addrinf->ai_addr, sizeof(address->addr.in6)); } else { address->addresstype = LHNETADDRESSTYPE_INET4; memcpy(&address->addr.in, addrinf->ai_addr, sizeof(address->addr.in)); } address->port = port; freeaddrinfo (addrinf); return 1; } int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; int i, port, d1, d2, d3, d4, resolved; size_t namelen; unsigned char *a; char name[128]; #ifdef STANDALONETEST char string2[128]; #endif const char* addr_start; const char* addr_end = NULL; const char* port_name = NULL; int addr_family = AF_UNSPEC; if (!address || !string || !*string) return 0; memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_NONE; port = 0; // If it's a bracketed IPv6 address if (string[0] == '[') { const char* end_bracket = strchr(string, ']'); if (end_bracket == NULL) return 0; if (end_bracket[1] == ':') port_name = end_bracket + 2; else if (end_bracket[1] != '\0') return 0; addr_family = AF_INET6; addr_start = &string[1]; addr_end = end_bracket; } else { const char* first_colon; addr_start = string; // If it's a numeric non-bracket IPv6 address (-> no port), // or it's a numeric IPv4 address, or a name, with a port first_colon = strchr(string, ':'); if (first_colon != NULL) { const char* last_colon = strrchr(first_colon + 1, ':'); // If it's an numeric IPv4 address, or a name, with a port if (last_colon == NULL) { addr_end = first_colon; port_name = first_colon + 1; } else addr_family = AF_INET6; } } if (addr_end != NULL) namelen = addr_end - addr_start; else namelen = strlen (addr_start); if (namelen >= sizeof(name)) namelen = sizeof(name) - 1; memcpy (name, addr_start, namelen); name[namelen] = 0; if (port_name) port = atoi(port_name); if (port == 0) port = defaultport; // handle loopback if (!strcmp(name, "local")) { address->addresstype = LHNETADDRESSTYPE_LOOP; address->port = port; return 1; } // try to parse as dotted decimal ipv4 address first // note this supports partial ip addresses d1 = d2 = d3 = d4 = 0; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif if (addr_family != AF_INET6 && sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) { // parsed a valid ipv4 address address->addresstype = LHNETADDRESSTYPE_INET4; address->port = port; address->addr.in.sin_family = AF_INET; address->addr.in.sin_port = htons((unsigned short)port); a = (unsigned char *)&address->addr.in.sin_addr; a[0] = d1; a[1] = d2; a[2] = d3; a[3] = d4; #ifdef STANDALONETEST LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); #endif return 1; } for (i = 0;i < MAX_NAMECACHE;i++) if (!strcmp(namecache[i].name, name)) break; #ifdef STANDALONETEST if (i < MAX_NAMECACHE) #else if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) #endif { *address = namecache[i].address; address->port = port; if (address->addresstype == LHNETADDRESSTYPE_INET6) { address->addr.in6.sin6_port = htons((unsigned short)port); return 1; } else if (address->addresstype == LHNETADDRESSTYPE_INET4) { address->addr.in.sin_port = htons((unsigned short)port); return 1; } return 0; } for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) namecache[namecacheposition].name[i] = name[i]; namecache[namecacheposition].name[i] = 0; #ifndef STANDALONETEST namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours #endif // try resolving the address (handles dns and other ip formats) resolved = LHNETADDRESS_Resolve(address, name, port); if (resolved) { #ifdef STANDALONETEST const char *protoname; switch (address->addresstype) { case LHNETADDRESSTYPE_INET6: protoname = "ipv6"; break; case LHNETADDRESSTYPE_INET4: protoname = "ipv4"; break; default: protoname = "UNKNOWN"; break; } LHNETADDRESS_ToString(vaddress, string2, sizeof(string2), 1); Con_Printf("LHNETADDRESS_Resolve(\"%s\") returned %s address %s\n", string, protoname, string2); #endif namecache[namecacheposition].address = *address; } else { #ifdef STANDALONETEST printf("name resolution failed on address \"%s\"\n", name); #endif namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; } namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; return resolved; } #else int LHNETADDRESS_FromString(lhnetaddress_t *vaddress, const char *string, int defaultport) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; int i, port, namelen, d1, d2, d3, d4; struct hostent *hostentry; unsigned char *a; const char *colon; char name[128]; #ifdef STANDALONETEST char string2[128]; #endif if (!address || !string || !*string) return 0; memset(address, 0, sizeof(*address)); address->addresstype = LHNETADDRESSTYPE_NONE; port = 0; colon = strrchr(string, ':'); if (colon && (colon == strchr(string, ':') || (string[0] == '[' && colon - string > 0 && colon[-1] == ']'))) // EITHER: colon is the ONLY colon OR: colon comes after [...] delimited IPv6 address // fixes misparsing of IPv6 addresses without port { port = atoi(colon + 1); } else colon = string + strlen(string); if (port == 0) port = defaultport; namelen = colon - string; if (namelen > 127) namelen = 127; if (string[0] == '[' && namelen > 0 && string[namelen-1] == ']') // ipv6 { string++; namelen -= 2; } memcpy(name, string, namelen); name[namelen] = 0; // handle loopback if (!strcmp(name, "local")) { address->addresstype = LHNETADDRESSTYPE_LOOP; address->port = port; return 1; } // try to parse as dotted decimal ipv4 address first // note this supports partial ip addresses d1 = d2 = d3 = d4 = 0; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif if (sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4) >= 1 && (unsigned int)d1 < 256 && (unsigned int)d2 < 256 && (unsigned int)d3 < 256 && (unsigned int)d4 < 256) { // parsed a valid ipv4 address address->addresstype = LHNETADDRESSTYPE_INET4; address->port = port; address->addr.in.sin_family = AF_INET; address->addr.in.sin_port = htons((unsigned short)port); a = (unsigned char *)&address->addr.in.sin_addr; a[0] = d1; a[1] = d2; a[2] = d3; a[3] = d4; #ifdef STANDALONETEST LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); printf("manual parsing of ipv4 dotted decimal address \"%s\" successful: %s\n", string, string2); #endif return 1; } for (i = 0;i < MAX_NAMECACHE;i++) if (!strcmp(namecache[i].name, name)) break; #ifdef STANDALONETEST if (i < MAX_NAMECACHE) #else if (i < MAX_NAMECACHE && realtime < namecache[i].expirationtime) #endif { *address = namecache[i].address; address->port = port; if (address->addresstype == LHNETADDRESSTYPE_INET6) { #ifndef NOSUPPORTIPV6 address->addr.in6.sin6_port = htons((unsigned short)port); return 1; #endif } else if (address->addresstype == LHNETADDRESSTYPE_INET4) { address->addr.in.sin_port = htons((unsigned short)port); return 1; } return 0; } // try gethostbyname (handles dns and other ip formats) hostentry = gethostbyname(name); if (hostentry) { if (hostentry->h_addrtype == AF_INET6) { #ifndef NOSUPPORTIPV6 // great it worked address->addresstype = LHNETADDRESSTYPE_INET6; address->port = port; address->addr.in6.sin6_family = hostentry->h_addrtype; address->addr.in6.sin6_port = htons((unsigned short)port); memcpy(&address->addr.in6.sin6_addr, hostentry->h_addr_list[0], sizeof(address->addr.in6.sin6_addr)); for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) namecache[namecacheposition].name[i] = name[i]; namecache[namecacheposition].name[i] = 0; #ifndef STANDALONETEST namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours #endif namecache[namecacheposition].address = *address; namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; #ifdef STANDALONETEST LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); printf("gethostbyname(\"%s\") returned ipv6 address %s\n", string, string2); #endif return 1; #endif } else if (hostentry->h_addrtype == AF_INET) { // great it worked address->addresstype = LHNETADDRESSTYPE_INET4; address->port = port; address->addr.in.sin_family = hostentry->h_addrtype; address->addr.in.sin_port = htons((unsigned short)port); memcpy(&address->addr.in.sin_addr, hostentry->h_addr_list[0], sizeof(address->addr.in.sin_addr)); for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) namecache[namecacheposition].name[i] = name[i]; namecache[namecacheposition].name[i] = 0; #ifndef STANDALONETEST namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours #endif namecache[namecacheposition].address = *address; namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; #ifdef STANDALONETEST LHNETADDRESS_ToString(address, string2, sizeof(string2), 1); printf("gethostbyname(\"%s\") returned ipv4 address %s\n", string, string2); #endif return 1; } } #ifdef STANDALONETEST printf("gethostbyname failed on address \"%s\"\n", name); #endif for (i = 0;i < (int)sizeof(namecache[namecacheposition].name)-1 && name[i];i++) namecache[namecacheposition].name[i] = name[i]; namecache[namecacheposition].name[i] = 0; #ifndef STANDALONETEST namecache[namecacheposition].expirationtime = realtime + 12 * 3600; // 12 hours #endif namecache[namecacheposition].address.addresstype = LHNETADDRESSTYPE_NONE; namecacheposition = (namecacheposition + 1) % MAX_NAMECACHE; return 0; } #endif int LHNETADDRESS_ToString(const lhnetaddress_t *vaddress, char *string, int stringbuffersize, int includeport) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; const unsigned char *a; if (!address || !string || stringbuffersize < 1) return 0; *string = 0; switch(address->addresstype) { default: break; case LHNETADDRESSTYPE_LOOP: if (includeport) { if (stringbuffersize >= 12) { dpsnprintf(string, stringbuffersize, "local:%d", address->port); return 1; } } else { if (stringbuffersize >= 6) { memcpy(string, "local", 6); return 1; } } break; case LHNETADDRESSTYPE_INET4: a = (const unsigned char *)(&address->addr.in.sin_addr); if (includeport) { if (stringbuffersize >= 22) { dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d:%d", a[0], a[1], a[2], a[3], address->port); return 1; } } else { if (stringbuffersize >= 16) { dpsnprintf(string, stringbuffersize, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); return 1; } } break; #ifndef NOSUPPORTIPV6 case LHNETADDRESSTYPE_INET6: a = (const unsigned char *)(&address->addr.in6.sin6_addr); if (includeport) { if (stringbuffersize >= 88) { dpsnprintf(string, stringbuffersize, "[%x:%x:%x:%x:%x:%x:%x:%x]:%d", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15], address->port); return 1; } } else { if (stringbuffersize >= 80) { dpsnprintf(string, stringbuffersize, "%x:%x:%x:%x:%x:%x:%x:%x", a[0] * 256 + a[1], a[2] * 256 + a[3], a[4] * 256 + a[5], a[6] * 256 + a[7], a[8] * 256 + a[9], a[10] * 256 + a[11], a[12] * 256 + a[13], a[14] * 256 + a[15]); return 1; } } break; #endif } return 0; } int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address) { if (address) return address->addresstype; else return LHNETADDRESSTYPE_NONE; } const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *vaddress, char *ifname, size_t ifnamelength) { #ifndef NOSUPPORTIPV6 lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; if (address && address->addresstype == LHNETADDRESSTYPE_INET6) { #ifndef _WIN32 if (if_indextoname(address->addr.in6.sin6_scope_id, ifname) == ifname) return ifname; #else // The Win32 API doesn't have if_indextoname() until Windows Vista, // but luckily it just uses the interface ID as the interface name if (dpsnprintf(ifname, ifnamelength, "%lu", address->addr.in6.sin6_scope_id) > 0) return ifname; #endif } #endif return NULL; } int LHNETADDRESS_GetPort(const lhnetaddress_t *address) { if (!address) return -1; return address->port; } int LHNETADDRESS_SetPort(lhnetaddress_t *vaddress, int port) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; if (!address) return 0; address->port = port; switch(address->addresstype) { case LHNETADDRESSTYPE_LOOP: return 1; case LHNETADDRESSTYPE_INET4: address->addr.in.sin_port = htons((unsigned short)port); return 1; #ifndef NOSUPPORTIPV6 case LHNETADDRESSTYPE_INET6: address->addr.in6.sin6_port = htons((unsigned short)port); return 1; #endif default: return 0; } } int LHNETADDRESS_Compare(const lhnetaddress_t *vaddress1, const lhnetaddress_t *vaddress2) { lhnetaddressnative_t *address1 = (lhnetaddressnative_t *)vaddress1; lhnetaddressnative_t *address2 = (lhnetaddressnative_t *)vaddress2; if (!address1 || !address2) return 1; if (address1->addresstype != address2->addresstype) return 1; switch(address1->addresstype) { case LHNETADDRESSTYPE_LOOP: if (address1->port != address2->port) return -1; return 0; case LHNETADDRESSTYPE_INET4: if (address1->addr.in.sin_family != address2->addr.in.sin_family) return 1; if (memcmp(&address1->addr.in.sin_addr, &address2->addr.in.sin_addr, sizeof(address1->addr.in.sin_addr))) return 1; if (address1->port != address2->port) return -1; return 0; #ifndef NOSUPPORTIPV6 case LHNETADDRESSTYPE_INET6: if (address1->addr.in6.sin6_family != address2->addr.in6.sin6_family) return 1; if (memcmp(&address1->addr.in6.sin6_addr, &address2->addr.in6.sin6_addr, sizeof(address1->addr.in6.sin6_addr))) return 1; if (address1->port != address2->port) return -1; return 0; #endif default: return 1; } } typedef struct lhnetpacket_s { void *data; int length; int sourceport; int destinationport; time_t timeout; #ifndef STANDALONETEST double sentdoubletime; #endif struct lhnetpacket_s *next, *prev; } lhnetpacket_t; static int lhnet_active; static lhnetsocket_t lhnet_socketlist; static lhnetpacket_t lhnet_packetlist; static int lhnet_default_dscp = 0; #ifdef WIN32 static int lhnet_didWSAStartup = 0; static WSADATA lhnet_winsockdata; #endif void LHNET_Init(void) { if (lhnet_active) return; lhnet_socketlist.next = lhnet_socketlist.prev = &lhnet_socketlist; lhnet_packetlist.next = lhnet_packetlist.prev = &lhnet_packetlist; lhnet_active = 1; #ifdef WIN32 lhnet_didWSAStartup = !WSAStartup(MAKEWORD(1, 1), &lhnet_winsockdata); if (!lhnet_didWSAStartup) Con_Print("LHNET_Init: WSAStartup failed, networking disabled\n"); #endif } int LHNET_DefaultDSCP(int dscp) { #ifdef IP_TOS int prev = lhnet_default_dscp; if(dscp >= 0) lhnet_default_dscp = dscp; return prev; #else return -1; #endif } void LHNET_Shutdown(void) { lhnetpacket_t *p; if (!lhnet_active) return; while (lhnet_socketlist.next != &lhnet_socketlist) LHNET_CloseSocket(lhnet_socketlist.next); while (lhnet_packetlist.next != &lhnet_packetlist) { p = lhnet_packetlist.next; p->prev->next = p->next; p->next->prev = p->prev; Z_Free(p); } #ifdef WIN32 if (lhnet_didWSAStartup) { lhnet_didWSAStartup = 0; WSACleanup(); } #endif lhnet_active = 0; } static const char *LHNETPRIVATE_StrError(void) { #ifdef WIN32 int i = WSAGetLastError(); switch (i) { case WSAEINTR: return "WSAEINTR"; case WSAEBADF: return "WSAEBADF"; case WSAEACCES: return "WSAEACCES"; case WSAEFAULT: return "WSAEFAULT"; case WSAEINVAL: return "WSAEINVAL"; case WSAEMFILE: return "WSAEMFILE"; case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; case WSAEINPROGRESS: return "WSAEINPROGRESS"; case WSAEALREADY: return "WSAEALREADY"; case WSAENOTSOCK: return "WSAENOTSOCK"; case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; case WSAEMSGSIZE: return "WSAEMSGSIZE"; case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; case WSAEADDRINUSE: return "WSAEADDRINUSE"; case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; case WSAENETDOWN: return "WSAENETDOWN"; case WSAENETUNREACH: return "WSAENETUNREACH"; case WSAENETRESET: return "WSAENETRESET"; case WSAECONNABORTED: return "WSAECONNABORTED"; case WSAECONNRESET: return "WSAECONNRESET"; case WSAENOBUFS: return "WSAENOBUFS"; case WSAEISCONN: return "WSAEISCONN"; case WSAENOTCONN: return "WSAENOTCONN"; case WSAESHUTDOWN: return "WSAESHUTDOWN"; case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; case WSAETIMEDOUT: return "WSAETIMEDOUT"; case WSAECONNREFUSED: return "WSAECONNREFUSED"; case WSAELOOP: return "WSAELOOP"; case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; case WSAEHOSTUNREACH: return "WSAEHOSTUNREACH"; case WSAENOTEMPTY: return "WSAENOTEMPTY"; case WSAEPROCLIM: return "WSAEPROCLIM"; case WSAEUSERS: return "WSAEUSERS"; case WSAEDQUOT: return "WSAEDQUOT"; case WSAESTALE: return "WSAESTALE"; case WSAEREMOTE: return "WSAEREMOTE"; case WSAEDISCON: return "WSAEDISCON"; case 0: return "no error"; default: return "unknown WSAE error"; } #else return strerror(errno); #endif } void LHNET_SleepUntilPacket_Microseconds(int microseconds) { #ifdef FD_SET fd_set fdreadset; struct timeval tv; int lastfd; lhnetsocket_t *s; FD_ZERO(&fdreadset); lastfd = 0; for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) { if (s->address.addresstype == LHNETADDRESSTYPE_INET4 || s->address.addresstype == LHNETADDRESSTYPE_INET6) { if (lastfd < s->inetsocket) lastfd = s->inetsocket; #if defined(WIN32) && !defined(_MSC_VER) FD_SET((int)s->inetsocket, &fdreadset); #else FD_SET((unsigned int)s->inetsocket, &fdreadset); #endif } } tv.tv_sec = microseconds / 1000000; tv.tv_usec = microseconds % 1000000; select(lastfd + 1, &fdreadset, NULL, NULL, &tv); #else Sys_Sleep(microseconds); #endif } lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address) { lhnetsocket_t *lhnetsocket, *s; if (!address) return NULL; lhnetsocket = (lhnetsocket_t *)Z_Malloc(sizeof(*lhnetsocket)); if (lhnetsocket) { memset(lhnetsocket, 0, sizeof(*lhnetsocket)); lhnetsocket->address = *address; switch(lhnetsocket->address.addresstype) { case LHNETADDRESSTYPE_LOOP: if (lhnetsocket->address.port == 0) { // allocate a port dynamically // this search will always terminate because there is never // an allocated socket with port 0, so if the number wraps it // will find the port is unused, and then refuse to use port // 0, causing an intentional failure condition lhnetsocket->address.port = 1024; for (;;) { for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) break; if (s == &lhnet_socketlist) break; lhnetsocket->address.port++; } } // check if the port is available for (s = lhnet_socketlist.next;s != &lhnet_socketlist;s = s->next) if (s->address.addresstype == lhnetsocket->address.addresstype && s->address.port == lhnetsocket->address.port) break; if (s == &lhnet_socketlist && lhnetsocket->address.port != 0) { lhnetsocket->next = &lhnet_socketlist; lhnetsocket->prev = lhnetsocket->next->prev; lhnetsocket->next->prev = lhnetsocket; lhnetsocket->prev->next = lhnetsocket; return lhnetsocket; } break; case LHNETADDRESSTYPE_INET4: #ifndef NOSUPPORTIPV6 case LHNETADDRESSTYPE_INET6: #endif #ifdef WIN32 if (lhnet_didWSAStartup) { #endif #ifndef NOSUPPORTIPV6 if ((lhnetsocket->inetsocket = socket(address->addresstype == LHNETADDRESSTYPE_INET6 ? PF_INET6 : PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) #else if ((lhnetsocket->inetsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) #endif { #ifdef WIN32 u_long _false = 0; #endif #ifdef MSG_DONTWAIT if (1) #else #ifdef WIN32 u_long _true = 1; #else char _true = 1; #endif if (ioctlsocket(lhnetsocket->inetsocket, FIONBIO, &_true) != -1) #endif { #ifdef IPV6_V6ONLY // We need to set this flag to tell the OS that we only listen on IPv6. If we don't // most OSes will create a dual-protocol socket that also listens on IPv4. In this case // if an IPv4 socket is already bound to the port we want, our bind() call will fail. int ipv6_only = 1; if (address->addresstype != LHNETADDRESSTYPE_INET6 || setsockopt (lhnetsocket->inetsocket, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&ipv6_only, sizeof(ipv6_only)) == 0 #ifdef WIN32 // The Win32 API only supports IPV6_V6ONLY since Windows Vista, but fortunately // the default value is what we want on Win32 anyway (IPV6_V6ONLY = true) || SOCKETERRNO == WSAENOPROTOOPT #endif ) #endif { lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; SOCKLEN_T namelen; int bindresult; #if defined(SOL_RFC1149) && defined(RFC1149_1149ONLY) // we got reports of massive lags when this protocol was chosen as transport // so better turn it off { int rfc1149only = 0; int rfc1149enabled = 0; if(setsockopt(lhnetsocket->inetsocket, SOL_RFC1149, RFC1149_1149ONLY, &rfc1149only)) Con_Printf("LHNET_OpenSocket_Connectionless: warning: setsockopt(RFC1149_1149ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); if(setsockopt(lhnetsocket->inetsocket, SOL_RFC1149, RFC1149_ENABLED, &rfc1149enabled)) Con_Printf("LHNET_OpenSocket_Connectionless: warning: setsockopt(RFC1149_ENABLED) returned error: %s\n", LHNETPRIVATE_StrError()); } #endif #ifndef NOSUPPORTIPV6 if (address->addresstype == LHNETADDRESSTYPE_INET6) { namelen = sizeof(localaddress->addr.in6); bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); if (bindresult != -1) { if (getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen)) { // If getsockname failed, we can assume the bound socket is useless. bindresult = -1; } } } else #endif { namelen = sizeof(localaddress->addr.in); bindresult = bind(lhnetsocket->inetsocket, &localaddress->addr.sock, namelen); if (bindresult != -1) { if (getsockname(lhnetsocket->inetsocket, &localaddress->addr.sock, &namelen)) { // If getsockname failed, we can assume the bound socket is useless. bindresult = -1; } } } if (bindresult != -1) { int i = 1; // enable broadcast on this socket setsockopt(lhnetsocket->inetsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i)); #ifdef IP_TOS { // enable DSCP for ToS support int tos = lhnet_default_dscp << 2; if (setsockopt(lhnetsocket->inetsocket, IPPROTO_IP, IP_TOS, (char *) &tos, sizeof(tos))) { // Error in setsockopt - fine, we'll simply set no TOS then. } } #endif lhnetsocket->next = &lhnet_socketlist; lhnetsocket->prev = lhnetsocket->next->prev; lhnetsocket->next->prev = lhnetsocket; lhnetsocket->prev->next = lhnetsocket; #ifdef WIN32 if (ioctlsocket(lhnetsocket->inetsocket, SIO_UDP_CONNRESET, &_false) == -1) Con_DPrintf("LHNET_OpenSocket_Connectionless: ioctlsocket SIO_UDP_CONNRESET returned error: %s\n", LHNETPRIVATE_StrError()); #endif return lhnetsocket; } else Con_Printf("LHNET_OpenSocket_Connectionless: bind returned error: %s\n", LHNETPRIVATE_StrError()); } #ifdef IPV6_V6ONLY else Con_Printf("LHNET_OpenSocket_Connectionless: setsockopt(IPV6_V6ONLY) returned error: %s\n", LHNETPRIVATE_StrError()); #endif } else Con_Printf("LHNET_OpenSocket_Connectionless: ioctlsocket returned error: %s\n", LHNETPRIVATE_StrError()); closesocket(lhnetsocket->inetsocket); } else Con_Printf("LHNET_OpenSocket_Connectionless: socket returned error: %s\n", LHNETPRIVATE_StrError()); #ifdef WIN32 } else Con_Print("LHNET_OpenSocket_Connectionless: can't open a socket (WSAStartup failed during LHNET_Init)\n"); #endif break; default: break; } Z_Free(lhnetsocket); } return NULL; } void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket) { if (lhnetsocket) { // unlink from socket list if (lhnetsocket->next == NULL) return; // invalid! lhnetsocket->next->prev = lhnetsocket->prev; lhnetsocket->prev->next = lhnetsocket->next; lhnetsocket->next = NULL; lhnetsocket->prev = NULL; // no special close code for loopback, just inet if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4 || lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) { closesocket(lhnetsocket->inetsocket); } Z_Free(lhnetsocket); } } lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock) { if (sock) return &sock->address; else return NULL; } int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *vaddress) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; int value = 0; if (!lhnetsocket || !address || !content || maxcontentlength < 1) return -1; if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) { time_t currenttime; lhnetpacket_t *p, *pnext; // scan for any old packets to timeout while searching for a packet // that is waiting to be delivered to this socket currenttime = time(NULL); for (p = lhnet_packetlist.next;p != &lhnet_packetlist;p = pnext) { pnext = p->next; if (p->timeout < currenttime) { // unlink and free p->next->prev = p->prev; p->prev->next = p->next; Z_Free(p); continue; } #ifndef STANDALONETEST if (cl_netlocalping.value && (realtime - cl_netlocalping.value * (1.0 / 2000.0)) < p->sentdoubletime) continue; #endif if (value == 0 && p->destinationport == lhnetsocket->address.port) { if (p->length <= maxcontentlength) { lhnetaddressnative_t *localaddress = (lhnetaddressnative_t *)&lhnetsocket->address; *address = *localaddress; address->port = p->sourceport; memcpy(content, p->data, p->length); value = p->length; } else value = -1; // unlink and free p->next->prev = p->prev; p->prev->next = p->next; Z_Free(p); } } } else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) { SOCKLEN_T inetaddresslength; address->addresstype = LHNETADDRESSTYPE_NONE; inetaddresslength = sizeof(address->addr.in); value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); if (value > 0) { address->addresstype = LHNETADDRESSTYPE_INET4; address->port = ntohs(address->addr.in.sin_port); return value; } else if (value < 0) { int e = SOCKETERRNO; if (e == EWOULDBLOCK) return 0; switch (e) { case ECONNREFUSED: Con_Print("Connection refused\n"); return 0; } Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); } } #ifndef NOSUPPORTIPV6 else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) { SOCKLEN_T inetaddresslength; address->addresstype = LHNETADDRESSTYPE_NONE; inetaddresslength = sizeof(address->addr.in6); value = recvfrom(lhnetsocket->inetsocket, (char *)content, maxcontentlength, LHNET_RECVFROM_FLAGS, &address->addr.sock, &inetaddresslength); if (value > 0) { address->addresstype = LHNETADDRESSTYPE_INET6; address->port = ntohs(address->addr.in6.sin6_port); return value; } else if (value == -1) { int e = SOCKETERRNO; if (e == EWOULDBLOCK) return 0; switch (e) { case ECONNREFUSED: Con_Print("Connection refused\n"); return 0; } Con_DPrintf("LHNET_Read: recvfrom returned error: %s\n", LHNETPRIVATE_StrError()); } } #endif return value; } int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *vaddress) { lhnetaddressnative_t *address = (lhnetaddressnative_t *)vaddress; int value = -1; if (!lhnetsocket || !address || !content || contentlength < 1) return -1; if (lhnetsocket->address.addresstype != address->addresstype) return -1; if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_LOOP) { lhnetpacket_t *p; p = (lhnetpacket_t *)Z_Malloc(sizeof(*p) + contentlength); p->data = (void *)(p + 1); memcpy(p->data, content, contentlength); p->length = contentlength; p->sourceport = lhnetsocket->address.port; p->destinationport = address->port; p->timeout = time(NULL) + 10; p->next = &lhnet_packetlist; p->prev = p->next->prev; p->next->prev = p; p->prev->next = p; #ifndef STANDALONETEST p->sentdoubletime = realtime; #endif value = contentlength; } else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET4) { value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, LHNET_SENDTO_FLAGS, (struct sockaddr *)&address->addr.in, sizeof(struct sockaddr_in)); if (value == -1) { if (SOCKETERRNO == EWOULDBLOCK) return 0; Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); } } #ifndef NOSUPPORTIPV6 else if (lhnetsocket->address.addresstype == LHNETADDRESSTYPE_INET6) { value = sendto(lhnetsocket->inetsocket, (char *)content, contentlength, 0, (struct sockaddr *)&address->addr.in6, sizeof(struct sockaddr_in6)); if (value == -1) { if (SOCKETERRNO == EWOULDBLOCK) return 0; Con_DPrintf("LHNET_Write: sendto returned error: %s\n", LHNETPRIVATE_StrError()); } } #endif return value; } #ifdef STANDALONETEST int main(int argc, char **argv) { #if 1 char *buffer = "test", buffer2[1024]; int blen = strlen(buffer); int b2len = 1024; lhnetsocket_t *sock1; lhnetsocket_t *sock2; lhnetaddress_t myaddy1; lhnetaddress_t myaddy2; lhnetaddress_t myaddy3; lhnetaddress_t localhostaddy1; lhnetaddress_t localhostaddy2; int test1; int test2; printf("calling LHNET_Init\n"); LHNET_Init(); printf("calling LHNET_FromPort twice to create two local addresses\n"); LHNETADDRESS_FromPort(&myaddy1, LHNETADDRESSTYPE_INET4, 4000); LHNETADDRESS_FromPort(&myaddy2, LHNETADDRESSTYPE_INET4, 4001); LHNETADDRESS_FromString(&localhostaddy1, "127.0.0.1", 4000); LHNETADDRESS_FromString(&localhostaddy2, "127.0.0.1", 4001); printf("calling LHNET_OpenSocket_Connectionless twice to create two local sockets\n"); sock1 = LHNET_OpenSocket_Connectionless(&myaddy1); sock2 = LHNET_OpenSocket_Connectionless(&myaddy2); printf("calling LHNET_Write to send a packet from the first socket to the second socket\n"); test1 = LHNET_Write(sock1, buffer, blen, &localhostaddy2); printf("sleeping briefly\n"); #ifdef WIN32 Sleep (100); #else usleep (100000); #endif printf("calling LHNET_Read on the second socket to read the packet sent from the first socket\n"); test2 = LHNET_Read(sock2, buffer2, b2len - 1, &myaddy3); if (test2 > 0) Con_Printf("socket to socket test succeeded\n"); else Con_Printf("socket to socket test failed\n"); #ifdef WIN32 printf("press any key to exit\n"); getchar(); #endif printf("calling LHNET_Shutdown\n"); LHNET_Shutdown(); printf("exiting\n"); return 0; #else lhnetsocket_t *sock[16], *sendsock; int i; int numsockets; int count; int length; int port; time_t oldtime; time_t newtime; char *sendmessage; int sendmessagelength; lhnetaddress_t destaddress; lhnetaddress_t receiveaddress; lhnetaddress_t sockaddress[16]; char buffer[1536], addressstring[128], addressstring2[128]; if ((argc == 2 || argc == 5) && (port = atoi(argv[1])) >= 1 && port < 65535) { printf("calling LHNET_Init()\n"); LHNET_Init(); numsockets = 0; LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_LOOP, port); LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET4, port); LHNETADDRESS_FromPort(&sockaddress[numsockets++], LHNETADDRESSTYPE_INET6, port+1); sendsock = NULL; sendmessage = NULL; sendmessagelength = 0; for (i = 0;i < numsockets;i++) { LHNETADDRESS_ToString(&sockaddress[i], addressstring, sizeof(addressstring), 1); printf("calling LHNET_OpenSocket_Connectionless(<%s>)\n", addressstring); if ((sock[i] = LHNET_OpenSocket_Connectionless(&sockaddress[i]))) { LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); printf("opened socket successfully (address \"%s\")\n", addressstring2); } else { printf("failed to open socket\n"); if (i == 0) { LHNET_Shutdown(); return -1; } } } count = 0; if (argc == 5) { count = atoi(argv[2]); if (LHNETADDRESS_FromString(&destaddress, argv[3], -1)) { sendmessage = argv[4]; sendmessagelength = strlen(sendmessage); sendsock = NULL; for (i = 0;i < numsockets;i++) if (sock[i] && LHNETADDRESS_GetAddressType(&destaddress) == LHNETADDRESS_GetAddressType(&sockaddress[i])) sendsock = sock[i]; if (sendsock == NULL) { printf("Could not find an open socket matching the addresstype (%i) of destination address, switching to listen only mode\n", LHNETADDRESS_GetAddressType(&destaddress)); argc = 2; } } else { printf("LHNETADDRESS_FromString did not like the address \"%s\", switching to listen only mode\n", argv[3]); argc = 2; } } printf("started, now listening for \"exit\" on the opened sockets\n"); oldtime = time(NULL); for(;;) { #ifdef WIN32 Sleep(1); #else usleep(1); #endif for (i = 0;i < numsockets;i++) { if (sock[i]) { length = LHNET_Read(sock[i], buffer, sizeof(buffer), &receiveaddress); if (length < 0) printf("localsock read error: length < 0"); else if (length > 0 && length < (int)sizeof(buffer)) { buffer[length] = 0; LHNETADDRESS_ToString(&receiveaddress, addressstring, sizeof(addressstring), 1); LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); printf("received message \"%s\" from \"%s\" on socket \"%s\"\n", buffer, addressstring, addressstring2); if (!strcmp(buffer, "exit")) break; } } } if (i < numsockets) break; if (argc == 5 && count > 0) { newtime = time(NULL); if (newtime != oldtime) { LHNETADDRESS_ToString(&destaddress, addressstring, sizeof(addressstring), 1); LHNETADDRESS_ToString(LHNET_AddressFromSocket(sendsock), addressstring2, sizeof(addressstring2), 1); printf("calling LHNET_Write(<%s>, \"%s\", %i, <%s>)\n", addressstring2, sendmessage, sendmessagelength, addressstring); length = LHNET_Write(sendsock, sendmessage, sendmessagelength, &destaddress); if (length == sendmessagelength) printf("sent successfully\n"); else printf("LH_Write failed, returned %i (length of message was %i)\n", length, strlen(argv[4])); oldtime = newtime; count--; if (count <= 0) printf("Done sending, still listening for \"exit\"\n"); } } } for (i = 0;i < numsockets;i++) { if (sock[i]) { LHNETADDRESS_ToString(LHNET_AddressFromSocket(sock[i]), addressstring2, sizeof(addressstring2), 1); printf("calling LHNET_CloseSocket(<%s>)\n", addressstring2); LHNET_CloseSocket(sock[i]); } } printf("calling LHNET_Shutdown()\n"); LHNET_Shutdown(); return 0; } printf("Testing code for lhnet.c\nusage: lhnettest [ ]\n"); return -1; #endif } #endif darkplaces/svbsp.h0000664000175000017500000000551313067716222013477 0ustar kalevkalev // Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. // Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 #ifndef SVBSP_H #define SVBSP_H typedef struct svbsp_node_s { // notes: // leaf nodes are not stored, these are always structural nodes // (they always have a plane and two children) // children[] can be -1 for empty leaf or -2 for shadow leaf, >= 0 is node // parent can be -1 if this is the root node, >= 0 is a node int parent, children[2], padding; // node plane, splits space float plane[4]; } svbsp_node_t; typedef struct svbsp_s { // lightsource or view origin float origin[3]; // current number of nodes in the svbsp int numnodes; // how big the nodes array is int maxnodes; // first node is the root node svbsp_node_t *nodes; // non-zero indicates that an insertion failed because of lack of nodes int ranoutofnodes; // tree statistics // note: do not use multithreading when gathering statistics! // (the code updating these counters is not thread-safe, increments may // sometimes fail when multithreaded) int stat_occluders_rejected; int stat_occluders_accepted; int stat_occluders_fragments_rejected; int stat_occluders_fragments_accepted; int stat_queries_rejected; int stat_queries_accepted; int stat_queries_fragments_rejected; int stat_queries_fragments_accepted; } svbsp_t; // this function initializes a tree to prepare for polygon insertions // // the maxnodes needed for a given polygon set can vary wildly, if there are // not enough maxnodes then later polygons will not be inserted and the field // svbsp_t->ranoutofnodes will be non-zero // // as a rule of thumb the minimum nodes needed for a polygon set is // numpolygons * (averagepolygonvertices + 1) void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes); // this function tests if any part of a polygon is not in shadow, and returns // non-zero if the polygon is not completely shadowed // // returns 0 if the polygon was rejected (not facing origin or no points) // returns 1 if all of the polygon is in shadow // returns 2 if all of the polygon is unshadowed // returns 3 if some of the polygon is shadowed and some unshadowed // // it also can add a new shadow volume (insertoccluder parameter) // // additionally it calls your fragmentcallback on each unshadowed clipped // part of the polygon // (beware that polygons often get split heavily, even if entirely unshadowed) // // thread-safety notes: do not multi-thread insertions! int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1); #endif darkplaces/snd_main.h0000664000175000017500000001752613067716222014141 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SND_MAIN_H #define SND_MAIN_H #include "sound.h" typedef struct snd_format_s { unsigned int speed; unsigned short width; unsigned short channels; } snd_format_t; typedef struct snd_buffer_s { snd_format_t format; unsigned int nbframes; // current size, in sample frames unsigned int maxframes; // max size (buffer size), in sample frames unsigned char samples[4]; // variable sized } snd_buffer_t; typedef struct snd_ringbuffer_s { snd_format_t format; unsigned char* ring; unsigned int maxframes; // max size (buffer size), in sample frames unsigned int startframe; // index of the first frame in the buffer // if startframe == endframe, the bufffer is empty unsigned int endframe; // index of the first EMPTY frame in the "ring" buffer // may be smaller than startframe if the "ring" buffer has wrapped } snd_ringbuffer_t; // sfx_t flags #define SFXFLAG_NONE 0 #define SFXFLAG_FILEMISSING (1 << 0) // wasn't able to load the associated sound file #define SFXFLAG_LEVELSOUND (1 << 1) // the sfx is part of the server or client precache list for this level #define SFXFLAG_STREAMED (1 << 2) // informative only. You shouldn't need to know that #define SFXFLAG_MENUSOUND (1 << 3) // not freed during level change (menu sounds, music, etc) typedef struct snd_fetcher_s snd_fetcher_t; struct sfx_s { char name[MAX_QPATH]; sfx_t *next; size_t memsize; // total memory used (including sfx_t and fetcher data) snd_format_t format; // format describing the audio data that fetcher->getsamplesfloat will return unsigned int flags; // cf SFXFLAG_* defines unsigned int loopstart; // in sample frames. equals total_length if not looped unsigned int total_length; // in sample frames const snd_fetcher_t *fetcher; void *fetcher_data; // Per-sfx data for the sound fetching functions float volume_mult; // for replay gain (multiplier to apply) float volume_peak; // for replay gain (highest peak); if set to 0, ReplayGain isn't supported }; // maximum supported speakers constant #define SND_LISTENERS 8 typedef struct channel_s { // provided sound information sfx_t *sfx; // pointer to sound sample being used float basevolume; // 0-1 master volume unsigned int flags; // cf CHANNELFLAG_* defines int entnum; // makes sound follow entity origin (allows replacing interrupting existing sound on same id) int entchannel; // which channel id on the entity vec3_t origin; // origin of sound effect vec_t distfade; // distance multiplier (attenuation/clipK) void *fetcher_data; // Per-channel data for the sound fetching function int prologic_invert;// whether a sound is played on the surround channels in prologic float basespeed; // playback rate multiplier for pitch variation // these are often updated while mixer is running, glitching should be minimized (mismatched channel volumes from spatialization is okay) // spatialized playback speed (speed * doppler ratio) float mixspeed; // spatialized volume per speaker (mastervol * distanceattenuation * channelvolume cvars) float volume[SND_LISTENERS]; // updated ONLY by mixer // position in sfx, starts at 0, loops or stops at sfx->total_length double position; } channel_t; // Sound fetching functions // "start" is both an input and output parameter: it returns the actual start time of the sound buffer typedef void (*snd_fetcher_getsamplesfloat_t) (channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat); typedef void (*snd_fetcher_stopchannel_t) (channel_t *ch); typedef void (*snd_fetcher_freesfx_t) (sfx_t *sfx); struct snd_fetcher_s { snd_fetcher_getsamplesfloat_t getsamplesfloat; snd_fetcher_stopchannel_t stopchannel; snd_fetcher_freesfx_t freesfx; }; extern unsigned int total_channels; extern channel_t channels[MAX_CHANNELS]; extern snd_ringbuffer_t *snd_renderbuffer; extern qboolean snd_threaded; // enables use of snd_usethreadedmixing, provided that no sound hacks are in effect (like timedemo) extern qboolean snd_usethreadedmixing; // if true, the main thread does not mix sound, soundtime does not advance, and neither does snd_renderbuffer->endframe, instead the audio thread will call S_MixToBuffer as needed extern cvar_t _snd_mixahead; extern cvar_t snd_swapstereo; extern cvar_t snd_streaming; extern cvar_t snd_streaming_length; #define SND_CHANNELLAYOUT_AUTO 0 #define SND_CHANNELLAYOUT_STANDARD 1 #define SND_CHANNELLAYOUT_ALSA 2 extern cvar_t snd_channellayout; extern int snd_blocked; // counter. When > 0, we stop submitting sound to the audio device extern mempool_t *snd_mempool; // If simsound is true, the sound card is not initialized and no sound is submitted to it. // More generally, all arch-dependent operations are skipped or emulated. // Used for isolating performance in the renderer. extern qboolean simsound; #define STREAM_BUFFERSIZE 16384 // in sampleframes // ==================================================================== // Architecture-independent functions // ==================================================================== void S_MixToBuffer(void *stream, unsigned int frames); qboolean S_LoadSound (sfx_t *sfx, qboolean complain); snd_buffer_t *Snd_CreateSndBuffer (const unsigned char *samples, unsigned int sampleframes, const snd_format_t* in_format, unsigned int sb_speed); qboolean Snd_AppendToSndBuffer (snd_buffer_t* sb, const unsigned char *samples, unsigned int sampleframes, const snd_format_t* format); // If "buffer" is NULL, the function allocates one buffer of "sampleframes" sample frames itself // (if "sampleframes" is 0, the function chooses the size). snd_ringbuffer_t *Snd_CreateRingBuffer (const snd_format_t* format, unsigned int sampleframes, void* buffer); // ==================================================================== // Architecture-dependent functions // ==================================================================== // Create "snd_renderbuffer" with the proper sound format if the call is successful // May return a suggested format if the requested format isn't available qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested); // Stop the sound card, delete "snd_renderbuffer" and free its other resources void SndSys_Shutdown (void); // Submit the contents of "snd_renderbuffer" to the sound card void SndSys_Submit (void); // Returns the number of sample frames consumed since the sound started unsigned int SndSys_GetSoundTime (void); // Get the exclusive lock on "snd_renderbuffer" qboolean SndSys_LockRenderBuffer (void); // Release the exclusive lock on "snd_renderbuffer" void SndSys_UnlockRenderBuffer (void); // if the sound system can generate events, send them void SndSys_SendKeyEvents(void); // exported for capturevideo so ogg can see all channels typedef struct portable_samplepair_s { float sample[SND_LISTENERS]; } portable_sampleframe_t; typedef struct listener_s { int channel_unswapped; // for un-swapping float yawangle; float dotscale; float dotbias; float ambientvolume; } listener_t; typedef struct speakerlayout_s { const char *name; unsigned int channels; listener_t listeners[SND_LISTENERS]; } speakerlayout_t; #endif darkplaces/polygon.c0000664000175000017500000002514513067716222014027 0ustar kalevkalev /* Polygon clipping routines written by Forest Hale and placed into public domain. */ #include #include "polygon.h" void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize) { float d, quadright[3], quadup[3]; if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) { quadup[0] = 1; quadup[1] = 0; quadup[2] = 0; } else { quadup[0] = 0; quadup[1] = 0; quadup[2] = 1; } // d = -DotProduct(quadup, planenormal); d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); // VectorMA(quadup, d, planenormal, quadup); quadup[0] += d * planenormalx; quadup[1] += d * planenormaly; quadup[2] += d * planenormalz; // VectorNormalize(quadup); d = (float)(1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2])); quadup[0] *= d; quadup[1] *= d; quadup[2] *= d; // CrossProduct(quadup,planenormal,quadright); quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; // make the points outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; } void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize) { double d, quadright[3], quadup[3]; if (fabs(planenormalz) > fabs(planenormalx) && fabs(planenormalz) > fabs(planenormaly)) { quadup[0] = 1; quadup[1] = 0; quadup[2] = 0; } else { quadup[0] = 0; quadup[1] = 0; quadup[2] = 1; } // d = -DotProduct(quadup, planenormal); d = -(quadup[0] * planenormalx + quadup[1] * planenormaly + quadup[2] * planenormalz); // VectorMA(quadup, d, planenormal, quadup); quadup[0] += d * planenormalx; quadup[1] += d * planenormaly; quadup[2] += d * planenormalz; // VectorNormalize(quadup); d = 1.0 / sqrt(quadup[0] * quadup[0] + quadup[1] * quadup[1] + quadup[2] * quadup[2]); quadup[0] *= d; quadup[1] *= d; quadup[2] *= d; // CrossProduct(quadup,planenormal,quadright); quadright[0] = quadup[1] * planenormalz - quadup[2] * planenormaly; quadright[1] = quadup[2] * planenormalx - quadup[0] * planenormalz; quadright[2] = quadup[0] * planenormaly - quadup[1] * planenormalx; // make the points outpoints[0] = planedist * planenormalx - quadsize * quadright[0] + quadsize * quadup[0]; outpoints[1] = planedist * planenormaly - quadsize * quadright[1] + quadsize * quadup[1]; outpoints[2] = planedist * planenormalz - quadsize * quadright[2] + quadsize * quadup[2]; outpoints[3] = planedist * planenormalx + quadsize * quadright[0] + quadsize * quadup[0]; outpoints[4] = planedist * planenormaly + quadsize * quadright[1] + quadsize * quadup[1]; outpoints[5] = planedist * planenormalz + quadsize * quadright[2] + quadsize * quadup[2]; outpoints[6] = planedist * planenormalx + quadsize * quadright[0] - quadsize * quadup[0]; outpoints[7] = planedist * planenormaly + quadsize * quadright[1] - quadsize * quadup[1]; outpoints[8] = planedist * planenormalz + quadsize * quadright[2] - quadsize * quadup[2]; outpoints[9] = planedist * planenormalx - quadsize * quadright[0] - quadsize * quadup[0]; outpoints[10] = planedist * planenormaly - quadsize * quadright[1] - quadsize * quadup[1]; outpoints[11] = planedist * planenormalz - quadsize * quadright[2] - quadsize * quadup[2]; } int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints) { int i, frontcount = 0; const float *n, *p; float frac, pdist, ndist; if (innumpoints < 1) return 0; n = inpoints; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; for(i = 0;i < innumpoints;i++) { p = n; pdist = ndist; n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; if (pdist >= -epsilon) { if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0]; *outfrontpoints++ = p[1]; *outfrontpoints++ = p[2]; } frontcount++; } if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) { frac = pdist / (pdist - ndist); if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); } frontcount++; } } return frontcount; } int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints) { int i, frontcount = 0; const double *n, *p; double frac, pdist, ndist; if (innumpoints < 1) return 0; n = inpoints; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; for(i = 0;i < innumpoints;i++) { p = n; pdist = ndist; n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; if (pdist >= -epsilon) { if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0]; *outfrontpoints++ = p[1]; *outfrontpoints++ = p[2]; } frontcount++; } if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) { frac = pdist / (pdist - ndist); if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); } frontcount++; } } return frontcount; } void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer) { int i, frontcount = 0, backcount = 0, oncount = 0; const float *n, *p; float frac, pdist, ndist; if (innumpoints) { n = inpoints; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; for(i = 0;i < innumpoints;i++) { p = n; pdist = ndist; n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; if (pdist >= -epsilon) { if (pdist <= epsilon) oncount++; if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0]; *outfrontpoints++ = p[1]; *outfrontpoints++ = p[2]; } frontcount++; } if (pdist <= epsilon) { if (backcount < outbackmaxpoints) { *outbackpoints++ = p[0]; *outbackpoints++ = p[1]; *outbackpoints++ = p[2]; } backcount++; } if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) { oncount++; frac = pdist / (pdist - ndist); if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); } frontcount++; if (backcount < outbackmaxpoints) { *outbackpoints++ = p[0] + frac * (n[0] - p[0]); *outbackpoints++ = p[1] + frac * (n[1] - p[1]); *outbackpoints++ = p[2] + frac * (n[2] - p[2]); } backcount++; } } } if (neededfrontpoints) *neededfrontpoints = frontcount; if (neededbackpoints) *neededbackpoints = backcount; if (oncountpointer) *oncountpointer = oncount; } void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer) { int i = 0, frontcount = 0, backcount = 0, oncount = 0; const double *n, *p; double frac, pdist, ndist; if (innumpoints) { n = inpoints; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; for(i = 0;i < innumpoints;i++) { p = n; pdist = ndist; n = inpoints + ((i + 1) < innumpoints ? (i + 1) : 0) * 3; ndist = n[0] * planenormalx + n[1] * planenormaly + n[2] * planenormalz - planedist; if (pdist >= -epsilon) { if (pdist <= epsilon) oncount++; if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0]; *outfrontpoints++ = p[1]; *outfrontpoints++ = p[2]; } frontcount++; } if (pdist <= epsilon) { if (backcount < outbackmaxpoints) { *outbackpoints++ = p[0]; *outbackpoints++ = p[1]; *outbackpoints++ = p[2]; } backcount++; } if ((pdist > epsilon && ndist < -epsilon) || (pdist < -epsilon && ndist > epsilon)) { oncount++; frac = pdist / (pdist - ndist); if (frontcount < outfrontmaxpoints) { *outfrontpoints++ = p[0] + frac * (n[0] - p[0]); *outfrontpoints++ = p[1] + frac * (n[1] - p[1]); *outfrontpoints++ = p[2] + frac * (n[2] - p[2]); } frontcount++; if (backcount < outbackmaxpoints) { *outbackpoints++ = p[0] + frac * (n[0] - p[0]); *outbackpoints++ = p[1] + frac * (n[1] - p[1]); *outbackpoints++ = p[2] + frac * (n[2] - p[2]); } backcount++; } } } if (neededfrontpoints) *neededfrontpoints = frontcount; if (neededbackpoints) *neededbackpoints = backcount; if (oncountpointer) *oncountpointer = oncount; } darkplaces/.travis-script-xonotic.sh0000775000175000017500000001246413067716216017107 0ustar kalevkalev#!/bin/sh set -e openssl aes-256-cbc -K $encrypted_eeb6f7a14a8e_key -iv $encrypted_eeb6f7a14a8e_iv -in .travis-id_rsa-xonotic -out id_rsa-xonotic -d set -x chmod 0600 id_rsa-xonotic # ssh-keygen -y -f id_rsa-xonotic export USRLOCAL="$PWD"/usrlocal rev=`git rev-parse HEAD` sftp -oStrictHostKeyChecking=no -i id_rsa-xonotic -P 2222 -b - autobuild-bin-uploader@beta.xonotic.org <> 8) & 0xFF; data[2] = (l >> 16) & 0xFF; data[3] = (l >> 24) & 0xFF; } static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) { size_t i; size_t pos; pos = 0; if(header) { if(len < 4) return 0; if(Crypto_LittleLong(buf) != header) return 0; pos += 4; } for(i = 0; i < nlumps; ++i) { if(pos + 4 > len) return 0; lumpsize[i] = Crypto_LittleLong(&buf[pos]); pos += 4; if(pos + lumpsize[i] > len) return 0; lumps[i] = &buf[pos]; pos += lumpsize[i]; } return pos; } static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) { size_t i; size_t pos; pos = 0; if(header) { if(len < 4) return 0; Crypto_UnLittleLong(buf, header); pos += 4; } for(i = 0; i < nlumps; ++i) { if(pos + 4 + lumpsize[i] > len) return 0; Crypto_UnLittleLong(&buf[pos], (unsigned long)lumpsize[i]); pos += 4; memcpy(&buf[pos], lumps[i], lumpsize[i]); pos += lumpsize[i]; } return pos; } // END stuff shared with xonotic-keygen #define USE_AES #ifdef LINK_TO_CRYPTO #include #define d0_blind_id_dll 1 #define Crypto_OpenLibrary() true #define Crypto_CloseLibrary() #define qd0_blind_id_new d0_blind_id_new #define qd0_blind_id_free d0_blind_id_free //#define qd0_blind_id_clear d0_blind_id_clear #define qd0_blind_id_copy d0_blind_id_copy //#define qd0_blind_id_generate_private_key d0_blind_id_generate_private_key //#define qd0_blind_id_generate_private_key_fastreject d0_blind_id_generate_private_key_fastreject //#define qd0_blind_id_read_private_key d0_blind_id_read_private_key #define qd0_blind_id_read_public_key d0_blind_id_read_public_key //#define qd0_blind_id_write_private_key d0_blind_id_write_private_key //#define qd0_blind_id_write_public_key d0_blind_id_write_public_key #define qd0_blind_id_fingerprint64_public_key d0_blind_id_fingerprint64_public_key //#define qd0_blind_id_generate_private_id_modulus d0_blind_id_generate_private_id_modulus #define qd0_blind_id_read_private_id_modulus d0_blind_id_read_private_id_modulus //#define qd0_blind_id_write_private_id_modulus d0_blind_id_write_private_id_modulus #define qd0_blind_id_generate_private_id_start d0_blind_id_generate_private_id_start #define qd0_blind_id_generate_private_id_request d0_blind_id_generate_private_id_request //#define qd0_blind_id_answer_private_id_request d0_blind_id_answer_private_id_request #define qd0_blind_id_finish_private_id_request d0_blind_id_finish_private_id_request //#define qd0_blind_id_read_private_id_request_camouflage d0_blind_id_read_private_id_request_camouflage //#define qd0_blind_id_write_private_id_request_camouflage d0_blind_id_write_private_id_request_camouflage #define qd0_blind_id_read_private_id d0_blind_id_read_private_id //#define qd0_blind_id_read_public_id d0_blind_id_read_public_id #define qd0_blind_id_write_private_id d0_blind_id_write_private_id //#define qd0_blind_id_write_public_id d0_blind_id_write_public_id #define qd0_blind_id_authenticate_with_private_id_start d0_blind_id_authenticate_with_private_id_start #define qd0_blind_id_authenticate_with_private_id_challenge d0_blind_id_authenticate_with_private_id_challenge #define qd0_blind_id_authenticate_with_private_id_response d0_blind_id_authenticate_with_private_id_response #define qd0_blind_id_authenticate_with_private_id_verify d0_blind_id_authenticate_with_private_id_verify #define qd0_blind_id_fingerprint64_public_id d0_blind_id_fingerprint64_public_id #define qd0_blind_id_sessionkey_public_id d0_blind_id_sessionkey_public_id #define qd0_blind_id_INITIALIZE d0_blind_id_INITIALIZE #define qd0_blind_id_SHUTDOWN d0_blind_id_SHUTDOWN #define qd0_blind_id_util_sha256 d0_blind_id_util_sha256 #define qd0_blind_id_sign_with_private_id_sign d0_blind_id_sign_with_private_id_sign #define qd0_blind_id_sign_with_private_id_sign_detached d0_blind_id_sign_with_private_id_sign_detached #define qd0_blind_id_setmallocfuncs d0_blind_id_setmallocfuncs #define qd0_blind_id_setmutexfuncs d0_blind_id_setmutexfuncs #define qd0_blind_id_verify_public_id d0_blind_id_verify_public_id #define qd0_blind_id_verify_private_id d0_blind_id_verify_private_id #else // d0_blind_id interface #define D0_EXPORT #ifdef __GNUC__ #define D0_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #else #define D0_WARN_UNUSED_RESULT #endif #define D0_BOOL int typedef void *(d0_malloc_t)(size_t len); typedef void (d0_free_t)(void *p); typedef void *(d0_createmutex_t)(void); typedef void (d0_destroymutex_t)(void *); typedef int (d0_lockmutex_t)(void *); // zero on success typedef int (d0_unlockmutex_t)(void *); // zero on success typedef struct d0_blind_id_s d0_blind_id_t; typedef D0_BOOL (*d0_fastreject_function) (const d0_blind_id_t *ctx, void *pass); static D0_EXPORT D0_WARN_UNUSED_RESULT d0_blind_id_t *(*qd0_blind_id_new) (void); static D0_EXPORT void (*qd0_blind_id_free) (d0_blind_id_t *a); //static D0_EXPORT void (*qd0_blind_id_clear) (d0_blind_id_t *ctx); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_copy) (d0_blind_id_t *ctx, const d0_blind_id_t *src); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key) (d0_blind_id_t *ctx, int k); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_key_fastreject) (d0_blind_id_t *ctx, int k, d0_fastreject_function reject, void *pass); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_key) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_key) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_modulus) (d0_blind_id_t *ctx); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_modulus) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_modulus) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_start) (d0_blind_id_t *ctx); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_generate_private_id_request) (d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_answer_private_id_request) (const d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_finish_private_id_request) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id_request_camouflage) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id_request_camouflage) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_private_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_read_public_id) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_private_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); //static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_write_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_start) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_challenge) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL recv_modulus, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen, D0_BOOL *status); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_response) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_authenticate_with_private_id_verify) (d0_blind_id_t *ctx, const char *inbuf, size_t inbuflen, char *msg, size_t *msglen, D0_BOOL *status); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_fingerprint64_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sessionkey_public_id) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); // can only be done after successful key exchange, this performs a modpow; key length is limited by SHA_DIGESTSIZE for now; also ONLY valid after successful d0_blind_id_authenticate_with_private_id_verify/d0_blind_id_fingerprint64_public_id static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_INITIALIZE) (void); static D0_EXPORT void (*qd0_blind_id_SHUTDOWN) (void); static D0_EXPORT void (*qd0_blind_id_util_sha256) (char *out, const char *in, size_t n); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_sign_with_private_id_sign_detached) (d0_blind_id_t *ctx, D0_BOOL is_first, D0_BOOL send_modulus, const char *message, size_t msglen, char *outbuf, size_t *outbuflen); static D0_EXPORT void (*qd0_blind_id_setmallocfuncs)(d0_malloc_t *m, d0_free_t *f); static D0_EXPORT void (*qd0_blind_id_setmutexfuncs)(d0_createmutex_t *c, d0_destroymutex_t *d, d0_lockmutex_t *l, d0_unlockmutex_t *u); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_verify_public_id)(const d0_blind_id_t *ctx, D0_BOOL *status); static D0_EXPORT D0_WARN_UNUSED_RESULT D0_BOOL (*qd0_blind_id_verify_private_id)(const d0_blind_id_t *ctx); static dllfunction_t d0_blind_id_funcs[] = { {"d0_blind_id_new", (void **) &qd0_blind_id_new}, {"d0_blind_id_free", (void **) &qd0_blind_id_free}, //{"d0_blind_id_clear", (void **) &qd0_blind_id_clear}, {"d0_blind_id_copy", (void **) &qd0_blind_id_copy}, //{"d0_blind_id_generate_private_key", (void **) &qd0_blind_id_generate_private_key}, //{"d0_blind_id_generate_private_key_fastreject", (void **) &qd0_blind_id_generate_private_key_fastreject}, //{"d0_blind_id_read_private_key", (void **) &qd0_blind_id_read_private_key}, {"d0_blind_id_read_public_key", (void **) &qd0_blind_id_read_public_key}, //{"d0_blind_id_write_private_key", (void **) &qd0_blind_id_write_private_key}, //{"d0_blind_id_write_public_key", (void **) &qd0_blind_id_write_public_key}, {"d0_blind_id_fingerprint64_public_key", (void **) &qd0_blind_id_fingerprint64_public_key}, //{"d0_blind_id_generate_private_id_modulus", (void **) &qd0_blind_id_generate_private_id_modulus}, {"d0_blind_id_read_private_id_modulus", (void **) &qd0_blind_id_read_private_id_modulus}, //{"d0_blind_id_write_private_id_modulus", (void **) &qd0_blind_id_write_private_id_modulus}, {"d0_blind_id_generate_private_id_start", (void **) &qd0_blind_id_generate_private_id_start}, {"d0_blind_id_generate_private_id_request", (void **) &qd0_blind_id_generate_private_id_request}, //{"d0_blind_id_answer_private_id_request", (void **) &qd0_blind_id_answer_private_id_request}, {"d0_blind_id_finish_private_id_request", (void **) &qd0_blind_id_finish_private_id_request}, //{"d0_blind_id_read_private_id_request_camouflage", (void **) &qd0_blind_id_read_private_id_request_camouflage}, //{"d0_blind_id_write_private_id_request_camouflage", (void **) &qd0_blind_id_write_private_id_request_camouflage}, {"d0_blind_id_read_private_id", (void **) &qd0_blind_id_read_private_id}, //{"d0_blind_id_read_public_id", (void **) &qd0_blind_id_read_public_id}, {"d0_blind_id_write_private_id", (void **) &qd0_blind_id_write_private_id}, //{"d0_blind_id_write_public_id", (void **) &qd0_blind_id_write_public_id}, {"d0_blind_id_authenticate_with_private_id_start", (void **) &qd0_blind_id_authenticate_with_private_id_start}, {"d0_blind_id_authenticate_with_private_id_challenge", (void **) &qd0_blind_id_authenticate_with_private_id_challenge}, {"d0_blind_id_authenticate_with_private_id_response", (void **) &qd0_blind_id_authenticate_with_private_id_response}, {"d0_blind_id_authenticate_with_private_id_verify", (void **) &qd0_blind_id_authenticate_with_private_id_verify}, {"d0_blind_id_fingerprint64_public_id", (void **) &qd0_blind_id_fingerprint64_public_id}, {"d0_blind_id_sessionkey_public_id", (void **) &qd0_blind_id_sessionkey_public_id}, {"d0_blind_id_INITIALIZE", (void **) &qd0_blind_id_INITIALIZE}, {"d0_blind_id_SHUTDOWN", (void **) &qd0_blind_id_SHUTDOWN}, {"d0_blind_id_util_sha256", (void **) &qd0_blind_id_util_sha256}, {"d0_blind_id_sign_with_private_id_sign", (void **) &qd0_blind_id_sign_with_private_id_sign}, {"d0_blind_id_sign_with_private_id_sign_detached", (void **) &qd0_blind_id_sign_with_private_id_sign_detached}, {"d0_blind_id_setmallocfuncs", (void **) &qd0_blind_id_setmallocfuncs}, {"d0_blind_id_setmutexfuncs", (void **) &qd0_blind_id_setmutexfuncs}, {"d0_blind_id_verify_public_id", (void **) &qd0_blind_id_verify_public_id}, {"d0_blind_id_verify_private_id", (void **) &qd0_blind_id_verify_private_id}, {NULL, NULL} }; // end of d0_blind_id interface static dllhandle_t d0_blind_id_dll = NULL; static qboolean Crypto_OpenLibrary (void) { const char* dllnames [] = { #if defined(WIN32) "libd0_blind_id-0.dll", #elif defined(MACOSX) "libd0_blind_id.0.dylib", #else "libd0_blind_id.so.0", "libd0_blind_id.so", // FreeBSD #endif NULL }; // Already loaded? if (d0_blind_id_dll) return true; // Load the DLL return Sys_LoadLibrary (dllnames, &d0_blind_id_dll, d0_blind_id_funcs); } static void Crypto_CloseLibrary (void) { Sys_UnloadLibrary (&d0_blind_id_dll); } #endif #ifdef LINK_TO_CRYPTO_RIJNDAEL #include #define d0_rijndael_dll 1 #define Crypto_Rijndael_OpenLibrary() true #define Crypto_Rijndael_CloseLibrary() #define qd0_rijndael_setup_encrypt d0_rijndael_setup_encrypt #define qd0_rijndael_setup_decrypt d0_rijndael_setup_decrypt #define qd0_rijndael_encrypt d0_rijndael_encrypt #define qd0_rijndael_decrypt d0_rijndael_decrypt #else // no need to do the #define dance here, as the upper part declares out macros either way D0_EXPORT int (*qd0_rijndael_setup_encrypt) (unsigned long *rk, const unsigned char *key, int keybits); D0_EXPORT int (*qd0_rijndael_setup_decrypt) (unsigned long *rk, const unsigned char *key, int keybits); D0_EXPORT void (*qd0_rijndael_encrypt) (const unsigned long *rk, int nrounds, const unsigned char plaintext[16], unsigned char ciphertext[16]); D0_EXPORT void (*qd0_rijndael_decrypt) (const unsigned long *rk, int nrounds, const unsigned char ciphertext[16], unsigned char plaintext[16]); #define D0_RIJNDAEL_KEYLENGTH(keybits) ((keybits)/8) #define D0_RIJNDAEL_RKLENGTH(keybits) ((keybits)/8+28) #define D0_RIJNDAEL_NROUNDS(keybits) ((keybits)/32+6) static dllfunction_t d0_rijndael_funcs[] = { {"d0_rijndael_setup_decrypt", (void **) &qd0_rijndael_setup_decrypt}, {"d0_rijndael_setup_encrypt", (void **) &qd0_rijndael_setup_encrypt}, {"d0_rijndael_decrypt", (void **) &qd0_rijndael_decrypt}, {"d0_rijndael_encrypt", (void **) &qd0_rijndael_encrypt}, {NULL, NULL} }; // end of d0_blind_id interface static dllhandle_t d0_rijndael_dll = NULL; static qboolean Crypto_Rijndael_OpenLibrary (void) { const char* dllnames [] = { #if defined(WIN32) "libd0_rijndael-0.dll", #elif defined(MACOSX) "libd0_rijndael.0.dylib", #else "libd0_rijndael.so.0", "libd0_rijndael.so", // FreeBSD #endif NULL }; // Already loaded? if (d0_rijndael_dll) return true; // Load the DLL return Sys_LoadLibrary (dllnames, &d0_rijndael_dll, d0_rijndael_funcs); } static void Crypto_Rijndael_CloseLibrary (void) { Sys_UnloadLibrary (&d0_rijndael_dll); } #endif // various helpers void sha256(unsigned char *out, const unsigned char *in, int n) { qd0_blind_id_util_sha256((char *) out, (const char *) in, n); } static size_t Crypto_LoadFile(const char *path, char *buf, size_t nmax, qboolean inuserdir) { char vabuf[1024]; qfile_t *f = NULL; fs_offset_t n; if(inuserdir) f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%s%s", *fs_userdir ? fs_userdir : fs_basedir, path), "rb", false); else f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%s%s", fs_basedir, path), "rb", false); if(!f) return 0; n = FS_Read(f, buf, nmax); if(n < 0) n = 0; FS_Close(f); return (size_t) n; } static qboolean PutWithNul(char **data, size_t *len, const char *str) { // invariant: data points to insertion point size_t l = strlen(str); if(l >= *len) return false; memcpy(*data, str, l+1); *data += l+1; *len -= l+1; return true; } static const char *GetUntilNul(const char **data, size_t *len) { // invariant: data points to next character to take const char *data_save = *data; size_t n; const char *p; if(!*data) return NULL; if(!*len) { *data = NULL; return NULL; } p = (const char *) memchr(*data, 0, *len); if(!p) // no terminating NUL { *data = NULL; *len = 0; return NULL; } n = (p - *data) + 1; *len -= n; *data += n; if(*len == 0) *data = NULL; return (const char *) data_save; } // d0pk reading static d0_blind_id_t *Crypto_ReadPublicKey(char *buf, size_t len) { d0_blind_id_t *pk = NULL; const char *p[2]; size_t l[2]; if(Crypto_ParsePack(buf, len, FOURCC_D0PK, p, l, 2)) { pk = qd0_blind_id_new(); if(pk) if(qd0_blind_id_read_public_key(pk, p[0], l[0])) if(qd0_blind_id_read_private_id_modulus(pk, p[1], l[1])) return pk; } if(pk) qd0_blind_id_free(pk); return NULL; } // d0si reading static qboolean Crypto_AddPrivateKey(d0_blind_id_t *pk, char *buf, size_t len) { const char *p[1]; size_t l[1]; if(Crypto_ParsePack(buf, len, FOURCC_D0SI, p, l, 1)) { if(qd0_blind_id_read_private_id(pk, p[0], l[0])) return true; } return false; } #define MAX_PUBKEYS 16 static d0_blind_id_t *pubkeys[MAX_PUBKEYS]; static char pubkeys_fp64[MAX_PUBKEYS][FP64_SIZE+1]; static qboolean pubkeys_havepriv[MAX_PUBKEYS]; static qboolean pubkeys_havesig[MAX_PUBKEYS]; static char pubkeys_priv_fp64[MAX_PUBKEYS][FP64_SIZE+1]; static char challenge_append[1400]; static size_t challenge_append_length; static int keygen_i = -1; static char keygen_buf[8192]; #define MAX_CRYPTOCONNECTS 16 #define CRYPTOCONNECT_NONE 0 #define CRYPTOCONNECT_PRECONNECT 1 #define CRYPTOCONNECT_CONNECT 2 #define CRYPTOCONNECT_RECONNECT 3 #define CRYPTOCONNECT_DUPLICATE 4 typedef struct server_cryptoconnect_s { double lasttime; lhnetaddress_t address; crypto_t crypto; int next_step; } server_cryptoconnect_t; static server_cryptoconnect_t cryptoconnects[MAX_CRYPTOCONNECTS]; static int cdata_id = 0; typedef struct { d0_blind_id_t *id; int s, c; int next_step; char challenge[2048]; char wantserver_idfp[FP64_SIZE+1]; qboolean wantserver_aes; qboolean wantserver_issigned; int cdata_id; } crypto_data_t; // crypto specific helpers #define CDATA ((crypto_data_t *) crypto->data) #define MAKE_CDATA if(!crypto->data) crypto->data = Z_Malloc(sizeof(crypto_data_t)) #define CLEAR_CDATA if(crypto->data) { if(CDATA->id) qd0_blind_id_free(CDATA->id); Z_Free(crypto->data); } crypto->data = NULL static crypto_t *Crypto_ServerFindInstance(lhnetaddress_t *peeraddress, qboolean allow_create) { crypto_t *crypto; int i, best; if(!d0_blind_id_dll) return NULL; // no support for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) if(LHNETADDRESS_Compare(peeraddress, &cryptoconnects[i].address)) break; if(i < MAX_CRYPTOCONNECTS && (allow_create || cryptoconnects[i].crypto.data)) { crypto = &cryptoconnects[i].crypto; cryptoconnects[i].lasttime = realtime; return crypto; } if(!allow_create) return NULL; best = 0; for(i = 1; i < MAX_CRYPTOCONNECTS; ++i) if(cryptoconnects[i].lasttime < cryptoconnects[best].lasttime) best = i; crypto = &cryptoconnects[best].crypto; cryptoconnects[best].lasttime = realtime; memcpy(&cryptoconnects[best].address, peeraddress, sizeof(cryptoconnects[best].address)); CLEAR_CDATA; return crypto; } qboolean Crypto_FinishInstance(crypto_t *out, crypto_t *crypto) { // no check needed here (returned pointers are only used in prefilled fields) if(!crypto || !crypto->authenticated) { Con_Printf("Passed an invalid crypto connect instance\n"); memset(out, 0, sizeof(*out)); return false; } CLEAR_CDATA; memcpy(out, crypto, sizeof(*out)); memset(crypto, 0, sizeof(*crypto)); return true; } crypto_t *Crypto_ServerGetInstance(lhnetaddress_t *peeraddress) { // no check needed here (returned pointers are only used in prefilled fields) return Crypto_ServerFindInstance(peeraddress, false); } typedef struct crypto_storedhostkey_s { struct crypto_storedhostkey_s *next; lhnetaddress_t addr; int keyid; char idfp[FP64_SIZE+1]; int aeslevel; qboolean issigned; } crypto_storedhostkey_t; static crypto_storedhostkey_t *crypto_storedhostkey_hashtable[CRYPTO_HOSTKEY_HASHSIZE]; static void Crypto_InitHostKeys(void) { int i; for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) crypto_storedhostkey_hashtable[i] = NULL; } static void Crypto_ClearHostKeys(void) { int i; crypto_storedhostkey_t *hk, *hkn; for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) { for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hkn) { hkn = hk->next; Z_Free(hk); } crypto_storedhostkey_hashtable[i] = NULL; } } static qboolean Crypto_ClearHostKey(lhnetaddress_t *peeraddress) { char buf[128]; int hashindex; crypto_storedhostkey_t **hkp; qboolean found = false; LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; for(hkp = &crypto_storedhostkey_hashtable[hashindex]; *hkp && LHNETADDRESS_Compare(&((*hkp)->addr), peeraddress); hkp = &((*hkp)->next)); if(*hkp) { crypto_storedhostkey_t *hk = *hkp; *hkp = hk->next; Z_Free(hk); found = true; } return found; } static void Crypto_StoreHostKey(lhnetaddress_t *peeraddress, const char *keystring, qboolean complain) { char buf[128]; int hashindex; crypto_storedhostkey_t *hk; int keyid; char idfp[FP64_SIZE+1]; int aeslevel; qboolean issigned; if(!d0_blind_id_dll) return; // syntax of keystring: // aeslevel id@key id@key ... if(!*keystring) return; aeslevel = bound(0, *keystring - '0', 3); while(*keystring && *keystring != ' ') ++keystring; keyid = -1; issigned = false; while(*keystring && keyid < 0) { // id@key const char *idstart, *idend, *keystart, *keyend; qboolean thisissigned = true; ++keystring; // skip the space idstart = keystring; while(*keystring && *keystring != ' ' && *keystring != '@') ++keystring; idend = keystring; if(!*keystring) break; ++keystring; keystart = keystring; while(*keystring && *keystring != ' ') ++keystring; keyend = keystring; if (keystart[0] == '~') { thisissigned = false; ++keystart; } if(idend - idstart == FP64_SIZE && keyend - keystart == FP64_SIZE) { int thiskeyid; for(thiskeyid = MAX_PUBKEYS - 1; thiskeyid >= 0; --thiskeyid) if(pubkeys[thiskeyid]) if(!memcmp(pubkeys_fp64[thiskeyid], keystart, FP64_SIZE)) { memcpy(idfp, idstart, FP64_SIZE); idfp[FP64_SIZE] = 0; keyid = thiskeyid; issigned = thisissigned; break; } // If this failed, keyid will be -1. } } if(keyid < 0) return; LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); if(hk) { if(complain) { if(hk->keyid != keyid || memcmp(hk->idfp, idfp, FP64_SIZE+1)) Con_Printf("Server %s tried to change the host key to a value not in the host cache. Connecting to it will fail. To accept the new host key, do crypto_hostkey_clear %s\n", buf, buf); if(hk->aeslevel > aeslevel) Con_Printf("Server %s tried to reduce encryption status, not accepted. Connecting to it will fail. To accept, do crypto_hostkey_clear %s\n", buf, buf); if(hk->issigned > issigned) Con_Printf("Server %s tried to reduce signature status, not accepted. Connecting to it will fail. To accept, do crypto_hostkey_clear %s\n", buf, buf); } hk->aeslevel = max(aeslevel, hk->aeslevel); hk->issigned = issigned; return; } // great, we did NOT have it yet hk = (crypto_storedhostkey_t *) Z_Malloc(sizeof(*hk)); memcpy(&hk->addr, peeraddress, sizeof(hk->addr)); hk->keyid = keyid; memcpy(hk->idfp, idfp, FP64_SIZE+1); hk->next = crypto_storedhostkey_hashtable[hashindex]; hk->aeslevel = aeslevel; hk->issigned = issigned; crypto_storedhostkey_hashtable[hashindex] = hk; } qboolean Crypto_RetrieveHostKey(lhnetaddress_t *peeraddress, int *keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, int *aeslevel, qboolean *issigned) { char buf[128]; int hashindex; crypto_storedhostkey_t *hk; if(!d0_blind_id_dll) return false; LHNETADDRESS_ToString(peeraddress, buf, sizeof(buf), 1); hashindex = CRC_Block((const unsigned char *) buf, strlen(buf)) % CRYPTO_HOSTKEY_HASHSIZE; for(hk = crypto_storedhostkey_hashtable[hashindex]; hk && LHNETADDRESS_Compare(&hk->addr, peeraddress); hk = hk->next); if(!hk) return false; if(keyid) *keyid = hk->keyid; if(keyfp) strlcpy(keyfp, pubkeys_fp64[hk->keyid], keyfplen); if(idfp) strlcpy(idfp, hk->idfp, idfplen); if(aeslevel) *aeslevel = hk->aeslevel; if(issigned) *issigned = hk->issigned; return true; } int Crypto_RetrieveLocalKey(int keyid, char *keyfp, size_t keyfplen, char *idfp, size_t idfplen, qboolean *issigned) // return value: -1 if more to come, +1 if valid, 0 if end of list { if(keyid < 0 || keyid >= MAX_PUBKEYS) return 0; if(keyfp) *keyfp = 0; if(idfp) *idfp = 0; if(!pubkeys[keyid]) return -1; if(keyfp) strlcpy(keyfp, pubkeys_fp64[keyid], keyfplen); if(idfp) if(pubkeys_havepriv[keyid]) strlcpy(idfp, pubkeys_priv_fp64[keyid], idfplen); if(issigned) *issigned = pubkeys_havesig[keyid]; return 1; } // end // init/shutdown code static void Crypto_BuildChallengeAppend(void) { char *p, *lengthptr, *startptr; size_t n; int i; p = challenge_append; n = sizeof(challenge_append); Crypto_UnLittleLong(p, PROTOCOL_VLEN); p += 4; n -= 4; lengthptr = p; Crypto_UnLittleLong(p, 0); p += 4; n -= 4; Crypto_UnLittleLong(p, PROTOCOL_D0_BLIND_ID); p += 4; n -= 4; startptr = p; for(i = 0; i < MAX_PUBKEYS; ++i) if(pubkeys_havepriv[i]) PutWithNul(&p, &n, pubkeys_fp64[i]); PutWithNul(&p, &n, ""); for(i = 0; i < MAX_PUBKEYS; ++i) if(!pubkeys_havepriv[i] && pubkeys[i]) PutWithNul(&p, &n, pubkeys_fp64[i]); Crypto_UnLittleLong(lengthptr, p - startptr); challenge_append_length = p - challenge_append; } static qboolean Crypto_SavePubKeyTextFile(int i) { qfile_t *f; char vabuf[1024]; if(!pubkeys_havepriv[i]) return false; f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d-public-fp%s.txt", *fs_userdir ? fs_userdir : fs_basedir, i, sessionid.string), "w", false); if(!f) return false; // we ignore errors for this file, as it's not necessary to have FS_Printf(f, "ID-Fingerprint: %s\n", pubkeys_priv_fp64[i]); FS_Printf(f, "ID-Is-Signed: %s\n", pubkeys_havesig[i] ? "yes" : "no"); FS_Printf(f, "ID-Is-For-Key: %s\n", pubkeys_fp64[i]); FS_Printf(f, "\n"); FS_Printf(f, "This is a PUBLIC ID file for DarkPlaces.\n"); FS_Printf(f, "You are free to share this file or its contents.\n"); FS_Printf(f, "\n"); FS_Printf(f, "This file will be automatically generated again if deleted.\n"); FS_Printf(f, "\n"); FS_Printf(f, "However, NEVER share the accompanying SECRET ID file called\n"); FS_Printf(f, "key_%d.d0si%s, as doing so would compromise security!\n", i, sessionid.string); FS_Close(f); return true; } static void Crypto_BuildIdString(void) { int i; char vabuf[1024]; crypto_idstring = NULL; dpsnprintf(crypto_idstring_buf, sizeof(crypto_idstring_buf), "%d", d0_rijndael_dll ? crypto_aeslevel.integer : 0); for (i = 0; i < MAX_PUBKEYS; ++i) if (pubkeys[i]) strlcat(crypto_idstring_buf, va(vabuf, sizeof(vabuf), " %s@%s%s", pubkeys_priv_fp64[i], pubkeys_havesig[i] ? "" : "~", pubkeys_fp64[i]), sizeof(crypto_idstring_buf)); crypto_idstring = crypto_idstring_buf; } void Crypto_LoadKeys(void) { char buf[8192]; size_t len, len2; int i; char vabuf[1024]; if(!d0_blind_id_dll) // don't if we can't return; if(crypto_idstring) // already loaded? then not return; Host_LockSession(); // we use the session ID here // load keys // note: we are just a CLIENT // so we load: // PUBLIC KEYS to accept (including modulus) // PRIVATE KEY of user for(i = 0; i < MAX_PUBKEYS; ++i) { memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); pubkeys_havepriv[i] = false; pubkeys_havesig[i] = false; len = Crypto_LoadFile(va(vabuf, sizeof(vabuf), "key_%d.d0pk", i), buf, sizeof(buf), false); if((pubkeys[i] = Crypto_ReadPublicKey(buf, len))) { len2 = FP64_SIZE; if(qd0_blind_id_fingerprint64_public_key(pubkeys[i], pubkeys_fp64[i], &len2)) // keeps final NUL { Con_Printf("Loaded public key key_%d.d0pk (fingerprint: %s)\n", i, pubkeys_fp64[i]); len = Crypto_LoadFile(va(vabuf, sizeof(vabuf), "key_%d.d0si%s", i, sessionid.string), buf, sizeof(buf), true); if(len) { if(Crypto_AddPrivateKey(pubkeys[i], buf, len)) { len2 = FP64_SIZE; if(qd0_blind_id_fingerprint64_public_id(pubkeys[i], pubkeys_priv_fp64[i], &len2)) // keeps final NUL { D0_BOOL status = 0; Con_Printf("Loaded private ID key_%d.d0si%s for key_%d.d0pk (public key fingerprint: %s)\n", i, sessionid.string, i, pubkeys_priv_fp64[i]); // verify the key we just loaded (just in case) if(qd0_blind_id_verify_private_id(pubkeys[i]) && qd0_blind_id_verify_public_id(pubkeys[i], &status)) { pubkeys_havepriv[i] = true; pubkeys_havesig[i] = status; // verify the key we just got (just in case) if(!status) Con_Printf("NOTE: this ID has not yet been signed!\n"); Crypto_SavePubKeyTextFile(i); } else { Con_Printf("d0_blind_id_verify_private_id failed, this is not a valid key!\n"); qd0_blind_id_free(pubkeys[i]); pubkeys[i] = NULL; } } else { Con_Printf("d0_blind_id_fingerprint64_public_id failed\n"); qd0_blind_id_free(pubkeys[i]); pubkeys[i] = NULL; } } } } else { // can't really happen qd0_blind_id_free(pubkeys[i]); pubkeys[i] = NULL; } } } keygen_i = -1; Crypto_BuildIdString(); Crypto_BuildChallengeAppend(); // find a good prefix length for all the keys we know (yes, algorithm is not perfect yet, may yield too long prefix length) crypto_keyfp_recommended_length = 0; memset(buf+256, 0, MAX_PUBKEYS + MAX_PUBKEYS); while(crypto_keyfp_recommended_length < FP64_SIZE) { memset(buf, 0, 256); for(i = 0; i < MAX_PUBKEYS; ++i) if(pubkeys[i]) { if(!buf[256 + i]) ++buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]]; if(pubkeys_havepriv[i]) if(!buf[256 + MAX_PUBKEYS + i]) ++buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]]; } for(i = 0; i < MAX_PUBKEYS; ++i) if(pubkeys[i]) { if(!buf[256 + i]) if(buf[(unsigned char) pubkeys_fp64[i][crypto_keyfp_recommended_length]] < 2) buf[256 + i] = 1; if(pubkeys_havepriv[i]) if(!buf[256 + MAX_PUBKEYS + i]) if(buf[(unsigned char) pubkeys_priv_fp64[i][crypto_keyfp_recommended_length]] < 2) buf[256 + MAX_PUBKEYS + i] = 1; } ++crypto_keyfp_recommended_length; for(i = 0; i < MAX_PUBKEYS; ++i) if(pubkeys[i]) { if(!buf[256 + i]) break; if(pubkeys_havepriv[i]) if(!buf[256 + MAX_PUBKEYS + i]) break; } if(i >= MAX_PUBKEYS) break; } if(crypto_keyfp_recommended_length < 7) crypto_keyfp_recommended_length = 7; } static void Crypto_UnloadKeys(void) { int i; keygen_i = -1; for(i = 0; i < MAX_PUBKEYS; ++i) { if(pubkeys[i]) qd0_blind_id_free(pubkeys[i]); pubkeys[i] = NULL; pubkeys_havepriv[i] = false; pubkeys_havesig[i] = false; memset(pubkeys_fp64[i], 0, sizeof(pubkeys_fp64[i])); memset(pubkeys_priv_fp64[i], 0, sizeof(pubkeys_fp64[i])); challenge_append_length = 0; } crypto_idstring = NULL; } static mempool_t *cryptomempool; #ifdef __cplusplus extern "C" { #endif static void *Crypto_d0_malloc(size_t len) { return Mem_Alloc(cryptomempool, len); } static void Crypto_d0_free(void *p) { Mem_Free(p); } static void *Crypto_d0_createmutex(void) { return Thread_CreateMutex(); } static void Crypto_d0_destroymutex(void *m) { Thread_DestroyMutex(m); } static int Crypto_d0_lockmutex(void *m) { return Thread_LockMutex(m); } static int Crypto_d0_unlockmutex(void *m) { return Thread_UnlockMutex(m); } #ifdef __cplusplus } #endif void Crypto_Shutdown(void) { crypto_t *crypto; int i; Crypto_Rijndael_CloseLibrary(); if(d0_blind_id_dll) { // free memory for(i = 0; i < MAX_CRYPTOCONNECTS; ++i) { crypto = &cryptoconnects[i].crypto; CLEAR_CDATA; } memset(cryptoconnects, 0, sizeof(cryptoconnects)); crypto = &cls.crypto; CLEAR_CDATA; Crypto_UnloadKeys(); qd0_blind_id_SHUTDOWN(); Crypto_CloseLibrary(); } Mem_FreePool(&cryptomempool); } void Crypto_Init(void) { cryptomempool = Mem_AllocPool("crypto", 0, NULL); if(!Crypto_OpenLibrary()) return; qd0_blind_id_setmallocfuncs(Crypto_d0_malloc, Crypto_d0_free); if (Thread_HasThreads()) qd0_blind_id_setmutexfuncs(Crypto_d0_createmutex, Crypto_d0_destroymutex, Crypto_d0_lockmutex, Crypto_d0_unlockmutex); if(!qd0_blind_id_INITIALIZE()) { Crypto_Rijndael_CloseLibrary(); Crypto_CloseLibrary(); Con_Printf("libd0_blind_id initialization FAILED, cryptography support has been disabled\n"); return; } (void) Crypto_Rijndael_OpenLibrary(); // if this fails, it's uncritical Crypto_InitHostKeys(); } // end qboolean Crypto_Available(void) { if(!d0_blind_id_dll) return false; return true; } // keygen code static void Crypto_KeyGen_Finished(int code, size_t length_received, unsigned char *buffer, void *cbdata) { const char *p[1]; size_t l[1]; static char buf[8192]; static char buf2[8192]; size_t buf2size; qfile_t *f = NULL; D0_BOOL status; char vabuf[1024]; SV_LockThreadMutex(); if(!d0_blind_id_dll) { Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } if(keygen_i < 0) { Con_Printf("Unexpected response from keygen server:\n"); Com_HexDumpToConsole(buffer, (int)length_received); SV_UnlockThreadMutex(); return; } if(keygen_i >= MAX_PUBKEYS || !pubkeys[keygen_i]) { Con_Printf("overflow of keygen_i\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } if(!Crypto_ParsePack((const char *) buffer, length_received, FOURCC_D0IR, p, l, 1)) { if(length_received >= 5 && Crypto_LittleLong((const char *) buffer) == FOURCC_D0ER) { Con_Printf("Error response from keygen server: %.*s\n", (int)(length_received - 5), buffer + 5); } else { Con_Printf("Invalid response from keygen server:\n"); Com_HexDumpToConsole(buffer, (int)length_received); } keygen_i = -1; SV_UnlockThreadMutex(); return; } if(!qd0_blind_id_finish_private_id_request(pubkeys[keygen_i], p[0], l[0])) { Con_Printf("d0_blind_id_finish_private_id_request failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } // verify the key we just got (just in case) if(!qd0_blind_id_verify_public_id(pubkeys[keygen_i], &status) || !status) { Con_Printf("d0_blind_id_verify_public_id failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } // we have a valid key now! // make the rest of crypto.c know that Con_Printf("Received signature for private ID key_%d.d0pk (public key fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); pubkeys_havesig[keygen_i] = true; // write the key to disk p[0] = buf; l[0] = sizeof(buf); if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) { Con_Printf("d0_blind_id_write_private_id failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) { Con_Printf("Crypto_UnParsePack failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } FS_CreatePath(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string)); f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string), "wb", false); if(!f) { Con_Printf("Cannot open key_%d.d0si%s\n", keygen_i, sessionid.string); keygen_i = -1; SV_UnlockThreadMutex(); return; } FS_Write(f, buf2, buf2size); FS_Close(f); Crypto_SavePubKeyTextFile(keygen_i); Con_Printf("Saved to key_%d.d0si%s\n", keygen_i, sessionid.string); Crypto_BuildIdString(); keygen_i = -1; SV_UnlockThreadMutex(); } static void Crypto_KeyGen_f(void) { int i; const char *p[1]; size_t l[1]; static char buf[8192]; static char buf2[8192]; size_t buf2size; size_t buf2l, buf2pos; char vabuf[1024]; size_t len2; qfile_t *f = NULL; if(!d0_blind_id_dll) { Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); return; } if(Cmd_Argc() != 3) { Con_Printf("usage:\n%s id url\n", Cmd_Argv(0)); return; } SV_LockThreadMutex(); Crypto_LoadKeys(); i = atoi(Cmd_Argv(1)); if(!pubkeys[i]) { Con_Printf("there is no public key %d\n", i); SV_UnlockThreadMutex(); return; } if(keygen_i >= 0) { Con_Printf("there is already a keygen run on the way\n"); SV_UnlockThreadMutex(); return; } keygen_i = i; // how to START the keygenning... if(pubkeys_havepriv[keygen_i]) { if(pubkeys_havesig[keygen_i]) { Con_Printf("there is already a signed private key for %d\n", i); keygen_i = -1; SV_UnlockThreadMutex(); return; } // if we get here, we only need a signature, no new keygen run needed Con_Printf("Only need a signature for an existing key...\n"); } else { // we also need a new ID itself if(!qd0_blind_id_generate_private_id_start(pubkeys[keygen_i])) { Con_Printf("d0_blind_id_start failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } // verify the key we just got (just in case) if(!qd0_blind_id_verify_private_id(pubkeys[keygen_i])) { Con_Printf("d0_blind_id_verify_private_id failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } // we have a valid key now! // make the rest of crypto.c know that len2 = FP64_SIZE; if(qd0_blind_id_fingerprint64_public_id(pubkeys[keygen_i], pubkeys_priv_fp64[keygen_i], &len2)) // keeps final NUL { Con_Printf("Generated private ID key_%d.d0pk (public key fingerprint: %s)\n", keygen_i, pubkeys_priv_fp64[keygen_i]); pubkeys_havepriv[keygen_i] = true; strlcat(crypto_idstring_buf, va(vabuf, sizeof(vabuf), " %s@%s", pubkeys_priv_fp64[keygen_i], pubkeys_fp64[keygen_i]), sizeof(crypto_idstring_buf)); crypto_idstring = crypto_idstring_buf; Crypto_BuildChallengeAppend(); } // write the key to disk p[0] = buf; l[0] = sizeof(buf); if(!qd0_blind_id_write_private_id(pubkeys[keygen_i], buf, &l[0])) { Con_Printf("d0_blind_id_write_private_id failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } if(!(buf2size = Crypto_UnParsePack(buf2, sizeof(buf2), FOURCC_D0SI, p, l, 1))) { Con_Printf("Crypto_UnParsePack failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } FS_CreatePath(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string)); f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%skey_%d.d0si%s", *fs_userdir ? fs_userdir : fs_basedir, keygen_i, sessionid.string), "wb", false); if(!f) { Con_Printf("Cannot open key_%d.d0si%s\n", keygen_i, sessionid.string); keygen_i = -1; SV_UnlockThreadMutex(); return; } FS_Write(f, buf2, buf2size); FS_Close(f); Crypto_SavePubKeyTextFile(keygen_i); Con_Printf("Saved unsigned key to key_%d.d0si%s\n", keygen_i, sessionid.string); } p[0] = buf; l[0] = sizeof(buf); if(!qd0_blind_id_generate_private_id_request(pubkeys[keygen_i], buf, &l[0])) { Con_Printf("d0_blind_id_generate_private_id_request failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } buf2pos = strlen(Cmd_Argv(2)); memcpy(buf2, Cmd_Argv(2), buf2pos); if(!(buf2l = Crypto_UnParsePack(buf2 + buf2pos, sizeof(buf2) - buf2pos - 1, FOURCC_D0IQ, p, l, 1))) { Con_Printf("Crypto_UnParsePack failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } if(!(buf2l = base64_encode((unsigned char *) (buf2 + buf2pos), buf2l, sizeof(buf2) - buf2pos - 1))) { Con_Printf("base64_encode failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } buf2l += buf2pos; buf2[buf2l] = 0; if(!Curl_Begin_ToMemory(buf2, 0, (unsigned char *) keygen_buf, sizeof(keygen_buf), Crypto_KeyGen_Finished, NULL)) { Con_Printf("curl failed\n"); keygen_i = -1; SV_UnlockThreadMutex(); return; } Con_Printf("Signature generation in progress...\n"); SV_UnlockThreadMutex(); } // end // console commands static void Crypto_Reload_f(void) { Crypto_ClearHostKeys(); Crypto_UnloadKeys(); Crypto_LoadKeys(); } static void Crypto_Keys_f(void) { int i; if(!d0_blind_id_dll) { Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); return; } for(i = 0; i < MAX_PUBKEYS; ++i) { if(pubkeys[i]) { Con_Printf("%2d: public key key_%d.d0pk (fingerprint: %s)\n", i, i, pubkeys_fp64[i]); if(pubkeys_havepriv[i]) { Con_Printf(" private ID key_%d.d0si%s (public key fingerprint: %s)\n", i, sessionid.string, pubkeys_priv_fp64[i]); if(!pubkeys_havesig[i]) Con_Printf(" NOTE: this ID has not yet been signed!\n"); } } } } static void Crypto_HostKeys_f(void) { int i; crypto_storedhostkey_t *hk; char buf[128]; if(!d0_blind_id_dll) { Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); return; } for(i = 0; i < CRYPTO_HOSTKEY_HASHSIZE; ++i) { for(hk = crypto_storedhostkey_hashtable[i]; hk; hk = hk->next) { LHNETADDRESS_ToString(&hk->addr, buf, sizeof(buf), 1); Con_Printf("%d %s@%.*s %s\n", hk->aeslevel, hk->idfp, crypto_keyfp_recommended_length, pubkeys_fp64[hk->keyid], buf); } } } static void Crypto_HostKey_Clear_f(void) { lhnetaddress_t addr; int i; if(!d0_blind_id_dll) { Con_Print("libd0_blind_id DLL not found, this command is inactive.\n"); return; } for(i = 1; i < Cmd_Argc(); ++i) { LHNETADDRESS_FromString(&addr, Cmd_Argv(i), 26000); if(Crypto_ClearHostKey(&addr)) { Con_Printf("cleared host key for %s\n", Cmd_Argv(i)); } } } void Crypto_Init_Commands(void) { if(d0_blind_id_dll) { Cmd_AddCommand("crypto_reload", Crypto_Reload_f, "reloads cryptographic keys"); Cmd_AddCommand("crypto_keygen", Crypto_KeyGen_f, "generates and saves a cryptographic key"); Cmd_AddCommand("crypto_keys", Crypto_Keys_f, "lists the loaded keys"); Cmd_AddCommand("crypto_hostkeys", Crypto_HostKeys_f, "lists the cached host keys"); Cmd_AddCommand("crypto_hostkey_clear", Crypto_HostKey_Clear_f, "clears a cached host key"); Cvar_RegisterVariable(&crypto_developer); if(d0_rijndael_dll) Cvar_RegisterVariable(&crypto_aeslevel); else crypto_aeslevel.integer = 0; // make sure Cvar_RegisterVariable(&crypto_servercpupercent); Cvar_RegisterVariable(&crypto_servercpumaxtime); Cvar_RegisterVariable(&crypto_servercpudebug); } } // end // AES encryption static void aescpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) { const unsigned char *xorpos = iv; unsigned char xorbuf[16]; unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; size_t i; qd0_rijndael_setup_encrypt(rk, key, DHKEY_SIZE * 8); while(len > 16) { for(i = 0; i < 16; ++i) xorbuf[i] = src[i] ^ xorpos[i]; qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); xorpos = dst; len -= 16; src += 16; dst += 16; } if(len > 0) { for(i = 0; i < len; ++i) xorbuf[i] = src[i] ^ xorpos[i]; for(; i < 16; ++i) xorbuf[i] = xorpos[i]; qd0_rijndael_encrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), xorbuf, dst); } } static void seacpy(unsigned char *key, const unsigned char *iv, unsigned char *dst, const unsigned char *src, size_t len) { const unsigned char *xorpos = iv; unsigned char xorbuf[16]; unsigned long rk[D0_RIJNDAEL_RKLENGTH(DHKEY_SIZE * 8)]; size_t i; qd0_rijndael_setup_decrypt(rk, key, DHKEY_SIZE * 8); while(len > 16) { qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); for(i = 0; i < 16; ++i) dst[i] = xorbuf[i] ^ xorpos[i]; xorpos = src; len -= 16; src += 16; dst += 16; } if(len > 0) { qd0_rijndael_decrypt(rk, D0_RIJNDAEL_NROUNDS(DHKEY_SIZE * 8), src, xorbuf); for(i = 0; i < len; ++i) dst[i] = xorbuf[i] ^ xorpos[i]; } } // NOTE: we MUST avoid the following begins of the packet: // 1. 0xFF, 0xFF, 0xFF, 0xFF // 2. 0x80, 0x00, length/256, length%256 // this luckily does NOT affect AES mode, where the first byte always is in the range from 0x00 to 0x0F const void *Crypto_EncryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) { unsigned char h[32]; int i; if(crypto->authenticated) { if(crypto->use_aes) { // AES packet = 1 byte length overhead, 15 bytes from HMAC-SHA-256, data, 0..15 bytes padding // 15 bytes HMAC-SHA-256 (112bit) suffice as the attacker can't do more than forge a random-looking packet // HMAC is needed to not leak information about packet content if(developer_networking.integer) { Con_Print("To be encrypted:\n"); Com_HexDumpToConsole((const unsigned char *) data_src, (int)len_src); } if(len_src + 32 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, (int)len_src, crypto->dhkey, DHKEY_SIZE)) { Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); return NULL; } *len_dst = ((len_src + 15) / 16) * 16 + 16; // add 16 for HMAC, then round to 16-size for AES ((unsigned char *) data_dst)[0] = (unsigned char)(*len_dst - len_src); memcpy(((unsigned char *) data_dst)+1, h, 15); aescpy(crypto->dhkey, (const unsigned char *) data_dst, ((unsigned char *) data_dst) + 16, (const unsigned char *) data_src, len_src); // IV dst src len } else { // HMAC packet = 16 bytes HMAC-SHA-256 (truncated to 128 bits), data if(len_src + 16 > len || !HMAC_SHA256_32BYTES(h, (const unsigned char *) data_src, (int)len_src, crypto->dhkey, DHKEY_SIZE)) { Con_Printf("Crypto_EncryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); return NULL; } *len_dst = len_src + 16; memcpy(data_dst, h, 16); memcpy(((unsigned char *) data_dst) + 16, (unsigned char *) data_src, len_src); // handle the "avoid" conditions: i = BuffBigLong((unsigned char *) data_dst); if( (i == (int)0xFFFFFFFF) // avoid QW control packet || (i == (int)0x80000000 + (int)*len_dst) // avoid NQ control packet ) *(unsigned char *)data_dst ^= 0x80; // this will ALWAYS fix it } return data_dst; } else { *len_dst = len_src; return data_src; } } const void *Crypto_DecryptPacket(crypto_t *crypto, const void *data_src, size_t len_src, void *data_dst, size_t *len_dst, size_t len) { unsigned char h[32]; int i; // silently handle non-crypto packets i = BuffBigLong((unsigned char *) data_src); if( (i == (int)0xFFFFFFFF) // avoid QW control packet || (i == (int)0x80000000 + (int)len_src) // avoid NQ control packet ) return NULL; if(crypto->authenticated) { if(crypto->use_aes) { if(len_src < 16 || ((len_src - 16) % 16)) { Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); return NULL; } *len_dst = len_src - ((unsigned char *) data_src)[0]; if(len < *len_dst || *len_dst > len_src - 16) { Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); return NULL; } seacpy(crypto->dhkey, (unsigned char *) data_src, (unsigned char *) data_dst, ((const unsigned char *) data_src) + 16, *len_dst); // IV dst src len if(!HMAC_SHA256_32BYTES(h, (const unsigned char *) data_dst, (int)*len_dst, crypto->dhkey, DHKEY_SIZE)) { Con_Printf("HMAC fail\n"); return NULL; } if(memcmp(((const unsigned char *) data_src)+1, h, 15)) // ignore first byte, used for length { Con_Printf("HMAC mismatch\n"); return NULL; } if(developer_networking.integer) { Con_Print("Decrypted:\n"); Com_HexDumpToConsole((const unsigned char *) data_dst, (int)*len_dst); } return data_dst; // no need to copy } else { if(len_src < 16) { Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d bytes out)\n", (int) len_src, (int) len); return NULL; } *len_dst = len_src - 16; if(len < *len_dst) { Con_Printf("Crypto_DecryptPacket failed (not enough space: %d bytes in, %d->%d bytes out)\n", (int) len_src, (int) *len_dst, (int) len); return NULL; } //memcpy(data_dst, data_src + 16, *len_dst); if(!HMAC_SHA256_32BYTES(h, ((const unsigned char *) data_src) + 16, (int)*len_dst, crypto->dhkey, DHKEY_SIZE)) { Con_Printf("HMAC fail\n"); Com_HexDumpToConsole((const unsigned char *) data_src, (int)len_src); return NULL; } if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length { // undo the "avoid conditions" if( (i == (int)0x7FFFFFFF) // avoided QW control packet || (i == (int)0x00000000 + (int)len_src) // avoided NQ control packet ) { // do the avoidance on the hash too h[0] ^= 0x80; if(memcmp((const unsigned char *) data_src, h, 16)) // ignore first byte, used for length { Con_Printf("HMAC mismatch\n"); Com_HexDumpToConsole((const unsigned char *) data_src, (int)len_src); return NULL; } } else { Con_Printf("HMAC mismatch\n"); Com_HexDumpToConsole((const unsigned char *) data_src, (int)len_src); return NULL; } } return ((const unsigned char *) data_src) + 16; // no need to copy, so data_dst is not used } } else { *len_dst = len_src; return data_src; } } // end const char *Crypto_GetInfoResponseDataString(void) { crypto_idstring_buf[0] = '0' + crypto_aeslevel.integer; return crypto_idstring; } // network protocol qboolean Crypto_ServerAppendToChallenge(const char *data_in, size_t len_in, char *data_out, size_t *len_out, size_t maxlen_out) { // cheap op, all is precomputed if(!d0_blind_id_dll) return false; // no support // append challenge if(maxlen_out <= *len_out + challenge_append_length) return false; memcpy(data_out + *len_out, challenge_append, challenge_append_length); *len_out += challenge_append_length; return false; } static int Crypto_ServerError(char *data_out, size_t *len_out, const char *msg, const char *msg_client) { if(!msg_client) msg_client = msg; Con_DPrintf("rejecting client: %s\n", msg); if(*msg_client) dpsnprintf(data_out, *len_out, "reject %s", msg_client); *len_out = strlen(data_out); return CRYPTO_DISCARD; } static int Crypto_SoftServerError(char *data_out, size_t *len_out, const char *msg) { *len_out = 0; Con_DPrintf("%s\n", msg); return CRYPTO_DISCARD; } static int Crypto_ServerParsePacket_Internal(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) { // if "connect": reject if in the middle of crypto handshake crypto_t *crypto = NULL; char *data_out_p = data_out; const char *string = data_in; int aeslevel; D0_BOOL aes; D0_BOOL status; char infostringvalue[MAX_INPUTLINE]; char vabuf[1024]; if(!d0_blind_id_dll) return CRYPTO_NOMATCH; // no support if (len_in > 8 && !memcmp(string, "connect\\", 8) && d0_rijndael_dll && crypto_aeslevel.integer >= 3) { const char *s; int i; // sorry, we have to verify the challenge here to not reflect network spam if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue)))) return CRYPTO_NOMATCH; // will be later accepted if encryption was set up // validate the challenge for (i = 0;i < MAX_CHALLENGES;i++) if(challenges[i].time > 0) if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strcmp(challenges[i].string, s)) break; // if the challenge is not recognized, drop the packet if (i == MAX_CHALLENGES) // challenge mismatch is silent return Crypto_SoftServerError(data_out, len_out, "missing challenge in connect"); crypto = Crypto_ServerFindInstance(peeraddress, false); if(!crypto || !crypto->authenticated) return Crypto_ServerError(data_out, len_out, "This server requires authentication and encryption to be supported by your client", NULL); } else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && ((LHNETADDRESS_GetAddressType(peeraddress) == LHNETADDRESSTYPE_LOOP) || sv_public.integer > -3)) { const char *cnt, *s, *p; int id; int clientid = -1, serverid = -1; cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue)); id = (cnt ? atoi(cnt) : -1); cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue)); if(!cnt) return Crypto_SoftServerError(data_out, len_out, "missing cnt in d0pk"); GetUntilNul(&data_in, &len_in); if(!data_in) return Crypto_SoftServerError(data_out, len_out, "missing appended data in d0pk"); if(!strcmp(cnt, "0")) { int i; if (!(s = InfoString_GetValue(string + 4, "challenge", infostringvalue, sizeof(infostringvalue)))) return Crypto_SoftServerError(data_out, len_out, "missing challenge in d0pk\\0"); // validate the challenge for (i = 0;i < MAX_CHALLENGES;i++) if(challenges[i].time > 0) if (!LHNETADDRESS_Compare(peeraddress, &challenges[i].address) && !strcmp(challenges[i].string, s)) break; // if the challenge is not recognized, drop the packet if (i == MAX_CHALLENGES) return Crypto_SoftServerError(data_out, len_out, "invalid challenge in d0pk\\0"); if (!(s = InfoString_GetValue(string + 4, "aeslevel", infostringvalue, sizeof(infostringvalue)))) aeslevel = 0; // not supported else aeslevel = bound(0, atoi(s), 3); switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) { default: // dummy, never happens, but to make gcc happy... case 0: if(aeslevel >= 3) return Crypto_ServerError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)", NULL); aes = false; break; case 1: aes = (aeslevel >= 2); break; case 2: aes = (aeslevel >= 1); break; case 3: if(aeslevel <= 0) return Crypto_ServerError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)", NULL); aes = true; break; } p = GetUntilNul(&data_in, &len_in); if(p && *p) { // Find the highest numbered matching key for p. for(i = 0; i < MAX_PUBKEYS; ++i) { if(pubkeys[i]) if(!strcmp(p, pubkeys_fp64[i])) if(pubkeys_havepriv[i]) serverid = i; } if(serverid < 0) return Crypto_ServerError(data_out, len_out, "Invalid server key", NULL); } p = GetUntilNul(&data_in, &len_in); if(p && *p) { // Find the highest numbered matching key for p. for(i = 0; i < MAX_PUBKEYS; ++i) { if(pubkeys[i]) if(!strcmp(p, pubkeys_fp64[i])) clientid = i; } if(clientid < 0) return Crypto_ServerError(data_out, len_out, "Invalid client key", NULL); } crypto = Crypto_ServerFindInstance(peeraddress, true); if(!crypto) return Crypto_ServerError(data_out, len_out, "Could not create a crypto connect instance", NULL); MAKE_CDATA; CDATA->cdata_id = id; CDATA->s = serverid; CDATA->c = clientid; memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); CDATA->challenge[0] = 0; crypto->client_keyfp[0] = 0; crypto->client_idfp[0] = 0; crypto->server_keyfp[0] = 0; crypto->server_idfp[0] = 0; crypto->use_aes = aes != 0; if(CDATA->s >= 0) { // I am the server, and my key is ok... so let's set server_keyfp and server_idfp strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); strlcpy(crypto->server_idfp, pubkeys_priv_fp64[CDATA->s], sizeof(crypto->server_idfp)); crypto->server_issigned = pubkeys_havesig[CDATA->s]; if(!CDATA->id) CDATA->id = qd0_blind_id_new(); if(!CDATA->id) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); } if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); } PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\1\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed", "Internal error"); } CDATA->next_step = 2; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else if(CDATA->c >= 0) { if(!CDATA->id) CDATA->id = qd0_blind_id_new(); if(!CDATA->id) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_new failed", "Internal error"); } if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); } PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\5\\id\\%d\\aes\\%d", CDATA->cdata_id, crypto->use_aes)); if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); } CDATA->next_step = 6; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "Missing client and server key", NULL); } } else if(!strcmp(cnt, "2")) { size_t fpbuflen; crypto = Crypto_ServerFindInstance(peeraddress, false); if(!crypto) return CRYPTO_NOMATCH; // pre-challenge, rather be silent if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 2) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\3\\id\\%d", CDATA->cdata_id)); if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed", "Internal error"); } fpbuflen = DHKEY_SIZE; if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); } if(CDATA->c >= 0) { if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_copy failed", "Internal error"); } CDATA->next_step = 4; } else { // session key is FINISHED (no server part is to be expected)! By this, all keys are set up crypto->authenticated = true; CDATA->next_step = 0; } data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else if(!strcmp(cnt, "4")) { crypto = Crypto_ServerFindInstance(peeraddress, false); if(!crypto) return CRYPTO_NOMATCH; // pre-challenge, rather be silent if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 4) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\5\\id\\%d", CDATA->cdata_id)); if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed", "Internal error"); } CDATA->next_step = 6; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else if(!strcmp(cnt, "6")) { static char msgbuf[32]; size_t msgbuflen = sizeof(msgbuf); size_t fpbuflen; int i; unsigned char dhkey[DHKEY_SIZE]; crypto = Crypto_ServerFindInstance(peeraddress, false); if(!crypto) return CRYPTO_NOMATCH; // pre-challenge, rather be silent if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 6) return Crypto_SoftServerError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (authentication error)", "Authentication error"); } strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); crypto->client_issigned = status; memset(crypto->client_idfp, 0, sizeof(crypto->client_idfp)); fpbuflen = FP64_SIZE; if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->client_idfp, &fpbuflen)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed", "Internal error"); } fpbuflen = DHKEY_SIZE; if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) { CLEAR_CDATA; return Crypto_ServerError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed", "Internal error"); } // XOR the two DH keys together to make one for(i = 0; i < DHKEY_SIZE; ++i) crypto->dhkey[i] ^= dhkey[i]; // session key is FINISHED (no server part is to be expected)! By this, all keys are set up crypto->authenticated = true; CDATA->next_step = 0; // send a challenge-less challenge PutWithNul(&data_out_p, len_out, "challenge "); *len_out = data_out_p - data_out; --*len_out; // remove NUL terminator return CRYPTO_MATCH; } return CRYPTO_NOMATCH; // pre-challenge, rather be silent } return CRYPTO_NOMATCH; } int Crypto_ServerParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) { int ret; double t = 0; static double complain_time = 0; const char *cnt; qboolean do_time = false; qboolean do_reject = false; char infostringvalue[MAX_INPUTLINE]; if(crypto_servercpupercent.value > 0 || crypto_servercpumaxtime.value > 0) if(len_in > 5 && !memcmp(data_in, "d0pk\\", 5)) { do_time = true; cnt = InfoString_GetValue(data_in + 4, "cnt", infostringvalue, sizeof(infostringvalue)); if(cnt) if(!strcmp(cnt, "0")) do_reject = true; } if(do_time) { // check if we may perform crypto... if(crypto_servercpupercent.value > 0) { crypto_servercpu_accumulator += (realtime - crypto_servercpu_lastrealtime) * crypto_servercpupercent.value * 0.01; if(crypto_servercpumaxtime.value) if(crypto_servercpu_accumulator > crypto_servercpumaxtime.value) crypto_servercpu_accumulator = crypto_servercpumaxtime.value; } else { if(crypto_servercpumaxtime.value > 0) if(realtime != crypto_servercpu_lastrealtime) crypto_servercpu_accumulator = crypto_servercpumaxtime.value; } crypto_servercpu_lastrealtime = realtime; if(do_reject && crypto_servercpu_accumulator < 0) { if(realtime > complain_time + 5) Con_Printf("crypto: cannot perform requested crypto operations; denial service attack or crypto_servercpupercent/crypto_servercpumaxtime are too low\n"); *len_out = 0; return CRYPTO_DISCARD; } t = Sys_DirtyTime(); } ret = Crypto_ServerParsePacket_Internal(data_in, len_in, data_out, len_out, peeraddress); if(do_time) { t = Sys_DirtyTime() - t;if (t < 0.0) t = 0.0; // dirtytime can step backwards if(crypto_servercpudebug.integer) Con_Printf("crypto: accumulator was %.1f ms, used %.1f ms for crypto, ", crypto_servercpu_accumulator * 1000, t * 1000); crypto_servercpu_accumulator -= t; if(crypto_servercpudebug.integer) Con_Printf("is %.1f ms\n", crypto_servercpu_accumulator * 1000); } return ret; } static int Crypto_ClientError(char *data_out, size_t *len_out, const char *msg) { dpsnprintf(data_out, *len_out, "reject %s", msg); *len_out = strlen(data_out); return CRYPTO_REPLACE; } static int Crypto_SoftClientError(char *data_out, size_t *len_out, const char *msg) { *len_out = 0; Con_DPrintf("%s\n", msg); return CRYPTO_DISCARD; } int Crypto_ClientParsePacket(const char *data_in, size_t len_in, char *data_out, size_t *len_out, lhnetaddress_t *peeraddress) { crypto_t *crypto = &cls.crypto; const char *string = data_in; const char *s; D0_BOOL aes; char *data_out_p = data_out; D0_BOOL status; char infostringvalue[MAX_INPUTLINE]; char vabuf[1024]; if(!d0_blind_id_dll) return CRYPTO_NOMATCH; // no support // if "challenge": verify challenge, and discard message, send next crypto protocol message instead // otherwise, just handle actual protocol messages if (len_in == 6 && !memcmp(string, "accept", 6) && cls.connect_trying && d0_rijndael_dll) { int wantserverid = -1; Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL, NULL); if(!crypto || !crypto->authenticated) // we ALSO get here if we are using an encrypted connection, so let's rule this out { if(wantserverid >= 0) return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); if(crypto_aeslevel.integer >= 3) return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); } return CRYPTO_NOMATCH; } else if (len_in >= 1 && string[0] == 'j' && cls.connect_trying && d0_rijndael_dll) { int wantserverid = -1; Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL, NULL); //if(!crypto || !crypto->authenticated) { if(wantserverid >= 0) return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); if(crypto_aeslevel.integer >= 3) return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); } return CRYPTO_NOMATCH; } else if (len_in >= 5 && BuffLittleLong((unsigned char *) string) == ((int)NETFLAG_CTL | (int)len_in)) { int wantserverid = -1; // these three are harmless if((unsigned char) string[4] == CCREP_SERVER_INFO) return CRYPTO_NOMATCH; if((unsigned char) string[4] == CCREP_PLAYER_INFO) return CRYPTO_NOMATCH; if((unsigned char) string[4] == CCREP_RULE_INFO) return CRYPTO_NOMATCH; Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, NULL, 0, NULL, NULL); //if(!crypto || !crypto->authenticated) { if(wantserverid >= 0) return Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present"); if(crypto_aeslevel.integer >= 3) return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); } return CRYPTO_NOMATCH; } else if (len_in >= 13 && !memcmp(string, "infoResponse\x0A", 13)) { s = InfoString_GetValue(string + 13, "d0_blind_id", infostringvalue, sizeof(infostringvalue)); if(s) Crypto_StoreHostKey(peeraddress, s, true); return CRYPTO_NOMATCH; } else if (len_in >= 15 && !memcmp(string, "statusResponse\x0A", 15)) { char save = 0; const char *p; p = strchr(string + 15, '\n'); if(p) { save = *p; * (char *) p = 0; // cut off the string there } s = InfoString_GetValue(string + 15, "d0_blind_id", infostringvalue, sizeof(infostringvalue)); if(s) Crypto_StoreHostKey(peeraddress, s, true); if(p) { * (char *) p = save; // invoking those nasal demons again (do not run this on the DS9k) } return CRYPTO_NOMATCH; } else if(len_in > 10 && !memcmp(string, "challenge ", 10) && cls.connect_trying) { const char *vlen_blind_id_ptr = NULL; size_t len_blind_id_ptr = 0; unsigned long k, v; const char *challenge = data_in + 10; const char *p; int i; int clientid = -1, serverid = -1, wantserverid = -1; qboolean server_can_auth = true; char wantserver_idfp[FP64_SIZE+1]; int wantserver_aeslevel = 0; qboolean wantserver_issigned = false; // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server. if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) return Crypto_SoftClientError(data_out, len_out, "challenge message from wrong server"); // if we have a stored host key for the server, assume serverid to already be selected! // (the loop will refuse to overwrite this one then) wantserver_idfp[0] = 0; Crypto_RetrieveHostKey(&cls.connect_address, &wantserverid, NULL, 0, wantserver_idfp, sizeof(wantserver_idfp), &wantserver_aeslevel, &wantserver_issigned); // requirement: wantserver_idfp is a full ID if wantserverid set // if we leave, we have to consider the connection // unauthenticated; NOTE: this may be faked by a clever // attacker to force an unauthenticated connection; so we have // a safeguard check in place when encryption is required too // in place, or when authentication is required by the server crypto->authenticated = false; GetUntilNul(&data_in, &len_in); if(!data_in) return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though a host key is present") : (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") : CRYPTO_NOMATCH; // FTEQW extension protocol while(len_in >= 8) { k = Crypto_LittleLong(data_in); v = Crypto_LittleLong(data_in + 4); data_in += 8; len_in -= 8; switch(k) { case PROTOCOL_VLEN: if(len_in >= 4 + v) { k = Crypto_LittleLong(data_in); data_in += 4; len_in -= 4; switch(k) { case PROTOCOL_D0_BLIND_ID: vlen_blind_id_ptr = data_in; len_blind_id_ptr = v; break; } data_in += v; len_in -= v; } break; default: break; } } if(!vlen_blind_id_ptr) return (wantserverid >= 0) ? Crypto_ClientError(data_out, len_out, "Server tried an unauthenticated connection even though authentication is required") : (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") : CRYPTO_NOMATCH; data_in = vlen_blind_id_ptr; len_in = len_blind_id_ptr; // parse fingerprints // once we found a fingerprint we can auth to (ANY), select it as clientfp // once we found a fingerprint in the first list that we know, select it as serverfp for(;;) { p = GetUntilNul(&data_in, &len_in); if(!p) break; if(!*p) { if(!server_can_auth) break; // other protocol message may follow server_can_auth = false; if(clientid >= 0) break; continue; } // Find the highest numbered matching key for p. for(i = 0; i < MAX_PUBKEYS; ++i) { if(pubkeys[i]) if(!strcmp(p, pubkeys_fp64[i])) { if(pubkeys_havepriv[i]) clientid = i; if(server_can_auth) if(wantserverid < 0 || i == wantserverid) serverid = i; } } // Not breaking, as higher keys in the list always have priority. } // if stored host key is not found: if(wantserverid >= 0 && serverid < 0) return Crypto_ClientError(data_out, len_out, "Server CA does not match stored host key, refusing to connect"); if(serverid >= 0 || clientid >= 0) { MAKE_CDATA; CDATA->cdata_id = ++cdata_id; CDATA->s = serverid; CDATA->c = clientid; memset(crypto->dhkey, 0, sizeof(crypto->dhkey)); strlcpy(CDATA->challenge, challenge, sizeof(CDATA->challenge)); crypto->client_keyfp[0] = 0; crypto->client_idfp[0] = 0; crypto->server_keyfp[0] = 0; crypto->server_idfp[0] = 0; memcpy(CDATA->wantserver_idfp, wantserver_idfp, sizeof(crypto->server_idfp)); CDATA->wantserver_issigned = wantserver_issigned; if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting switch(bound(0, d0_rijndael_dll ? crypto_aeslevel.integer : 0, 3)) { default: // dummy, never happens, but to make gcc happy... case 0: if(wantserver_aeslevel >= 3) return Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)"); CDATA->wantserver_aes = false; break; case 1: CDATA->wantserver_aes = (wantserver_aeslevel >= 2); break; case 2: CDATA->wantserver_aes = (wantserver_aeslevel >= 1); break; case 3: if(wantserver_aeslevel <= 0) return Crypto_ClientError(data_out, len_out, "This server requires encryption to be supported (crypto_aeslevel >= 1, and d0_rijndael library must be present)"); CDATA->wantserver_aes = true; break; } // build outgoing message // append regular stuff PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\0\\id\\%d\\aeslevel\\%d\\challenge\\%s", CDATA->cdata_id, d0_rijndael_dll ? crypto_aeslevel.integer : 0, challenge)); PutWithNul(&data_out_p, len_out, serverid >= 0 ? pubkeys_fp64[serverid] : ""); PutWithNul(&data_out_p, len_out, clientid >= 0 ? pubkeys_fp64[clientid] : ""); if(clientid >= 0) { // I am the client, and my key is ok... so let's set client_keyfp and client_idfp strlcpy(crypto->client_keyfp, pubkeys_fp64[CDATA->c], sizeof(crypto->client_keyfp)); strlcpy(crypto->client_idfp, pubkeys_priv_fp64[CDATA->c], sizeof(crypto->client_idfp)); crypto->client_issigned = pubkeys_havesig[CDATA->c]; } if(serverid >= 0) { if(!CDATA->id) CDATA->id = qd0_blind_id_new(); if(!CDATA->id) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); } if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->s])) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); } CDATA->next_step = 1; *len_out = data_out_p - data_out; } else // if(clientid >= 0) // guaranteed by condition one level outside { // skip over server auth, perform client auth only if(!CDATA->id) CDATA->id = qd0_blind_id_new(); if(!CDATA->id) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_new failed"); } if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); } if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); } CDATA->next_step = 5; data_out_p += *len_out; *len_out = data_out_p - data_out; } return CRYPTO_DISCARD; } else { if(wantserver_idfp[0]) // if we know a host key, honor its encryption setting if(wantserver_aeslevel >= 3) return Crypto_ClientError(data_out, len_out, "Server insists on encryption, but neither can authenticate to the other"); return (d0_rijndael_dll && crypto_aeslevel.integer >= 3) ? Crypto_ClientError(data_out, len_out, "This server requires encryption to be not required (crypto_aeslevel <= 2)") : CRYPTO_NOMATCH; } } else if(len_in > 5 && !memcmp(string, "d0pk\\", 5) && cls.connect_trying) { const char *cnt; int id; // Must check the source IP here, if we want to prevent other servers' replies from falsely advancing the crypto state, preventing successful connect to the real server. if (net_sourceaddresscheck.integer && LHNETADDRESS_Compare(peeraddress, &cls.connect_address)) return Crypto_SoftClientError(data_out, len_out, "d0pk\\ message from wrong server"); cnt = InfoString_GetValue(string + 4, "id", infostringvalue, sizeof(infostringvalue)); id = (cnt ? atoi(cnt) : -1); cnt = InfoString_GetValue(string + 4, "cnt", infostringvalue, sizeof(infostringvalue)); if(!cnt) return Crypto_ClientError(data_out, len_out, "d0pk\\ message without cnt"); GetUntilNul(&data_in, &len_in); if(!data_in) return Crypto_ClientError(data_out, len_out, "d0pk\\ message without attachment"); if(!strcmp(cnt, "1")) { if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 1) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" if((s = InfoString_GetValue(string + 4, "aes", infostringvalue, sizeof(infostringvalue)))) aes = atoi(s); else aes = false; // we CANNOT toggle the AES status any more! // as the server already decided if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting if(!aes && CDATA->wantserver_aes) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); } if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); } if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); } crypto->use_aes = aes != 0; PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\2\\id\\%d", CDATA->cdata_id)); if(!qd0_blind_id_authenticate_with_private_id_challenge(CDATA->id, true, false, data_in, len_in, data_out_p, len_out, &status)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_challenge failed"); } CDATA->next_step = 3; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else if(!strcmp(cnt, "3")) { static char msgbuf[32]; size_t msgbuflen = sizeof(msgbuf); size_t fpbuflen; if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 3) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" if(!qd0_blind_id_authenticate_with_private_id_verify(CDATA->id, data_in, len_in, msgbuf, &msgbuflen, &status)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_verify failed (server authentication error)"); } strlcpy(crypto->server_keyfp, pubkeys_fp64[CDATA->s], sizeof(crypto->server_keyfp)); if (!status && CDATA->wantserver_issigned) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Stored host key requires a valid signature, but server did not provide any"); } crypto->server_issigned = status; memset(crypto->server_idfp, 0, sizeof(crypto->server_idfp)); fpbuflen = FP64_SIZE; if(!qd0_blind_id_fingerprint64_public_id(CDATA->id, crypto->server_idfp, &fpbuflen)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_fingerprint64_public_id failed"); } if(CDATA->wantserver_idfp[0]) if(memcmp(CDATA->wantserver_idfp, crypto->server_idfp, sizeof(crypto->server_idfp))) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Server ID does not match stored host key, refusing to connect"); } fpbuflen = DHKEY_SIZE; if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) crypto->dhkey, &fpbuflen)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); } // cache the server key Crypto_StoreHostKey(&cls.connect_address, va(vabuf, sizeof(vabuf), "%d %s@%s%s", crypto->use_aes ? 1 : 0, crypto->server_idfp, crypto->server_issigned ? "" : "~", pubkeys_fp64[CDATA->s]), false); if(CDATA->c >= 0) { // client will auth next PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\4\\id\\%d", CDATA->cdata_id)); if(!qd0_blind_id_copy(CDATA->id, pubkeys[CDATA->c])) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_copy failed"); } if(!qd0_blind_id_authenticate_with_private_id_start(CDATA->id, true, false, "XONOTIC", 8, data_out_p, len_out)) // len_out receives used size by this op { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_start failed"); } CDATA->next_step = 5; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } else { // session key is FINISHED (no server part is to be expected)! By this, all keys are set up crypto->authenticated = true; CDATA->next_step = 0; // assume we got the empty challenge to finish the protocol PutWithNul(&data_out_p, len_out, "challenge "); *len_out = data_out_p - data_out; --*len_out; // remove NUL terminator return CRYPTO_REPLACE; } } else if(!strcmp(cnt, "5")) { size_t fpbuflen; unsigned char dhkey[DHKEY_SIZE]; int i; if(id >= 0) if(CDATA->cdata_id != id) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\id\\%d when expecting %d", id, CDATA->cdata_id)); if(CDATA->next_step != 5) return Crypto_SoftClientError(data_out, len_out, va(vabuf, sizeof(vabuf), "Got d0pk\\cnt\\%s when expecting %d", cnt, CDATA->next_step)); cls.connect_nextsendtime = max(cls.connect_nextsendtime, realtime + 1); // prevent "hammering" if(CDATA->s < 0) // only if server didn't auth { if((s = InfoString_GetValue(string + 4, "aes", infostringvalue, sizeof(infostringvalue)))) aes = atoi(s); else aes = false; if(CDATA->wantserver_idfp[0]) // if we know a host key, honor its encryption setting if(!aes && CDATA->wantserver_aes) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Stored host key requires encryption, but server did not enable encryption"); } if(aes && (!d0_rijndael_dll || crypto_aeslevel.integer <= 0)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Server insists on encryption too hard"); } if(!aes && (d0_rijndael_dll && crypto_aeslevel.integer >= 3)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "Server insists on plaintext too hard"); } crypto->use_aes = aes != 0; } PutWithNul(&data_out_p, len_out, va(vabuf, sizeof(vabuf), "d0pk\\cnt\\6\\id\\%d", CDATA->cdata_id)); if(!qd0_blind_id_authenticate_with_private_id_response(CDATA->id, data_in, len_in, data_out_p, len_out)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_authenticate_with_private_id_response failed"); } fpbuflen = DHKEY_SIZE; if(!qd0_blind_id_sessionkey_public_id(CDATA->id, (char *) dhkey, &fpbuflen)) { CLEAR_CDATA; return Crypto_ClientError(data_out, len_out, "d0_blind_id_sessionkey_public_id failed"); } // XOR the two DH keys together to make one for(i = 0; i < DHKEY_SIZE; ++i) crypto->dhkey[i] ^= dhkey[i]; // session key is FINISHED! By this, all keys are set up crypto->authenticated = true; CDATA->next_step = 0; data_out_p += *len_out; *len_out = data_out_p - data_out; return CRYPTO_DISCARD; } return Crypto_SoftClientError(data_out, len_out, "Got unknown d0_blind_id message from server"); } return CRYPTO_NOMATCH; } size_t Crypto_SignData(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) { if(keyid < 0 || keyid >= MAX_PUBKEYS) return 0; if(!pubkeys_havepriv[keyid]) return 0; if(qd0_blind_id_sign_with_private_id_sign(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) return signed_size; return 0; } size_t Crypto_SignDataDetached(const void *data, size_t datasize, int keyid, void *signed_data, size_t signed_size) { if(keyid < 0 || keyid >= MAX_PUBKEYS) return 0; if(!pubkeys_havepriv[keyid]) return 0; if(qd0_blind_id_sign_with_private_id_sign_detached(pubkeys[keyid], true, false, (const char *)data, datasize, (char *)signed_data, &signed_size)) return signed_size; return 0; } darkplaces/model_alias.h0000664000175000017500000001320713067716220014610 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MODEL_ALIAS_H #define MODEL_ALIAS_H /* ============================================================================== ALIAS MODELS Alias models are position independent, so the cache manager can move them. ============================================================================== */ #include "modelgen.h" typedef struct daliashdr_s { int ident; int version; vec3_t scale; vec3_t scale_origin; float boundingradius; vec3_t eyeposition; int numskins; int skinwidth; int skinheight; int numverts; int numtris; int numframes; synctype_t synctype; int flags; float size; } daliashdr_t; /* ======================================================================== .MD2 triangle model file format ======================================================================== */ // LordHavoc: grabbed this from the Q2 utility source, // renamed a things to avoid conflicts #define MD2ALIAS_VERSION 8 #define MD2_SKINNAME 64 typedef struct md2stvert_s { short s; short t; } md2stvert_t; typedef struct md2triangle_s { short index_xyz[3]; short index_st[3]; } md2triangle_t; typedef struct md2frame_s { float scale[3]; // multiply byte verts by this float translate[3]; // then add this char name[16]; // frame name from grabbing } md2frame_t; // the glcmd format: // a positive integer starts a tristrip command, followed by that many // vertex structures. // a negative integer starts a trifan command, followed by -x vertexes // a zero indicates the end of the command list. // a vertex consists of a floating point s, a floating point t, // and an integer vertex index. typedef struct md2_s { int ident; int version; int skinwidth; int skinheight; int framesize; // byte size of each frame int num_skins; int num_xyz; int num_st; // greater than num_xyz for seams int num_tris; int num_glcmds; // dwords in strip/fan command list int num_frames; int ofs_skins; // each skin is a MAX_SKINNAME string int ofs_st; // byte offset from start for stverts int ofs_tris; // offset for dtriangles int ofs_frames; // offset for first frame int ofs_glcmds; int ofs_end; // end of file } md2_t; // all md3 ints, floats, and shorts, are little endian, and thus need to be // passed through LittleLong/LittleFloat/LittleShort to avoid breaking on // bigendian machines #define MD3VERSION 15 #define MD3NAME 64 #define MD3FRAMENAME 16 // the origin is at 1/64th scale // the pitch and yaw are encoded as 8 bits each typedef struct md3vertex_s { short origin[3]; unsigned char pitch; unsigned char yaw; } md3vertex_t; // one per frame typedef struct md3frameinfo_s { float mins[3]; float maxs[3]; float origin[3]; float radius; char name[MD3FRAMENAME]; } md3frameinfo_t; // one per tag per frame typedef struct md3tag_s { char name[MD3NAME]; float origin[3]; float rotationmatrix[9]; } md3tag_t; // one per shader per mesh typedef struct md3shader_s { char name[MD3NAME]; // engine field (yes this empty int does exist in the file) int shadernum; } md3shader_t; // one per mesh per model // // note that the lump_ offsets in this struct are relative to the beginning // of the mesh struct // // to find the next mesh in the file, you must go to lump_end, which puts you // at the beginning of the next mesh typedef struct md3mesh_s { char identifier[4]; // "IDP3" char name[MD3NAME]; int flags; int num_frames; int num_shaders; int num_vertices; int num_triangles; int lump_elements; int lump_shaders; int lump_texcoords; int lump_framevertices; int lump_end; } md3mesh_t; // this struct is at the beginning of the md3 file // // note that the lump_ offsets in this struct are relative to the beginning // of the header struct (which is the beginning of the file) typedef struct md3modelheader_s { char identifier[4]; // "IDP3" int version; // 15 char name[MD3NAME]; int flags; int num_frames; int num_tags; int num_meshes; int num_skins; int lump_frameinfo; int lump_tags; int lump_meshes; int lump_end; } md3modelheader_t; typedef struct aliastag_s { char name[MD3NAME]; float matrixgl[12]; } aliastag_t; typedef struct aliasbone_s { char name[MD3NAME]; int flags; int parent; // -1 for no parent } aliasbone_t; #include "model_zymotic.h" #include "model_dpmodel.h" #include "model_psk.h" #include "model_iqm.h" // for decoding md3 model latlong vertex normals extern float mod_md3_sin[320]; extern cvar_t r_skeletal_debugbone; extern cvar_t r_skeletal_debugbonecomponent; extern cvar_t r_skeletal_debugbonevalue; extern cvar_t r_skeletal_debugtranslatex; extern cvar_t r_skeletal_debugtranslatey; extern cvar_t r_skeletal_debugtranslatez; struct model_s; struct frameblend_s; void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes); void Mod_Skeletal_BuildTransforms(const struct model_s * RESTRICT model, const struct frameblend_s * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT bonepose, float * RESTRICT boneposerelative); #endif darkplaces/csprogs.h0000664000175000017500000001231213067716216014020 0ustar kalevkalev#ifndef CSPROGS_H #define CSPROGS_H // LordHavoc: changed to match MAX_EDICTS #define CL_MAX_EDICTS MAX_EDICTS #define ENTMASK_ENGINE 1 #define ENTMASK_ENGINEVIEWMODELS 2 #define ENTMASK_NORMAL 4 #define VF_MIN 1 //(vector) #define VF_MIN_X 2 //(float) #define VF_MIN_Y 3 //(float) #define VF_SIZE 4 //(vector) (viewport size) #define VF_SIZE_X 5 //(float) #define VF_SIZE_Y 6 //(float) #define VF_VIEWPORT 7 //(vector, vector) #define VF_FOV 8 //(vector) #define VF_FOVX 9 //(float) #define VF_FOVY 10 //(float) #define VF_ORIGIN 11 //(vector) #define VF_ORIGIN_X 12 //(float) #define VF_ORIGIN_Y 13 //(float) #define VF_ORIGIN_Z 14 //(float) #define VF_ANGLES 15 //(vector) #define VF_ANGLES_X 16 //(float) #define VF_ANGLES_Y 17 //(float) #define VF_ANGLES_Z 18 //(float) #define VF_DRAWWORLD 19 //(float) //actually world model and sky #define VF_DRAWENGINESBAR 20 //(float) #define VF_DRAWCROSSHAIR 21 //(float) #define VF_CL_VIEWANGLES 33 //(vector) //sweet thing for RPGs/... #define VF_CL_VIEWANGLES_X 34 //(float) #define VF_CL_VIEWANGLES_Y 35 //(float) #define VF_CL_VIEWANGLES_Z 36 //(float) // FTEQW's extension range #define VF_PERSPECTIVE 200 //(float) // what is this doing here? This is a DP extension introduced by Black, should be in 4xx range #define VF_CLEARSCREEN 201 //(float) // what is this doing here? This is a DP extension introduced by VorteX, should be in 4xx range #define VF_FOG_DENSITY 202 //(float) #define VF_FOG_COLOR 203 //(vector) #define VF_FOG_COLOR_R 204 //(float) #define VF_FOG_COLOR_G 205 //(float) #define VF_FOG_COLOR_B 206 //(float) #define VF_FOG_ALPHA 207 //(float) #define VF_FOG_START 208 //(float) #define VF_FOG_END 209 //(float) #define VF_FOG_HEIGHT 210 //(float) #define VF_FOG_FADEDEPTH 211 //(float) // DP's extension range #define VF_MAINVIEW 400 //(float) #define VF_MINFPS_QUALITY 401 //(float) #define RF_VIEWMODEL 1 // The entity is never drawn in mirrors. In engines with realtime lighting, it casts no shadows. #define RF_EXTERNALMODEL 2 // The entity is appears in mirrors but not in the normal view. It does still cast shadows in engines with realtime lighting. #define RF_DEPTHHACK 4 // The entity appears closer to the view than normal, either by scaling it wierdly or by just using a depthrange. This will usually be found in conjunction with RF_VIEWMODEL #define RF_ADDITIVE 8 // Add the entity acording to it's alpha values instead of the normal blend #define RF_USEAXIS 16 // When set, the entity will use the v_forward, v_right and v_up globals instead of it's angles field for orientation. Angles will be ignored compleatly. // Note that to use this properly, you'll NEED to use the predraw function to set the globals. //#define RF_DOUBLESIDED 32 #define RF_USETRANSPARENTOFFSET 64 // Allows QC to customize origin used for transparent sorting via transparent_origin global, helps to fix transparent sorting bugs on a very large entities #define RF_WORLDOBJECT 128 // for large outdoor entities that should not be culled #define RF_MODELLIGHT 4096 // CSQC-set model light #define RF_DYNAMICMODELLIGHT 8192 // origin-dependent model light #define RF_FULLBRIGHT 256 #define RF_NOSHADOW 512 extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat extern cvar_t csqc_progcrc; extern cvar_t csqc_progsize; void CL_VM_PreventInformationLeaks(void); qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol); qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out); qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin); void CL_VM_Init(void); void CL_VM_ShutDown(void); void CL_VM_UpdateIntermissionState(int intermission); void CL_VM_UpdateShowingScoresState(int showingscores); qboolean CL_VM_InputEvent(int eventtype, float x, float y); qboolean CL_VM_ConsoleCommand(const char *cmd); void CL_VM_UpdateDmgGlobals(int dmg_take, int dmg_save, vec3_t dmg_origin); void CL_VM_UpdateIntermissionState(int intermission); qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos, int flags, float speed); qboolean CL_VM_Parse_TempEntity(void); void CL_VM_Parse_StuffCmd(const char *msg); void CL_VM_Parse_CenterPrint(const char *msg); int CL_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent); int CL_GetTagMatrix(prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex); void CL_GetEntityMatrix(prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); /* VMs exposing the polygon calls must call this on Init/Reset */ void VM_Polygons_Reset(prvm_prog_t *prog); void QW_CL_StartUpload(unsigned char *data, int size); void CSQC_UpdateNetworkTimes(double newtime, double oldtime); void CSQC_AddPrintText(const char *msg); void CSQC_ReadEntities(void); void CSQC_RelinkAllEntities(int drawmask); void CSQC_RelinkCSQCEntities(void); void CSQC_Predraw(prvm_edict_t *ed); void CSQC_Think(prvm_edict_t *ed); qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum);//csprogs.c void CSQC_R_RecalcView(void); dp_model_t *CL_GetModelByIndex(int modelindex); int CL_VM_GetViewEntity(void); #endif darkplaces/darkplaces-wgl-vs2015.vcxproj0000664000175000017500000004522313067716220017444 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28} darkplaceswgl Win32Proj darkplaces-wgl-vs2015 Application v140 MultiByte true Application v140 MultiByte Application v140 MultiByte true Application v140 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX86 true X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 true X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/matrixlib.h0000664000175000017500000002344713067716220014341 0ustar kalevkalev #ifndef MATRIXLIB_H #define MATRIXLIB_H #ifndef M_PI #define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h #endif //#define MATRIX4x4_OPENGLORIENTATION typedef struct matrix4x4_s { vec_t m[4][4]; } matrix4x4_t; extern const matrix4x4_t identitymatrix; // functions for manipulating 4x4 matrices // copy a matrix4x4 void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in); // copy only the rotation portion of a matrix4x4 void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in); // copy only the translate portion of a matrix4x4 void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in); // multiply two matrix4x4 together, combining their transformations // (warning: order matters - Concat(a, b, c) != Concat(a, c, b)) void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2); // swaps the rows and columns of the matrix // (is this useful for anything?) void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1); // creates a matrix that does the opposite of the matrix provided // this is a full matrix inverter, it should be able to invert any matrix that // is possible to invert // (non-uniform scaling, rotation, shearing, and translation, possibly others) // warning: this function is SLOW int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1); // creates a matrix that does the opposite of the matrix provided // only supports translate, rotate, scale (not scale3) matrices void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1); // blends between two matrices, used primarily for animation interpolation // (note: it is recommended to follow this with Matrix4x4_Normalize, a method // known as nlerp rotation, often better for animation purposes than slerp) void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac); // zeros all matrix components, used with Matrix4x4_Accumulate void Matrix4x4_Clear (matrix4x4_t *out); // adds a weighted contribution from the supplied matrix, used to blend 3 or // more matrices with weighting, it is recommended that Matrix4x4_Normalize be // called afterward (a method known as nlerp rotation, often better for // animation purposes than slerp) void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight); // creates a matrix that does the same rotation and translation as the matrix // provided, but no uniform scaling, does not support scale3 matrices void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1); // creates a matrix with vectors normalized individually (use after // Matrix4x4_Accumulate) void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1); // modifies a matrix to have all vectors and origin reflected across the plane // to the opposite side (at least if axisscale is -2) void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale); // creates an identity matrix // (a matrix which does nothing) void Matrix4x4_CreateIdentity (matrix4x4_t *out); // creates a translate matrix // (moves vectors) void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z); // creates a rotate matrix // (rotates vectors) void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z); // creates a scaling matrix // (expands or contracts vectors) // (warning: do not apply this kind of matrix to direction vectors) void Matrix4x4_CreateScale (matrix4x4_t *out, double x); // creates a squishing matrix // (expands or contracts vectors differently in different axis) // (warning: this is not reversed by Invert_Simple) // (warning: do not apply this kind of matrix to direction vectors) void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z); // creates a matrix for a quake entity void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale); // creates a duke3d view matrix for a quake view matrix ;) void Matrix4x4_QuakeToDuke3D(const matrix4x4_t *in, matrix4x4_t *out, double maxShearAngle); // converts a matrix4x4 to a set of 3D vectors for the 3 axial directions, and the translate void Matrix4x4_ToVectors(const matrix4x4_t *in, vec_t vx[3], vec_t vy[3], vec_t vz[3], vec_t t[3]); // creates a matrix4x4 from a set of 3D vectors for axial directions, and translate void Matrix4x4_FromVectors(matrix4x4_t *out, const vec_t vx[3], const vec_t vy[3], const vec_t vz[3], const vec_t t[3]); // converts a matrix4x4 to a double[16] array in the OpenGL orientation void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]); // creates a matrix4x4 from a double[16] array in the OpenGL orientation void Matrix4x4_FromArrayDoubleGL(matrix4x4_t *out, const double in[16]); // converts a matrix4x4 to a double[16] array in the Direct3D orientation void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]); // creates a matrix4x4 from a double[16] array in the Direct3D orientation void Matrix4x4_FromArrayDoubleD3D(matrix4x4_t *out, const double in[16]); // converts a matrix4x4 to a float[16] array in the OpenGL orientation void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]); // creates a matrix4x4 from a float[16] array in the OpenGL orientation void Matrix4x4_FromArrayFloatGL(matrix4x4_t *out, const float in[16]); // converts a matrix4x4 to a float[16] array in the Direct3D orientation void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]); // creates a matrix4x4 from a float[16] array in the Direct3D orientation void Matrix4x4_FromArrayFloatD3D(matrix4x4_t *out, const float in[16]); // converts a matrix4x4 to a float[12] array in the OpenGL orientation void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]); // creates a matrix4x4 from a float[12] array in the OpenGL orientation void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]); // converts a matrix4x4 to a float[12] array in the Direct3D orientation void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]); // creates a matrix4x4 from a float[12] array in the Direct3D orientation void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]); // creates a matrix4x4 from an origin and quaternion (used mostly with skeletal model formats such as PSK) void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w); // creates an origin and quaternion from a matrix4x4_t, quat[3] is always positive void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat); // creates a matrix4x4 from an origin and canonical unit-length quaternion (used mostly with skeletal model formats such as MD5) void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z); // creates a matrix4x4_t from an origin and canonical unit-length quaternion in short[7] normalized format void Matrix4x4_FromBonePose7s(matrix4x4_t *m, float originscale, const short *pose7s); // creates a short[7] representation from normalized matrix4x4_t void Matrix4x4_ToBonePose7s(const matrix4x4_t *m, float origininvscale, short *pose7s); // blends two matrices together, at a given percentage (blend controls percentage of in2) void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend); // transforms a 3D vector through a matrix4x4 void Matrix4x4_Transform (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); // transforms a 4D vector through a matrix4x4 // (warning: if you don't know why you would need this, you don't need it) // (warning: the 4th component of the vector should be 1.0) void Matrix4x4_Transform4 (const matrix4x4_t *in, const vec_t v[4], vec_t out[4]); // reverse transforms a 3D vector through a matrix4x4, at least for *simple* // cases (rotation and translation *ONLY*), this attempts to undo the results // of Transform //void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); // transforms a direction vector through the rotation part of a matrix void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const vec_t v[3], vec_t out[3]); // transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix void Matrix4x4_TransformPositivePlane (const matrix4x4_t *in, vec_t x, vec_t y, vec_t z, vec_t d, vec_t *o); // transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix void Matrix4x4_TransformStandardPlane (const matrix4x4_t *in, vec_t x, vec_t y, vec_t z, vec_t d, vec_t *o); // ease of use functions // immediately applies a Translate to the matrix void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z); // immediately applies a Rotate to the matrix void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z); // immediately applies a Scale to the matrix void Matrix4x4_ConcatScale (matrix4x4_t *out, double x); // immediately applies a Scale3 to the matrix void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z); // extracts origin vector (translate) from matrix void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, vec_t *out); // extracts scaling factor from matrix (only works for uniform scaling) double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in); // replaces origin vector (translate) in matrix void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z); // moves origin vector (translate) in matrix by a simple translate void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z); // scales vectors of a matrix in place and allows you to scale origin as well void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale); // ensures each element of the 3x3 rotation matrix is facing in the + direction void Matrix4x4_Abs (matrix4x4_t *out); #endif darkplaces/svvm_cmds.c0000664000175000017500000040776313067716222014353 0ustar kalevkalev#include "quakedef.h" #include "prvm_cmds.h" #include "jpeg.h" //============================================================================ // Server const char *vm_sv_extensions = "BX_WAL_SUPPORT " "DP_BUTTONCHAT " "DP_BUTTONUSE " "DP_CL_LOADSKY " "DP_CON_ALIASPARAMETERS " "DP_CON_BESTWEAPON " "DP_CON_EXPANDCVAR " "DP_CON_SET " "DP_CON_SETA " "DP_CON_STARTMAP " "DP_COVERAGE " "DP_CRYPTO " "DP_CSQC_BINDMAPS " "DP_CSQC_ENTITYWORLDOBJECT " "DP_CSQC_ENTITYMODELLIGHT " "DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET " "DP_CSQC_MAINVIEW " "DP_CSQC_MINFPS_QUALITY " "DP_CSQC_MULTIFRAME_INTERPOLATION " "DP_CSQC_BOXPARTICLES " "DP_CSQC_SPAWNPARTICLE " "DP_CSQC_QUERYRENDERENTITY " "DP_CSQC_ROTATEMOVES " "DP_CSQC_SETPAUSE " "DP_CSQC_V_CALCREFDEF_WIP1 " "DP_CSQC_V_CALCREFDEF_WIP2 " "DP_EF_ADDITIVE " "DP_EF_BLUE " "DP_EF_DOUBLESIDED " "DP_EF_DYNAMICMODELLIGHT " "DP_EF_FLAME " "DP_EF_FULLBRIGHT " "DP_EF_NODEPTHTEST " "DP_EF_NODRAW " "DP_EF_NOGUNBOB " "DP_EF_NOSELFSHADOW " "DP_EF_NOSHADOW " "DP_EF_RED " "DP_EF_RESTARTANIM_BIT " "DP_EF_STARDUST " "DP_EF_TELEPORT_BIT " "DP_ENT_ALPHA " "DP_ENT_COLORMOD " "DP_ENT_CUSTOMCOLORMAP " "DP_ENT_EXTERIORMODELTOCLIENT " "DP_ENT_GLOW " "DP_ENT_GLOWMOD " "DP_ENT_LOWPRECISION " "DP_ENT_SCALE " "DP_ENT_TRAILEFFECTNUM " "DP_ENT_VIEWMODEL " "DP_GFX_EXTERNALTEXTURES " "DP_GFX_EXTERNALTEXTURES_PERMAP " "DP_GFX_FOG " "DP_GFX_MODEL_INTERPOLATION " "DP_GFX_QUAKE3MODELTAGS " "DP_GFX_SKINFILES " "DP_GFX_SKYBOX " "DP_GFX_FONTS " "DP_GFX_FONTS_FREETYPE " "DP_UTF8 " "DP_FONT_VARIABLEWIDTH " "DP_HALFLIFE_MAP " "DP_HALFLIFE_MAP_CVAR " "DP_HALFLIFE_SPRITE " "DP_INPUTBUTTONS " "DP_LIGHTSTYLE_STATICVALUE " "DP_LITSPRITES " "DP_LITSUPPORT " "DP_MONSTERWALK " "DP_MOVETYPEBOUNCEMISSILE " "DP_MOVETYPEFLYWORLDONLY " "DP_MOVETYPEFOLLOW " "DP_NULL_MODEL " "DP_QC_ASINACOSATANATAN2TAN " "DP_QC_AUTOCVARS " "DP_QC_CHANGEPITCH " "DP_QC_CMD " "DP_QC_COPYENTITY " "DP_QC_CRC16 " "DP_QC_CVAR_DEFSTRING " "DP_QC_CVAR_DESCRIPTION " "DP_QC_CVAR_STRING " "DP_QC_CVAR_TYPE " "DP_QC_DIGEST " "DP_QC_DIGEST_SHA256 " "DP_QC_EDICT_NUM " "DP_QC_ENTITYDATA " "DP_QC_ENTITYSTRING " "DP_QC_ETOS " "DP_QC_EXTRESPONSEPACKET " "DP_QC_FINDCHAIN " "DP_QC_FINDCHAINFLAGS " "DP_QC_FINDCHAINFLOAT " "DP_QC_FINDCHAIN_TOFIELD " "DP_QC_FINDFLAGS " "DP_QC_FINDFLOAT " "DP_QC_FS_SEARCH " "DP_QC_GETLIGHT " "DP_QC_GETSURFACE " "DP_QC_GETSURFACETRIANGLE " "DP_QC_GETSURFACEPOINTATTRIBUTE " "DP_QC_GETTAGINFO " "DP_QC_GETTAGINFO_BONEPROPERTIES " "DP_QC_GETTIME " "DP_QC_GETTIME_CDTRACK " "DP_QC_I18N " "DP_QC_LOG " "DP_QC_MINMAXBOUND " "DP_QC_MULTIPLETEMPSTRINGS " "DP_QC_NUM_FOR_EDICT " "DP_QC_RANDOMVEC " "DP_QC_SINCOSSQRTPOW " "DP_QC_SPRINTF " "DP_QC_STRFTIME " "DP_QC_STRINGBUFFERS " "DP_QC_STRINGBUFFERS_CVARLIST " "DP_QC_STRINGBUFFERS_EXT_WIP " "DP_QC_STRINGCOLORFUNCTIONS " "DP_QC_STRING_CASE_FUNCTIONS " "DP_QC_STRREPLACE " "DP_QC_TOKENIZEBYSEPARATOR " "DP_QC_TOKENIZE_CONSOLE " "DP_QC_TRACEBOX " "DP_QC_TRACETOSS " "DP_QC_TRACE_MOVETYPE_HITMODEL " "DP_QC_TRACE_MOVETYPE_WORLDONLY " "DP_QC_UNLIMITEDTEMPSTRINGS " "DP_QC_URI_ESCAPE " "DP_QC_URI_GET " "DP_QC_URI_POST " "DP_QC_VECTOANGLES_WITH_ROLL " "DP_QC_VECTORVECTORS " "DP_QC_WHICHPACK " "DP_QUAKE2_MODEL " "DP_QUAKE2_SPRITE " "DP_QUAKE3_MAP " "DP_QUAKE3_MODEL " "DP_REGISTERCVAR " "DP_SKELETONOBJECTS " "DP_SND_DIRECTIONLESSATTNNONE " "DP_SND_FAKETRACKS " "DP_SND_SOUND7_WIP1 " "DP_SND_SOUND7_WIP2 " "DP_SND_OGGVORBIS " "DP_SND_SETPARAMS " "DP_SND_STEREOWAV " "DP_SND_GETSOUNDTIME " "DP_VIDEO_DPV " "DP_VIDEO_SUBTITLES " "DP_SOLIDCORPSE " "DP_SPRITE32 " "DP_SV_BOTCLIENT " "DP_SV_BOUNCEFACTOR " "DP_SV_CLIENTCAMERA " "DP_SV_CLIENTCOLORS " "DP_SV_CLIENTNAME " "DP_SV_CMD " "DP_SV_CUSTOMIZEENTITYFORCLIENT " "DP_SV_DISABLECLIENTPREDICTION " "DP_SV_DISCARDABLEDEMO " "DP_SV_DRAWONLYTOCLIENT " "DP_SV_DROPCLIENT " "DP_SV_EFFECT " "DP_SV_ENTITYCONTENTSTRANSITION " "DP_SV_MODELFLAGS_AS_EFFECTS " "DP_SV_MOVETYPESTEP_LANDEVENT " "DP_SV_NETADDRESS " "DP_SV_NODRAWTOCLIENT " "DP_SV_ONENTITYNOSPAWNFUNCTION " "DP_SV_ONENTITYPREPOSTSPAWNFUNCTION " "DP_SV_PING " "DP_SV_PING_PACKETLOSS " "DP_SV_PLAYERPHYSICS " "DP_PHYSICS_ODE " "DP_SV_POINTPARTICLES " "DP_SV_POINTSOUND " "DP_SV_PRECACHEANYTIME " "DP_SV_PRINT " "DP_SV_PUNCHVECTOR " "DP_SV_QCSTATUS " "DP_SV_ROTATINGBMODEL " "DP_SV_SETCOLOR " "DP_SV_SHUTDOWN " "DP_SV_SLOWMO " "DP_SV_SPAWNFUNC_PREFIX " "DP_SV_WRITEPICTURE " "DP_SV_WRITEUNTERMINATEDSTRING " "DP_TE_BLOOD " "DP_TE_BLOODSHOWER " "DP_TE_CUSTOMFLASH " "DP_TE_EXPLOSIONRGB " "DP_TE_FLAMEJET " "DP_TE_PARTICLECUBE " "DP_TE_PARTICLERAIN " "DP_TE_PARTICLESNOW " "DP_TE_PLASMABURN " "DP_TE_QUADEFFECTS1 " "DP_TE_SMALLFLASH " "DP_TE_SPARK " "DP_TE_STANDARDEFFECTBUILTINS " "DP_TRACE_HITCONTENTSMASK_SURFACEINFO " "DP_USERMOVETYPES " "DP_VIEWZOOM " "EXT_BITSHIFT " "FRIK_FILE " "FTE_CSQC_SKELETONOBJECTS " "FTE_QC_CHECKPVS " "FTE_STRINGS " "KRIMZON_SV_PARSECLIENTCOMMAND " "NEH_CMD_PLAY2 " "NEH_RESTOREGAME " "NEXUIZ_PLAYERMODEL " "NXQ_GFX_LETTERBOX " "PRYDON_CLIENTCURSOR " "TENEBRAE_GFX_DLIGHTS " "TW_SV_STEPCONTROL " "ZQ_PAUSE " //"EXT_CSQC " // not ready yet ; /* ================= VM_SV_setorigin This is the only valid way to move an object without using the physics of the world (setting velocity and waiting). Directly changing origin will not set internal links correctly, so clipping would be messed up. This should be called when an object is spawned, and then only if it is teleported. setorigin (entity, origin) ================= */ static void VM_SV_setorigin(prvm_prog_t *prog) { prvm_edict_t *e; VM_SAFEPARMCOUNT(2, VM_setorigin); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setorigin: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setorigin: can not modify free entity\n"); return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM1), PRVM_serveredictvector(e, origin)); if(e->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) e->priv.required->mark = PRVM_EDICT_MARK_SETORIGIN_CAUGHT; SV_LinkEdict(e); } // TODO: rotate param isnt used.. could be a bug. please check this and remove it if possible [1/10/2008 Black] static void SetMinMaxSize (prvm_prog_t *prog, prvm_edict_t *e, float *min, float *max, qboolean rotate) { int i; for (i=0 ; i<3 ; i++) if (min[i] > max[i]) prog->error_cmd("SetMinMaxSize: backwards mins/maxs"); // set derived values VectorCopy (min, PRVM_serveredictvector(e, mins)); VectorCopy (max, PRVM_serveredictvector(e, maxs)); VectorSubtract (max, min, PRVM_serveredictvector(e, size)); SV_LinkEdict(e); } /* ================= VM_SV_setsize the size box is rotated by the current angle LordHavoc: no it isn't... setsize (entity, minvector, maxvector) ================= */ static void VM_SV_setsize(prvm_prog_t *prog) { prvm_edict_t *e; vec3_t mins, maxs; VM_SAFEPARMCOUNT(3, VM_setsize); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setsize: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setsize: can not modify free entity\n"); return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM1), mins); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), maxs); SetMinMaxSize(prog, e, mins, maxs, false); } /* ================= VM_SV_setmodel setmodel(entity, model) ================= */ static vec3_t quakemins = {-16, -16, -16}, quakemaxs = {16, 16, 16}; static void VM_SV_setmodel(prvm_prog_t *prog) { prvm_edict_t *e; dp_model_t *mod; int i; VM_SAFEPARMCOUNT(2, VM_setmodel); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setmodel: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setmodel: can not modify free entity\n"); return; } i = SV_ModelIndex(PRVM_G_STRING(OFS_PARM1), 1); PRVM_serveredictstring(e, model) = PRVM_SetEngineString(prog, sv.model_precache[i]); PRVM_serveredictfloat(e, modelindex) = i; mod = SV_GetModelByIndex(i); if (mod) { if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) SetMinMaxSize(prog, e, mod->normalmins, mod->normalmaxs, true); else SetMinMaxSize(prog, e, quakemins, quakemaxs, true); } else SetMinMaxSize(prog, e, vec3_origin, vec3_origin, true); } /* ================= VM_SV_sprint single print to a specific client sprint(clientent, value) ================= */ static void VM_SV_sprint(prvm_prog_t *prog) { client_t *client; int entnum; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_sprint); VM_VarString(prog, 1, string, sizeof(string)); entnum = PRVM_G_EDICTNUM(OFS_PARM0); // LordHavoc: div0 requested that sprintto world operate like print if (entnum == 0) { Con_Print(string); return; } if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) { VM_Warning(prog, "tried to centerprint to a non-client\n"); return; } client = svs.clients + entnum-1; if (!client->netconnection) return; MSG_WriteChar(&client->netconnection->message,svc_print); MSG_WriteString(&client->netconnection->message, string); } /* ================= VM_SV_centerprint single print to a specific client centerprint(clientent, value) ================= */ static void VM_SV_centerprint(prvm_prog_t *prog) { client_t *client; int entnum; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_centerprint); entnum = PRVM_G_EDICTNUM(OFS_PARM0); if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) { VM_Warning(prog, "tried to centerprint to a non-client\n"); return; } client = svs.clients + entnum-1; if (!client->netconnection) return; VM_VarString(prog, 1, string, sizeof(string)); MSG_WriteChar(&client->netconnection->message,svc_centerprint); MSG_WriteString(&client->netconnection->message, string); } /* ================= VM_SV_particle particle(origin, color, count) ================= */ static void VM_SV_particle(prvm_prog_t *prog) { vec3_t org, dir; int color; int count; VM_SAFEPARMCOUNT(4, VM_SV_particle); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); color = (int)PRVM_G_FLOAT(OFS_PARM2); count = (int)PRVM_G_FLOAT(OFS_PARM3); SV_StartParticle (org, dir, color, count); } /* ================= VM_SV_ambientsound ================= */ static void VM_SV_ambientsound(prvm_prog_t *prog) { const char *samp; vec3_t pos; prvm_vec_t vol, attenuation; int soundnum, large; VM_SAFEPARMCOUNT(4, VM_SV_ambientsound); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); samp = PRVM_G_STRING(OFS_PARM1); vol = PRVM_G_FLOAT(OFS_PARM2); attenuation = PRVM_G_FLOAT(OFS_PARM3); // check to see if samp was properly precached soundnum = SV_SoundIndex(samp, 1); if (!soundnum) return; large = false; if (soundnum >= 256) large = true; // add an svc_spawnambient command to the level signon packet if (large) MSG_WriteByte (&sv.signon, svc_spawnstaticsound2); else MSG_WriteByte (&sv.signon, svc_spawnstaticsound); MSG_WriteVector(&sv.signon, pos, sv.protocol); if (large || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) MSG_WriteShort (&sv.signon, soundnum); else MSG_WriteByte (&sv.signon, soundnum); MSG_WriteByte (&sv.signon, (int)(vol*255)); MSG_WriteByte (&sv.signon, (int)(attenuation*64)); } /* ================= VM_SV_sound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. ================= */ static void VM_SV_sound(prvm_prog_t *prog) { const char *sample; int channel; prvm_edict_t *entity; int nvolume; int flags; float attenuation; float pitchchange; VM_SAFEPARMCOUNTRANGE(4, 7, VM_SV_sound); entity = PRVM_G_EDICT(OFS_PARM0); channel = (int)PRVM_G_FLOAT(OFS_PARM1); sample = PRVM_G_STRING(OFS_PARM2); nvolume = (int)(PRVM_G_FLOAT(OFS_PARM3) * 255); if (prog->argc < 5) { Con_DPrintf("VM_SV_sound: given only 4 parameters, expected 5, assuming attenuation = ATTN_NORMAL\n"); attenuation = 1; } else attenuation = PRVM_G_FLOAT(OFS_PARM4); if (prog->argc < 6) pitchchange = 0; else pitchchange = PRVM_G_FLOAT(OFS_PARM5) * 0.01f; if (prog->argc < 7) { flags = 0; if(channel >= 8 && channel <= 15) // weird QW feature { flags |= CHANNELFLAG_RELIABLE; channel -= 8; } } else { // LordHavoc: we only let the qc set certain flags, others are off-limits flags = (int)PRVM_G_FLOAT(OFS_PARM6) & (CHANNELFLAG_RELIABLE | CHANNELFLAG_FORCELOOP | CHANNELFLAG_PAUSED); } if (nvolume < 0 || nvolume > 255) { VM_Warning(prog, "SV_StartSound: volume must be in range 0-1\n"); return; } if (attenuation < 0 || attenuation > 4) { VM_Warning(prog, "SV_StartSound: attenuation must be in range 0-4\n"); return; } channel = CHAN_USER2ENGINE(channel); if (!IS_CHAN(channel)) { VM_Warning(prog, "SV_StartSound: channel must be in range 0-127\n"); return; } SV_StartSound (entity, channel, sample, nvolume, attenuation, flags & CHANNELFLAG_RELIABLE, pitchchange); } /* ================= VM_SV_pointsound Follows the same logic as VM_SV_sound, except instead of an entity, an origin for the sound is provided, and channel is omitted (since no entity is being tracked). ================= */ static void VM_SV_pointsound(prvm_prog_t *prog) { const char *sample; int nvolume; float attenuation; float pitchchange; vec3_t org; VM_SAFEPARMCOUNTRANGE(4, 5, VM_SV_pointsound); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); sample = PRVM_G_STRING(OFS_PARM1); nvolume = (int)(PRVM_G_FLOAT(OFS_PARM2) * 255); attenuation = PRVM_G_FLOAT(OFS_PARM3); pitchchange = prog->argc < 5 ? 0 : PRVM_G_FLOAT(OFS_PARM4) * 0.01f; if (nvolume < 0 || nvolume > 255) { VM_Warning(prog, "SV_StartPointSound: volume must be in range 0-1\n"); return; } if (attenuation < 0 || attenuation > 4) { VM_Warning(prog, "SV_StartPointSound: attenuation must be in range 0-4\n"); return; } SV_StartPointSound (org, sample, nvolume, attenuation, pitchchange); } /* ================= VM_SV_traceline Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. traceline (vector1, vector2, movetype, ignore) ================= */ static void VM_SV_traceline(prvm_prog_t *prog) { vec3_t v1, v2; trace_t trace; int move; prvm_edict_t *ent; VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_traceline); // allow more parameters for future expansion prog->xfunction->builtinsprofile += 30; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), v2); move = (int)PRVM_G_FLOAT(OFS_PARM2); ent = PRVM_G_EDICT(OFS_PARM3); if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) prog->error_cmd("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); trace = SV_TraceLine(v1, v2, move, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendtracelinelength.value); VM_SetTraceGlobals(prog, &trace); } /* ================= VM_SV_tracebox Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. tracebox (vector1, vector mins, vector maxs, vector2, tryents) ================= */ // LordHavoc: added this for my own use, VERY useful, similar to traceline static void VM_SV_tracebox(prvm_prog_t *prog) { vec3_t v1, v2, m1, m2; trace_t trace; int move; prvm_edict_t *ent; VM_SAFEPARMCOUNTRANGE(6, 8, VM_SV_tracebox); // allow more parameters for future expansion prog->xfunction->builtinsprofile += 30; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), m1); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), m2); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), v2); move = (int)PRVM_G_FLOAT(OFS_PARM4); ent = PRVM_G_EDICT(OFS_PARM5); if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) prog->error_cmd("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); trace = SV_TraceBox(v1, m1, m2, v2, move, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendtraceboxlength.value); VM_SetTraceGlobals(prog, &trace); } static trace_t SV_Trace_Toss(prvm_prog_t *prog, prvm_edict_t *tossent, prvm_edict_t *ignore) { int i; float gravity; vec3_t move, end, tossentorigin, tossentmins, tossentmaxs; vec3_t original_origin; vec3_t original_velocity; vec3_t original_angles; vec3_t original_avelocity; trace_t trace; VectorCopy(PRVM_serveredictvector(tossent, origin) , original_origin ); VectorCopy(PRVM_serveredictvector(tossent, velocity) , original_velocity ); VectorCopy(PRVM_serveredictvector(tossent, angles) , original_angles ); VectorCopy(PRVM_serveredictvector(tossent, avelocity), original_avelocity); gravity = PRVM_serveredictfloat(tossent, gravity); if (!gravity) gravity = 1.0f; gravity *= sv_gravity.value * 0.025; for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds { SV_CheckVelocity (tossent); PRVM_serveredictvector(tossent, velocity)[2] -= gravity; VectorMA (PRVM_serveredictvector(tossent, angles), 0.05, PRVM_serveredictvector(tossent, avelocity), PRVM_serveredictvector(tossent, angles)); VectorScale (PRVM_serveredictvector(tossent, velocity), 0.05, move); VectorAdd (PRVM_serveredictvector(tossent, origin), move, end); VectorCopy(PRVM_serveredictvector(tossent, origin), tossentorigin); VectorCopy(PRVM_serveredictvector(tossent, mins), tossentmins); VectorCopy(PRVM_serveredictvector(tossent, maxs), tossentmaxs); trace = SV_TraceBox(tossentorigin, tossentmins, tossentmaxs, end, MOVE_NORMAL, tossent, SV_GenericHitSuperContentsMask(tossent), 0, collision_extendmovelength.value); VectorCopy (trace.endpos, PRVM_serveredictvector(tossent, origin)); PRVM_serveredictvector(tossent, velocity)[2] -= gravity; if (trace.fraction < 1) break; } VectorCopy(original_origin , PRVM_serveredictvector(tossent, origin) ); VectorCopy(original_velocity , PRVM_serveredictvector(tossent, velocity) ); VectorCopy(original_angles , PRVM_serveredictvector(tossent, angles) ); VectorCopy(original_avelocity, PRVM_serveredictvector(tossent, avelocity)); return trace; } static void VM_SV_tracetoss(prvm_prog_t *prog) { trace_t trace; prvm_edict_t *ent; prvm_edict_t *ignore; VM_SAFEPARMCOUNT(2, VM_SV_tracetoss); prog->xfunction->builtinsprofile += 600; ent = PRVM_G_EDICT(OFS_PARM0); if (ent == prog->edicts) { VM_Warning(prog, "tracetoss: can not use world entity\n"); return; } ignore = PRVM_G_EDICT(OFS_PARM1); trace = SV_Trace_Toss(prog, ent, ignore); VM_SetTraceGlobals(prog, &trace); } //============================================================================ static int checkpvsbytes; static unsigned char checkpvs[MAX_MAP_LEAFS/8]; static int VM_SV_newcheckclient(prvm_prog_t *prog, int check) { int i; prvm_edict_t *ent; vec3_t org; // cycle to the next one check = bound(1, check, svs.maxclients); if (check == svs.maxclients) i = 1; else i = check + 1; for ( ; ; i++) { // count the cost prog->xfunction->builtinsprofile++; // wrap around if (i == svs.maxclients+1) i = 1; // look up the client's edict ent = PRVM_EDICT_NUM(i); // check if it is to be ignored, but never ignore the one we started on (prevent infinite loop) if (i != check && (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0 || ((int)PRVM_serveredictfloat(ent, flags) & FL_NOTARGET))) continue; // found a valid client (possibly the same one again) break; } // get the PVS for the entity VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, view_ofs), org); checkpvsbytes = 0; if (sv.worldmodel && sv.worldmodel->brush.FatPVS) checkpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, org, 0, checkpvs, sizeof(checkpvs), false); return i; } /* ================= VM_SV_checkclient Returns a client (or object that has a client enemy) that would be a valid target. If there is more than one valid option, they are cycled each frame If (self.origin + self.viewofs) is not in the PVS of the current target, it is not returned at all. name checkclient () ================= */ int c_invis, c_notvis; static void VM_SV_checkclient(prvm_prog_t *prog) { prvm_edict_t *ent, *self; vec3_t view; VM_SAFEPARMCOUNT(0, VM_SV_checkclient); // find a new check if on a new frame if (sv.time - sv.lastchecktime >= 0.1) { sv.lastcheck = VM_SV_newcheckclient(prog, sv.lastcheck); sv.lastchecktime = sv.time; } // return check if it might be visible ent = PRVM_EDICT_NUM(sv.lastcheck); if (ent->priv.server->free || PRVM_serveredictfloat(ent, health) <= 0) { VM_RETURN_EDICT(prog->edicts); return; } // if current entity can't possibly see the check entity, return 0 self = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); VectorAdd(PRVM_serveredictvector(self, origin), PRVM_serveredictvector(self, view_ofs), view); if (sv.worldmodel && checkpvsbytes && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, checkpvs, view, view)) { c_notvis++; VM_RETURN_EDICT(prog->edicts); return; } // might be able to see it c_invis++; VM_RETURN_EDICT(ent); } //============================================================================ /* ================= VM_SV_checkpvs Checks if an entity is in a point's PVS. Should be fast but can be inexact. float checkpvs(vector viewpos, entity viewee) = #240; ================= */ static void VM_SV_checkpvs(prvm_prog_t *prog) { vec3_t viewpos, absmin, absmax; prvm_edict_t *viewee; #if 1 unsigned char *pvs; #else int fatpvsbytes; unsigned char fatpvs[MAX_MAP_LEAFS/8]; #endif VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); viewee = PRVM_G_EDICT(OFS_PARM1); if(viewee->priv.server->free) { VM_Warning(prog, "checkpvs: can not check free entity\n"); PRVM_G_FLOAT(OFS_RETURN) = 4; return; } #if 1 if(!sv.worldmodel || !sv.worldmodel->brush.GetPVS || !sv.worldmodel->brush.BoxTouchingPVS) { // no PVS support on this worldmodel... darn PRVM_G_FLOAT(OFS_RETURN) = 3; return; } pvs = sv.worldmodel->brush.GetPVS(sv.worldmodel, viewpos); if(!pvs) { // viewpos isn't in any PVS... darn PRVM_G_FLOAT(OFS_RETURN) = 2; return; } VectorCopy(PRVM_serveredictvector(viewee, absmin), absmin); VectorCopy(PRVM_serveredictvector(viewee, absmax), absmax); PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, pvs, absmin, absmax); #else // using fat PVS like FTEQW does (slow) if(!sv.worldmodel || !sv.worldmodel->brush.FatPVS || !sv.worldmodel->brush.BoxTouchingPVS) { // no PVS support on this worldmodel... darn PRVM_G_FLOAT(OFS_RETURN) = 3; return; } fatpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); if(!fatpvsbytes) { // viewpos isn't in any PVS... darn PRVM_G_FLOAT(OFS_RETURN) = 2; return; } VectorCopy(PRVM_serveredictvector(viewee, absmin), absmin); VectorCopy(PRVM_serveredictvector(viewee, absmax), absmax); PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, fatpvs, absmin, absmax); #endif } /* ================= VM_SV_stuffcmd Sends text over to the client's execution buffer stuffcmd (clientent, value, ...) ================= */ static void VM_SV_stuffcmd(prvm_prog_t *prog) { int entnum; client_t *old; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_SV_stuffcmd); entnum = PRVM_G_EDICTNUM(OFS_PARM0); if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) { VM_Warning(prog, "Can't stuffcmd to a non-client\n"); return; } VM_VarString(prog, 1, string, sizeof(string)); old = host_client; host_client = svs.clients + entnum-1; Host_ClientCommands ("%s", string); host_client = old; } /* ================= VM_SV_findradius Returns a chain of entities that have origins within a spherical area findradius (origin, radius) ================= */ static void VM_SV_findradius(prvm_prog_t *prog) { prvm_edict_t *ent, *chain; vec_t radius, radius2; vec3_t org, eorg, mins, maxs; int i; int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; int chainfield; VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_findradius); if(prog->argc == 3) chainfield = PRVM_G_INT(OFS_PARM2); else chainfield = prog->fieldoffsets.chain; if (chainfield < 0) prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); chain = (prvm_edict_t *)prog->edicts; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); radius = PRVM_G_FLOAT(OFS_PARM1); radius2 = radius * radius; mins[0] = org[0] - (radius + 1); mins[1] = org[1] - (radius + 1); mins[2] = org[2] - (radius + 1); maxs[0] = org[0] + (radius + 1); maxs[1] = org[1] + (radius + 1); maxs[2] = org[2] + (radius + 1); numtouchedicts = SV_EntitiesInBox(mins, maxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { ent = touchedicts[i]; prog->xfunction->builtinsprofile++; // Quake did not return non-solid entities but darkplaces does // (note: this is the reason you can't blow up fallen zombies) if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) continue; // LordHavoc: compare against bounding box rather than center so it // doesn't miss large objects, and use DotProduct instead of Length // for a major speedup VectorSubtract(org, PRVM_serveredictvector(ent, origin), eorg); if (sv_gameplayfix_findradiusdistancetobox.integer) { eorg[0] -= bound(PRVM_serveredictvector(ent, mins)[0], eorg[0], PRVM_serveredictvector(ent, maxs)[0]); eorg[1] -= bound(PRVM_serveredictvector(ent, mins)[1], eorg[1], PRVM_serveredictvector(ent, maxs)[1]); eorg[2] -= bound(PRVM_serveredictvector(ent, mins)[2], eorg[2], PRVM_serveredictvector(ent, maxs)[2]); } else VectorMAMAM(1, eorg, -0.5f, PRVM_serveredictvector(ent, mins), -0.5f, PRVM_serveredictvector(ent, maxs), eorg); if (DotProduct(eorg, eorg) < radius2) { PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); chain = ent; } } VM_RETURN_EDICT(chain); } static void VM_SV_precache_sound(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_precache_sound); PRVM_G_FLOAT(OFS_RETURN) = SV_SoundIndex(PRVM_G_STRING(OFS_PARM0), 2); } static void VM_SV_precache_model(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_precache_model); SV_ModelIndex(PRVM_G_STRING(OFS_PARM0), 2); PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); } /* =============== VM_SV_walkmove float(float yaw, float dist[, settrace]) walkmove =============== */ static void VM_SV_walkmove(prvm_prog_t *prog) { prvm_edict_t *ent; float yaw, dist; vec3_t move; mfunction_t *oldf; int oldself; qboolean settrace; VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_walkmove); // assume failure if it returns early PRVM_G_FLOAT(OFS_RETURN) = 0; ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "walkmove: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "walkmove: can not modify free entity\n"); return; } yaw = PRVM_G_FLOAT(OFS_PARM0); dist = PRVM_G_FLOAT(OFS_PARM1); settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) return; yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; // save program state, because SV_movestep may call other progs oldf = prog->xfunction; oldself = PRVM_serverglobaledict(self); PRVM_G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true, false, settrace); // restore program state prog->xfunction = oldf; PRVM_serverglobaledict(self) = oldself; } /* =============== VM_SV_droptofloor void() droptofloor =============== */ static void VM_SV_droptofloor(prvm_prog_t *prog) { prvm_edict_t *ent; vec3_t end, entorigin, entmins, entmaxs; trace_t trace; VM_SAFEPARMCOUNTRANGE(0, 2, VM_SV_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype // assume failure if it returns early PRVM_G_FLOAT(OFS_RETURN) = 0; ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "droptofloor: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "droptofloor: can not modify free entity\n"); return; } VectorCopy (PRVM_serveredictvector(ent, origin), end); end[2] -= 256; if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer) SV_NudgeOutOfSolid(ent); VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); VectorCopy(PRVM_serveredictvector(ent, mins), entmins); VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); trace = SV_TraceBox(entorigin, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.startsolid && sv_gameplayfix_droptofloorstartsolid.integer) { vec3_t offset, org; VectorSet(offset, 0.5f * (PRVM_serveredictvector(ent, mins)[0] + PRVM_serveredictvector(ent, maxs)[0]), 0.5f * (PRVM_serveredictvector(ent, mins)[1] + PRVM_serveredictvector(ent, maxs)[1]), PRVM_serveredictvector(ent, mins)[2]); VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); trace = SV_TraceLine(org, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); VectorSubtract(trace.endpos, offset, trace.endpos); if (trace.startsolid) { Con_DPrintf("droptofloor at %f %f %f - COULD NOT FIX BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); SV_LinkEdict(ent); PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = 0; PRVM_G_FLOAT(OFS_RETURN) = 1; } else if (trace.fraction < 1) { Con_DPrintf("droptofloor at %f %f %f - FIXED BADLY PLACED ENTITY\n", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer) SV_NudgeOutOfSolid(ent); SV_LinkEdict(ent); PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); PRVM_G_FLOAT(OFS_RETURN) = 1; // if support is destroyed, keep suspended (gross hack for floating items in various maps) ent->priv.server->suspendedinairflag = true; } } else { if (!trace.allsolid && trace.fraction < 1) { VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); SV_LinkEdict(ent); PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); PRVM_G_FLOAT(OFS_RETURN) = 1; // if support is destroyed, keep suspended (gross hack for floating items in various maps) ent->priv.server->suspendedinairflag = true; } } } /* =============== VM_SV_lightstyle void(float style, string value) lightstyle =============== */ static void VM_SV_lightstyle(prvm_prog_t *prog) { int style; const char *val; client_t *client; int j; VM_SAFEPARMCOUNT(2, VM_SV_lightstyle); style = (int)PRVM_G_FLOAT(OFS_PARM0); val = PRVM_G_STRING(OFS_PARM1); if( (unsigned) style >= MAX_LIGHTSTYLES ) { prog->error_cmd( "PF_lightstyle: style: %i >= 64", style ); } // change the string in sv strlcpy(sv.lightstyles[style], val, sizeof(sv.lightstyles[style])); // send message to all clients on this server if (sv.state != ss_active) return; for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) { if (client->active && client->netconnection) { MSG_WriteChar (&client->netconnection->message, svc_lightstyle); MSG_WriteChar (&client->netconnection->message,style); MSG_WriteString (&client->netconnection->message, val); } } } /* ============= VM_SV_checkbottom ============= */ static void VM_SV_checkbottom(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_checkbottom); PRVM_G_FLOAT(OFS_RETURN) = SV_CheckBottom (PRVM_G_EDICT(OFS_PARM0)); } /* ============= VM_SV_pointcontents ============= */ static void VM_SV_pointcontents(prvm_prog_t *prog) { vec3_t point; VM_SAFEPARMCOUNT(1, VM_SV_pointcontents); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), point); PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(point)); } /* ============= VM_SV_aim Pick a vector for the player to shoot along vector aim(entity, missilespeed) ============= */ static void VM_SV_aim(prvm_prog_t *prog) { prvm_edict_t *ent, *check, *bestent; vec3_t start, dir, end, bestdir; int i, j; trace_t tr; float dist, bestdist; //float speed; VM_SAFEPARMCOUNT(2, VM_SV_aim); // assume failure if it returns early VectorCopy(PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); // if sv_aim is so high it can't possibly accept anything, skip out early if (sv_aim.value >= 1) return; ent = PRVM_G_EDICT(OFS_PARM0); if (ent == prog->edicts) { VM_Warning(prog, "aim: can not use world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "aim: can not use free entity\n"); return; } //speed = PRVM_G_FLOAT(OFS_PARM1); VectorCopy (PRVM_serveredictvector(ent, origin), start); start[2] += 20; // try sending a trace straight VectorCopy (PRVM_serverglobalvector(v_forward), dir); VectorMA (start, 2048, dir, end); tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, collision_extendmovelength.value); if (tr.ent && PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), takedamage) == DAMAGE_AIM && (!teamplay.integer || PRVM_serveredictfloat(ent, team) <=0 || PRVM_serveredictfloat(ent, team) != PRVM_serveredictfloat(((prvm_edict_t *)tr.ent), team)) ) { VectorCopy (PRVM_serverglobalvector(v_forward), PRVM_G_VECTOR(OFS_RETURN)); return; } // try all possible entities VectorCopy (dir, bestdir); bestdist = sv_aim.value; bestent = NULL; check = PRVM_NEXT_EDICT(prog->edicts); for (i=1 ; inum_edicts ; i++, check = PRVM_NEXT_EDICT(check) ) { prog->xfunction->builtinsprofile++; if (PRVM_serveredictfloat(check, takedamage) != DAMAGE_AIM) continue; if (check == ent) continue; if (teamplay.integer && PRVM_serveredictfloat(ent, team) > 0 && PRVM_serveredictfloat(ent, team) == PRVM_serveredictfloat(check, team)) continue; // don't aim at teammate for (j=0 ; j<3 ; j++) end[j] = PRVM_serveredictvector(check, origin)[j] + 0.5*(PRVM_serveredictvector(check, mins)[j] + PRVM_serveredictvector(check, maxs)[j]); VectorSubtract (end, start, dir); VectorNormalize (dir); dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); if (dist < bestdist) continue; // to far to turn tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, 0, collision_extendmovelength.value); if (tr.ent == check) { // can shoot at this one bestdist = dist; bestent = check; } } if (bestent) { VectorSubtract (PRVM_serveredictvector(bestent, origin), PRVM_serveredictvector(ent, origin), dir); dist = DotProduct (dir, PRVM_serverglobalvector(v_forward)); VectorScale (PRVM_serverglobalvector(v_forward), dist, end); end[2] = dir[2]; VectorNormalize (end); VectorCopy (end, PRVM_G_VECTOR(OFS_RETURN)); } else { VectorCopy (bestdir, PRVM_G_VECTOR(OFS_RETURN)); } } /* =============================================================================== MESSAGE WRITING =============================================================================== */ #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string #define MSG_ENTITY 5 static sizebuf_t *WriteDest(prvm_prog_t *prog) { int entnum; int dest; prvm_edict_t *ent; dest = (int)PRVM_G_FLOAT(OFS_PARM0); switch (dest) { case MSG_BROADCAST: return &sv.datagram; case MSG_ONE: ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(msg_entity)); entnum = PRVM_NUM_FOR_EDICT(ent); if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active || !svs.clients[entnum-1].netconnection) { VM_Warning(prog, "WriteDest: tried to write to non-client\n"); return &sv.reliable_datagram; } else return &svs.clients[entnum-1].netconnection->message; default: VM_Warning(prog, "WriteDest: bad destination\n"); case MSG_ALL: return &sv.reliable_datagram; case MSG_INIT: return &sv.signon; case MSG_ENTITY: return sv.writeentitiestoclient_msg; } //return NULL; } static void VM_SV_WriteByte(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteByte); MSG_WriteByte (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); } static void VM_SV_WriteChar(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteChar); MSG_WriteChar (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); } static void VM_SV_WriteShort(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteShort); MSG_WriteShort (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); } static void VM_SV_WriteLong(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteLong); MSG_WriteLong (WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM1)); } static void VM_SV_WriteAngle(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteAngle); MSG_WriteAngle (WriteDest(prog), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); } static void VM_SV_WriteCoord(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteCoord); MSG_WriteCoord (WriteDest(prog), PRVM_G_FLOAT(OFS_PARM1), sv.protocol); } static void VM_SV_WriteString(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteString); MSG_WriteString (WriteDest(prog), PRVM_G_STRING(OFS_PARM1)); } static void VM_SV_WriteUnterminatedString(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteUnterminatedString); MSG_WriteUnterminatedString (WriteDest(prog), PRVM_G_STRING(OFS_PARM1)); } static void VM_SV_WriteEntity(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_WriteEntity); MSG_WriteShort (WriteDest(prog), PRVM_G_EDICTNUM(OFS_PARM1)); } // writes a picture as at most size bytes of data // message: // IMGNAME \0 SIZE(short) IMGDATA // if failed to read/compress: // IMGNAME \0 \0 \0 //#501 void(float dest, string name, float maxsize) WritePicture (DP_SV_WRITEPICTURE)) static void VM_SV_WritePicture(prvm_prog_t *prog) { const char *imgname; void *buf; size_t size; VM_SAFEPARMCOUNT(3, VM_SV_WritePicture); imgname = PRVM_G_STRING(OFS_PARM1); size = (size_t) PRVM_G_FLOAT(OFS_PARM2); if(size > 65535) size = 65535; MSG_WriteString(WriteDest(prog), imgname); if(Image_Compress(imgname, size, &buf, &size)) { // actual picture MSG_WriteShort(WriteDest(prog), (int)size); SZ_Write(WriteDest(prog), (unsigned char *) buf, (int)size); } else { // placeholder MSG_WriteShort(WriteDest(prog), 0); } } ////////////////////////////////////////////////////////// static void VM_SV_makestatic(prvm_prog_t *prog) { prvm_edict_t *ent; int i, large; // allow 0 parameters due to an id1 qc bug in which this function is used // with no parameters (but directly after setmodel with self in OFS_PARM0) VM_SAFEPARMCOUNTRANGE(0, 1, VM_SV_makestatic); if (prog->argc >= 1) ent = PRVM_G_EDICT(OFS_PARM0); else ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "makestatic: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "makestatic: can not modify free entity\n"); return; } large = false; if (PRVM_serveredictfloat(ent, modelindex) >= 256 || PRVM_serveredictfloat(ent, frame) >= 256) large = true; if (large) { MSG_WriteByte (&sv.signon,svc_spawnstatic2); MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); } else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) { MSG_WriteByte (&sv.signon,svc_spawnstatic); MSG_WriteShort (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); } else { MSG_WriteByte (&sv.signon,svc_spawnstatic); MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, modelindex)); MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, frame)); } MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, colormap)); MSG_WriteByte (&sv.signon, (int)PRVM_serveredictfloat(ent, skin)); for (i=0 ; i<3 ; i++) { MSG_WriteCoord(&sv.signon, PRVM_serveredictvector(ent, origin)[i], sv.protocol); MSG_WriteAngle(&sv.signon, PRVM_serveredictvector(ent, angles)[i], sv.protocol); } // throw the entity away now PRVM_ED_Free(prog, ent); } //============================================================================= /* ============== VM_SV_setspawnparms ============== */ static void VM_SV_setspawnparms(prvm_prog_t *prog) { prvm_edict_t *ent; int i; client_t *client; VM_SAFEPARMCOUNT(1, VM_SV_setspawnparms); ent = PRVM_G_EDICT(OFS_PARM0); i = PRVM_NUM_FOR_EDICT(ent); if (i < 1 || i > svs.maxclients || !svs.clients[i-1].active) { Con_Print("tried to setspawnparms on a non-client\n"); return; } // copy spawn parms out of the client_t client = svs.clients + i-1; for (i=0 ; i< NUM_SPAWN_PARMS ; i++) (&PRVM_serverglobalfloat(parm1))[i] = client->spawn_parms[i]; } /* ================= VM_SV_getlight Returns a color vector indicating the lighting at the requested point. (Internal Operation note: actually measures the light beneath the point, just like the model lighting on the client) getlight(vector) ================= */ static void VM_SV_getlight(prvm_prog_t *prog) { vec3_t ambientcolor, diffusecolor, diffusenormal; vec3_t p; VM_SAFEPARMCOUNT(1, VM_SV_getlight); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), p); VectorClear(ambientcolor); VectorClear(diffusecolor); VectorClear(diffusenormal); if (sv.worldmodel && sv.worldmodel->brush.LightPoint) sv.worldmodel->brush.LightPoint(sv.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); } typedef struct { unsigned char type; // 1/2/8 or other value if isn't used int fieldoffset; }customstat_t; static customstat_t *vm_customstats = NULL; //[515]: it starts from 0, not 32 static int vm_customstats_last; void VM_CustomStats_Clear (void) { if(vm_customstats) { Z_Free(vm_customstats); vm_customstats = NULL; vm_customstats_last = -1; } } void VM_SV_UpdateCustomStats (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) { prvm_prog_t *prog = SVVM_prog; int i; char s[17]; if(!vm_customstats) return; for(i=0; i= (MAX_CL_STATS-32)) { VM_Warning(prog, "PF_SV_AddStat: index >= MAX_CL_STATS\n"); return; } if(i > (MAX_CL_STATS-32-4) && type == 1) { VM_Warning(prog, "PF_SV_AddStat: index > (MAX_CL_STATS-4) with string\n"); return; } vm_customstats[i].type = type; vm_customstats[i].fieldoffset = off; if(vm_customstats_last < i) vm_customstats_last = i; } /* ================= VM_SV_copyentity copies data from one entity to another copyentity(src, dst) ================= */ static void VM_SV_copyentity(prvm_prog_t *prog) { prvm_edict_t *in, *out; VM_SAFEPARMCOUNT(2, VM_SV_copyentity); in = PRVM_G_EDICT(OFS_PARM0); if (in == prog->edicts) { VM_Warning(prog, "copyentity: can not read world entity\n"); return; } if (in->priv.server->free) { VM_Warning(prog, "copyentity: can not read free entity\n"); return; } out = PRVM_G_EDICT(OFS_PARM1); if (out == prog->edicts) { VM_Warning(prog, "copyentity: can not modify world entity\n"); return; } if (out->priv.server->free) { VM_Warning(prog, "copyentity: can not modify free entity\n"); return; } memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); SV_LinkEdict(out); } /* ================= VM_SV_setcolor sets the color of a client and broadcasts the update to all connected clients setcolor(clientent, value) ================= */ static void VM_SV_setcolor(prvm_prog_t *prog) { client_t *client; int entnum, i; VM_SAFEPARMCOUNT(2, VM_SV_setcolor); entnum = PRVM_G_EDICTNUM(OFS_PARM0); i = (int)PRVM_G_FLOAT(OFS_PARM1); if (entnum < 1 || entnum > svs.maxclients || !svs.clients[entnum-1].active) { Con_Print("tried to setcolor a non-client\n"); return; } client = svs.clients + entnum-1; if (client->edict) { PRVM_serveredictfloat(client->edict, clientcolors) = i; PRVM_serveredictfloat(client->edict, team) = (i & 15) + 1; } client->colors = i; if (client->old_colors != client->colors) { client->old_colors = client->colors; // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); MSG_WriteByte (&sv.reliable_datagram, client - svs.clients); MSG_WriteByte (&sv.reliable_datagram, client->colors); } } /* ================= VM_SV_effect effect(origin, modelname, startframe, framecount, framerate) ================= */ static void VM_SV_effect(prvm_prog_t *prog) { int i; const char *s; vec3_t org; VM_SAFEPARMCOUNT(5, VM_SV_effect); s = PRVM_G_STRING(OFS_PARM1); if (!s[0]) { VM_Warning(prog, "effect: no model specified\n"); return; } i = SV_ModelIndex(s, 1); if (!i) { VM_Warning(prog, "effect: model not precached\n"); return; } if (PRVM_G_FLOAT(OFS_PARM3) < 1) { VM_Warning(prog, "effect: framecount < 1\n"); return; } if (PRVM_G_FLOAT(OFS_PARM4) < 1) { VM_Warning(prog, "effect: framerate < 1\n"); return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); SV_StartEffect(org, i, (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4)); } static void VM_SV_te_blood(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_blood); if (PRVM_G_FLOAT(OFS_PARM2) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_BLOOD); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // velocity MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); // count MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); SV_FlushBroadcastMessages(); } static void VM_SV_te_bloodshower(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(4, VM_SV_te_bloodshower); if (PRVM_G_FLOAT(OFS_PARM3) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_BLOODSHOWER); // min MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // max MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // speed MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM2), sv.protocol); // count MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); SV_FlushBroadcastMessages(); } static void VM_SV_te_explosionrgb(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2, VM_SV_te_explosionrgb); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_EXPLOSIONRGB); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // color MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[0] * 255), 255)); MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[1] * 255), 255)); MSG_WriteByte(&sv.datagram, bound(0, (int) (PRVM_G_VECTOR(OFS_PARM1)[2] * 255), 255)); SV_FlushBroadcastMessages(); } static void VM_SV_te_particlecube(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(7, VM_SV_te_particlecube); if (PRVM_G_FLOAT(OFS_PARM3) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_PARTICLECUBE); // min MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // max MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // velocity MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); // count MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); // color MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); // gravity true/false MSG_WriteByte(&sv.datagram, ((int) PRVM_G_FLOAT(OFS_PARM5)) != 0); // randomvel MSG_WriteCoord(&sv.datagram, PRVM_G_FLOAT(OFS_PARM6), sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_particlerain(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(5, VM_SV_te_particlerain); if (PRVM_G_FLOAT(OFS_PARM3) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_PARTICLERAIN); // min MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // max MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // velocity MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); // count MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); // color MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); SV_FlushBroadcastMessages(); } static void VM_SV_te_particlesnow(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(5, VM_SV_te_particlesnow); if (PRVM_G_FLOAT(OFS_PARM3) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_PARTICLESNOW); // min MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // max MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // velocity MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); // count MSG_WriteShort(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM3), 65535)); // color MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM4)); SV_FlushBroadcastMessages(); } static void VM_SV_te_spark(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_spark); if (PRVM_G_FLOAT(OFS_PARM2) < 1) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SPARK); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // velocity MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[0], 127)); MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[1], 127)); MSG_WriteChar(&sv.datagram, bound(-128, (int) PRVM_G_VECTOR(OFS_PARM1)[2], 127)); // count MSG_WriteByte(&sv.datagram, bound(0, (int) PRVM_G_FLOAT(OFS_PARM2), 255)); SV_FlushBroadcastMessages(); } static void VM_SV_te_gunshotquad(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_gunshotquad); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_GUNSHOTQUAD); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_spikequad(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_spikequad); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SPIKEQUAD); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_superspikequad(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_superspikequad); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SUPERSPIKEQUAD); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_explosionquad(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_explosionquad); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_EXPLOSIONQUAD); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_smallflash(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_smallflash); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SMALLFLASH); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_customflash(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(4, VM_SV_te_customflash); if (PRVM_G_FLOAT(OFS_PARM1) < 8 || PRVM_G_FLOAT(OFS_PARM2) < (1.0 / 256.0)) return; MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_CUSTOMFLASH); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // radius MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM1) / 8 - 1, 255)); // lifetime MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_FLOAT(OFS_PARM2) * 256 - 1, 255)); // color MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[0] * 255, 255)); MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[1] * 255, 255)); MSG_WriteByte(&sv.datagram, (int)bound(0, PRVM_G_VECTOR(OFS_PARM3)[2] * 255, 255)); SV_FlushBroadcastMessages(); } static void VM_SV_te_gunshot(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_gunshot); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_GUNSHOT); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_spike(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_spike); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SPIKE); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_superspike(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_superspike); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_SUPERSPIKE); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_explosion(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_explosion); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_EXPLOSION); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_tarexplosion(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_tarexplosion); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_TAREXPLOSION); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_wizspike(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_wizspike); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_WIZSPIKE); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_knightspike(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_knightspike); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_KNIGHTSPIKE); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_lavasplash(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_lavasplash); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_LAVASPLASH); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_teleport(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_teleport); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_TELEPORT); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_explosion2(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_explosion2); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_EXPLOSION2); // origin MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // color MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); SV_FlushBroadcastMessages(); } static void VM_SV_te_lightning1(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_lightning1); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_LIGHTNING1); // owner entity MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); // start MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // end MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_lightning2(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_lightning2); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_LIGHTNING2); // owner entity MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); // start MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // end MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_lightning3(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_lightning3); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_LIGHTNING3); // owner entity MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); // start MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // end MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_beam(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_beam); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_BEAM); // owner entity MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); // start MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // end MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM2)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_plasmaburn(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_SV_te_plasmaburn); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_PLASMABURN); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); SV_FlushBroadcastMessages(); } static void VM_SV_te_flamejet(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3, VM_SV_te_flamejet); MSG_WriteByte(&sv.datagram, svc_temp_entity); MSG_WriteByte(&sv.datagram, TE_FLAMEJET); // org MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM0)[2], sv.protocol); // vel MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[0], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[1], sv.protocol); MSG_WriteCoord(&sv.datagram, PRVM_G_VECTOR(OFS_PARM1)[2], sv.protocol); // count MSG_WriteByte(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM2)); SV_FlushBroadcastMessages(); } //void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client //this function originally written by KrimZon, made shorter by LordHavoc static void VM_SV_clientcommand(prvm_prog_t *prog) { client_t *temp_client; int i; VM_SAFEPARMCOUNT(2, VM_SV_clientcommand); //find client for this entity i = (PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)) - 1); if (i < 0 || i >= svs.maxclients || !svs.clients[i].active) { Con_Print("PF_clientcommand: entity is not a client\n"); return; } temp_client = host_client; host_client = svs.clients + i; Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); host_client = temp_client; } //void(entity e, entity tagentity, string tagname) setattachment = #443; // attachs e to a tag on tagentity (note: use "" to attach to entity origin/angles instead of a tag) static void VM_SV_setattachment(prvm_prog_t *prog) { prvm_edict_t *e = PRVM_G_EDICT(OFS_PARM0); prvm_edict_t *tagentity = PRVM_G_EDICT(OFS_PARM1); const char *tagname = PRVM_G_STRING(OFS_PARM2); dp_model_t *model; int tagindex; VM_SAFEPARMCOUNT(3, VM_SV_setattachment); if (e == prog->edicts) { VM_Warning(prog, "setattachment: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setattachment: can not modify free entity\n"); return; } if (tagentity == NULL) tagentity = prog->edicts; tagindex = 0; if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) { model = SV_GetModelFromEdict(tagentity); if (model) { tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_serveredictfloat(tagentity, skin), tagname); if (tagindex == 0) Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); } else Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); } PRVM_serveredictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); PRVM_serveredictfloat(e, tag_index) = tagindex; } ///////////////////////////////////////// // DP_MD3_TAGINFO extension coded by VorteX static int SV_GetTagIndex (prvm_prog_t *prog, prvm_edict_t *e, const char *tagname) { int i; i = (int)PRVM_serveredictfloat(e, modelindex); if (i < 1 || i >= MAX_MODELS) return -1; return Mod_Alias_GetTagIndexForName(SV_GetModelByIndex(i), (int)PRVM_serveredictfloat(e, skin), tagname); } static int SV_GetExtendedTagInfo (prvm_prog_t *prog, prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) { int r; dp_model_t *model; *tagname = NULL; *parentindex = 0; Matrix4x4_CreateIdentity(tag_localmatrix); if (tagindex >= 0 && (model = SV_GetModelFromEdict(e)) && model->num_bones) { r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_serveredictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); if(!r) // success? *parentindex += 1; return r; } return 1; } void SV_GetEntityMatrix (prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) { float scale; float pitchsign = 1; scale = PRVM_serveredictfloat(ent, scale); if (!scale) scale = 1.0f; if (viewmatrix) Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2], PRVM_serveredictvector(ent, v_angle)[0], PRVM_serveredictvector(ent, v_angle)[1], PRVM_serveredictvector(ent, v_angle)[2], scale * cl_viewmodel_scale.value); else { pitchsign = SV_GetPitchSign(prog, ent); Matrix4x4_CreateFromQuakeEntity(out, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2], pitchsign * PRVM_serveredictvector(ent, angles)[0], PRVM_serveredictvector(ent, angles)[1], PRVM_serveredictvector(ent, angles)[2], scale); } } static int SV_GetEntityLocalTagMatrix(prvm_prog_t *prog, prvm_edict_t *ent, int tagindex, matrix4x4_t *out) { dp_model_t *model; if (tagindex >= 0 && (model = SV_GetModelFromEdict(ent)) && model->animscenes) { VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); } *out = identitymatrix; return 0; } // Warnings/errors code: // 0 - normal (everything all-right) // 1 - world entity // 2 - free entity // 3 - null or non-precached model // 4 - no tags with requested index // 5 - runaway loop at attachment chain extern cvar_t cl_bob; extern cvar_t cl_bobcycle; extern cvar_t cl_bobup; static int SV_GetTagMatrix (prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex) { int ret; int modelindex, attachloop; matrix4x4_t entitymatrix, tagmatrix, attachmatrix; dp_model_t *model; *out = identitymatrix; // warnings and errors return identical matrix if (ent == prog->edicts) return 1; if (ent->priv.server->free) return 2; modelindex = (int)PRVM_serveredictfloat(ent, modelindex); if (modelindex <= 0 || modelindex >= MAX_MODELS) return 3; model = SV_GetModelByIndex(modelindex); VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); tagmatrix = identitymatrix; // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity attachloop = 0; for (;;) { if (attachloop >= 256) // prevent runaway looping return 5; // apply transformation by child's tagindex on parent entity and then // by parent entity itself ret = SV_GetEntityLocalTagMatrix(prog, ent, tagindex - 1, &attachmatrix); if (ret && attachloop == 0) return ret; SV_GetEntityMatrix(prog, ent, &entitymatrix, false); Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); // next iteration we process the parent entity if (PRVM_serveredictedict(ent, tag_entity)) { tagindex = (int)PRVM_serveredictfloat(ent, tag_index); ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, tag_entity)); } else break; attachloop++; } // RENDER_VIEWMODEL magic if (PRVM_serveredictedict(ent, viewmodelforclient)) { Matrix4x4_Copy(&tagmatrix, out); ent = PRVM_EDICT_NUM(PRVM_serveredictedict(ent, viewmodelforclient)); SV_GetEntityMatrix(prog, ent, &entitymatrix, true); Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); /* // Cl_bob, ported from rendering code if (PRVM_serveredictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) { double bob, cycle; // LordHavoc: this code is *weird*, but not replacable (I think it // should be done in QC on the server, but oh well, quake is quake) // LordHavoc: figured out bobup: the time at which the sin is at 180 // degrees (which allows lengthening or squishing the peak or valley) cycle = sv.time/cl_bobcycle.value; cycle -= (int)cycle; if (cycle < cl_bobup.value) cycle = sin(M_PI * cycle / cl_bobup.value); else cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); // bob is proportional to velocity in the xy plane // (don't count Z, or jumping messes it up) bob = sqrt(PRVM_serveredictvector(ent, velocity)[0]*PRVM_serveredictvector(ent, velocity)[0] + PRVM_serveredictvector(ent, velocity)[1]*PRVM_serveredictvector(ent, velocity)[1])*cl_bob.value; bob = bob*0.3 + bob*0.7*cycle; Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); } */ } return 0; } //float(entity ent, string tagname) gettagindex; static void VM_SV_gettagindex(prvm_prog_t *prog) { prvm_edict_t *ent; const char *tag_name; int tag_index; VM_SAFEPARMCOUNT(2, VM_SV_gettagindex); ent = PRVM_G_EDICT(OFS_PARM0); tag_name = PRVM_G_STRING(OFS_PARM1); if (ent == prog->edicts) { VM_Warning(prog, "VM_SV_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); return; } if (ent->priv.server->free) { VM_Warning(prog, "VM_SV_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); return; } tag_index = 0; if (!SV_GetModelFromEdict(ent)) Con_DPrintf("VM_SV_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); else { tag_index = SV_GetTagIndex(prog, ent, tag_name); if (tag_index == 0) if(developer_extra.integer) Con_DPrintf("VM_SV_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); } PRVM_G_FLOAT(OFS_RETURN) = tag_index; } //vector(entity ent, float tagindex) gettaginfo; static void VM_SV_gettaginfo(prvm_prog_t *prog) { prvm_edict_t *e; int tagindex; matrix4x4_t tag_matrix; matrix4x4_t tag_localmatrix; int parentindex; const char *tagname; int returncode; vec3_t forward, left, up, origin; const dp_model_t *model; VM_SAFEPARMCOUNT(2, VM_SV_gettaginfo); e = PRVM_G_EDICT(OFS_PARM0); tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); returncode = SV_GetTagMatrix(prog, &tag_matrix, e, tagindex); Matrix4x4_ToVectors(&tag_matrix, forward, left, up, origin); VectorCopy(forward, PRVM_serverglobalvector(v_forward)); VectorNegate(left, PRVM_serverglobalvector(v_right)); VectorCopy(up, PRVM_serverglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); model = SV_GetModelFromEdict(e); VM_GenerateFrameGroupBlend(prog, e->priv.server->framegroupblend, e); VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, e, model, e->priv.server->frameblend); SV_GetExtendedTagInfo(prog, e, tagindex, &parentindex, &tagname, &tag_localmatrix); Matrix4x4_ToVectors(&tag_localmatrix, forward, left, up, origin); PRVM_serverglobalfloat(gettaginfo_parent) = parentindex; PRVM_serverglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(prog, tagname) : 0; VectorCopy(forward, PRVM_serverglobalvector(gettaginfo_forward)); VectorNegate(left, PRVM_serverglobalvector(gettaginfo_right)); VectorCopy(up, PRVM_serverglobalvector(gettaginfo_up)); VectorCopy(origin, PRVM_serverglobalvector(gettaginfo_offset)); switch(returncode) { case 1: VM_Warning(prog, "gettagindex: can't affect world entity\n"); break; case 2: VM_Warning(prog, "gettagindex: can't affect free entity\n"); break; case 3: Con_DPrintf("SV_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); break; case 4: Con_DPrintf("SV_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); break; case 5: Con_DPrintf("SV_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); break; } } //void(entity clent) dropclient (DP_SV_DROPCLIENT) static void VM_SV_dropclient(prvm_prog_t *prog) { int clientnum; client_t *oldhostclient; VM_SAFEPARMCOUNT(1, VM_SV_dropclient); clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; if (clientnum < 0 || clientnum >= svs.maxclients) { VM_Warning(prog, "dropclient: not a client\n"); return; } if (!svs.clients[clientnum].active) { VM_Warning(prog, "dropclient: that client slot is not connected\n"); return; } oldhostclient = host_client; host_client = svs.clients + clientnum; SV_DropClient(false); host_client = oldhostclient; } //entity() spawnclient (DP_SV_BOTCLIENT) static void VM_SV_spawnclient(prvm_prog_t *prog) { int i; prvm_edict_t *ed; VM_SAFEPARMCOUNT(0, VM_SV_spawnclient); prog->xfunction->builtinsprofile += 2; ed = prog->edicts; for (i = 0;i < svs.maxclients;i++) { if (!svs.clients[i].active) { prog->xfunction->builtinsprofile += 100; SV_ConnectClient (i, NULL); // this has to be set or else ClientDisconnect won't be called // we assume the qc will call ClientConnect... svs.clients[i].clientconnectcalled = true; ed = PRVM_EDICT_NUM(i + 1); break; } } VM_RETURN_EDICT(ed); } //float(entity clent) clienttype (DP_SV_BOTCLIENT) static void VM_SV_clienttype(prvm_prog_t *prog) { int clientnum; VM_SAFEPARMCOUNT(1, VM_SV_clienttype); clientnum = PRVM_G_EDICTNUM(OFS_PARM0) - 1; if (clientnum < 0 || clientnum >= svs.maxclients) PRVM_G_FLOAT(OFS_RETURN) = 3; else if (!svs.clients[clientnum].active) PRVM_G_FLOAT(OFS_RETURN) = 0; else if (svs.clients[clientnum].netconnection) PRVM_G_FLOAT(OFS_RETURN) = 1; else PRVM_G_FLOAT(OFS_RETURN) = 2; } /* =============== VM_SV_serverkey string(string key) serverkey =============== */ static void VM_SV_serverkey(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(1, VM_SV_serverkey); InfoString_GetValue(svs.serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } //#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) static void VM_SV_setmodelindex(prvm_prog_t *prog) { prvm_edict_t *e; dp_model_t *mod; int i; VM_SAFEPARMCOUNT(2, VM_SV_setmodelindex); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setmodelindex: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setmodelindex: can not modify free entity\n"); return; } i = (int)PRVM_G_FLOAT(OFS_PARM1); if (i <= 0 || i >= MAX_MODELS) { VM_Warning(prog, "setmodelindex: invalid modelindex\n"); return; } if (!sv.model_precache[i][0]) { VM_Warning(prog, "setmodelindex: model not precached\n"); return; } PRVM_serveredictstring(e, model) = PRVM_SetEngineString(prog, sv.model_precache[i]); PRVM_serveredictfloat(e, modelindex) = i; mod = SV_GetModelByIndex(i); if (mod) { if (mod->type != mod_alias || sv_gameplayfix_setmodelrealbox.integer) SetMinMaxSize(prog, e, mod->normalmins, mod->normalmaxs, true); else SetMinMaxSize(prog, e, quakemins, quakemaxs, true); } else SetMinMaxSize(prog, e, vec3_origin, vec3_origin, true); } //#334 string(float mdlindex) modelnameforindex (EXT_CSQC) static void VM_SV_modelnameforindex(prvm_prog_t *prog) { int i; VM_SAFEPARMCOUNT(1, VM_SV_modelnameforindex); PRVM_G_INT(OFS_RETURN) = OFS_NULL; i = (int)PRVM_G_FLOAT(OFS_PARM0); if (i <= 0 || i >= MAX_MODELS) { VM_Warning(prog, "modelnameforindex: invalid modelindex\n"); return; } if (!sv.model_precache[i][0]) { VM_Warning(prog, "modelnameforindex: model not precached\n"); return; } PRVM_G_INT(OFS_RETURN) = PRVM_SetEngineString(prog, sv.model_precache[i]); } //#335 float(string effectname) particleeffectnum (EXT_CSQC) static void VM_SV_particleeffectnum(prvm_prog_t *prog) { int i; VM_SAFEPARMCOUNT(1, VM_SV_particleeffectnum); i = SV_ParticleEffectIndex(PRVM_G_STRING(OFS_PARM0)); if (i == 0) i = -1; PRVM_G_FLOAT(OFS_RETURN) = i; } // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) static void VM_SV_trailparticles(prvm_prog_t *prog) { vec3_t start, end; VM_SAFEPARMCOUNT(4, VM_SV_trailparticles); if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) return; MSG_WriteByte(&sv.datagram, svc_trailparticles); MSG_WriteShort(&sv.datagram, PRVM_G_EDICTNUM(OFS_PARM0)); MSG_WriteShort(&sv.datagram, (int)PRVM_G_FLOAT(OFS_PARM1)); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), end); MSG_WriteVector(&sv.datagram, start, sv.protocol); MSG_WriteVector(&sv.datagram, end, sv.protocol); SV_FlushBroadcastMessages(); } //#337 void(float effectnum, vector origin, vector dir, float count) pointparticles (EXT_CSQC) static void VM_SV_pointparticles(prvm_prog_t *prog) { int effectnum, count; vec3_t org, vel; VM_SAFEPARMCOUNTRANGE(4, 8, VM_SV_pointparticles); if ((int)PRVM_G_FLOAT(OFS_PARM0) < 0) return; effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), org); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); count = bound(0, (int)PRVM_G_FLOAT(OFS_PARM3), 65535); if (count == 1 && !VectorLength2(vel)) { // 1+2+12=15 bytes MSG_WriteByte(&sv.datagram, svc_pointparticles1); MSG_WriteShort(&sv.datagram, effectnum); MSG_WriteVector(&sv.datagram, org, sv.protocol); } else { // 1+2+12+12+2=29 bytes MSG_WriteByte(&sv.datagram, svc_pointparticles); MSG_WriteShort(&sv.datagram, effectnum); MSG_WriteVector(&sv.datagram, org, sv.protocol); MSG_WriteVector(&sv.datagram, vel, sv.protocol); MSG_WriteShort(&sv.datagram, count); } SV_FlushBroadcastMessages(); } //PF_setpause, // void(float pause) setpause = #531; static void VM_SV_setpause(prvm_prog_t *prog) { int pauseValue; pauseValue = (int)PRVM_G_FLOAT(OFS_PARM0); if (pauseValue != 0) { //pause the game sv.paused = 1; sv.pausedstart = realtime; } else { //disable pause, in case it was enabled if (sv.paused != 0) { sv.paused = 0; sv.pausedstart = 0; } } // send notification to all clients MSG_WriteByte(&sv.reliable_datagram, svc_setpause); MSG_WriteByte(&sv.reliable_datagram, sv.paused); } // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. static void VM_SV_skel_create(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = SV_GetModelByIndex(modelindex); skeleton_t *skeleton; int i; PRVM_G_FLOAT(OFS_RETURN) = 0; if (!model || !model->num_bones) return; for (i = 0;i < MAX_EDICTS;i++) if (!prog->skeletons[i]) break; if (i == MAX_EDICTS) return; prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(prog->progs_mempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); PRVM_G_FLOAT(OFS_RETURN) = i + 1; skeleton->model = model; skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); // initialize to identity matrices for (i = 0;i < skeleton->model->num_bones;i++) skeleton->relativetransforms[i] = identitymatrix; } // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure static void VM_SV_skel_build(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); float retainfrac = PRVM_G_FLOAT(OFS_PARM3); int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; dp_model_t *model = SV_GetModelByIndex(modelindex); int numblends; int bonenum; int blendindex; framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; frameblend_t frameblend[MAX_FRAMEBLENDS]; matrix4x4_t bonematrix; matrix4x4_t matrix; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; firstbone = max(0, firstbone); lastbone = min(lastbone, model->num_bones - 1); lastbone = min(lastbone, skeleton->model->num_bones - 1); VM_GenerateFrameGroupBlend(prog, framegroupblend, ed); VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model, sv.time); for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) ; for (bonenum = firstbone;bonenum <= lastbone;bonenum++) { memset(&bonematrix, 0, sizeof(bonematrix)); for (blendindex = 0;blendindex < numblends;blendindex++) { Matrix4x4_FromBonePose7s(&matrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); Matrix4x4_Accumulate(&bonematrix, &matrix, frameblend[blendindex].lerp); } Matrix4x4_Normalize3(&bonematrix, &bonematrix); Matrix4x4_Interpolate(&skeleton->relativetransforms[bonenum], &bonematrix, &skeleton->relativetransforms[bonenum], retainfrac); } PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; } // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton static void VM_SV_skel_get_numbones(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; } // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) static void VM_SV_skel_get_bonename(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; PRVM_G_INT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, skeleton->model->data_bones[bonenum].name); } // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) static void VM_SV_skel_get_boneparent(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; } // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex static void VM_SV_skel_find_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; const char *tagname = PRVM_G_STRING(OFS_PARM1); skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname) + 1; } // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) static void VM_SV_skel_get_bonerel(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; matrix4x4_t matrix; vec3_t forward, left, up, origin; VectorClear(PRVM_G_VECTOR(OFS_RETURN)); VectorClear(PRVM_clientglobalvector(v_forward)); VectorClear(PRVM_clientglobalvector(v_right)); VectorClear(PRVM_clientglobalvector(v_up)); if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; matrix = skeleton->relativetransforms[bonenum]; Matrix4x4_ToVectors(&matrix, forward, left, up, origin); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorNegate(left, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); } // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) static void VM_SV_skel_get_boneabs(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; vec3_t forward, left, up, origin; VectorClear(PRVM_G_VECTOR(OFS_RETURN)); VectorClear(PRVM_clientglobalvector(v_forward)); VectorClear(PRVM_clientglobalvector(v_right)); VectorClear(PRVM_clientglobalvector(v_up)); if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; matrix = skeleton->relativetransforms[bonenum]; // convert to absolute while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) { temp = matrix; Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); } Matrix4x4_ToVectors(&matrix, forward, left, up, origin); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorNegate(left, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); } // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) static void VM_SV_skel_set_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); skeleton->relativetransforms[bonenum] = matrix; } // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) static void VM_SV_skel_mul_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); temp = skeleton->relativetransforms[bonenum]; Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); } // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) static void VM_SV_skel_mul_bones(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; int bonenum; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); firstbone = max(0, firstbone); lastbone = min(lastbone, skeleton->model->num_bones - 1); for (bonenum = firstbone;bonenum <= lastbone;bonenum++) { temp = skeleton->relativetransforms[bonenum]; Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); } } // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse static void VM_SV_skel_copybones(prvm_prog_t *prog) { int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; int bonenum; skeleton_t *skeletondst; skeleton_t *skeletonsrc; if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) return; if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) return; firstbone = max(0, firstbone); lastbone = min(lastbone, skeletondst->model->num_bones - 1); lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); for (bonenum = firstbone;bonenum <= lastbone;bonenum++) skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; } // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) static void VM_SV_skel_delete(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; Mem_Free(skeleton); prog->skeletons[skeletonindex] = NULL; } // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found static void VM_SV_frameforname(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = SV_GetModelByIndex(modelindex); const char *name = PRVM_G_STRING(OFS_PARM1); int i; PRVM_G_FLOAT(OFS_RETURN) = -1; if (!model || !model->animscenes) return; for (i = 0;i < model->numframes;i++) { if (!strcasecmp(model->animscenes[i].name, name)) { PRVM_G_FLOAT(OFS_RETURN) = i; break; } } } // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. static void VM_SV_frameduration(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = SV_GetModelByIndex(modelindex); int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = 0; if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) return; if (model->animscenes[framenum].framerate) PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; } prvm_builtin_t vm_sv_builtins[] = { NULL, // #0 NULL function (not callable) (QUAKE) VM_makevectors, // #1 void(vector ang) makevectors (QUAKE) VM_SV_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) VM_SV_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) VM_SV_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) VM_break, // #6 void() break (QUAKE) VM_random, // #7 float() random (QUAKE) VM_SV_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) VM_normalize, // #9 vector(vector v) normalize (QUAKE) VM_error, // #10 void(string e) error (QUAKE) VM_objerror, // #11 void(string e) objerror (QUAKE) VM_vlen, // #12 float(vector v) vlen (QUAKE) VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) VM_spawn, // #14 entity() spawn (QUAKE) VM_remove, // #15 void(entity e) remove (QUAKE) VM_SV_traceline, // #16 void(vector v1, vector v2, float tryents) traceline (QUAKE) VM_SV_checkclient, // #17 entity() checkclient (QUAKE) VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) VM_SV_precache_sound, // #19 void(string s) precache_sound (QUAKE) VM_SV_precache_model, // #20 void(string s) precache_model (QUAKE) VM_SV_stuffcmd, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) VM_SV_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) VM_bprint, // #23 void(string s, ...) bprint (QUAKE) VM_SV_sprint, // #24 void(entity client, string s, ...) sprint (QUAKE) VM_dprint, // #25 void(string s, ...) dprint (QUAKE) VM_ftos, // #26 string(float f) ftos (QUAKE) VM_vtos, // #27 string(vector v) vtos (QUAKE) VM_coredump, // #28 void() coredump (QUAKE) VM_traceon, // #29 void() traceon (QUAKE) VM_traceoff, // #30 void() traceoff (QUAKE) VM_eprint, // #31 void(entity e) eprint (QUAKE) VM_SV_walkmove, // #32 float(float yaw, float dist) walkmove (QUAKE) NULL, // #33 (QUAKE) VM_SV_droptofloor, // #34 float() droptofloor (QUAKE) VM_SV_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) VM_rint, // #36 float(float v) rint (QUAKE) VM_floor, // #37 float(float v) floor (QUAKE) VM_ceil, // #38 float(float v) ceil (QUAKE) NULL, // #39 (QUAKE) VM_SV_checkbottom, // #40 float(entity e) checkbottom (QUAKE) VM_SV_pointcontents, // #41 float(vector v) pointcontents (QUAKE) NULL, // #42 (QUAKE) VM_fabs, // #43 float(float f) fabs (QUAKE) VM_SV_aim, // #44 vector(entity e, float speed) aim (QUAKE) VM_cvar, // #45 float(string s) cvar (QUAKE) VM_localcmd, // #46 void(string s) localcmd (QUAKE) VM_nextent, // #47 entity(entity e) nextent (QUAKE) VM_SV_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) VM_changeyaw, // #49 void() ChangeYaw (QUAKE) NULL, // #50 (QUAKE) VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) VM_SV_WriteByte, // #52 void(float to, float f) WriteByte (QUAKE) VM_SV_WriteChar, // #53 void(float to, float f) WriteChar (QUAKE) VM_SV_WriteShort, // #54 void(float to, float f) WriteShort (QUAKE) VM_SV_WriteLong, // #55 void(float to, float f) WriteLong (QUAKE) VM_SV_WriteCoord, // #56 void(float to, float f) WriteCoord (QUAKE) VM_SV_WriteAngle, // #57 void(float to, float f) WriteAngle (QUAKE) VM_SV_WriteString, // #58 void(float to, string s) WriteString (QUAKE) VM_SV_WriteEntity, // #59 void(float to, entity e) WriteEntity (QUAKE) VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) (QUAKE) VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) (QUAKE) VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) (QUAKE) VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) (QUAKE) VM_SV_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) (QUAKE) VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) (QUAKE) NULL, // #66 (QUAKE) VM_SV_MoveToGoal, // #67 void(float step) movetogoal (QUAKE) VM_precache_file, // #68 string(string s) precache_file (QUAKE) VM_SV_makestatic, // #69 void(entity e) makestatic (QUAKE) VM_changelevel, // #70 void(string s) changelevel (QUAKE) NULL, // #71 (QUAKE) VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) VM_SV_centerprint, // #73 void(entity client, strings) centerprint (QUAKE) VM_SV_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) VM_SV_precache_model, // #75 string(string s) precache_model2 (QUAKE) VM_SV_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) VM_SV_setspawnparms, // #78 void(entity e) setspawnparms (QUAKE) NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) VM_stof, // #81 float(string s) stof (FRIK_FILE) NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) NULL, // #83 (QUAKE) NULL, // #84 (QUAKE) NULL, // #85 (QUAKE) NULL, // #86 (QUAKE) NULL, // #87 (QUAKE) NULL, // #88 (QUAKE) NULL, // #89 (QUAKE) VM_SV_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) VM_SV_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) // FrikaC and Telejano range #100-#199 NULL, // #100 NULL, // #101 NULL, // #102 NULL, // #103 NULL, // #104 NULL, // #105 NULL, // #106 NULL, // #107 NULL, // #108 NULL, // #109 VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) VM_strlen, // #114 float(string s) strlen (FRIK_FILE) VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) VM_stov, // #117 vector(string) stov (FRIK_FILE) VM_strzone, // #118 string(string s) strzone (FRIK_FILE) VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) NULL, // #120 NULL, // #121 NULL, // #122 NULL, // #123 NULL, // #124 NULL, // #125 NULL, // #126 NULL, // #127 NULL, // #128 NULL, // #129 NULL, // #130 NULL, // #131 NULL, // #132 NULL, // #133 NULL, // #134 NULL, // #135 NULL, // #136 NULL, // #137 NULL, // #138 NULL, // #139 NULL, // #140 NULL, // #141 NULL, // #142 NULL, // #143 NULL, // #144 NULL, // #145 NULL, // #146 NULL, // #147 NULL, // #148 NULL, // #149 NULL, // #150 NULL, // #151 NULL, // #152 NULL, // #153 NULL, // #154 NULL, // #155 NULL, // #156 NULL, // #157 NULL, // #158 NULL, // #159 NULL, // #160 NULL, // #161 NULL, // #162 NULL, // #163 NULL, // #164 NULL, // #165 NULL, // #166 NULL, // #167 NULL, // #168 NULL, // #169 NULL, // #170 NULL, // #171 NULL, // #172 NULL, // #173 NULL, // #174 NULL, // #175 NULL, // #176 NULL, // #177 NULL, // #178 NULL, // #179 NULL, // #180 NULL, // #181 NULL, // #182 NULL, // #183 NULL, // #184 NULL, // #185 NULL, // #186 NULL, // #187 NULL, // #188 NULL, // #189 NULL, // #190 NULL, // #191 NULL, // #192 NULL, // #193 NULL, // #194 NULL, // #195 NULL, // #196 NULL, // #197 NULL, // #198 NULL, // #199 // FTEQW range #200-#299 NULL, // #200 NULL, // #201 NULL, // #202 NULL, // #203 NULL, // #204 NULL, // #205 NULL, // #206 NULL, // #207 NULL, // #208 NULL, // #209 NULL, // #210 NULL, // #211 NULL, // #212 NULL, // #213 NULL, // #214 NULL, // #215 NULL, // #216 NULL, // #217 VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) NULL, // #219 NULL, // #220 VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) NULL, // #231 VM_SV_AddStat, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) NULL, // #233 NULL, // #234 NULL, // #235 NULL, // #236 NULL, // #237 NULL, // #238 NULL, // #239 VM_SV_checkpvs, // #240 float(vector viewpos, entity viewee) checkpvs; NULL, // #241 NULL, // #242 NULL, // #243 NULL, // #244 NULL, // #245 NULL, // #246 NULL, // #247 NULL, // #248 NULL, // #249 NULL, // #250 NULL, // #251 NULL, // #252 NULL, // #253 NULL, // #254 NULL, // #255 NULL, // #256 NULL, // #257 NULL, // #258 NULL, // #259 NULL, // #260 NULL, // #261 NULL, // #262 VM_SV_skel_create, // #263 float(float modlindex) skel_create = #263; // (DP_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. VM_SV_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (DP_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure VM_SV_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (DP_SKELETONOBJECTS) returns how many bones exist in the created skeleton VM_SV_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (DP_SKELETONOBJECTS) returns name of bone (as a tempstring) VM_SV_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (DP_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) VM_SV_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (DP_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex VM_SV_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) VM_SV_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) VM_SV_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (DP_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) VM_SV_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (DP_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) VM_SV_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (DP_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) VM_SV_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (DP_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse VM_SV_skel_delete, // #275 void(float skel) skel_delete = #275; // (DP_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) VM_SV_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (DP_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found VM_SV_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (DP_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. NULL, // #278 NULL, // #279 NULL, // #280 NULL, // #281 NULL, // #282 NULL, // #283 NULL, // #284 NULL, // #285 NULL, // #286 NULL, // #287 NULL, // #288 NULL, // #289 NULL, // #290 NULL, // #291 NULL, // #292 NULL, // #293 NULL, // #294 NULL, // #295 NULL, // #296 NULL, // #297 NULL, // #298 NULL, // #299 // CSQC range #300-#399 NULL, // #300 void() clearscene (EXT_CSQC) NULL, // #301 void(float mask) addentities (EXT_CSQC) NULL, // #302 void(entity ent) addentity (EXT_CSQC) NULL, // #303 float(float property, ...) setproperty (EXT_CSQC) NULL, // #304 void() renderscene (EXT_CSQC) NULL, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) NULL, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon NULL, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex NULL, // #308 void() R_EndPolygon NULL, // #309 NULL, // #310 vector (vector v) cs_unproject (EXT_CSQC) NULL, // #311 vector (vector v) cs_project (EXT_CSQC) NULL, // #312 NULL, // #313 NULL, // #314 NULL, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) NULL, // #316 float(string name) iscachedpic (EXT_CSQC) NULL, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) NULL, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) NULL, // #319 void(string name) freepic (EXT_CSQC) NULL, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) NULL, // #321 float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring (EXT_CSQC) NULL, // #322 float(vector position, string pic, vector size, vector rgb, float alpha, float flag) drawpic (EXT_CSQC) NULL, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) NULL, // #324 void(float x, float y, float width, float height) drawsetcliparea NULL, // #325 void(void) drawresetcliparea NULL, // #326 NULL, // #327 NULL, // #328 NULL, // #329 NULL, // #330 float(float stnum) getstatf (EXT_CSQC) NULL, // #331 float(float stnum) getstati (EXT_CSQC) NULL, // #332 string(float firststnum) getstats (EXT_CSQC) VM_SV_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) VM_SV_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) VM_SV_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) VM_SV_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) VM_SV_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) NULL, // #338 void(string s, ...) centerprint (EXT_CSQC) VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) NULL, // #340 string(float keynum) keynumtostring (EXT_CSQC) NULL, // #341 float(string keyname) stringtokeynum (EXT_CSQC) NULL, // #342 string(float keynum) getkeybind (EXT_CSQC) NULL, // #343 void(float usecursor) setcursormode (EXT_CSQC) NULL, // #344 vector() getmousepos (EXT_CSQC) NULL, // #345 float(float framenum) getinputstate (EXT_CSQC) NULL, // #346 void(float sens) setsensitivityscaler (EXT_CSQC) NULL, // #347 void() runstandardplayerphysics (EXT_CSQC) NULL, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) NULL, // #349 float() isdemo (EXT_CSQC) VM_isserver, // #350 float() isserver (EXT_CSQC) NULL, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) NULL, // #352 void(string cmdname) registercommand (EXT_CSQC) VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) VM_SV_serverkey, // #354 string(string key) serverkey (EXT_CSQC) NULL, // #355 NULL, // #356 NULL, // #357 NULL, // #358 NULL, // #359 NULL, // #360 float() readbyte (EXT_CSQC) NULL, // #361 float() readchar (EXT_CSQC) NULL, // #362 float() readshort (EXT_CSQC) NULL, // #363 float() readlong (EXT_CSQC) NULL, // #364 float() readcoord (EXT_CSQC) NULL, // #365 float() readangle (EXT_CSQC) NULL, // #366 string() readstring (EXT_CSQC) NULL, // #367 float() readfloat (EXT_CSQC) NULL, // #368 NULL, // #369 NULL, // #370 NULL, // #371 NULL, // #372 NULL, // #373 NULL, // #374 NULL, // #375 NULL, // #376 NULL, // #377 NULL, // #378 NULL, // #379 NULL, // #380 NULL, // #381 NULL, // #382 NULL, // #383 NULL, // #384 NULL, // #385 NULL, // #386 NULL, // #387 NULL, // #388 NULL, // #389 NULL, // #390 NULL, // #391 NULL, // #392 NULL, // #393 NULL, // #394 NULL, // #395 NULL, // #396 NULL, // #397 NULL, // #398 NULL, // #399 // LordHavoc's range #400-#499 VM_SV_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) VM_SV_setcolor, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) VM_SV_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) VM_SV_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) VM_SV_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) VM_SV_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) VM_SV_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) VM_SV_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) VM_SV_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) VM_SV_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) VM_SV_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) VM_SV_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) VM_SV_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) VM_SV_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) VM_SV_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) VM_SV_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) VM_SV_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) VM_SV_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) VM_SV_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) VM_SV_clientcommand, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) VM_SV_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) VM_SV_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) VM_SV_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) VM_SV_dropclient, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) VM_SV_spawnclient, // #454 entity() spawnclient (DP_SV_BOTCLIENT) VM_SV_clienttype, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) VM_SV_WriteUnterminatedString, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) VM_SV_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet = #457 (DP_TE_FLAMEJET) NULL, // #458 VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) NULL, // #470 VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_SV_STRINGCOLORFUNCTIONS) VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) VM_SV_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) (DP_SV_POINTSOUND) VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; NULL, // #487 NULL, // #488 NULL, // #489 NULL, // #490 NULL, // #491 NULL, // #492 NULL, // #493 VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) VM_numentityfields, // #496 float() numentityfields = #496; (DP_QC_ENTITYDATA) VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) VM_SV_WritePicture, // #501 NULL, // #502 VM_whichpack, // #503 string(string) whichpack = #503; NULL, // #504 NULL, // #505 NULL, // #506 NULL, // #507 NULL, // #508 NULL, // #509 VM_uri_escape, // #510 string(string in) uri_escape = #510; VM_uri_unescape, // #511 string(string in) uri_unescape = #511; VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) NULL, // #520 NULL, // #521 NULL, // #522 NULL, // #523 NULL, // #524 NULL, // #525 NULL, // #526 NULL, // #527 NULL, // #528 VM_loadfromdata, // #529 VM_loadfromfile, // #530 VM_SV_setpause, // #531 void(float pause) setpause = #531; VM_log, // #532 VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) NULL, // #539 VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) NULL, // #543 NULL, // #544 NULL, // #545 NULL, // #546 NULL, // #547 NULL, // #548 NULL, // #549 NULL, // #550 NULL, // #551 NULL, // #552 NULL, // #553 NULL, // #554 NULL, // #555 NULL, // #556 NULL, // #557 NULL, // #558 NULL, // #559 NULL, // #560 NULL, // #561 NULL, // #562 NULL, // #563 NULL, // #564 NULL, // #565 NULL, // #566 NULL, // #567 NULL, // #568 NULL, // #569 NULL, // #570 NULL, // #571 NULL, // #572 NULL, // #573 NULL, // #574 NULL, // #575 NULL, // #576 NULL, // #577 NULL, // #578 NULL, // #579 NULL, // #580 NULL, // #581 NULL, // #582 NULL, // #583 NULL, // #584 NULL, // #585 NULL, // #586 NULL, // #587 NULL, // #588 NULL, // #589 NULL, // #590 NULL, // #591 NULL, // #592 NULL, // #593 NULL, // #594 NULL, // #595 NULL, // #596 NULL, // #597 NULL, // #598 NULL, // #599 NULL, // #600 NULL, // #601 NULL, // #602 NULL, // #603 NULL, // #604 VM_callfunction, // #605 VM_writetofile, // #606 VM_isfunction, // #607 NULL, // #608 NULL, // #609 NULL, // #610 NULL, // #611 NULL, // #612 VM_parseentitydata, // #613 NULL, // #614 NULL, // #615 NULL, // #616 NULL, // #617 NULL, // #618 NULL, // #619 NULL, // #620 NULL, // #621 NULL, // #622 NULL, // #623 VM_SV_getextresponse, // #624 string getextresponse(void) NULL, // #625 NULL, // #626 VM_sprintf, // #627 string sprintf(string format, ...) VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) NULL, // #630 NULL, // #631 NULL, // #632 NULL, // #633 NULL, // #634 NULL, // #635 NULL, // #636 NULL, // #637 NULL, // #638 VM_digest_hex, // #639 NULL, // #640 NULL, // #641 VM_coverage, // #642 NULL, // #643 }; const int vm_sv_numbuiltins = sizeof(vm_sv_builtins) / sizeof(prvm_builtin_t); void SVVM_init_cmd(prvm_prog_t *prog) { VM_Cmd_Init(prog); } void SVVM_reset_cmd(prvm_prog_t *prog) { World_End(&sv.world); if(prog->loaded && PRVM_serverfunction(SV_Shutdown)) { func_t s = PRVM_serverfunction(SV_Shutdown); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); } VM_Cmd_Reset(prog); } darkplaces/jpeg.h0000664000175000017500000000241413067716220013262 0ustar kalevkalev/* Copyright (C) 2002 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifndef JPEG_H #define JPEG_H qboolean JPEG_OpenLibrary (void); void JPEG_CloseLibrary (void); unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel); qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data); /*! \returns 0 if failed, or the size actually used. */ size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data); qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size); #endif darkplaces/prvm_exec.c0000664000175000017500000007170713067716222014335 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "progsvm.h" const char *prvm_opnames[] = { "^5DONE", "MUL_F", "MUL_V", "MUL_FV", "MUL_VF", "DIV", "ADD_F", "ADD_V", "SUB_F", "SUB_V", "^2EQ_F", "^2EQ_V", "^2EQ_S", "^2EQ_E", "^2EQ_FNC", "^2NE_F", "^2NE_V", "^2NE_S", "^2NE_E", "^2NE_FNC", "^2LE", "^2GE", "^2LT", "^2GT", "^6FIELD_F", "^6FIELD_V", "^6FIELD_S", "^6FIELD_ENT", "^6FIELD_FLD", "^6FIELD_FNC", "^1ADDRESS", "STORE_F", "STORE_V", "STORE_S", "STORE_ENT", "STORE_FLD", "STORE_FNC", "^1STOREP_F", "^1STOREP_V", "^1STOREP_S", "^1STOREP_ENT", "^1STOREP_FLD", "^1STOREP_FNC", "^5RETURN", "^2NOT_F", "^2NOT_V", "^2NOT_S", "^2NOT_ENT", "^2NOT_FNC", "^5IF", "^5IFNOT", "^3CALL0", "^3CALL1", "^3CALL2", "^3CALL3", "^3CALL4", "^3CALL5", "^3CALL6", "^3CALL7", "^3CALL8", "^1STATE", "^5GOTO", "^2AND", "^2OR", "BITAND", "BITOR" }; //============================================================================= /* ================= PRVM_PrintStatement ================= */ extern cvar_t prvm_coverage; extern cvar_t prvm_statementprofiling; extern cvar_t prvm_timeprofiling; static void PRVM_PrintStatement(prvm_prog_t *prog, mstatement_t *s) { size_t i; int opnum = (int)(s - prog->statements); char valuebuf[MAX_INPUTLINE]; Con_Printf("s%i: ", opnum); if( prog->statement_linenums ) { if ( prog->statement_columnnums ) Con_Printf( "%s:%i:%i: ", PRVM_GetString( prog, prog->xfunction->s_file ), prog->statement_linenums[ opnum ], prog->statement_columnnums[ opnum ] ); else Con_Printf( "%s:%i: ", PRVM_GetString( prog, prog->xfunction->s_file ), prog->statement_linenums[ opnum ] ); } if (prvm_statementprofiling.integer) Con_Printf("%7.0f ", prog->statement_profile[s - prog->statements]); if ( (unsigned)s->op < sizeof(prvm_opnames)/sizeof(prvm_opnames[0])) { Con_Printf("%s ", prvm_opnames[s->op]); i = strlen(prvm_opnames[s->op]); // don't count a preceding color tag when padding the name if (prvm_opnames[s->op][0] == STRING_COLOR_TAG) i -= 2; for ( ; i<10 ; i++) Con_Print(" "); } if (s->operand[0] >= 0) Con_Printf( "%s", PRVM_GlobalString(prog, s->operand[0], valuebuf, sizeof(valuebuf))); if (s->operand[1] >= 0) Con_Printf(", %s", PRVM_GlobalString(prog, s->operand[1], valuebuf, sizeof(valuebuf))); if (s->operand[2] >= 0) Con_Printf(", %s", PRVM_GlobalString(prog, s->operand[2], valuebuf, sizeof(valuebuf))); if (s->jumpabsolute >= 0) Con_Printf(", statement %i", s->jumpabsolute); Con_Print("\n"); } void PRVM_PrintFunctionStatements (prvm_prog_t *prog, const char *name) { int i, firststatement, endstatement; mfunction_t *func; func = PRVM_ED_FindFunction (prog, name); if (!func) { Con_Printf("%s progs: no function named %s\n", prog->name, name); return; } firststatement = func->first_statement; if (firststatement < 0) { Con_Printf("%s progs: function %s is builtin #%i\n", prog->name, name, -firststatement); return; } // find the end statement endstatement = prog->numstatements; for (i = 0;i < prog->numfunctions;i++) if (endstatement > prog->functions[i].first_statement && firststatement < prog->functions[i].first_statement) endstatement = prog->functions[i].first_statement; // now print the range of statements Con_Printf("%s progs: disassembly of function %s (statements %i-%i, locals %i-%i):\n", prog->name, name, firststatement, endstatement, func->parm_start, func->parm_start + func->locals - 1); prog->xfunction = func; for (i = firststatement;i < endstatement;i++) { PRVM_PrintStatement(prog, prog->statements + i); if (!(prvm_coverage.integer & 4)) prog->statement_profile[i] = 0; } if (prvm_coverage.integer & 4) Con_Printf("Collecting statement coverage, not flushing statement profile.\n"); } /* ============ PRVM_PrintFunction_f ============ */ void PRVM_PrintFunction_f (void) { prvm_prog_t *prog; if (Cmd_Argc() != 3) { Con_Printf("usage: prvm_printfunction \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; PRVM_PrintFunctionStatements(prog, Cmd_Argv(2)); } /* ============ PRVM_StackTrace ============ */ void PRVM_StackTrace (prvm_prog_t *prog) { mfunction_t *f; int i; prog->stack[prog->depth].s = prog->xstatement; prog->stack[prog->depth].f = prog->xfunction; for (i = prog->depth;i > 0;i--) { f = prog->stack[i].f; if (!f) Con_Print("\n"); else { if (prog->statement_linenums) { if (prog->statement_columnnums) Con_Printf("%12s:%i:%i : %s : statement %i\n", PRVM_GetString(prog, f->s_file), prog->statement_linenums[prog->stack[i].s], prog->statement_columnnums[prog->stack[i].s], PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement); else Con_Printf("%12s:%i : %s : statement %i\n", PRVM_GetString(prog, f->s_file), prog->statement_linenums[prog->stack[i].s], PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement); } else Con_Printf("%12s : %s : statement %i\n", PRVM_GetString(prog, f->s_file), PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement); } } } void PRVM_ShortStackTrace(prvm_prog_t *prog, char *buf, size_t bufsize) { mfunction_t *f; int i; char vabuf[1024]; if(prog) { dpsnprintf(buf, bufsize, "(%s) ", prog->name); } else { strlcpy(buf, "", bufsize); return; } prog->stack[prog->depth].s = prog->xstatement; prog->stack[prog->depth].f = prog->xfunction; for (i = prog->depth;i > 0;i--) { f = prog->stack[i].f; if(strlcat(buf, f ? va(vabuf, sizeof(vabuf), "%s:%s(%i) ", PRVM_GetString(prog, f->s_file), PRVM_GetString(prog, f->s_name), prog->stack[i].s - f->first_statement) : " ", bufsize ) >= bufsize) break; } } static void PRVM_CallProfile (prvm_prog_t *prog) { mfunction_t *f, *best; int i; double max; double sum; double newprofiletime; Con_Printf( "%s Call Profile:\n", prog->name ); sum = 0; do { max = 0; best = NULL; for (i=0 ; inumfunctions ; i++) { f = &prog->functions[i]; if (max < f->totaltime) { max = f->totaltime; best = f; } } if (best) { sum += best->totaltime; Con_Printf("%9.4f %s\n", best->totaltime, PRVM_GetString(prog, best->s_name)); best->totaltime = 0; } } while (best); newprofiletime = Sys_DirtyTime(); Con_Printf("Total time since last profile reset: %9.4f\n", newprofiletime - prog->profiletime); Con_Printf(" - used by QC code of this VM: %9.4f\n", sum); prog->profiletime = newprofiletime; } void PRVM_Profile (prvm_prog_t *prog, int maxfunctions, double mintime, int sortby) { mfunction_t *f, *best; int i, num; double max; if(!prvm_timeprofiling.integer) mintime *= 10000000; // count each statement as about 0.1µs if(prvm_timeprofiling.integer) Con_Printf( "%s Profile:\n[CallCount] [Time] [BuiltinTm] [Statement] [BuiltinCt] [TimeTotal] [StmtTotal] [BltnTotal] [self]\n", prog->name ); // 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% else Con_Printf( "%s Profile:\n[CallCount] [Statement] [BuiltinCt] [StmtTotal] [BltnTotal] [self]\n", prog->name ); // 12345678901 12345678901 12345678901 12345678901 12345678901 123.45% num = 0; do { max = 0; best = NULL; for (i=0 ; inumfunctions ; i++) { f = &prog->functions[i]; if(prvm_timeprofiling.integer) { if(sortby) { if(f->first_statement < 0) { if (max < f->tprofile) { max = f->tprofile; best = f; } } else { if (max < f->tprofile_total) { max = f->tprofile_total; best = f; } } } else { if (max < f->tprofile + f->tbprofile) { max = f->tprofile + f->tbprofile; best = f; } } } else { if(sortby) { if (max < f->profile_total + f->builtinsprofile_total + f->callcount) { max = f->profile_total + f->builtinsprofile_total + f->callcount; best = f; } } else { if (max < f->profile + f->builtinsprofile + f->callcount) { max = f->profile + f->builtinsprofile + f->callcount; best = f; } } } } if (best) { if (num < maxfunctions && max > mintime) { if(prvm_timeprofiling.integer) { if (best->first_statement < 0) Con_Printf("%11.0f %11.6f ------------- builtin ------------- %11.6f ----------- builtin ----------- %s\n", best->callcount, best->tprofile, best->tprofile, PRVM_GetString(prog, best->s_name)); // %11.6f 12345678901 12345678901 12345678901 %11.6f 12345678901 12345678901 123.45% else Con_Printf("%11.0f %11.6f %11.6f %11.0f %11.0f %11.6f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->tprofile, best->tbprofile, best->profile, best->builtinsprofile, best->tprofile_total, best->profile_total, best->builtinsprofile_total, (best->tprofile_total > 0) ? ((best->tprofile) * 100.0 / (best->tprofile_total)) : -99.99, PRVM_GetString(prog, best->s_name)); } else { if (best->first_statement < 0) Con_Printf("%11.0f ----------------------- builtin ----------------------- %s\n", best->callcount, PRVM_GetString(prog, best->s_name)); // 12345678901 12345678901 12345678901 12345678901 123.45% else Con_Printf("%11.0f %11.0f %11.0f %11.0f %11.0f %6.2f%% %s\n", best->callcount, best->profile, best->builtinsprofile, best->profile_total, best->builtinsprofile_total, (best->profile + best->builtinsprofile) * 100.0 / (best->profile_total + best->builtinsprofile_total), PRVM_GetString(prog, best->s_name)); } } num++; best->profile = 0; best->tprofile = 0; best->tbprofile = 0; best->builtinsprofile = 0; best->profile_total = 0; best->tprofile_total = 0; best->builtinsprofile_total = 0; best->callcount = 0; } } while (best); } /* ============ PRVM_CallProfile_f ============ */ void PRVM_CallProfile_f (void) { prvm_prog_t *prog; if (Cmd_Argc() != 2) { Con_Print("prvm_callprofile \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; PRVM_CallProfile(prog); } /* ============ PRVM_Profile_f ============ */ void PRVM_Profile_f (void) { prvm_prog_t *prog; int howmany; if (prvm_coverage.integer & 1) { Con_Printf("Collecting function coverage, cannot profile - sorry!\n"); return; } howmany = 1<<30; if (Cmd_Argc() == 3) howmany = atoi(Cmd_Argv(2)); else if (Cmd_Argc() != 2) { Con_Print("prvm_profile \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; PRVM_Profile(prog, howmany, 0, 0); } void PRVM_ChildProfile_f (void) { prvm_prog_t *prog; int howmany; if (prvm_coverage.integer & 1) { Con_Printf("Collecting function coverage, cannot profile - sorry!\n"); return; } howmany = 1<<30; if (Cmd_Argc() == 3) howmany = atoi(Cmd_Argv(2)); else if (Cmd_Argc() != 2) { Con_Print("prvm_childprofile \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; PRVM_Profile(prog, howmany, 0, 1); } void PRVM_PrintState(prvm_prog_t *prog, int stack_index) { int i; mfunction_t *func = prog->xfunction; int st = prog->xstatement; if (stack_index > 0 && stack_index <= prog->depth) { func = prog->stack[prog->depth - stack_index].f; st = prog->stack[prog->depth - stack_index].s; } if (prog->statestring) { Con_Printf("Caller-provided information: %s\n", prog->statestring); } if (func) { for (i = -7; i <= 0;i++) if (st + i >= func->first_statement) PRVM_PrintStatement(prog, prog->statements + st + i); } PRVM_StackTrace(prog); } extern cvar_t prvm_errordump; void PRVM_Crash(prvm_prog_t *prog) { char vabuf[1024]; if (prog == NULL) return; if (!prog->loaded) return; PRVM_serverfunction(SV_Shutdown) = 0; // don't call SV_Shutdown on crash if( prog->depth > 0 ) { Con_Printf("QuakeC crash report for %s:\n", prog->name); PRVM_PrintState(prog, 0); } if(prvm_errordump.integer) { // make a savegame Host_Savegame_to(prog, va(vabuf, sizeof(vabuf), "crash-%s.dmp", prog->name)); } // dump the stack so host_error can shutdown functions prog->depth = 0; prog->localstack_used = 0; // delete all tempstrings (FIXME: is this safe in VM->engine->VM recursion?) prog->tempstringsbuf.cursize = 0; // reset the prog pointer prog = NULL; } /* ============================================================================ PRVM_ExecuteProgram The interpretation main loop ============================================================================ */ /* ==================== PRVM_EnterFunction Returns the new program statement counter ==================== */ static int PRVM_EnterFunction (prvm_prog_t *prog, mfunction_t *f) { int i, j, c, o; if (!f) prog->error_cmd("PRVM_EnterFunction: NULL function in %s", prog->name); prog->stack[prog->depth].s = prog->xstatement; prog->stack[prog->depth].f = prog->xfunction; prog->stack[prog->depth].profile_acc = -f->profile; prog->stack[prog->depth].tprofile_acc = -f->tprofile + -f->tbprofile; prog->stack[prog->depth].builtinsprofile_acc = -f->builtinsprofile; prog->depth++; if (prog->depth >=PRVM_MAX_STACK_DEPTH) prog->error_cmd("stack overflow"); // save off any locals that the new function steps on c = f->locals; if (prog->localstack_used + c > PRVM_LOCALSTACK_SIZE) prog->error_cmd("PRVM_ExecuteProgram: locals stack overflow in %s", prog->name); for (i=0 ; i < c ; i++) prog->localstack[prog->localstack_used+i] = prog->globals.ip[f->parm_start + i]; prog->localstack_used += c; // copy parameters o = f->parm_start; for (i=0 ; inumparms ; i++) { for (j=0 ; jparm_size[i] ; j++) { prog->globals.ip[o] = prog->globals.ip[OFS_PARM0+i*3+j]; o++; } } ++f->recursion; prog->xfunction = f; return f->first_statement - 1; // offset the s++ } /* ==================== PRVM_LeaveFunction ==================== */ static int PRVM_LeaveFunction (prvm_prog_t *prog) { int i, c; mfunction_t *f; if (prog->depth <= 0) prog->error_cmd("prog stack underflow in %s", prog->name); if (!prog->xfunction) prog->error_cmd("PR_LeaveFunction: NULL function in %s", prog->name); // restore locals from the stack c = prog->xfunction->locals; prog->localstack_used -= c; if (prog->localstack_used < 0) prog->error_cmd("PRVM_ExecuteProgram: locals stack underflow in %s", prog->name); for (i=0 ; i < c ; i++) prog->globals.ip[prog->xfunction->parm_start + i] = prog->localstack[prog->localstack_used+i]; // up stack prog->depth--; f = prog->xfunction; --f->recursion; prog->xfunction = prog->stack[prog->depth].f; prog->stack[prog->depth].profile_acc += f->profile; prog->stack[prog->depth].tprofile_acc += f->tprofile + f->tbprofile; prog->stack[prog->depth].builtinsprofile_acc += f->builtinsprofile; if(prog->depth > 0) { prog->stack[prog->depth-1].profile_acc += prog->stack[prog->depth].profile_acc; prog->stack[prog->depth-1].tprofile_acc += prog->stack[prog->depth].tprofile_acc; prog->stack[prog->depth-1].builtinsprofile_acc += prog->stack[prog->depth].builtinsprofile_acc; } if(!f->recursion) { // if f is already on the call stack... // we cannot add this profile data to it now // or we would add it more than once // so, let's only add to the function's profile if it is the outermost call f->profile_total += prog->stack[prog->depth].profile_acc; f->tprofile_total += prog->stack[prog->depth].tprofile_acc; f->builtinsprofile_total += prog->stack[prog->depth].builtinsprofile_acc; } return prog->stack[prog->depth].s; } void PRVM_Init_Exec(prvm_prog_t *prog) { // dump the stack prog->depth = 0; prog->localstack_used = 0; // reset the string table // nothing here yet } /* ================== Coverage ================== */ // Note: in these two calls, prog->xfunction is assumed to be sane. static const char *PRVM_WhereAmI(char *buf, size_t bufsize, prvm_prog_t *prog, mfunction_t *func, int statement) { if (prog->statement_linenums) { if (prog->statement_columnnums) return va(buf, bufsize, "%s:%i:%i(%s, %i)", PRVM_GetString(prog, func->s_file), prog->statement_linenums[statement], prog->statement_columnnums[statement], PRVM_GetString(prog, func->s_name), statement - func->first_statement); else return va(buf, bufsize, "%s:%i(%s, %i)", PRVM_GetString(prog, func->s_file), prog->statement_linenums[statement], PRVM_GetString(prog, func->s_name), statement - func->first_statement); } else return va(buf, bufsize, "%s(%s, %i)", PRVM_GetString(prog, func->s_file), PRVM_GetString(prog, func->s_name), statement - func->first_statement); } static void PRVM_FunctionCoverageEvent(prvm_prog_t *prog, mfunction_t *func) { ++prog->functions_covered; Con_Printf("prvm_coverage: %s just called %s for the first time. Coverage: %.2f%%.\n", prog->name, PRVM_GetString(prog, func->s_name), prog->functions_covered * 100.0 / prog->numfunctions); } void PRVM_ExplicitCoverageEvent(prvm_prog_t *prog, mfunction_t *func, int statement) { char vabuf[128]; ++prog->explicit_covered; Con_Printf("prvm_coverage: %s just executed a coverage() statement at %s for the first time. Coverage: %.2f%%.\n", prog->name, PRVM_WhereAmI(vabuf, sizeof(vabuf), prog, func, statement), prog->explicit_covered * 100.0 / prog->numexplicitcoveragestatements); } static void PRVM_StatementCoverageEvent(prvm_prog_t *prog, mfunction_t *func, int statement) { char vabuf[128]; ++prog->statements_covered; Con_Printf("prvm_coverage: %s just executed a statement at %s for the first time. Coverage: %.2f%%.\n", prog->name, PRVM_WhereAmI(vabuf, sizeof(vabuf), prog, func, statement), prog->statements_covered * 100.0 / prog->numstatements); } #ifdef __GNUC__ #define HAVE_COMPUTED_GOTOS 1 #endif #define OPA ((prvm_eval_t *)&prog->globals.fp[st->operand[0]]) #define OPB ((prvm_eval_t *)&prog->globals.fp[st->operand[1]]) #define OPC ((prvm_eval_t *)&prog->globals.fp[st->operand[2]]) extern cvar_t prvm_traceqc; extern cvar_t prvm_statementprofiling; extern qboolean prvm_runawaycheck; #ifdef PROFILING #ifdef CONFIG_MENU /* ==================== MVM_ExecuteProgram ==================== */ void MVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) { mstatement_t *st, *startst; mfunction_t *func, *enterfunc; prvm_edict_t *ed; prvm_eval_t *ptr; int jumpcount, cachedpr_trace, exitdepth; int restorevm_tempstringsbuf_cursize; double calltime; double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly prvm_vec_t *cached_edictsfields = prog->edictsfields; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; unsigned int cached_max_edicts = prog->max_edicts; // these do not change mstatement_t *cached_statements = prog->statements; qboolean cached_allowworldwrites = prog->allowworldwrites; unsigned int cached_flag = prog->flag; calltime = Sys_DirtyTime(); if (!fnum || fnum >= (unsigned int)prog->numfunctions) { if (PRVM_allglobaledict(self)) PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); prog->error_cmd("MVM_ExecuteProgram: %s", errormessage); } func = &prog->functions[fnum]; // after executing this function, delete all tempstrings it created restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; prog->trace = prvm_traceqc.integer; // we know we're done when pr_depth drops to this exitdepth = prog->depth; // make a stack frame st = &prog->statements[PRVM_EnterFunction(prog, func)]; // save the starting statement pointer for profiling // (when the function exits or jumps, the (st - startst) integer value is // added to the function's profile counter) startst = st; starttm = calltime; // instead of counting instructions, we count jumps jumpcount = 0; // add one to the callcount of this function because otherwise engine-called functions aren't counted if (prog->xfunction->callcount++ == 0 && (prvm_coverage.integer & 1)) PRVM_FunctionCoverageEvent(prog, prog->xfunction); chooseexecprogram: cachedpr_trace = prog->trace; if (prog->trace || prog->watch_global_type != ev_void || prog->watch_field_type != ev_void || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } #undef PRVMSLOWINTERPRETER } else { if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } } cleanup: if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) Con_DPrintf("MVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); // delete tempstrings created by this function prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; func->totaltime += tm; if (prog == SVVM_prog) SV_FlushBroadcastMessages(); } #endif /* ==================== CLVM_ExecuteProgram ==================== */ void CLVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) { mstatement_t *st, *startst; mfunction_t *func, *enterfunc; prvm_edict_t *ed; prvm_eval_t *ptr; int jumpcount, cachedpr_trace, exitdepth; int restorevm_tempstringsbuf_cursize; double calltime; double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly prvm_vec_t *cached_edictsfields = prog->edictsfields; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; unsigned int cached_max_edicts = prog->max_edicts; // these do not change mstatement_t *cached_statements = prog->statements; qboolean cached_allowworldwrites = prog->allowworldwrites; unsigned int cached_flag = prog->flag; calltime = Sys_DirtyTime(); if (!fnum || fnum >= (unsigned int)prog->numfunctions) { if (PRVM_allglobaledict(self)) PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); prog->error_cmd("CLVM_ExecuteProgram: %s", errormessage); } func = &prog->functions[fnum]; // after executing this function, delete all tempstrings it created restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; prog->trace = prvm_traceqc.integer; // we know we're done when pr_depth drops to this exitdepth = prog->depth; // make a stack frame st = &prog->statements[PRVM_EnterFunction(prog, func)]; // save the starting statement pointer for profiling // (when the function exits or jumps, the (st - startst) integer value is // added to the function's profile counter) startst = st; starttm = calltime; // instead of counting instructions, we count jumps jumpcount = 0; // add one to the callcount of this function because otherwise engine-called functions aren't counted if (prog->xfunction->callcount++ == 0 && (prvm_coverage.integer & 1)) PRVM_FunctionCoverageEvent(prog, prog->xfunction); chooseexecprogram: cachedpr_trace = prog->trace; if (prog->trace || prog->watch_global_type != ev_void || prog->watch_field_type != ev_void || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } #undef PRVMSLOWINTERPRETER } else { if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } } cleanup: if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) Con_DPrintf("CLVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); // delete tempstrings created by this function prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; func->totaltime += tm; if (prog == SVVM_prog) SV_FlushBroadcastMessages(); } #endif /* ==================== SVVM_ExecuteProgram ==================== */ #ifdef PROFILING void SVVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) #else void PRVM_ExecuteProgram (prvm_prog_t *prog, func_t fnum, const char *errormessage) #endif { mstatement_t *st, *startst; mfunction_t *func, *enterfunc; prvm_edict_t *ed; prvm_eval_t *ptr; int jumpcount, cachedpr_trace, exitdepth; int restorevm_tempstringsbuf_cursize; double calltime; double tm, starttm; prvm_vec_t tempfloat; // these may become out of date when a builtin is called, and are updated accordingly prvm_vec_t *cached_edictsfields = prog->edictsfields; unsigned int cached_entityfields = prog->entityfields; unsigned int cached_entityfields_3 = prog->entityfields - 3; unsigned int cached_entityfieldsarea = prog->entityfieldsarea; unsigned int cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; unsigned int cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; unsigned int cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; unsigned int cached_max_edicts = prog->max_edicts; // these do not change mstatement_t *cached_statements = prog->statements; qboolean cached_allowworldwrites = prog->allowworldwrites; unsigned int cached_flag = prog->flag; calltime = Sys_DirtyTime(); if (!fnum || fnum >= (unsigned int)prog->numfunctions) { if (PRVM_allglobaledict(self)) PRVM_ED_Print(prog, PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)), NULL); prog->error_cmd("SVVM_ExecuteProgram: %s", errormessage); } func = &prog->functions[fnum]; // after executing this function, delete all tempstrings it created restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; prog->trace = prvm_traceqc.integer; // we know we're done when pr_depth drops to this exitdepth = prog->depth; // make a stack frame st = &prog->statements[PRVM_EnterFunction(prog, func)]; // save the starting statement pointer for profiling // (when the function exits or jumps, the (st - startst) integer value is // added to the function's profile counter) startst = st; starttm = calltime; // instead of counting instructions, we count jumps jumpcount = 0; // add one to the callcount of this function because otherwise engine-called functions aren't counted if (prog->xfunction->callcount++ == 0 && (prvm_coverage.integer & 1)) PRVM_FunctionCoverageEvent(prog, prog->xfunction); chooseexecprogram: cachedpr_trace = prog->trace; if (prog->trace || prog->watch_global_type != ev_void || prog->watch_field_type != ev_void || prog->break_statement >= 0) { #define PRVMSLOWINTERPRETER 1 if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } #undef PRVMSLOWINTERPRETER } else { if (prvm_timeprofiling.integer) { #define PRVMTIMEPROFILING 1 #include "prvm_execprogram.h" #undef PRVMTIMEPROFILING } else { #include "prvm_execprogram.h" } } cleanup: if (developer_insane.integer && prog->tempstringsbuf.cursize > restorevm_tempstringsbuf_cursize) Con_DPrintf("SVVM_ExecuteProgram: %s used %i bytes of tempstrings\n", PRVM_GetString(prog, prog->functions[fnum].s_name), prog->tempstringsbuf.cursize - restorevm_tempstringsbuf_cursize); // delete tempstrings created by this function prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; tm = Sys_DirtyTime() - calltime;if (tm < 0 || tm >= 1800) tm = 0; func->totaltime += tm; if (prog == SVVM_prog) SV_FlushBroadcastMessages(); } darkplaces/darkplaces-dedicated.dsp0000664000175000017500000003336113067716220016716 0ustar kalevkalev# Microsoft Developer Studio Project File - Name="darkplaces-dedicated" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Application" 0x0101 CFG=darkplaces-dedicated - Win32 Debug !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "darkplaces-dedicated.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "darkplaces-dedicated.mak" CFG="darkplaces-dedicated - Win32 Debug" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "darkplaces-dedicated - Win32 Release" (based on "Win32 (x86) Application") !MESSAGE "darkplaces-dedicated - Win32 Debug" (based on "Win32 (x86) Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName "" # PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe !IF "$(CFG)" == "darkplaces-dedicated - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release-Dedicated" # PROP Intermediate_Dir "Release-Dedicated" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "NDEBUG" # ADD RSC /l 0x40c /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 # ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /machine:I386 # SUBTRACT LINK32 /pdb:none !ELSEIF "$(CFG)" == "darkplaces-dedicated - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug-Dedicated" # PROP Intermediate_Dir "Debug-Dedicated" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "_DEBUG" # ADD RSC /l 0x40c /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /debug /machine:I386 /out:"Debug-Dedicated/darkplaces-dedicated-debug.exe" /pdbtype:sept # SUBTRACT LINK32 /pdb:none !ENDIF # Begin Target # Name "darkplaces-dedicated - Win32 Release" # Name "darkplaces-dedicated - Win32 Debug" # Begin Group "Source Files" # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File SOURCE=.\builddate.c # End Source File # Begin Source File SOURCE=.\cd_null.c # End Source File # Begin Source File SOURCE=.\cd_shared.c # End Source File # Begin Source File SOURCE=.\cl_collision.c # End Source File # Begin Source File SOURCE=.\cl_demo.c # End Source File # Begin Source File SOURCE=.\cl_dyntexture.h # End Source File # Begin Source File SOURCE=.\cl_gecko.h # End Source File # Begin Source File SOURCE=.\cl_input.c # End Source File # Begin Source File SOURCE=.\cl_main.c # End Source File # Begin Source File SOURCE=.\cl_parse.c # End Source File # Begin Source File SOURCE=.\cl_particles.c # End Source File # Begin Source File SOURCE=.\cl_screen.c # End Source File # Begin Source File SOURCE=.\cl_video.c # End Source File # Begin Source File SOURCE=.\clvm_cmds.c # End Source File # Begin Source File SOURCE=.\cmd.c # End Source File # Begin Source File SOURCE=.\collision.c # End Source File # Begin Source File SOURCE=.\common.c # End Source File # Begin Source File SOURCE=.\console.c # End Source File # Begin Source File SOURCE=.\csprogs.c # End Source File # Begin Source File SOURCE=.\curves.c # End Source File # Begin Source File SOURCE=.\cvar.c # End Source File # Begin Source File SOURCE=.\darkplaces.rc # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.c # End Source File # Begin Source File SOURCE=.\filematch.c # End Source File # Begin Source File SOURCE=.\fractalnoise.c # End Source File # Begin Source File SOURCE=.\fs.c # End Source File # Begin Source File SOURCE=.\gl_backend.c # End Source File # Begin Source File SOURCE=.\gl_draw.c # End Source File # Begin Source File SOURCE=.\gl_rmain.c # End Source File # Begin Source File SOURCE=.\gl_rsurf.c # End Source File # Begin Source File SOURCE=.\gl_textures.c # End Source File # Begin Source File SOURCE=.\host.c # End Source File # Begin Source File SOURCE=.\host_cmd.c # End Source File # Begin Source File SOURCE=.\image.c # End Source File # Begin Source File SOURCE=.\image_png.c # End Source File # Begin Source File SOURCE=.\jpeg.c # End Source File # Begin Source File SOURCE=.\keys.c # End Source File # Begin Source File SOURCE=.\lhnet.c # End Source File # Begin Source File SOURCE=.\libcurl.c # End Source File # Begin Source File SOURCE=.\mathlib.c # End Source File # Begin Source File SOURCE=.\matrixlib.c # End Source File # Begin Source File SOURCE=.\mdfour.c # End Source File # Begin Source File SOURCE=.\menu.c # End Source File # Begin Source File SOURCE=.\meshqueue.c # End Source File # Begin Source File SOURCE=.\model_alias.c # End Source File # Begin Source File SOURCE=.\model_brush.c # End Source File # Begin Source File SOURCE=.\model_shared.c # End Source File # Begin Source File SOURCE=.\model_sprite.c # End Source File # Begin Source File SOURCE=.\mvm_cmds.c # End Source File # Begin Source File SOURCE=.\netconn.c # End Source File # Begin Source File SOURCE=.\palette.c # End Source File # Begin Source File SOURCE=.\polygon.c # End Source File # Begin Source File SOURCE=.\portals.c # End Source File # Begin Source File SOURCE=.\protocol.c # End Source File # Begin Source File SOURCE=.\prvm_cmds.c # End Source File # Begin Source File SOURCE=.\prvm_edict.c # End Source File # Begin Source File SOURCE=.\prvm_exec.c # End Source File # Begin Source File SOURCE=.\r_explosion.c # End Source File # Begin Source File SOURCE=.\r_lerpanim.c # End Source File # Begin Source File SOURCE=.\r_lightning.c # End Source File # Begin Source File SOURCE=.\r_modules.c # End Source File # Begin Source File SOURCE=.\r_shadow.c # End Source File # Begin Source File SOURCE=.\r_sky.c # End Source File # Begin Source File SOURCE=.\r_sprites.c # End Source File # Begin Source File SOURCE=.\sbar.c # End Source File # Begin Source File SOURCE=.\snd_null.c # End Source File # Begin Source File SOURCE=.\sv_demo.c # End Source File # Begin Source File SOURCE=.\sv_main.c # End Source File # Begin Source File SOURCE=.\sv_move.c # End Source File # Begin Source File SOURCE=.\sv_phys.c # End Source File # Begin Source File SOURCE=.\sv_user.c # End Source File # Begin Source File SOURCE=.\svbsp.c # End Source File # Begin Source File SOURCE=.\svvm_cmds.c # End Source File # Begin Source File SOURCE=.\sys_linux.c # End Source File # Begin Source File SOURCE=.\sys_shared.c # End Source File # Begin Source File SOURCE=.\vid_null.c # End Source File # Begin Source File SOURCE=.\vid_shared.c # End Source File # Begin Source File SOURCE=.\view.c # End Source File # Begin Source File SOURCE=.\wad.c # End Source File # Begin Source File SOURCE=.\world.c # End Source File # Begin Source File SOURCE=.\zone.c # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File SOURCE=.\bspfile.h # End Source File # Begin Source File SOURCE=.\cdaudio.h # End Source File # Begin Source File SOURCE=.\cl_collision.h # End Source File # Begin Source File SOURCE=.\cl_dyntexture.h # End Source File # Begin Source File SOURCE=.\cl_gecko.h # End Source File # Begin Source File SOURCE=.\cl_screen.h # End Source File # Begin Source File SOURCE=.\cl_video.h # End Source File # Begin Source File SOURCE=.\client.h # End Source File # Begin Source File SOURCE=.\clprogdefs.h # End Source File # Begin Source File SOURCE=.\cmd.h # End Source File # Begin Source File SOURCE=.\collision.h # End Source File # Begin Source File SOURCE=.\common.h # End Source File # Begin Source File SOURCE=.\console.h # End Source File # Begin Source File SOURCE=.\csprogs.h # End Source File # Begin Source File SOURCE=.\curves.h # End Source File # Begin Source File SOURCE=.\cvar.h # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.h # End Source File # Begin Source File SOURCE=.\draw.h # End Source File # Begin Source File SOURCE=.\fs.h # End Source File # Begin Source File SOURCE=.\gl_backend.h # End Source File # Begin Source File SOURCE=.\glquake.h # End Source File # Begin Source File SOURCE=.\image.h # End Source File # Begin Source File SOURCE=.\image_png.h # End Source File # Begin Source File SOURCE=.\input.h # End Source File # Begin Source File SOURCE=.\jpeg.h # End Source File # Begin Source File SOURCE=.\keys.h # End Source File # Begin Source File SOURCE=.\lhfont.h # End Source File # Begin Source File SOURCE=.\lhnet.h # End Source File # Begin Source File SOURCE=.\libcurl.h # End Source File # Begin Source File SOURCE=.\mathlib.h # End Source File # Begin Source File SOURCE=.\matrixlib.h # End Source File # Begin Source File SOURCE=.\mdfour.h # End Source File # Begin Source File SOURCE=.\menu.h # End Source File # Begin Source File SOURCE=.\meshqueue.h # End Source File # Begin Source File SOURCE=.\model_alias.h # End Source File # Begin Source File SOURCE=.\model_brush.h # End Source File # Begin Source File SOURCE=.\model_dpmodel.h # End Source File # Begin Source File SOURCE=.\model_psk.h # End Source File # Begin Source File SOURCE=.\model_shared.h # End Source File # Begin Source File SOURCE=.\model_sprite.h # End Source File # Begin Source File SOURCE=.\model_zymotic.h # End Source File # Begin Source File SOURCE=.\modelgen.h # End Source File # Begin Source File SOURCE=.\mprogdefs.h # End Source File # Begin Source File SOURCE=.\netconn.h # End Source File # Begin Source File SOURCE=.\palette.h # End Source File # Begin Source File SOURCE=.\polygon.h # End Source File # Begin Source File SOURCE=.\portals.h # End Source File # Begin Source File SOURCE=.\pr_comp.h # End Source File # Begin Source File SOURCE=.\pr_execprogram.h # End Source File # Begin Source File SOURCE=.\progdefs.h # End Source File # Begin Source File SOURCE=.\progs.h # End Source File # Begin Source File SOURCE=.\progsvm.h # End Source File # Begin Source File SOURCE=.\protocol.h # End Source File # Begin Source File SOURCE=.\prvm_cmds.h # End Source File # Begin Source File SOURCE=.\prvm_execprogram.h # End Source File # Begin Source File SOURCE=.\qtypes.h # End Source File # Begin Source File SOURCE=.\quakedef.h # End Source File # Begin Source File SOURCE=.\r_lerpanim.h # End Source File # Begin Source File SOURCE=.\r_modules.h # End Source File # Begin Source File SOURCE=.\r_shadow.h # End Source File # Begin Source File SOURCE=.\r_textures.h # End Source File # Begin Source File SOURCE=.\render.h # End Source File # Begin Source File SOURCE=.\sbar.h # End Source File # Begin Source File SOURCE=.\screen.h # End Source File # Begin Source File SOURCE=.\server.h # End Source File # Begin Source File SOURCE=.\snd_main.h # End Source File # Begin Source File SOURCE=.\sound.h # End Source File # Begin Source File SOURCE=.\spritegn.h # End Source File # Begin Source File SOURCE=.\sv_demo.h # End Source File # Begin Source File SOURCE=.\svbsp.h # End Source File # Begin Source File SOURCE=.\sys.h # End Source File # Begin Source File SOURCE=.\vid.h # End Source File # Begin Source File SOURCE=.\wad.h # End Source File # Begin Source File SOURCE=.\world.h # End Source File # Begin Source File SOURCE=.\zone.h # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # Begin Source File SOURCE=.\darkplaces.ico # End Source File # Begin Source File SOURCE=.\darkplaces.rc # End Source File # Begin Source File SOURCE=.\resource.h # End Source File # End Group # End Target # End Project darkplaces/image_png.c0000664000175000017500000004227613067716220014270 0ustar kalevkalev/* Copyright (C) 2006 Serge "(515)" Ziryukin, Forest "LordHavoc" Hale This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ //[515]: png implemented into DP ONLY FOR TESTING 2d stuff with csqc // so delete this bullshit :D // //LordHavoc: rewrote most of this. #include "quakedef.h" #include "image.h" #include "image_png.h" static void (*qpng_set_sig_bytes) (void*, int); static int (*qpng_sig_cmp) (const unsigned char*, size_t, size_t); static void* (*qpng_create_read_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); static void* (*qpng_create_write_struct) (const char*, void*, void(*)(void *png, const char *message), void(*)(void *png, const char *message)); static void* (*qpng_create_info_struct) (void*); static void (*qpng_read_info) (void*, void*); static void (*qpng_set_compression_level) (void*, int); static void (*qpng_set_filter) (void*, int, int); static void (*qpng_set_expand) (void*); static void (*qpng_set_palette_to_rgb) (void*); static void (*qpng_set_tRNS_to_alpha) (void*); static void (*qpng_set_gray_to_rgb) (void*); static void (*qpng_set_filler) (void*, unsigned int, int); static void (*qpng_set_IHDR) (void*, void*, unsigned long, unsigned long, int, int, int, int, int); static void (*qpng_set_packing) (void*); static void (*qpng_set_bgr) (void*); static int (*qpng_set_interlace_handling) (void*); static void (*qpng_read_update_info) (void*, void*); static void (*qpng_read_image) (void*, unsigned char**); static void (*qpng_read_end) (void*, void*); static void (*qpng_destroy_read_struct) (void**, void**, void**); static void (*qpng_destroy_write_struct) (void**, void**); static void (*qpng_set_read_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length)); static void (*qpng_set_write_fn) (void*, void*, void(*)(void *png, unsigned char *data, size_t length), void(*)(void *png)); static unsigned int (*qpng_get_valid) (void*, void*, unsigned int); static unsigned int (*qpng_get_rowbytes) (void*, void*); static unsigned char (*qpng_get_channels) (void*, void*); static unsigned char (*qpng_get_bit_depth) (void*, void*); static unsigned int (*qpng_get_IHDR) (void*, void*, unsigned long*, unsigned long*, int *, int *, int *, int *, int *); static unsigned int (*qpng_access_version_number) (void); // FIXME is this return type right? It is a png_uint_32 in libpng static void (*qpng_write_info) (void*, void*); static void (*qpng_write_row) (void*, unsigned char*); static void (*qpng_write_end) (void*, void*); // libpng 1.4+ longjmp hack typedef void (*qpng_longjmp_ptr) (jmp_buf, int); static jmp_buf* (*qpng_set_longjmp_fn) (void *, qpng_longjmp_ptr, size_t); #define qpng_jmpbuf_14(png_ptr) (*qpng_set_longjmp_fn((png_ptr), longjmp, sizeof (jmp_buf))) // libpng 1.2 longjmp hack #define qpng_jmpbuf_12(png_ptr) (*((jmp_buf *) png_ptr)) // all version support #define qpng_jmpbuf(png_ptr) \ (qpng_set_longjmp_fn ? qpng_jmpbuf_14(png_ptr) : qpng_jmpbuf_12(png_ptr)) static dllfunction_t pngfuncs[] = { {"png_set_sig_bytes", (void **) &qpng_set_sig_bytes}, {"png_sig_cmp", (void **) &qpng_sig_cmp}, {"png_create_read_struct", (void **) &qpng_create_read_struct}, {"png_create_write_struct", (void **) &qpng_create_write_struct}, {"png_create_info_struct", (void **) &qpng_create_info_struct}, {"png_read_info", (void **) &qpng_read_info}, {"png_set_compression_level", (void **) &qpng_set_compression_level}, {"png_set_filter", (void **) &qpng_set_filter}, {"png_set_expand", (void **) &qpng_set_expand}, {"png_set_palette_to_rgb", (void **) &qpng_set_palette_to_rgb}, {"png_set_tRNS_to_alpha", (void **) &qpng_set_tRNS_to_alpha}, {"png_set_gray_to_rgb", (void **) &qpng_set_gray_to_rgb}, {"png_set_filler", (void **) &qpng_set_filler}, {"png_set_IHDR", (void **) &qpng_set_IHDR}, {"png_set_packing", (void **) &qpng_set_packing}, {"png_set_bgr", (void **) &qpng_set_bgr}, {"png_set_interlace_handling", (void **) &qpng_set_interlace_handling}, {"png_read_update_info", (void **) &qpng_read_update_info}, {"png_read_image", (void **) &qpng_read_image}, {"png_read_end", (void **) &qpng_read_end}, {"png_destroy_read_struct", (void **) &qpng_destroy_read_struct}, {"png_destroy_write_struct", (void **) &qpng_destroy_write_struct}, {"png_set_read_fn", (void **) &qpng_set_read_fn}, {"png_set_write_fn", (void **) &qpng_set_write_fn}, {"png_get_valid", (void **) &qpng_get_valid}, {"png_get_rowbytes", (void **) &qpng_get_rowbytes}, {"png_get_channels", (void **) &qpng_get_channels}, {"png_get_bit_depth", (void **) &qpng_get_bit_depth}, {"png_get_IHDR", (void **) &qpng_get_IHDR}, {"png_access_version_number", (void **) &qpng_access_version_number}, {"png_write_info", (void **) &qpng_write_info}, {"png_write_row", (void **) &qpng_write_row}, {"png_write_end", (void **) &qpng_write_end}, {NULL, NULL} }; static dllfunction_t png14funcs[] = { {"png_set_longjmp_fn", (void **) &qpng_set_longjmp_fn}, {NULL, NULL} }; // Handle for PNG DLL dllhandle_t png_dll = NULL; dllhandle_t png14_dll = NULL; /* ================================================================= DLL load & unload ================================================================= */ /* ==================== PNG_OpenLibrary Try to load the PNG DLL ==================== */ qboolean PNG_OpenLibrary (void) { const char* dllnames [] = { #if WIN32 "libpng16.dll", "libpng16-16.dll", "libpng15-15.dll", "libpng15.dll", "libpng14-14.dll", "libpng14.dll", "libpng12.dll", #elif defined(MACOSX) "libpng16.16.dylib", "libpng15.15.dylib", "libpng14.14.dylib", "libpng12.0.dylib", #else "libpng16.so.16", "libpng15.so.15", // WTF libtool guidelines anyone? "libpng14.so.14", // WTF libtool guidelines anyone? "libpng12.so.0", "libpng.so", // FreeBSD #endif NULL }; // Already loaded? if (png_dll) return true; // Load the DLL if(!Sys_LoadLibrary (dllnames, &png_dll, pngfuncs)) return false; if(qpng_access_version_number() / 100 >= 104) if(!Sys_LoadLibrary (dllnames, &png14_dll, png14funcs)) { Sys_UnloadLibrary (&png_dll); return false; } return true; } /* ==================== PNG_CloseLibrary Unload the PNG DLL ==================== */ void PNG_CloseLibrary (void) { Sys_UnloadLibrary (&png14_dll); Sys_UnloadLibrary (&png_dll); } /* ================================================================= PNG decompression ================================================================= */ #define PNG_LIBPNG_VER_STRING_12 "1.2.4" #define PNG_LIBPNG_VER_STRING_14 "1.4.0" #define PNG_LIBPNG_VER_STRING_15 "1.5.0" #define PNG_LIBPNG_VER_STRING_16 "1.6.0" #define PNG_COLOR_MASK_PALETTE 1 #define PNG_COLOR_MASK_COLOR 2 #define PNG_COLOR_MASK_ALPHA 4 #define PNG_COLOR_TYPE_GRAY 0 #define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE) #define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR) #define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA) #define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA) #define PNG_COLOR_TYPE_RGBA PNG_COLOR_TYPE_RGB_ALPHA #define PNG_COLOR_TYPE_GA PNG_COLOR_TYPE_GRAY_ALPHA #define PNG_INFO_tRNS 0x0010 // this struct is only used for status information during loading static struct { const unsigned char *tmpBuf; int tmpBuflength; int tmpi; //int FBgColor; //int FTransparent; unsigned int FRowBytes; //double FGamma; //double FScreenGamma; unsigned char **FRowPtrs; unsigned char *Data; //char *Title; //char *Author; //char *Description; int BitDepth; int BytesPerPixel; int ColorType; unsigned long Height; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) unsigned long Width; // retarded libpng 1.2 pngconf.h uses long (64bit/32bit depending on arch) int Interlace; int Compression; int Filter; //double LastModified; //int Transparent; qfile_t *outfile; } my_png; //LordHavoc: removed __cdecl prefix, added overrun protection, and rewrote this to be more efficient static void PNG_fReadData(void *png, unsigned char *data, size_t length) { size_t l; l = my_png.tmpBuflength - my_png.tmpi; if (l < length) { Con_Printf("PNG_fReadData: overrun by %i bytes\n", (int)(length - l)); // a read going past the end of the file, fill in the remaining bytes // with 0 just to be consistent memset(data + l, 0, length - l); length = l; } memcpy(data, my_png.tmpBuf + my_png.tmpi, length); my_png.tmpi += (int)length; //Com_HexDumpToConsole(data, (int)length); } static void PNG_fWriteData(void *png, unsigned char *data, size_t length) { FS_Write(my_png.outfile, data, length); } static void PNG_fFlushData(void *png) { } static void PNG_error_fn(void *png, const char *message) { Con_Printf("PNG_LoadImage: error: %s\n", message); } static void PNG_warning_fn(void *png, const char *message) { Con_Printf("PNG_LoadImage: warning: %s\n", message); } unsigned char *PNG_LoadImage_BGRA (const unsigned char *raw, int filesize, int *miplevel) { unsigned int c; unsigned int y; void *png, *pnginfo; unsigned char *imagedata = NULL; unsigned char ioBuffer[8192]; // FIXME: register an error handler so that abort() won't be called on error // No DLL = no PNGs if (!png_dll) return NULL; if(qpng_sig_cmp(raw, 0, filesize)) return NULL; png = (void *)qpng_create_read_struct( (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : (qpng_access_version_number() / 100 == 104) ? PNG_LIBPNG_VER_STRING_14 : (qpng_access_version_number() / 100 == 105) ? PNG_LIBPNG_VER_STRING_15 : PNG_LIBPNG_VER_STRING_16, // nasty hack... whatever 0, PNG_error_fn, PNG_warning_fn ); if(!png) return NULL; // this must be memset before the setjmp error handler, because it relies // on the fields in this struct for cleanup memset(&my_png, 0, sizeof(my_png)); // NOTE: this relies on jmp_buf being the first thing in the png structure // created by libpng! (this is correct for libpng 1.2.x) if (setjmp(qpng_jmpbuf(png))) { if (my_png.Data) Mem_Free(my_png.Data); my_png.Data = NULL; if (my_png.FRowPtrs) Mem_Free(my_png.FRowPtrs); my_png.FRowPtrs = NULL; qpng_destroy_read_struct(&png, &pnginfo, 0); return NULL; } // pnginfo = qpng_create_info_struct(png); if(!pnginfo) { qpng_destroy_read_struct(&png, &pnginfo, 0); return NULL; } qpng_set_sig_bytes(png, 0); my_png.tmpBuf = raw; my_png.tmpBuflength = filesize; my_png.tmpi = 0; //my_png.Data = NULL; //my_png.FRowPtrs = NULL; //my_png.Height = 0; //my_png.Width = 0; my_png.ColorType = PNG_COLOR_TYPE_RGB; //my_png.Interlace = 0; //my_png.Compression = 0; //my_png.Filter = 0; qpng_set_read_fn(png, ioBuffer, PNG_fReadData); qpng_read_info(png, pnginfo); qpng_get_IHDR(png, pnginfo, &my_png.Width, &my_png.Height,&my_png.BitDepth, &my_png.ColorType, &my_png.Interlace, &my_png.Compression, &my_png.Filter); // this check guards against pngconf.h with unsigned int *width/height parameters on big endian systems by detecting the strange values and shifting them down 32bits // (if it's little endian the unwritten bytes are the most significant // ones and we don't worry about that) // // this is only necessary because of retarded 64bit png_uint_32 types in libpng 1.2, which can (conceivably) vary by platform #if LONG_MAX > 4000000000 if (my_png.Width > LONG_MAX || my_png.Height > LONG_MAX) { my_png.Width >>= 32; my_png.Height >>= 32; } #endif if (my_png.ColorType == PNG_COLOR_TYPE_PALETTE) qpng_set_palette_to_rgb(png); if (my_png.ColorType == PNG_COLOR_TYPE_GRAY || my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA) qpng_set_gray_to_rgb(png); if (qpng_get_valid(png, pnginfo, PNG_INFO_tRNS)) qpng_set_tRNS_to_alpha(png); if (my_png.BitDepth == 8 && !(my_png.ColorType & PNG_COLOR_MASK_ALPHA)) qpng_set_filler(png, 255, 1); if (( my_png.ColorType == PNG_COLOR_TYPE_GRAY) || (my_png.ColorType == PNG_COLOR_TYPE_GRAY_ALPHA )) qpng_set_gray_to_rgb(png); if (my_png.BitDepth < 8) qpng_set_expand(png); qpng_read_update_info(png, pnginfo); my_png.FRowBytes = qpng_get_rowbytes(png, pnginfo); my_png.BytesPerPixel = qpng_get_channels(png, pnginfo); my_png.FRowPtrs = (unsigned char **)Mem_Alloc(tempmempool, my_png.Height * sizeof(*my_png.FRowPtrs)); if (my_png.FRowPtrs) { imagedata = (unsigned char *)Mem_Alloc(tempmempool, my_png.Height * my_png.FRowBytes); if(imagedata) { my_png.Data = imagedata; for(y = 0;y < my_png.Height;y++) my_png.FRowPtrs[y] = my_png.Data + y * my_png.FRowBytes; qpng_read_image(png, my_png.FRowPtrs); } else { Con_Printf("PNG_LoadImage : not enough memory\n"); qpng_destroy_read_struct(&png, &pnginfo, 0); Mem_Free(my_png.FRowPtrs); return NULL; } Mem_Free(my_png.FRowPtrs); my_png.FRowPtrs = NULL; } else { Con_Printf("PNG_LoadImage : not enough memory\n"); qpng_destroy_read_struct(&png, &pnginfo, 0); return NULL; } qpng_read_end(png, pnginfo); qpng_destroy_read_struct(&png, &pnginfo, 0); image_width = (int)my_png.Width; image_height = (int)my_png.Height; if (my_png.BitDepth != 8) { Con_Printf ("PNG_LoadImage : bad color depth\n"); Mem_Free(imagedata); return NULL; } // swizzle RGBA to BGRA for (y = 0;y < (unsigned int)(image_width*image_height*4);y += 4) { c = imagedata[y+0]; imagedata[y+0] = imagedata[y+2]; imagedata[y+2] = c; } return imagedata; } /* ================================================================= PNG compression ================================================================= */ #define Z_BEST_SPEED 1 #define Z_BEST_COMPRESSION 9 #define PNG_INTERLACE_NONE 0 #define PNG_INTERLACE_ADAM7 1 #define PNG_FILTER_TYPE_BASE 0 #define PNG_FILTER_TYPE_DEFAULT PNG_FILTER_TYPE_BASE #define PNG_COMPRESSION_TYPE_BASE 0 #define PNG_COMPRESSION_TYPE_DEFAULT PNG_COMPRESSION_TYPE_BASE #define PNG_NO_FILTERS 0x00 #define PNG_FILTER_NONE 0x08 #define PNG_FILTER_SUB 0x10 #define PNG_FILTER_UP 0x20 #define PNG_FILTER_AVG 0x40 #define PNG_FILTER_PAETH 0x80 #define PNG_ALL_FILTERS (PNG_FILTER_NONE | PNG_FILTER_SUB | PNG_FILTER_UP | \ PNG_FILTER_AVG | PNG_FILTER_PAETH) /* ==================== PNG_SaveImage_preflipped Save a preflipped PNG image to a file ==================== */ qboolean PNG_SaveImage_preflipped (const char *filename, int width, int height, qboolean has_alpha, unsigned char *data) { unsigned int offset, linesize; qfile_t* file = NULL; void *png, *pnginfo; unsigned char ioBuffer[8192]; int passes, i, j; // No DLL = no JPEGs if (!png_dll) { Con_Print("You need the libpng library to save PNG images\n"); return false; } png = (void *)qpng_create_write_struct( (qpng_access_version_number() / 100 == 102) ? PNG_LIBPNG_VER_STRING_12 : (qpng_access_version_number() / 100 == 104) ? PNG_LIBPNG_VER_STRING_14 : (qpng_access_version_number() / 100 == 105) ? PNG_LIBPNG_VER_STRING_15 : PNG_LIBPNG_VER_STRING_16, // nasty hack... whatever 0, PNG_error_fn, PNG_warning_fn ); if(!png) return false; pnginfo = (void *)qpng_create_info_struct(png); if(!pnginfo) { qpng_destroy_write_struct(&png, NULL); return false; } // this must be memset before the setjmp error handler, because it relies // on the fields in this struct for cleanup memset(&my_png, 0, sizeof(my_png)); // NOTE: this relies on jmp_buf being the first thing in the png structure // created by libpng! (this is correct for libpng 1.2.x) #ifdef __cplusplus #ifdef WIN64 if (setjmp((_JBTYPE *)png)) #elif defined(MACOSX) || defined(WIN32) if (setjmp((int *)png)) #elif defined(__ANDROID__) if (setjmp((long *)png)) #else if (setjmp((__jmp_buf_tag *)png)) #endif #else if (setjmp(png)) #endif { qpng_destroy_write_struct(&png, &pnginfo); return false; } // Open the file file = FS_OpenRealFile(filename, "wb", true); if (!file) return false; my_png.outfile = file; qpng_set_write_fn(png, ioBuffer, PNG_fWriteData, PNG_fFlushData); //qpng_set_compression_level(png, Z_BEST_COMPRESSION); qpng_set_compression_level(png, Z_BEST_SPEED); qpng_set_IHDR(png, pnginfo, width, height, 8, has_alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_ADAM7, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); qpng_set_filter(png, 0, PNG_NO_FILTERS); qpng_write_info(png, pnginfo); qpng_set_packing(png); qpng_set_bgr(png); passes = qpng_set_interlace_handling(png); linesize = width * (has_alpha ? 4 : 3); offset = linesize * (height - 1); for(i = 0; i < passes; ++i) for(j = 0; j < height; ++j) qpng_write_row(png, &data[offset - j * linesize]); qpng_write_end(png, NULL); qpng_destroy_write_struct(&png, &pnginfo); FS_Close (file); return true; } darkplaces/clvm_cmds.c0000664000175000017500000055070513067716216014317 0ustar kalevkalev#include "quakedef.h" #include "prvm_cmds.h" #include "csprogs.h" #include "cl_collision.h" #include "r_shadow.h" #include "jpeg.h" #include "image.h" //============================================================================ // Client //[515]: unsolved PROBLEMS //- finish player physics code (cs_runplayerphysics) //- EntWasFreed ? //- RF_DEPTHHACK is not like it should be //- add builtin that sets cl.viewangles instead of reading "input_angles" global //- finish lines support for R_Polygon*** //- insert selecttraceline into traceline somehow //4 feature darkplaces csqc: add builtin to clientside qc for reading triangles of model meshes (useful to orient a ui along a triangle of a model mesh) //4 feature darkplaces csqc: add builtins to clientside qc for gl calls extern cvar_t v_flipped; extern cvar_t r_equalize_entities_fullbright; r_refdef_view_t csqc_original_r_refdef_view; r_refdef_view_t csqc_main_r_refdef_view; // #1 void(vector ang) makevectors static void VM_CL_makevectors (prvm_prog_t *prog) { vec3_t angles, forward, right, up; VM_SAFEPARMCOUNT(1, VM_CL_makevectors); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), angles); AngleVectors(angles, forward, right, up); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorCopy(right, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); } // #2 void(entity e, vector o) setorigin static void VM_CL_setorigin (prvm_prog_t *prog) { prvm_edict_t *e; prvm_vec_t *org; VM_SAFEPARMCOUNT(2, VM_CL_setorigin); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setorigin: can not modify world entity\n"); return; } if (e->priv.required->free) { VM_Warning(prog, "setorigin: can not modify free entity\n"); return; } org = PRVM_G_VECTOR(OFS_PARM1); VectorCopy (org, PRVM_clientedictvector(e, origin)); if(e->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) e->priv.required->mark = PRVM_EDICT_MARK_SETORIGIN_CAUGHT; CL_LinkEdict(e); } static void SetMinMaxSizePRVM (prvm_prog_t *prog, prvm_edict_t *e, prvm_vec_t *min, prvm_vec_t *max) { int i; for (i=0 ; i<3 ; i++) if (min[i] > max[i]) prog->error_cmd("SetMinMaxSize: backwards mins/maxs"); // set derived values VectorCopy (min, PRVM_clientedictvector(e, mins)); VectorCopy (max, PRVM_clientedictvector(e, maxs)); VectorSubtract (max, min, PRVM_clientedictvector(e, size)); CL_LinkEdict (e); } static void SetMinMaxSize (prvm_prog_t *prog, prvm_edict_t *e, const vec_t *min, const vec_t *max) { prvm_vec3_t mins, maxs; VectorCopy(min, mins); VectorCopy(max, maxs); SetMinMaxSizePRVM(prog, e, mins, maxs); } // #3 void(entity e, string m) setmodel static void VM_CL_setmodel (prvm_prog_t *prog) { prvm_edict_t *e; const char *m; dp_model_t *mod; int i; VM_SAFEPARMCOUNT(2, VM_CL_setmodel); e = PRVM_G_EDICT(OFS_PARM0); PRVM_clientedictfloat(e, modelindex) = 0; PRVM_clientedictstring(e, model) = 0; m = PRVM_G_STRING(OFS_PARM1); mod = NULL; for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) { if (!strcmp(cl.csqc_model_precache[i]->name, m)) { mod = cl.csqc_model_precache[i]; PRVM_clientedictstring(e, model) = PRVM_SetEngineString(prog, mod->name); PRVM_clientedictfloat(e, modelindex) = -(i+1); break; } } if( !mod ) { for (i = 0;i < MAX_MODELS;i++) { mod = cl.model_precache[i]; if (mod && !strcmp(mod->name, m)) { PRVM_clientedictstring(e, model) = PRVM_SetEngineString(prog, mod->name); PRVM_clientedictfloat(e, modelindex) = i; break; } } } if( mod ) { // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] // LordHavoc: erm you broke it by commenting this out - setmodel must do setsize or else the qc can't find out the model size, and ssqc does this by necessity, consistency. SetMinMaxSize (prog, e, mod->normalmins, mod->normalmaxs); } else { SetMinMaxSize (prog, e, vec3_origin, vec3_origin); VM_Warning(prog, "setmodel: model '%s' not precached\n", m); } } // #4 void(entity e, vector min, vector max) setsize static void VM_CL_setsize (prvm_prog_t *prog) { prvm_edict_t *e; vec3_t mins, maxs; VM_SAFEPARMCOUNT(3, VM_CL_setsize); e = PRVM_G_EDICT(OFS_PARM0); if (e == prog->edicts) { VM_Warning(prog, "setsize: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setsize: can not modify free entity\n"); return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM1), mins); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), maxs); SetMinMaxSize( prog, e, mins, maxs ); CL_LinkEdict(e); } // #8 void(entity e, float chan, string samp, float volume, float atten[, float pitchchange[, float flags]]) sound static void VM_CL_sound (prvm_prog_t *prog) { const char *sample; int channel; prvm_edict_t *entity; float fvolume; float attenuation; float pitchchange; float startposition; int flags; vec3_t org; VM_SAFEPARMCOUNTRANGE(5, 7, VM_CL_sound); entity = PRVM_G_EDICT(OFS_PARM0); channel = (int)PRVM_G_FLOAT(OFS_PARM1); sample = PRVM_G_STRING(OFS_PARM2); fvolume = PRVM_G_FLOAT(OFS_PARM3); attenuation = PRVM_G_FLOAT(OFS_PARM4); if (fvolume < 0 || fvolume > 1) { VM_Warning(prog, "VM_CL_sound: volume must be in range 0-1\n"); return; } if (attenuation < 0 || attenuation > 4) { VM_Warning(prog, "VM_CL_sound: attenuation must be in range 0-4\n"); return; } if (prog->argc < 6) pitchchange = 0; else pitchchange = PRVM_G_FLOAT(OFS_PARM5); if (prog->argc < 7) flags = 0; else { // LordHavoc: we only let the qc set certain flags, others are off-limits flags = (int)PRVM_G_FLOAT(OFS_PARM6) & (CHANNELFLAG_RELIABLE | CHANNELFLAG_FORCELOOP | CHANNELFLAG_PAUSED); } // sound_starttime exists instead of sound_startposition because in a // networking sense you might not know when something is being received, // so making sounds match up in sync would be impossible if relative // position was sent if (PRVM_clientglobalfloat(sound_starttime)) startposition = cl.time - PRVM_clientglobalfloat(sound_starttime); else startposition = 0; channel = CHAN_USER2ENGINE(channel); if (!IS_CHAN(channel)) { VM_Warning(prog, "VM_CL_sound: channel must be in range 0-127\n"); return; } CL_VM_GetEntitySoundOrigin(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), org); S_StartSound_StartPosition_Flags(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), org, fvolume, attenuation, startposition, flags, pitchchange > 0.0f ? pitchchange * 0.01f : 1.0f); } // #483 void(vector origin, string sample, float volume, float attenuation) pointsound static void VM_CL_pointsound(prvm_prog_t *prog) { const char *sample; float fvolume; float attenuation; vec3_t org; VM_SAFEPARMCOUNT(4, VM_CL_pointsound); VectorCopy( PRVM_G_VECTOR(OFS_PARM0), org); sample = PRVM_G_STRING(OFS_PARM1); fvolume = PRVM_G_FLOAT(OFS_PARM2); attenuation = PRVM_G_FLOAT(OFS_PARM3); if (fvolume < 0 || fvolume > 1) { VM_Warning(prog, "VM_CL_pointsound: volume must be in range 0-1\n"); return; } if (attenuation < 0 || attenuation > 4) { VM_Warning(prog, "VM_CL_pointsound: attenuation must be in range 0-4\n"); return; } // Send World Entity as Entity to Play Sound (for CSQC, that is MAX_EDICTS) S_StartSound(MAX_EDICTS, 0, S_FindName(sample), org, fvolume, attenuation); } // #14 entity() spawn static void VM_CL_spawn (prvm_prog_t *prog) { prvm_edict_t *ed; ed = PRVM_ED_Alloc(prog); VM_RETURN_EDICT(ed); } static void CL_VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace, int svent) { VM_SetTraceGlobals(prog, trace); PRVM_clientglobalfloat(trace_networkentity) = svent; } #define CL_HitNetworkBrushModels(move) !((move) == MOVE_WORLDONLY) #define CL_HitNetworkPlayers(move) !((move) == MOVE_WORLDONLY || (move) == MOVE_NOMONSTERS) // #16 void(vector v1, vector v2, float movetype, entity ignore) traceline static void VM_CL_traceline (prvm_prog_t *prog) { vec3_t v1, v2; trace_t trace; int move, svent; prvm_edict_t *ent; // R_TimeReport("pretraceline"); VM_SAFEPARMCOUNTRANGE(4, 4, VM_CL_traceline); prog->xfunction->builtinsprofile += 30; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), v2); move = (int)PRVM_G_FLOAT(OFS_PARM2); ent = PRVM_G_EDICT(OFS_PARM3); if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) prog->error_cmd("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); trace = CL_TraceLine(v1, v2, move, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendtracelinelength.value, CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true, false); CL_VM_SetTraceGlobals(prog, &trace, svent); // R_TimeReport("traceline"); } /* ================= VM_CL_tracebox Used for use tracing and shot targeting Traces are blocked by bbox and exact bsp entityes, and also slide box entities if the tryents flag is set. tracebox (vector1, vector mins, vector maxs, vector2, tryents) ================= */ // LordHavoc: added this for my own use, VERY useful, similar to traceline static void VM_CL_tracebox (prvm_prog_t *prog) { vec3_t v1, v2, m1, m2; trace_t trace; int move, svent; prvm_edict_t *ent; // R_TimeReport("pretracebox"); VM_SAFEPARMCOUNTRANGE(6, 8, VM_CL_tracebox); // allow more parameters for future expansion prog->xfunction->builtinsprofile += 30; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), v1); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), m1); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), m2); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), v2); move = (int)PRVM_G_FLOAT(OFS_PARM4); ent = PRVM_G_EDICT(OFS_PARM5); if (VEC_IS_NAN(v1[0]) || VEC_IS_NAN(v1[1]) || VEC_IS_NAN(v1[2]) || VEC_IS_NAN(v2[0]) || VEC_IS_NAN(v2[1]) || VEC_IS_NAN(v2[2])) prog->error_cmd("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", prog->name, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent)); trace = CL_TraceBox(v1, m1, m2, v2, move, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendtraceboxlength.value, CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true); CL_VM_SetTraceGlobals(prog, &trace, svent); // R_TimeReport("tracebox"); } static trace_t CL_Trace_Toss (prvm_prog_t *prog, prvm_edict_t *tossent, prvm_edict_t *ignore, int *svent) { int i; float gravity; vec3_t start, end, mins, maxs, move; vec3_t original_origin; vec3_t original_velocity; vec3_t original_angles; vec3_t original_avelocity; trace_t trace; VectorCopy(PRVM_clientedictvector(tossent, origin) , original_origin ); VectorCopy(PRVM_clientedictvector(tossent, velocity) , original_velocity ); VectorCopy(PRVM_clientedictvector(tossent, angles) , original_angles ); VectorCopy(PRVM_clientedictvector(tossent, avelocity), original_avelocity); gravity = PRVM_clientedictfloat(tossent, gravity); if (!gravity) gravity = 1.0f; gravity *= cl.movevars_gravity * 0.05; for (i = 0;i < 200;i++) // LordHavoc: sanity check; never trace more than 10 seconds { PRVM_clientedictvector(tossent, velocity)[2] -= gravity; VectorMA (PRVM_clientedictvector(tossent, angles), 0.05, PRVM_clientedictvector(tossent, avelocity), PRVM_clientedictvector(tossent, angles)); VectorScale (PRVM_clientedictvector(tossent, velocity), 0.05, move); VectorAdd (PRVM_clientedictvector(tossent, origin), move, end); VectorCopy(PRVM_clientedictvector(tossent, origin), start); VectorCopy(PRVM_clientedictvector(tossent, mins), mins); VectorCopy(PRVM_clientedictvector(tossent, maxs), maxs); trace = CL_TraceBox(start, mins, maxs, end, MOVE_NORMAL, tossent, CL_GenericHitSuperContentsMask(tossent), 0, collision_extendmovelength.value, true, true, NULL, true); VectorCopy (trace.endpos, PRVM_clientedictvector(tossent, origin)); if (trace.fraction < 1) break; } VectorCopy(original_origin , PRVM_clientedictvector(tossent, origin) ); VectorCopy(original_velocity , PRVM_clientedictvector(tossent, velocity) ); VectorCopy(original_angles , PRVM_clientedictvector(tossent, angles) ); VectorCopy(original_avelocity, PRVM_clientedictvector(tossent, avelocity)); return trace; } static void VM_CL_tracetoss (prvm_prog_t *prog) { trace_t trace; prvm_edict_t *ent; prvm_edict_t *ignore; int svent = 0; prog->xfunction->builtinsprofile += 600; VM_SAFEPARMCOUNT(2, VM_CL_tracetoss); ent = PRVM_G_EDICT(OFS_PARM0); if (ent == prog->edicts) { VM_Warning(prog, "tracetoss: can not use world entity\n"); return; } ignore = PRVM_G_EDICT(OFS_PARM1); trace = CL_Trace_Toss (prog, ent, ignore, &svent); CL_VM_SetTraceGlobals(prog, &trace, svent); } // #20 void(string s) precache_model static void VM_CL_precache_model (prvm_prog_t *prog) { const char *name; int i; dp_model_t *m; VM_SAFEPARMCOUNT(1, VM_CL_precache_model); name = PRVM_G_STRING(OFS_PARM0); for (i = 0;i < MAX_MODELS && cl.csqc_model_precache[i];i++) { if(!strcmp(cl.csqc_model_precache[i]->name, name)) { PRVM_G_FLOAT(OFS_RETURN) = -(i+1); return; } } PRVM_G_FLOAT(OFS_RETURN) = 0; m = Mod_ForName(name, false, false, name[0] == '*' ? cl.model_name[1] : NULL); if(m && m->loaded) { for (i = 0;i < MAX_MODELS;i++) { if (!cl.csqc_model_precache[i]) { cl.csqc_model_precache[i] = (dp_model_t*)m; PRVM_G_FLOAT(OFS_RETURN) = -(i+1); return; } } VM_Warning(prog, "VM_CL_precache_model: no free models\n"); return; } VM_Warning(prog, "VM_CL_precache_model: model \"%s\" not found\n", name); } // #22 entity(vector org, float rad) findradius static void VM_CL_findradius (prvm_prog_t *prog) { prvm_edict_t *ent, *chain; vec_t radius, radius2; vec3_t org, eorg, mins, maxs; int i, numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; int chainfield; VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_findradius); if(prog->argc == 3) chainfield = PRVM_G_INT(OFS_PARM2); else chainfield = prog->fieldoffsets.chain; if(chainfield < 0) prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); chain = (prvm_edict_t *)prog->edicts; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); radius = PRVM_G_FLOAT(OFS_PARM1); radius2 = radius * radius; mins[0] = org[0] - (radius + 1); mins[1] = org[1] - (radius + 1); mins[2] = org[2] - (radius + 1); maxs[0] = org[0] + (radius + 1); maxs[1] = org[1] + (radius + 1); maxs[2] = org[2] + (radius + 1); numtouchedicts = World_EntitiesInBox(&cl.world, mins, maxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens //[515]: for what then ? Con_Printf("CSQC_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { ent = touchedicts[i]; // Quake did not return non-solid entities but darkplaces does // (note: this is the reason you can't blow up fallen zombies) if (PRVM_clientedictfloat(ent, solid) == SOLID_NOT && !sv_gameplayfix_blowupfallenzombies.integer) continue; // LordHavoc: compare against bounding box rather than center so it // doesn't miss large objects, and use DotProduct instead of Length // for a major speedup VectorSubtract(org, PRVM_clientedictvector(ent, origin), eorg); if (sv_gameplayfix_findradiusdistancetobox.integer) { eorg[0] -= bound(PRVM_clientedictvector(ent, mins)[0], eorg[0], PRVM_clientedictvector(ent, maxs)[0]); eorg[1] -= bound(PRVM_clientedictvector(ent, mins)[1], eorg[1], PRVM_clientedictvector(ent, maxs)[1]); eorg[2] -= bound(PRVM_clientedictvector(ent, mins)[2], eorg[2], PRVM_clientedictvector(ent, maxs)[2]); } else VectorMAMAM(1, eorg, -0.5f, PRVM_clientedictvector(ent, mins), -0.5f, PRVM_clientedictvector(ent, maxs), eorg); if (DotProduct(eorg, eorg) < radius2) { PRVM_EDICTFIELDEDICT(ent, chainfield) = PRVM_EDICT_TO_PROG(chain); chain = ent; } } VM_RETURN_EDICT(chain); } // #34 float() droptofloor static void VM_CL_droptofloor (prvm_prog_t *prog) { prvm_edict_t *ent; vec3_t start, end, mins, maxs; trace_t trace; VM_SAFEPARMCOUNTRANGE(0, 2, VM_CL_droptofloor); // allow 2 parameters because the id1 defs.qc had an incorrect prototype // assume failure if it returns early PRVM_G_FLOAT(OFS_RETURN) = 0; ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "droptofloor: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "droptofloor: can not modify free entity\n"); return; } VectorCopy(PRVM_clientedictvector(ent, origin), start); VectorCopy(PRVM_clientedictvector(ent, mins), mins); VectorCopy(PRVM_clientedictvector(ent, maxs), maxs); VectorCopy(PRVM_clientedictvector(ent, origin), end); end[2] -= 256; trace = CL_TraceBox(start, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, NULL, true); if (trace.fraction != 1) { VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) | FL_ONGROUND; PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); PRVM_G_FLOAT(OFS_RETURN) = 1; // if support is destroyed, keep suspended (gross hack for floating items in various maps) // ent->priv.server->suspendedinairflag = true; } } // #35 void(float style, string value) lightstyle static void VM_CL_lightstyle (prvm_prog_t *prog) { int i; const char *c; VM_SAFEPARMCOUNT(2, VM_CL_lightstyle); i = (int)PRVM_G_FLOAT(OFS_PARM0); c = PRVM_G_STRING(OFS_PARM1); if (i >= cl.max_lightstyle) { VM_Warning(prog, "VM_CL_lightstyle >= MAX_LIGHTSTYLES\n"); return; } strlcpy (cl.lightstyle[i].map, c, sizeof (cl.lightstyle[i].map)); cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); } // #40 float(entity e) checkbottom static void VM_CL_checkbottom (prvm_prog_t *prog) { static int cs_yes, cs_no; prvm_edict_t *ent; vec3_t mins, maxs, start, stop; trace_t trace; int x, y; float mid, bottom; VM_SAFEPARMCOUNT(1, VM_CL_checkbottom); ent = PRVM_G_EDICT(OFS_PARM0); PRVM_G_FLOAT(OFS_RETURN) = 0; VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); // if all of the points under the corners are solid world, don't bother // with the tougher checks // the corners must be within 16 of the midpoint start[2] = mins[2] - 1; for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = x ? maxs[0] : mins[0]; start[1] = y ? maxs[1] : mins[1]; if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) goto realcheck; } cs_yes++; PRVM_G_FLOAT(OFS_RETURN) = true; return; // we got out easy realcheck: cs_no++; // // check it for real... // start[2] = mins[2]; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0])*0.5; start[1] = stop[1] = (mins[1] + maxs[1])*0.5; stop[2] = start[2] - 2*sv_stepheight.value; trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, NULL, true, false); if (trace.fraction == 1.0) return; mid = bottom = trace.endpos[2]; // the corners must be within 16 of the midpoint for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = stop[0] = x ? maxs[0] : mins[0]; start[1] = stop[1] = y ? maxs[1] : mins[1]; trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, NULL, true, false); if (trace.fraction != 1.0 && trace.endpos[2] > bottom) bottom = trace.endpos[2]; if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) return; } cs_yes++; PRVM_G_FLOAT(OFS_RETURN) = true; } // #41 float(vector v) pointcontents static void VM_CL_pointcontents (prvm_prog_t *prog) { vec3_t point; VM_SAFEPARMCOUNT(1, VM_CL_pointcontents); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), point); PRVM_G_FLOAT(OFS_RETURN) = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, CL_PointSuperContents(point)); } // #48 void(vector o, vector d, float color, float count) particle static void VM_CL_particle (prvm_prog_t *prog) { vec3_t org, dir; int count; unsigned char color; VM_SAFEPARMCOUNT(4, VM_CL_particle); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); color = (int)PRVM_G_FLOAT(OFS_PARM2); count = (int)PRVM_G_FLOAT(OFS_PARM3); CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); } // #74 void(vector pos, string samp, float vol, float atten) ambientsound static void VM_CL_ambientsound (prvm_prog_t *prog) { vec3_t f; sfx_t *s; VM_SAFEPARMCOUNT(4, VM_CL_ambientsound); s = S_FindName(PRVM_G_STRING(OFS_PARM0)); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f); S_StaticSound (s, f, PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM3)*64); } // #92 vector(vector org[, float lpflag]) getlight (DP_QC_GETLIGHT) static void VM_CL_getlight (prvm_prog_t *prog) { vec3_t ambientcolor, diffusecolor, diffusenormal; vec3_t p; VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_getlight); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), p); VectorClear(ambientcolor); VectorClear(diffusecolor); VectorClear(diffusenormal); if (prog->argc >= 2) R_CompleteLightPoint(ambientcolor, diffusecolor, diffusenormal, p, PRVM_G_FLOAT(OFS_PARM1)); else if (cl.worldmodel && cl.worldmodel->brush.LightPoint) cl.worldmodel->brush.LightPoint(cl.worldmodel, p, ambientcolor, diffusecolor, diffusenormal); VectorMA(ambientcolor, 0.5, diffusecolor, PRVM_G_VECTOR(OFS_RETURN)); if (PRVM_clientglobalvector(getlight_ambient)) VectorCopy(ambientcolor, PRVM_clientglobalvector(getlight_ambient)); if (PRVM_clientglobalvector(getlight_diffuse)) VectorCopy(diffusecolor, PRVM_clientglobalvector(getlight_diffuse)); if (PRVM_clientglobalvector(getlight_dir)) VectorCopy(diffusenormal, PRVM_clientglobalvector(getlight_dir)); } //============================================================================ //[515]: SCENE MANAGER builtins extern cvar_t v_yshearing; void CSQC_R_RecalcView (void) { extern matrix4x4_t viewmodelmatrix_nobob; extern matrix4x4_t viewmodelmatrix_withbob; Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, cl.csqc_vieworigin[0], cl.csqc_vieworigin[1], cl.csqc_vieworigin[2], cl.csqc_viewangles[0], cl.csqc_viewangles[1], cl.csqc_viewangles[2], 1); if (v_yshearing.value > 0) Matrix4x4_QuakeToDuke3D(&r_refdef.view.matrix, &r_refdef.view.matrix, v_yshearing.value); Matrix4x4_Copy(&viewmodelmatrix_nobob, &r_refdef.view.matrix); Matrix4x4_ConcatScale(&viewmodelmatrix_nobob, cl_viewmodel_scale.value); Matrix4x4_Concat(&viewmodelmatrix_withbob, &r_refdef.view.matrix, &cl.csqc_viewmodelmatrixfromengine); } //#300 void() clearscene (EXT_CSQC) static void VM_CL_R_ClearScene (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_R_ClearScene); // clear renderable entity and light lists r_refdef.scene.numentities = 0; r_refdef.scene.numlights = 0; // restore the view settings to the values that VM_CL_UpdateView received from the client code r_refdef.view = csqc_original_r_refdef_view; VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); cl.csqc_vidvars.drawworld = r_drawworld.integer != 0; cl.csqc_vidvars.drawenginesbar = false; cl.csqc_vidvars.drawcrosshair = false; CSQC_R_RecalcView(); } //#301 void(float mask) addentities (EXT_CSQC) static void VM_CL_R_AddEntities (prvm_prog_t *prog) { double t = Sys_DirtyTime(); int i, drawmask; prvm_edict_t *ed; VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntities); drawmask = (int)PRVM_G_FLOAT(OFS_PARM0); CSQC_RelinkAllEntities(drawmask); CL_RelinkLightFlashes(); PRVM_clientglobalfloat(time) = cl.time; for(i=1;inum_edicts;i++) { // so we can easily check if CSQC entity #edictnum is currently drawn cl.csqcrenderentities[i].entitynumber = 0; ed = &prog->edicts[i]; if(ed->priv.required->free) continue; CSQC_Think(ed); if(ed->priv.required->free) continue; // note that for RF_USEAXIS entities, Predraw sets v_forward/v_right/v_up globals that are read by CSQC_AddRenderEdict CSQC_Predraw(ed); if(ed->priv.required->free) continue; if(!((int)PRVM_clientedictfloat(ed, drawmask) & drawmask)) continue; CSQC_AddRenderEdict(ed, i); } // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; } //#302 void(entity ent) addentity (EXT_CSQC) static void VM_CL_R_AddEntity (prvm_prog_t *prog) { double t = Sys_DirtyTime(); VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntity); CSQC_AddRenderEdict(PRVM_G_EDICT(OFS_PARM0), 0); t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; } //#303 float(float property, ...) setproperty (EXT_CSQC) //#303 float(float property) getproperty //#303 vector(float property) getpropertyvec //#309 float(float property) getproperty //#309 vector(float property) getpropertyvec // VorteX: make this function be able to return previously set property if new value is not given static void VM_CL_R_SetView (prvm_prog_t *prog) { int c; prvm_vec_t *f; float k; VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_R_SetView); c = (int)PRVM_G_FLOAT(OFS_PARM0); // return value? if (prog->argc < 2) { switch(c) { case VF_MIN: VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.x, r_refdef.view.y, 0); break; case VF_MIN_X: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.x; break; case VF_MIN_Y: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.y; break; case VF_SIZE: VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.width, r_refdef.view.height, 0); break; case VF_SIZE_X: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.width; break; case VF_SIZE_Y: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.height; break; case VF_VIEWPORT: VM_Warning(prog, "VM_CL_R_GetView : VF_VIEWPORT can't be retrieved, use VF_MIN/VF_SIZE instead\n"); break; case VF_FOV: VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.ortho_x, r_refdef.view.ortho_y, 0); break; case VF_FOVX: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_x; break; case VF_FOVY: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ortho_y; break; case VF_ORIGIN: VectorCopy(cl.csqc_vieworigin, PRVM_G_VECTOR(OFS_RETURN)); break; case VF_ORIGIN_X: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[0]; break; case VF_ORIGIN_Y: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[1]; break; case VF_ORIGIN_Z: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vieworigin[2]; break; case VF_ANGLES: VectorCopy(cl.csqc_viewangles, PRVM_G_VECTOR(OFS_RETURN)); break; case VF_ANGLES_X: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[0]; break; case VF_ANGLES_Y: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[1]; break; case VF_ANGLES_Z: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_viewangles[2]; break; case VF_DRAWWORLD: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawworld; break; case VF_DRAWENGINESBAR: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawenginesbar; break; case VF_DRAWCROSSHAIR: PRVM_G_FLOAT(OFS_RETURN) = cl.csqc_vidvars.drawcrosshair; break; case VF_CL_VIEWANGLES: VectorCopy(cl.viewangles, PRVM_G_VECTOR(OFS_RETURN));; break; case VF_CL_VIEWANGLES_X: PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[0]; break; case VF_CL_VIEWANGLES_Y: PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[1]; break; case VF_CL_VIEWANGLES_Z: PRVM_G_FLOAT(OFS_RETURN) = cl.viewangles[2]; break; case VF_PERSPECTIVE: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.useperspective; break; case VF_CLEARSCREEN: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.isoverlay; break; case VF_MAINVIEW: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ismain; break; case VF_FOG_DENSITY: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_density; break; case VF_FOG_COLOR: PRVM_G_VECTOR(OFS_RETURN)[0] = r_refdef.fog_red; PRVM_G_VECTOR(OFS_RETURN)[1] = r_refdef.fog_green; PRVM_G_VECTOR(OFS_RETURN)[2] = r_refdef.fog_blue; break; case VF_FOG_COLOR_R: PRVM_G_VECTOR(OFS_RETURN)[0] = r_refdef.fog_red; break; case VF_FOG_COLOR_G: PRVM_G_VECTOR(OFS_RETURN)[1] = r_refdef.fog_green; break; case VF_FOG_COLOR_B: PRVM_G_VECTOR(OFS_RETURN)[2] = r_refdef.fog_blue; break; case VF_FOG_ALPHA: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_alpha; break; case VF_FOG_START: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_start; break; case VF_FOG_END: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_end; break; case VF_FOG_HEIGHT: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_height; break; case VF_FOG_FADEDEPTH: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.fog_fadedepth; break; case VF_MINFPS_QUALITY: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.quality; break; default: PRVM_G_FLOAT(OFS_RETURN) = 0; VM_Warning(prog, "VM_CL_R_GetView : unknown parm %i\n", c); return; } return; } f = PRVM_G_VECTOR(OFS_PARM1); k = PRVM_G_FLOAT(OFS_PARM1); switch(c) { case VF_MIN: r_refdef.view.x = (int)(f[0]); r_refdef.view.y = (int)(f[1]); DrawQ_RecalcView(); break; case VF_MIN_X: r_refdef.view.x = (int)(k); DrawQ_RecalcView(); break; case VF_MIN_Y: r_refdef.view.y = (int)(k); DrawQ_RecalcView(); break; case VF_SIZE: r_refdef.view.width = (int)(f[0]); r_refdef.view.height = (int)(f[1]); DrawQ_RecalcView(); break; case VF_SIZE_X: r_refdef.view.width = (int)(k); DrawQ_RecalcView(); break; case VF_SIZE_Y: r_refdef.view.height = (int)(k); DrawQ_RecalcView(); break; case VF_VIEWPORT: r_refdef.view.x = (int)(f[0]); r_refdef.view.y = (int)(f[1]); f = PRVM_G_VECTOR(OFS_PARM2); r_refdef.view.width = (int)(f[0]); r_refdef.view.height = (int)(f[1]); DrawQ_RecalcView(); break; case VF_FOV: r_refdef.view.frustum_x = tan(f[0] * M_PI / 360.0);r_refdef.view.ortho_x = f[0]; r_refdef.view.frustum_y = tan(f[1] * M_PI / 360.0);r_refdef.view.ortho_y = f[1]; break; case VF_FOVX: r_refdef.view.frustum_x = tan(k * M_PI / 360.0);r_refdef.view.ortho_x = k; break; case VF_FOVY: r_refdef.view.frustum_y = tan(k * M_PI / 360.0);r_refdef.view.ortho_y = k; break; case VF_ORIGIN: VectorCopy(f, cl.csqc_vieworigin); CSQC_R_RecalcView(); break; case VF_ORIGIN_X: cl.csqc_vieworigin[0] = k; CSQC_R_RecalcView(); break; case VF_ORIGIN_Y: cl.csqc_vieworigin[1] = k; CSQC_R_RecalcView(); break; case VF_ORIGIN_Z: cl.csqc_vieworigin[2] = k; CSQC_R_RecalcView(); break; case VF_ANGLES: VectorCopy(f, cl.csqc_viewangles); CSQC_R_RecalcView(); break; case VF_ANGLES_X: cl.csqc_viewangles[0] = k; CSQC_R_RecalcView(); break; case VF_ANGLES_Y: cl.csqc_viewangles[1] = k; CSQC_R_RecalcView(); break; case VF_ANGLES_Z: cl.csqc_viewangles[2] = k; CSQC_R_RecalcView(); break; case VF_DRAWWORLD: cl.csqc_vidvars.drawworld = ((k != 0) && r_drawworld.integer); break; case VF_DRAWENGINESBAR: cl.csqc_vidvars.drawenginesbar = k != 0; break; case VF_DRAWCROSSHAIR: cl.csqc_vidvars.drawcrosshair = k != 0; break; case VF_CL_VIEWANGLES: VectorCopy(f, cl.viewangles); break; case VF_CL_VIEWANGLES_X: cl.viewangles[0] = k; break; case VF_CL_VIEWANGLES_Y: cl.viewangles[1] = k; break; case VF_CL_VIEWANGLES_Z: cl.viewangles[2] = k; break; case VF_PERSPECTIVE: r_refdef.view.useperspective = k != 0; break; case VF_CLEARSCREEN: r_refdef.view.isoverlay = !k; break; case VF_MAINVIEW: PRVM_G_FLOAT(OFS_RETURN) = r_refdef.view.ismain; break; case VF_FOG_DENSITY: r_refdef.fog_density = k; break; case VF_FOG_COLOR: r_refdef.fog_red = f[0]; r_refdef.fog_green = f[1]; r_refdef.fog_blue = f[2]; break; case VF_FOG_COLOR_R: r_refdef.fog_red = k; break; case VF_FOG_COLOR_G: r_refdef.fog_green = k; break; case VF_FOG_COLOR_B: r_refdef.fog_blue = k; break; case VF_FOG_ALPHA: r_refdef.fog_alpha = k; break; case VF_FOG_START: r_refdef.fog_start = k; break; case VF_FOG_END: r_refdef.fog_end = k; break; case VF_FOG_HEIGHT: r_refdef.fog_height = k; break; case VF_FOG_FADEDEPTH: r_refdef.fog_fadedepth = k; break; case VF_MINFPS_QUALITY: r_refdef.view.quality = k; break; default: PRVM_G_FLOAT(OFS_RETURN) = 0; VM_Warning(prog, "VM_CL_R_SetView : unknown parm %i\n", c); return; } PRVM_G_FLOAT(OFS_RETURN) = 1; } //#305 void(vector org, float radius, vector lightcolours[, float style, string cubemapname, float pflags]) adddynamiclight (EXT_CSQC) static void VM_CL_R_AddDynamicLight (prvm_prog_t *prog) { double t = Sys_DirtyTime(); vec3_t org; float radius = 300; vec3_t col; int style = -1; const char *cubemapname = NULL; int pflags = PFLAGS_CORONA | PFLAGS_FULLDYNAMIC; float coronaintensity = 1; float coronasizescale = 0.25; qboolean castshadow = true; float ambientscale = 0; float diffusescale = 1; float specularscale = 1; matrix4x4_t matrix; vec3_t forward, left, up; VM_SAFEPARMCOUNTRANGE(3, 8, VM_CL_R_AddDynamicLight); // if we've run out of dlights, just return if (r_refdef.scene.numlights >= MAX_DLIGHTS) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); radius = PRVM_G_FLOAT(OFS_PARM1); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), col); if (prog->argc >= 4) { style = (int)PRVM_G_FLOAT(OFS_PARM3); if (style >= MAX_LIGHTSTYLES) { Con_DPrintf("VM_CL_R_AddDynamicLight: out of bounds lightstyle index %i\n", style); style = -1; } } if (prog->argc >= 5) cubemapname = PRVM_G_STRING(OFS_PARM4); if (prog->argc >= 6) pflags = (int)PRVM_G_FLOAT(OFS_PARM5); coronaintensity = (pflags & PFLAGS_CORONA) != 0; castshadow = (pflags & PFLAGS_NOSHADOW) == 0; VectorScale(PRVM_clientglobalvector(v_forward), radius, forward); VectorScale(PRVM_clientglobalvector(v_right), -radius, left); VectorScale(PRVM_clientglobalvector(v_up), radius, up); Matrix4x4_FromVectors(&matrix, forward, left, up, org); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &matrix, col, style, cubemapname, castshadow, coronaintensity, coronasizescale, ambientscale, diffusescale, specularscale, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; } //============================================================================ //#310 vector (vector v) cs_unproject (EXT_CSQC) static void VM_CL_unproject (prvm_prog_t *prog) { vec3_t f; vec3_t temp; vec3_t result; VM_SAFEPARMCOUNT(1, VM_CL_unproject); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), f); VectorSet(temp, f[2], (-1.0 + 2.0 * (f[0] / vid_conwidth.integer)) * f[2] * -r_refdef.view.frustum_x, (-1.0 + 2.0 * (f[1] / vid_conheight.integer)) * f[2] * -r_refdef.view.frustum_y); if(v_flipped.integer) temp[1] = -temp[1]; Matrix4x4_Transform(&r_refdef.view.matrix, temp, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); } //#311 vector (vector v) cs_project (EXT_CSQC) static void VM_CL_project (prvm_prog_t *prog) { vec3_t f; vec3_t v; matrix4x4_t m; VM_SAFEPARMCOUNT(1, VM_CL_project); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), f); Matrix4x4_Invert_Full(&m, &r_refdef.view.matrix); Matrix4x4_Transform(&m, f, v); if(v_flipped.integer) v[1] = -v[1]; VectorSet(PRVM_G_VECTOR(OFS_RETURN), vid_conwidth.integer * (0.5*(1.0+v[1]/v[0]/-r_refdef.view.frustum_x)), vid_conheight.integer * (0.5*(1.0+v[2]/v[0]/-r_refdef.view.frustum_y)), v[0]); // explanation: // after transforming, relative position to viewport (0..1) = 0.5 * (1 + v[2]/v[0]/-frustum_{x \or y}) // as 2D drawing honors the viewport too, to get the same pixel, we simply multiply this by conwidth/height } //#330 float(float stnum) getstatf (EXT_CSQC) static void VM_CL_getstatf (prvm_prog_t *prog) { int i; union { float f; int l; }dat; VM_SAFEPARMCOUNT(1, VM_CL_getstatf); i = (int)PRVM_G_FLOAT(OFS_PARM0); if(i < 0 || i >= MAX_CL_STATS) { VM_Warning(prog, "VM_CL_getstatf: index>=MAX_CL_STATS or index<0\n"); return; } dat.l = cl.stats[i]; PRVM_G_FLOAT(OFS_RETURN) = dat.f; } //#331 float(float stnum) getstati (EXT_CSQC) static void VM_CL_getstati (prvm_prog_t *prog) { int i, index; int firstbit, bitcount; VM_SAFEPARMCOUNTRANGE(1, 3, VM_CL_getstati); index = (int)PRVM_G_FLOAT(OFS_PARM0); if (prog->argc > 1) { firstbit = (int)PRVM_G_FLOAT(OFS_PARM1); if (prog->argc > 2) bitcount = (int)PRVM_G_FLOAT(OFS_PARM2); else bitcount = 1; } else { firstbit = 0; bitcount = 32; } if(index < 0 || index >= MAX_CL_STATS) { VM_Warning(prog, "VM_CL_getstati: index>=MAX_CL_STATS or index<0\n"); return; } i = cl.stats[index]; if (bitcount != 32) //32 causes the mask to overflow, so there's nothing to subtract from. i = (((unsigned int)i)&(((1<>firstbit; PRVM_G_FLOAT(OFS_RETURN) = i; } //#332 string(float firststnum) getstats (EXT_CSQC) static void VM_CL_getstats (prvm_prog_t *prog) { int i; char t[17]; VM_SAFEPARMCOUNT(1, VM_CL_getstats); i = (int)PRVM_G_FLOAT(OFS_PARM0); if(i < 0 || i > MAX_CL_STATS-4) { PRVM_G_INT(OFS_RETURN) = OFS_NULL; VM_Warning(prog, "VM_CL_getstats: index>MAX_CL_STATS-4 or index<0\n"); return; } strlcpy(t, (char*)&cl.stats[i], sizeof(t)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); } //#333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) static void VM_CL_setmodelindex (prvm_prog_t *prog) { int i; prvm_edict_t *t; struct model_s *model; VM_SAFEPARMCOUNT(2, VM_CL_setmodelindex); t = PRVM_G_EDICT(OFS_PARM0); i = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_clientedictstring(t, model) = 0; PRVM_clientedictfloat(t, modelindex) = 0; if (!i) return; model = CL_GetModelByIndex(i); if (!model) { VM_Warning(prog, "VM_CL_setmodelindex: null model\n"); return; } PRVM_clientedictstring(t, model) = PRVM_SetEngineString(prog, model->name); PRVM_clientedictfloat(t, modelindex) = i; // TODO: check if this breaks needed consistency and maybe add a cvar for it too?? [1/10/2008 Black] if (model) { SetMinMaxSize (prog, t, model->normalmins, model->normalmaxs); } else SetMinMaxSize (prog, t, vec3_origin, vec3_origin); } //#334 string(float mdlindex) modelnameforindex (EXT_CSQC) static void VM_CL_modelnameforindex (prvm_prog_t *prog) { dp_model_t *model; VM_SAFEPARMCOUNT(1, VM_CL_modelnameforindex); PRVM_G_INT(OFS_RETURN) = OFS_NULL; model = CL_GetModelByIndex((int)PRVM_G_FLOAT(OFS_PARM0)); PRVM_G_INT(OFS_RETURN) = model ? PRVM_SetEngineString(prog, model->name) : 0; } //#335 float(string effectname) particleeffectnum (EXT_CSQC) static void VM_CL_particleeffectnum (prvm_prog_t *prog) { int i; VM_SAFEPARMCOUNT(1, VM_CL_particleeffectnum); i = CL_ParticleEffectIndexForName(PRVM_G_STRING(OFS_PARM0)); if (i == 0) i = -1; PRVM_G_FLOAT(OFS_RETURN) = i; } // #336 void(entity ent, float effectnum, vector start, vector end[, float color]) trailparticles (EXT_CSQC) static void VM_CL_trailparticles (prvm_prog_t *prog) { int i; vec3_t start, end, velocity; prvm_edict_t *t; VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_trailparticles); t = PRVM_G_EDICT(OFS_PARM0); i = (int)PRVM_G_FLOAT(OFS_PARM1); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), end); VectorCopy(PRVM_clientedictvector(t, velocity), velocity); if (i < 0) return; CL_ParticleTrail(i, 1, start, end, velocity, velocity, NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0, true, true, NULL, NULL, 1); } //#337 void(float effectnum, vector origin, vector dir, float count[, float color]) pointparticles (EXT_CSQC) static void VM_CL_pointparticles (prvm_prog_t *prog) { int i; float n; vec3_t f, v; VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_pointparticles); i = (int)PRVM_G_FLOAT(OFS_PARM0); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), v); n = PRVM_G_FLOAT(OFS_PARM3); if (i < 0) return; CL_ParticleEffect(i, n, f, f, v, v, NULL, prog->argc >= 5 ? (int)PRVM_G_FLOAT(OFS_PARM4) : 0); } //#502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count, float extflags) boxparticles (DP_CSQC_BOXPARTICLES) static void VM_CL_boxparticles (prvm_prog_t *prog) { int effectnum; // prvm_edict_t *own; vec3_t origin_from, origin_to, dir_from, dir_to; float count; int flags; qboolean istrail; float tintmins[4], tintmaxs[4], fade; VM_SAFEPARMCOUNTRANGE(7, 8, VM_CL_boxparticles); effectnum = (int)PRVM_G_FLOAT(OFS_PARM0); if (effectnum < 0) return; // own = PRVM_G_EDICT(OFS_PARM1); // TODO find use for this VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin_from); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin_to ); VectorCopy(PRVM_G_VECTOR(OFS_PARM4), dir_from ); VectorCopy(PRVM_G_VECTOR(OFS_PARM5), dir_to ); count = PRVM_G_FLOAT(OFS_PARM6); if(prog->argc >= 8) flags = PRVM_G_FLOAT(OFS_PARM7); else flags = 0; Vector4Set(tintmins, 1, 1, 1, 1); Vector4Set(tintmaxs, 1, 1, 1, 1); fade = 1; istrail = false; if(flags & 1) // read alpha { tintmins[3] = PRVM_clientglobalfloat(particles_alphamin); tintmaxs[3] = PRVM_clientglobalfloat(particles_alphamax); } if(flags & 2) // read color { VectorCopy(PRVM_clientglobalvector(particles_colormin), tintmins); VectorCopy(PRVM_clientglobalvector(particles_colormax), tintmaxs); } if(flags & 4) // read fade { fade = PRVM_clientglobalfloat(particles_fade); } if(flags & 128) // draw as trail { istrail = true; } if (istrail) CL_ParticleTrail(effectnum, count, origin_from, origin_to, dir_from, dir_to, NULL, 0, true, true, tintmins, tintmaxs, fade); else CL_ParticleBox(effectnum, count, origin_from, origin_to, dir_from, dir_to, NULL, 0, true, true, tintmins, tintmaxs, fade); } //#531 void(float pause) setpause static void VM_CL_setpause(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_CL_setpause); if ((int)PRVM_G_FLOAT(OFS_PARM0) != 0) cl.csqc_paused = true; else cl.csqc_paused = false; } //#343 void(float usecursor) setcursormode (DP_CSQC) static void VM_CL_setcursormode (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_CL_setcursormode); cl.csqc_wantsmousemove = PRVM_G_FLOAT(OFS_PARM0) != 0; cl_ignoremousemoves = 2; } //#344 vector() getmousepos (DP_CSQC) static void VM_CL_getmousepos(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_CL_getmousepos); if (key_consoleactive || key_dest != key_game) VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); else if (cl.csqc_wantsmousemove) VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); else VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); } //#345 float(float framenum) getinputstate (EXT_CSQC) static void VM_CL_getinputstate (prvm_prog_t *prog) { unsigned int i, frame; VM_SAFEPARMCOUNT(1, VM_CL_getinputstate); frame = (unsigned int)PRVM_G_FLOAT(OFS_PARM0); PRVM_G_FLOAT(OFS_RETURN) = false; for (i = 0;i < CL_MAX_USERCMDS;i++) { if (cl.movecmd[i].sequence == frame) { VectorCopy(cl.movecmd[i].viewangles, PRVM_clientglobalvector(input_angles)); PRVM_clientglobalfloat(input_buttons) = cl.movecmd[i].buttons; // FIXME: this should not be directly exposed to csqc (translation layer needed?) PRVM_clientglobalvector(input_movevalues)[0] = cl.movecmd[i].forwardmove; PRVM_clientglobalvector(input_movevalues)[1] = cl.movecmd[i].sidemove; PRVM_clientglobalvector(input_movevalues)[2] = cl.movecmd[i].upmove; PRVM_clientglobalfloat(input_timelength) = cl.movecmd[i].frametime; // this probably shouldn't be here if(cl.movecmd[i].crouch) { VectorCopy(cl.playercrouchmins, PRVM_clientglobalvector(pmove_mins)); VectorCopy(cl.playercrouchmaxs, PRVM_clientglobalvector(pmove_maxs)); } else { VectorCopy(cl.playerstandmins, PRVM_clientglobalvector(pmove_mins)); VectorCopy(cl.playerstandmaxs, PRVM_clientglobalvector(pmove_maxs)); } PRVM_G_FLOAT(OFS_RETURN) = true; } } } //#346 void(float sens) setsensitivityscaler (EXT_CSQC) static void VM_CL_setsensitivityscale (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_CL_setsensitivityscale); cl.sensitivityscale = PRVM_G_FLOAT(OFS_PARM0); } //#347 void() runstandardplayerphysics (EXT_CSQC) #define PMF_JUMP_HELD 1 // matches FTEQW #define PMF_LADDER 2 // not used by DP, FTEQW sets this in runplayerphysics but does not read it #define PMF_DUCKED 4 // FIXME FTEQW doesn't have this for Q1 like movement because Q1 cannot crouch #define PMF_ONGROUND 8 // FIXME FTEQW doesn't have this for Q1 like movement and expects CSQC code to do its own trace, this is stupid CPU waste static void VM_CL_runplayerphysics (prvm_prog_t *prog) { cl_clientmovement_state_t s; prvm_edict_t *ent; memset(&s, 0, sizeof(s)); VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_runplayerphysics); ent = (prog->argc == 1 ? PRVM_G_EDICT(OFS_PARM0) : prog->edicts); if(ent == prog->edicts) { // deprecated use s.self = NULL; VectorCopy(PRVM_clientglobalvector(pmove_org), s.origin); VectorCopy(PRVM_clientglobalvector(pmove_vel), s.velocity); VectorCopy(PRVM_clientglobalvector(pmove_mins), s.mins); VectorCopy(PRVM_clientglobalvector(pmove_maxs), s.maxs); s.crouched = 0; s.waterjumptime = PRVM_clientglobalfloat(pmove_waterjumptime); s.cmd.canjump = (int)PRVM_clientglobalfloat(pmove_jump_held) == 0; } else { // new use s.self = ent; VectorCopy(PRVM_clientedictvector(ent, origin), s.origin); VectorCopy(PRVM_clientedictvector(ent, velocity), s.velocity); VectorCopy(PRVM_clientedictvector(ent, mins), s.mins); VectorCopy(PRVM_clientedictvector(ent, maxs), s.maxs); s.crouched = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_DUCKED) != 0; s.waterjumptime = 0; // FIXME where do we get this from? FTEQW lacks support for this too s.cmd.canjump = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_JUMP_HELD) == 0; } VectorCopy(PRVM_clientglobalvector(input_angles), s.cmd.viewangles); s.cmd.forwardmove = PRVM_clientglobalvector(input_movevalues)[0]; s.cmd.sidemove = PRVM_clientglobalvector(input_movevalues)[1]; s.cmd.upmove = PRVM_clientglobalvector(input_movevalues)[2]; s.cmd.buttons = PRVM_clientglobalfloat(input_buttons); s.cmd.frametime = PRVM_clientglobalfloat(input_timelength); s.cmd.jump = (s.cmd.buttons & 2) != 0; s.cmd.crouch = (s.cmd.buttons & 16) != 0; CL_ClientMovement_PlayerMove_Frame(&s); if(ent == prog->edicts) { // deprecated use VectorCopy(s.origin, PRVM_clientglobalvector(pmove_org)); VectorCopy(s.velocity, PRVM_clientglobalvector(pmove_vel)); PRVM_clientglobalfloat(pmove_jump_held) = !s.cmd.canjump; PRVM_clientglobalfloat(pmove_waterjumptime) = s.waterjumptime; } else { // new use VectorCopy(s.origin, PRVM_clientedictvector(ent, origin)); VectorCopy(s.velocity, PRVM_clientedictvector(ent, velocity)); PRVM_clientedictfloat(ent, pmove_flags) = (s.crouched ? PMF_DUCKED : 0) | (s.cmd.canjump ? 0 : PMF_JUMP_HELD) | (s.onground ? PMF_ONGROUND : 0); } } //#348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) static void VM_CL_getplayerkey (prvm_prog_t *prog) { int i; char t[128]; const char *c; VM_SAFEPARMCOUNT(2, VM_CL_getplayerkey); i = (int)PRVM_G_FLOAT(OFS_PARM0); c = PRVM_G_STRING(OFS_PARM1); PRVM_G_INT(OFS_RETURN) = OFS_NULL; Sbar_SortFrags(); if (i < 0) i = Sbar_GetSortedPlayerIndex(-1-i); if(i < 0 || i >= cl.maxclients) return; t[0] = 0; if(!strcasecmp(c, "name")) strlcpy(t, cl.scores[i].name, sizeof(t)); else if(!strcasecmp(c, "frags")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].frags); else if(!strcasecmp(c, "ping")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_ping); else if(!strcasecmp(c, "pl")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_packetloss); else if(!strcasecmp(c, "movementloss")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_movementloss); else if(!strcasecmp(c, "entertime")) dpsnprintf(t, sizeof(t), "%f", cl.scores[i].qw_entertime); else if(!strcasecmp(c, "colors")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors); else if(!strcasecmp(c, "topcolor")) dpsnprintf(t, sizeof(t), "%i", cl.scores[i].colors & 0xf0); else if(!strcasecmp(c, "bottomcolor")) dpsnprintf(t, sizeof(t), "%i", (cl.scores[i].colors &15)<<4); else if(!strcasecmp(c, "viewentity")) dpsnprintf(t, sizeof(t), "%i", i+1); if(!t[0]) return; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); } //#351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) static void VM_CL_setlistener (prvm_prog_t *prog) { vec3_t origin, forward, left, up; VM_SAFEPARMCOUNT(4, VM_CL_setlistener); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), origin); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), forward); VectorNegate(PRVM_G_VECTOR(OFS_PARM2), left); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), up); Matrix4x4_FromVectors(&cl.csqc_listenermatrix, forward, left, up, origin); cl.csqc_usecsqclistener = true; //use csqc listener at this frame } //#352 void(string cmdname) registercommand (EXT_CSQC) static void VM_CL_registercmd (prvm_prog_t *prog) { char *t; VM_SAFEPARMCOUNT(1, VM_CL_registercmd); if(!Cmd_Exists(PRVM_G_STRING(OFS_PARM0))) { size_t alloclen; alloclen = strlen(PRVM_G_STRING(OFS_PARM0)) + 1; t = (char *)Z_Malloc(alloclen); memcpy(t, PRVM_G_STRING(OFS_PARM0), alloclen); Cmd_AddCommand(t, NULL, "console command created by QuakeC"); } else Cmd_AddCommand(PRVM_G_STRING(OFS_PARM0), NULL, "console command created by QuakeC"); } //#360 float() readbyte (EXT_CSQC) static void VM_CL_ReadByte (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadByte); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadByte(&cl_message); } //#361 float() readchar (EXT_CSQC) static void VM_CL_ReadChar (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadChar); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadChar(&cl_message); } //#362 float() readshort (EXT_CSQC) static void VM_CL_ReadShort (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadShort); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadShort(&cl_message); } //#363 float() readlong (EXT_CSQC) static void VM_CL_ReadLong (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadLong); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadLong(&cl_message); } //#364 float() readcoord (EXT_CSQC) static void VM_CL_ReadCoord (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadCoord); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadCoord(&cl_message, cls.protocol); } //#365 float() readangle (EXT_CSQC) static void VM_CL_ReadAngle (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadAngle); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadAngle(&cl_message, cls.protocol); } //#366 string() readstring (EXT_CSQC) static void VM_CL_ReadString (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadString); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); } //#367 float() readfloat (EXT_CSQC) static void VM_CL_ReadFloat (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ReadFloat); PRVM_G_FLOAT(OFS_RETURN) = MSG_ReadFloat(&cl_message); } //#501 string() readpicture (DP_CSQC_READWRITEPICTURE) extern cvar_t cl_readpicture_force; static void VM_CL_ReadPicture (prvm_prog_t *prog) { const char *name; unsigned char *data; unsigned char *buf; unsigned short size; int i; cachepic_t *pic; VM_SAFEPARMCOUNT(0, VM_CL_ReadPicture); name = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); size = (unsigned short) MSG_ReadShort(&cl_message); // check if a texture of that name exists // if yes, it is used and the data is discarded // if not, the (low quality) data is used to build a new texture, whose name will get returned pic = Draw_CachePic_Flags (name, CACHEPICFLAG_NOTPERSISTENT); if(size) { if(pic->tex == r_texture_notexture) pic->tex = NULL; // don't overwrite the notexture by Draw_NewPic if(pic->tex && !cl_readpicture_force.integer) { // texture found and loaded // skip over the jpeg as we don't need it for(i = 0; i < size; ++i) (void) MSG_ReadByte(&cl_message); } else { // texture not found // use the attached jpeg as texture buf = (unsigned char *) Mem_Alloc(tempmempool, size); MSG_ReadBytes(&cl_message, size, buf); data = JPEG_LoadImage_BGRA(buf, size, NULL); Mem_Free(buf); Draw_NewPic(name, image_width, image_height, false, data); Mem_Free(data); } } PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, name); } ////////////////////////////////////////////////////////// static void VM_CL_makestatic (prvm_prog_t *prog) { prvm_edict_t *ent; VM_SAFEPARMCOUNT(1, VM_CL_makestatic); ent = PRVM_G_EDICT(OFS_PARM0); if (ent == prog->edicts) { VM_Warning(prog, "makestatic: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "makestatic: can not modify free entity\n"); return; } if (cl.num_static_entities < cl.max_static_entities) { int renderflags; entity_t *staticent = &cl.static_entities[cl.num_static_entities++]; // copy it to the current state memset(staticent, 0, sizeof(*staticent)); staticent->render.model = CL_GetModelByIndex((int)PRVM_clientedictfloat(ent, modelindex)); staticent->render.framegroupblend[0].frame = (int)PRVM_clientedictfloat(ent, frame); staticent->render.framegroupblend[0].lerp = 1; // make torchs play out of sync staticent->render.framegroupblend[0].start = lhrandom(-10, -1); staticent->render.skinnum = (int)PRVM_clientedictfloat(ent, skin); staticent->render.effects = (int)PRVM_clientedictfloat(ent, effects); staticent->render.alpha = PRVM_clientedictfloat(ent, alpha); staticent->render.scale = PRVM_clientedictfloat(ent, scale); VectorCopy(PRVM_clientedictvector(ent, colormod), staticent->render.colormod); VectorCopy(PRVM_clientedictvector(ent, glowmod), staticent->render.glowmod); // sanitize values if (!staticent->render.alpha) staticent->render.alpha = 1.0f; if (!staticent->render.scale) staticent->render.scale = 1.0f; if (!VectorLength2(staticent->render.colormod)) VectorSet(staticent->render.colormod, 1, 1, 1); if (!VectorLength2(staticent->render.glowmod)) VectorSet(staticent->render.glowmod, 1, 1, 1); renderflags = (int)PRVM_clientedictfloat(ent, renderflags); if (renderflags & RF_USEAXIS) { vec3_t forward, left, up, origin; VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); VectorCopy(PRVM_clientedictvector(ent, origin), origin); Matrix4x4_FromVectors(&staticent->render.matrix, forward, left, up, origin); Matrix4x4_Scale(&staticent->render.matrix, staticent->render.scale, 1); } else Matrix4x4_CreateFromQuakeEntity(&staticent->render.matrix, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], staticent->render.scale); // either fullbright or lit if(!r_fullbright.integer) { if (!(staticent->render.effects & EF_FULLBRIGHT)) staticent->render.flags |= RENDER_LIGHT; else if(r_equalize_entities_fullbright.integer) staticent->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE; } // turn off shadows from transparent objects if (!(staticent->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (staticent->render.alpha >= 1)) staticent->render.flags |= RENDER_SHADOW; if (staticent->render.effects & EF_NODEPTHTEST) staticent->render.flags |= RENDER_NODEPTHTEST; if (staticent->render.effects & EF_ADDITIVE) staticent->render.flags |= RENDER_ADDITIVE; if (staticent->render.effects & EF_DOUBLESIDED) staticent->render.flags |= RENDER_DOUBLESIDED; staticent->render.allowdecals = true; CL_UpdateRenderEntity(&staticent->render); } else Con_Printf("Too many static entities"); // throw the entity away now PRVM_ED_Free(prog, ent); } //=================================================================// /* ================= VM_CL_copyentity copies data from one entity to another copyentity(src, dst) ================= */ static void VM_CL_copyentity (prvm_prog_t *prog) { prvm_edict_t *in, *out; VM_SAFEPARMCOUNT(2, VM_CL_copyentity); in = PRVM_G_EDICT(OFS_PARM0); if (in == prog->edicts) { VM_Warning(prog, "copyentity: can not read world entity\n"); return; } if (in->priv.server->free) { VM_Warning(prog, "copyentity: can not read free entity\n"); return; } out = PRVM_G_EDICT(OFS_PARM1); if (out == prog->edicts) { VM_Warning(prog, "copyentity: can not modify world entity\n"); return; } if (out->priv.server->free) { VM_Warning(prog, "copyentity: can not modify free entity\n"); return; } memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); CL_LinkEdict(out); } //=================================================================// // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) static void VM_CL_effect (prvm_prog_t *prog) { #if 1 Con_Printf("WARNING: VM_CL_effect not implemented\n"); // FIXME: this needs to take modelname not modelindex, the csqc defs has it as string and so it shall be #else vec3_t org; VM_SAFEPARMCOUNT(5, VM_CL_effect); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); CL_Effect(org, (int)PRVM_G_FLOAT(OFS_PARM1), (int)PRVM_G_FLOAT(OFS_PARM2), (int)PRVM_G_FLOAT(OFS_PARM3), PRVM_G_FLOAT(OFS_PARM4)); #endif } // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) static void VM_CL_te_blood (prvm_prog_t *prog) { vec3_t pos, vel, pos2; VM_SAFEPARMCOUNT(3, VM_CL_te_blood); if (PRVM_G_FLOAT(OFS_PARM2) < 1) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); } // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) static void VM_CL_te_bloodshower (prvm_prog_t *prog) { vec_t speed; vec3_t mincorner, maxcorner, vel1, vel2; VM_SAFEPARMCOUNT(4, VM_CL_te_bloodshower); if (PRVM_G_FLOAT(OFS_PARM3) < 1) return; speed = PRVM_G_FLOAT(OFS_PARM2); vel1[0] = -speed; vel1[1] = -speed; vel1[2] = -speed; vel2[0] = speed; vel2[1] = speed; vel2[2] = speed; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); CL_ParticleEffect(EFFECT_TE_BLOOD, PRVM_G_FLOAT(OFS_PARM3), mincorner, maxcorner, vel1, vel2, NULL, 0); } // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) static void VM_CL_te_explosionrgb (prvm_prog_t *prog) { vec3_t pos; vec3_t pos2; matrix4x4_t tempmatrix; VM_SAFEPARMCOUNT(2, VM_CL_te_explosionrgb); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleExplosion(pos2); Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); CL_AllocLightFlash(NULL, &tempmatrix, 350, PRVM_G_VECTOR(OFS_PARM1)[0], PRVM_G_VECTOR(OFS_PARM1)[1], PRVM_G_VECTOR(OFS_PARM1)[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) static void VM_CL_te_particlecube (prvm_prog_t *prog) { vec3_t mincorner, maxcorner, vel; VM_SAFEPARMCOUNT(7, VM_CL_te_particlecube); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); CL_ParticleCube(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), PRVM_G_FLOAT(OFS_PARM5), PRVM_G_FLOAT(OFS_PARM6)); } // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) static void VM_CL_te_particlerain (prvm_prog_t *prog) { vec3_t mincorner, maxcorner, vel; VM_SAFEPARMCOUNT(5, VM_CL_te_particlerain); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); CL_ParticleRain(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 0); } // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) static void VM_CL_te_particlesnow (prvm_prog_t *prog) { vec3_t mincorner, maxcorner, vel; VM_SAFEPARMCOUNT(5, VM_CL_te_particlesnow); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), mincorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), maxcorner); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), vel); CL_ParticleRain(mincorner, maxcorner, vel, (int)PRVM_G_FLOAT(OFS_PARM3), (int)PRVM_G_FLOAT(OFS_PARM4), 1); } // #411 void(vector org, vector vel, float howmany) te_spark static void VM_CL_te_spark (prvm_prog_t *prog) { vec3_t pos, pos2, vel; VM_SAFEPARMCOUNT(3, VM_CL_te_spark); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_SPARK, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); } extern cvar_t cl_sound_ric_gunshot; // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) static void VM_CL_te_gunshotquad (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_gunshotquad); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if(cl_sound_ric_gunshot.integer >= 2) { if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } } // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) static void VM_CL_te_spikequad (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_spikequad); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) static void VM_CL_te_superspikequad (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_superspikequad); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) static void VM_CL_te_explosionquad (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_explosionquad); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); } // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) static void VM_CL_te_smallflash (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_smallflash); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); } // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) static void VM_CL_te_customflash (prvm_prog_t *prog) { vec3_t pos, pos2; matrix4x4_t tempmatrix; VM_SAFEPARMCOUNT(4, VM_CL_te_customflash); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); CL_AllocLightFlash(NULL, &tempmatrix, PRVM_G_FLOAT(OFS_PARM1), PRVM_G_VECTOR(OFS_PARM3)[0], PRVM_G_VECTOR(OFS_PARM3)[1], PRVM_G_VECTOR(OFS_PARM3)[2], PRVM_G_FLOAT(OFS_PARM1) / PRVM_G_FLOAT(OFS_PARM2), PRVM_G_FLOAT(OFS_PARM2), 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_gunshot (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_gunshot); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if(cl_sound_ric_gunshot.integer == 1 || cl_sound_ric_gunshot.integer == 3) { if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } } // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_spike (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_spike); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_superspike (prvm_prog_t *prog) { vec3_t pos, pos2; int rnd; VM_SAFEPARMCOUNT(1, VM_CL_te_superspike); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos2, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos2, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos2, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos2, 1, 1); } } // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_explosion (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_explosion); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); } // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_tarexplosion (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_tarexplosion); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); } // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_wizspike (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_wizspike); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_wizhit, pos2, 1, 1); } // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_knightspike (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_knightspike); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_knighthit, pos2, 1, 1); } // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_lavasplash (prvm_prog_t *prog) { vec3_t pos; VM_SAFEPARMCOUNT(1, VM_CL_te_lavasplash); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); } // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_teleport (prvm_prog_t *prog) { vec3_t pos; VM_SAFEPARMCOUNT(1, VM_CL_te_teleport); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); } // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_explosion2 (prvm_prog_t *prog) { vec3_t pos, pos2, color; matrix4x4_t tempmatrix; int colorStart, colorLength; unsigned char *tempcolor; VM_SAFEPARMCOUNT(3, VM_CL_te_explosion2); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); colorStart = (int)PRVM_G_FLOAT(OFS_PARM1); colorLength = (int)PRVM_G_FLOAT(OFS_PARM2); CL_FindNonSolidLocation(pos, pos2, 10); CL_ParticleExplosion2(pos2, colorStart, colorLength); tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; color[0] = tempcolor[0] * (2.0f / 255.0f); color[1] = tempcolor[1] * (2.0f / 255.0f); color[2] = tempcolor[2] * (2.0f / 255.0f); Matrix4x4_CreateTranslate(&tempmatrix, pos2[0], pos2[1], pos2[2]); CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); S_StartSound(-1, 0, cl.sfx_r_exp3, pos2, 1, 1); } // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_lightning1 (prvm_prog_t *prog) { vec3_t start, end; VM_SAFEPARMCOUNT(3, VM_CL_te_lightning1); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt, true); } // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_lightning2 (prvm_prog_t *prog) { vec3_t start, end; VM_SAFEPARMCOUNT(3, VM_CL_te_lightning2); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt2, true); } // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_lightning3 (prvm_prog_t *prog) { vec3_t start, end; VM_SAFEPARMCOUNT(3, VM_CL_te_lightning3); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_bolt3, false); } // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) static void VM_CL_te_beam (prvm_prog_t *prog) { vec3_t start, end; VM_SAFEPARMCOUNT(3, VM_CL_te_beam); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), start); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), end); CL_NewBeam(PRVM_G_EDICTNUM(OFS_PARM0), start, end, cl.model_beam, false); } // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) static void VM_CL_te_plasmaburn (prvm_prog_t *prog) { vec3_t pos, pos2; VM_SAFEPARMCOUNT(1, VM_CL_te_plasmaburn); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos2, pos2, vec3_origin, vec3_origin, NULL, 0); } // #457 void(vector org, vector velocity, float howmany) te_flamejet (DP_TE_FLAMEJET) static void VM_CL_te_flamejet (prvm_prog_t *prog) { vec3_t pos, pos2, vel; VM_SAFEPARMCOUNT(3, VM_CL_te_flamejet); if (PRVM_G_FLOAT(OFS_PARM2) < 1) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM0), pos); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), vel); CL_FindNonSolidLocation(pos, pos2, 4); CL_ParticleEffect(EFFECT_TE_FLAMEJET, PRVM_G_FLOAT(OFS_PARM2), pos2, pos2, vel, vel, NULL, 0); } // #443 void(entity e, entity tagentity, string tagname) setattachment static void VM_CL_setattachment (prvm_prog_t *prog) { prvm_edict_t *e; prvm_edict_t *tagentity; const char *tagname; int modelindex; int tagindex; dp_model_t *model; VM_SAFEPARMCOUNT(3, VM_CL_setattachment); e = PRVM_G_EDICT(OFS_PARM0); tagentity = PRVM_G_EDICT(OFS_PARM1); tagname = PRVM_G_STRING(OFS_PARM2); if (e == prog->edicts) { VM_Warning(prog, "setattachment: can not modify world entity\n"); return; } if (e->priv.server->free) { VM_Warning(prog, "setattachment: can not modify free entity\n"); return; } if (tagentity == NULL) tagentity = prog->edicts; tagindex = 0; if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0]) { modelindex = (int)PRVM_clientedictfloat(tagentity, modelindex); model = CL_GetModelByIndex(modelindex); if (model) { tagindex = Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(tagentity, skin), tagname); if (tagindex == 0) Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i (model \"%s\") but could not find it\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity), model->name); } else Con_DPrintf("setattachment(edict %i, edict %i, string \"%s\"): tried to find tag named \"%s\" on entity %i but it has no model\n", PRVM_NUM_FOR_EDICT(e), PRVM_NUM_FOR_EDICT(tagentity), tagname, tagname, PRVM_NUM_FOR_EDICT(tagentity)); } PRVM_clientedictedict(e, tag_entity) = PRVM_EDICT_TO_PROG(tagentity); PRVM_clientedictfloat(e, tag_index) = tagindex; } ///////////////////////////////////////// // DP_MD3_TAGINFO extension coded by VorteX static int CL_GetTagIndex (prvm_prog_t *prog, prvm_edict_t *e, const char *tagname) { dp_model_t *model = CL_GetModelFromEdict(e); if (model) return Mod_Alias_GetTagIndexForName(model, (int)PRVM_clientedictfloat(e, skin), tagname); else return -1; } static int CL_GetExtendedTagInfo (prvm_prog_t *prog, prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) { int r; dp_model_t *model; *tagname = NULL; *parentindex = 0; Matrix4x4_CreateIdentity(tag_localmatrix); if (tagindex >= 0 && (model = CL_GetModelFromEdict(e)) && model->animscenes) { r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)PRVM_clientedictfloat(e, skin), e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix); if(!r) // success? *parentindex += 1; return r; } return 1; } int CL_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent) { dp_model_t *model; if ((model = CL_GetModelFromEdict(ent)) && model->type == mod_alias) return -1; return 1; } void CL_GetEntityMatrix (prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix) { float scale; float pitchsign = 1; scale = PRVM_clientedictfloat(ent, scale); if (!scale) scale = 1.0f; if(viewmatrix) *out = r_refdef.view.matrix; else if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_USEAXIS) { vec3_t forward; vec3_t left; vec3_t up; vec3_t origin; VectorScale(PRVM_clientglobalvector(v_forward), scale, forward); VectorScale(PRVM_clientglobalvector(v_right), -scale, left); VectorScale(PRVM_clientglobalvector(v_up), scale, up); VectorCopy(PRVM_clientedictvector(ent, origin), origin); Matrix4x4_FromVectors(out, forward, left, up, origin); } else { pitchsign = CL_GetPitchSign(prog, ent); Matrix4x4_CreateFromQuakeEntity(out, PRVM_clientedictvector(ent, origin)[0], PRVM_clientedictvector(ent, origin)[1], PRVM_clientedictvector(ent, origin)[2], pitchsign * PRVM_clientedictvector(ent, angles)[0], PRVM_clientedictvector(ent, angles)[1], PRVM_clientedictvector(ent, angles)[2], scale); } } static int CL_GetEntityLocalTagMatrix(prvm_prog_t *prog, prvm_edict_t *ent, int tagindex, matrix4x4_t *out) { dp_model_t *model; if (tagindex >= 0 && (model = CL_GetModelFromEdict(ent)) && model->animscenes) { VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, cl.time); VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out); } *out = identitymatrix; return 0; } // Warnings/errors code: // 0 - normal (everything all-right) // 1 - world entity // 2 - free entity // 3 - null or non-precached model // 4 - no tags with requested index // 5 - runaway loop at attachment chain extern cvar_t cl_bob; extern cvar_t cl_bobcycle; extern cvar_t cl_bobup; int CL_GetTagMatrix (prvm_prog_t *prog, matrix4x4_t *out, prvm_edict_t *ent, int tagindex) { int ret; int attachloop; matrix4x4_t entitymatrix, tagmatrix, attachmatrix; dp_model_t *model; *out = identitymatrix; // warnings and errors return identical matrix if (ent == prog->edicts) return 1; if (ent->priv.server->free) return 2; model = CL_GetModelFromEdict(ent); if(!model) return 3; tagmatrix = identitymatrix; attachloop = 0; for(;;) { if(attachloop >= 256) return 5; // apply transformation by child's tagindex on parent entity and then // by parent entity itself ret = CL_GetEntityLocalTagMatrix(prog, ent, tagindex - 1, &attachmatrix); if(ret && attachloop == 0) return ret; CL_GetEntityMatrix(prog, ent, &entitymatrix, false); Matrix4x4_Concat(&tagmatrix, &attachmatrix, out); Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); // next iteration we process the parent entity if (PRVM_clientedictedict(ent, tag_entity)) { tagindex = (int)PRVM_clientedictfloat(ent, tag_index); ent = PRVM_EDICT_NUM(PRVM_clientedictedict(ent, tag_entity)); } else break; attachloop++; } // RENDER_VIEWMODEL magic if ((int)PRVM_clientedictfloat(ent, renderflags) & RF_VIEWMODEL) { Matrix4x4_Copy(&tagmatrix, out); CL_GetEntityMatrix(prog, prog->edicts, &entitymatrix, true); Matrix4x4_Concat(out, &entitymatrix, &tagmatrix); /* // Cl_bob, ported from rendering code if (PRVM_clientedictfloat(ent, health) > 0 && cl_bob.value && cl_bobcycle.value) { double bob, cycle; // LordHavoc: this code is *weird*, but not replacable (I think it // should be done in QC on the server, but oh well, quake is quake) // LordHavoc: figured out bobup: the time at which the sin is at 180 // degrees (which allows lengthening or squishing the peak or valley) cycle = cl.time/cl_bobcycle.value; cycle -= (int)cycle; if (cycle < cl_bobup.value) cycle = sin(M_PI * cycle / cl_bobup.value); else cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value)); // bob is proportional to velocity in the xy plane // (don't count Z, or jumping messes it up) bob = sqrt(PRVM_clientedictvector(ent, velocity)[0]*PRVM_clientedictvector(ent, velocity)[0] + PRVM_clientedictvector(ent, velocity)[1]*PRVM_clientedictvector(ent, velocity)[1])*cl_bob.value; bob = bob*0.3 + bob*0.7*cycle; Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4)); } */ } return 0; } // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) static void VM_CL_gettagindex (prvm_prog_t *prog) { prvm_edict_t *ent; const char *tag_name; int tag_index; VM_SAFEPARMCOUNT(2, VM_CL_gettagindex); ent = PRVM_G_EDICT(OFS_PARM0); tag_name = PRVM_G_STRING(OFS_PARM1); if (ent == prog->edicts) { VM_Warning(prog, "VM_CL_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent)); return; } if (ent->priv.server->free) { VM_Warning(prog, "VM_CL_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent)); return; } tag_index = 0; if (!CL_GetModelFromEdict(ent)) Con_DPrintf("VM_CL_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent)); else { tag_index = CL_GetTagIndex(prog, ent, tag_name); if (tag_index == 0) if(developer_extra.integer) Con_DPrintf("VM_CL_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name); } PRVM_G_FLOAT(OFS_RETURN) = tag_index; } // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) static void VM_CL_gettaginfo (prvm_prog_t *prog) { prvm_edict_t *e; int tagindex; matrix4x4_t tag_matrix; matrix4x4_t tag_localmatrix; int parentindex; const char *tagname; int returncode; vec3_t forward, left, up, origin; const dp_model_t *model; VM_SAFEPARMCOUNT(2, VM_CL_gettaginfo); e = PRVM_G_EDICT(OFS_PARM0); tagindex = (int)PRVM_G_FLOAT(OFS_PARM1); returncode = CL_GetTagMatrix(prog, &tag_matrix, e, tagindex); Matrix4x4_ToVectors(&tag_matrix, forward, left, up, origin); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorScale(left, -1, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); model = CL_GetModelFromEdict(e); VM_GenerateFrameGroupBlend(prog, e->priv.server->framegroupblend, e); VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model, cl.time); VM_UpdateEdictSkeleton(prog, e, model, e->priv.server->frameblend); CL_GetExtendedTagInfo(prog, e, tagindex, &parentindex, &tagname, &tag_localmatrix); Matrix4x4_ToVectors(&tag_localmatrix, forward, left, up, origin); PRVM_clientglobalfloat(gettaginfo_parent) = parentindex; PRVM_clientglobalstring(gettaginfo_name) = tagname ? PRVM_SetTempString(prog, tagname) : 0; VectorCopy(forward, PRVM_clientglobalvector(gettaginfo_forward)); VectorScale(left, -1, PRVM_clientglobalvector(gettaginfo_right)); VectorCopy(up, PRVM_clientglobalvector(gettaginfo_up)); VectorCopy(origin, PRVM_clientglobalvector(gettaginfo_offset)); switch(returncode) { case 1: VM_Warning(prog, "gettagindex: can't affect world entity\n"); break; case 2: VM_Warning(prog, "gettagindex: can't affect free entity\n"); break; case 3: Con_DPrintf("CL_GetTagMatrix(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(e)); break; case 4: Con_DPrintf("CL_GetTagMatrix(entity #%i): model has no tag with requested index %i\n", PRVM_NUM_FOR_EDICT(e), tagindex); break; case 5: Con_DPrintf("CL_GetTagMatrix(entity #%i): runaway loop at attachment chain\n", PRVM_NUM_FOR_EDICT(e)); break; } } //============================================================================ //==================== // DP_CSQC_SPAWNPARTICLE // a QC hook to engine's CL_NewParticle //==================== // particle theme struct typedef struct vmparticletheme_s { unsigned short typeindex; qboolean initialized; pblend_t blendmode; porientation_t orientation; int color1; int color2; int tex; float size; float sizeincrease; float alpha; float alphafade; float gravity; float bounce; float airfriction; float liquidfriction; float originjitter; float velocityjitter; qboolean qualityreduction; float lifetime; float stretch; int staincolor1; int staincolor2; int staintex; float stainalpha; float stainsize; float delayspawn; float delaycollision; float angle; float spin; }vmparticletheme_t; // particle spawner typedef struct vmparticlespawner_s { mempool_t *pool; qboolean initialized; qboolean verified; vmparticletheme_t *themes; int max_themes; }vmparticlespawner_t; vmparticlespawner_t vmpartspawner; // TODO: automatic max_themes grow static void VM_InitParticleSpawner (prvm_prog_t *prog, int maxthemes) { // bound max themes to not be an insane value if (maxthemes < 4) maxthemes = 4; if (maxthemes > 2048) maxthemes = 2048; // allocate and set up structure if (vmpartspawner.initialized) // reallocate { Mem_FreePool(&vmpartspawner.pool); memset(&vmpartspawner, 0, sizeof(vmparticlespawner_t)); } vmpartspawner.pool = Mem_AllocPool("VMPARTICLESPAWNER", 0, NULL); vmpartspawner.themes = (vmparticletheme_t *)Mem_Alloc(vmpartspawner.pool, sizeof(vmparticletheme_t)*maxthemes); vmpartspawner.max_themes = maxthemes; vmpartspawner.initialized = true; vmpartspawner.verified = true; } // reset particle theme to default values static void VM_ResetParticleTheme (vmparticletheme_t *theme) { theme->initialized = true; theme->typeindex = pt_static; theme->blendmode = PBLEND_ADD; theme->orientation = PARTICLE_BILLBOARD; theme->color1 = 0x808080; theme->color2 = 0xFFFFFF; theme->tex = 63; theme->size = 2; theme->sizeincrease = 0; theme->alpha = 256; theme->alphafade = 512; theme->gravity = 0.0f; theme->bounce = 0.0f; theme->airfriction = 1.0f; theme->liquidfriction = 4.0f; theme->originjitter = 0.0f; theme->velocityjitter = 0.0f; theme->qualityreduction = false; theme->lifetime = 4; theme->stretch = 1; theme->staincolor1 = -1; theme->staincolor2 = -1; theme->staintex = -1; theme->delayspawn = 0.0f; theme->delaycollision = 0.0f; theme->angle = 0.0f; theme->spin = 0.0f; } // particle theme -> QC globals static void VM_CL_ParticleThemeToGlobals(vmparticletheme_t *theme, prvm_prog_t *prog) { PRVM_clientglobalfloat(particle_type) = theme->typeindex; PRVM_clientglobalfloat(particle_blendmode) = theme->blendmode; PRVM_clientglobalfloat(particle_orientation) = theme->orientation; // VorteX: int only can store 0-255, not 0-256 which means 0 - 0,99609375... VectorSet(PRVM_clientglobalvector(particle_color1), (theme->color1 >> 16) & 0xFF, (theme->color1 >> 8) & 0xFF, (theme->color1 >> 0) & 0xFF); VectorSet(PRVM_clientglobalvector(particle_color2), (theme->color2 >> 16) & 0xFF, (theme->color2 >> 8) & 0xFF, (theme->color2 >> 0) & 0xFF); PRVM_clientglobalfloat(particle_tex) = (prvm_vec_t)theme->tex; PRVM_clientglobalfloat(particle_size) = theme->size; PRVM_clientglobalfloat(particle_sizeincrease) = theme->sizeincrease; PRVM_clientglobalfloat(particle_alpha) = theme->alpha/256; PRVM_clientglobalfloat(particle_alphafade) = theme->alphafade/256; PRVM_clientglobalfloat(particle_time) = theme->lifetime; PRVM_clientglobalfloat(particle_gravity) = theme->gravity; PRVM_clientglobalfloat(particle_bounce) = theme->bounce; PRVM_clientglobalfloat(particle_airfriction) = theme->airfriction; PRVM_clientglobalfloat(particle_liquidfriction) = theme->liquidfriction; PRVM_clientglobalfloat(particle_originjitter) = theme->originjitter; PRVM_clientglobalfloat(particle_velocityjitter) = theme->velocityjitter; PRVM_clientglobalfloat(particle_qualityreduction) = theme->qualityreduction; PRVM_clientglobalfloat(particle_stretch) = theme->stretch; VectorSet(PRVM_clientglobalvector(particle_staincolor1), ((int)theme->staincolor1 >> 16) & 0xFF, ((int)theme->staincolor1 >> 8) & 0xFF, ((int)theme->staincolor1 >> 0) & 0xFF); VectorSet(PRVM_clientglobalvector(particle_staincolor2), ((int)theme->staincolor2 >> 16) & 0xFF, ((int)theme->staincolor2 >> 8) & 0xFF, ((int)theme->staincolor2 >> 0) & 0xFF); PRVM_clientglobalfloat(particle_staintex) = (prvm_vec_t)theme->staintex; PRVM_clientglobalfloat(particle_stainalpha) = (prvm_vec_t)theme->stainalpha/256; PRVM_clientglobalfloat(particle_stainsize) = (prvm_vec_t)theme->stainsize; PRVM_clientglobalfloat(particle_delayspawn) = theme->delayspawn; PRVM_clientglobalfloat(particle_delaycollision) = theme->delaycollision; PRVM_clientglobalfloat(particle_angle) = theme->angle; PRVM_clientglobalfloat(particle_spin) = theme->spin; } // QC globals -> particle theme static void VM_CL_ParticleThemeFromGlobals(vmparticletheme_t *theme, prvm_prog_t *prog) { theme->typeindex = (unsigned short)PRVM_clientglobalfloat(particle_type); theme->blendmode = (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode); theme->orientation = (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation); theme->color1 = ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]); theme->color2 = ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]); theme->tex = (int)PRVM_clientglobalfloat(particle_tex); theme->size = PRVM_clientglobalfloat(particle_size); theme->sizeincrease = PRVM_clientglobalfloat(particle_sizeincrease); theme->alpha = PRVM_clientglobalfloat(particle_alpha)*256; theme->alphafade = PRVM_clientglobalfloat(particle_alphafade)*256; theme->lifetime = PRVM_clientglobalfloat(particle_time); theme->gravity = PRVM_clientglobalfloat(particle_gravity); theme->bounce = PRVM_clientglobalfloat(particle_bounce); theme->airfriction = PRVM_clientglobalfloat(particle_airfriction); theme->liquidfriction = PRVM_clientglobalfloat(particle_liquidfriction); theme->originjitter = PRVM_clientglobalfloat(particle_originjitter); theme->velocityjitter = PRVM_clientglobalfloat(particle_velocityjitter); theme->qualityreduction = PRVM_clientglobalfloat(particle_qualityreduction) != 0 ? true : false; theme->stretch = PRVM_clientglobalfloat(particle_stretch); theme->staincolor1 = ((int)PRVM_clientglobalvector(particle_staincolor1)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor1)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor1)[2]); theme->staincolor2 = (int)(PRVM_clientglobalvector(particle_staincolor2)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor2)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor2)[2]); theme->staintex =(int)PRVM_clientglobalfloat(particle_staintex); theme->stainalpha = PRVM_clientglobalfloat(particle_stainalpha)*256; theme->stainsize = PRVM_clientglobalfloat(particle_stainsize); theme->delayspawn = PRVM_clientglobalfloat(particle_delayspawn); theme->delaycollision = PRVM_clientglobalfloat(particle_delaycollision); theme->angle = PRVM_clientglobalfloat(particle_angle); theme->spin = PRVM_clientglobalfloat(particle_spin); } // init particle spawner interface // # float(float max_themes) initparticlespawner static void VM_CL_InitParticleSpawner (prvm_prog_t *prog) { VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_InitParticleSpawner); VM_InitParticleSpawner(prog, (int)PRVM_G_FLOAT(OFS_PARM0)); vmpartspawner.themes[0].initialized = true; VM_ResetParticleTheme(&vmpartspawner.themes[0]); PRVM_G_FLOAT(OFS_RETURN) = (vmpartspawner.verified == true) ? 1 : 0; } // void() resetparticle static void VM_CL_ResetParticle (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_ResetParticle); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_ResetParticle: particle spawner not initialized\n"); return; } VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); } // void(float themenum) particletheme static void VM_CL_ParticleTheme (prvm_prog_t *prog) { int themenum; VM_SAFEPARMCOUNT(1, VM_CL_ParticleTheme); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_ParticleTheme: particle spawner not initialized\n"); return; } themenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (themenum < 0 || themenum >= vmpartspawner.max_themes) { VM_Warning(prog, "VM_CL_ParticleTheme: bad theme number %i\n", themenum); VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); return; } if (vmpartspawner.themes[themenum].initialized == false) { VM_Warning(prog, "VM_CL_ParticleTheme: theme #%i not exists\n", themenum); VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); return; } // load particle theme into globals VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[themenum], prog); } // float() saveparticletheme // void(float themenum) updateparticletheme static void VM_CL_ParticleThemeSave (prvm_prog_t *prog) { int themenum; VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_ParticleThemeSave); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_ParticleThemeSave: particle spawner not initialized\n"); return; } // allocate new theme, save it and return if (prog->argc < 1) { for (themenum = 0; themenum < vmpartspawner.max_themes; themenum++) if (vmpartspawner.themes[themenum].initialized == false) break; if (themenum >= vmpartspawner.max_themes) { if (vmpartspawner.max_themes == 2048) VM_Warning(prog, "VM_CL_ParticleThemeSave: no free theme slots\n"); else VM_Warning(prog, "VM_CL_ParticleThemeSave: no free theme slots, try initparticlespawner() with highter max_themes\n"); PRVM_G_FLOAT(OFS_RETURN) = -1; return; } vmpartspawner.themes[themenum].initialized = true; VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum], prog); PRVM_G_FLOAT(OFS_RETURN) = themenum; return; } // update existing theme themenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (themenum < 0 || themenum >= vmpartspawner.max_themes) { VM_Warning(prog, "VM_CL_ParticleThemeSave: bad theme number %i\n", themenum); return; } vmpartspawner.themes[themenum].initialized = true; VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum], prog); } // void(float themenum) freeparticletheme static void VM_CL_ParticleThemeFree (prvm_prog_t *prog) { int themenum; VM_SAFEPARMCOUNT(1, VM_CL_ParticleThemeFree); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_ParticleThemeFree: particle spawner not initialized\n"); return; } themenum = (int)PRVM_G_FLOAT(OFS_PARM0); // check parms if (themenum <= 0 || themenum >= vmpartspawner.max_themes) { VM_Warning(prog, "VM_CL_ParticleThemeFree: bad theme number %i\n", themenum); return; } if (vmpartspawner.themes[themenum].initialized == false) { VM_Warning(prog, "VM_CL_ParticleThemeFree: theme #%i already freed\n", themenum); VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0], prog); return; } // free theme VM_ResetParticleTheme(&vmpartspawner.themes[themenum]); vmpartspawner.themes[themenum].initialized = false; } // float(vector org, vector dir, [float theme]) particle // returns 0 if failed, 1 if succesful static void VM_CL_SpawnParticle (prvm_prog_t *prog) { vec3_t org, dir; vmparticletheme_t *theme; particle_t *part; int themenum; VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_SpawnParticle2); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_SpawnParticle: particle spawner not initialized\n"); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); if (prog->argc < 3) // global-set particle { part = CL_NewParticle(org, (unsigned short)PRVM_clientglobalfloat(particle_type), ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]), ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]), (int)PRVM_clientglobalfloat(particle_tex), PRVM_clientglobalfloat(particle_size), PRVM_clientglobalfloat(particle_sizeincrease), PRVM_clientglobalfloat(particle_alpha)*256, PRVM_clientglobalfloat(particle_alphafade)*256, PRVM_clientglobalfloat(particle_gravity), PRVM_clientglobalfloat(particle_bounce), org[0], org[1], org[2], dir[0], dir[1], dir[2], PRVM_clientglobalfloat(particle_airfriction), PRVM_clientglobalfloat(particle_liquidfriction), PRVM_clientglobalfloat(particle_originjitter), PRVM_clientglobalfloat(particle_velocityjitter), (PRVM_clientglobalfloat(particle_qualityreduction)) ? true : false, PRVM_clientglobalfloat(particle_time), PRVM_clientglobalfloat(particle_stretch), (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode), (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation), (int)(PRVM_clientglobalvector(particle_staincolor1)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor1)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor1)[2]), (int)(PRVM_clientglobalvector(particle_staincolor2)[0])*65536 + (int)(PRVM_clientglobalvector(particle_staincolor2)[1])*256 + (int)(PRVM_clientglobalvector(particle_staincolor2)[2]), (int)PRVM_clientglobalfloat(particle_staintex), PRVM_clientglobalfloat(particle_stainalpha)*256, PRVM_clientglobalfloat(particle_stainsize), PRVM_clientglobalfloat(particle_angle), PRVM_clientglobalfloat(particle_spin), NULL); if (!part) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } if (PRVM_clientglobalfloat(particle_delayspawn)) part->delayedspawn = cl.time + PRVM_clientglobalfloat(particle_delayspawn); //if (PRVM_clientglobalfloat(particle_delaycollision)) // part->delayedcollisions = cl.time + PRVM_clientglobalfloat(particle_delaycollision); } else // quick themed particle { themenum = (int)PRVM_G_FLOAT(OFS_PARM2); if (themenum <= 0 || themenum >= vmpartspawner.max_themes) { VM_Warning(prog, "VM_CL_SpawnParticle: bad theme number %i\n", themenum); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } theme = &vmpartspawner.themes[themenum]; part = CL_NewParticle(org, theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex, theme->stainalpha, theme->stainsize, theme->angle, theme->spin, NULL); if (!part) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } if (theme->delayspawn) part->delayedspawn = cl.time + theme->delayspawn; //if (theme->delaycollision) // part->delayedcollisions = cl.time + theme->delaycollision; } PRVM_G_FLOAT(OFS_RETURN) = 1; } // float(vector org, vector dir, float spawndelay, float collisiondelay, [float theme]) delayedparticle // returns 0 if failed, 1 if success static void VM_CL_SpawnParticleDelayed (prvm_prog_t *prog) { vec3_t org, dir; vmparticletheme_t *theme; particle_t *part; int themenum; VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_SpawnParticle2); if (vmpartspawner.verified == false) { VM_Warning(prog, "VM_CL_SpawnParticle: particle spawner not initialized\n"); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } VectorCopy(PRVM_G_VECTOR(OFS_PARM0), org); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), dir); if (prog->argc < 5) // global-set particle part = CL_NewParticle(org, (unsigned short)PRVM_clientglobalfloat(particle_type), ((int)PRVM_clientglobalvector(particle_color1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color1)[2]), ((int)PRVM_clientglobalvector(particle_color2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_color2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_color2)[2]), (int)PRVM_clientglobalfloat(particle_tex), PRVM_clientglobalfloat(particle_size), PRVM_clientglobalfloat(particle_sizeincrease), PRVM_clientglobalfloat(particle_alpha)*256, PRVM_clientglobalfloat(particle_alphafade)*256, PRVM_clientglobalfloat(particle_gravity), PRVM_clientglobalfloat(particle_bounce), org[0], org[1], org[2], dir[0], dir[1], dir[2], PRVM_clientglobalfloat(particle_airfriction), PRVM_clientglobalfloat(particle_liquidfriction), PRVM_clientglobalfloat(particle_originjitter), PRVM_clientglobalfloat(particle_velocityjitter), (PRVM_clientglobalfloat(particle_qualityreduction)) ? true : false, PRVM_clientglobalfloat(particle_time), PRVM_clientglobalfloat(particle_stretch), (pblend_t)(int)PRVM_clientglobalfloat(particle_blendmode), (porientation_t)(int)PRVM_clientglobalfloat(particle_orientation), ((int)PRVM_clientglobalvector(particle_staincolor1)[0] << 16) + ((int)PRVM_clientglobalvector(particle_staincolor1)[1] << 8) + ((int)PRVM_clientglobalvector(particle_staincolor1)[2]), ((int)PRVM_clientglobalvector(particle_staincolor2)[0] << 16) + ((int)PRVM_clientglobalvector(particle_staincolor2)[1] << 8) + ((int)PRVM_clientglobalvector(particle_staincolor2)[2]), (int)PRVM_clientglobalfloat(particle_staintex), PRVM_clientglobalfloat(particle_stainalpha)*256, PRVM_clientglobalfloat(particle_stainsize), PRVM_clientglobalfloat(particle_angle), PRVM_clientglobalfloat(particle_spin), NULL); else // themed particle { themenum = (int)PRVM_G_FLOAT(OFS_PARM4); if (themenum <= 0 || themenum >= vmpartspawner.max_themes) { VM_Warning(prog, "VM_CL_SpawnParticle: bad theme number %i\n", themenum); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } theme = &vmpartspawner.themes[themenum]; part = CL_NewParticle(org, theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex, theme->stainalpha, theme->stainsize, theme->angle, theme->spin, NULL); } if (!part) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } part->delayedspawn = cl.time + PRVM_G_FLOAT(OFS_PARM2); //part->delayedcollisions = cl.time + PRVM_G_FLOAT(OFS_PARM3); PRVM_G_FLOAT(OFS_RETURN) = 0; } //==================== //CSQC engine entities query //==================== // float(float entitynum, float whatfld) getentity; // vector(float entitynum, float whatfld) getentityvec; // querying engine-drawn entity // VorteX: currently it's only tested with whatfld = 1..7 static void VM_CL_GetEntity (prvm_prog_t *prog) { int entnum, fieldnum; vec3_t forward, left, up, org; VM_SAFEPARMCOUNT(2, VM_CL_GetEntityVec); entnum = PRVM_G_FLOAT(OFS_PARM0); if (entnum < 0 || entnum >= cl.num_entities) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } fieldnum = PRVM_G_FLOAT(OFS_PARM1); switch(fieldnum) { case 0: // active state PRVM_G_FLOAT(OFS_RETURN) = cl.entities_active[entnum]; break; case 1: // origin Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); VectorCopy(org, PRVM_G_VECTOR(OFS_RETURN)); break; case 2: // forward Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); VectorCopy(forward, PRVM_G_VECTOR(OFS_RETURN)); break; case 3: // right Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); VectorNegate(left, PRVM_G_VECTOR(OFS_RETURN)); break; case 4: // up Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); VectorCopy(up, PRVM_G_VECTOR(OFS_RETURN)); break; case 5: // scale PRVM_G_FLOAT(OFS_RETURN) = Matrix4x4_ScaleFromMatrix(&cl.entities[entnum].render.matrix); break; case 6: // origin + v_forward, v_right, v_up Matrix4x4_ToVectors(&cl.entities[entnum].render.matrix, forward, left, up, org); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorNegate(left, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(org, PRVM_G_VECTOR(OFS_RETURN)); break; case 7: // alpha PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.alpha; break; case 8: // colormor VectorCopy(cl.entities[entnum].render.colormod, PRVM_G_VECTOR(OFS_RETURN)); break; case 9: // pants colormod VectorCopy(cl.entities[entnum].render.colormap_pantscolor, PRVM_G_VECTOR(OFS_RETURN)); break; case 10: // shirt colormod VectorCopy(cl.entities[entnum].render.colormap_shirtcolor, PRVM_G_VECTOR(OFS_RETURN)); break; case 11: // skinnum PRVM_G_FLOAT(OFS_RETURN) = cl.entities[entnum].render.skinnum; break; case 12: // mins VectorCopy(cl.entities[entnum].render.mins, PRVM_G_VECTOR(OFS_RETURN)); break; case 13: // maxs VectorCopy(cl.entities[entnum].render.maxs, PRVM_G_VECTOR(OFS_RETURN)); break; case 14: // absmin Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); VectorAdd(cl.entities[entnum].render.mins, org, PRVM_G_VECTOR(OFS_RETURN)); break; case 15: // absmax Matrix4x4_OriginFromMatrix(&cl.entities[entnum].render.matrix, org); VectorAdd(cl.entities[entnum].render.maxs, org, PRVM_G_VECTOR(OFS_RETURN)); break; case 16: // light VectorMA(cl.entities[entnum].render.modellight_ambient, 0.5, cl.entities[entnum].render.modellight_diffuse, PRVM_G_VECTOR(OFS_RETURN)); break; default: PRVM_G_FLOAT(OFS_RETURN) = 0; break; } } //==================== //QC POLYGON functions //==================== //#304 void() renderscene (EXT_CSQC) // moved that here to reset the polygons, // resetting them earlier causes R_Mesh_Draw to be called with numvertices = 0 // --blub static void VM_CL_R_RenderScene (prvm_prog_t *prog) { double t = Sys_DirtyTime(); vmpolygons_t *polys = &prog->vmpolygons; VM_SAFEPARMCOUNT(0, VM_CL_R_RenderScene); // update the views if(r_refdef.view.ismain) { // set the main view csqc_main_r_refdef_view = r_refdef.view; // clear the flags so no other view becomes "main" unless CSQC sets VF_MAINVIEW r_refdef.view.ismain = false; csqc_original_r_refdef_view.ismain = false; } // we need to update any RENDER_VIEWMODEL entities at this point because // csqc supplies its own view matrix CL_UpdateViewEntities(); // now draw stuff! R_RenderView(); polys->num_vertices = polys->num_triangles = 0; // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView t = Sys_DirtyTime() - t;if (t < 0 || t >= 1800) t = 0; prog->functions[PRVM_clientfunction(CSQC_UpdateView)].totaltime -= t; } static void VM_ResizePolygons(vmpolygons_t *polys) { float *oldvertex3f = polys->data_vertex3f; float *oldcolor4f = polys->data_color4f; float *oldtexcoord2f = polys->data_texcoord2f; vmpolygons_triangle_t *oldtriangles = polys->data_triangles; unsigned short *oldsortedelement3s = polys->data_sortedelement3s; polys->max_vertices = min(polys->max_triangles*3, 65536); polys->data_vertex3f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[3])); polys->data_color4f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[4])); polys->data_texcoord2f = (float *)Mem_Alloc(polys->pool, polys->max_vertices*sizeof(float[2])); polys->data_triangles = (vmpolygons_triangle_t *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(vmpolygons_triangle_t)); polys->data_sortedelement3s = (unsigned short *)Mem_Alloc(polys->pool, polys->max_triangles*sizeof(unsigned short[3])); if (polys->num_vertices) { memcpy(polys->data_vertex3f, oldvertex3f, polys->num_vertices*sizeof(float[3])); memcpy(polys->data_color4f, oldcolor4f, polys->num_vertices*sizeof(float[4])); memcpy(polys->data_texcoord2f, oldtexcoord2f, polys->num_vertices*sizeof(float[2])); } if (polys->num_triangles) { memcpy(polys->data_triangles, oldtriangles, polys->num_triangles*sizeof(vmpolygons_triangle_t)); memcpy(polys->data_sortedelement3s, oldsortedelement3s, polys->num_triangles*sizeof(unsigned short[3])); } if (oldvertex3f) Mem_Free(oldvertex3f); if (oldcolor4f) Mem_Free(oldcolor4f); if (oldtexcoord2f) Mem_Free(oldtexcoord2f); if (oldtriangles) Mem_Free(oldtriangles); if (oldsortedelement3s) Mem_Free(oldsortedelement3s); } static void VM_InitPolygons (vmpolygons_t* polys) { memset(polys, 0, sizeof(*polys)); polys->pool = Mem_AllocPool("VMPOLY", 0, NULL); polys->max_triangles = 1024; VM_ResizePolygons(polys); polys->initialized = true; } static void VM_DrawPolygonCallback (const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int surfacelistindex; vmpolygons_t *polys = (vmpolygons_t *)ent; // R_Mesh_ResetTextureState(); R_EntityMatrix(&identitymatrix); GL_CullFace(GL_NONE); GL_DepthTest(true); // polys in 3D space shall always have depth test GL_DepthRange(0, 1); R_Mesh_PrepareVertices_Generic_Arrays(polys->num_vertices, polys->data_vertex3f, polys->data_color4f, polys->data_texcoord2f); for (surfacelistindex = 0;surfacelistindex < numsurfaces;) { int numtriangles = 0; rtexture_t *tex = polys->data_triangles[surfacelist[surfacelistindex]].texture; int drawflag = polys->data_triangles[surfacelist[surfacelistindex]].drawflag; DrawQ_ProcessDrawFlag(drawflag, polys->data_triangles[surfacelist[surfacelistindex]].hasalpha); R_SetupShader_Generic(tex, NULL, GL_MODULATE, 1, false, false, false); numtriangles = 0; for (;surfacelistindex < numsurfaces;surfacelistindex++) { if (polys->data_triangles[surfacelist[surfacelistindex]].texture != tex || polys->data_triangles[surfacelist[surfacelistindex]].drawflag != drawflag) break; VectorCopy(polys->data_triangles[surfacelist[surfacelistindex]].elements, polys->data_sortedelement3s + 3*numtriangles); numtriangles++; } R_Mesh_Draw(0, polys->num_vertices, 0, numtriangles, NULL, NULL, 0, polys->data_sortedelement3s, NULL, 0); } } static void VMPolygons_Store(vmpolygons_t *polys) { qboolean hasalpha; int i; // detect if we have alpha hasalpha = polys->begin_texture_hasalpha; for(i = 0; !hasalpha && (i < polys->begin_vertices); ++i) if(polys->begin_color[i][3] < 1) hasalpha = true; if (polys->begin_draw2d) { // draw the polygon as 2D immediately drawqueuemesh_t mesh; mesh.texture = polys->begin_texture; mesh.num_vertices = polys->begin_vertices; mesh.num_triangles = polys->begin_vertices-2; mesh.data_element3i = polygonelement3i; mesh.data_element3s = polygonelement3s; mesh.data_vertex3f = polys->begin_vertex[0]; mesh.data_color4f = polys->begin_color[0]; mesh.data_texcoord2f = polys->begin_texcoord[0]; DrawQ_Mesh(&mesh, polys->begin_drawflag, hasalpha); } else { // queue the polygon as 3D for sorted transparent rendering later if (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) { while (polys->max_triangles < polys->num_triangles + polys->begin_vertices-2) polys->max_triangles *= 2; VM_ResizePolygons(polys); } if (polys->num_vertices + polys->begin_vertices <= polys->max_vertices) { // needle in a haystack! // polys->num_vertices was used for copying where we actually want to copy begin_vertices // that also caused it to not render the first polygon that is added // --blub memcpy(polys->data_vertex3f + polys->num_vertices * 3, polys->begin_vertex[0], polys->begin_vertices * sizeof(float[3])); memcpy(polys->data_color4f + polys->num_vertices * 4, polys->begin_color[0], polys->begin_vertices * sizeof(float[4])); memcpy(polys->data_texcoord2f + polys->num_vertices * 2, polys->begin_texcoord[0], polys->begin_vertices * sizeof(float[2])); for (i = 0;i < polys->begin_vertices-2;i++) { polys->data_triangles[polys->num_triangles].texture = polys->begin_texture; polys->data_triangles[polys->num_triangles].drawflag = polys->begin_drawflag; polys->data_triangles[polys->num_triangles].elements[0] = polys->num_vertices; polys->data_triangles[polys->num_triangles].elements[1] = polys->num_vertices + i+1; polys->data_triangles[polys->num_triangles].elements[2] = polys->num_vertices + i+2; polys->data_triangles[polys->num_triangles].hasalpha = hasalpha; polys->num_triangles++; } polys->num_vertices += polys->begin_vertices; } } polys->begin_active = false; } // TODO: move this into the client code and clean-up everything else, too! [1/6/2008 Black] // LordHavoc: agreed, this is a mess void VM_CL_AddPolygonsToMeshQueue (prvm_prog_t *prog) { int i; vmpolygons_t *polys = &prog->vmpolygons; vec3_t center; // only add polygons of the currently active prog to the queue - if there is none, we're done if( !prog ) return; if (!polys->num_triangles) return; for (i = 0;i < polys->num_triangles;i++) { VectorMAMAM(1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[0], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[1], 1.0f / 3.0f, polys->data_vertex3f + 3*polys->data_triangles[i].elements[2], center); R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, VM_DrawPolygonCallback, (entity_render_t *)polys, i, NULL); } /*polys->num_triangles = 0; // now done after rendering the scene, polys->num_vertices = 0; // otherwise it's not rendered at all and prints an error message --blub */ } //void(string texturename, float flag[, float is2d]) R_BeginPolygon static void VM_CL_R_PolygonBegin (prvm_prog_t *prog) { const char *picname; skinframe_t *sf; vmpolygons_t *polys = &prog->vmpolygons; int tf; // TODO instead of using skinframes here (which provides the benefit of // better management of flags, and is more suited for 3D rendering), what // about supporting Q3 shaders? VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_R_PolygonBegin); if (!polys->initialized) VM_InitPolygons(polys); if (polys->begin_active) { VM_Warning(prog, "VM_CL_R_PolygonBegin: called twice without VM_CL_R_PolygonBegin after first\n"); return; } picname = PRVM_G_STRING(OFS_PARM0); sf = NULL; if(*picname) { tf = TEXF_ALPHA; if((int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MIPMAP) tf |= TEXF_MIPMAP; do { sf = R_SkinFrame_FindNextByName(sf, picname); } while(sf && sf->textureflags != tf); if(!sf || !sf->base) sf = R_SkinFrame_LoadExternal(picname, tf, true); if(sf) R_SkinFrame_MarkUsed(sf); } polys->begin_texture = (sf && sf->base) ? sf->base : r_texture_white; polys->begin_texture_hasalpha = (sf && sf->base) ? sf->hasalpha : false; polys->begin_drawflag = (int)PRVM_G_FLOAT(OFS_PARM1) & DRAWFLAG_MASK; polys->begin_vertices = 0; polys->begin_active = true; polys->begin_draw2d = (prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : r_refdef.draw2dstage); } //void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex static void VM_CL_R_PolygonVertex (prvm_prog_t *prog) { vmpolygons_t *polys = &prog->vmpolygons; VM_SAFEPARMCOUNT(4, VM_CL_R_PolygonVertex); if (!polys->begin_active) { VM_Warning(prog, "VM_CL_R_PolygonVertex: VM_CL_R_PolygonBegin wasn't called\n"); return; } if (polys->begin_vertices >= VMPOLYGONS_MAXPOINTS) { VM_Warning(prog, "VM_CL_R_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); return; } polys->begin_vertex[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM0)[0]; polys->begin_vertex[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM0)[1]; polys->begin_vertex[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM0)[2]; polys->begin_texcoord[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM1)[0]; polys->begin_texcoord[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM1)[1]; polys->begin_color[polys->begin_vertices][0] = PRVM_G_VECTOR(OFS_PARM2)[0]; polys->begin_color[polys->begin_vertices][1] = PRVM_G_VECTOR(OFS_PARM2)[1]; polys->begin_color[polys->begin_vertices][2] = PRVM_G_VECTOR(OFS_PARM2)[2]; polys->begin_color[polys->begin_vertices][3] = PRVM_G_FLOAT(OFS_PARM3); polys->begin_vertices++; } //void() R_EndPolygon static void VM_CL_R_PolygonEnd (prvm_prog_t *prog) { vmpolygons_t *polys = &prog->vmpolygons; VM_SAFEPARMCOUNT(0, VM_CL_R_PolygonEnd); if (!polys->begin_active) { VM_Warning(prog, "VM_CL_R_PolygonEnd: VM_CL_R_PolygonBegin wasn't called\n"); return; } polys->begin_active = false; if (polys->begin_vertices >= 3) VMPolygons_Store(polys); else VM_Warning(prog, "VM_CL_R_PolygonEnd: %i vertices isn't a good choice\n", polys->begin_vertices); } static vmpolygons_t debugPolys; void Debug_PolygonBegin(const char *picname, int drawflag) { if(!debugPolys.initialized) VM_InitPolygons(&debugPolys); if(debugPolys.begin_active) { Con_Printf("Debug_PolygonBegin: called twice without Debug_PolygonEnd after first\n"); return; } debugPolys.begin_texture = picname[0] ? Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT)->tex : r_texture_white; debugPolys.begin_drawflag = drawflag; debugPolys.begin_vertices = 0; debugPolys.begin_active = true; } void Debug_PolygonVertex(float x, float y, float z, float s, float t, float r, float g, float b, float a) { if(!debugPolys.begin_active) { Con_Printf("Debug_PolygonVertex: Debug_PolygonBegin wasn't called\n"); return; } if(debugPolys.begin_vertices >= VMPOLYGONS_MAXPOINTS) { Con_Printf("Debug_PolygonVertex: may have %i vertices max\n", VMPOLYGONS_MAXPOINTS); return; } debugPolys.begin_vertex[debugPolys.begin_vertices][0] = x; debugPolys.begin_vertex[debugPolys.begin_vertices][1] = y; debugPolys.begin_vertex[debugPolys.begin_vertices][2] = z; debugPolys.begin_texcoord[debugPolys.begin_vertices][0] = s; debugPolys.begin_texcoord[debugPolys.begin_vertices][1] = t; debugPolys.begin_color[debugPolys.begin_vertices][0] = r; debugPolys.begin_color[debugPolys.begin_vertices][1] = g; debugPolys.begin_color[debugPolys.begin_vertices][2] = b; debugPolys.begin_color[debugPolys.begin_vertices][3] = a; debugPolys.begin_vertices++; } void Debug_PolygonEnd(void) { if (!debugPolys.begin_active) { Con_Printf("Debug_PolygonEnd: Debug_PolygonBegin wasn't called\n"); return; } debugPolys.begin_active = false; if (debugPolys.begin_vertices >= 3) VMPolygons_Store(&debugPolys); else Con_Printf("Debug_PolygonEnd: %i vertices isn't a good choice\n", debugPolys.begin_vertices); } /* ============= CL_CheckBottom Returns false if any part of the bottom of the entity is off an edge that is not a staircase. ============= */ static qboolean CL_CheckBottom (prvm_edict_t *ent) { prvm_prog_t *prog = CLVM_prog; vec3_t mins, maxs, start, stop; trace_t trace; int x, y; float mid, bottom; VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); VectorAdd (PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); // if all of the points under the corners are solid world, don't bother // with the tougher checks // the corners must be within 16 of the midpoint start[2] = mins[2] - 1; for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = x ? maxs[0] : mins[0]; start[1] = y ? maxs[1] : mins[1]; if (!(CL_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) goto realcheck; } return true; // we got out easy realcheck: // // check it for real... // start[2] = mins[2]; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0])*0.5; start[1] = stop[1] = (mins[1] + maxs[1])*0.5; stop[2] = start[2] - 2*sv_stepheight.value; trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, false, NULL, true, false); if (trace.fraction == 1.0) return false; mid = bottom = trace.endpos[2]; // the corners must be within 16 of the midpoint for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = stop[0] = x ? maxs[0] : mins[0]; start[1] = stop[1] = y ? maxs[1] : mins[1]; trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, false, NULL, true, false); if (trace.fraction != 1.0 && trace.endpos[2] > bottom) bottom = trace.endpos[2]; if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) return false; } return true; } /* ============= CL_movestep Called by monster program code. The move will be adjusted for slopes and stairs, but if the move isn't possible, no move is done and false is returned ============= */ static qboolean CL_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) { prvm_prog_t *prog = CLVM_prog; float dz; vec3_t oldorg, neworg, end, traceendpos; vec3_t mins, maxs, start; trace_t trace; int i, svent; prvm_edict_t *enemy; // try the move VectorCopy(PRVM_clientedictvector(ent, mins), mins); VectorCopy(PRVM_clientedictvector(ent, maxs), maxs); VectorCopy (PRVM_clientedictvector(ent, origin), oldorg); VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); // flying monsters don't step up if ( (int)PRVM_clientedictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) { // try one move with vertical motion, then one without for (i=0 ; i<2 ; i++) { VectorAdd (PRVM_clientedictvector(ent, origin), move, neworg); enemy = PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)); if (i == 0 && enemy != prog->edicts) { dz = PRVM_clientedictvector(ent, origin)[2] - PRVM_clientedictvector(PRVM_PROG_TO_EDICT(PRVM_clientedictedict(ent, enemy)), origin)[2]; if (dz > 40) neworg[2] -= 8; if (dz < 30) neworg[2] += 8; } VectorCopy(PRVM_clientedictvector(ent, origin), start); trace = CL_TraceBox(start, mins, maxs, neworg, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, &svent, true); if (settrace) CL_VM_SetTraceGlobals(prog, &trace, svent); if (trace.fraction == 1) { VectorCopy(trace.endpos, traceendpos); if (((int)PRVM_clientedictfloat(ent, flags) & FL_SWIM) && !(CL_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) return false; // swim monster left water VectorCopy (traceendpos, PRVM_clientedictvector(ent, origin)); if (relink) CL_LinkEdict(ent); return true; } if (enemy == prog->edicts) break; } return false; } // push down from a step height above the wished position neworg[2] += sv_stepheight.value; VectorCopy (neworg, end); end[2] -= sv_stepheight.value*2; trace = CL_TraceBox(neworg, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, &svent, true); if (settrace) CL_VM_SetTraceGlobals(prog, &trace, svent); if (trace.startsolid) { neworg[2] -= sv_stepheight.value; trace = CL_TraceBox(neworg, mins, maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value, true, true, &svent, true); if (settrace) CL_VM_SetTraceGlobals(prog, &trace, svent); if (trace.startsolid) return false; } if (trace.fraction == 1) { // if monster had the ground pulled out, go ahead and fall if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) { VectorAdd (PRVM_clientedictvector(ent, origin), move, PRVM_clientedictvector(ent, origin)); if (relink) CL_LinkEdict(ent); PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_ONGROUND; return true; } return false; // walked off an edge } // check point traces down for dangling corners VectorCopy (trace.endpos, PRVM_clientedictvector(ent, origin)); if (!CL_CheckBottom (ent)) { if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) CL_LinkEdict(ent); return true; } VectorCopy (oldorg, PRVM_clientedictvector(ent, origin)); return false; } if ( (int)PRVM_clientedictfloat(ent, flags) & FL_PARTIALGROUND ) PRVM_clientedictfloat(ent, flags) = (int)PRVM_clientedictfloat(ent, flags) & ~FL_PARTIALGROUND; PRVM_clientedictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); // the move is ok if (relink) CL_LinkEdict(ent); return true; } /* =============== VM_CL_walkmove float(float yaw, float dist[, settrace]) walkmove =============== */ static void VM_CL_walkmove (prvm_prog_t *prog) { prvm_edict_t *ent; float yaw, dist; vec3_t move; mfunction_t *oldf; int oldself; qboolean settrace; VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_walkmove); // assume failure if it returns early PRVM_G_FLOAT(OFS_RETURN) = 0; ent = PRVM_PROG_TO_EDICT(PRVM_clientglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "walkmove: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "walkmove: can not modify free entity\n"); return; } yaw = PRVM_G_FLOAT(OFS_PARM0); dist = PRVM_G_FLOAT(OFS_PARM1); settrace = prog->argc >= 3 && PRVM_G_FLOAT(OFS_PARM2); if ( !( (int)PRVM_clientedictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) return; yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; // save program state, because CL_movestep may call other progs oldf = prog->xfunction; oldself = PRVM_clientglobaledict(self); PRVM_G_FLOAT(OFS_RETURN) = CL_movestep(ent, move, true, false, settrace); // restore program state prog->xfunction = oldf; PRVM_clientglobaledict(self) = oldself; } /* =============== VM_CL_serverkey string(string key) serverkey =============== */ static void VM_CL_serverkey(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(1, VM_CL_serverkey); InfoString_GetValue(cl.qw_serverinfo, PRVM_G_STRING(OFS_PARM0), string, sizeof(string)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } /* ================= VM_CL_checkpvs Checks if an entity is in a point's PVS. Should be fast but can be inexact. float checkpvs(vector viewpos, entity viewee) = #240; ================= */ static void VM_CL_checkpvs (prvm_prog_t *prog) { vec3_t viewpos; prvm_edict_t *viewee; vec3_t mi, ma; #if 1 unsigned char *pvs; #else int fatpvsbytes; unsigned char fatpvs[MAX_MAP_LEAFS/8]; #endif VM_SAFEPARMCOUNT(2, VM_SV_checkpvs); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos); viewee = PRVM_G_EDICT(OFS_PARM1); if(viewee->priv.required->free) { VM_Warning(prog, "checkpvs: can not check free entity\n"); PRVM_G_FLOAT(OFS_RETURN) = 4; return; } VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, mins), mi); VectorAdd(PRVM_serveredictvector(viewee, origin), PRVM_serveredictvector(viewee, maxs), ma); #if 1 if(!cl.worldmodel || !cl.worldmodel->brush.GetPVS || !cl.worldmodel->brush.BoxTouchingPVS) { // no PVS support on this worldmodel... darn PRVM_G_FLOAT(OFS_RETURN) = 3; return; } pvs = cl.worldmodel->brush.GetPVS(cl.worldmodel, viewpos); if(!pvs) { // viewpos isn't in any PVS... darn PRVM_G_FLOAT(OFS_RETURN) = 2; return; } PRVM_G_FLOAT(OFS_RETURN) = cl.worldmodel->brush.BoxTouchingPVS(cl.worldmodel, pvs, mi, ma); #else // using fat PVS like FTEQW does (slow) if(!cl.worldmodel || !cl.worldmodel->brush.FatPVS || !cl.worldmodel->brush.BoxTouchingPVS) { // no PVS support on this worldmodel... darn PRVM_G_FLOAT(OFS_RETURN) = 3; return; } fatpvsbytes = cl.worldmodel->brush.FatPVS(cl.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false); if(!fatpvsbytes) { // viewpos isn't in any PVS... darn PRVM_G_FLOAT(OFS_RETURN) = 2; return; } PRVM_G_FLOAT(OFS_RETURN) = cl.worldmodel->brush.BoxTouchingPVS(cl.worldmodel, fatpvs, mi, ma); #endif } // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. static void VM_CL_skel_create(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = CL_GetModelByIndex(modelindex); skeleton_t *skeleton; int i; PRVM_G_FLOAT(OFS_RETURN) = 0; if (!model || !model->num_bones) return; for (i = 0;i < MAX_EDICTS;i++) if (!prog->skeletons[i]) break; if (i == MAX_EDICTS) return; prog->skeletons[i] = skeleton = (skeleton_t *)Mem_Alloc(cls.levelmempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t)); PRVM_G_FLOAT(OFS_RETURN) = i + 1; skeleton->model = model; skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1); // initialize to identity matrices for (i = 0;i < skeleton->model->num_bones;i++) skeleton->relativetransforms[i] = identitymatrix; } // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure static void VM_CL_skel_build(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1); int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2); float retainfrac = PRVM_G_FLOAT(OFS_PARM3); int firstbone = PRVM_G_FLOAT(OFS_PARM4) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM5) - 1; dp_model_t *model = CL_GetModelByIndex(modelindex); int numblends; int bonenum; int blendindex; framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; frameblend_t frameblend[MAX_FRAMEBLENDS]; matrix4x4_t bonematrix; matrix4x4_t matrix; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; firstbone = max(0, firstbone); lastbone = min(lastbone, model->num_bones - 1); lastbone = min(lastbone, skeleton->model->num_bones - 1); VM_GenerateFrameGroupBlend(prog, framegroupblend, ed); VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model, cl.time); for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++) ; for (bonenum = firstbone;bonenum <= lastbone;bonenum++) { memset(&bonematrix, 0, sizeof(bonematrix)); for (blendindex = 0;blendindex < numblends;blendindex++) { Matrix4x4_FromBonePose7s(&matrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + bonenum)); Matrix4x4_Accumulate(&bonematrix, &matrix, frameblend[blendindex].lerp); } Matrix4x4_Normalize3(&bonematrix, &bonematrix); Matrix4x4_Interpolate(&skeleton->relativetransforms[bonenum], &bonematrix, &skeleton->relativetransforms[bonenum], retainfrac); } PRVM_G_FLOAT(OFS_RETURN) = skeletonindex + 1; } // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton static void VM_CL_skel_get_numbones(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones; } // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) static void VM_CL_skel_get_bonename(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; PRVM_G_INT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, skeleton->model->data_bones[bonenum].name); } // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) static void VM_CL_skel_get_boneparent(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1; } // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex static void VM_CL_skel_find_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; const char *tagname = PRVM_G_STRING(OFS_PARM1); skeleton_t *skeleton; PRVM_G_FLOAT(OFS_RETURN) = 0; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname); } // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) static void VM_CL_skel_get_bonerel(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; matrix4x4_t matrix; vec3_t forward, left, up, origin; VectorClear(PRVM_G_VECTOR(OFS_RETURN)); VectorClear(PRVM_clientglobalvector(v_forward)); VectorClear(PRVM_clientglobalvector(v_right)); VectorClear(PRVM_clientglobalvector(v_up)); if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; matrix = skeleton->relativetransforms[bonenum]; Matrix4x4_ToVectors(&matrix, forward, left, up, origin); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorNegate(left, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); } // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) static void VM_CL_skel_get_boneabs(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; vec3_t forward, left, up, origin; VectorClear(PRVM_G_VECTOR(OFS_RETURN)); VectorClear(PRVM_clientglobalvector(v_forward)); VectorClear(PRVM_clientglobalvector(v_right)); VectorClear(PRVM_clientglobalvector(v_up)); if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; matrix = skeleton->relativetransforms[bonenum]; // convert to absolute while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0) { temp = matrix; Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp); } Matrix4x4_ToVectors(&matrix, forward, left, up, origin); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorNegate(left, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN)); } // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) static void VM_CL_skel_set_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); skeleton->relativetransforms[bonenum] = matrix; } // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) static void VM_CL_skel_mul_bone(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; if (bonenum < 0 || bonenum >= skeleton->model->num_bones) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin); VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); temp = skeleton->relativetransforms[bonenum]; Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); } // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) static void VM_CL_skel_mul_bones(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1; int bonenum; vec3_t forward, left, up, origin; skeleton_t *skeleton; matrix4x4_t matrix; matrix4x4_t temp; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin); VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorNegate(PRVM_clientglobalvector(v_right), left); VectorCopy(PRVM_clientglobalvector(v_up), up); Matrix4x4_FromVectors(&matrix, forward, left, up, origin); firstbone = max(0, firstbone); lastbone = min(lastbone, skeleton->model->num_bones - 1); for (bonenum = firstbone;bonenum <= lastbone;bonenum++) { temp = skeleton->relativetransforms[bonenum]; Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp); } } // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse static void VM_CL_skel_copybones(prvm_prog_t *prog) { int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1; int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1; int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1; int bonenum; skeleton_t *skeletondst; skeleton_t *skeletonsrc; if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst])) return; if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc])) return; firstbone = max(0, firstbone); lastbone = min(lastbone, skeletondst->model->num_bones - 1); lastbone = min(lastbone, skeletonsrc->model->num_bones - 1); for (bonenum = firstbone;bonenum <= lastbone;bonenum++) skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum]; } // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) static void VM_CL_skel_delete(prvm_prog_t *prog) { int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1; skeleton_t *skeleton; if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex])) return; Mem_Free(skeleton); prog->skeletons[skeletonindex] = NULL; } // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found static void VM_CL_frameforname(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = CL_GetModelByIndex(modelindex); const char *name = PRVM_G_STRING(OFS_PARM1); int i; PRVM_G_FLOAT(OFS_RETURN) = -1; if (!model || !model->animscenes) return; for (i = 0;i < model->numframes;i++) { if (!strcasecmp(model->animscenes[i].name, name)) { PRVM_G_FLOAT(OFS_RETURN) = i; break; } } } // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. static void VM_CL_frameduration(prvm_prog_t *prog) { int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0); dp_model_t *model = CL_GetModelByIndex(modelindex); int framenum = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = 0; if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) return; if (model->animscenes[framenum].framerate) PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; } static void VM_CL_RotateMoves(prvm_prog_t *prog) { /* * Obscure builtin used by GAME_XONOTIC. * * Edits the input history of cl_movement by rotating all move commands * currently in the queue using the given transform. * * The vector passed is an "angles transform" as used by warpzonelib, i.e. * v_angle-like (non-inverted) euler angles that perform the rotation * of the space that is to be done. * * This is meant to be used as a fixangle replacement after passing * through a warpzone/portal: the client is told about the warp transform, * and calls this function in the same frame as the one on which the * client's origin got changed by the serverside teleport. Then this code * transforms the pre-warp input (which matches the empty space behind * the warp plane) into post-warp input (which matches the target area * of the warp). Also, at the same time, the client has to use * R_SetView to adjust VF_CL_VIEWANGLES according to the same transform. * * This together allows warpzone motion to be perfectly predicted by * the client! * * Furthermore, for perfect warpzone behaviour, the server side also * has to detect input the client sent before it received the origin * update, but after the warp occurred on the server, and has to adjust * input appropriately. */ matrix4x4_t m; vec3_t v = {0, 0, 0}; vec3_t a, x, y, z; VM_SAFEPARMCOUNT(1, VM_CL_RotateMoves); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), a); AngleVectorsFLU(a, x, y, z); Matrix4x4_FromVectors(&m, x, y, z, v); CL_RotateMoves(&m); } // #358 void(string cubemapname) loadcubemap static void VM_CL_loadcubemap(prvm_prog_t *prog) { const char *name; VM_SAFEPARMCOUNT(1, VM_CL_loadcubemap); name = PRVM_G_STRING(OFS_PARM0); R_GetCubemap(name); } #define REFDEFFLAG_TELEPORTED 1 #define REFDEFFLAG_JUMPING 2 #define REFDEFFLAG_DEAD 4 #define REFDEFFLAG_INTERMISSION 8 static void VM_CL_V_CalcRefdef(prvm_prog_t *prog) { matrix4x4_t entrendermatrix; vec3_t clviewangles; vec3_t clvelocity; qboolean teleported; qboolean clonground; qboolean clcmdjump; qboolean cldead; qboolean clintermission; float clstatsviewheight; prvm_edict_t *ent; int flags; VM_SAFEPARMCOUNT(2, VM_CL_V_CalcRefdef); ent = PRVM_G_EDICT(OFS_PARM0); flags = PRVM_G_FLOAT(OFS_PARM1); // use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad) CL_GetTagMatrix(prog, &entrendermatrix, ent, 0); VectorCopy(cl.csqc_viewangles, clviewangles); teleported = (flags & REFDEFFLAG_TELEPORTED) != 0; clonground = ((int)PRVM_clientedictfloat(ent, pmove_flags) & PMF_ONGROUND) != 0; clcmdjump = (flags & REFDEFFLAG_JUMPING) != 0; clstatsviewheight = PRVM_clientedictvector(ent, view_ofs)[2]; cldead = (flags & REFDEFFLAG_DEAD) != 0; clintermission = (flags & REFDEFFLAG_INTERMISSION) != 0; VectorCopy(PRVM_clientedictvector(ent, velocity), clvelocity); V_CalcRefdefUsing(&entrendermatrix, clviewangles, teleported, clonground, clcmdjump, clstatsviewheight, cldead, clintermission, clvelocity); VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); CSQC_R_RecalcView(); } //============================================================================ // To create a almost working builtin file from this replace: // "^NULL.*" with "" // "^{.*//.*}:Wh\(.*\)" with "\1" // "\:" with "//" // "^.*//:Wh{\#:d*}:Wh{.*}" with "\2 = \1;" // "\n\n+" with "\n\n" prvm_builtin_t vm_cl_builtins[] = { NULL, // #0 NULL function (not callable) (QUAKE) VM_CL_makevectors, // #1 void(vector ang) makevectors (QUAKE) VM_CL_setorigin, // #2 void(entity e, vector o) setorigin (QUAKE) VM_CL_setmodel, // #3 void(entity e, string m) setmodel (QUAKE) VM_CL_setsize, // #4 void(entity e, vector min, vector max) setsize (QUAKE) NULL, // #5 void(entity e, vector min, vector max) setabssize (QUAKE) VM_break, // #6 void() break (QUAKE) VM_random, // #7 float() random (QUAKE) VM_CL_sound, // #8 void(entity e, float chan, string samp) sound (QUAKE) VM_normalize, // #9 vector(vector v) normalize (QUAKE) VM_error, // #10 void(string e) error (QUAKE) VM_objerror, // #11 void(string e) objerror (QUAKE) VM_vlen, // #12 float(vector v) vlen (QUAKE) VM_vectoyaw, // #13 float(vector v) vectoyaw (QUAKE) VM_CL_spawn, // #14 entity() spawn (QUAKE) VM_remove, // #15 void(entity e) remove (QUAKE) VM_CL_traceline, // #16 void(vector v1, vector v2, float tryents, entity ignoreentity) traceline (QUAKE) NULL, // #17 entity() checkclient (QUAKE) VM_find, // #18 entity(entity start, .string fld, string match) find (QUAKE) VM_precache_sound, // #19 void(string s) precache_sound (QUAKE) VM_CL_precache_model, // #20 void(string s) precache_model (QUAKE) NULL, // #21 void(entity client, string s, ...) stuffcmd (QUAKE) VM_CL_findradius, // #22 entity(vector org, float rad) findradius (QUAKE) NULL, // #23 void(string s, ...) bprint (QUAKE) NULL, // #24 void(entity client, string s, ...) sprint (QUAKE) VM_dprint, // #25 void(string s, ...) dprint (QUAKE) VM_ftos, // #26 string(float f) ftos (QUAKE) VM_vtos, // #27 string(vector v) vtos (QUAKE) VM_coredump, // #28 void() coredump (QUAKE) VM_traceon, // #29 void() traceon (QUAKE) VM_traceoff, // #30 void() traceoff (QUAKE) VM_eprint, // #31 void(entity e) eprint (QUAKE) VM_CL_walkmove, // #32 float(float yaw, float dist[, float settrace]) walkmove (QUAKE) NULL, // #33 (QUAKE) VM_CL_droptofloor, // #34 float() droptofloor (QUAKE) VM_CL_lightstyle, // #35 void(float style, string value) lightstyle (QUAKE) VM_rint, // #36 float(float v) rint (QUAKE) VM_floor, // #37 float(float v) floor (QUAKE) VM_ceil, // #38 float(float v) ceil (QUAKE) NULL, // #39 (QUAKE) VM_CL_checkbottom, // #40 float(entity e) checkbottom (QUAKE) VM_CL_pointcontents, // #41 float(vector v) pointcontents (QUAKE) NULL, // #42 (QUAKE) VM_fabs, // #43 float(float f) fabs (QUAKE) NULL, // #44 vector(entity e, float speed) aim (QUAKE) VM_cvar, // #45 float(string s) cvar (QUAKE) VM_localcmd, // #46 void(string s) localcmd (QUAKE) VM_nextent, // #47 entity(entity e) nextent (QUAKE) VM_CL_particle, // #48 void(vector o, vector d, float color, float count) particle (QUAKE) VM_changeyaw, // #49 void() ChangeYaw (QUAKE) NULL, // #50 (QUAKE) VM_vectoangles, // #51 vector(vector v) vectoangles (QUAKE) NULL, // #52 void(float to, float f) WriteByte (QUAKE) NULL, // #53 void(float to, float f) WriteChar (QUAKE) NULL, // #54 void(float to, float f) WriteShort (QUAKE) NULL, // #55 void(float to, float f) WriteLong (QUAKE) NULL, // #56 void(float to, float f) WriteCoord (QUAKE) NULL, // #57 void(float to, float f) WriteAngle (QUAKE) NULL, // #58 void(float to, string s) WriteString (QUAKE) NULL, // #59 (QUAKE) VM_sin, // #60 float(float f) sin (DP_QC_SINCOSSQRTPOW) VM_cos, // #61 float(float f) cos (DP_QC_SINCOSSQRTPOW) VM_sqrt, // #62 float(float f) sqrt (DP_QC_SINCOSSQRTPOW) VM_changepitch, // #63 void(entity ent) changepitch (DP_QC_CHANGEPITCH) VM_CL_tracetoss, // #64 void(entity e, entity ignore) tracetoss (DP_QC_TRACETOSS) VM_etos, // #65 string(entity ent) etos (DP_QC_ETOS) NULL, // #66 (QUAKE) NULL, // #67 void(float step) movetogoal (QUAKE) VM_precache_file, // #68 string(string s) precache_file (QUAKE) VM_CL_makestatic, // #69 void(entity e) makestatic (QUAKE) NULL, // #70 void(string s) changelevel (QUAKE) NULL, // #71 (QUAKE) VM_cvar_set, // #72 void(string var, string val) cvar_set (QUAKE) NULL, // #73 void(entity client, strings) centerprint (QUAKE) VM_CL_ambientsound, // #74 void(vector pos, string samp, float vol, float atten) ambientsound (QUAKE) VM_CL_precache_model, // #75 string(string s) precache_model2 (QUAKE) VM_precache_sound, // #76 string(string s) precache_sound2 (QUAKE) VM_precache_file, // #77 string(string s) precache_file2 (QUAKE) NULL, // #78 void(entity e) setspawnparms (QUAKE) NULL, // #79 void(entity killer, entity killee) logfrag (QUAKEWORLD) NULL, // #80 string(entity e, string keyname) infokey (QUAKEWORLD) VM_stof, // #81 float(string s) stof (FRIK_FILE) NULL, // #82 void(vector where, float set) multicast (QUAKEWORLD) NULL, // #83 (QUAKE) NULL, // #84 (QUAKE) NULL, // #85 (QUAKE) NULL, // #86 (QUAKE) NULL, // #87 (QUAKE) NULL, // #88 (QUAKE) NULL, // #89 (QUAKE) VM_CL_tracebox, // #90 void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox (DP_QC_TRACEBOX) VM_randomvec, // #91 vector() randomvec (DP_QC_RANDOMVEC) VM_CL_getlight, // #92 vector(vector org) getlight (DP_QC_GETLIGHT) VM_registercvar, // #93 float(string name, string value) registercvar (DP_REGISTERCVAR) VM_min, // #94 float(float a, floats) min (DP_QC_MINMAXBOUND) VM_max, // #95 float(float a, floats) max (DP_QC_MINMAXBOUND) VM_bound, // #96 float(float minimum, float val, float maximum) bound (DP_QC_MINMAXBOUND) VM_pow, // #97 float(float f, float f) pow (DP_QC_SINCOSSQRTPOW) VM_findfloat, // #98 entity(entity start, .float fld, float match) findfloat (DP_QC_FINDFLOAT) VM_checkextension, // #99 float(string s) checkextension (the basis of the extension system) // FrikaC and Telejano range #100-#199 NULL, // #100 NULL, // #101 NULL, // #102 NULL, // #103 NULL, // #104 NULL, // #105 NULL, // #106 NULL, // #107 NULL, // #108 NULL, // #109 VM_fopen, // #110 float(string filename, float mode) fopen (FRIK_FILE) VM_fclose, // #111 void(float fhandle) fclose (FRIK_FILE) VM_fgets, // #112 string(float fhandle) fgets (FRIK_FILE) VM_fputs, // #113 void(float fhandle, string s) fputs (FRIK_FILE) VM_strlen, // #114 float(string s) strlen (FRIK_FILE) VM_strcat, // #115 string(string s1, string s2, ...) strcat (FRIK_FILE) VM_substring, // #116 string(string s, float start, float length) substring (FRIK_FILE) VM_stov, // #117 vector(string) stov (FRIK_FILE) VM_strzone, // #118 string(string s) strzone (FRIK_FILE) VM_strunzone, // #119 void(string s) strunzone (FRIK_FILE) NULL, // #120 NULL, // #121 NULL, // #122 NULL, // #123 NULL, // #124 NULL, // #125 NULL, // #126 NULL, // #127 NULL, // #128 NULL, // #129 NULL, // #130 NULL, // #131 NULL, // #132 NULL, // #133 NULL, // #134 NULL, // #135 NULL, // #136 NULL, // #137 NULL, // #138 NULL, // #139 NULL, // #140 NULL, // #141 NULL, // #142 NULL, // #143 NULL, // #144 NULL, // #145 NULL, // #146 NULL, // #147 NULL, // #148 NULL, // #149 NULL, // #150 NULL, // #151 NULL, // #152 NULL, // #153 NULL, // #154 NULL, // #155 NULL, // #156 NULL, // #157 NULL, // #158 NULL, // #159 NULL, // #160 NULL, // #161 NULL, // #162 NULL, // #163 NULL, // #164 NULL, // #165 NULL, // #166 NULL, // #167 NULL, // #168 NULL, // #169 NULL, // #170 NULL, // #171 NULL, // #172 NULL, // #173 NULL, // #174 NULL, // #175 NULL, // #176 NULL, // #177 NULL, // #178 NULL, // #179 NULL, // #180 NULL, // #181 NULL, // #182 NULL, // #183 NULL, // #184 NULL, // #185 NULL, // #186 NULL, // #187 NULL, // #188 NULL, // #189 NULL, // #190 NULL, // #191 NULL, // #192 NULL, // #193 NULL, // #194 NULL, // #195 NULL, // #196 NULL, // #197 NULL, // #198 NULL, // #199 // FTEQW range #200-#299 NULL, // #200 NULL, // #201 NULL, // #202 NULL, // #203 NULL, // #204 NULL, // #205 NULL, // #206 NULL, // #207 NULL, // #208 NULL, // #209 NULL, // #210 NULL, // #211 NULL, // #212 NULL, // #213 NULL, // #214 NULL, // #215 NULL, // #216 NULL, // #217 VM_bitshift, // #218 float(float number, float quantity) bitshift (EXT_BITSHIFT) NULL, // #219 NULL, // #220 VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) NULL, // #231 NULL, // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) NULL, // #233 NULL, // #234 NULL, // #235 NULL, // #236 NULL, // #237 NULL, // #238 NULL, // #239 VM_CL_checkpvs, // #240 NULL, // #241 NULL, // #242 NULL, // #243 NULL, // #244 NULL, // #245 NULL, // #246 NULL, // #247 NULL, // #248 NULL, // #249 NULL, // #250 NULL, // #251 NULL, // #252 NULL, // #253 NULL, // #254 NULL, // #255 NULL, // #256 NULL, // #257 NULL, // #258 NULL, // #259 NULL, // #260 NULL, // #261 NULL, // #262 VM_CL_skel_create, // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex. VM_CL_skel_build, // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure VM_CL_skel_get_numbones, // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton VM_CL_skel_get_bonename, // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring) VM_CL_skel_get_boneparent, // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) VM_CL_skel_find_bone, // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex VM_CL_skel_get_bonerel, // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) VM_CL_skel_get_boneabs, // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) VM_CL_skel_set_bone, // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) VM_CL_skel_mul_bone, // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) VM_CL_skel_mul_bones, // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) VM_CL_skel_copybones, // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse VM_CL_skel_delete, // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) VM_CL_frameforname, // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found VM_CL_frameduration, // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. NULL, // #278 NULL, // #279 NULL, // #280 NULL, // #281 NULL, // #282 NULL, // #283 NULL, // #284 NULL, // #285 NULL, // #286 NULL, // #287 NULL, // #288 NULL, // #289 NULL, // #290 NULL, // #291 NULL, // #292 NULL, // #293 NULL, // #294 NULL, // #295 NULL, // #296 NULL, // #297 NULL, // #298 NULL, // #299 // CSQC range #300-#399 VM_CL_R_ClearScene, // #300 void() clearscene (EXT_CSQC) VM_CL_R_AddEntities, // #301 void(float mask) addentities (EXT_CSQC) VM_CL_R_AddEntity, // #302 void(entity ent) addentity (EXT_CSQC) VM_CL_R_SetView, // #303 float(float property, ...) setproperty (EXT_CSQC) VM_CL_R_RenderScene, // #304 void() renderscene (EXT_CSQC) VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (EXT_CSQC) VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag, float is2d[NYI: , float lines]) R_BeginPolygon VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon VM_CL_R_SetView, // #309 float(float property) getproperty (EXT_CSQC) VM_CL_unproject, // #310 vector (vector v) cs_unproject (EXT_CSQC) VM_CL_project, // #311 vector (vector v) cs_project (EXT_CSQC) NULL, // #312 NULL, // #313 NULL, // #314 VM_drawline, // #315 void(float width, vector pos1, vector pos2, float flag) drawline (EXT_CSQC) VM_iscachedpic, // #316 float(string name) iscachedpic (EXT_CSQC) VM_precache_pic, // #317 string(string name, float trywad) precache_pic (EXT_CSQC) VM_getimagesize, // #318 vector(string picname) draw_getimagesize (EXT_CSQC) VM_freepic, // #319 void(string name) freepic (EXT_CSQC) VM_drawcharacter, // #320 float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter (EXT_CSQC) VM_drawstring, // #321 float(vector position, string text, vector scale, vector rgb, float alpha[, float flag]) drawstring (EXT_CSQC, DP_CSQC) VM_drawpic, // #322 float(vector position, string pic, vector size, vector rgb, float alpha[, float flag]) drawpic (EXT_CSQC) VM_drawfill, // #323 float(vector position, vector size, vector rgb, float alpha, float flag) drawfill (EXT_CSQC) VM_drawsetcliparea, // #324 void(float x, float y, float width, float height) drawsetcliparea VM_drawresetcliparea, // #325 void(void) drawresetcliparea VM_drawcolorcodedstring, // #326 float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) (EXT_CSQC) VM_stringwidth, // #327 // FIXME is this okay? VM_drawsubpic, // #328 // FIXME is this okay? VM_drawrotpic, // #329 // FIXME is this okay? VM_CL_getstatf, // #330 float(float stnum) getstatf (EXT_CSQC) VM_CL_getstati, // #331 float(float stnum) getstati (EXT_CSQC) VM_CL_getstats, // #332 string(float firststnum) getstats (EXT_CSQC) VM_CL_setmodelindex, // #333 void(entity e, float mdlindex) setmodelindex (EXT_CSQC) VM_CL_modelnameforindex, // #334 string(float mdlindex) modelnameforindex (EXT_CSQC) VM_CL_particleeffectnum, // #335 float(string effectname) particleeffectnum (EXT_CSQC) VM_CL_trailparticles, // #336 void(entity ent, float effectnum, vector start, vector end) trailparticles (EXT_CSQC) VM_CL_pointparticles, // #337 void(float effectnum, vector origin [, vector dir, float count]) pointparticles (EXT_CSQC) VM_centerprint, // #338 void(string s, ...) centerprint (EXT_CSQC) VM_print, // #339 void(string s, ...) print (EXT_CSQC, DP_SV_PRINT) VM_keynumtostring, // #340 string(float keynum) keynumtostring (EXT_CSQC) VM_stringtokeynum, // #341 float(string keyname) stringtokeynum (EXT_CSQC) VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) VM_CL_setcursormode, // #343 void(float usecursor) setcursormode (DP_CSQC) VM_CL_getmousepos, // #344 vector() getmousepos (DP_CSQC) VM_CL_getinputstate, // #345 float(float framenum) getinputstate (EXT_CSQC) VM_CL_setsensitivityscale, // #346 void(float sens) setsensitivityscale (EXT_CSQC) VM_CL_runplayerphysics, // #347 void() runstandardplayerphysics (EXT_CSQC) VM_CL_getplayerkey, // #348 string(float playernum, string keyname) getplayerkeyvalue (EXT_CSQC) VM_CL_isdemo, // #349 float() isdemo (EXT_CSQC) VM_isserver, // #350 float() isserver (EXT_CSQC) VM_CL_setlistener, // #351 void(vector origin, vector forward, vector right, vector up) SetListener (EXT_CSQC) VM_CL_registercmd, // #352 void(string cmdname) registercommand (EXT_CSQC) VM_wasfreed, // #353 float(entity ent) wasfreed (EXT_CSQC) (should be availabe on server too) VM_CL_serverkey, // #354 string(string key) serverkey (EXT_CSQC) VM_CL_videoplaying, // #355 VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) VM_CL_loadcubemap, // #358 void(string cubemapname) loadcubemap (DP_GFX_) NULL, // #359 VM_CL_ReadByte, // #360 float() readbyte (EXT_CSQC) VM_CL_ReadChar, // #361 float() readchar (EXT_CSQC) VM_CL_ReadShort, // #362 float() readshort (EXT_CSQC) VM_CL_ReadLong, // #363 float() readlong (EXT_CSQC) VM_CL_ReadCoord, // #364 float() readcoord (EXT_CSQC) VM_CL_ReadAngle, // #365 float() readangle (EXT_CSQC) VM_CL_ReadString, // #366 string() readstring (EXT_CSQC) VM_CL_ReadFloat, // #367 float() readfloat (EXT_CSQC) NULL, // #368 NULL, // #369 NULL, // #370 NULL, // #371 NULL, // #372 NULL, // #373 NULL, // #374 NULL, // #375 NULL, // #376 NULL, // #377 NULL, // #378 NULL, // #379 NULL, // #380 NULL, // #381 NULL, // #382 NULL, // #383 NULL, // #384 NULL, // #385 NULL, // #386 NULL, // #387 NULL, // #388 NULL, // #389 NULL, // #390 NULL, // #391 NULL, // #392 NULL, // #393 NULL, // #394 NULL, // #395 NULL, // #396 NULL, // #397 NULL, // #398 NULL, // #399 // LordHavoc's range #400-#499 VM_CL_copyentity, // #400 void(entity from, entity to) copyentity (DP_QC_COPYENTITY) NULL, // #401 void(entity ent, float colors) setcolor (DP_QC_SETCOLOR) VM_findchain, // #402 entity(.string fld, string match) findchain (DP_QC_FINDCHAIN) VM_findchainfloat, // #403 entity(.float fld, float match) findchainfloat (DP_QC_FINDCHAINFLOAT) VM_CL_effect, // #404 void(vector org, string modelname, float startframe, float endframe, float framerate) effect (DP_SV_EFFECT) VM_CL_te_blood, // #405 void(vector org, vector velocity, float howmany) te_blood (DP_TE_BLOOD) VM_CL_te_bloodshower, // #406 void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower (DP_TE_BLOODSHOWER) VM_CL_te_explosionrgb, // #407 void(vector org, vector color) te_explosionrgb (DP_TE_EXPLOSIONRGB) VM_CL_te_particlecube, // #408 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube (DP_TE_PARTICLECUBE) VM_CL_te_particlerain, // #409 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain (DP_TE_PARTICLERAIN) VM_CL_te_particlesnow, // #410 void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow (DP_TE_PARTICLESNOW) VM_CL_te_spark, // #411 void(vector org, vector vel, float howmany) te_spark (DP_TE_SPARK) VM_CL_te_gunshotquad, // #412 void(vector org) te_gunshotquad (DP_QUADEFFECTS1) VM_CL_te_spikequad, // #413 void(vector org) te_spikequad (DP_QUADEFFECTS1) VM_CL_te_superspikequad, // #414 void(vector org) te_superspikequad (DP_QUADEFFECTS1) VM_CL_te_explosionquad, // #415 void(vector org) te_explosionquad (DP_QUADEFFECTS1) VM_CL_te_smallflash, // #416 void(vector org) te_smallflash (DP_TE_SMALLFLASH) VM_CL_te_customflash, // #417 void(vector org, float radius, float lifetime, vector color) te_customflash (DP_TE_CUSTOMFLASH) VM_CL_te_gunshot, // #418 void(vector org) te_gunshot (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_spike, // #419 void(vector org) te_spike (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_superspike, // #420 void(vector org) te_superspike (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_explosion, // #421 void(vector org) te_explosion (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_tarexplosion, // #422 void(vector org) te_tarexplosion (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_wizspike, // #423 void(vector org) te_wizspike (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_knightspike, // #424 void(vector org) te_knightspike (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_lavasplash, // #425 void(vector org) te_lavasplash (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_teleport, // #426 void(vector org) te_teleport (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_explosion2, // #427 void(vector org, float colorstart, float colorlength) te_explosion2 (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_lightning1, // #428 void(entity own, vector start, vector end) te_lightning1 (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_lightning2, // #429 void(entity own, vector start, vector end) te_lightning2 (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_lightning3, // #430 void(entity own, vector start, vector end) te_lightning3 (DP_TE_STANDARDEFFECTBUILTINS) VM_CL_te_beam, // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS) VM_vectorvectors, // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS) VM_CL_te_plasmaburn, // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN) VM_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE) VM_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE) VM_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE) VM_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE) VM_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE) VM_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE) NULL, // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND) VM_tokenize, // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND) VM_argv, // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND) VM_CL_setattachment, // #443 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) VM_search_begin, // #444 float(string pattern, float caseinsensitive, float quiet) search_begin (DP_QC_FS_SEARCH) VM_search_end, // #445 void(float handle) search_end (DP_QC_FS_SEARCH) VM_search_getsize, // #446 float(float handle) search_getsize (DP_QC_FS_SEARCH) VM_search_getfilename, // #447 string(float handle, float num) search_getfilename (DP_QC_FS_SEARCH) VM_cvar_string, // #448 string(string s) cvar_string (DP_QC_CVAR_STRING) VM_findflags, // #449 entity(entity start, .float fld, float match) findflags (DP_QC_FINDFLAGS) VM_findchainflags, // #450 entity(.float fld, float match) findchainflags (DP_QC_FINDCHAINFLAGS) VM_CL_gettagindex, // #451 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) VM_CL_gettaginfo, // #452 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) NULL, // #453 void(entity clent) dropclient (DP_SV_DROPCLIENT) NULL, // #454 entity() spawnclient (DP_SV_BOTCLIENT) NULL, // #455 float(entity clent) clienttype (DP_SV_BOTCLIENT) NULL, // #456 void(float to, string s) WriteUnterminatedString (DP_SV_WRITEUNTERMINATEDSTRING) VM_CL_te_flamejet, // #457 void(vector org, vector vel, float howmany) te_flamejet (DP_TE_FLAMEJET) NULL, // #458 VM_ftoe, // #459 entity(float num) entitybyindex (DP_QC_EDICT_NUM) VM_buf_create, // #460 float() buf_create (DP_QC_STRINGBUFFERS) VM_buf_del, // #461 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) VM_buf_getsize, // #462 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) VM_buf_copy, // #463 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) VM_buf_sort, // #464 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) VM_buf_implode, // #465 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) VM_bufstr_get, // #466 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) VM_bufstr_set, // #467 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) VM_bufstr_add, // #468 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) VM_bufstr_free, // #469 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) NULL, // #470 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) VM_strtolower, // #480 string(string s) VM_strtolower (DP_QC_STRING_CASE_FUNCTIONS) VM_strtoupper, // #481 string(string s) VM_strtoupper (DP_QC_STRING_CASE_FUNCTIONS) VM_cvar_defstring, // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTRING) VM_CL_pointsound, // #483 void(vector origin, string sample, float volume, float attenuation) pointsound (DP_SV_POINTSOUND) VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute VM_gecko_create, // #487 float gecko_create( string name ) VM_gecko_destroy, // #488 void gecko_destroy( string name ) VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) VM_numentityfields, // #496 float() numentityfields = #496; (QP_QC_ENTITYDATA) VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) VM_CL_ReadPicture, // #501 string() ReadPicture = #501; VM_CL_boxparticles, // #502 void(float effectnum, entity own, vector origin_from, vector origin_to, vector dir_from, vector dir_to, float count) boxparticles (DP_CSQC_BOXPARTICLES) VM_whichpack, // #503 string(string) whichpack = #503; VM_CL_GetEntity, // #504 float(float entitynum, float fldnum) getentity = #504; vector(float entitynum, float fldnum) getentityvec = #504; NULL, // #505 NULL, // #506 NULL, // #507 NULL, // #508 NULL, // #509 VM_uri_escape, // #510 string(string in) uri_escape = #510; VM_uri_unescape, // #511 string(string in) uri_unescape = #511; VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) VM_gettime, // #519 float(float timer) gettime = #519; (DP_QC_GETTIME) VM_keynumtostring, // #520 string keynumtostring(float keynum) VM_findkeysforcommand, // #521 string findkeysforcommand(string command[, float bindmap]) VM_CL_InitParticleSpawner, // #522 void(float max_themes) initparticlespawner (DP_CSQC_SPAWNPARTICLE) VM_CL_ResetParticle, // #523 void() resetparticle (DP_CSQC_SPAWNPARTICLE) VM_CL_ParticleTheme, // #524 void(float theme) particletheme (DP_CSQC_SPAWNPARTICLE) VM_CL_ParticleThemeSave, // #525 void() particlethemesave, void(float theme) particlethemeupdate (DP_CSQC_SPAWNPARTICLE) VM_CL_ParticleThemeFree, // #526 void() particlethemefree (DP_CSQC_SPAWNPARTICLE) VM_CL_SpawnParticle, // #527 float(vector org, vector vel, [float theme]) particle (DP_CSQC_SPAWNPARTICLE) VM_CL_SpawnParticleDelayed, // #528 float(vector org, vector vel, float delay, float collisiondelay, [float theme]) delayedparticle (DP_CSQC_SPAWNPARTICLE) VM_loadfromdata, // #529 VM_loadfromfile, // #530 VM_CL_setpause, // #531 float(float ispaused) setpause = #531 (DP_CSQC_SETPAUSE) VM_log, // #532 VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) NULL, // #539 VM_physics_enable, // #540 void(entity e, float physics_enabled) physics_enable = #540; (DP_PHYSICS_ODE) VM_physics_addforce, // #541 void(entity e, vector force, vector relative_ofs) physics_addforce = #541; (DP_PHYSICS_ODE) VM_physics_addtorque, // #542 void(entity e, vector torque) physics_addtorque = #542; (DP_PHYSICS_ODE) NULL, // #543 NULL, // #544 NULL, // #545 NULL, // #546 NULL, // #547 NULL, // #548 NULL, // #549 NULL, // #550 NULL, // #551 NULL, // #552 NULL, // #553 NULL, // #554 NULL, // #555 NULL, // #556 NULL, // #557 NULL, // #558 NULL, // #559 NULL, // #560 NULL, // #561 NULL, // #562 NULL, // #563 NULL, // #564 NULL, // #565 NULL, // #566 NULL, // #567 NULL, // #568 NULL, // #569 NULL, // #570 NULL, // #571 NULL, // #572 NULL, // #573 NULL, // #574 NULL, // #575 NULL, // #576 NULL, // #577 NULL, // #578 NULL, // #579 NULL, // #580 NULL, // #581 NULL, // #582 NULL, // #583 NULL, // #584 NULL, // #585 NULL, // #586 NULL, // #587 NULL, // #588 NULL, // #589 NULL, // #590 NULL, // #591 NULL, // #592 NULL, // #593 NULL, // #594 NULL, // #595 NULL, // #596 NULL, // #597 NULL, // #598 NULL, // #599 NULL, // #600 NULL, // #601 NULL, // #602 NULL, // #603 NULL, // #604 VM_callfunction, // #605 VM_writetofile, // #606 VM_isfunction, // #607 NULL, // #608 NULL, // #609 VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) NULL, // #611 NULL, // #612 VM_parseentitydata, // #613 NULL, // #614 NULL, // #615 NULL, // #616 NULL, // #617 NULL, // #618 NULL, // #619 NULL, // #620 NULL, // #621 NULL, // #622 NULL, // #623 VM_CL_getextresponse, // #624 string getextresponse(void) NULL, // #625 NULL, // #626 VM_sprintf, // #627 string sprintf(string format, ...) VM_getsurfacenumtriangles, // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE) VM_getsurfacetriangle, // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE) VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind VM_getbindmaps, // #631 vector(void) getbindmap VM_setbindmaps, // #632 float(vector bm) setbindmap NULL, // #633 NULL, // #634 NULL, // #635 NULL, // #636 NULL, // #637 VM_CL_RotateMoves, // #638 VM_digest_hex, // #639 VM_CL_V_CalcRefdef, // #640 void(entity e) V_CalcRefdef (DP_CSQC_V_CALCREFDEF) NULL, // #641 VM_coverage, // #642 NULL }; const int vm_cl_numbuiltins = sizeof(vm_cl_builtins) / sizeof(prvm_builtin_t); void VM_Polygons_Reset(prvm_prog_t *prog) { vmpolygons_t *polys = &prog->vmpolygons; // TODO: replace vm_polygons stuff with a more general debugging polygon system, and make vm_polygons functions use that system if(polys->initialized) { Mem_FreePool(&polys->pool); polys->initialized = false; } } void CLVM_init_cmd(prvm_prog_t *prog) { VM_Cmd_Init(prog); VM_Polygons_Reset(prog); } void CLVM_reset_cmd(prvm_prog_t *prog) { World_End(&cl.world); VM_Cmd_Reset(prog); VM_Polygons_Reset(prog); } darkplaces/darkplaces.dev0000664000175000017500000005406613067716220015007 0ustar kalevkalev[Project] FileName=darkplaces.dev Name=DarkPlaces UnitCount=175 Type=0 Ver=1 ObjFiles= Includes= Libs= PrivateResource=darkplaces_private.rc ResourceIncludes= MakeIncludes= Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@_ CppCompiler= Linker=-lwinmm -lws2_32 -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -Wl,--large-address-aware_@@_ IsCpp=0 Icon=darkplaces.ico ExeOutput= ObjectOutput= OverrideOutput=1 OverrideOutputName=darkplaces.exe HostApplication= Folders="Header Files","Source Files" CommandLine= UseCustomMakefile=0 CustomMakefile= IncludeVersionInfo=1 SupportXPThemes=0 CompilerSet=0 CompilerSettings=0000000000000000000100 [Unit1] FileName=dpvsimpledecode.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit2] FileName=cdaudio.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit3] FileName=cl_collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit4] FileName=cl_screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit5] FileName=cl_video.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit6] FileName=client.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit7] FileName=clprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit8] FileName=cmd.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit9] FileName=collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit10] FileName=common.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit11] FileName=conproc.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit12] FileName=console.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit13] FileName=snd_main.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit14] FileName=curves.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit15] FileName=cvar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit16] FileName=bspfile.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit17] FileName=draw.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit18] FileName=fs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit19] FileName=gl_backend.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit20] FileName=polygon.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit21] FileName=glquake.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit22] FileName=image.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit23] FileName=input.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit24] FileName=jpeg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit25] FileName=keys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit26] FileName=lhnet.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit27] FileName=mathlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit28] FileName=matrixlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit29] FileName=menu.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit30] FileName=meshqueue.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit31] FileName=model_alias.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit32] FileName=model_brush.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit33] FileName=model_shared.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit34] FileName=model_sprite.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit35] FileName=model_zymotic.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit36] FileName=modelgen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit37] FileName=mprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit38] FileName=netconn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit39] FileName=palette.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit40] FileName=portals.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit41] FileName=pr_comp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit42] FileName=progdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit43] FileName=progs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit44] FileName=progsvm.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit45] FileName=protocol.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit46] FileName=prvm_execprogram.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit47] FileName=qtypes.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit48] FileName=quakedef.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit49] FileName=r_lerpanim.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit50] FileName=r_modules.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit51] FileName=r_shadow.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit52] FileName=r_textures.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit53] FileName=render.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit54] FileName=resource.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit55] FileName=sbar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit56] FileName=screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit57] FileName=server.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit58] FileName=snd_ogg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit59] FileName=snd_wav.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit60] FileName=sound.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit61] FileName=spritegn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit62] FileName=sys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit63] FileName=vid.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit64] FileName=wad.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit65] FileName=world.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit66] FileName=zone.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit67] FileName=zone.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit68] FileName=cd_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit69] FileName=cd_win.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit70] FileName=cl_collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit71] FileName=cl_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit72] FileName=cl_input.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit73] FileName=cl_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit74] FileName=cl_parse.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit75] FileName=cl_particles.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit76] FileName=cl_screen.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit77] FileName=cl_video.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit78] FileName=cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit79] FileName=collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit80] FileName=common.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit81] FileName=conproc.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit82] FileName=console.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit83] FileName=polygon.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit84] FileName=curves.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit85] FileName=cvar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit86] FileName=dpvsimpledecode.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit87] FileName=filematch.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit88] FileName=fractalnoise.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit89] FileName=fs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit90] FileName=gl_backend.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit91] FileName=gl_draw.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit92] FileName=gl_rmain.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit93] FileName=gl_rsurf.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit94] FileName=gl_textures.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit95] FileName=host.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit96] FileName=host_cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit97] FileName=image.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit98] FileName=jpeg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit99] FileName=keys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit100] FileName=lhnet.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit101] FileName=mathlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit102] FileName=matrixlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit103] FileName=menu.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit104] FileName=meshqueue.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit105] FileName=model_alias.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit106] FileName=model_brush.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit107] FileName=model_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit108] FileName=model_sprite.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit109] FileName=netconn.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit110] FileName=palette.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit111] FileName=portals.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit112] FileName=protocol.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit113] FileName=prvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit114] FileName=prvm_edict.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit115] FileName=prvm_exec.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit116] FileName=builddate.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit118] FileName=r_lerpanim.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit119] FileName=r_lightning.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit120] FileName=r_modules.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit121] FileName=r_shadow.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit123] FileName=r_sprites.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit124] FileName=sbar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit125] FileName=snd_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit126] FileName=snd_mem.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit127] FileName=snd_mix.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit128] FileName=snd_ogg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit129] FileName=snd_wav.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit130] FileName=snd_win.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit131] FileName=sv_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit132] FileName=sv_move.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit133] FileName=sv_phys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit134] FileName=sv_user.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit135] FileName=sys_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit136] FileName=sys_win.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit137] FileName=vid_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit138] FileName=vid_wgl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit139] FileName=view.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit140] FileName=wad.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit141] FileName=world.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit142] FileName=svvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit143] FileName=mvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit145] FileName=csprogs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit146] FileName=image_png.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit147] FileName=lhfont.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit148] FileName=mdfour.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit149] FileName=model_dpmodel.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit150] FileName=model_psk.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit151] FileName=csprogs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit152] FileName=image_png.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit153] FileName=mdfour.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit154] FileName=libcurl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit155] FileName=libcurl.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit156] FileName=clvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit157] FileName=svbsp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit117] FileName=r_explosion.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [VersionInfo] Major=1 Minor=0 Release=0 Build=0 LanguageID=1033 CharsetID=1252 CompanyName=Forest Hale Digital Services FileVersion=1.0 FileDescription=DarkPlaces Game Engine InternalName=darkplaces.exe LegalCopyright=id Software, Forest Hale, and contributors LegalTrademarks= OriginalFilename=darkplaces.exe ProductName=DarkPlaces ProductVersion=1.0 AutoIncBuildNr=0 [Unit122] FileName=r_sky.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit144] FileName=prvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit158] FileName=svbsp.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit159] FileName=sv_demo.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit160] FileName=sv_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit161] FileName=snd_modplug.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit162] FileName=snd_modplug.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit163] FileName=cl_gecko.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit164] FileName=cl_gecko.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit165] FileName=cl_dyntexture.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit166] FileName=cl_dyntexture.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit167] FileName=hmac.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit168] FileName=hmac.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit169] FileName=cap_avi.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit170] FileName=cap_avi.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit171] FileName=cap_ogg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit172] FileName=cap_ogg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit173] FileName=utf8lib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit174] FileName=ft2.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit175] FileName=bih.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= darkplaces/prvm_edict.c0000664000175000017500000030414613067716222014475 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // AK new vm #include "quakedef.h" #include "progsvm.h" #include "csprogs.h" prvm_prog_t prvm_prog_list[PRVM_PROG_MAX]; int prvm_type_size[8] = {1,sizeof(string_t)/4,1,3,1,1,sizeof(func_t)/4,sizeof(void *)/4}; prvm_eval_t prvm_badvalue; // used only for error returns cvar_t prvm_language = {CVAR_SAVE, "prvm_language", "", "when set, loads PROGSFILE.LANGUAGENAME.po and common.LANGUAGENAME.po for string translations; when set to dump, PROGSFILE.pot is written from the strings in the progs"}; // LordHavoc: prints every opcode as it executes - warning: this is significant spew cvar_t prvm_traceqc = {0, "prvm_traceqc", "0", "prints every QuakeC statement as it is executed (only for really thorough debugging!)"}; // LordHavoc: counts usage of each QuakeC statement cvar_t prvm_statementprofiling = {0, "prvm_statementprofiling", "0", "counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled)"}; cvar_t prvm_timeprofiling = {0, "prvm_timeprofiling", "0", "counts how long each function has been executed, these counts are displayed in prvm_profile output (if enabled)"}; cvar_t prvm_coverage = {0, "prvm_coverage", "0", "report and count coverage events (1: per-function, 2: coverage() builtin, 4: per-statement)"}; cvar_t prvm_backtraceforwarnings = {0, "prvm_backtraceforwarnings", "0", "print a backtrace for warnings too"}; cvar_t prvm_leaktest = {0, "prvm_leaktest", "0", "try to detect memory leaks in strings or entities"}; cvar_t prvm_leaktest_follow_targetname = {0, "prvm_leaktest_follow_targetname", "0", "if set, target/targetname links are considered when leak testing; this should normally not be required, as entities created during startup - e.g. info_notnull - are never considered leaky"}; cvar_t prvm_leaktest_ignore_classnames = {0, "prvm_leaktest_ignore_classnames", "", "classnames of entities to NOT leak check because they are found by find(world, classname, ...) but are actually spawned by QC code (NOT map entities)"}; cvar_t prvm_errordump = {0, "prvm_errordump", "0", "write a savegame on crash to crash-server.dmp"}; cvar_t prvm_breakpointdump = {0, "prvm_breakpointdump", "0", "write a savegame on breakpoint to breakpoint-server.dmp"}; cvar_t prvm_reuseedicts_startuptime = {0, "prvm_reuseedicts_startuptime", "2", "allows immediate re-use of freed entity slots during start of new level (value in seconds)"}; cvar_t prvm_reuseedicts_neverinsameframe = {0, "prvm_reuseedicts_neverinsameframe", "1", "never allows re-use of freed entity slots during same frame"}; static double prvm_reuseedicts_always_allow = 0; qboolean prvm_runawaycheck = true; //============================================================================ // mempool handling /* =============== PRVM_MEM_Alloc =============== */ static void PRVM_MEM_Alloc(prvm_prog_t *prog) { int i; // reserve space for the null entity aka world // check bound of max_edicts prog->max_edicts = bound(1 + prog->reserved_edicts, prog->max_edicts, prog->limit_edicts); prog->num_edicts = bound(1 + prog->reserved_edicts, prog->num_edicts, prog->max_edicts); // edictprivate_size has to be min as big prvm_edict_private_t prog->edictprivate_size = max(prog->edictprivate_size,(int)sizeof(prvm_edict_private_t)); // alloc edicts prog->edicts = (prvm_edict_t *)Mem_Alloc(prog->progs_mempool,prog->limit_edicts * sizeof(prvm_edict_t)); // alloc edict private space prog->edictprivate = Mem_Alloc(prog->progs_mempool, prog->max_edicts * prog->edictprivate_size); // alloc edict fields prog->entityfieldsarea = prog->entityfields * prog->max_edicts; prog->edictsfields = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, prog->entityfieldsarea * sizeof(prvm_vec_t)); // set edict pointers for(i = 0; i < prog->max_edicts; i++) { prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; } } /* =============== PRVM_MEM_IncreaseEdicts =============== */ void PRVM_MEM_IncreaseEdicts(prvm_prog_t *prog) { int i; if(prog->max_edicts >= prog->limit_edicts) return; prog->begin_increase_edicts(prog); // increase edicts prog->max_edicts = min(prog->max_edicts + 256, prog->limit_edicts); prog->entityfieldsarea = prog->entityfields * prog->max_edicts; prog->edictsfields = (prvm_vec_t*)Mem_Realloc(prog->progs_mempool, (void *)prog->edictsfields, prog->entityfieldsarea * sizeof(prvm_vec_t)); prog->edictprivate = (void *)Mem_Realloc(prog->progs_mempool, (void *)prog->edictprivate, prog->max_edicts * prog->edictprivate_size); //set e and v pointers for(i = 0; i < prog->max_edicts; i++) { prog->edicts[i].priv.required = (prvm_edict_private_t *)((unsigned char *)prog->edictprivate + i * prog->edictprivate_size); prog->edicts[i].fields.fp = prog->edictsfields + i * prog->entityfields; } prog->end_increase_edicts(prog); } //============================================================================ // normal prvm int PRVM_ED_FindFieldOffset(prvm_prog_t *prog, const char *field) { ddef_t *d; d = PRVM_ED_FindField(prog, field); if (!d) return -1; return d->ofs; } int PRVM_ED_FindGlobalOffset(prvm_prog_t *prog, const char *global) { ddef_t *d; d = PRVM_ED_FindGlobal(prog, global); if (!d) return -1; return d->ofs; } func_t PRVM_ED_FindFunctionOffset(prvm_prog_t *prog, const char *function) { mfunction_t *f; f = PRVM_ED_FindFunction(prog, function); if (!f) return 0; return (func_t)(f - prog->functions); } /* ================= PRVM_ProgFromString ================= */ prvm_prog_t *PRVM_ProgFromString(const char *str) { if (!strcmp(str, "server")) return SVVM_prog; if (!strcmp(str, "client")) return CLVM_prog; #ifdef CONFIG_MENU if (!strcmp(str, "menu")) return MVM_prog; #endif return NULL; } /* ================= PRVM_FriendlyProgFromString ================= */ prvm_prog_t *PRVM_FriendlyProgFromString(const char *str) { prvm_prog_t *prog = PRVM_ProgFromString(str); if (!prog) { Con_Printf("%s: unknown program name\n", str); return NULL; } if (!prog->loaded) { Con_Printf("%s: program is not loaded\n", str); return NULL; } return prog; } /* ================= PRVM_ED_ClearEdict Sets everything to NULL. Nota bene: this also marks the entity as allocated if it has been previously freed and sets the allocation origin. ================= */ void PRVM_ED_ClearEdict(prvm_prog_t *prog, prvm_edict_t *e) { memset(e->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); e->priv.required->free = false; e->priv.required->freetime = realtime; if(e->priv.required->allocation_origin) Mem_Free((char *)e->priv.required->allocation_origin); e->priv.required->allocation_origin = PRVM_AllocationOrigin(prog); // AK: Let the init_edict function determine if something needs to be initialized prog->init_edict(prog, e); } const char *PRVM_AllocationOrigin(prvm_prog_t *prog) { char *buf = NULL; if(prog->leaktest_active) if(prog->depth > 0) // actually in QC code and not just parsing the entities block of a map/savegame { buf = (char *)PRVM_Alloc(256); PRVM_ShortStackTrace(prog, buf, 256); } return buf; } /* ================= PRVM_ED_CanAlloc Returns if this particular edict could get allocated by PRVM_ED_Alloc ================= */ qboolean PRVM_ED_CanAlloc(prvm_prog_t *prog, prvm_edict_t *e) { if(!e->priv.required->free) return false; if(prvm_reuseedicts_always_allow == realtime) return true; if(realtime <= e->priv.required->freetime + 0.1 && prvm_reuseedicts_neverinsameframe.integer) return false; // never allow reuse in same frame (causes networking trouble) if(e->priv.required->freetime < prog->starttime + prvm_reuseedicts_startuptime.value) return true; if(realtime > e->priv.required->freetime + 1) return true; return false; // entity slot still blocked because the entity was freed less than one second ago } /* ================= PRVM_ED_Alloc Either finds a free edict, or allocates a new one. Try to avoid reusing an entity that was recently freed, because it can cause the client to think the entity morphed into something else instead of being removed and recreated, which can cause interpolated angles and bad trails. ================= */ prvm_edict_t *PRVM_ED_Alloc(prvm_prog_t *prog) { int i; prvm_edict_t *e; // the client qc dont need maxclients // thus it doesnt need to use svs.maxclients // AK: changed i=svs.maxclients+1 // AK: changed so the edict 0 wont spawn -> used as reserved/world entity // although the menu/client has no world for (i = prog->reserved_edicts + 1;i < prog->num_edicts;i++) { e = PRVM_EDICT_NUM(i); if(PRVM_ED_CanAlloc(prog, e)) { PRVM_ED_ClearEdict (prog, e); return e; } } if (i == prog->limit_edicts) prog->error_cmd("%s: PRVM_ED_Alloc: no free edicts", prog->name); prog->num_edicts++; if (prog->num_edicts >= prog->max_edicts) PRVM_MEM_IncreaseEdicts(prog); e = PRVM_EDICT_NUM(i); PRVM_ED_ClearEdict(prog, e); return e; } /* ================= PRVM_ED_Free Marks the edict as free FIXME: walk all entities and NULL out references to this entity ================= */ void PRVM_ED_Free(prvm_prog_t *prog, prvm_edict_t *ed) { // dont delete the null entity (world) or reserved edicts if (ed - prog->edicts <= prog->reserved_edicts) return; prog->free_edict(prog, ed); ed->priv.required->free = true; ed->priv.required->freetime = realtime; if(ed->priv.required->allocation_origin) { Mem_Free((char *)ed->priv.required->allocation_origin); ed->priv.required->allocation_origin = NULL; } } //=========================================================================== /* ============ PRVM_ED_GlobalAtOfs ============ */ static ddef_t *PRVM_ED_GlobalAtOfs (prvm_prog_t *prog, int ofs) { ddef_t *def; int i; for (i = 0;i < prog->numglobaldefs;i++) { def = &prog->globaldefs[i]; if (def->ofs == ofs) return def; } return NULL; } /* ============ PRVM_ED_FieldAtOfs ============ */ ddef_t *PRVM_ED_FieldAtOfs (prvm_prog_t *prog, int ofs) { ddef_t *def; int i; for (i = 0;i < prog->numfielddefs;i++) { def = &prog->fielddefs[i]; if (def->ofs == ofs) return def; } return NULL; } /* ============ PRVM_ED_FindField ============ */ ddef_t *PRVM_ED_FindField (prvm_prog_t *prog, const char *name) { ddef_t *def; int i; for (i = 0;i < prog->numfielddefs;i++) { def = &prog->fielddefs[i]; if (!strcmp(PRVM_GetString(prog, def->s_name), name)) return def; } return NULL; } /* ============ PRVM_ED_FindGlobal ============ */ ddef_t *PRVM_ED_FindGlobal (prvm_prog_t *prog, const char *name) { ddef_t *def; int i; for (i = 0;i < prog->numglobaldefs;i++) { def = &prog->globaldefs[i]; if (!strcmp(PRVM_GetString(prog, def->s_name), name)) return def; } return NULL; } /* ============ PRVM_ED_FindFunction ============ */ mfunction_t *PRVM_ED_FindFunction (prvm_prog_t *prog, const char *name) { mfunction_t *func; int i; for (i = 0;i < prog->numfunctions;i++) { func = &prog->functions[i]; if (!strcmp(PRVM_GetString(prog, func->s_name), name)) return func; } return NULL; } /* ============ PRVM_ValueString Returns a string describing *data in a type specific manner ============= */ static char *PRVM_ValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength) { ddef_t *def; mfunction_t *f; int n; type = (etype_t)((int) type & ~DEF_SAVEGLOBAL); switch (type) { case ev_string: strlcpy (line, PRVM_GetString (prog, val->string), linelength); break; case ev_entity: n = val->edict; if (n < 0 || n >= prog->max_edicts) dpsnprintf (line, linelength, "entity %i (invalid!)", n); else dpsnprintf (line, linelength, "entity %i", n); break; case ev_function: f = prog->functions + val->function; dpsnprintf (line, linelength, "%s()", PRVM_GetString(prog, f->s_name)); break; case ev_field: def = PRVM_ED_FieldAtOfs ( prog, val->_int ); dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name)); break; case ev_void: dpsnprintf (line, linelength, "void"); break; case ev_float: // LordHavoc: changed from %5.1f to %10.4f dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float); break; case ev_vector: // LordHavoc: changed from %5.1f to %10.4f dpsnprintf (line, linelength, "'" VECTOR_LOSSLESS_FORMAT "'", val->vector[0], val->vector[1], val->vector[2]); break; case ev_pointer: dpsnprintf (line, linelength, "pointer"); break; default: dpsnprintf (line, linelength, "bad type %i", (int) type); break; } return line; } /* ============ PRVM_UglyValueString Returns a string describing *data in a type specific manner Easier to parse than PR_ValueString ============= */ char *PRVM_UglyValueString (prvm_prog_t *prog, etype_t type, prvm_eval_t *val, char *line, size_t linelength) { int i; const char *s; ddef_t *def; mfunction_t *f; type = (etype_t)((int)type & ~DEF_SAVEGLOBAL); switch (type) { case ev_string: // Parse the string a bit to turn special characters // (like newline, specifically) into escape codes, // this fixes saving games from various mods s = PRVM_GetString (prog, val->string); for (i = 0;i < (int)linelength - 2 && *s;) { if (*s == '\n') { line[i++] = '\\'; line[i++] = 'n'; } else if (*s == '\r') { line[i++] = '\\'; line[i++] = 'r'; } else if (*s == '\\') { line[i++] = '\\'; line[i++] = '\\'; } else if (*s == '"') { line[i++] = '\\'; line[i++] = '"'; } else line[i++] = *s; s++; } line[i] = '\0'; break; case ev_entity: dpsnprintf (line, linelength, "%i", val->edict); break; case ev_function: f = prog->functions + val->function; strlcpy (line, PRVM_GetString (prog, f->s_name), linelength); break; case ev_field: def = PRVM_ED_FieldAtOfs ( prog, val->_int ); dpsnprintf (line, linelength, ".%s", PRVM_GetString(prog, def->s_name)); break; case ev_void: dpsnprintf (line, linelength, "void"); break; case ev_float: dpsnprintf (line, linelength, FLOAT_LOSSLESS_FORMAT, val->_float); break; case ev_vector: dpsnprintf (line, linelength, VECTOR_LOSSLESS_FORMAT, val->vector[0], val->vector[1], val->vector[2]); break; default: dpsnprintf (line, linelength, "bad type %i", type); break; } return line; } /* ============ PRVM_GlobalString Returns a string with a description and the contents of a global, padded to 20 field width ============ */ char *PRVM_GlobalString (prvm_prog_t *prog, int ofs, char *line, size_t linelength) { char *s; //size_t i; ddef_t *def; prvm_eval_t *val; char valuebuf[MAX_INPUTLINE]; val = (prvm_eval_t *)&prog->globals.fp[ofs]; def = PRVM_ED_GlobalAtOfs(prog, ofs); if (!def) dpsnprintf (line, linelength, "GLOBAL%i", ofs); else { s = PRVM_ValueString (prog, (etype_t)def->type, val, valuebuf, sizeof(valuebuf)); dpsnprintf (line, linelength, "%s (=%s)", PRVM_GetString(prog, def->s_name), s); } //i = strlen(line); //for ( ; i<20 ; i++) // strcat (line," "); //strcat (line," "); return line; } char *PRVM_GlobalStringNoContents (prvm_prog_t *prog, int ofs, char *line, size_t linelength) { //size_t i; ddef_t *def; def = PRVM_ED_GlobalAtOfs(prog, ofs); if (!def) dpsnprintf (line, linelength, "GLOBAL%i", ofs); else dpsnprintf (line, linelength, "%s", PRVM_GetString(prog, def->s_name)); //i = strlen(line); //for ( ; i<20 ; i++) // strcat (line," "); //strcat (line," "); return line; } /* ============= PRVM_ED_Print For debugging ============= */ // LordHavoc: optimized this to print out much more quickly (tempstring) // LordHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print) void PRVM_ED_Print(prvm_prog_t *prog, prvm_edict_t *ed, const char *wildcard_fieldname) { size_t l; ddef_t *d; prvm_eval_t *val; int i, j; const char *name; int type; char tempstring[MAX_INPUTLINE], tempstring2[260]; // temporary string buffers char valuebuf[MAX_INPUTLINE]; if (ed->priv.required->free) { Con_Printf("%s: FREE\n",prog->name); return; } tempstring[0] = 0; dpsnprintf(tempstring, sizeof(tempstring), "\n%s EDICT %i:\n", prog->name, PRVM_NUM_FOR_EDICT(ed)); for (i = 1;i < prog->numfielddefs;i++) { d = &prog->fielddefs[i]; name = PRVM_GetString(prog, d->s_name); if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) continue; // skip _x, _y, _z vars // Check Field Name Wildcard if(wildcard_fieldname) if( !matchpattern(name, wildcard_fieldname, 1) ) // Didn't match; skip continue; val = (prvm_eval_t *)(ed->fields.fp + d->ofs); // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j=0 ; jivector[j]) break; if (j == prvm_type_size[type]) continue; if (strlen(name) > sizeof(tempstring2)-4) { memcpy (tempstring2, name, sizeof(tempstring2)-4); tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; tempstring2[sizeof(tempstring2)-1] = 0; name = tempstring2; } strlcat(tempstring, name, sizeof(tempstring)); for (l = strlen(name);l < 14;l++) strlcat(tempstring, " ", sizeof(tempstring)); strlcat(tempstring, " ", sizeof(tempstring)); name = PRVM_ValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf)); if (strlen(name) > sizeof(tempstring2)-4) { memcpy (tempstring2, name, sizeof(tempstring2)-4); tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; tempstring2[sizeof(tempstring2)-1] = 0; name = tempstring2; } strlcat(tempstring, name, sizeof(tempstring)); strlcat(tempstring, "\n", sizeof(tempstring)); if (strlen(tempstring) >= sizeof(tempstring)/2) { Con_Print(tempstring); tempstring[0] = 0; } } if (tempstring[0]) Con_Print(tempstring); } /* ============= PRVM_ED_Write For savegames ============= */ extern cvar_t developer_entityparsing; void PRVM_ED_Write (prvm_prog_t *prog, qfile_t *f, prvm_edict_t *ed) { ddef_t *d; prvm_eval_t *val; int i, j; const char *name; int type; char vabuf[1024]; char valuebuf[MAX_INPUTLINE]; FS_Print(f, "{\n"); if (ed->priv.required->free) { FS_Print(f, "}\n"); return; } for (i = 1;i < prog->numfielddefs;i++) { d = &prog->fielddefs[i]; name = PRVM_GetString(prog, d->s_name); if(developer_entityparsing.integer) Con_Printf("PRVM_ED_Write: at entity %d field %s\n", PRVM_NUM_FOR_EDICT(ed), name); //if(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) if(strlen(name) > 1 && name[strlen(name)-2] == '_') continue; // skip _x, _y, _z vars, and ALSO other _? vars as some mods expect them to be never saved (TODO: a gameplayfix for using the "more precise" condition above?) val = (prvm_eval_t *)(ed->fields.fp + d->ofs); // if the value is still all 0, skip the field type = d->type & ~DEF_SAVEGLOBAL; for (j=0 ; jivector[j]) break; if (j == prvm_type_size[type]) continue; FS_Printf(f,"\"%s\" ",name); prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_Write, ent=%d, name=%s", i, name); FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf))); prog->statestring = NULL; } FS_Print(f, "}\n"); } void PRVM_ED_PrintNum (prvm_prog_t *prog, int ent, const char *wildcard_fieldname) { PRVM_ED_Print(prog, PRVM_EDICT_NUM(ent), wildcard_fieldname); } /* ============= PRVM_ED_PrintEdicts_f For debugging, prints all the entities in the current server ============= */ void PRVM_ED_PrintEdicts_f (void) { prvm_prog_t *prog; int i; const char *wildcard_fieldname; if(Cmd_Argc() < 2 || Cmd_Argc() > 3) { Con_Print("prvm_edicts \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; if( Cmd_Argc() == 3) wildcard_fieldname = Cmd_Argv(2); else wildcard_fieldname = NULL; Con_Printf("%s: %i entities\n", prog->name, prog->num_edicts); for (i=0 ; inum_edicts ; i++) PRVM_ED_PrintNum (prog, i, wildcard_fieldname); } /* ============= PRVM_ED_PrintEdict_f For debugging, prints a single edict ============= */ static void PRVM_ED_PrintEdict_f (void) { prvm_prog_t *prog; int i; const char *wildcard_fieldname; if(Cmd_Argc() < 3 || Cmd_Argc() > 4) { Con_Print("prvm_edict \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; i = atoi (Cmd_Argv(2)); if (i >= prog->num_edicts) { Con_Print("Bad edict number\n"); return; } if( Cmd_Argc() == 4) // Optional Wildcard Provided wildcard_fieldname = Cmd_Argv(3); else // Use All wildcard_fieldname = NULL; PRVM_ED_PrintNum (prog, i, wildcard_fieldname); } /* ============= PRVM_ED_Count For debugging ============= */ // 2 possibilities : 1. just displaying the active edict count // 2. making a function pointer [x] static void PRVM_ED_Count_f (void) { prvm_prog_t *prog; if(Cmd_Argc() != 2) { Con_Print("prvm_count \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; prog->count_edicts(prog); } /* ============================================================================== ARCHIVING GLOBALS FIXME: need to tag constants, doesn't really work ============================================================================== */ /* ============= PRVM_ED_WriteGlobals ============= */ void PRVM_ED_WriteGlobals (prvm_prog_t *prog, qfile_t *f) { ddef_t *def; int i; const char *name; int type; char vabuf[1024]; char valuebuf[MAX_INPUTLINE]; FS_Print(f,"{\n"); for (i = 0;i < prog->numglobaldefs;i++) { def = &prog->globaldefs[i]; type = def->type; if ( !(def->type & DEF_SAVEGLOBAL) ) continue; type &= ~DEF_SAVEGLOBAL; if (type != ev_string && type != ev_float && type != ev_entity) continue; name = PRVM_GetString(prog, def->s_name); if(developer_entityparsing.integer) Con_Printf("PRVM_ED_WriteGlobals: at global %s\n", name); prog->statestring = va(vabuf, sizeof(vabuf), "PRVM_ED_WriteGlobals, name=%s", name); FS_Printf(f,"\"%s\" ", name); FS_Printf(f,"\"%s\"\n", PRVM_UglyValueString(prog, (etype_t)type, (prvm_eval_t *)&prog->globals.fp[def->ofs], valuebuf, sizeof(valuebuf))); prog->statestring = NULL; } FS_Print(f,"}\n"); } /* ============= PRVM_ED_ParseGlobals ============= */ void PRVM_ED_ParseGlobals (prvm_prog_t *prog, const char *data) { char keyname[MAX_INPUTLINE]; ddef_t *key; while (1) { // parse key if (!COM_ParseToken_Simple(&data, false, false, true)) prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace"); if (com_token[0] == '}') break; if (developer_entityparsing.integer) Con_Printf("Key: \"%s\"", com_token); strlcpy (keyname, com_token, sizeof(keyname)); // parse value if (!COM_ParseToken_Simple(&data, false, true, true)) prog->error_cmd("PRVM_ED_ParseGlobals: EOF without closing brace"); if (developer_entityparsing.integer) Con_Printf(" \"%s\"\n", com_token); if (com_token[0] == '}') prog->error_cmd("PRVM_ED_ParseGlobals: closing brace without data"); key = PRVM_ED_FindGlobal (prog, keyname); if (!key) { Con_DPrintf("'%s' is not a global on %s\n", keyname, prog->name); continue; } if (!PRVM_ED_ParseEpair(prog, NULL, key, com_token, true)) prog->error_cmd("PRVM_ED_ParseGlobals: parse error"); } } //============================================================================ /* ============= PRVM_ED_ParseEval Can parse either fields or globals returns false if error ============= */ qboolean PRVM_ED_ParseEpair(prvm_prog_t *prog, prvm_edict_t *ent, ddef_t *key, const char *s, qboolean parsebackslash) { int i, l; char *new_p; ddef_t *def; prvm_eval_t *val; mfunction_t *func; if (ent) val = (prvm_eval_t *)(ent->fields.fp + key->ofs); else val = (prvm_eval_t *)(prog->globals.fp + key->ofs); switch (key->type & ~DEF_SAVEGLOBAL) { case ev_string: l = (int)strlen(s) + 1; val->string = PRVM_AllocString(prog, l, &new_p); for (i = 0;i < l;i++) { if (s[i] == '\\' && s[i+1] && parsebackslash) { i++; if (s[i] == 'n') *new_p++ = '\n'; else if (s[i] == 'r') *new_p++ = '\r'; else *new_p++ = s[i]; } else *new_p++ = s[i]; } break; case ev_float: while (*s && ISWHITESPACE(*s)) s++; val->_float = atof(s); break; case ev_vector: for (i = 0;i < 3;i++) { while (*s && ISWHITESPACE(*s)) s++; if (!*s) break; val->vector[i] = atof(s); while (!ISWHITESPACE(*s)) s++; if (!*s) break; } break; case ev_entity: while (*s && ISWHITESPACE(*s)) s++; i = atoi(s); if (i >= prog->limit_edicts) Con_Printf("PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= MAX_EDICTS %u) on %s\n", (unsigned int)i, prog->limit_edicts, prog->name); while (i >= prog->max_edicts) PRVM_MEM_IncreaseEdicts(prog); // if IncreaseEdicts was called the base pointer needs to be updated if (ent) val = (prvm_eval_t *)(ent->fields.fp + key->ofs); val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i)); break; case ev_field: if (*s != '.') { Con_DPrintf("PRVM_ED_ParseEpair: Bogus field name %s in %s\n", s, prog->name); return false; } def = PRVM_ED_FindField(prog, s + 1); if (!def) { Con_DPrintf("PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, prog->name); return false; } val->_int = def->ofs; break; case ev_function: func = PRVM_ED_FindFunction(prog, s); if (!func) { Con_Printf("PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, prog->name); return false; } val->function = func - prog->functions; break; default: Con_Printf("PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(prog, key->s_name), prog->name); return false; } return true; } /* ============= PRVM_GameCommand_f Console command to send a string to QC function GameCommand of the indicated progs Usage: sv_cmd adminmsg 3 "do not teamkill" cl_cmd someclientcommand menu_cmd somemenucommand All progs can support this extension; sg calls it in server QC, cg in client QC, mg in menu QC. ============= */ static void PRVM_GameCommand(const char *whichprogs, const char *whichcmd) { prvm_prog_t *prog; if(Cmd_Argc() < 1) { Con_Printf("%s text...\n", whichcmd); return; } if (!(prog = PRVM_FriendlyProgFromString(whichprogs))) return; if(!PRVM_allfunction(GameCommand)) { Con_Printf("%s program do not support GameCommand!\n", whichprogs); } else { int restorevm_tempstringsbuf_cursize; const char *s; s = Cmd_Args(); restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, s ? s : ""); prog->ExecuteProgram(prog, PRVM_allfunction(GameCommand), "QC function GameCommand is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } } static void PRVM_GameCommand_Server_f(void) { PRVM_GameCommand("server", "sv_cmd"); } static void PRVM_GameCommand_Client_f(void) { PRVM_GameCommand("client", "cl_cmd"); } static void PRVM_GameCommand_Menu_f(void) { PRVM_GameCommand("menu", "menu_cmd"); } /* ============= PRVM_ED_EdictGet_f Console command to load a field of a specified edict ============= */ static void PRVM_ED_EdictGet_f(void) { prvm_prog_t *prog; prvm_edict_t *ed; ddef_t *key; const char *s; prvm_eval_t *v; char valuebuf[MAX_INPUTLINE]; if(Cmd_Argc() != 4 && Cmd_Argc() != 5) { Con_Print("prvm_edictget []\n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); if((key = PRVM_ED_FindField(prog, Cmd_Argv(3))) == 0) { Con_Printf("Key %s not found !\n", Cmd_Argv(3)); goto fail; } v = (prvm_eval_t *)(ed->fields.fp + key->ofs); s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf)); if(Cmd_Argc() == 5) { cvar_t *cvar = Cvar_FindVar(Cmd_Argv(4)); if (cvar && cvar->flags & CVAR_READONLY) { Con_Printf("prvm_edictget: %s is read-only\n", cvar->name); goto fail; } Cvar_Get(Cmd_Argv(4), s, 0, NULL); } else Con_Printf("%s\n", s); fail: ; } static void PRVM_ED_GlobalGet_f(void) { prvm_prog_t *prog; ddef_t *key; const char *s; prvm_eval_t *v; char valuebuf[MAX_INPUTLINE]; if(Cmd_Argc() != 3 && Cmd_Argc() != 4) { Con_Print("prvm_globalget []\n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; key = PRVM_ED_FindGlobal(prog, Cmd_Argv(2)); if(!key) { Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); goto fail; } v = (prvm_eval_t *) &prog->globals.fp[key->ofs]; s = PRVM_UglyValueString(prog, (etype_t)key->type, v, valuebuf, sizeof(valuebuf)); if(Cmd_Argc() == 4) { cvar_t *cvar = Cvar_FindVar(Cmd_Argv(3)); if (cvar && cvar->flags & CVAR_READONLY) { Con_Printf("prvm_globalget: %s is read-only\n", cvar->name); goto fail; } Cvar_Get(Cmd_Argv(3), s, 0, NULL); } else Con_Printf("%s\n", s); fail: ; } /* ============= PRVM_ED_EdictSet_f Console command to set a field of a specified edict ============= */ static void PRVM_ED_EdictSet_f(void) { prvm_prog_t *prog; prvm_edict_t *ed; ddef_t *key; if(Cmd_Argc() != 5) { Con_Print("prvm_edictset \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; ed = PRVM_EDICT_NUM(atoi(Cmd_Argv(2))); if((key = PRVM_ED_FindField(prog, Cmd_Argv(3))) == 0) Con_Printf("Key %s not found !\n", Cmd_Argv(3)); else PRVM_ED_ParseEpair(prog, ed, key, Cmd_Argv(4), true); } /* ==================== PRVM_ED_ParseEdict Parses an edict out of the given string, returning the new position ed should be a properly initialized empty edict. Used for initial level load and for savegames. ==================== */ const char *PRVM_ED_ParseEdict (prvm_prog_t *prog, const char *data, prvm_edict_t *ent) { ddef_t *key; qboolean anglehack; qboolean init; char keyname[256]; size_t n; init = false; // go through all the dictionary pairs while (1) { // parse key if (!COM_ParseToken_Simple(&data, false, false, true)) prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace"); if (developer_entityparsing.integer) Con_Printf("Key: \"%s\"", com_token); if (com_token[0] == '}') break; // anglehack is to allow QuakeEd to write single scalar angles // and allow them to be turned into vectors. (FIXME...) if (!strcmp(com_token, "angle")) { strlcpy (com_token, "angles", sizeof(com_token)); anglehack = true; } else anglehack = false; // FIXME: change light to _light to get rid of this hack if (!strcmp(com_token, "light")) strlcpy (com_token, "light_lev", sizeof(com_token)); // hack for single light def strlcpy (keyname, com_token, sizeof(keyname)); // another hack to fix keynames with trailing spaces n = strlen(keyname); while (n && keyname[n-1] == ' ') { keyname[n-1] = 0; n--; } // parse value if (!COM_ParseToken_Simple(&data, false, false, true)) prog->error_cmd("PRVM_ED_ParseEdict: EOF without closing brace"); if (developer_entityparsing.integer) Con_Printf(" \"%s\"\n", com_token); if (com_token[0] == '}') prog->error_cmd("PRVM_ED_ParseEdict: closing brace without data"); init = true; // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp) if (!keyname[0]) continue; // keynames with a leading underscore are used for utility comments, // and are immediately discarded by quake if (keyname[0] == '_') continue; key = PRVM_ED_FindField (prog, keyname); if (!key) { Con_DPrintf("%s: '%s' is not a field\n", prog->name, keyname); continue; } if (anglehack) { char temp[32]; strlcpy (temp, com_token, sizeof(temp)); dpsnprintf (com_token, sizeof(com_token), "0 %s 0", temp); } if (!PRVM_ED_ParseEpair(prog, ent, key, com_token, strcmp(keyname, "wad") != 0)) prog->error_cmd("PRVM_ED_ParseEdict: parse error"); } if (!init) { ent->priv.required->free = true; ent->priv.required->freetime = realtime; } return data; } /* ================ PRVM_ED_LoadFromFile The entities are directly placed in the array, rather than allocated with PRVM_ED_Alloc, because otherwise an error loading the map would have entity number references out of order. Creates a server's entity / program execution context by parsing textual entity definitions out of an ent file. Used for both fresh maps and savegame loads. A fresh map would also need to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves. ================ */ void PRVM_ED_LoadFromFile (prvm_prog_t *prog, const char *data) { prvm_edict_t *ent; int parsed, inhibited, spawned, died; const char *funcname; mfunction_t *func; char vabuf[1024]; parsed = 0; inhibited = 0; spawned = 0; died = 0; prvm_reuseedicts_always_allow = realtime; // parse ents while (1) { // parse the opening brace if (!COM_ParseToken_Simple(&data, false, false, true)) break; if (com_token[0] != '{') prog->error_cmd("PRVM_ED_LoadFromFile: %s: found %s when expecting {", prog->name, com_token); // CHANGED: this is not conform to PR_LoadFromFile if(prog->loadintoworld) { prog->loadintoworld = false; ent = PRVM_EDICT_NUM(0); } else ent = PRVM_ED_Alloc(prog); // clear it if (ent != prog->edicts) // hack memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); data = PRVM_ED_ParseEdict (prog, data, ent); parsed++; // remove the entity ? if(!prog->load_edict(prog, ent)) { PRVM_ED_Free(prog, ent); inhibited++; continue; } if (PRVM_serverfunction(SV_OnEntityPreSpawnFunction)) { // self = ent PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPreSpawnFunction), "QC function SV_OnEntityPreSpawnFunction is missing"); } if(ent->priv.required->free) { inhibited++; continue; } // // immediately call spawn function, but only if there is a self global and a classname // if(!ent->priv.required->free) { if (!PRVM_alledictstring(ent, classname)) { Con_Print("No classname for:\n"); PRVM_ED_Print(prog, ent, NULL); PRVM_ED_Free (prog, ent); continue; } // look for the spawn function funcname = PRVM_GetString(prog, PRVM_alledictstring(ent, classname)); func = PRVM_ED_FindFunction (prog, va(vabuf, sizeof(vabuf), "spawnfunc_%s", funcname)); if(!func) if(!PRVM_allglobalfloat(require_spawnfunc_prefix)) func = PRVM_ED_FindFunction (prog, funcname); if (!func) { // check for OnEntityNoSpawnFunction if (PRVM_serverfunction(SV_OnEntityNoSpawnFunction)) { // self = ent PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityNoSpawnFunction), "QC function SV_OnEntityNoSpawnFunction is missing"); } else { if (developer.integer > 0) // don't confuse non-developers with errors { Con_Print("No spawn function for:\n"); PRVM_ED_Print(prog, ent, NULL); } PRVM_ED_Free (prog, ent); continue; // not included in "inhibited" count } } else { // self = ent PRVM_serverglobalfloat(time) = sv.time; PRVM_allglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, func - prog->functions, ""); } } if(!ent->priv.required->free) if (PRVM_serverfunction(SV_OnEntityPostSpawnFunction)) { // self = ent PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_OnEntityPostSpawnFunction), "QC function SV_OnEntityPostSpawnFunction is missing"); } spawned++; if (ent->priv.required->free) died++; } Con_DPrintf("%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", prog->name, parsed, inhibited, prog->num_edicts, spawned, died, spawned - died); prvm_reuseedicts_always_allow = 0; } static void PRVM_FindOffsets(prvm_prog_t *prog) { // field and global searches use -1 for NULL memset(&prog->fieldoffsets, -1, sizeof(prog->fieldoffsets)); memset(&prog->globaloffsets, -1, sizeof(prog->globaloffsets)); // function searches use 0 for NULL memset(&prog->funcoffsets, 0, sizeof(prog->funcoffsets)); #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) prog->fieldoffsets.x = PRVM_ED_FindFieldOffset(prog, #x); #define PRVM_DECLARE_global(x) prog->globaloffsets.x = PRVM_ED_FindGlobalOffset(prog, #x); #define PRVM_DECLARE_function(x) prog->funcoffsets.x = PRVM_ED_FindFunctionOffset(prog, #x); #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function } // not used /* typedef struct dpfield_s { int type; char *string; } dpfield_t; #define DPFIELDS (sizeof(dpfields) / sizeof(dpfield_t)) dpfield_t dpfields[] = { }; */ /* =============== PRVM_ResetProg =============== */ #define PO_HASHSIZE 16384 typedef struct po_string_s { char *key, *value; struct po_string_s *nextonhashchain; } po_string_t; typedef struct po_s { po_string_t *hashtable[PO_HASHSIZE]; } po_t; static void PRVM_PO_UnparseString(char *out, const char *in, size_t outsize) { for(;;) { switch(*in) { case 0: *out++ = 0; return; case '\a': if(outsize >= 2) { *out++ = '\\'; *out++ = 'a'; outsize -= 2; } break; case '\b': if(outsize >= 2) { *out++ = '\\'; *out++ = 'b'; outsize -= 2; } break; case '\t': if(outsize >= 2) { *out++ = '\\'; *out++ = 't'; outsize -= 2; } break; case '\r': if(outsize >= 2) { *out++ = '\\'; *out++ = 'r'; outsize -= 2; } break; case '\n': if(outsize >= 2) { *out++ = '\\'; *out++ = 'n'; outsize -= 2; } break; case '\\': if(outsize >= 2) { *out++ = '\\'; *out++ = '\\'; outsize -= 2; } break; case '"': if(outsize >= 2) { *out++ = '\\'; *out++ = '"'; outsize -= 2; } break; default: if(*in >= 0 && *in <= 0x1F) { if(outsize >= 4) { *out++ = '\\'; *out++ = '0' + ((*in & 0700) >> 6); *out++ = '0' + ((*in & 0070) >> 3); *out++ = '0' + (*in & 0007) ; outsize -= 4; } } else { if(outsize >= 1) { *out++ = *in; outsize -= 1; } } break; } ++in; } } static void PRVM_PO_ParseString(char *out, const char *in, size_t outsize) { for(;;) { switch(*in) { case 0: *out++ = 0; return; case '\\': ++in; switch(*in) { case 'a': if(outsize > 0) { *out++ = '\a'; --outsize; } break; case 'b': if(outsize > 0) { *out++ = '\b'; --outsize; } break; case 't': if(outsize > 0) { *out++ = '\t'; --outsize; } break; case 'r': if(outsize > 0) { *out++ = '\r'; --outsize; } break; case 'n': if(outsize > 0) { *out++ = '\n'; --outsize; } break; case '\\': if(outsize > 0) { *out++ = '\\'; --outsize; } break; case '"': if(outsize > 0) { *out++ = '"'; --outsize; } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': if(outsize > 0) *out = *in - '0'; ++in; if(*in >= '0' && *in <= '7') { if(outsize > 0) *out = (*out << 3) | (*in - '0'); ++in; } if(*in >= '0' && *in <= '7') { if(outsize > 0) *out = (*out << 3) | (*in - '0'); ++in; } --in; if(outsize > 0) { ++out; --outsize; } break; default: if(outsize > 0) { *out++ = *in; --outsize; } break; } break; default: if(outsize > 0) { *out++ = *in; --outsize; } break; } ++in; } } static po_t *PRVM_PO_Load(const char *filename, const char *filename2, mempool_t *pool) { po_t *po = NULL; const char *p, *q; int mode; char inbuf[MAX_INPUTLINE]; char decodedbuf[MAX_INPUTLINE]; size_t decodedpos; int hashindex; po_string_t thisstr; int i; for (i = 0; i < 2; ++i) { const char *buf = (const char *) FS_LoadFile((i > 0 ? filename : filename2), pool, true, NULL); // first read filename2, then read filename // so that progs.dat.de.po wins over common.de.po // and within file, last item wins if(!buf) continue; if (!po) { po = (po_t *)Mem_Alloc(pool, sizeof(*po)); memset(po, 0, sizeof(*po)); } memset(&thisstr, 0, sizeof(thisstr)); // hush compiler warning p = buf; while(*p) { if(*p == '#') { // skip to newline p = strchr(p, '\n'); if(!p) break; ++p; continue; } if(*p == '\r' || *p == '\n') { ++p; continue; } if(!strncmp(p, "msgid \"", 7)) { mode = 0; p += 6; } else if(!strncmp(p, "msgstr \"", 8)) { mode = 1; p += 7; } else { p = strchr(p, '\n'); if(!p) break; ++p; continue; } decodedpos = 0; while(*p == '"') { ++p; q = strchr(p, '\n'); if(!q) break; if(*(q-1) == '\r') --q; if(*(q-1) != '"') break; if((size_t)(q - p) >= (size_t) sizeof(inbuf)) break; strlcpy(inbuf, p, q - p); // not - 1, because this adds a NUL PRVM_PO_ParseString(decodedbuf + decodedpos, inbuf, sizeof(decodedbuf) - decodedpos); decodedpos += strlen(decodedbuf + decodedpos); if(*q == '\r') ++q; if(*q == '\n') ++q; p = q; } if(mode == 0) { if(thisstr.key) Mem_Free(thisstr.key); thisstr.key = (char *)Mem_Alloc(pool, decodedpos + 1); memcpy(thisstr.key, decodedbuf, decodedpos + 1); } else if(decodedpos > 0 && thisstr.key) // skip empty translation results { thisstr.value = (char *)Mem_Alloc(pool, decodedpos + 1); memcpy(thisstr.value, decodedbuf, decodedpos + 1); hashindex = CRC_Block((const unsigned char *) thisstr.key, strlen(thisstr.key)) % PO_HASHSIZE; thisstr.nextonhashchain = po->hashtable[hashindex]; po->hashtable[hashindex] = (po_string_t *)Mem_Alloc(pool, sizeof(thisstr)); memcpy(po->hashtable[hashindex], &thisstr, sizeof(thisstr)); memset(&thisstr, 0, sizeof(thisstr)); } } Mem_Free((char *) buf); } return po; } static const char *PRVM_PO_Lookup(po_t *po, const char *str) { int hashindex = CRC_Block((const unsigned char *) str, strlen(str)) % PO_HASHSIZE; po_string_t *p = po->hashtable[hashindex]; while(p) { if(!strcmp(str, p->key)) return p->value; p = p->nextonhashchain; } return NULL; } static void PRVM_PO_Destroy(po_t *po) { int i; for(i = 0; i < PO_HASHSIZE; ++i) { po_string_t *p = po->hashtable[i]; while(p) { po_string_t *q = p; p = p->nextonhashchain; Mem_Free(q->key); Mem_Free(q->value); Mem_Free(q); } } Mem_Free(po); } void PRVM_LeakTest(prvm_prog_t *prog); void PRVM_Prog_Reset(prvm_prog_t *prog) { if (prog->loaded) { PRVM_LeakTest(prog); prog->reset_cmd(prog); Mem_FreePool(&prog->progs_mempool); if(prog->po) PRVM_PO_Destroy((po_t *) prog->po); } memset(prog,0,sizeof(prvm_prog_t)); prog->break_statement = -1; prog->watch_global_type = ev_void; prog->watch_field_type = ev_void; } /* =============== PRVM_LoadLNO =============== */ static void PRVM_LoadLNO( prvm_prog_t *prog, const char *progname ) { fs_offset_t filesize; unsigned char *lno; unsigned int *header; char filename[512]; FS_StripExtension( progname, filename, sizeof( filename ) ); strlcat( filename, ".lno", sizeof( filename ) ); lno = FS_LoadFile( filename, tempmempool, false, &filesize ); if( !lno ) { return; } /* SafeWrite (h, &lnotype, sizeof(int)); SafeWrite (h, &version, sizeof(int)); SafeWrite (h, &numglobaldefs, sizeof(int)); SafeWrite (h, &numpr_globals, sizeof(int)); SafeWrite (h, &numfielddefs, sizeof(int)); SafeWrite (h, &numstatements, sizeof(int)); SafeWrite (h, statement_linenums, numstatements*sizeof(int)); */ if ((unsigned int)filesize < (6 + prog->progs_numstatements) * sizeof(int)) { Mem_Free(lno); return; } header = (unsigned int *) lno; if( header[ 0 ] == *(unsigned int *) "LNOF" && LittleLong( header[ 1 ] ) == 1 && (unsigned int)LittleLong( header[ 2 ] ) == (unsigned int)prog->progs_numglobaldefs && (unsigned int)LittleLong( header[ 3 ] ) == (unsigned int)prog->progs_numglobals && (unsigned int)LittleLong( header[ 4 ] ) == (unsigned int)prog->progs_numfielddefs && (unsigned int)LittleLong( header[ 5 ] ) == (unsigned int)prog->progs_numstatements ) { prog->statement_linenums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) ); memcpy( prog->statement_linenums, header + 6, prog->progs_numstatements * sizeof( int ) ); /* gmqcc suports columnums */ if ((unsigned int)filesize > ((6 + 2 * prog->progs_numstatements) * sizeof( int ))) { prog->statement_columnnums = (int *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof( int ) ); memcpy( prog->statement_columnnums, header + 6 + prog->progs_numstatements, prog->progs_numstatements * sizeof( int ) ); } } Mem_Free( lno ); } /* =============== PRVM_LoadProgs =============== */ static void PRVM_UpdateBreakpoints(prvm_prog_t *prog); void PRVM_Prog_Load(prvm_prog_t *prog, const char * filename, unsigned char * data, fs_offset_t size, int numrequiredfunc, const char **required_func, int numrequiredfields, prvm_required_field_t *required_field, int numrequiredglobals, prvm_required_field_t *required_global) { int i; dprograms_t *dprograms; dstatement_t *instatements; ddef_t *infielddefs; ddef_t *inglobaldefs; int *inglobals; dfunction_t *infunctions; char *instrings; fs_offset_t filesize; int requiredglobalspace; opcode_t op; int a; int b; int c; union { unsigned int i; float f; } u; unsigned int d; char vabuf[1024]; char vabuf2[1024]; cvar_t *cvar; if (prog->loaded) prog->error_cmd("PRVM_LoadProgs: there is already a %s program loaded!", prog->name ); Host_LockSession(); // all progs can use the session cvar Crypto_LoadKeys(); // all progs might use the keys at init time if (data) { dprograms = (dprograms_t *) data; filesize = size; } else dprograms = (dprograms_t *)FS_LoadFile (filename, prog->progs_mempool, false, &filesize); if (dprograms == NULL || filesize < (fs_offset_t)sizeof(dprograms_t)) prog->error_cmd("PRVM_LoadProgs: couldn't load %s for %s", filename, prog->name); // TODO bounds check header fields (e.g. numstatements), they must never go behind end of file prog->profiletime = Sys_DirtyTime(); prog->starttime = realtime; Con_DPrintf("%s programs occupy %iK.\n", prog->name, (int)(filesize/1024)); requiredglobalspace = 0; for (i = 0;i < numrequiredglobals;i++) requiredglobalspace += required_global[i].type == ev_vector ? 3 : 1; prog->filecrc = CRC_Block((unsigned char *)dprograms, filesize); // byte swap the header prog->progs_version = LittleLong(dprograms->version); prog->progs_crc = LittleLong(dprograms->crc); if (prog->progs_version != PROG_VERSION) prog->error_cmd("%s: %s has wrong version number (%i should be %i)", prog->name, filename, prog->progs_version, PROG_VERSION); instatements = (dstatement_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_statements)); prog->progs_numstatements = LittleLong(dprograms->numstatements); inglobaldefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globaldefs)); prog->progs_numglobaldefs = LittleLong(dprograms->numglobaldefs); infielddefs = (ddef_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_fielddefs)); prog->progs_numfielddefs = LittleLong(dprograms->numfielddefs); infunctions = (dfunction_t *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_functions)); prog->progs_numfunctions = LittleLong(dprograms->numfunctions); instrings = (char *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_strings)); prog->progs_numstrings = LittleLong(dprograms->numstrings); inglobals = (int *)((unsigned char *)dprograms + LittleLong(dprograms->ofs_globals)); prog->progs_numglobals = LittleLong(dprograms->numglobals); prog->progs_entityfields = LittleLong(dprograms->entityfields); prog->numstatements = prog->progs_numstatements; prog->numglobaldefs = prog->progs_numglobaldefs; prog->numfielddefs = prog->progs_numfielddefs; prog->numfunctions = prog->progs_numfunctions; prog->numstrings = prog->progs_numstrings; prog->numglobals = prog->progs_numglobals; prog->entityfields = prog->progs_entityfields; if (LittleLong(dprograms->ofs_strings) + prog->progs_numstrings > (int)filesize) prog->error_cmd("%s: %s strings go past end of file", prog->name, filename); prog->strings = (char *)Mem_Alloc(prog->progs_mempool, prog->progs_numstrings); memcpy(prog->strings, instrings, prog->progs_numstrings); prog->stringssize = prog->progs_numstrings; prog->numknownstrings = 0; prog->maxknownstrings = 0; prog->knownstrings = NULL; prog->knownstrings_freeable = NULL; Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); // we need to expand the globaldefs and fielddefs to include engine defs prog->globaldefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobaldefs + numrequiredglobals) * sizeof(ddef_t)); prog->globals.fp = (prvm_vec_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numglobals + requiredglobalspace + 2) * sizeof(prvm_vec_t)); // + 2 is because of an otherwise occurring overrun in RETURN instruction // when trying to return the last or second-last global // (RETURN always returns a vector, there is no RETURN_F instruction) prog->fielddefs = (ddef_t *)Mem_Alloc(prog->progs_mempool, (prog->progs_numfielddefs + numrequiredfields) * sizeof(ddef_t)); // we need to convert the statements to our memory format prog->statements = (mstatement_t *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(mstatement_t)); // allocate space for profiling statement usage prog->statement_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile)); prog->explicit_profile = (double *)Mem_Alloc(prog->progs_mempool, prog->progs_numstatements * sizeof(*prog->statement_profile)); // functions need to be converted to the memory format prog->functions = (mfunction_t *)Mem_Alloc(prog->progs_mempool, sizeof(mfunction_t) * prog->progs_numfunctions); for (i = 0;i < prog->progs_numfunctions;i++) { prog->functions[i].first_statement = LittleLong(infunctions[i].first_statement); prog->functions[i].parm_start = LittleLong(infunctions[i].parm_start); prog->functions[i].s_name = LittleLong(infunctions[i].s_name); prog->functions[i].s_file = LittleLong(infunctions[i].s_file); prog->functions[i].numparms = LittleLong(infunctions[i].numparms); prog->functions[i].locals = LittleLong(infunctions[i].locals); memcpy(prog->functions[i].parm_size, infunctions[i].parm_size, sizeof(infunctions[i].parm_size)); if(prog->functions[i].first_statement >= prog->numstatements) prog->error_cmd("PRVM_LoadProgs: out of bounds function statement (function %d) in %s", i, prog->name); // TODO bounds check parm_start, s_name, s_file, numparms, locals, parm_size } // copy the globaldefs to the new globaldefs list for (i=0 ; inumglobaldefs ; i++) { prog->globaldefs[i].type = LittleShort(inglobaldefs[i].type); prog->globaldefs[i].ofs = LittleShort(inglobaldefs[i].ofs); prog->globaldefs[i].s_name = LittleLong(inglobaldefs[i].s_name); // TODO bounds check ofs, s_name } // append the required globals for (i = 0;i < numrequiredglobals;i++) { prog->globaldefs[prog->numglobaldefs].type = required_global[i].type; prog->globaldefs[prog->numglobaldefs].ofs = prog->numglobals; prog->globaldefs[prog->numglobaldefs].s_name = PRVM_SetEngineString(prog, required_global[i].name); if (prog->globaldefs[prog->numglobaldefs].type == ev_vector) prog->numglobals += 3; else prog->numglobals++; prog->numglobaldefs++; } // copy the progs fields to the new fields list for (i = 0;i < prog->numfielddefs;i++) { prog->fielddefs[i].type = LittleShort(infielddefs[i].type); if (prog->fielddefs[i].type & DEF_SAVEGLOBAL) prog->error_cmd("PRVM_LoadProgs: prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", prog->name); prog->fielddefs[i].ofs = LittleShort(infielddefs[i].ofs); prog->fielddefs[i].s_name = LittleLong(infielddefs[i].s_name); // TODO bounds check ofs, s_name } // append the required fields for (i = 0;i < numrequiredfields;i++) { prog->fielddefs[prog->numfielddefs].type = required_field[i].type; prog->fielddefs[prog->numfielddefs].ofs = prog->entityfields; prog->fielddefs[prog->numfielddefs].s_name = PRVM_SetEngineString(prog, required_field[i].name); if (prog->fielddefs[prog->numfielddefs].type == ev_vector) prog->entityfields += 3; else prog->entityfields++; prog->numfielddefs++; } // LordHavoc: TODO: reorder globals to match engine struct // LordHavoc: TODO: reorder fields to match engine struct #define remapglobal(index) (index) #define remapfield(index) (index) // copy globals // FIXME: LordHavoc: this uses a crude way to identify integer constants, rather than checking for matching globaldefs and checking their type for (i = 0;i < prog->progs_numglobals;i++) { u.i = LittleLong(inglobals[i]); // most globals are 0, we only need to deal with the ones that are not if (u.i) { d = u.i & 0xFF800000; if ((d == 0xFF800000) || (d == 0)) { // Looks like an integer (expand to int64) prog->globals.ip[remapglobal(i)] = u.i; } else { // Looks like a float (expand to double) prog->globals.fp[remapglobal(i)] = u.f; } } } // LordHavoc: TODO: support 32bit progs statement formats // copy, remap globals in statements, bounds check for (i = 0;i < prog->progs_numstatements;i++) { op = (opcode_t)LittleShort(instatements[i].op); a = (unsigned short)LittleShort(instatements[i].a); b = (unsigned short)LittleShort(instatements[i].b); c = (unsigned short)LittleShort(instatements[i].c); switch (op) { case OP_IF: case OP_IFNOT: b = (short)b; if (a >= prog->progs_numglobals || b + i < 0 || b + i >= prog->progs_numstatements) prog->error_cmd("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, prog->name); prog->statements[i].op = op; prog->statements[i].operand[0] = remapglobal(a); prog->statements[i].operand[1] = -1; prog->statements[i].operand[2] = -1; prog->statements[i].jumpabsolute = i + b; break; case OP_GOTO: a = (short)a; if (a + i < 0 || a + i >= prog->progs_numstatements) prog->error_cmd("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, prog->name); prog->statements[i].op = op; prog->statements[i].operand[0] = -1; prog->statements[i].operand[1] = -1; prog->statements[i].operand[2] = -1; prog->statements[i].jumpabsolute = i + a; break; default: Con_DPrintf("PRVM_LoadProgs: unknown opcode %d at statement %d in %s\n", (int)op, i, prog->name); break; // global global global case OP_ADD_F: case OP_ADD_V: case OP_SUB_F: case OP_SUB_V: case OP_MUL_F: case OP_MUL_V: case OP_MUL_FV: case OP_MUL_VF: case OP_DIV_F: case OP_BITAND: case OP_BITOR: case OP_GE: case OP_LE: case OP_GT: case OP_LT: case OP_AND: case OP_OR: case OP_EQ_F: case OP_EQ_V: case OP_EQ_S: case OP_EQ_E: case OP_EQ_FNC: case OP_NE_F: case OP_NE_V: case OP_NE_S: case OP_NE_E: case OP_NE_FNC: case OP_ADDRESS: case OP_LOAD_F: case OP_LOAD_FLD: case OP_LOAD_ENT: case OP_LOAD_S: case OP_LOAD_FNC: case OP_LOAD_V: if (a >= prog->progs_numglobals || b >= prog->progs_numglobals || c >= prog->progs_numglobals) prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d)", i); prog->statements[i].op = op; prog->statements[i].operand[0] = remapglobal(a); prog->statements[i].operand[1] = remapglobal(b); prog->statements[i].operand[2] = remapglobal(c); prog->statements[i].jumpabsolute = -1; break; // global none global case OP_NOT_F: case OP_NOT_V: case OP_NOT_S: case OP_NOT_FNC: case OP_NOT_ENT: if (a >= prog->progs_numglobals || c >= prog->progs_numglobals) prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); prog->statements[i].op = op; prog->statements[i].operand[0] = remapglobal(a); prog->statements[i].operand[1] = -1; prog->statements[i].operand[2] = remapglobal(c); prog->statements[i].jumpabsolute = -1; break; // 2 globals case OP_STOREP_F: case OP_STOREP_ENT: case OP_STOREP_FLD: case OP_STOREP_S: case OP_STOREP_FNC: case OP_STORE_F: case OP_STORE_ENT: case OP_STORE_FLD: case OP_STORE_S: case OP_STORE_FNC: case OP_STATE: case OP_STOREP_V: case OP_STORE_V: if (a >= prog->progs_numglobals || b >= prog->progs_numglobals) prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); prog->statements[i].op = op; prog->statements[i].operand[0] = remapglobal(a); prog->statements[i].operand[1] = remapglobal(b); prog->statements[i].operand[2] = -1; prog->statements[i].jumpabsolute = -1; break; // 1 global case OP_CALL0: if ( a < prog->progs_numglobals) if ( prog->globals.ip[remapglobal(a)] >= 0 ) if ( prog->globals.ip[remapglobal(a)] < prog->progs_numfunctions ) if ( prog->functions[prog->globals.ip[remapglobal(a)]].first_statement == -642 ) ++prog->numexplicitcoveragestatements; case OP_CALL1: case OP_CALL2: case OP_CALL3: case OP_CALL4: case OP_CALL5: case OP_CALL6: case OP_CALL7: case OP_CALL8: case OP_DONE: case OP_RETURN: if ( a >= prog->progs_numglobals) prog->error_cmd("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, prog->name); prog->statements[i].op = op; prog->statements[i].operand[0] = remapglobal(a); prog->statements[i].operand[1] = -1; prog->statements[i].operand[2] = -1; prog->statements[i].jumpabsolute = -1; break; } } if(prog->numstatements < 1) { prog->error_cmd("PRVM_LoadProgs: empty program in %s", prog->name); } else switch(prog->statements[prog->numstatements - 1].op) { case OP_RETURN: case OP_GOTO: case OP_DONE: break; default: prog->error_cmd("PRVM_LoadProgs: program may fall off the edge (does not end with RETURN, GOTO or DONE) in %s", prog->name); break; } // we're done with the file now if(!data) Mem_Free(dprograms); dprograms = NULL; // check required functions for(i=0 ; i < numrequiredfunc ; i++) if(PRVM_ED_FindFunction(prog, required_func[i]) == 0) prog->error_cmd("%s: %s not found in %s",prog->name, required_func[i], filename); PRVM_LoadLNO(prog, filename); PRVM_Init_Exec(prog); if(*prvm_language.string) // in CSQC we really shouldn't be able to change how stuff works... sorry for now // later idea: include a list of authorized .po file checksums with the csprogs { qboolean deftrans = prog == CLVM_prog; const char *realfilename = (prog != CLVM_prog ? filename : csqc_progname.string); if(deftrans) // once we have dotranslate_ strings, ALWAYS use the opt-in method! { for (i=0 ; inumglobaldefs ; i++) { const char *name; name = PRVM_GetString(prog, prog->globaldefs[i].s_name); if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) if(name && !strncmp(name, "dotranslate_", 12)) { deftrans = false; break; } } } if(!strcmp(prvm_language.string, "dump")) { qfile_t *f = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.pot", realfilename), "w", false); Con_Printf("Dumping to %s.pot\n", realfilename); if(f) { for (i=0 ; inumglobaldefs ; i++) { const char *name; name = PRVM_GetString(prog, prog->globaldefs[i].s_name); if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) { prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); const char *value = PRVM_GetString(prog, val->string); if(*value) { char buf[MAX_INPUTLINE]; PRVM_PO_UnparseString(buf, value, sizeof(buf)); FS_Printf(f, "msgid \"%s\"\nmsgstr \"\"\n\n", buf); } } } FS_Close(f); } } else { po_t *po = PRVM_PO_Load( va(vabuf, sizeof(vabuf), "%s.%s.po", realfilename, prvm_language.string), va(vabuf2, sizeof(vabuf2), "common.%s.po", prvm_language.string), prog->progs_mempool); if(po) { for (i=0 ; inumglobaldefs ; i++) { const char *name; name = PRVM_GetString(prog, prog->globaldefs[i].s_name); if(deftrans ? (!name || strncmp(name, "notranslate_", 12)) : (name && !strncmp(name, "dotranslate_", 12))) if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) { prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); const char *value = PRVM_GetString(prog, val->string); if(*value) { value = PRVM_PO_Lookup(po, value); if(value) val->string = PRVM_SetEngineString(prog, value); } } } } } } for (cvar = cvar_vars; cvar; cvar = cvar->next) cvar->globaldefindex[prog - prvm_prog_list] = -1; for (i=0 ; inumglobaldefs ; i++) { const char *name; name = PRVM_GetString(prog, prog->globaldefs[i].s_name); //Con_Printf("found var %s\n", name); if(name && !strncmp(name, "autocvar_", 9) && !(strlen(name) > 1 && name[strlen(name)-2] == '_' && (name[strlen(name)-1] == 'x' || name[strlen(name)-1] == 'y' || name[strlen(name)-1] == 'z')) ) { prvm_eval_t *val = PRVM_GLOBALFIELDVALUE(prog->globaldefs[i].ofs); cvar = Cvar_FindVar(name + 9); //Con_Printf("PRVM_LoadProgs: autocvar global %s in %s, processing...\n", name, prog->name); if(!cvar) { const char *value; char buf[64]; Con_DPrintf("PRVM_LoadProgs: no cvar for autocvar global %s in %s, creating...\n", name, prog->name); switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) { case ev_float: if((float)((int)(val->_float)) == val->_float) dpsnprintf(buf, sizeof(buf), "%i", (int)(val->_float)); else dpsnprintf(buf, sizeof(buf), "%.9g", val->_float); value = buf; break; case ev_vector: dpsnprintf(buf, sizeof(buf), "%.9g %.9g %.9g", val->vector[0], val->vector[1], val->vector[2]); value = buf; break; case ev_string: value = PRVM_GetString(prog, val->string); break; default: Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name); goto fail; } cvar = Cvar_Get(name + 9, value, 0, NULL); if((prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) == ev_string) { val->string = PRVM_SetEngineString(prog, cvar->string); cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string; } if(!cvar) prog->error_cmd("PRVM_LoadProgs: could not create cvar for autocvar global %s in %s", name, prog->name); cvar->globaldefindex[prog - prvm_prog_list] = i; } else if((cvar->flags & CVAR_PRIVATE) == 0) { // MUST BE SYNCED WITH cvar.c Cvar_Set int j; const char *s; switch(prog->globaldefs[i].type & ~DEF_SAVEGLOBAL) { case ev_float: val->_float = cvar->value; break; case ev_vector: s = cvar->string; VectorClear(val->vector); for (j = 0;j < 3;j++) { while (*s && ISWHITESPACE(*s)) s++; if (!*s) break; val->vector[j] = atof(s); while (!ISWHITESPACE(*s)) s++; if (!*s) break; } break; case ev_string: val->string = PRVM_SetEngineString(prog, cvar->string); cvar->globaldefindex_stringno[prog - prvm_prog_list] = val->string; break; default: Con_Printf("PRVM_LoadProgs: invalid type of autocvar global %s in %s\n", name, prog->name); goto fail; } cvar->globaldefindex[prog - prvm_prog_list] = i; } else Con_Printf("PRVM_LoadProgs: private cvar for autocvar global %s in %s\n", name, prog->name); } fail: ; } prog->loaded = TRUE; PRVM_UpdateBreakpoints(prog); // set flags & ddef_ts in prog prog->flag = 0; PRVM_FindOffsets(prog); prog->init_cmd(prog); // init mempools PRVM_MEM_Alloc(prog); // Inittime is at least the time when this function finished. However, // later events may bump it. prog->inittime = realtime; } static void PRVM_Fields_f (void) { prvm_prog_t *prog; int i, j, ednum, used, usedamount; int *counts; char tempstring[MAX_INPUTLINE], tempstring2[260]; const char *name; prvm_edict_t *ed; ddef_t *d; prvm_eval_t *val; // TODO /* if (!sv.active) { Con_Print("no progs loaded\n"); return; } */ if(Cmd_Argc() != 2) { Con_Print("prvm_fields \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; counts = (int *)Mem_Alloc(tempmempool, prog->numfielddefs * sizeof(int)); for (ednum = 0;ednum < prog->max_edicts;ednum++) { ed = PRVM_EDICT_NUM(ednum); if (ed->priv.required->free) continue; for (i = 1;i < prog->numfielddefs;i++) { d = &prog->fielddefs[i]; name = PRVM_GetString(prog, d->s_name); if (name[strlen(name)-2] == '_') continue; // skip _x, _y, _z vars val = (prvm_eval_t *)(ed->fields.fp + d->ofs); // if the value is still all 0, skip the field for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++) { if (val->ivector[j]) { counts[i]++; break; } } } } used = 0; usedamount = 0; tempstring[0] = 0; for (i = 0;i < prog->numfielddefs;i++) { d = &prog->fielddefs[i]; name = PRVM_GetString(prog, d->s_name); if (name[strlen(name)-2] == '_') continue; // skip _x, _y, _z vars switch(d->type & ~DEF_SAVEGLOBAL) { case ev_string: strlcat(tempstring, "string ", sizeof(tempstring)); break; case ev_entity: strlcat(tempstring, "entity ", sizeof(tempstring)); break; case ev_function: strlcat(tempstring, "function ", sizeof(tempstring)); break; case ev_field: strlcat(tempstring, "field ", sizeof(tempstring)); break; case ev_void: strlcat(tempstring, "void ", sizeof(tempstring)); break; case ev_float: strlcat(tempstring, "float ", sizeof(tempstring)); break; case ev_vector: strlcat(tempstring, "vector ", sizeof(tempstring)); break; case ev_pointer: strlcat(tempstring, "pointer ", sizeof(tempstring)); break; default: dpsnprintf (tempstring2, sizeof(tempstring2), "bad type %i ", d->type & ~DEF_SAVEGLOBAL); strlcat(tempstring, tempstring2, sizeof(tempstring)); break; } if (strlen(name) > sizeof(tempstring2)-4) { memcpy (tempstring2, name, sizeof(tempstring2)-4); tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; tempstring2[sizeof(tempstring2)-1] = 0; name = tempstring2; } strlcat(tempstring, name, sizeof(tempstring)); for (j = (int)strlen(name);j < 25;j++) strlcat(tempstring, " ", sizeof(tempstring)); dpsnprintf(tempstring2, sizeof(tempstring2), "%5d", counts[i]); strlcat(tempstring, tempstring2, sizeof(tempstring)); strlcat(tempstring, "\n", sizeof(tempstring)); if (strlen(tempstring) >= sizeof(tempstring)/2) { Con_Print(tempstring); tempstring[0] = 0; } if (counts[i]) { used++; usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL]; } } Mem_Free(counts); Con_Printf("%s: %i entity fields (%i in use), totalling %i bytes per edict (%i in use), %i edicts allocated, %i bytes total spent on edict fields (%i needed)\n", prog->name, prog->entityfields, used, prog->entityfields * 4, usedamount * 4, prog->max_edicts, prog->entityfields * 4 * prog->max_edicts, usedamount * 4 * prog->max_edicts); } static void PRVM_Globals_f (void) { prvm_prog_t *prog; int i; const char *wildcard; int numculled; numculled = 0; // TODO /*if (!sv.active) { Con_Print("no progs loaded\n"); return; }*/ if(Cmd_Argc () < 2 || Cmd_Argc() > 3) { Con_Print("prvm_globals \n"); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; if( Cmd_Argc() == 3) wildcard = Cmd_Argv(2); else wildcard = NULL; Con_Printf("%s :", prog->name); for (i = 0;i < prog->numglobaldefs;i++) { if(wildcard) if( !matchpattern( PRVM_GetString(prog, prog->globaldefs[i].s_name), wildcard, 1) ) { numculled++; continue; } Con_Printf("%s\n", PRVM_GetString(prog, prog->globaldefs[i].s_name)); } Con_Printf("%i global variables, %i culled, totalling %i bytes\n", prog->numglobals, numculled, prog->numglobals * 4); } /* =============== PRVM_Global =============== */ static void PRVM_Global_f(void) { prvm_prog_t *prog; ddef_t *global; char valuebuf[MAX_INPUTLINE]; if( Cmd_Argc() != 3 ) { Con_Printf( "prvm_global \n" ); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; global = PRVM_ED_FindGlobal( prog, Cmd_Argv(2) ); if( !global ) Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); else Con_Printf( "%s: %s\n", Cmd_Argv(2), PRVM_ValueString( prog, (etype_t)global->type, PRVM_GLOBALFIELDVALUE(global->ofs), valuebuf, sizeof(valuebuf) ) ); } /* =============== PRVM_GlobalSet =============== */ static void PRVM_GlobalSet_f(void) { prvm_prog_t *prog; ddef_t *global; if( Cmd_Argc() != 4 ) { Con_Printf( "prvm_globalset \n" ); return; } if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; global = PRVM_ED_FindGlobal( prog, Cmd_Argv(2) ); if( !global ) Con_Printf( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); else PRVM_ED_ParseEpair( prog, NULL, global, Cmd_Argv(3), true ); } /* ====================== Break- and Watchpoints ====================== */ typedef struct { char break_statement[256]; char watch_global[256]; int watch_edict; char watch_field[256]; } debug_data_t; static debug_data_t debug_data[PRVM_PROG_MAX]; void PRVM_Breakpoint(prvm_prog_t *prog, int stack_index, const char *text) { char vabuf[1024]; Con_Printf("PRVM_Breakpoint: %s\n", text); PRVM_PrintState(prog, stack_index); if (prvm_breakpointdump.integer) Host_Savegame_to(prog, va(vabuf, sizeof(vabuf), "breakpoint-%s.dmp", prog->name)); } void PRVM_Watchpoint(prvm_prog_t *prog, int stack_index, const char *text, etype_t type, prvm_eval_t *o, prvm_eval_t *n) { size_t sz = sizeof(prvm_vec_t) * ((type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); if (memcmp(o, n, sz)) { char buf[1024]; char valuebuf_o[128]; char valuebuf_n[128]; PRVM_UglyValueString(prog, type, o, valuebuf_o, sizeof(valuebuf_o)); PRVM_UglyValueString(prog, type, n, valuebuf_n, sizeof(valuebuf_n)); dpsnprintf(buf, sizeof(buf), "%s: %s -> %s", text, valuebuf_o, valuebuf_n); PRVM_Breakpoint(prog, stack_index, buf); memcpy(o, n, sz); } } static void PRVM_UpdateBreakpoints(prvm_prog_t *prog) { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; if (!prog->loaded) return; if (debug->break_statement[0]) { if (debug->break_statement[0] >= '0' && debug->break_statement[0] <= '9') { prog->break_statement = atoi(debug->break_statement); prog->break_stack_index = 0; } else { mfunction_t *func; func = PRVM_ED_FindFunction (prog, debug->break_statement); if (!func) { Con_Printf("%s progs: no function or statement named %s to break on!\n", prog->name, debug->break_statement); prog->break_statement = -1; } else { prog->break_statement = func->first_statement; prog->break_stack_index = 1; } } if (prog->break_statement >= -1) Con_Printf("%s progs: breakpoint is at statement %d\n", prog->name, prog->break_statement); } else prog->break_statement = -1; if (debug->watch_global[0]) { ddef_t *global = PRVM_ED_FindGlobal( prog, debug->watch_global ); if( !global ) { Con_Printf( "%s progs: no global named '%s' to watch!\n", prog->name, debug->watch_global ); prog->watch_global_type = ev_void; } else { size_t sz = sizeof(prvm_vec_t) * ((global->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); prog->watch_global = global->ofs; prog->watch_global_type = (etype_t)global->type; memcpy(&prog->watch_global_value, PRVM_GLOBALFIELDVALUE(prog->watch_global), sz); } if (prog->watch_global_type != ev_void) Con_Printf("%s progs: global watchpoint is at global index %d\n", prog->name, prog->watch_global); } else prog->watch_global_type = ev_void; if (debug->watch_field[0]) { ddef_t *field = PRVM_ED_FindField( prog, debug->watch_field ); if( !field ) { Con_Printf( "%s progs: no field named '%s' to watch!\n", prog->name, debug->watch_field ); prog->watch_field_type = ev_void; } else { size_t sz = sizeof(prvm_vec_t) * ((field->type & ~DEF_SAVEGLOBAL) == ev_vector ? 3 : 1); prog->watch_edict = debug->watch_edict; prog->watch_field = field->ofs; prog->watch_field_type = (etype_t)field->type; if (prog->watch_edict < prog->num_edicts) memcpy(&prog->watch_edictfield_value, PRVM_EDICTFIELDVALUE(PRVM_EDICT_NUM(prog->watch_edict), prog->watch_field), sz); else memset(&prog->watch_edictfield_value, 0, sz); } if (prog->watch_edict != ev_void) Con_Printf("%s progs: edict field watchpoint is at edict %d field index %d\n", prog->name, prog->watch_edict, prog->watch_field); } else prog->watch_field_type = ev_void; } static void PRVM_Breakpoint_f(void) { prvm_prog_t *prog; if( Cmd_Argc() == 2 ) { if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; debug->break_statement[0] = 0; } PRVM_UpdateBreakpoints(prog); return; } if( Cmd_Argc() != 3 ) { Con_Printf( "prvm_breakpoint \n" ); return; } if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; strlcpy(debug->break_statement, Cmd_Argv(2), sizeof(debug->break_statement)); } PRVM_UpdateBreakpoints(prog); } static void PRVM_GlobalWatchpoint_f(void) { prvm_prog_t *prog; if( Cmd_Argc() == 2 ) { if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; debug->watch_global[0] = 0; } PRVM_UpdateBreakpoints(prog); return; } if( Cmd_Argc() != 3 ) { Con_Printf( "prvm_globalwatchpoint \n" ); return; } if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; strlcpy(debug->watch_global, Cmd_Argv(2), sizeof(debug->watch_global)); } PRVM_UpdateBreakpoints(prog); } static void PRVM_EdictWatchpoint_f(void) { prvm_prog_t *prog; if( Cmd_Argc() == 2 ) { if (!(prog = PRVM_FriendlyProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; debug->watch_field[0] = 0; } PRVM_UpdateBreakpoints(prog); return; } if( Cmd_Argc() != 4 ) { Con_Printf( "prvm_edictwatchpoint \n" ); return; } if (!(prog = PRVM_ProgFromString(Cmd_Argv(1)))) return; { debug_data_t *debug = &debug_data[prog - prvm_prog_list]; debug->watch_edict = atoi(Cmd_Argv(2)); strlcpy(debug->watch_field, Cmd_Argv(3), sizeof(debug->watch_field)); } PRVM_UpdateBreakpoints(prog); } /* =============== PRVM_Init =============== */ void PRVM_Init (void) { Cmd_AddCommand ("prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_edicts", PRVM_ED_PrintEdicts_f, "prints all data about all entities in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_childprofile", PRVM_ChildProfile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu), sorted by time taken in function with child calls"); Cmd_AddCommand ("prvm_callprofile", PRVM_CallProfile_f, "prints execution statistics about the most time consuming QuakeC calls from the engine in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, menu)"); Cmd_AddCommand ("prvm_edictget", PRVM_ED_EdictGet_f, "retrieves the value of a specified property of a specified entity in the selected VM (server, client menu) into a cvar or to the console"); Cmd_AddCommand ("prvm_globalget", PRVM_ED_GlobalGet_f, "retrieves the value of a specified global variable in the selected VM (server, client menu) into a cvar or to the console"); Cmd_AddCommand ("prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu)"); Cmd_AddCommand ("cl_cmd", PRVM_GameCommand_Client_f, "calls the client QC function GameCommand with the supplied string as argument"); Cmd_AddCommand ("menu_cmd", PRVM_GameCommand_Menu_f, "calls the menu QC function GameCommand with the supplied string as argument"); Cmd_AddCommand ("sv_cmd", PRVM_GameCommand_Server_f, "calls the server QC function GameCommand with the supplied string as argument"); Cmd_AddCommand ("prvm_breakpoint", PRVM_Breakpoint_f, "marks a statement or function as breakpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear breakpoint"); Cmd_AddCommand ("prvm_globalwatchpoint", PRVM_GlobalWatchpoint_f, "marks a global as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); Cmd_AddCommand ("prvm_edictwatchpoint", PRVM_EdictWatchpoint_f, "marks an entity field as watchpoint (when this is executed, a stack trace is printed); to actually halt and investigate state, combine this with a gdb breakpoint on PRVM_Breakpoint, or with prvm_breakpointdump; run with just progs name to clear watchpoint"); Cvar_RegisterVariable (&prvm_language); Cvar_RegisterVariable (&prvm_traceqc); Cvar_RegisterVariable (&prvm_statementprofiling); Cvar_RegisterVariable (&prvm_timeprofiling); Cvar_RegisterVariable (&prvm_coverage); Cvar_RegisterVariable (&prvm_backtraceforwarnings); Cvar_RegisterVariable (&prvm_leaktest); Cvar_RegisterVariable (&prvm_leaktest_follow_targetname); Cvar_RegisterVariable (&prvm_leaktest_ignore_classnames); Cvar_RegisterVariable (&prvm_errordump); Cvar_RegisterVariable (&prvm_breakpointdump); Cvar_RegisterVariable (&prvm_reuseedicts_startuptime); Cvar_RegisterVariable (&prvm_reuseedicts_neverinsameframe); // COMMANDLINEOPTION: PRVM: -norunaway disables the runaway loop check (it might be impossible to exit DarkPlaces if used!) prvm_runawaycheck = !COM_CheckParm("-norunaway"); //VM_Cmd_Init(); } /* =============== PRVM_InitProg =============== */ void PRVM_Prog_Init(prvm_prog_t *prog) { PRVM_Prog_Reset(prog); prog->leaktest_active = prvm_leaktest.integer != 0; } // LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons unsigned int PRVM_EDICT_NUM_ERROR(prvm_prog_t *prog, unsigned int n, const char *filename, int fileline) { prog->error_cmd("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", prog->name, n, filename, fileline); return 0; } #define PRVM_KNOWNSTRINGBASE 0x40000000 const char *PRVM_GetString(prvm_prog_t *prog, int num) { if (num < 0) { // invalid VM_Warning(prog, "PRVM_GetString: Invalid string offset (%i < 0)\n", num); return ""; } else if (num < prog->stringssize) { // constant string from progs.dat return prog->strings + num; } else if (num <= prog->stringssize + prog->tempstringsbuf.maxsize) { // tempstring returned by engine to QC (becomes invalid after returning to engine) num -= prog->stringssize; if (num < prog->tempstringsbuf.cursize) return (char *)prog->tempstringsbuf.data + num; else { VM_Warning(prog, "PRVM_GetString: Invalid temp-string offset (%i >= %i prog->tempstringsbuf.cursize)\n", num, prog->tempstringsbuf.cursize); return ""; } } else if (num & PRVM_KNOWNSTRINGBASE) { // allocated string num = num - PRVM_KNOWNSTRINGBASE; if (num >= 0 && num < prog->numknownstrings) { if (!prog->knownstrings[num]) { VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num); return ""; } return prog->knownstrings[num]; } else { VM_Warning(prog, "PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, prog->numknownstrings); return ""; } } else { // invalid string offset VM_Warning(prog, "PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, prog->stringssize); return ""; } } const char *PRVM_ChangeEngineString(prvm_prog_t *prog, int i, const char *s) { const char *old; i = i - PRVM_KNOWNSTRINGBASE; if(i < 0 || i >= prog->numknownstrings) prog->error_cmd("PRVM_ChangeEngineString: s is not an engine string"); old = prog->knownstrings[i]; prog->knownstrings[i] = s; return old; } int PRVM_SetEngineString(prvm_prog_t *prog, const char *s) { int i; if (!s) return 0; if (s >= prog->strings && s <= prog->strings + prog->stringssize) prog->error_cmd("PRVM_SetEngineString: s in prog->strings area"); // if it's in the tempstrings area, use a reserved range // (otherwise we'd get millions of useless string offsets cluttering the database) if (s >= (char *)prog->tempstringsbuf.data && s < (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.maxsize) return prog->stringssize + (s - (char *)prog->tempstringsbuf.data); // see if it's a known string address for (i = 0;i < prog->numknownstrings;i++) if (prog->knownstrings[i] == s) return PRVM_KNOWNSTRINGBASE + i; // new unknown engine string if (developer_insane.integer) Con_DPrintf("new engine string %p = \"%s\"\n", s, s); for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) if (!prog->knownstrings[i]) break; if (i >= prog->numknownstrings) { if (i >= prog->maxknownstrings) { const char **oldstrings = prog->knownstrings; const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; const char **oldstrings_origin = prog->knownstrings_origin; prog->maxknownstrings += 128; prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); if(prog->leaktest_active) prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); if (prog->numknownstrings) { memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); if(prog->leaktest_active) memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); } } prog->numknownstrings++; } prog->firstfreeknownstring = i + 1; prog->knownstrings[i] = s; prog->knownstrings_freeable[i] = false; if(prog->leaktest_active) prog->knownstrings_origin[i] = NULL; return PRVM_KNOWNSTRINGBASE + i; } // temp string handling // all tempstrings go into this buffer consecutively, and it is reset // whenever PRVM_ExecuteProgram returns to the engine // (technically each PRVM_ExecuteProgram call saves the cursize value and // restores it on return, so multiple recursive calls can share the same // buffer) // the buffer size is automatically grown as needed int PRVM_SetTempString(prvm_prog_t *prog, const char *s) { int size; char *t; if (!s) return 0; size = (int)strlen(s) + 1; if (developer_insane.integer) Con_DPrintf("PRVM_SetTempString: cursize %i, size %i\n", prog->tempstringsbuf.cursize, size); if (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size) { sizebuf_t old = prog->tempstringsbuf; if (prog->tempstringsbuf.cursize + size >= 1<<28) prog->error_cmd("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", prog->tempstringsbuf.cursize, size); prog->tempstringsbuf.maxsize = max(prog->tempstringsbuf.maxsize, 65536); while (prog->tempstringsbuf.maxsize < prog->tempstringsbuf.cursize + size) prog->tempstringsbuf.maxsize *= 2; if (prog->tempstringsbuf.maxsize != old.maxsize || prog->tempstringsbuf.data == NULL) { Con_DPrintf("PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old.maxsize/1024, prog->tempstringsbuf.maxsize/1024); prog->tempstringsbuf.data = (unsigned char *) Mem_Alloc(prog->progs_mempool, prog->tempstringsbuf.maxsize); if (old.data) { if (old.cursize) memcpy(prog->tempstringsbuf.data, old.data, old.cursize); Mem_Free(old.data); } } } t = (char *)prog->tempstringsbuf.data + prog->tempstringsbuf.cursize; memcpy(t, s, size); prog->tempstringsbuf.cursize += size; return PRVM_SetEngineString(prog, t); } int PRVM_AllocString(prvm_prog_t *prog, size_t bufferlength, char **pointer) { int i; if (!bufferlength) { if (pointer) *pointer = NULL; return 0; } for (i = prog->firstfreeknownstring;i < prog->numknownstrings;i++) if (!prog->knownstrings[i]) break; if (i >= prog->numknownstrings) { if (i >= prog->maxknownstrings) { const char **oldstrings = prog->knownstrings; const unsigned char *oldstrings_freeable = prog->knownstrings_freeable; const char **oldstrings_origin = prog->knownstrings_origin; prog->maxknownstrings += 128; prog->knownstrings = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); prog->knownstrings_freeable = (unsigned char *)PRVM_Alloc(prog->maxknownstrings * sizeof(unsigned char)); if(prog->leaktest_active) prog->knownstrings_origin = (const char **)PRVM_Alloc(prog->maxknownstrings * sizeof(char *)); if (prog->numknownstrings) { memcpy((char **)prog->knownstrings, oldstrings, prog->numknownstrings * sizeof(char *)); memcpy((char **)prog->knownstrings_freeable, oldstrings_freeable, prog->numknownstrings * sizeof(unsigned char)); if(prog->leaktest_active) memcpy((char **)prog->knownstrings_origin, oldstrings_origin, prog->numknownstrings * sizeof(char *)); } if (oldstrings) Mem_Free((char **)oldstrings); if (oldstrings_freeable) Mem_Free((unsigned char *)oldstrings_freeable); if (oldstrings_origin) Mem_Free((char **)oldstrings_origin); } prog->numknownstrings++; } prog->firstfreeknownstring = i + 1; prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength); prog->knownstrings_freeable[i] = true; if(prog->leaktest_active) prog->knownstrings_origin[i] = PRVM_AllocationOrigin(prog); if (pointer) *pointer = (char *)(prog->knownstrings[i]); return PRVM_KNOWNSTRINGBASE + i; } void PRVM_FreeString(prvm_prog_t *prog, int num) { if (num == 0) prog->error_cmd("PRVM_FreeString: attempt to free a NULL string"); else if (num >= 0 && num < prog->stringssize) prog->error_cmd("PRVM_FreeString: attempt to free a constant string"); else if (num >= PRVM_KNOWNSTRINGBASE && num < PRVM_KNOWNSTRINGBASE + prog->numknownstrings) { num = num - PRVM_KNOWNSTRINGBASE; if (!prog->knownstrings[num]) prog->error_cmd("PRVM_FreeString: attempt to free a non-existent or already freed string"); if (!prog->knownstrings_freeable[num]) prog->error_cmd("PRVM_FreeString: attempt to free a string owned by the engine"); PRVM_Free((char *)prog->knownstrings[num]); if(prog->leaktest_active) if(prog->knownstrings_origin[num]) PRVM_Free((char *)prog->knownstrings_origin[num]); prog->knownstrings[num] = NULL; prog->knownstrings_freeable[num] = false; prog->firstfreeknownstring = min(prog->firstfreeknownstring, num); } else prog->error_cmd("PRVM_FreeString: invalid string offset %i", num); } static qboolean PRVM_IsStringReferenced(prvm_prog_t *prog, string_t string) { int i, j; for (i = 0;i < prog->numglobaldefs;i++) { ddef_t *d = &prog->globaldefs[i]; if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) continue; if(string == PRVM_GLOBALFIELDSTRING(d->ofs)) return true; } for(j = 0; j < prog->num_edicts; ++j) { prvm_edict_t *ed = PRVM_EDICT_NUM(j); if (ed->priv.required->free) continue; for (i=0; inumfielddefs; ++i) { ddef_t *d = &prog->fielddefs[i]; if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_string) continue; if(string == PRVM_EDICTFIELDSTRING(ed, d->ofs)) return true; } } return false; } static qboolean PRVM_IsEdictRelevant(prvm_prog_t *prog, prvm_edict_t *edict) { char vabuf[1024]; char vabuf2[1024]; if(PRVM_NUM_FOR_EDICT(edict) <= prog->reserved_edicts) return true; // world or clients if (edict->priv.required->freetime <= prog->inittime) return true; // created during startup if (prog == SVVM_prog) { if(PRVM_serveredictfloat(edict, solid)) // can block other stuff, or is a trigger? return true; if(PRVM_serveredictfloat(edict, modelindex)) // visible ent? return true; if(PRVM_serveredictfloat(edict, effects)) // particle effect? return true; if(PRVM_serveredictfunction(edict, think)) // has a think function? if(PRVM_serveredictfloat(edict, nextthink) > 0) // that actually will eventually run? return true; if(PRVM_serveredictfloat(edict, takedamage)) return true; if(*prvm_leaktest_ignore_classnames.string) { if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_serveredictstring(edict, classname))))) return true; } } else if (prog == CLVM_prog) { // TODO someone add more stuff here if(PRVM_clientedictfloat(edict, entnum)) // csqc networked return true; if(PRVM_clientedictfloat(edict, modelindex)) // visible ent? return true; if(PRVM_clientedictfloat(edict, effects)) // particle effect? return true; if(PRVM_clientedictfunction(edict, think)) // has a think function? if(PRVM_clientedictfloat(edict, nextthink) > 0) // that actually will eventually run? return true; if(*prvm_leaktest_ignore_classnames.string) { if(strstr(va(vabuf, sizeof(vabuf), " %s ", prvm_leaktest_ignore_classnames.string), va(vabuf2, sizeof(vabuf2), " %s ", PRVM_GetString(prog, PRVM_clientedictstring(edict, classname))))) return true; } } else { // menu prog does not have classnames } return false; } static qboolean PRVM_IsEdictReferenced(prvm_prog_t *prog, prvm_edict_t *edict, int mark) { int i, j; int edictnum = PRVM_NUM_FOR_EDICT(edict); const char *targetname = NULL; if (prog == SVVM_prog && prvm_leaktest_follow_targetname.integer) targetname = PRVM_GetString(prog, PRVM_serveredictstring(edict, targetname)); if(targetname) if(!*targetname) // "" targetname = NULL; for(j = 0; j < prog->num_edicts; ++j) { prvm_edict_t *ed = PRVM_EDICT_NUM(j); if (ed->priv.required->mark < mark) continue; if(ed == edict) continue; if(targetname) { const char *target = PRVM_GetString(prog, PRVM_serveredictstring(ed, target)); if(target) if(!strcmp(target, targetname)) return true; } for (i=0; inumfielddefs; ++i) { ddef_t *d = &prog->fielddefs[i]; if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) continue; if(edictnum == PRVM_EDICTFIELDEDICT(ed, d->ofs)) return true; } } return false; } static void PRVM_MarkReferencedEdicts(prvm_prog_t *prog) { int i, j; qboolean found_new; int stage; // Stage 1: world, all entities that are relevant, and all entities that are referenced by globals. stage = 1; for(j = 0; j < prog->num_edicts; ++j) { prvm_edict_t *ed = PRVM_EDICT_NUM(j); if(ed->priv.required->free) continue; ed->priv.required->mark = PRVM_IsEdictRelevant(prog, ed) ? stage : 0; } for (i = 0;i < prog->numglobaldefs;i++) { ddef_t *d = &prog->globaldefs[i]; prvm_edict_t *ed; if((etype_t)((int) d->type & ~DEF_SAVEGLOBAL) != ev_entity) continue; j = PRVM_GLOBALFIELDEDICT(d->ofs); if (i < 0 || j >= prog->max_edicts) { Con_Printf("Invalid entity reference from global %s.\n", PRVM_GetString(prog, d->s_name)); continue; } ed = PRVM_EDICT_NUM(j);; ed->priv.required->mark = stage; } // Future stages: all entities that are referenced by an entity of the previous stage. do { found_new = false; for(j = 0; j < prog->num_edicts; ++j) { prvm_edict_t *ed = PRVM_EDICT_NUM(j); if(ed->priv.required->free) continue; if(ed->priv.required->mark) continue; if(PRVM_IsEdictReferenced(prog, ed, stage)) { ed->priv.required->mark = stage + 1; found_new = true; } } ++stage; } while(found_new); Con_DPrintf("leak check used %d stages to find all references\n", stage); } void PRVM_LeakTest(prvm_prog_t *prog) { int i, j; qboolean leaked = false; if(!prog->leaktest_active) return; // 1. Strings for (i = 0; i < prog->numknownstrings; ++i) { if(prog->knownstrings[i]) if(prog->knownstrings_freeable[i]) if(prog->knownstrings_origin[i]) if(!PRVM_IsStringReferenced(prog, PRVM_KNOWNSTRINGBASE + i)) { Con_Printf("Unreferenced string found!\n Value: %s\n Origin: %s\n", prog->knownstrings[i], prog->knownstrings_origin[i]); leaked = true; } } // 2. Edicts PRVM_MarkReferencedEdicts(prog); for(j = 0; j < prog->num_edicts; ++j) { prvm_edict_t *ed = PRVM_EDICT_NUM(j); if(ed->priv.required->free) continue; if(!ed->priv.required->mark) if(ed->priv.required->allocation_origin) { Con_Printf("Unreferenced edict found!\n Allocated at: %s\n", ed->priv.required->allocation_origin); PRVM_ED_Print(prog, ed, NULL); Con_Print("\n"); leaked = true; } ed->priv.required->mark = 0; // clear marks again when done } for (i = 0; i < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); ++i) { prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); if(stringbuffer) if(stringbuffer->origin) { Con_Printf("Open string buffer handle found!\n Allocated at: %s\n", stringbuffer->origin); leaked = true; } } for(i = 0; i < PRVM_MAX_OPENFILES; ++i) { if(prog->openfiles[i]) if(prog->openfiles_origin[i]) { Con_Printf("Open file handle found!\n Allocated at: %s\n", prog->openfiles_origin[i]); leaked = true; } } for(i = 0; i < PRVM_MAX_OPENSEARCHES; ++i) { if(prog->opensearches[i]) if(prog->opensearches_origin[i]) { Con_Printf("Open search handle found!\n Allocated at: %s\n", prog->opensearches_origin[i]); leaked = true; } } if(!leaked) Con_Printf("Congratulations. No leaks found.\n"); } darkplaces/r_explosion.c0000664000175000017500000002126513067716222014700 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "cl_collision.h" #ifdef MAX_EXPLOSIONS #define EXPLOSIONGRID 8 #define EXPLOSIONVERTS ((EXPLOSIONGRID+1)*(EXPLOSIONGRID+1)) #define EXPLOSIONTRIS (EXPLOSIONGRID*EXPLOSIONGRID*2) static int numexplosions = 0; static float explosiontexcoord2f[EXPLOSIONVERTS][2]; static unsigned short explosiontris[EXPLOSIONTRIS][3]; static int explosionnoiseindex[EXPLOSIONVERTS]; static vec3_t explosionpoint[EXPLOSIONVERTS]; typedef struct explosion_s { float starttime; float endtime; float time; float alpha; float fade; vec3_t origin; vec3_t vert[EXPLOSIONVERTS]; vec3_t vertvel[EXPLOSIONVERTS]; qboolean clipping; } explosion_t; static explosion_t explosion[MAX_EXPLOSIONS]; static rtexture_t *explosiontexture; //static rtexture_t *explosiontexturefog; static rtexturepool_t *explosiontexturepool; #endif cvar_t r_explosionclip = {CVAR_SAVE, "r_explosionclip", "1", "enables collision detection for explosion shell (so that it flattens against walls and floors)"}; #ifdef MAX_EXPLOSIONS static cvar_t r_drawexplosions = {0, "r_drawexplosions", "1", "enables rendering of explosion shells (see also cl_particles_explosions_shell)"}; //extern qboolean r_loadfog; static void r_explosion_start(void) { int x, y; static unsigned char noise1[128][128], noise2[128][128], noise3[128][128], data[128][128][4]; explosiontexturepool = R_AllocTexturePool(); explosiontexture = NULL; //explosiontexturefog = NULL; fractalnoise(&noise1[0][0], 128, 32); fractalnoise(&noise2[0][0], 128, 4); fractalnoise(&noise3[0][0], 128, 4); for (y = 0;y < 128;y++) { for (x = 0;x < 128;x++) { int j, r, g, b, a; j = (noise1[y][x] * noise2[y][x]) * 3 / 256 - 128; r = (j * 512) / 256; g = (j * 256) / 256; b = (j * 128) / 256; a = noise3[y][x] * 3 - 128; data[y][x][2] = bound(0, r, 255); data[y][x][1] = bound(0, g, 255); data[y][x][0] = bound(0, b, 255); data[y][x][3] = bound(0, a, 255); } } explosiontexture = R_LoadTexture2D(explosiontexturepool, "explosiontexture", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); // if (r_loadfog) // { // for (y = 0;y < 128;y++) // for (x = 0;x < 128;x++) // data[y][x][0] = data[y][x][1] = data[y][x][2] = 255; // explosiontexturefog = R_LoadTexture2D(explosiontexturepool, "explosiontexture_fog", 128, 128, &data[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_ALPHA | TEXF_FORCELINEAR, NULL); // } // note that explosions survive the restart } static void r_explosion_shutdown(void) { R_FreeTexturePool(&explosiontexturepool); } static void r_explosion_newmap(void) { numexplosions = 0; memset(explosion, 0, sizeof(explosion)); } static int R_ExplosionVert(int column, int row) { int i; float yaw, pitch; // top and bottom rows are all one position... if (row == 0 || row == EXPLOSIONGRID) column = 0; i = row * (EXPLOSIONGRID + 1) + column; yaw = ((double) column / EXPLOSIONGRID) * M_PI * 2; pitch = (((double) row / EXPLOSIONGRID) - 0.5) * M_PI; explosionpoint[i][0] = cos(yaw) * cos(pitch); explosionpoint[i][1] = sin(yaw) * cos(pitch); explosionpoint[i][2] = 1 * -sin(pitch); explosiontexcoord2f[i][0] = (float) column / (float) EXPLOSIONGRID; explosiontexcoord2f[i][1] = (float) row / (float) EXPLOSIONGRID; explosionnoiseindex[i] = (row % EXPLOSIONGRID) * EXPLOSIONGRID + (column % EXPLOSIONGRID); return i; } #endif void R_Explosion_Init(void) { #ifdef MAX_EXPLOSIONS int i, x, y; i = 0; for (y = 0;y < EXPLOSIONGRID;y++) { for (x = 0;x < EXPLOSIONGRID;x++) { explosiontris[i][0] = R_ExplosionVert(x , y ); explosiontris[i][1] = R_ExplosionVert(x + 1, y ); explosiontris[i][2] = R_ExplosionVert(x , y + 1); i++; explosiontris[i][0] = R_ExplosionVert(x + 1, y ); explosiontris[i][1] = R_ExplosionVert(x + 1, y + 1); explosiontris[i][2] = R_ExplosionVert(x , y + 1); i++; } } #endif Cvar_RegisterVariable(&r_explosionclip); #ifdef MAX_EXPLOSIONS Cvar_RegisterVariable(&r_drawexplosions); R_RegisterModule("R_Explosions", r_explosion_start, r_explosion_shutdown, r_explosion_newmap, NULL, NULL); #endif } void R_NewExplosion(const vec3_t org) { #ifdef MAX_EXPLOSIONS int i, j; float dist, n; explosion_t *e; trace_t trace; unsigned char noise[EXPLOSIONGRID*EXPLOSIONGRID]; fractalnoisequick(noise, EXPLOSIONGRID, 4); // adjust noise grid size according to explosion for (i = 0, e = explosion;i < MAX_EXPLOSIONS;i++, e++) { if (!e->alpha) { numexplosions = max(numexplosions, i + 1); e->starttime = cl.time; e->endtime = cl.time + cl_explosions_lifetime.value; e->time = e->starttime; e->alpha = cl_explosions_alpha_start.value; e->fade = (cl_explosions_alpha_start.value - cl_explosions_alpha_end.value) / cl_explosions_lifetime.value; e->clipping = r_explosionclip.integer != 0; VectorCopy(org, e->origin); for (j = 0;j < EXPLOSIONVERTS;j++) { // calculate start origin and velocity n = noise[explosionnoiseindex[j]] * (1.0f / 255.0f) + 0.5; dist = n * cl_explosions_size_start.value; VectorMA(e->origin, dist, explosionpoint[j], e->vert[j]); dist = n * (cl_explosions_size_end.value - cl_explosions_size_start.value) / cl_explosions_lifetime.value; VectorScale(explosionpoint[j], dist, e->vertvel[j]); // clip start origin if (e->clipping) { trace = CL_TraceLine(e->origin, e->vert[j], MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, collision_extendmovelength.value, true, false, NULL, false, false); VectorCopy(trace.endpos, e->vert[i]); } } break; } } #endif } #ifdef MAX_EXPLOSIONS static void R_DrawExplosion_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int surfacelistindex = 0; const int numtriangles = EXPLOSIONTRIS, numverts = EXPLOSIONVERTS; GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); GL_DepthTest(true); GL_CullFace(r_refdef.view.cullface_back); R_EntityMatrix(&identitymatrix); // R_Mesh_ResetTextureState(); R_SetupShader_Generic(explosiontexture, NULL, GL_MODULATE, 1, false, false, false); for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { const explosion_t *e = explosion + surfacelist[surfacelistindex]; // FIXME: this can't properly handle r_refdef.view.colorscale > 1 GL_Color(e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, e->alpha * r_refdef.view.colorscale, 1); R_Mesh_PrepareVertices_Generic_Arrays(numverts, e->vert[0], NULL, explosiontexcoord2f[0]); R_Mesh_Draw(0, numverts, 0, numtriangles, NULL, NULL, 0, explosiontris[0], NULL, 0); } } static void R_MoveExplosion(explosion_t *e) { int i; float dot, end[3], frametime; trace_t trace; frametime = cl.time - e->time; e->time = cl.time; e->alpha = e->alpha - (e->fade * frametime); if (e->alpha < 0 || cl.time > e->endtime) { e->alpha = 0; return; } for (i = 0;i < EXPLOSIONVERTS;i++) { if (e->vertvel[i][0] || e->vertvel[i][1] || e->vertvel[i][2]) { VectorMA(e->vert[i], frametime, e->vertvel[i], end); if (e->clipping) { trace = CL_TraceLine(e->vert[i], end, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, collision_extendmovelength.value, true, false, NULL, false, false); if (trace.fraction < 1) { // clip velocity against the wall dot = -DotProduct(e->vertvel[i], trace.plane.normal); VectorMA(e->vertvel[i], dot, trace.plane.normal, e->vertvel[i]); } VectorCopy(trace.endpos, e->vert[i]); } else VectorCopy(end, e->vert[i]); } } } #endif void R_DrawExplosions(void) { #ifdef MAX_EXPLOSIONS int i; if (!r_drawexplosions.integer) return; for (i = 0;i < numexplosions;i++) { if (explosion[i].alpha) { R_MoveExplosion(&explosion[i]); if (explosion[i].alpha) R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, explosion[i].origin, R_DrawExplosion_TransparentCallback, NULL, i, NULL); } } while (numexplosions > 0 && explosion[i-1].alpha <= 0) numexplosions--; #endif } darkplaces/keys.h0000664000175000017500000001523213067716220013312 0ustar kalevkalev/* $RCSfile$ Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifndef __KEYS_H #define __KEYS_H #include "qtypes.h" // // these are the key numbers that should be passed to Key_Event // typedef enum keynum_e { K_TEXT = 1, // used only for unicode character input K_TAB = 9, K_ENTER = 13, K_ESCAPE = 27, K_SPACE = 32, // normal keys should be passed as lowercased ascii K_BACKSPACE = 127, K_UPARROW, K_DOWNARROW, K_LEFTARROW, K_RIGHTARROW, K_ALT, K_CTRL, K_SHIFT, K_F1, K_F2, K_F3, K_F4, K_F5, K_F6, K_F7, K_F8, K_F9, K_F10, K_F11, K_F12, K_INS, K_DEL, K_PGDN, K_PGUP, K_HOME, K_END, K_PAUSE, K_NUMLOCK, K_CAPSLOCK, K_SCROLLOCK, K_KP_0, K_KP_INS = K_KP_0, K_KP_1, K_KP_END = K_KP_1, K_KP_2, K_KP_DOWNARROW = K_KP_2, K_KP_3, K_KP_PGDN = K_KP_3, K_KP_4, K_KP_LEFTARROW = K_KP_4, K_KP_5, K_KP_6, K_KP_RIGHTARROW = K_KP_6, K_KP_7, K_KP_HOME = K_KP_7, K_KP_8, K_KP_UPARROW = K_KP_8, K_KP_9, K_KP_PGUP = K_KP_9, K_KP_PERIOD, K_KP_DEL = K_KP_PERIOD, K_KP_DIVIDE, K_KP_SLASH = K_KP_DIVIDE, K_KP_MULTIPLY, K_KP_MINUS, K_KP_PLUS, K_KP_ENTER, K_KP_EQUALS, K_PRINTSCREEN, // mouse buttons generate virtual keys K_MOUSE1 = 512, K_OTHERDEVICESBEGIN = K_MOUSE1, K_MOUSE2, K_MOUSE3, K_MWHEELUP, K_MWHEELDOWN, K_MOUSE4, K_MOUSE5, K_MOUSE6, K_MOUSE7, K_MOUSE8, K_MOUSE9, K_MOUSE10, K_MOUSE11, K_MOUSE12, K_MOUSE13, K_MOUSE14, K_MOUSE15, K_MOUSE16, // // joystick buttons // K_JOY1 = 768, K_JOY2, K_JOY3, K_JOY4, K_JOY5, K_JOY6, K_JOY7, K_JOY8, K_JOY9, K_JOY10, K_JOY11, K_JOY12, K_JOY13, K_JOY14, K_JOY15, K_JOY16, // // aux keys are for multi-buttoned joysticks to generate so they can use // the normal binding process // K_AUX1, K_AUX2, K_AUX3, K_AUX4, K_AUX5, K_AUX6, K_AUX7, K_AUX8, K_AUX9, K_AUX10, K_AUX11, K_AUX12, K_AUX13, K_AUX14, K_AUX15, K_AUX16, K_AUX17, K_AUX18, K_AUX19, K_AUX20, K_AUX21, K_AUX22, K_AUX23, K_AUX24, K_AUX25, K_AUX26, K_AUX27, K_AUX28, K_AUX29, K_AUX30, K_AUX31, K_AUX32, // Microsoft Xbox 360 Controller For Windows K_X360_DPAD_UP, K_X360_DPAD_DOWN, K_X360_DPAD_LEFT, K_X360_DPAD_RIGHT, K_X360_START, K_X360_BACK, K_X360_LEFT_THUMB, K_X360_RIGHT_THUMB, K_X360_LEFT_SHOULDER, K_X360_RIGHT_SHOULDER, K_X360_A, K_X360_B, K_X360_X, K_X360_Y, K_X360_LEFT_TRIGGER, K_X360_RIGHT_TRIGGER, K_X360_LEFT_THUMB_UP, K_X360_LEFT_THUMB_DOWN, K_X360_LEFT_THUMB_LEFT, K_X360_LEFT_THUMB_RIGHT, K_X360_RIGHT_THUMB_UP, K_X360_RIGHT_THUMB_DOWN, K_X360_RIGHT_THUMB_LEFT, K_X360_RIGHT_THUMB_RIGHT, // generic joystick emulation for menu K_JOY_UP, K_JOY_DOWN, K_JOY_LEFT, K_JOY_RIGHT, K_MIDINOTE0 = 896, // to this, the note number is added K_MIDINOTE1, K_MIDINOTE2, K_MIDINOTE3, K_MIDINOTE4, K_MIDINOTE5, K_MIDINOTE6, K_MIDINOTE7, K_MIDINOTE8, K_MIDINOTE9, K_MIDINOTE10, K_MIDINOTE11, K_MIDINOTE12, K_MIDINOTE13, K_MIDINOTE14, K_MIDINOTE15, K_MIDINOTE16, K_MIDINOTE17, K_MIDINOTE18, K_MIDINOTE19, K_MIDINOTE20, K_MIDINOTE21, K_MIDINOTE22, K_MIDINOTE23, K_MIDINOTE24, K_MIDINOTE25, K_MIDINOTE26, K_MIDINOTE27, K_MIDINOTE28, K_MIDINOTE29, K_MIDINOTE30, K_MIDINOTE31, K_MIDINOTE32, K_MIDINOTE33, K_MIDINOTE34, K_MIDINOTE35, K_MIDINOTE36, K_MIDINOTE37, K_MIDINOTE38, K_MIDINOTE39, K_MIDINOTE40, K_MIDINOTE41, K_MIDINOTE42, K_MIDINOTE43, K_MIDINOTE44, K_MIDINOTE45, K_MIDINOTE46, K_MIDINOTE47, K_MIDINOTE48, K_MIDINOTE49, K_MIDINOTE50, K_MIDINOTE51, K_MIDINOTE52, K_MIDINOTE53, K_MIDINOTE54, K_MIDINOTE55, K_MIDINOTE56, K_MIDINOTE57, K_MIDINOTE58, K_MIDINOTE59, K_MIDINOTE60, K_MIDINOTE61, K_MIDINOTE62, K_MIDINOTE63, K_MIDINOTE64, K_MIDINOTE65, K_MIDINOTE66, K_MIDINOTE67, K_MIDINOTE68, K_MIDINOTE69, K_MIDINOTE70, K_MIDINOTE71, K_MIDINOTE72, K_MIDINOTE73, K_MIDINOTE74, K_MIDINOTE75, K_MIDINOTE76, K_MIDINOTE77, K_MIDINOTE78, K_MIDINOTE79, K_MIDINOTE80, K_MIDINOTE81, K_MIDINOTE82, K_MIDINOTE83, K_MIDINOTE84, K_MIDINOTE85, K_MIDINOTE86, K_MIDINOTE87, K_MIDINOTE88, K_MIDINOTE89, K_MIDINOTE90, K_MIDINOTE91, K_MIDINOTE92, K_MIDINOTE93, K_MIDINOTE94, K_MIDINOTE95, K_MIDINOTE96, K_MIDINOTE97, K_MIDINOTE98, K_MIDINOTE99, K_MIDINOTE100, K_MIDINOTE101, K_MIDINOTE102, K_MIDINOTE103, K_MIDINOTE104, K_MIDINOTE105, K_MIDINOTE106, K_MIDINOTE107, K_MIDINOTE108, K_MIDINOTE109, K_MIDINOTE110, K_MIDINOTE111, K_MIDINOTE112, K_MIDINOTE113, K_MIDINOTE114, K_MIDINOTE115, K_MIDINOTE116, K_MIDINOTE117, K_MIDINOTE118, K_MIDINOTE119, K_MIDINOTE120, K_MIDINOTE121, K_MIDINOTE122, K_MIDINOTE123, K_MIDINOTE124, K_MIDINOTE125, K_MIDINOTE126, K_MIDINOTE127, MAX_KEYS } keynum_t; typedef enum keydest_e { key_game, key_message, key_menu, key_menu_grabbed, key_console, key_void } keydest_t; extern char key_line[MAX_INPUTLINE]; extern int key_linepos; extern qboolean key_insert; // insert key toggle (for editing) extern keydest_t key_dest; // key_consoleactive bits // user wants console (halfscreen) #define KEY_CONSOLEACTIVE_USER 1 // console forced because there's nothing else active (fullscreen) #define KEY_CONSOLEACTIVE_FORCED 4 extern int key_consoleactive; extern char *keybindings[MAX_BINDMAPS][MAX_KEYS]; extern int chat_mode; // 0 for say, 1 for say_team, -1 for command extern char chat_buffer[MAX_INPUTLINE]; extern unsigned int chat_bufferlen; void Key_ClearEditLine(int edit_line); void Key_WriteBindings(qfile_t *f); void Key_Init(void); void Key_Shutdown(void); void Key_Init_Cvars(void); void Key_Event(int key, int ascii, qboolean down); void Key_ReleaseAll (void); void Key_ClearStates (void); // FIXME: should this function still exist? Or should Key_ReleaseAll be used instead when shutting down a vid driver? void Key_EventQueue_Block(void); void Key_EventQueue_Unblock(void); qboolean Key_SetBinding (int keynum, int bindmap, const char *binding); const char *Key_GetBind (int key, int bindmap); void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap); qboolean Key_SetBindMap(int fg, int bg); void Key_GetBindMap(int *fg, int *bg); #endif // __KEYS_H darkplaces/server.h0000664000175000017500000005161413067716222013653 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // server.h #ifndef SERVER_H #define SERVER_H typedef struct server_static_s { /// number of svs.clients slots (updated by maxplayers command) int maxclients, maxclients_next; /// client slots struct client_s *clients; /// episode completion information int serverflags; /// cleared when at SV_SpawnServer qboolean changelevel_issued; /// server infostring char serverinfo[MAX_SERVERINFO_STRING]; // performance data float perf_cpuload; float perf_lost; float perf_offset_avg; float perf_offset_max; float perf_offset_sdev; // temporary performance data accumulators float perf_acc_realtime; float perf_acc_sleeptime; float perf_acc_lost; float perf_acc_offset; float perf_acc_offset_squared; float perf_acc_offset_max; int perf_acc_offset_samples; // csqc stuff unsigned char *csqc_progdata; size_t csqc_progsize_deflated; unsigned char *csqc_progdata_deflated; // independent server thread (when running client) qboolean threaded; // true if server is running on separate thread qboolean volatile threadstop; void *threadmutex; void *thread; } server_static_t; //============================================================================= typedef enum server_state_e {ss_loading, ss_active} server_state_t; #define MAX_CONNECTFLOODADDRESSES 16 #define MAX_GETSTATUSFLOODADDRESSES 128 typedef struct server_floodaddress_s { double lasttime; lhnetaddress_t address; } server_floodaddress_t; typedef struct server_s { /// false if only a net client qboolean active; qboolean paused; double pausedstart; /// handle connections specially qboolean loadgame; /// one of the PROTOCOL_ values protocolversion_t protocol; double time; double frametime; // used by PF_checkclient int lastcheck; double lastchecktime; // crc of clientside progs at time of level start int csqc_progcrc; // -1 = no progs int csqc_progsize; // -1 = no progs char csqc_progname[MAX_QPATH]; // copied from csqc_progname at level start /// collision culling data world_t world; /// map name char name[64]; // %s followed by entrance name // variants of map name char worldmessage[40]; // map title (not related to filename) char worldbasename[MAX_QPATH]; // %s char worldname[MAX_QPATH]; // maps/%s.bsp char worldnamenoextension[MAX_QPATH]; // maps/%s struct model_s *worldmodel; // NULL terminated // LordHavoc: precaches are now MAX_QPATH rather than a pointer // updated by SV_ModelIndex char model_precache[MAX_MODELS][MAX_QPATH]; struct model_s *models[MAX_MODELS]; // NULL terminated // LordHavoc: precaches are now MAX_QPATH rather than a pointer // updated by SV_SoundIndex char sound_precache[MAX_SOUNDS][MAX_QPATH]; char lightstyles[MAX_LIGHTSTYLES][64]; /// some actions are only valid during load server_state_t state; sizebuf_t datagram; unsigned char datagram_buf[NET_MAXMESSAGE]; // copied to all clients at end of frame sizebuf_t reliable_datagram; unsigned char reliable_datagram_buf[NET_MAXMESSAGE]; sizebuf_t signon; /// LordHavoc: increased signon message buffer from 8192 unsigned char signon_buf[NET_MAXMESSAGE]; /// connection flood blocking /// note this is in server_t rather than server_static_t so that it is /// reset on each map command (such as New Game in singleplayer) server_floodaddress_t connectfloodaddresses[MAX_CONNECTFLOODADDRESSES]; server_floodaddress_t getstatusfloodaddresses[MAX_GETSTATUSFLOODADDRESSES]; qboolean particleeffectnamesloaded; char particleeffectname[MAX_PARTICLEEFFECTNAME][MAX_QPATH]; int writeentitiestoclient_stats_culled_pvs; int writeentitiestoclient_stats_culled_trace; int writeentitiestoclient_stats_visibleentities; int writeentitiestoclient_stats_totalentities; int writeentitiestoclient_cliententitynumber; int writeentitiestoclient_clientnumber; sizebuf_t *writeentitiestoclient_msg; vec3_t writeentitiestoclient_eyes[MAX_CLIENTNETWORKEYES]; int writeentitiestoclient_numeyes; int writeentitiestoclient_pvsbytes; unsigned char writeentitiestoclient_pvs[MAX_MAP_LEAFS/8]; const entity_state_t *writeentitiestoclient_sendstates[MAX_EDICTS]; unsigned short writeentitiestoclient_csqcsendstates[MAX_EDICTS]; int numsendentities; entity_state_t sendentities[MAX_EDICTS]; entity_state_t *sendentitiesindex[MAX_EDICTS]; int sententitiesmark; int sententities[MAX_EDICTS]; int sententitiesconsideration[MAX_EDICTS]; /// legacy support for self.Version based csqc entity networking unsigned char csqcentityversion[MAX_EDICTS]; // legacy } server_t; #define NUM_CSQCENTITIES_PER_FRAME 256 typedef struct csqcentityframedb_s { int framenum; int num; unsigned short entno[NUM_CSQCENTITIES_PER_FRAME]; int sendflags[NUM_CSQCENTITIES_PER_FRAME]; } csqcentityframedb_t; // if defined this does ping smoothing, otherwise it does not //#define NUM_PING_TIMES 16 #define NUM_SPAWN_PARMS 16 typedef struct client_s { /// false = empty client slot qboolean active; /// false = don't do ClientDisconnect on drop qboolean clientconnectcalled; /// false = don't allow spawn qboolean prespawned; /// false = don't allow begin qboolean spawned; /// false = don't send datagrams qboolean begun; /// 1 = send svc_serverinfo and advance to 2, 2 doesn't send, then advances to 0 (allowing unlimited sending) when prespawn is received int sendsignon; /// requested rate in bytes per second int rate; /// temporarily exceed rate by this amount of bytes int rate_burstsize; /// realtime this client connected double connecttime; /// keepalive messages must be sent periodically during signon double keepalivetime; /// communications handle netconn_t *netconnection; unsigned int movesequence; signed char movement_count[NETGRAPH_PACKETS]; unsigned int movement_highestsequence_seen; // not the same as movesequence if prediction is off /// movement usercmd_t cmd; /// intended motion calced from cmd vec3_t wishdir; /// PRVM_EDICT_NUM(clientnum+1) prvm_edict_t *edict; #ifdef NUM_PING_TIMES float ping_times[NUM_PING_TIMES]; /// ping_times[num_pings%NUM_PING_TIMES] int num_pings; #endif /// LordHavoc: can be used for prediction or whatever... float ping; /// this is used by sv_clmovement_minping code double clmovement_disabletimeout; /// this is used by sv_clmovement_inputtimeout code float clmovement_inputtimeout; /// spawn parms are carried from level to level prvm_vec_t spawn_parms[NUM_SPAWN_PARMS]; // properties that are sent across the network only when changed char name[MAX_SCOREBOARDNAME], old_name[MAX_SCOREBOARDNAME]; int colors, old_colors; int frags, old_frags; char playermodel[MAX_QPATH], old_model[MAX_QPATH]; char playerskin[MAX_QPATH], old_skin[MAX_QPATH]; /// netaddress support char netaddress[MAX_QPATH]; /// visibility state float visibletime[MAX_EDICTS]; // scope is whether an entity is currently being networked to this client // sendflags is what properties have changed on the entity since the last // update that was sent int csqcnumedicts; unsigned char csqcentityscope[MAX_EDICTS]; unsigned int csqcentitysendflags[MAX_EDICTS]; #define NUM_CSQCENTITYDB_FRAMES 256 csqcentityframedb_t csqcentityframehistory[NUM_CSQCENTITYDB_FRAMES]; int csqcentityframehistory_next; int csqcentityframe_lastreset; /// prevent animated names float nametime; /// latest received clc_ackframe (used to detect packet loss) int latestframenum; /// cache weaponmodel name lookups char weaponmodel[MAX_QPATH]; int weaponmodelindex; /// clientcamera (entity to use as camera) int clientcamera; entityframe_database_t *entitydatabase; entityframe4_database_t *entitydatabase4; entityframe5_database_t *entitydatabase5; // delta compression of stats unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; int stats[MAX_CL_STATS]; unsigned char unreliablemsg_data[NET_MAXMESSAGE]; sizebuf_t unreliablemsg; int unreliablemsg_splitpoints; int unreliablemsg_splitpoint[NET_MAXMESSAGE/16]; // information on an active download if any qfile_t *download_file; int download_expectedposition; ///< next position the client should ack qboolean download_started; char download_name[MAX_QPATH]; qboolean download_deflate; // fixangle data qboolean fixangle_angles_set; vec3_t fixangle_angles; /// demo recording qfile_t *sv_demo_file; // number of skipped entity frames // if it exceeds a limit, an empty entity frame is sent int num_skippedentityframes; // last sent move sequence // if the move sequence changed, an empty entity frame is sent unsigned int lastmovesequence; } client_t; //============================================================================= // edict->movetype values #define MOVETYPE_NONE 0 ///< never moves #define MOVETYPE_ANGLENOCLIP 1 #define MOVETYPE_ANGLECLIP 2 #define MOVETYPE_WALK 3 ///< gravity #define MOVETYPE_STEP 4 ///< gravity, special edge handling #define MOVETYPE_FLY 5 #define MOVETYPE_TOSS 6 ///< gravity #define MOVETYPE_PUSH 7 ///< no clip to world, push and crush #define MOVETYPE_NOCLIP 8 #define MOVETYPE_FLYMISSILE 9 ///< extra size to monsters #define MOVETYPE_BOUNCE 10 #define MOVETYPE_BOUNCEMISSILE 11 ///< bounce w/o gravity #define MOVETYPE_FOLLOW 12 ///< track movement of aiment #define MOVETYPE_FAKEPUSH 13 ///< tenebrae's push that doesn't push #define MOVETYPE_PHYSICS 32 ///< indicates this object is physics controlled #define MOVETYPE_FLY_WORLDONLY 33 ///< like MOVETYPE_FLY, but uses MOVE_WORLDONLY for all its traces; objects of this movetype better be SOLID_NOT or SOLID_TRIGGER please, or else... #define MOVETYPE_USER_FIRST 128 ///< user defined movetypes #define MOVETYPE_USER_LAST 191 // edict->solid values #define SOLID_NOT 0 ///< no interaction with other objects #define SOLID_TRIGGER 1 ///< touch on edge, but not blocking #define SOLID_BBOX 2 ///< touch on edge, block #define SOLID_SLIDEBOX 3 ///< touch on edge, but not an onground #define SOLID_BSP 4 ///< bsp clip, touch on edge, block // LordHavoc: corpse code #define SOLID_CORPSE 5 ///< same as SOLID_BBOX, except it behaves as SOLID_NOT against SOLID_SLIDEBOX objects (players/monsters) // LordHavoc: physics // VorteX: now these fields are deprecated, as geomtype is more flexible #define SOLID_PHYSICS_BOX 32 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_SPHERE 33 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_CAPSULE 34 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_TRIMESH 35 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) #define SOLID_PHYSICS_CYLINDER 36 ///< physics object (mins, maxs, mass, origin, axis_forward, axis_left, axis_up, velocity, spinvelocity) // edict->deadflag values #define DEAD_NO 0 #define DEAD_DYING 1 #define DEAD_DEAD 2 #define DAMAGE_NO 0 #define DAMAGE_YES 1 #define DAMAGE_AIM 2 // edict->flags #define FL_FLY 1 #define FL_SWIM 2 #define FL_CONVEYOR 4 #define FL_CLIENT 8 #define FL_INWATER 16 #define FL_MONSTER 32 #define FL_GODMODE 64 #define FL_NOTARGET 128 #define FL_ITEM 256 #define FL_ONGROUND 512 #define FL_PARTIALGROUND 1024 ///< not all corners are valid #define FL_WATERJUMP 2048 ///< player jumping out of water #define FL_JUMPRELEASED 4096 ///< for jump debouncing #define SPAWNFLAG_NOT_EASY 256 #define SPAWNFLAG_NOT_MEDIUM 512 #define SPAWNFLAG_NOT_HARD 1024 #define SPAWNFLAG_NOT_DEATHMATCH 2048 //============================================================================ extern cvar_t coop; extern cvar_t deathmatch; extern cvar_t fraglimit; extern cvar_t gamecfg; extern cvar_t noexit; extern cvar_t nomonsters; extern cvar_t pausable; extern cvar_t pr_checkextension; extern cvar_t samelevel; extern cvar_t saved1; extern cvar_t saved2; extern cvar_t saved3; extern cvar_t saved4; extern cvar_t savedgamecfg; extern cvar_t scratch1; extern cvar_t scratch2; extern cvar_t scratch3; extern cvar_t scratch4; extern cvar_t skill; extern cvar_t slowmo; extern cvar_t sv_accelerate; extern cvar_t sv_aim; extern cvar_t sv_airaccel_qw; extern cvar_t sv_airaccel_sideways_friction; extern cvar_t sv_airaccelerate; extern cvar_t sv_airstopaccelerate; extern cvar_t sv_airstrafeaccelerate; extern cvar_t sv_maxairstrafespeed; extern cvar_t sv_airstrafeaccel_qw; extern cvar_t sv_aircontrol; extern cvar_t sv_aircontrol_power; extern cvar_t sv_aircontrol_penalty; extern cvar_t sv_airspeedlimit_nonqw; extern cvar_t sv_allowdownloads; extern cvar_t sv_allowdownloads_archive; extern cvar_t sv_allowdownloads_config; extern cvar_t sv_allowdownloads_dlcache; extern cvar_t sv_allowdownloads_inarchive; extern cvar_t sv_areagrid_mingridsize; extern cvar_t sv_checkforpacketsduringsleep; extern cvar_t sv_clmovement_enable; extern cvar_t sv_clmovement_minping; extern cvar_t sv_clmovement_minping_disabletime; extern cvar_t sv_clmovement_inputtimeout; extern cvar_t sv_clmovement_maxnetfps; extern cvar_t sv_cullentities_nevercullbmodels; extern cvar_t sv_cullentities_pvs; extern cvar_t sv_cullentities_stats; extern cvar_t sv_cullentities_trace; extern cvar_t sv_cullentities_trace_delay; extern cvar_t sv_cullentities_trace_enlarge; extern cvar_t sv_cullentities_trace_prediction; extern cvar_t sv_cullentities_trace_samples; extern cvar_t sv_cullentities_trace_samples_extra; extern cvar_t sv_debugmove; extern cvar_t sv_echobprint; extern cvar_t sv_edgefriction; extern cvar_t sv_entpatch; extern cvar_t sv_fixedframeratesingleplayer; extern cvar_t sv_freezenonclients; extern cvar_t sv_friction; extern cvar_t sv_gameplayfix_blowupfallenzombies; extern cvar_t sv_gameplayfix_consistentplayerprethink; extern cvar_t sv_gameplayfix_delayprojectiles; extern cvar_t sv_gameplayfix_droptofloorstartsolid; extern cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect; extern cvar_t sv_gameplayfix_easierwaterjump; extern cvar_t sv_gameplayfix_findradiusdistancetobox; extern cvar_t sv_gameplayfix_gravityunaffectedbyticrate; extern cvar_t sv_gameplayfix_grenadebouncedownslopes; extern cvar_t sv_gameplayfix_multiplethinksperframe; extern cvar_t sv_gameplayfix_noairborncorpse; extern cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems; extern cvar_t sv_gameplayfix_nudgeoutofsolid; extern cvar_t sv_gameplayfix_nudgeoutofsolid_separation; extern cvar_t sv_gameplayfix_q2airaccelerate; extern cvar_t sv_gameplayfix_nogravityonground; extern cvar_t sv_gameplayfix_setmodelrealbox; extern cvar_t sv_gameplayfix_slidemoveprojectiles; extern cvar_t sv_gameplayfix_stepdown; extern cvar_t sv_gameplayfix_stepmultipletimes; extern cvar_t sv_gameplayfix_nostepmoveonsteepslopes; extern cvar_t sv_gameplayfix_swiminbmodels; extern cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag; extern cvar_t sv_gameplayfix_downtracesupportsongroundflag; extern cvar_t sv_gameplayfix_q1bsptracelinereportstexture; extern cvar_t sv_gameplayfix_unstickplayers; extern cvar_t sv_gameplayfix_unstickentities; extern cvar_t sv_gameplayfix_fixedcheckwatertransition; extern cvar_t sv_gravity; extern cvar_t sv_idealpitchscale; extern cvar_t sv_jumpstep; extern cvar_t sv_jumpvelocity; extern cvar_t sv_maxairspeed; extern cvar_t sv_maxrate; extern cvar_t sv_maxspeed; extern cvar_t sv_maxvelocity; extern cvar_t sv_nostep; extern cvar_t sv_playerphysicsqc; extern cvar_t sv_progs; extern cvar_t sv_protocolname; extern cvar_t sv_random_seed; extern cvar_t sv_ratelimitlocalplayer; extern cvar_t sv_sound_land; extern cvar_t sv_sound_watersplash; extern cvar_t sv_stepheight; extern cvar_t sv_stopspeed; extern cvar_t sv_wallfriction; extern cvar_t sv_wateraccelerate; extern cvar_t sv_waterfriction; extern cvar_t sv_areadebug; extern cvar_t sys_ticrate; extern cvar_t teamplay; extern cvar_t temp1; extern cvar_t timelimit; extern mempool_t *sv_mempool; /// persistant server info extern server_static_t svs; /// local server extern server_t sv; extern client_t *host_client; //=========================================================== void SV_Init (void); void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count); void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate); void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int volume, float attenuation, qboolean reliable, float speed); void SV_StartPointSound (vec3_t origin, const char *sample, int volume, float attenuation, float speed); void SV_ConnectClient (int clientnum, netconn_t *netconnection); void SV_DropClient (qboolean crash); void SV_SendClientMessages(void); void SV_ReadClientMessage(void); // precachemode values: // 0 = fail if not precached, // 1 = warn if not found and precache if possible // 2 = precache int SV_ModelIndex(const char *s, int precachemode); int SV_SoundIndex(const char *s, int precachemode); int SV_ParticleEffectIndex(const char *name); dp_model_t *SV_GetModelByIndex(int modelindex); dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed); void SV_SetIdealPitch (void); void SV_AddUpdates (void); void SV_ClientThink (void); void SV_ClientPrint(const char *msg); void SV_ClientPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); void SV_BroadcastPrint(const char *msg); void SV_BroadcastPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); void SV_Physics (void); void SV_Physics_ClientMove (void); //void SV_Physics_ClientEntity (prvm_edict_t *ent); qboolean SV_PlayerCheckGround (prvm_edict_t *ent); qboolean SV_CheckBottom (prvm_edict_t *ent); qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace); /*! Needs to be called any time an entity changes origin, mins, maxs, or solid * sets ent->v.absmin and ent->v.absmax * call TouchAreaGrid as well to fire triggers that overlap the box */ void SV_LinkEdict(prvm_edict_t *ent); void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent); void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent); // if we detected a touch from another source /*! move an entity that is stuck by small amounts in various directions to try to nudge it back into the collision hull * returns true if it found a better place */ qboolean SV_UnstickEntity (prvm_edict_t *ent); /*! move an entity that is stuck out of the surface it is stuck in (can move large amounts) * returns true if it found a better place */ qboolean SV_NudgeOutOfSolid(prvm_edict_t *ent); /// calculates hitsupercontentsmask for a generic qc entity int SV_GenericHitSuperContentsMask(const prvm_edict_t *edict); /// traces a box move against worldmodel and all entities in the specified area trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend); trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend); trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask); int SV_EntitiesInBox(const vec3_t mins, const vec3_t maxs, int maxedicts, prvm_edict_t **resultedicts); qboolean SV_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs); int SV_PointSuperContents(const vec3_t point); void SV_FlushBroadcastMessages(void); void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); void VM_SV_MoveToGoal(prvm_prog_t *prog); void SV_ApplyClientMove (void); void SV_SaveSpawnparms (void); void SV_SpawnServer (const char *server); void SV_CheckVelocity (prvm_edict_t *ent); void SV_SetupVM(void); const char *Host_TimingReport(char *buf, size_t buflen); ///< for output in Host_Status_f int SV_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent); void SV_GetEntityMatrix(prvm_prog_t *prog, prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix); void SV_StartThread(void); void SV_StopThread(void); #define SV_LockThreadMutex() (void)(svs.threaded ? Thread_LockMutex(svs.threadmutex) : 0) #define SV_UnlockThreadMutex() (void)(svs.threaded ? Thread_UnlockMutex(svs.threadmutex) : 0) void VM_CustomStats_Clear(void); void VM_SV_UpdateCustomStats(client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats); void Host_Savegame_to(prvm_prog_t *prog, const char *name); void SV_SendServerinfo(client_t *client); #endif darkplaces/Doxyfile0000664000175000017500000017405013067716216013705 0ustar kalevkalev# Doxyfile 1.5.9 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = darkplaces # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = docs # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it parses. # With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this tag. # The format is ext=language, where ext is a file extension, and language is one of # the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, # Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), # use: inc=Fortran f=C. Note that for custom extensions you also need to set # FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = YES # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. # For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's # filter section matches. # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = NO # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Options related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO darkplaces/gl_rmain.c0000664000175000017500000217540113067716220014131 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_main.c #include "quakedef.h" #include "cl_dyntexture.h" #include "r_shadow.h" #include "polygon.h" #include "image.h" #include "ft2.h" #include "csprogs.h" #include "cl_video.h" #include "dpsoftrast.h" #ifdef SUPPORTD3D #include extern LPDIRECT3DDEVICE9 vid_d3d9dev; #endif #ifdef WIN32 // Enable NVIDIA High Performance Graphics while using Integrated Graphics. #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; #ifdef __cplusplus } #endif #endif mempool_t *r_main_mempool; rtexturepool_t *r_main_texturepool; static int r_textureframe = 0; ///< used only by R_GetCurrentTexture static qboolean r_loadnormalmap; static qboolean r_loadgloss; qboolean r_loadfog; static qboolean r_loaddds; static qboolean r_savedds; static qboolean r_gpuskeletal; // // screen size info // r_refdef_t r_refdef; cvar_t r_motionblur = {CVAR_SAVE, "r_motionblur", "0", "screen motionblur - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"}; cvar_t r_damageblur = {CVAR_SAVE, "r_damageblur", "0", "screen motionblur based on damage - value represents intensity, somewhere around 0.5 recommended - NOTE: bad performance on multi-gpu!"}; cvar_t r_motionblur_averaging = {CVAR_SAVE, "r_motionblur_averaging", "0.1", "sliding average reaction time for velocity (higher = slower adaption to change)"}; cvar_t r_motionblur_randomize = {CVAR_SAVE, "r_motionblur_randomize", "0.1", "randomizing coefficient to workaround ghosting"}; cvar_t r_motionblur_minblur = {CVAR_SAVE, "r_motionblur_minblur", "0.5", "factor of blur to apply at all times (always have this amount of blur no matter what the other factors are)"}; cvar_t r_motionblur_maxblur = {CVAR_SAVE, "r_motionblur_maxblur", "0.9", "maxmimum amount of blur"}; cvar_t r_motionblur_velocityfactor = {CVAR_SAVE, "r_motionblur_velocityfactor", "1", "factoring in of player velocity to the blur equation - the faster the player moves around the map, the more blur they get"}; cvar_t r_motionblur_velocityfactor_minspeed = {CVAR_SAVE, "r_motionblur_velocityfactor_minspeed", "400", "lower value of velocity when it starts to factor into blur equation"}; cvar_t r_motionblur_velocityfactor_maxspeed = {CVAR_SAVE, "r_motionblur_velocityfactor_maxspeed", "800", "upper value of velocity when it reaches the peak factor into blur equation"}; cvar_t r_motionblur_mousefactor = {CVAR_SAVE, "r_motionblur_mousefactor", "2", "factoring in of mouse acceleration to the blur equation - the faster the player turns their mouse, the more blur they get"}; cvar_t r_motionblur_mousefactor_minspeed = {CVAR_SAVE, "r_motionblur_mousefactor_minspeed", "0", "lower value of mouse acceleration when it starts to factor into blur equation"}; cvar_t r_motionblur_mousefactor_maxspeed = {CVAR_SAVE, "r_motionblur_mousefactor_maxspeed", "50", "upper value of mouse acceleration when it reaches the peak factor into blur equation"}; // TODO do we want a r_equalize_entities cvar that works on all ents, or would that be a cheat? cvar_t r_equalize_entities_fullbright = {CVAR_SAVE, "r_equalize_entities_fullbright", "0", "render fullbright entities by equalizing their lightness, not by not rendering light"}; cvar_t r_equalize_entities_minambient = {CVAR_SAVE, "r_equalize_entities_minambient", "0.5", "light equalizing: ensure at least this ambient/diffuse ratio"}; cvar_t r_equalize_entities_by = {CVAR_SAVE, "r_equalize_entities_by", "0.7", "light equalizing: exponent of dynamics compression (0 = no compression, 1 = full compression)"}; cvar_t r_equalize_entities_to = {CVAR_SAVE, "r_equalize_entities_to", "0.8", "light equalizing: target light level"}; cvar_t r_depthfirst = {CVAR_SAVE, "r_depthfirst", "0", "renders a depth-only version of the scene before normal rendering begins to eliminate overdraw, values: 0 = off, 1 = world depth, 2 = world and model depth"}; cvar_t r_useinfinitefarclip = {CVAR_SAVE, "r_useinfinitefarclip", "1", "enables use of a special kind of projection matrix that has an extremely large farclip"}; cvar_t r_farclip_base = {0, "r_farclip_base", "65536", "farclip (furthest visible distance) for rendering when r_useinfinitefarclip is 0"}; cvar_t r_farclip_world = {0, "r_farclip_world", "2", "adds map size to farclip multiplied by this value"}; cvar_t r_nearclip = {0, "r_nearclip", "1", "distance from camera of nearclip plane" }; cvar_t r_deformvertexes = {0, "r_deformvertexes", "1", "allows use of deformvertexes in shader files (can be turned off to check performance impact)"}; cvar_t r_transparent = {0, "r_transparent", "1", "allows use of transparent surfaces (can be turned off to check performance impact)"}; cvar_t r_transparent_alphatocoverage = {0, "r_transparent_alphatocoverage", "1", "enables GL_ALPHA_TO_COVERAGE antialiasing technique on alphablend and alphatest surfaces when using vid_samples 2 or higher"}; cvar_t r_transparent_sortsurfacesbynearest = {0, "r_transparent_sortsurfacesbynearest", "1", "sort entity and world surfaces by nearest point on bounding box instead of using the center of the bounding box, usually reduces sorting artifacts"}; cvar_t r_transparent_useplanardistance = {0, "r_transparent_useplanardistance", "0", "sort transparent meshes by distance from view plane rather than spherical distance to the chosen point"}; cvar_t r_showoverdraw = {0, "r_showoverdraw", "0", "shows overlapping geometry"}; cvar_t r_showbboxes = {0, "r_showbboxes", "0", "shows bounding boxes of server entities, value controls opacity scaling (1 = 10%, 10 = 100%)"}; cvar_t r_showsurfaces = {0, "r_showsurfaces", "0", "1 shows surfaces as different colors, or a value of 2 shows triangle draw order (for analyzing whether meshes are optimized for vertex cache)"}; cvar_t r_showtris = {0, "r_showtris", "0", "shows triangle outlines, value controls brightness (can be above 1)"}; cvar_t r_shownormals = {0, "r_shownormals", "0", "shows per-vertex surface normals and tangent vectors for bumpmapped lighting"}; cvar_t r_showlighting = {0, "r_showlighting", "0", "shows areas lit by lights, useful for finding out why some areas of a map render slowly (bright orange = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; cvar_t r_showshadowvolumes = {0, "r_showshadowvolumes", "0", "shows areas shadowed by lights, useful for finding out why some areas of a map render slowly (bright blue = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful"}; cvar_t r_showcollisionbrushes = {0, "r_showcollisionbrushes", "0", "draws collision brushes in quake3 maps (mode 1), mode 2 disables rendering of world (trippy!)"}; cvar_t r_showcollisionbrushes_polygonfactor = {0, "r_showcollisionbrushes_polygonfactor", "-1", "expands outward the brush polygons a little bit, used to make collision brushes appear infront of walls"}; cvar_t r_showcollisionbrushes_polygonoffset = {0, "r_showcollisionbrushes_polygonoffset", "0", "nudges brush polygon depth in hardware depth units, used to make collision brushes appear infront of walls"}; cvar_t r_showdisabledepthtest = {0, "r_showdisabledepthtest", "0", "disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing"}; cvar_t r_drawportals = {0, "r_drawportals", "0", "shows portals (separating polygons) in world interior in quake1 maps"}; cvar_t r_drawentities = {0, "r_drawentities","1", "draw entities (doors, players, projectiles, etc)"}; cvar_t r_draw2d = {0, "r_draw2d","1", "draw 2D stuff (dangerous to turn off)"}; cvar_t r_drawworld = {0, "r_drawworld","1", "draw world (most static stuff)"}; cvar_t r_drawviewmodel = {0, "r_drawviewmodel","1", "draw your weapon model"}; cvar_t r_drawexteriormodel = {0, "r_drawexteriormodel","1", "draw your player model (e.g. in chase cam, reflections)"}; cvar_t r_cullentities_trace = {0, "r_cullentities_trace", "1", "probabistically cull invisible entities"}; cvar_t r_cullentities_trace_samples = {0, "r_cullentities_trace_samples", "2", "number of samples to test for entity culling (in addition to center sample)"}; cvar_t r_cullentities_trace_tempentitysamples = {0, "r_cullentities_trace_tempentitysamples", "-1", "number of samples to test for entity culling of temp entities (including all CSQC entities), -1 disables trace culling on these entities to prevent flicker (pvs still applies)"}; cvar_t r_cullentities_trace_enlarge = {0, "r_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; cvar_t r_cullentities_trace_delay = {0, "r_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; cvar_t r_sortentities = {0, "r_sortentities", "0", "sort entities before drawing (might be faster)"}; cvar_t r_speeds = {0, "r_speeds","0", "displays rendering statistics and per-subsystem timings"}; cvar_t r_fullbright = {0, "r_fullbright","0", "makes map very bright and renders faster"}; cvar_t r_fakelight = {0, "r_fakelight","0", "render 'fake' lighting instead of real lightmaps"}; cvar_t r_fakelight_intensity = {0, "r_fakelight_intensity","0.75", "fakelight intensity modifier"}; #define FAKELIGHT_ENABLED (r_fakelight.integer >= 2 || (r_fakelight.integer && r_refdef.scene.worldmodel && !r_refdef.scene.worldmodel->lit)) cvar_t r_wateralpha = {CVAR_SAVE, "r_wateralpha","1", "opacity of water polygons"}; cvar_t r_dynamic = {CVAR_SAVE, "r_dynamic","1", "enables dynamic lights (rocket glow and such)"}; cvar_t r_fullbrights = {CVAR_SAVE, "r_fullbrights", "1", "enables glowing pixels in quake textures (changes need r_restart to take effect)"}; cvar_t r_shadows = {CVAR_SAVE, "r_shadows", "0", "casts fake stencil shadows from models onto the world (rtlights are unaffected by this); when set to 2, always cast the shadows in the direction set by r_shadows_throwdirection, otherwise use the model lighting."}; cvar_t r_shadows_darken = {CVAR_SAVE, "r_shadows_darken", "0.5", "how much shadowed areas will be darkened"}; cvar_t r_shadows_throwdistance = {CVAR_SAVE, "r_shadows_throwdistance", "500", "how far to cast shadows from models"}; cvar_t r_shadows_throwdirection = {CVAR_SAVE, "r_shadows_throwdirection", "0 0 -1", "override throwing direction for r_shadows 2"}; cvar_t r_shadows_drawafterrtlighting = {CVAR_SAVE, "r_shadows_drawafterrtlighting", "0", "draw fake shadows AFTER realtime lightning is drawn. May be useful for simulating fast sunlight on large outdoor maps with only one noshadow rtlight. The price is less realistic appearance of dynamic light shadows."}; cvar_t r_shadows_castfrombmodels = {CVAR_SAVE, "r_shadows_castfrombmodels", "0", "do cast shadows from bmodels"}; cvar_t r_shadows_focus = {CVAR_SAVE, "r_shadows_focus", "0 0 0", "offset the shadowed area focus"}; cvar_t r_shadows_shadowmapscale = {CVAR_SAVE, "r_shadows_shadowmapscale", "1", "increases shadowmap quality (multiply global shadowmap precision) for fake shadows. Needs shadowmapping ON."}; cvar_t r_shadows_shadowmapbias = {CVAR_SAVE, "r_shadows_shadowmapbias", "-1", "sets shadowmap bias for fake shadows. -1 sets the value of r_shadow_shadowmapping_bias. Needs shadowmapping ON."}; cvar_t r_q1bsp_skymasking = {0, "r_q1bsp_skymasking", "1", "allows sky polygons in quake1 maps to obscure other geometry"}; cvar_t r_polygonoffset_submodel_factor = {0, "r_polygonoffset_submodel_factor", "0", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"}; cvar_t r_polygonoffset_submodel_offset = {0, "r_polygonoffset_submodel_offset", "14", "biases depth values of world submodels such as doors, to prevent z-fighting artifacts in Quake maps"}; cvar_t r_polygonoffset_decals_factor = {0, "r_polygonoffset_decals_factor", "0", "biases depth values of decals to prevent z-fighting artifacts"}; cvar_t r_polygonoffset_decals_offset = {0, "r_polygonoffset_decals_offset", "-14", "biases depth values of decals to prevent z-fighting artifacts"}; cvar_t r_fog_exp2 = {0, "r_fog_exp2", "0", "uses GL_EXP2 fog (as in Nehahra) rather than realistic GL_EXP fog"}; cvar_t r_fog_clear = {0, "r_fog_clear", "1", "clears renderbuffer with fog color before render starts"}; cvar_t r_drawfog = {CVAR_SAVE, "r_drawfog", "1", "allows one to disable fog rendering"}; cvar_t r_transparentdepthmasking = {CVAR_SAVE, "r_transparentdepthmasking", "0", "enables depth writes on transparent meshes whose materially is normally opaque, this prevents seeing the inside of a transparent mesh"}; cvar_t r_transparent_sortmindist = {CVAR_SAVE, "r_transparent_sortmindist", "0", "lower distance limit for transparent sorting"}; cvar_t r_transparent_sortmaxdist = {CVAR_SAVE, "r_transparent_sortmaxdist", "32768", "upper distance limit for transparent sorting"}; cvar_t r_transparent_sortarraysize = {CVAR_SAVE, "r_transparent_sortarraysize", "4096", "number of distance-sorting layers"}; cvar_t r_celshading = {CVAR_SAVE, "r_celshading", "0", "cartoon-style light shading (OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9 cvar_t r_celoutlines = {CVAR_SAVE, "r_celoutlines", "0", "cartoon-style outlines (requires r_shadow_deferred; OpenGL 2.x only)"}; // FIXME remove OpenGL 2.x only once implemented for DX9 cvar_t gl_fogenable = {0, "gl_fogenable", "0", "nehahra fog enable (for Nehahra compatibility only)"}; cvar_t gl_fogdensity = {0, "gl_fogdensity", "0.25", "nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only)"}; cvar_t gl_fogred = {0, "gl_fogred","0.3", "nehahra fog color red value (for Nehahra compatibility only)"}; cvar_t gl_foggreen = {0, "gl_foggreen","0.3", "nehahra fog color green value (for Nehahra compatibility only)"}; cvar_t gl_fogblue = {0, "gl_fogblue","0.3", "nehahra fog color blue value (for Nehahra compatibility only)"}; cvar_t gl_fogstart = {0, "gl_fogstart", "0", "nehahra fog start distance (for Nehahra compatibility only)"}; cvar_t gl_fogend = {0, "gl_fogend","0", "nehahra fog end distance (for Nehahra compatibility only)"}; cvar_t gl_skyclip = {0, "gl_skyclip", "4608", "nehahra farclip distance - the real fog end (for Nehahra compatibility only)"}; cvar_t r_texture_dds_load = {CVAR_SAVE, "r_texture_dds_load", "0", "load compressed dds/filename.dds texture instead of filename.tga, if the file exists (requires driver support)"}; cvar_t r_texture_dds_save = {CVAR_SAVE, "r_texture_dds_save", "0", "save compressed dds/filename.dds texture when filename.tga is loaded, so that it can be loaded instead next time"}; cvar_t r_textureunits = {0, "r_textureunits", "32", "number of texture units to use in GL 1.1 and GL 1.3 rendering paths"}; static cvar_t gl_combine = {CVAR_READONLY, "gl_combine", "1", "indicates whether the OpenGL 1.3 rendering path is active"}; static cvar_t r_glsl = {CVAR_READONLY, "r_glsl", "1", "indicates whether the OpenGL 2.0 rendering path is active"}; cvar_t r_usedepthtextures = {CVAR_SAVE, "r_usedepthtextures", "1", "use depth texture instead of depth renderbuffer where possible, uses less video memory but may render slower (or faster) depending on hardware"}; cvar_t r_viewfbo = {CVAR_SAVE, "r_viewfbo", "0", "enables use of an 8bit (1) or 16bit (2) or 32bit (3) per component float framebuffer render, which may be at a different resolution than the video mode"}; cvar_t r_viewscale = {CVAR_SAVE, "r_viewscale", "1", "scaling factor for resolution of the fbo rendering method, must be > 0, can be above 1 for a costly antialiasing behavior, typical values are 0.5 for 1/4th as many pixels rendered, or 1 for normal rendering"}; cvar_t r_viewscale_fpsscaling = {CVAR_SAVE, "r_viewscale_fpsscaling", "0", "change resolution based on framerate"}; cvar_t r_viewscale_fpsscaling_min = {CVAR_SAVE, "r_viewscale_fpsscaling_min", "0.0625", "worst acceptable quality"}; cvar_t r_viewscale_fpsscaling_multiply = {CVAR_SAVE, "r_viewscale_fpsscaling_multiply", "5", "adjust quality up or down by the frametime difference from 1.0/target, multiplied by this factor"}; cvar_t r_viewscale_fpsscaling_stepsize = {CVAR_SAVE, "r_viewscale_fpsscaling_stepsize", "0.01", "smallest adjustment to hit the target framerate (this value prevents minute oscillations)"}; cvar_t r_viewscale_fpsscaling_stepmax = {CVAR_SAVE, "r_viewscale_fpsscaling_stepmax", "1.00", "largest adjustment to hit the target framerate (this value prevents wild overshooting of the estimate)"}; cvar_t r_viewscale_fpsscaling_target = {CVAR_SAVE, "r_viewscale_fpsscaling_target", "70", "desired framerate"}; cvar_t r_glsl_skeletal = {CVAR_SAVE, "r_glsl_skeletal", "1", "render skeletal models faster using a gpu-skinning technique"}; cvar_t r_glsl_deluxemapping = {CVAR_SAVE, "r_glsl_deluxemapping", "1", "use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps)"}; cvar_t r_glsl_offsetmapping = {CVAR_SAVE, "r_glsl_offsetmapping", "0", "offset mapping effect (also known as parallax mapping or virtual displacement mapping)"}; cvar_t r_glsl_offsetmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_steps", "2", "offset mapping steps (note: too high values may be not supported by your GPU)"}; cvar_t r_glsl_offsetmapping_reliefmapping = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping", "0", "relief mapping effect (higher quality)"}; cvar_t r_glsl_offsetmapping_reliefmapping_steps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_steps", "10", "relief mapping steps (note: too high values may be not supported by your GPU)"}; cvar_t r_glsl_offsetmapping_reliefmapping_refinesteps = {CVAR_SAVE, "r_glsl_offsetmapping_reliefmapping_refinesteps", "5", "relief mapping refine steps (these are a binary search executed as the last step as given by r_glsl_offsetmapping_reliefmapping_steps)"}; cvar_t r_glsl_offsetmapping_scale = {CVAR_SAVE, "r_glsl_offsetmapping_scale", "0.04", "how deep the offset mapping effect is"}; cvar_t r_glsl_offsetmapping_lod = {CVAR_SAVE, "r_glsl_offsetmapping_lod", "0", "apply distance-based level-of-detail correction to number of offsetmappig steps, effectively making it render faster on large open-area maps"}; cvar_t r_glsl_offsetmapping_lod_distance = {CVAR_SAVE, "r_glsl_offsetmapping_lod_distance", "32", "first LOD level distance, second level (-50% steps) is 2x of this, third (33%) - 3x etc."}; cvar_t r_glsl_postprocess = {CVAR_SAVE, "r_glsl_postprocess", "0", "use a GLSL postprocessing shader"}; cvar_t r_glsl_postprocess_uservec1 = {CVAR_SAVE, "r_glsl_postprocess_uservec1", "0 0 0 0", "a 4-component vector to pass as uservec1 to the postprocessing shader (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec2 = {CVAR_SAVE, "r_glsl_postprocess_uservec2", "0 0 0 0", "a 4-component vector to pass as uservec2 to the postprocessing shader (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec3 = {CVAR_SAVE, "r_glsl_postprocess_uservec3", "0 0 0 0", "a 4-component vector to pass as uservec3 to the postprocessing shader (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec4 = {CVAR_SAVE, "r_glsl_postprocess_uservec4", "0 0 0 0", "a 4-component vector to pass as uservec4 to the postprocessing shader (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec1_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec1_enable", "1", "enables postprocessing uservec1 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec2_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec2_enable", "1", "enables postprocessing uservec2 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec3_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec3_enable", "1", "enables postprocessing uservec3 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; cvar_t r_glsl_postprocess_uservec4_enable = {CVAR_SAVE, "r_glsl_postprocess_uservec4_enable", "1", "enables postprocessing uservec4 usage, creates USERVEC1 define (only useful if default.glsl has been customized)"}; cvar_t r_water = {CVAR_SAVE, "r_water", "0", "whether to use reflections and refraction on water surfaces (note: r_wateralpha must be set below 1)"}; cvar_t r_water_cameraentitiesonly = {CVAR_SAVE, "r_water_cameraentitiesonly", "0", "whether to only show QC-defined reflections/refractions (typically used for camera- or portal-like effects)"}; cvar_t r_water_clippingplanebias = {CVAR_SAVE, "r_water_clippingplanebias", "1", "a rather technical setting which avoids black pixels around water edges"}; cvar_t r_water_resolutionmultiplier = {CVAR_SAVE, "r_water_resolutionmultiplier", "0.5", "multiplier for screen resolution when rendering refracted/reflected scenes, 1 is full quality, lower values are faster"}; cvar_t r_water_refractdistort = {CVAR_SAVE, "r_water_refractdistort", "0.01", "how much water refractions shimmer"}; cvar_t r_water_reflectdistort = {CVAR_SAVE, "r_water_reflectdistort", "0.01", "how much water reflections shimmer"}; cvar_t r_water_scissormode = {0, "r_water_scissormode", "3", "scissor (1) or cull (2) or both (3) water renders"}; cvar_t r_water_lowquality = {0, "r_water_lowquality", "0", "special option to accelerate water rendering, 1 disables shadows and particles, 2 disables all dynamic lights"}; cvar_t r_water_hideplayer = {CVAR_SAVE, "r_water_hideplayer", "0", "if set to 1 then player will be hidden in refraction views, if set to 2 then player will also be hidden in reflection views, player is always visible in camera views"}; cvar_t r_water_fbo = {CVAR_SAVE, "r_water_fbo", "1", "enables use of render to texture for water effects, otherwise copy to texture is used (slower)"}; cvar_t r_lerpsprites = {CVAR_SAVE, "r_lerpsprites", "0", "enables animation smoothing on sprites"}; cvar_t r_lerpmodels = {CVAR_SAVE, "r_lerpmodels", "1", "enables animation smoothing on models"}; cvar_t r_lerplightstyles = {CVAR_SAVE, "r_lerplightstyles", "0", "enable animation smoothing on flickering lights"}; cvar_t r_waterscroll = {CVAR_SAVE, "r_waterscroll", "1", "makes water scroll around, value controls how much"}; cvar_t r_bloom = {CVAR_SAVE, "r_bloom", "0", "enables bloom effect (makes bright pixels affect neighboring pixels)"}; cvar_t r_bloom_colorscale = {CVAR_SAVE, "r_bloom_colorscale", "1", "how bright the glow is"}; cvar_t r_bloom_brighten = {CVAR_SAVE, "r_bloom_brighten", "2", "how bright the glow is, after subtract/power"}; cvar_t r_bloom_blur = {CVAR_SAVE, "r_bloom_blur", "4", "how large the glow is"}; cvar_t r_bloom_resolution = {CVAR_SAVE, "r_bloom_resolution", "320", "what resolution to perform the bloom effect at (independent of screen resolution)"}; cvar_t r_bloom_colorexponent = {CVAR_SAVE, "r_bloom_colorexponent", "1", "how exaggerated the glow is"}; cvar_t r_bloom_colorsubtract = {CVAR_SAVE, "r_bloom_colorsubtract", "0.125", "reduces bloom colors by a certain amount"}; cvar_t r_bloom_scenebrightness = {CVAR_SAVE, "r_bloom_scenebrightness", "1", "global rendering brightness when bloom is enabled"}; cvar_t r_hdr_scenebrightness = {CVAR_SAVE, "r_hdr_scenebrightness", "1", "global rendering brightness"}; cvar_t r_hdr_glowintensity = {CVAR_SAVE, "r_hdr_glowintensity", "1", "how bright light emitting textures should appear"}; cvar_t r_hdr_irisadaptation = {CVAR_SAVE, "r_hdr_irisadaptation", "0", "adjust scene brightness according to light intensity at player location"}; cvar_t r_hdr_irisadaptation_multiplier = {CVAR_SAVE, "r_hdr_irisadaptation_multiplier", "2", "brightness at which value will be 1.0"}; cvar_t r_hdr_irisadaptation_minvalue = {CVAR_SAVE, "r_hdr_irisadaptation_minvalue", "0.5", "minimum value that can result from multiplier / brightness"}; cvar_t r_hdr_irisadaptation_maxvalue = {CVAR_SAVE, "r_hdr_irisadaptation_maxvalue", "4", "maximum value that can result from multiplier / brightness"}; cvar_t r_hdr_irisadaptation_value = {0, "r_hdr_irisadaptation_value", "1", "current value as scenebrightness multiplier, changes continuously when irisadaptation is active"}; cvar_t r_hdr_irisadaptation_fade_up = {CVAR_SAVE, "r_hdr_irisadaptation_fade_up", "0.1", "fade rate at which value adjusts to darkness"}; cvar_t r_hdr_irisadaptation_fade_down = {CVAR_SAVE, "r_hdr_irisadaptation_fade_down", "0.5", "fade rate at which value adjusts to brightness"}; cvar_t r_hdr_irisadaptation_radius = {CVAR_SAVE, "r_hdr_irisadaptation_radius", "15", "lighting within this many units of the eye is averaged"}; cvar_t r_smoothnormals_areaweighting = {0, "r_smoothnormals_areaweighting", "1", "uses significantly faster (and supposedly higher quality) area-weighted vertex normals and tangent vectors rather than summing normalized triangle normals and tangents"}; cvar_t developer_texturelogging = {0, "developer_texturelogging", "0", "produces a textures.log file containing names of skins and map textures the engine tried to load"}; cvar_t gl_lightmaps = {0, "gl_lightmaps", "0", "draws only lightmaps, no texture (for level designers), a value of 2 keeps normalmap shading"}; cvar_t r_test = {0, "r_test", "0", "internal development use only, leave it alone (usually does nothing anyway)"}; cvar_t r_batch_multidraw = {CVAR_SAVE, "r_batch_multidraw", "1", "issue multiple glDrawElements calls when rendering a batch of surfaces with the same texture (otherwise the index data is copied to make it one draw)"}; cvar_t r_batch_multidraw_mintriangles = {CVAR_SAVE, "r_batch_multidraw_mintriangles", "0", "minimum number of triangles to activate multidraw path (copying small groups of triangles may be faster)"}; cvar_t r_batch_debugdynamicvertexpath = {CVAR_SAVE, "r_batch_debugdynamicvertexpath", "0", "force the dynamic batching code path for debugging purposes"}; cvar_t r_batch_dynamicbuffer = {CVAR_SAVE, "r_batch_dynamicbuffer", "0", "use vertex/index buffers for drawing dynamic and copytriangles batches"}; cvar_t r_glsl_saturation = {CVAR_SAVE, "r_glsl_saturation", "1", "saturation multiplier (only working in glsl!)"}; cvar_t r_glsl_saturation_redcompensate = {CVAR_SAVE, "r_glsl_saturation_redcompensate", "0", "a 'vampire sight' addition to desaturation effect, does compensation for red color, r_glsl_restart is required"}; cvar_t r_glsl_vertextextureblend_usebothalphas = {CVAR_SAVE, "r_glsl_vertextextureblend_usebothalphas", "0", "use both alpha layers on vertex blended surfaces, each alpha layer sets amount of 'blend leak' on another layer, requires mod_q3shader_force_terrain_alphaflag on."}; cvar_t r_framedatasize = {CVAR_SAVE, "r_framedatasize", "0.5", "size of renderer data cache used during one frame (for skeletal animation caching, light processing, etc)"}; cvar_t r_buffermegs[R_BUFFERDATA_COUNT] = { {CVAR_SAVE, "r_buffermegs_vertex", "4", "vertex buffer size for one frame"}, {CVAR_SAVE, "r_buffermegs_index16", "1", "index buffer size for one frame (16bit indices)"}, {CVAR_SAVE, "r_buffermegs_index32", "1", "index buffer size for one frame (32bit indices)"}, {CVAR_SAVE, "r_buffermegs_uniform", "0.25", "uniform buffer size for one frame"}, }; extern cvar_t v_glslgamma; extern cvar_t v_glslgamma_2d; extern qboolean v_flipped_state; r_framebufferstate_t r_fb; /// shadow volume bsp struct with automatically growing nodes buffer svbsp_t r_svbsp; int r_uniformbufferalignment = 32; // dynamically updated to match GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT rtexture_t *r_texture_blanknormalmap; rtexture_t *r_texture_white; rtexture_t *r_texture_grey128; rtexture_t *r_texture_black; rtexture_t *r_texture_notexture; rtexture_t *r_texture_whitecube; rtexture_t *r_texture_normalizationcube; rtexture_t *r_texture_fogattenuation; rtexture_t *r_texture_fogheighttexture; rtexture_t *r_texture_gammaramps; unsigned int r_texture_gammaramps_serial; //rtexture_t *r_texture_fogintensity; rtexture_t *r_texture_reflectcube; // TODO: hash lookups? typedef struct cubemapinfo_s { char basename[64]; rtexture_t *texture; } cubemapinfo_t; int r_texture_numcubemaps; cubemapinfo_t *r_texture_cubemaps[MAX_CUBEMAPS]; unsigned int r_queries[MAX_OCCLUSION_QUERIES]; unsigned int r_numqueries; unsigned int r_maxqueries; typedef struct r_qwskincache_s { char name[MAX_QPATH]; skinframe_t *skinframe; } r_qwskincache_t; static r_qwskincache_t *r_qwskincache; static int r_qwskincache_size; /// vertex coordinates for a quad that covers the screen exactly extern const float r_screenvertex3f[12]; extern const float r_d3dscreenvertex3f[12]; const float r_screenvertex3f[12] = { 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0 }; const float r_d3dscreenvertex3f[12] = { 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }; void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b) { int i; for (i = 0;i < verts;i++) { out[0] = in[0] * r; out[1] = in[1] * g; out[2] = in[2] * b; out[3] = in[3]; in += 4; out += 4; } } void R_FillColors(float *out, int verts, float r, float g, float b, float a) { int i; for (i = 0;i < verts;i++) { out[0] = r; out[1] = g; out[2] = b; out[3] = a; out += 4; } } // FIXME: move this to client? void FOG_clear(void) { if (gamemode == GAME_NEHAHRA) { Cvar_Set("gl_fogenable", "0"); Cvar_Set("gl_fogdensity", "0.2"); Cvar_Set("gl_fogred", "0.3"); Cvar_Set("gl_foggreen", "0.3"); Cvar_Set("gl_fogblue", "0.3"); } r_refdef.fog_density = 0; r_refdef.fog_red = 0; r_refdef.fog_green = 0; r_refdef.fog_blue = 0; r_refdef.fog_alpha = 1; r_refdef.fog_start = 0; r_refdef.fog_end = 16384; r_refdef.fog_height = 1<<30; r_refdef.fog_fadedepth = 128; memset(r_refdef.fog_height_texturename, 0, sizeof(r_refdef.fog_height_texturename)); } static void R_BuildBlankTextures(void) { unsigned char data[4]; data[2] = 128; // normal X data[1] = 128; // normal Y data[0] = 255; // normal Z data[3] = 255; // height r_texture_blanknormalmap = R_LoadTexture2D(r_main_texturepool, "blankbump", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); data[0] = 255; data[1] = 255; data[2] = 255; data[3] = 255; r_texture_white = R_LoadTexture2D(r_main_texturepool, "blankwhite", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); data[0] = 128; data[1] = 128; data[2] = 128; data[3] = 255; r_texture_grey128 = R_LoadTexture2D(r_main_texturepool, "blankgrey128", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 255; r_texture_black = R_LoadTexture2D(r_main_texturepool, "blankblack", 1, 1, data, TEXTYPE_BGRA, TEXF_PERSISTENT, -1, NULL); } static void R_BuildNoTexture(void) { int x, y; unsigned char pix[16][16][4]; // this makes a light grey/dark grey checkerboard texture for (y = 0;y < 16;y++) { for (x = 0;x < 16;x++) { if ((y < 8) ^ (x < 8)) { pix[y][x][0] = 128; pix[y][x][1] = 128; pix[y][x][2] = 128; pix[y][x][3] = 255; } else { pix[y][x][0] = 64; pix[y][x][1] = 64; pix[y][x][2] = 64; pix[y][x][3] = 255; } } } r_texture_notexture = R_LoadTexture2D(r_main_texturepool, "notexture", 16, 16, &pix[0][0][0], TEXTYPE_BGRA, TEXF_MIPMAP | TEXF_PERSISTENT, -1, NULL); } static void R_BuildWhiteCube(void) { unsigned char data[6*1*1*4]; memset(data, 255, sizeof(data)); r_texture_whitecube = R_LoadTextureCubeMap(r_main_texturepool, "whitecube", 1, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); } static void R_BuildNormalizationCube(void) { int x, y, side; vec3_t v; vec_t s, t, intensity; #define NORMSIZE 64 unsigned char *data; data = (unsigned char *)Mem_Alloc(tempmempool, 6*NORMSIZE*NORMSIZE*4); for (side = 0;side < 6;side++) { for (y = 0;y < NORMSIZE;y++) { for (x = 0;x < NORMSIZE;x++) { s = (x + 0.5f) * (2.0f / NORMSIZE) - 1.0f; t = (y + 0.5f) * (2.0f / NORMSIZE) - 1.0f; switch(side) { default: case 0: v[0] = 1; v[1] = -t; v[2] = -s; break; case 1: v[0] = -1; v[1] = -t; v[2] = s; break; case 2: v[0] = s; v[1] = 1; v[2] = t; break; case 3: v[0] = s; v[1] = -1; v[2] = -t; break; case 4: v[0] = s; v[1] = -t; v[2] = 1; break; case 5: v[0] = -s; v[1] = -t; v[2] = -1; break; } intensity = 127.0f / sqrt(DotProduct(v, v)); data[((side*64+y)*64+x)*4+2] = (unsigned char)(128.0f + intensity * v[0]); data[((side*64+y)*64+x)*4+1] = (unsigned char)(128.0f + intensity * v[1]); data[((side*64+y)*64+x)*4+0] = (unsigned char)(128.0f + intensity * v[2]); data[((side*64+y)*64+x)*4+3] = 255; } } } r_texture_normalizationcube = R_LoadTextureCubeMap(r_main_texturepool, "normalcube", NORMSIZE, data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); Mem_Free(data); } static void R_BuildFogTexture(void) { int x, b; #define FOGWIDTH 256 unsigned char data1[FOGWIDTH][4]; //unsigned char data2[FOGWIDTH][4]; double d, r, alpha; r_refdef.fogmasktable_start = r_refdef.fog_start; r_refdef.fogmasktable_alpha = r_refdef.fog_alpha; r_refdef.fogmasktable_range = r_refdef.fogrange; r_refdef.fogmasktable_density = r_refdef.fog_density; r = r_refdef.fogmasktable_range / FOGMASKTABLEWIDTH; for (x = 0;x < FOGMASKTABLEWIDTH;x++) { d = (x * r - r_refdef.fogmasktable_start); if(developer_extra.integer) Con_DPrintf("%f ", d); d = max(0, d); if (r_fog_exp2.integer) alpha = exp(-r_refdef.fogmasktable_density * r_refdef.fogmasktable_density * 0.0001 * d * d); else alpha = exp(-r_refdef.fogmasktable_density * 0.004 * d); if(developer_extra.integer) Con_DPrintf(" : %f ", alpha); alpha = 1 - (1 - alpha) * r_refdef.fogmasktable_alpha; if(developer_extra.integer) Con_DPrintf(" = %f\n", alpha); r_refdef.fogmasktable[x] = bound(0, alpha, 1); } for (x = 0;x < FOGWIDTH;x++) { b = (int)(r_refdef.fogmasktable[x * (FOGMASKTABLEWIDTH - 1) / (FOGWIDTH - 1)] * 255); data1[x][0] = b; data1[x][1] = b; data1[x][2] = b; data1[x][3] = 255; //data2[x][0] = 255 - b; //data2[x][1] = 255 - b; //data2[x][2] = 255 - b; //data2[x][3] = 255; } if (r_texture_fogattenuation) { R_UpdateTexture(r_texture_fogattenuation, &data1[0][0], 0, 0, 0, FOGWIDTH, 1, 1); //R_UpdateTexture(r_texture_fogattenuation, &data2[0][0], 0, 0, 0, FOGWIDTH, 1, 1); } else { r_texture_fogattenuation = R_LoadTexture2D(r_main_texturepool, "fogattenuation", FOGWIDTH, 1, &data1[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); //r_texture_fogintensity = R_LoadTexture2D(r_main_texturepool, "fogintensity", FOGWIDTH, 1, &data2[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP, NULL); } } static void R_BuildFogHeightTexture(void) { unsigned char *inpixels; int size; int x; int y; int j; float c[4]; float f; inpixels = NULL; strlcpy(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename, sizeof(r_refdef.fogheighttexturename)); if (r_refdef.fogheighttexturename[0]) inpixels = loadimagepixelsbgra(r_refdef.fogheighttexturename, true, false, false, NULL); if (!inpixels) { r_refdef.fog_height_tablesize = 0; if (r_texture_fogheighttexture) R_FreeTexture(r_texture_fogheighttexture); r_texture_fogheighttexture = NULL; if (r_refdef.fog_height_table2d) Mem_Free(r_refdef.fog_height_table2d); r_refdef.fog_height_table2d = NULL; if (r_refdef.fog_height_table1d) Mem_Free(r_refdef.fog_height_table1d); r_refdef.fog_height_table1d = NULL; return; } size = image_width; r_refdef.fog_height_tablesize = size; r_refdef.fog_height_table1d = (unsigned char *)Mem_Alloc(r_main_mempool, size * 4); r_refdef.fog_height_table2d = (unsigned char *)Mem_Alloc(r_main_mempool, size * size * 4); memcpy(r_refdef.fog_height_table1d, inpixels, size * 4); Mem_Free(inpixels); // LordHavoc: now the magic - what is that table2d for? it is a cooked // average fog color table accounting for every fog layer between a point // and the camera. (Note: attenuation is handled separately!) for (y = 0;y < size;y++) { for (x = 0;x < size;x++) { Vector4Clear(c); f = 0; if (x < y) { for (j = x;j <= y;j++) { Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); f++; } } else { for (j = x;j >= y;j--) { Vector4Add(c, r_refdef.fog_height_table1d + j*4, c); f++; } } f = 1.0f / f; r_refdef.fog_height_table2d[(y*size+x)*4+0] = (unsigned char)(c[0] * f); r_refdef.fog_height_table2d[(y*size+x)*4+1] = (unsigned char)(c[1] * f); r_refdef.fog_height_table2d[(y*size+x)*4+2] = (unsigned char)(c[2] * f); r_refdef.fog_height_table2d[(y*size+x)*4+3] = (unsigned char)(c[3] * f); } } r_texture_fogheighttexture = R_LoadTexture2D(r_main_texturepool, "fogheighttable", size, size, r_refdef.fog_height_table2d, TEXTYPE_BGRA, TEXF_ALPHA | TEXF_CLAMP, -1, NULL); } //======================================================================================================================================================= static const char *builtinshaderstrings[] = { #include "shader_glsl.h" 0 }; const char *builtinhlslshaderstrings[] = { #include "shader_hlsl.h" 0 }; char *glslshaderstring = NULL; char *hlslshaderstring = NULL; //======================================================================================================================================================= typedef struct shaderpermutationinfo_s { const char *pretext; const char *name; } shaderpermutationinfo_t; typedef struct shadermodeinfo_s { const char *filename; const char *pretext; const char *name; } shadermodeinfo_t; // NOTE: MUST MATCH ORDER OF SHADERPERMUTATION_* DEFINES! shaderpermutationinfo_t shaderpermutationinfo[SHADERPERMUTATION_COUNT] = { {"#define USEDIFFUSE\n", " diffuse"}, {"#define USEVERTEXTEXTUREBLEND\n", " vertextextureblend"}, {"#define USEVIEWTINT\n", " viewtint"}, {"#define USECOLORMAPPING\n", " colormapping"}, {"#define USESATURATION\n", " saturation"}, {"#define USEFOGINSIDE\n", " foginside"}, {"#define USEFOGOUTSIDE\n", " fogoutside"}, {"#define USEFOGHEIGHTTEXTURE\n", " fogheighttexture"}, {"#define USEFOGALPHAHACK\n", " fogalphahack"}, {"#define USEGAMMARAMPS\n", " gammaramps"}, {"#define USECUBEFILTER\n", " cubefilter"}, {"#define USEGLOW\n", " glow"}, {"#define USEBLOOM\n", " bloom"}, {"#define USESPECULAR\n", " specular"}, {"#define USEPOSTPROCESSING\n", " postprocessing"}, {"#define USEREFLECTION\n", " reflection"}, {"#define USEOFFSETMAPPING\n", " offsetmapping"}, {"#define USEOFFSETMAPPING_RELIEFMAPPING\n", " reliefmapping"}, {"#define USESHADOWMAP2D\n", " shadowmap2d"}, {"#define USESHADOWMAPVSDCT\n", " shadowmapvsdct"}, // TODO make this a static parm {"#define USESHADOWMAPORTHO\n", " shadowmaportho"}, {"#define USEDEFERREDLIGHTMAP\n", " deferredlightmap"}, {"#define USEALPHAKILL\n", " alphakill"}, {"#define USEREFLECTCUBE\n", " reflectcube"}, {"#define USENORMALMAPSCROLLBLEND\n", " normalmapscrollblend"}, {"#define USEBOUNCEGRID\n", " bouncegrid"}, {"#define USEBOUNCEGRIDDIRECTIONAL\n", " bouncegriddirectional"}, // TODO make this a static parm {"#define USETRIPPY\n", " trippy"}, {"#define USEDEPTHRGB\n", " depthrgb"}, {"#define USEALPHAGENVERTEX\n", " alphagenvertex"}, {"#define USESKELETAL\n", " skeletal"}, {"#define USEOCCLUDE\n", " occlude"} }; // NOTE: MUST MATCH ORDER OF SHADERMODE_* ENUMS! shadermodeinfo_t glslshadermodeinfo[SHADERMODE_COUNT] = { {"glsl/default.glsl", "#define MODE_GENERIC\n", " generic"}, {"glsl/default.glsl", "#define MODE_POSTPROCESS\n", " postprocess"}, {"glsl/default.glsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, {"glsl/default.glsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, {"glsl/default.glsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, {"glsl/default.glsl", "#define MODE_LIGHTMAP\n", " lightmap"}, {"glsl/default.glsl", "#define MODE_FAKELIGHT\n", " fakelight"}, {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"}, {"glsl/default.glsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"}, {"glsl/default.glsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, {"glsl/default.glsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, {"glsl/default.glsl", "#define MODE_REFRACTION\n", " refraction"}, {"glsl/default.glsl", "#define MODE_WATER\n", " water"}, {"glsl/default.glsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, {"glsl/default.glsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, }; shadermodeinfo_t hlslshadermodeinfo[SHADERMODE_COUNT] = { {"hlsl/default.hlsl", "#define MODE_GENERIC\n", " generic"}, {"hlsl/default.hlsl", "#define MODE_POSTPROCESS\n", " postprocess"}, {"hlsl/default.hlsl", "#define MODE_DEPTH_OR_SHADOW\n", " depth/shadow"}, {"hlsl/default.hlsl", "#define MODE_FLATCOLOR\n", " flatcolor"}, {"hlsl/default.hlsl", "#define MODE_VERTEXCOLOR\n", " vertexcolor"}, {"hlsl/default.hlsl", "#define MODE_LIGHTMAP\n", " lightmap"}, {"hlsl/default.hlsl", "#define MODE_FAKELIGHT\n", " fakelight"}, {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " lightdirectionmap_modelspace"}, {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " lightdirectionmap_tangentspace"}, {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP\n", " lightdirectionmap_forced_lightmap"}, {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR\n", " lightdirectionmap_forced_vertexcolor"}, {"hlsl/default.hlsl", "#define MODE_LIGHTDIRECTION\n", " lightdirection"}, {"hlsl/default.hlsl", "#define MODE_LIGHTSOURCE\n", " lightsource"}, {"hlsl/default.hlsl", "#define MODE_REFRACTION\n", " refraction"}, {"hlsl/default.hlsl", "#define MODE_WATER\n", " water"}, {"hlsl/default.hlsl", "#define MODE_DEFERREDGEOMETRY\n", " deferredgeometry"}, {"hlsl/default.hlsl", "#define MODE_DEFERREDLIGHTSOURCE\n", " deferredlightsource"}, }; struct r_glsl_permutation_s; typedef struct r_glsl_permutation_s { /// hash lookup data struct r_glsl_permutation_s *hashnext; unsigned int mode; unsigned int permutation; /// indicates if we have tried compiling this permutation already qboolean compiled; /// 0 if compilation failed int program; // texture units assigned to each detected uniform int tex_Texture_First; int tex_Texture_Second; int tex_Texture_GammaRamps; int tex_Texture_Normal; int tex_Texture_Color; int tex_Texture_Gloss; int tex_Texture_Glow; int tex_Texture_SecondaryNormal; int tex_Texture_SecondaryColor; int tex_Texture_SecondaryGloss; int tex_Texture_SecondaryGlow; int tex_Texture_Pants; int tex_Texture_Shirt; int tex_Texture_FogHeightTexture; int tex_Texture_FogMask; int tex_Texture_Lightmap; int tex_Texture_Deluxemap; int tex_Texture_Attenuation; int tex_Texture_Cube; int tex_Texture_Refraction; int tex_Texture_Reflection; int tex_Texture_ShadowMap2D; int tex_Texture_CubeProjection; int tex_Texture_ScreenNormalMap; int tex_Texture_ScreenDiffuse; int tex_Texture_ScreenSpecular; int tex_Texture_ReflectMask; int tex_Texture_ReflectCube; int tex_Texture_BounceGrid; /// locations of detected uniforms in program object, or -1 if not found int loc_Texture_First; int loc_Texture_Second; int loc_Texture_GammaRamps; int loc_Texture_Normal; int loc_Texture_Color; int loc_Texture_Gloss; int loc_Texture_Glow; int loc_Texture_SecondaryNormal; int loc_Texture_SecondaryColor; int loc_Texture_SecondaryGloss; int loc_Texture_SecondaryGlow; int loc_Texture_Pants; int loc_Texture_Shirt; int loc_Texture_FogHeightTexture; int loc_Texture_FogMask; int loc_Texture_Lightmap; int loc_Texture_Deluxemap; int loc_Texture_Attenuation; int loc_Texture_Cube; int loc_Texture_Refraction; int loc_Texture_Reflection; int loc_Texture_ShadowMap2D; int loc_Texture_CubeProjection; int loc_Texture_ScreenNormalMap; int loc_Texture_ScreenDiffuse; int loc_Texture_ScreenSpecular; int loc_Texture_ReflectMask; int loc_Texture_ReflectCube; int loc_Texture_BounceGrid; int loc_Alpha; int loc_BloomBlur_Parameters; int loc_ClientTime; int loc_Color_Ambient; int loc_Color_Diffuse; int loc_Color_Specular; int loc_Color_Glow; int loc_Color_Pants; int loc_Color_Shirt; int loc_DeferredColor_Ambient; int loc_DeferredColor_Diffuse; int loc_DeferredColor_Specular; int loc_DeferredMod_Diffuse; int loc_DeferredMod_Specular; int loc_DistortScaleRefractReflect; int loc_EyePosition; int loc_FogColor; int loc_FogHeightFade; int loc_FogPlane; int loc_FogPlaneViewDist; int loc_FogRangeRecip; int loc_LightColor; int loc_LightDir; int loc_LightPosition; int loc_OffsetMapping_ScaleSteps; int loc_OffsetMapping_LodDistance; int loc_OffsetMapping_Bias; int loc_PixelSize; int loc_ReflectColor; int loc_ReflectFactor; int loc_ReflectOffset; int loc_RefractColor; int loc_Saturation; int loc_ScreenCenterRefractReflect; int loc_ScreenScaleRefractReflect; int loc_ScreenToDepth; int loc_ShadowMap_Parameters; int loc_ShadowMap_TextureScale; int loc_SpecularPower; int loc_Skeletal_Transform12; int loc_UserVec1; int loc_UserVec2; int loc_UserVec3; int loc_UserVec4; int loc_ViewTintColor; int loc_ViewToLight; int loc_ModelToLight; int loc_TexMatrix; int loc_BackgroundTexMatrix; int loc_ModelViewProjectionMatrix; int loc_ModelViewMatrix; int loc_PixelToScreenTexCoord; int loc_ModelToReflectCube; int loc_ShadowMapMatrix; int loc_BloomColorSubtract; int loc_NormalmapScrollBlend; int loc_BounceGridMatrix; int loc_BounceGridIntensity; /// uniform block bindings int ubibind_Skeletal_Transform12_UniformBlock; /// uniform block indices int ubiloc_Skeletal_Transform12_UniformBlock; } r_glsl_permutation_t; #define SHADERPERMUTATION_HASHSIZE 256 // non-degradable "lightweight" shader parameters to keep the permutations simpler // these can NOT degrade! only use for simple stuff enum { SHADERSTATICPARM_SATURATION_REDCOMPENSATE = 0, ///< red compensation filter for saturation SHADERSTATICPARM_EXACTSPECULARMATH = 1, ///< (lightsource or deluxemapping) use exact reflection map for specular effects, as opposed to the usual OpenGL approximation SHADERSTATICPARM_POSTPROCESS_USERVEC1 = 2, ///< postprocess uservec1 is enabled SHADERSTATICPARM_POSTPROCESS_USERVEC2 = 3, ///< postprocess uservec2 is enabled SHADERSTATICPARM_POSTPROCESS_USERVEC3 = 4, ///< postprocess uservec3 is enabled SHADERSTATICPARM_POSTPROCESS_USERVEC4 = 5, ///< postprocess uservec4 is enabled SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS = 6, // use both alpha layers while blending materials, allows more advanced microblending SHADERSTATICPARM_OFFSETMAPPING_USELOD = 7, ///< LOD for offsetmapping SHADERSTATICPARM_SHADOWMAPPCF_1 = 8, ///< PCF 1 SHADERSTATICPARM_SHADOWMAPPCF_2 = 9, ///< PCF 2 SHADERSTATICPARM_SHADOWSAMPLER = 10, ///< sampler SHADERSTATICPARM_CELSHADING = 11, ///< celshading (alternative diffuse and specular math) SHADERSTATICPARM_CELOUTLINES = 12, ///< celoutline (depth buffer analysis to produce outlines) SHADERSTATICPARM_FXAA = 13 ///< fast approximate anti aliasing }; #define SHADERSTATICPARMS_COUNT 14 static const char *shaderstaticparmstrings_list[SHADERSTATICPARMS_COUNT]; static int shaderstaticparms_count = 0; static unsigned int r_compileshader_staticparms[(SHADERSTATICPARMS_COUNT + 0x1F) >> 5] = {0}; #define R_COMPILESHADER_STATICPARM_ENABLE(p) r_compileshader_staticparms[(p) >> 5] |= (1 << ((p) & 0x1F)) extern qboolean r_shadow_shadowmapsampler; extern int r_shadow_shadowmappcf; qboolean R_CompileShader_CheckStaticParms(void) { static int r_compileshader_staticparms_save[(SHADERSTATICPARMS_COUNT + 0x1F) >> 5]; memcpy(r_compileshader_staticparms_save, r_compileshader_staticparms, sizeof(r_compileshader_staticparms)); memset(r_compileshader_staticparms, 0, sizeof(r_compileshader_staticparms)); // detect all if (r_glsl_saturation_redcompensate.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SATURATION_REDCOMPENSATE); if (r_glsl_vertextextureblend_usebothalphas.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS); if (r_shadow_glossexact.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_EXACTSPECULARMATH); if (r_glsl_postprocess.integer) { if (r_glsl_postprocess_uservec1_enable.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC1); if (r_glsl_postprocess_uservec2_enable.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC2); if (r_glsl_postprocess_uservec3_enable.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC3); if (r_glsl_postprocess_uservec4_enable.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_POSTPROCESS_USERVEC4); } if (r_fxaa.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_FXAA); if (r_glsl_offsetmapping_lod.integer && r_glsl_offsetmapping_lod_distance.integer > 0) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_OFFSETMAPPING_USELOD); if (r_shadow_shadowmapsampler) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWSAMPLER); if (r_shadow_shadowmappcf > 1) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWMAPPCF_2); else if (r_shadow_shadowmappcf) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_SHADOWMAPPCF_1); if (r_celshading.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_CELSHADING); if (r_celoutlines.integer) R_COMPILESHADER_STATICPARM_ENABLE(SHADERSTATICPARM_CELOUTLINES); return memcmp(r_compileshader_staticparms, r_compileshader_staticparms_save, sizeof(r_compileshader_staticparms)) != 0; } #define R_COMPILESHADER_STATICPARM_EMIT(p, n) \ if(r_compileshader_staticparms[(p) >> 5] & (1 << ((p) & 0x1F))) \ shaderstaticparmstrings_list[shaderstaticparms_count++] = "#define " n "\n"; \ else \ shaderstaticparmstrings_list[shaderstaticparms_count++] = "\n" static void R_CompileShader_AddStaticParms(unsigned int mode, unsigned int permutation) { shaderstaticparms_count = 0; // emit all R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SATURATION_REDCOMPENSATE, "SATURATION_REDCOMPENSATE"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_EXACTSPECULARMATH, "USEEXACTSPECULARMATH"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC1, "USERVEC1"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC2, "USERVEC2"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC3, "USERVEC3"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_POSTPROCESS_USERVEC4, "USERVEC4"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_VERTEXTEXTUREBLEND_USEBOTHALPHAS, "USEBOTHALPHAS"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_OFFSETMAPPING_USELOD, "USEOFFSETMAPPING_LOD"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWMAPPCF_1, "USESHADOWMAPPCF 1"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWMAPPCF_2, "USESHADOWMAPPCF 2"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_SHADOWSAMPLER, "USESHADOWSAMPLER"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_CELSHADING, "USECELSHADING"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_CELOUTLINES, "USECELOUTLINES"); R_COMPILESHADER_STATICPARM_EMIT(SHADERSTATICPARM_FXAA, "USEFXAA"); } /// information about each possible shader permutation r_glsl_permutation_t *r_glsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; /// currently selected permutation r_glsl_permutation_t *r_glsl_permutation; /// storage for permutations linked in the hash table memexpandablearray_t r_glsl_permutationarray; static r_glsl_permutation_t *R_GLSL_FindPermutation(unsigned int mode, unsigned int permutation) { //unsigned int hashdepth = 0; unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); r_glsl_permutation_t *p; for (p = r_glsl_permutationhash[mode][hashindex];p;p = p->hashnext) { if (p->mode == mode && p->permutation == permutation) { //if (hashdepth > 10) // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); return p; } //hashdepth++; } p = (r_glsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_glsl_permutationarray); p->mode = mode; p->permutation = permutation; p->hashnext = r_glsl_permutationhash[mode][hashindex]; r_glsl_permutationhash[mode][hashindex] = p; //if (hashdepth > 10) // Con_Printf("R_GLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); return p; } static char *R_ShaderStrCat(const char **strings) { char *string, *s; const char **p = strings; const char *t; size_t len = 0; for (p = strings;(t = *p);p++) len += strlen(t); len++; s = string = (char *)Mem_Alloc(r_main_mempool, len); len = 0; for (p = strings;(t = *p);p++) { len = strlen(t); memcpy(s, t, len); s += len; } *s = 0; return string; } static char *R_GetShaderText(const char *filename, qboolean printfromdisknotice, qboolean builtinonly) { char *shaderstring; if (!filename || !filename[0]) return NULL; // LordHavoc: note that FS_LoadFile appends a 0 byte to make it a valid string, so does R_ShaderStrCat if (!strcmp(filename, "glsl/default.glsl")) { if (builtinonly) return R_ShaderStrCat(builtinshaderstrings); if (!glslshaderstring) { glslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); if (glslshaderstring) Con_DPrintf("Loading shaders from file %s...\n", filename); else glslshaderstring = R_ShaderStrCat(builtinshaderstrings); } shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(glslshaderstring) + 1); memcpy(shaderstring, glslshaderstring, strlen(glslshaderstring) + 1); return shaderstring; } if (!strcmp(filename, "hlsl/default.hlsl")) { if (builtinonly) return R_ShaderStrCat(builtinhlslshaderstrings); if (!hlslshaderstring) { hlslshaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); if (hlslshaderstring) Con_DPrintf("Loading shaders from file %s...\n", filename); else hlslshaderstring = R_ShaderStrCat(builtinhlslshaderstrings); } shaderstring = (char *) Mem_Alloc(r_main_mempool, strlen(hlslshaderstring) + 1); memcpy(shaderstring, hlslshaderstring, strlen(hlslshaderstring) + 1); return shaderstring; } // we don't have builtin strings for any other files if (builtinonly) return NULL; shaderstring = (char *)FS_LoadFile(filename, r_main_mempool, false, NULL); if (shaderstring) { if (printfromdisknotice) Con_DPrintf("from disk %s... ", filename); return shaderstring; } return shaderstring; } static void R_GLSL_CompilePermutation(r_glsl_permutation_t *p, unsigned int mode, unsigned int permutation) { int i; int ubibind; int sampler; shadermodeinfo_t *modeinfo = glslshadermodeinfo + mode; char *sourcestring; char permutationname[256]; int vertstrings_count = 0; int geomstrings_count = 0; int fragstrings_count = 0; const char *vertstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; const char *geomstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; const char *fragstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; if (p->compiled) return; p->compiled = true; p->program = 0; permutationname[0] = 0; sourcestring = R_GetShaderText(modeinfo->filename, true, false); strlcat(permutationname, modeinfo->filename, sizeof(permutationname)); // we need 140 for r_glsl_skeletal (GL_ARB_uniform_buffer_object) if(vid.support.glshaderversion >= 140) { vertstrings_list[vertstrings_count++] = "#version 140\n"; geomstrings_list[geomstrings_count++] = "#version 140\n"; fragstrings_list[fragstrings_count++] = "#version 140\n"; vertstrings_list[vertstrings_count++] = "#define GLSL140\n"; geomstrings_list[geomstrings_count++] = "#define GLSL140\n"; fragstrings_list[fragstrings_count++] = "#define GLSL140\n"; } // if we can do #version 130, we should (this improves quality of offset/reliefmapping thanks to textureGrad) else if(vid.support.glshaderversion >= 130) { vertstrings_list[vertstrings_count++] = "#version 130\n"; geomstrings_list[geomstrings_count++] = "#version 130\n"; fragstrings_list[fragstrings_count++] = "#version 130\n"; vertstrings_list[vertstrings_count++] = "#define GLSL130\n"; geomstrings_list[geomstrings_count++] = "#define GLSL130\n"; fragstrings_list[fragstrings_count++] = "#define GLSL130\n"; } // the first pretext is which type of shader to compile as // (later these will all be bound together as a program object) vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; // the second pretext is the mode (for example a light source) vertstrings_list[vertstrings_count++] = modeinfo->pretext; geomstrings_list[geomstrings_count++] = modeinfo->pretext; fragstrings_list[fragstrings_count++] = modeinfo->pretext; strlcat(permutationname, modeinfo->name, sizeof(permutationname)); // now add all the permutation pretexts for (i = 0;i < SHADERPERMUTATION_COUNT;i++) { if (permutation & (1<program = GL_Backend_CompileProgram(vertstrings_count, vertstrings_list, geomstrings_count, geomstrings_list, fragstrings_count, fragstrings_list); if (p->program) { CHECKGLERROR qglUseProgram(p->program);CHECKGLERROR // look up all the uniform variable names we care about, so we don't // have to look them up every time we set them #if 0 // debugging aid { GLint activeuniformindex = 0; GLint numactiveuniforms = 0; char uniformname[128]; GLsizei uniformnamelength = 0; GLint uniformsize = 0; GLenum uniformtype = 0; memset(uniformname, 0, sizeof(uniformname)); qglGetProgramiv(p->program, GL_ACTIVE_UNIFORMS, &numactiveuniforms); Con_Printf("Shader has %i uniforms\n", numactiveuniforms); for (activeuniformindex = 0;activeuniformindex < numactiveuniforms;activeuniformindex++) { qglGetActiveUniform(p->program, activeuniformindex, sizeof(uniformname) - 1, &uniformnamelength, &uniformsize, &uniformtype, uniformname); Con_Printf("Uniform %i name \"%s\" size %i type %i\n", (int)activeuniformindex, uniformname, (int)uniformsize, (int)uniformtype); } } #endif p->loc_Texture_First = qglGetUniformLocation(p->program, "Texture_First"); p->loc_Texture_Second = qglGetUniformLocation(p->program, "Texture_Second"); p->loc_Texture_GammaRamps = qglGetUniformLocation(p->program, "Texture_GammaRamps"); p->loc_Texture_Normal = qglGetUniformLocation(p->program, "Texture_Normal"); p->loc_Texture_Color = qglGetUniformLocation(p->program, "Texture_Color"); p->loc_Texture_Gloss = qglGetUniformLocation(p->program, "Texture_Gloss"); p->loc_Texture_Glow = qglGetUniformLocation(p->program, "Texture_Glow"); p->loc_Texture_SecondaryNormal = qglGetUniformLocation(p->program, "Texture_SecondaryNormal"); p->loc_Texture_SecondaryColor = qglGetUniformLocation(p->program, "Texture_SecondaryColor"); p->loc_Texture_SecondaryGloss = qglGetUniformLocation(p->program, "Texture_SecondaryGloss"); p->loc_Texture_SecondaryGlow = qglGetUniformLocation(p->program, "Texture_SecondaryGlow"); p->loc_Texture_Pants = qglGetUniformLocation(p->program, "Texture_Pants"); p->loc_Texture_Shirt = qglGetUniformLocation(p->program, "Texture_Shirt"); p->loc_Texture_FogHeightTexture = qglGetUniformLocation(p->program, "Texture_FogHeightTexture"); p->loc_Texture_FogMask = qglGetUniformLocation(p->program, "Texture_FogMask"); p->loc_Texture_Lightmap = qglGetUniformLocation(p->program, "Texture_Lightmap"); p->loc_Texture_Deluxemap = qglGetUniformLocation(p->program, "Texture_Deluxemap"); p->loc_Texture_Attenuation = qglGetUniformLocation(p->program, "Texture_Attenuation"); p->loc_Texture_Cube = qglGetUniformLocation(p->program, "Texture_Cube"); p->loc_Texture_Refraction = qglGetUniformLocation(p->program, "Texture_Refraction"); p->loc_Texture_Reflection = qglGetUniformLocation(p->program, "Texture_Reflection"); p->loc_Texture_ShadowMap2D = qglGetUniformLocation(p->program, "Texture_ShadowMap2D"); p->loc_Texture_CubeProjection = qglGetUniformLocation(p->program, "Texture_CubeProjection"); p->loc_Texture_ScreenNormalMap = qglGetUniformLocation(p->program, "Texture_ScreenNormalMap"); p->loc_Texture_ScreenDiffuse = qglGetUniformLocation(p->program, "Texture_ScreenDiffuse"); p->loc_Texture_ScreenSpecular = qglGetUniformLocation(p->program, "Texture_ScreenSpecular"); p->loc_Texture_ReflectMask = qglGetUniformLocation(p->program, "Texture_ReflectMask"); p->loc_Texture_ReflectCube = qglGetUniformLocation(p->program, "Texture_ReflectCube"); p->loc_Texture_BounceGrid = qglGetUniformLocation(p->program, "Texture_BounceGrid"); p->loc_Alpha = qglGetUniformLocation(p->program, "Alpha"); p->loc_BloomBlur_Parameters = qglGetUniformLocation(p->program, "BloomBlur_Parameters"); p->loc_ClientTime = qglGetUniformLocation(p->program, "ClientTime"); p->loc_Color_Ambient = qglGetUniformLocation(p->program, "Color_Ambient"); p->loc_Color_Diffuse = qglGetUniformLocation(p->program, "Color_Diffuse"); p->loc_Color_Specular = qglGetUniformLocation(p->program, "Color_Specular"); p->loc_Color_Glow = qglGetUniformLocation(p->program, "Color_Glow"); p->loc_Color_Pants = qglGetUniformLocation(p->program, "Color_Pants"); p->loc_Color_Shirt = qglGetUniformLocation(p->program, "Color_Shirt"); p->loc_DeferredColor_Ambient = qglGetUniformLocation(p->program, "DeferredColor_Ambient"); p->loc_DeferredColor_Diffuse = qglGetUniformLocation(p->program, "DeferredColor_Diffuse"); p->loc_DeferredColor_Specular = qglGetUniformLocation(p->program, "DeferredColor_Specular"); p->loc_DeferredMod_Diffuse = qglGetUniformLocation(p->program, "DeferredMod_Diffuse"); p->loc_DeferredMod_Specular = qglGetUniformLocation(p->program, "DeferredMod_Specular"); p->loc_DistortScaleRefractReflect = qglGetUniformLocation(p->program, "DistortScaleRefractReflect"); p->loc_EyePosition = qglGetUniformLocation(p->program, "EyePosition"); p->loc_FogColor = qglGetUniformLocation(p->program, "FogColor"); p->loc_FogHeightFade = qglGetUniformLocation(p->program, "FogHeightFade"); p->loc_FogPlane = qglGetUniformLocation(p->program, "FogPlane"); p->loc_FogPlaneViewDist = qglGetUniformLocation(p->program, "FogPlaneViewDist"); p->loc_FogRangeRecip = qglGetUniformLocation(p->program, "FogRangeRecip"); p->loc_LightColor = qglGetUniformLocation(p->program, "LightColor"); p->loc_LightDir = qglGetUniformLocation(p->program, "LightDir"); p->loc_LightPosition = qglGetUniformLocation(p->program, "LightPosition"); p->loc_OffsetMapping_ScaleSteps = qglGetUniformLocation(p->program, "OffsetMapping_ScaleSteps"); p->loc_OffsetMapping_LodDistance = qglGetUniformLocation(p->program, "OffsetMapping_LodDistance"); p->loc_OffsetMapping_Bias = qglGetUniformLocation(p->program, "OffsetMapping_Bias"); p->loc_PixelSize = qglGetUniformLocation(p->program, "PixelSize"); p->loc_ReflectColor = qglGetUniformLocation(p->program, "ReflectColor"); p->loc_ReflectFactor = qglGetUniformLocation(p->program, "ReflectFactor"); p->loc_ReflectOffset = qglGetUniformLocation(p->program, "ReflectOffset"); p->loc_RefractColor = qglGetUniformLocation(p->program, "RefractColor"); p->loc_Saturation = qglGetUniformLocation(p->program, "Saturation"); p->loc_ScreenCenterRefractReflect = qglGetUniformLocation(p->program, "ScreenCenterRefractReflect"); p->loc_ScreenScaleRefractReflect = qglGetUniformLocation(p->program, "ScreenScaleRefractReflect"); p->loc_ScreenToDepth = qglGetUniformLocation(p->program, "ScreenToDepth"); p->loc_ShadowMap_Parameters = qglGetUniformLocation(p->program, "ShadowMap_Parameters"); p->loc_ShadowMap_TextureScale = qglGetUniformLocation(p->program, "ShadowMap_TextureScale"); p->loc_SpecularPower = qglGetUniformLocation(p->program, "SpecularPower"); p->loc_UserVec1 = qglGetUniformLocation(p->program, "UserVec1"); p->loc_UserVec2 = qglGetUniformLocation(p->program, "UserVec2"); p->loc_UserVec3 = qglGetUniformLocation(p->program, "UserVec3"); p->loc_UserVec4 = qglGetUniformLocation(p->program, "UserVec4"); p->loc_ViewTintColor = qglGetUniformLocation(p->program, "ViewTintColor"); p->loc_ViewToLight = qglGetUniformLocation(p->program, "ViewToLight"); p->loc_ModelToLight = qglGetUniformLocation(p->program, "ModelToLight"); p->loc_TexMatrix = qglGetUniformLocation(p->program, "TexMatrix"); p->loc_BackgroundTexMatrix = qglGetUniformLocation(p->program, "BackgroundTexMatrix"); p->loc_ModelViewMatrix = qglGetUniformLocation(p->program, "ModelViewMatrix"); p->loc_ModelViewProjectionMatrix = qglGetUniformLocation(p->program, "ModelViewProjectionMatrix"); p->loc_PixelToScreenTexCoord = qglGetUniformLocation(p->program, "PixelToScreenTexCoord"); p->loc_ModelToReflectCube = qglGetUniformLocation(p->program, "ModelToReflectCube"); p->loc_ShadowMapMatrix = qglGetUniformLocation(p->program, "ShadowMapMatrix"); p->loc_BloomColorSubtract = qglGetUniformLocation(p->program, "BloomColorSubtract"); p->loc_NormalmapScrollBlend = qglGetUniformLocation(p->program, "NormalmapScrollBlend"); p->loc_BounceGridMatrix = qglGetUniformLocation(p->program, "BounceGridMatrix"); p->loc_BounceGridIntensity = qglGetUniformLocation(p->program, "BounceGridIntensity"); // initialize the samplers to refer to the texture units we use p->tex_Texture_First = -1; p->tex_Texture_Second = -1; p->tex_Texture_GammaRamps = -1; p->tex_Texture_Normal = -1; p->tex_Texture_Color = -1; p->tex_Texture_Gloss = -1; p->tex_Texture_Glow = -1; p->tex_Texture_SecondaryNormal = -1; p->tex_Texture_SecondaryColor = -1; p->tex_Texture_SecondaryGloss = -1; p->tex_Texture_SecondaryGlow = -1; p->tex_Texture_Pants = -1; p->tex_Texture_Shirt = -1; p->tex_Texture_FogHeightTexture = -1; p->tex_Texture_FogMask = -1; p->tex_Texture_Lightmap = -1; p->tex_Texture_Deluxemap = -1; p->tex_Texture_Attenuation = -1; p->tex_Texture_Cube = -1; p->tex_Texture_Refraction = -1; p->tex_Texture_Reflection = -1; p->tex_Texture_ShadowMap2D = -1; p->tex_Texture_CubeProjection = -1; p->tex_Texture_ScreenNormalMap = -1; p->tex_Texture_ScreenDiffuse = -1; p->tex_Texture_ScreenSpecular = -1; p->tex_Texture_ReflectMask = -1; p->tex_Texture_ReflectCube = -1; p->tex_Texture_BounceGrid = -1; // bind the texture samplers in use sampler = 0; if (p->loc_Texture_First >= 0) {p->tex_Texture_First = sampler;qglUniform1i(p->loc_Texture_First , sampler);sampler++;} if (p->loc_Texture_Second >= 0) {p->tex_Texture_Second = sampler;qglUniform1i(p->loc_Texture_Second , sampler);sampler++;} if (p->loc_Texture_GammaRamps >= 0) {p->tex_Texture_GammaRamps = sampler;qglUniform1i(p->loc_Texture_GammaRamps , sampler);sampler++;} if (p->loc_Texture_Normal >= 0) {p->tex_Texture_Normal = sampler;qglUniform1i(p->loc_Texture_Normal , sampler);sampler++;} if (p->loc_Texture_Color >= 0) {p->tex_Texture_Color = sampler;qglUniform1i(p->loc_Texture_Color , sampler);sampler++;} if (p->loc_Texture_Gloss >= 0) {p->tex_Texture_Gloss = sampler;qglUniform1i(p->loc_Texture_Gloss , sampler);sampler++;} if (p->loc_Texture_Glow >= 0) {p->tex_Texture_Glow = sampler;qglUniform1i(p->loc_Texture_Glow , sampler);sampler++;} if (p->loc_Texture_SecondaryNormal >= 0) {p->tex_Texture_SecondaryNormal = sampler;qglUniform1i(p->loc_Texture_SecondaryNormal , sampler);sampler++;} if (p->loc_Texture_SecondaryColor >= 0) {p->tex_Texture_SecondaryColor = sampler;qglUniform1i(p->loc_Texture_SecondaryColor , sampler);sampler++;} if (p->loc_Texture_SecondaryGloss >= 0) {p->tex_Texture_SecondaryGloss = sampler;qglUniform1i(p->loc_Texture_SecondaryGloss , sampler);sampler++;} if (p->loc_Texture_SecondaryGlow >= 0) {p->tex_Texture_SecondaryGlow = sampler;qglUniform1i(p->loc_Texture_SecondaryGlow , sampler);sampler++;} if (p->loc_Texture_Pants >= 0) {p->tex_Texture_Pants = sampler;qglUniform1i(p->loc_Texture_Pants , sampler);sampler++;} if (p->loc_Texture_Shirt >= 0) {p->tex_Texture_Shirt = sampler;qglUniform1i(p->loc_Texture_Shirt , sampler);sampler++;} if (p->loc_Texture_FogHeightTexture>= 0) {p->tex_Texture_FogHeightTexture = sampler;qglUniform1i(p->loc_Texture_FogHeightTexture, sampler);sampler++;} if (p->loc_Texture_FogMask >= 0) {p->tex_Texture_FogMask = sampler;qglUniform1i(p->loc_Texture_FogMask , sampler);sampler++;} if (p->loc_Texture_Lightmap >= 0) {p->tex_Texture_Lightmap = sampler;qglUniform1i(p->loc_Texture_Lightmap , sampler);sampler++;} if (p->loc_Texture_Deluxemap >= 0) {p->tex_Texture_Deluxemap = sampler;qglUniform1i(p->loc_Texture_Deluxemap , sampler);sampler++;} if (p->loc_Texture_Attenuation >= 0) {p->tex_Texture_Attenuation = sampler;qglUniform1i(p->loc_Texture_Attenuation , sampler);sampler++;} if (p->loc_Texture_Cube >= 0) {p->tex_Texture_Cube = sampler;qglUniform1i(p->loc_Texture_Cube , sampler);sampler++;} if (p->loc_Texture_Refraction >= 0) {p->tex_Texture_Refraction = sampler;qglUniform1i(p->loc_Texture_Refraction , sampler);sampler++;} if (p->loc_Texture_Reflection >= 0) {p->tex_Texture_Reflection = sampler;qglUniform1i(p->loc_Texture_Reflection , sampler);sampler++;} if (p->loc_Texture_ShadowMap2D >= 0) {p->tex_Texture_ShadowMap2D = sampler;qglUniform1i(p->loc_Texture_ShadowMap2D , sampler);sampler++;} if (p->loc_Texture_CubeProjection >= 0) {p->tex_Texture_CubeProjection = sampler;qglUniform1i(p->loc_Texture_CubeProjection , sampler);sampler++;} if (p->loc_Texture_ScreenNormalMap >= 0) {p->tex_Texture_ScreenNormalMap = sampler;qglUniform1i(p->loc_Texture_ScreenNormalMap , sampler);sampler++;} if (p->loc_Texture_ScreenDiffuse >= 0) {p->tex_Texture_ScreenDiffuse = sampler;qglUniform1i(p->loc_Texture_ScreenDiffuse , sampler);sampler++;} if (p->loc_Texture_ScreenSpecular >= 0) {p->tex_Texture_ScreenSpecular = sampler;qglUniform1i(p->loc_Texture_ScreenSpecular , sampler);sampler++;} if (p->loc_Texture_ReflectMask >= 0) {p->tex_Texture_ReflectMask = sampler;qglUniform1i(p->loc_Texture_ReflectMask , sampler);sampler++;} if (p->loc_Texture_ReflectCube >= 0) {p->tex_Texture_ReflectCube = sampler;qglUniform1i(p->loc_Texture_ReflectCube , sampler);sampler++;} if (p->loc_Texture_BounceGrid >= 0) {p->tex_Texture_BounceGrid = sampler;qglUniform1i(p->loc_Texture_BounceGrid , sampler);sampler++;} // get the uniform block indices so we can bind them #ifndef USE_GLES2 /* FIXME: GLES3 only */ if (vid.support.arb_uniform_buffer_object) p->ubiloc_Skeletal_Transform12_UniformBlock = qglGetUniformBlockIndex(p->program, "Skeletal_Transform12_UniformBlock"); else #endif p->ubiloc_Skeletal_Transform12_UniformBlock = -1; // clear the uniform block bindings p->ubibind_Skeletal_Transform12_UniformBlock = -1; // bind the uniform blocks in use ubibind = 0; #ifndef USE_GLES2 /* FIXME: GLES3 only */ if (p->ubiloc_Skeletal_Transform12_UniformBlock >= 0) {p->ubibind_Skeletal_Transform12_UniformBlock = ubibind;qglUniformBlockBinding(p->program, p->ubiloc_Skeletal_Transform12_UniformBlock, ubibind);ubibind++;} #endif // we're done compiling and setting up the shader, at least until it is used CHECKGLERROR Con_DPrintf("^5GLSL shader %s compiled (%i textures).\n", permutationname, sampler); } else Con_Printf("^1GLSL shader %s failed! some features may not work properly.\n", permutationname); // free the strings if (sourcestring) Mem_Free(sourcestring); } static void R_SetupShader_SetPermutationGLSL(unsigned int mode, unsigned int permutation) { r_glsl_permutation_t *perm = R_GLSL_FindPermutation(mode, permutation); if (r_glsl_permutation != perm) { r_glsl_permutation = perm; if (!r_glsl_permutation->program) { if (!r_glsl_permutation->compiled) { Con_DPrintf("Compiling shader mode %u permutation %u\n", mode, permutation); R_GLSL_CompilePermutation(perm, mode, permutation); } if (!r_glsl_permutation->program) { // remove features until we find a valid permutation int i; for (i = 0;i < SHADERPERMUTATION_COUNT;i++) { // reduce i more quickly whenever it would not remove any bits int j = 1<<(SHADERPERMUTATION_COUNT-1-i); if (!(permutation & j)) continue; permutation -= j; r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); if (!r_glsl_permutation->compiled) R_GLSL_CompilePermutation(perm, mode, permutation); if (r_glsl_permutation->program) break; } if (i >= SHADERPERMUTATION_COUNT) { //Con_Printf("Could not find a working OpenGL 2.0 shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext); r_glsl_permutation = R_GLSL_FindPermutation(mode, permutation); qglUseProgram(0);CHECKGLERROR return; // no bit left to clear, entire mode is broken } } } CHECKGLERROR qglUseProgram(r_glsl_permutation->program);CHECKGLERROR } if (r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); if (r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); if (r_glsl_permutation->loc_ClientTime >= 0) qglUniform1f(r_glsl_permutation->loc_ClientTime, cl.time); CHECKGLERROR } #ifdef SUPPORTD3D #ifdef SUPPORTD3D #include extern LPDIRECT3DDEVICE9 vid_d3d9dev; extern D3DCAPS9 vid_d3d9caps; #endif struct r_hlsl_permutation_s; typedef struct r_hlsl_permutation_s { /// hash lookup data struct r_hlsl_permutation_s *hashnext; unsigned int mode; unsigned int permutation; /// indicates if we have tried compiling this permutation already qboolean compiled; /// NULL if compilation failed IDirect3DVertexShader9 *vertexshader; IDirect3DPixelShader9 *pixelshader; } r_hlsl_permutation_t; typedef enum D3DVSREGISTER_e { D3DVSREGISTER_TexMatrix = 0, // float4x4 D3DVSREGISTER_BackgroundTexMatrix = 4, // float4x4 D3DVSREGISTER_ModelViewProjectionMatrix = 8, // float4x4 D3DVSREGISTER_ModelViewMatrix = 12, // float4x4 D3DVSREGISTER_ShadowMapMatrix = 16, // float4x4 D3DVSREGISTER_ModelToLight = 20, // float4x4 D3DVSREGISTER_EyePosition = 24, D3DVSREGISTER_FogPlane = 25, D3DVSREGISTER_LightDir = 26, D3DVSREGISTER_LightPosition = 27, } D3DVSREGISTER_t; typedef enum D3DPSREGISTER_e { D3DPSREGISTER_Alpha = 0, D3DPSREGISTER_BloomBlur_Parameters = 1, D3DPSREGISTER_ClientTime = 2, D3DPSREGISTER_Color_Ambient = 3, D3DPSREGISTER_Color_Diffuse = 4, D3DPSREGISTER_Color_Specular = 5, D3DPSREGISTER_Color_Glow = 6, D3DPSREGISTER_Color_Pants = 7, D3DPSREGISTER_Color_Shirt = 8, D3DPSREGISTER_DeferredColor_Ambient = 9, D3DPSREGISTER_DeferredColor_Diffuse = 10, D3DPSREGISTER_DeferredColor_Specular = 11, D3DPSREGISTER_DeferredMod_Diffuse = 12, D3DPSREGISTER_DeferredMod_Specular = 13, D3DPSREGISTER_DistortScaleRefractReflect = 14, D3DPSREGISTER_EyePosition = 15, // unused D3DPSREGISTER_FogColor = 16, D3DPSREGISTER_FogHeightFade = 17, D3DPSREGISTER_FogPlane = 18, D3DPSREGISTER_FogPlaneViewDist = 19, D3DPSREGISTER_FogRangeRecip = 20, D3DPSREGISTER_LightColor = 21, D3DPSREGISTER_LightDir = 22, // unused D3DPSREGISTER_LightPosition = 23, D3DPSREGISTER_OffsetMapping_ScaleSteps = 24, D3DPSREGISTER_PixelSize = 25, D3DPSREGISTER_ReflectColor = 26, D3DPSREGISTER_ReflectFactor = 27, D3DPSREGISTER_ReflectOffset = 28, D3DPSREGISTER_RefractColor = 29, D3DPSREGISTER_Saturation = 30, D3DPSREGISTER_ScreenCenterRefractReflect = 31, D3DPSREGISTER_ScreenScaleRefractReflect = 32, D3DPSREGISTER_ScreenToDepth = 33, D3DPSREGISTER_ShadowMap_Parameters = 34, D3DPSREGISTER_ShadowMap_TextureScale = 35, D3DPSREGISTER_SpecularPower = 36, D3DPSREGISTER_UserVec1 = 37, D3DPSREGISTER_UserVec2 = 38, D3DPSREGISTER_UserVec3 = 39, D3DPSREGISTER_UserVec4 = 40, D3DPSREGISTER_ViewTintColor = 41, D3DPSREGISTER_PixelToScreenTexCoord = 42, D3DPSREGISTER_BloomColorSubtract = 43, D3DPSREGISTER_ViewToLight = 44, // float4x4 D3DPSREGISTER_ModelToReflectCube = 48, // float4x4 D3DPSREGISTER_NormalmapScrollBlend = 52, D3DPSREGISTER_OffsetMapping_LodDistance = 53, D3DPSREGISTER_OffsetMapping_Bias = 54, // next at 54 } D3DPSREGISTER_t; /// information about each possible shader permutation r_hlsl_permutation_t *r_hlsl_permutationhash[SHADERMODE_COUNT][SHADERPERMUTATION_HASHSIZE]; /// currently selected permutation r_hlsl_permutation_t *r_hlsl_permutation; /// storage for permutations linked in the hash table memexpandablearray_t r_hlsl_permutationarray; static r_hlsl_permutation_t *R_HLSL_FindPermutation(unsigned int mode, unsigned int permutation) { //unsigned int hashdepth = 0; unsigned int hashindex = (permutation * 0x1021) & (SHADERPERMUTATION_HASHSIZE - 1); r_hlsl_permutation_t *p; for (p = r_hlsl_permutationhash[mode][hashindex];p;p = p->hashnext) { if (p->mode == mode && p->permutation == permutation) { //if (hashdepth > 10) // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); return p; } //hashdepth++; } p = (r_hlsl_permutation_t*)Mem_ExpandableArray_AllocRecord(&r_hlsl_permutationarray); p->mode = mode; p->permutation = permutation; p->hashnext = r_hlsl_permutationhash[mode][hashindex]; r_hlsl_permutationhash[mode][hashindex] = p; //if (hashdepth > 10) // Con_Printf("R_HLSL_FindPermutation: Warning: %i:%i has hashdepth %i\n", mode, permutation, hashdepth); return p; } #include //#include //#include static void R_HLSL_CacheShader(r_hlsl_permutation_t *p, const char *cachename, const char *vertstring, const char *fragstring) { DWORD *vsbin = NULL; DWORD *psbin = NULL; fs_offset_t vsbinsize; fs_offset_t psbinsize; // IDirect3DVertexShader9 *vs = NULL; // IDirect3DPixelShader9 *ps = NULL; ID3DXBuffer *vslog = NULL; ID3DXBuffer *vsbuffer = NULL; ID3DXConstantTable *vsconstanttable = NULL; ID3DXBuffer *pslog = NULL; ID3DXBuffer *psbuffer = NULL; ID3DXConstantTable *psconstanttable = NULL; int vsresult = 0; int psresult = 0; char temp[MAX_INPUTLINE]; const char *vsversion = "vs_3_0", *psversion = "ps_3_0"; char vabuf[1024]; qboolean debugshader = gl_paranoid.integer != 0; if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} if (p->permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) {vsversion = "vs_3_0";psversion = "ps_3_0";} if (!debugshader) { vsbin = (DWORD *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.vsbin", cachename), r_main_mempool, true, &vsbinsize); psbin = (DWORD *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.psbin", cachename), r_main_mempool, true, &psbinsize); } if ((!vsbin && vertstring) || (!psbin && fragstring)) { const char* dllnames_d3dx9 [] = { "d3dx9_43.dll", "d3dx9_42.dll", "d3dx9_41.dll", "d3dx9_40.dll", "d3dx9_39.dll", "d3dx9_38.dll", "d3dx9_37.dll", "d3dx9_36.dll", "d3dx9_35.dll", "d3dx9_34.dll", "d3dx9_33.dll", "d3dx9_32.dll", "d3dx9_31.dll", "d3dx9_30.dll", "d3dx9_29.dll", "d3dx9_28.dll", "d3dx9_27.dll", "d3dx9_26.dll", "d3dx9_25.dll", "d3dx9_24.dll", NULL }; dllhandle_t d3dx9_dll = NULL; HRESULT (WINAPI *qD3DXCompileShaderFromFileA)(LPCSTR pSrcFile, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); HRESULT (WINAPI *qD3DXPreprocessShader)(LPCSTR pSrcData, UINT SrcDataSize, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPD3DXBUFFER* ppShaderText, LPD3DXBUFFER* ppErrorMsgs); HRESULT (WINAPI *qD3DXCompileShader)(LPCSTR pSrcData, UINT SrcDataLen, CONST D3DXMACRO* pDefines, LPD3DXINCLUDE pInclude, LPCSTR pFunctionName, LPCSTR pProfile, DWORD Flags, LPD3DXBUFFER* ppShader, LPD3DXBUFFER* ppErrorMsgs, LPD3DXCONSTANTTABLE* ppConstantTable); dllfunction_t d3dx9_dllfuncs[] = { {"D3DXCompileShaderFromFileA", (void **) &qD3DXCompileShaderFromFileA}, {"D3DXPreprocessShader", (void **) &qD3DXPreprocessShader}, {"D3DXCompileShader", (void **) &qD3DXCompileShader}, {NULL, NULL} }; // LordHavoc: the June 2010 SDK lacks these macros to make ID3DXBuffer usable in C, and to make it work in both C and C++ the macros are needed... #ifndef ID3DXBuffer_GetBufferPointer #if !defined(__cplusplus) || defined(CINTERFACE) #define ID3DXBuffer_GetBufferPointer(p) (p)->lpVtbl->GetBufferPointer(p) #define ID3DXBuffer_GetBufferSize(p) (p)->lpVtbl->GetBufferSize(p) #define ID3DXBuffer_Release(p) (p)->lpVtbl->Release(p) #else #define ID3DXBuffer_GetBufferPointer(p) (p)->GetBufferPointer() #define ID3DXBuffer_GetBufferSize(p) (p)->GetBufferSize() #define ID3DXBuffer_Release(p) (p)->Release() #endif #endif if (Sys_LoadLibrary(dllnames_d3dx9, &d3dx9_dll, d3dx9_dllfuncs)) { DWORD shaderflags = 0; if (debugshader) shaderflags = D3DXSHADER_DEBUG | D3DXSHADER_SKIPOPTIMIZATION; vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); if (vertstring && vertstring[0]) { if (debugshader) { FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_vs.fx", cachename), vertstring, strlen(vertstring)); vsresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_vs.fx", fs_gamedir, cachename), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); } else vsresult = qD3DXCompileShader(vertstring, (unsigned int)strlen(vertstring), NULL, NULL, "main", vsversion, shaderflags, &vsbuffer, &vslog, &vsconstanttable); if (vsbuffer) { vsbinsize = ID3DXBuffer_GetBufferSize(vsbuffer); vsbin = (DWORD *)Mem_Alloc(tempmempool, vsbinsize); memcpy(vsbin, ID3DXBuffer_GetBufferPointer(vsbuffer), vsbinsize); ID3DXBuffer_Release(vsbuffer); } if (vslog) { strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(vslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(vslog))); Con_DPrintf("HLSL vertex shader compile output for %s follows:\n%s\n", cachename, temp); ID3DXBuffer_Release(vslog); } } if (fragstring && fragstring[0]) { if (debugshader) { FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_ps.fx", cachename), fragstring, strlen(fragstring)); psresult = qD3DXCompileShaderFromFileA(va(vabuf, sizeof(vabuf), "%s/%s_ps.fx", fs_gamedir, cachename), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); } else psresult = qD3DXCompileShader(fragstring, (unsigned int)strlen(fragstring), NULL, NULL, "main", psversion, shaderflags, &psbuffer, &pslog, &psconstanttable); if (psbuffer) { psbinsize = ID3DXBuffer_GetBufferSize(psbuffer); psbin = (DWORD *)Mem_Alloc(tempmempool, psbinsize); memcpy(psbin, ID3DXBuffer_GetBufferPointer(psbuffer), psbinsize); ID3DXBuffer_Release(psbuffer); } if (pslog) { strlcpy(temp, (const char *)ID3DXBuffer_GetBufferPointer(pslog), min(sizeof(temp), ID3DXBuffer_GetBufferSize(pslog))); Con_DPrintf("HLSL pixel shader compile output for %s follows:\n%s\n", cachename, temp); ID3DXBuffer_Release(pslog); } } Sys_UnloadLibrary(&d3dx9_dll); } else Con_DPrintf("Unable to compile shader - D3DXCompileShader function not found\n"); } if (vsbin && psbin) { vsresult = IDirect3DDevice9_CreateVertexShader(vid_d3d9dev, vsbin, &p->vertexshader); if (FAILED(vsresult)) Con_DPrintf("HLSL CreateVertexShader failed for %s (hresult = %8x)\n", cachename, vsresult); psresult = IDirect3DDevice9_CreatePixelShader(vid_d3d9dev, psbin, &p->pixelshader); if (FAILED(psresult)) Con_DPrintf("HLSL CreatePixelShader failed for %s (hresult = %8x)\n", cachename, psresult); } // free the shader data vsbin = (DWORD *)Mem_Realloc(tempmempool, vsbin, 0); psbin = (DWORD *)Mem_Realloc(tempmempool, psbin, 0); } static void R_HLSL_CompilePermutation(r_hlsl_permutation_t *p, unsigned int mode, unsigned int permutation) { int i; shadermodeinfo_t *modeinfo = hlslshadermodeinfo + mode; int vertstring_length = 0; int geomstring_length = 0; int fragstring_length = 0; char *t; char *sourcestring; char *vertstring, *geomstring, *fragstring; char permutationname[256]; char cachename[256]; int vertstrings_count = 0; int geomstrings_count = 0; int fragstrings_count = 0; const char *vertstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; const char *geomstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; const char *fragstrings_list[32+5+SHADERSTATICPARMS_COUNT+1]; if (p->compiled) return; p->compiled = true; p->vertexshader = NULL; p->pixelshader = NULL; permutationname[0] = 0; cachename[0] = 0; sourcestring = R_GetShaderText(modeinfo->filename, true, false); strlcat(permutationname, modeinfo->filename, sizeof(permutationname)); strlcat(cachename, "hlsl/", sizeof(cachename)); // define HLSL so that the shader can tell apart the HLSL compiler and the Cg compiler vertstrings_count = 0; geomstrings_count = 0; fragstrings_count = 0; vertstrings_list[vertstrings_count++] = "#define HLSL\n"; geomstrings_list[geomstrings_count++] = "#define HLSL\n"; fragstrings_list[fragstrings_count++] = "#define HLSL\n"; // the first pretext is which type of shader to compile as // (later these will all be bound together as a program object) vertstrings_list[vertstrings_count++] = "#define VERTEX_SHADER\n"; geomstrings_list[geomstrings_count++] = "#define GEOMETRY_SHADER\n"; fragstrings_list[fragstrings_count++] = "#define FRAGMENT_SHADER\n"; // the second pretext is the mode (for example a light source) vertstrings_list[vertstrings_count++] = modeinfo->pretext; geomstrings_list[geomstrings_count++] = modeinfo->pretext; fragstrings_list[fragstrings_count++] = modeinfo->pretext; strlcat(permutationname, modeinfo->name, sizeof(permutationname)); strlcat(cachename, modeinfo->name, sizeof(cachename)); // now add all the permutation pretexts for (i = 0;i < SHADERPERMUTATION_COUNT;i++) { if (permutation & (1<vertexshader || !vertstring[0]) && (p->pixelshader || !fragstring[0])) Con_DPrintf("^5HLSL shader %s compiled.\n", permutationname); else Con_Printf("^1HLSL shader %s failed! some features may not work properly.\n", permutationname); // free the strings if (vertstring) Mem_Free(vertstring); if (geomstring) Mem_Free(geomstring); if (fragstring) Mem_Free(fragstring); if (sourcestring) Mem_Free(sourcestring); } static inline void hlslVSSetParameter16f(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 4);} static inline void hlslVSSetParameter4fv(D3DVSREGISTER_t r, const float *a) {IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, a, 1);} static inline void hlslVSSetParameter4f(D3DVSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslVSSetParameter3f(D3DVSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslVSSetParameter2f(D3DVSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslVSSetParameter1f(D3DVSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetVertexShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslPSSetParameter16f(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 4);} static inline void hlslPSSetParameter4fv(D3DPSREGISTER_t r, const float *a) {IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, a, 1);} static inline void hlslPSSetParameter4f(D3DPSREGISTER_t r, float x, float y, float z, float w) {float temp[4];Vector4Set(temp, x, y, z, w);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslPSSetParameter3f(D3DPSREGISTER_t r, float x, float y, float z) {float temp[4];Vector4Set(temp, x, y, z, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslPSSetParameter2f(D3DPSREGISTER_t r, float x, float y) {float temp[4];Vector4Set(temp, x, y, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} static inline void hlslPSSetParameter1f(D3DPSREGISTER_t r, float x) {float temp[4];Vector4Set(temp, x, 0, 0, 0);IDirect3DDevice9_SetPixelShaderConstantF(vid_d3d9dev, r, temp, 1);} void R_SetupShader_SetPermutationHLSL(unsigned int mode, unsigned int permutation) { r_hlsl_permutation_t *perm = R_HLSL_FindPermutation(mode, permutation); if (r_hlsl_permutation != perm) { r_hlsl_permutation = perm; if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) { if (!r_hlsl_permutation->compiled) R_HLSL_CompilePermutation(perm, mode, permutation); if (!r_hlsl_permutation->vertexshader && !r_hlsl_permutation->pixelshader) { // remove features until we find a valid permutation int i; for (i = 0;i < SHADERPERMUTATION_COUNT;i++) { // reduce i more quickly whenever it would not remove any bits int j = 1<<(SHADERPERMUTATION_COUNT-1-i); if (!(permutation & j)) continue; permutation -= j; r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); if (!r_hlsl_permutation->compiled) R_HLSL_CompilePermutation(perm, mode, permutation); if (r_hlsl_permutation->vertexshader || r_hlsl_permutation->pixelshader) break; } if (i >= SHADERPERMUTATION_COUNT) { //Con_Printf("Could not find a working HLSL shader for permutation %s %s\n", shadermodeinfo[mode].filename, shadermodeinfo[mode].pretext); r_hlsl_permutation = R_HLSL_FindPermutation(mode, permutation); return; // no bit left to clear, entire mode is broken } } } IDirect3DDevice9_SetVertexShader(vid_d3d9dev, r_hlsl_permutation->vertexshader); IDirect3DDevice9_SetPixelShader(vid_d3d9dev, r_hlsl_permutation->pixelshader); } hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); hlslPSSetParameter1f(D3DPSREGISTER_ClientTime, cl.time); } #endif static void R_SetupShader_SetPermutationSoft(unsigned int mode, unsigned int permutation) { DPSOFTRAST_SetShader(mode, permutation, r_shadow_glossexact.integer); DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ClientTime, cl.time); } void R_GLSL_Restart_f(void) { unsigned int i, limit; if (glslshaderstring) Mem_Free(glslshaderstring); glslshaderstring = NULL; if (hlslshaderstring) Mem_Free(hlslshaderstring); hlslshaderstring = NULL; switch(vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D { r_hlsl_permutation_t *p; r_hlsl_permutation = NULL; limit = (unsigned int)Mem_ExpandableArray_IndexRange(&r_hlsl_permutationarray); for (i = 0;i < limit;i++) { if ((p = (r_hlsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_hlsl_permutationarray, i))) { if (p->vertexshader) IDirect3DVertexShader9_Release(p->vertexshader); if (p->pixelshader) IDirect3DPixelShader9_Release(p->pixelshader); Mem_ExpandableArray_FreeRecord(&r_hlsl_permutationarray, (void*)p); } } memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: { r_glsl_permutation_t *p; r_glsl_permutation = NULL; limit = (unsigned int)Mem_ExpandableArray_IndexRange(&r_glsl_permutationarray); for (i = 0;i < limit;i++) { if ((p = (r_glsl_permutation_t*)Mem_ExpandableArray_RecordAtIndex(&r_glsl_permutationarray, i))) { GL_Backend_FreeProgram(p->program); Mem_ExpandableArray_FreeRecord(&r_glsl_permutationarray, (void*)p); } } memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: break; case RENDERPATH_SOFT: break; } } static void R_GLSL_DumpShader_f(void) { int i, language, mode, dupe; char *text; shadermodeinfo_t *modeinfo; qfile_t *file; for (language = 0;language < 2;language++) { modeinfo = (language == 0 ? glslshadermodeinfo : hlslshadermodeinfo); for (mode = 0;mode < SHADERMODE_COUNT;mode++) { // don't dump the same file multiple times (most or all shaders come from the same file) for (dupe = mode - 1;dupe >= 0;dupe--) if (!strcmp(modeinfo[mode].filename, modeinfo[dupe].filename)) break; if (dupe >= 0) continue; text = R_GetShaderText(modeinfo[mode].filename, false, true); if (!text) continue; file = FS_OpenRealFile(modeinfo[mode].filename, "w", false); if (file) { FS_Print(file, "/* The engine may define the following macros:\n"); FS_Print(file, "#define VERTEX_SHADER\n#define GEOMETRY_SHADER\n#define FRAGMENT_SHADER\n"); for (i = 0;i < SHADERMODE_COUNT;i++) FS_Print(file, modeinfo[i].pretext); for (i = 0;i < SHADERPERMUTATION_COUNT;i++) FS_Print(file, shaderpermutationinfo[i].pretext); FS_Print(file, "*/\n"); FS_Print(file, text); FS_Close(file); Con_Printf("%s written\n", modeinfo[mode].filename); } else Con_Printf("failed to write to %s\n", modeinfo[mode].filename); Mem_Free(text); } } } void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean usegamma, qboolean notrippy, qboolean suppresstexalpha) { unsigned int permutation = 0; if (r_trippy.integer && !notrippy) permutation |= SHADERPERMUTATION_TRIPPY; permutation |= SHADERPERMUTATION_VIEWTINT; if (first) permutation |= SHADERPERMUTATION_DIFFUSE; if (second) permutation |= SHADERPERMUTATION_SPECULAR; if (texturemode == GL_MODULATE) permutation |= SHADERPERMUTATION_COLORMAPPING; else if (texturemode == GL_ADD) permutation |= SHADERPERMUTATION_GLOW; else if (texturemode == GL_DECAL) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (usegamma && v_glslgamma.integer && v_glslgamma_2d.integer && !vid.sRGB2D && r_texture_gammaramps && !vid_gammatables_trivial) permutation |= SHADERPERMUTATION_GAMMARAMPS; if (suppresstexalpha) permutation |= SHADERPERMUTATION_REFLECTCUBE; if (!second) texturemode = GL_MODULATE; if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); switch (vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D R_SetupShader_SetPermutationHLSL(SHADERMODE_GENERIC, permutation); R_Mesh_TexBind(GL20TU_FIRST , first ); R_Mesh_TexBind(GL20TU_SECOND, second); if (permutation & SHADERPERMUTATION_GAMMARAMPS) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: R_SetupShader_SetPermutationGLSL(SHADERMODE_GENERIC, permutation); if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , first ); if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second, second); if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps); break; case RENDERPATH_GL13: case RENDERPATH_GLES1: R_Mesh_TexBind(0, first ); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexMatrix(0, NULL); R_Mesh_TexBind(1, second); if (second) { R_Mesh_TexCombine(1, texturemode, texturemode, rgbscale, 1); R_Mesh_TexMatrix(1, NULL); } break; case RENDERPATH_GL11: R_Mesh_TexBind(0, first ); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexMatrix(0, NULL); break; case RENDERPATH_SOFT: R_SetupShader_SetPermutationSoft(SHADERMODE_GENERIC, permutation); R_Mesh_TexBind(GL20TU_FIRST , first ); R_Mesh_TexBind(GL20TU_SECOND, second); break; } } void R_SetupShader_Generic_NoTexture(qboolean usegamma, qboolean notrippy) { R_SetupShader_Generic(NULL, NULL, GL_MODULATE, 1, usegamma, notrippy, false); } void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean skeletal) { unsigned int permutation = 0; if (r_trippy.integer && !notrippy) permutation |= SHADERPERMUTATION_TRIPPY; if (depthrgb) permutation |= SHADERPERMUTATION_DEPTHRGB; if (skeletal) permutation |= SHADERPERMUTATION_SKELETAL; if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); switch (vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D R_SetupShader_SetPermutationHLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: R_SetupShader_SetPermutationGLSL(SHADERMODE_DEPTH_OR_SHADOW, permutation); #ifndef USE_GLES2 /* FIXME: GLES3 only */ if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size); #endif break; case RENDERPATH_GL13: case RENDERPATH_GLES1: R_Mesh_TexBind(0, 0); R_Mesh_TexBind(1, 0); break; case RENDERPATH_GL11: R_Mesh_TexBind(0, 0); break; case RENDERPATH_SOFT: R_SetupShader_SetPermutationSoft(SHADERMODE_DEPTH_OR_SHADOW, permutation); break; } } extern qboolean r_shadow_usingdeferredprepass; extern rtexture_t *r_shadow_attenuationgradienttexture; extern rtexture_t *r_shadow_attenuation2dtexture; extern rtexture_t *r_shadow_attenuation3dtexture; extern qboolean r_shadow_usingshadowmap2d; extern qboolean r_shadow_usingshadowmaportho; extern float r_shadow_shadowmap_texturescale[2]; extern float r_shadow_shadowmap_parameters[4]; extern qboolean r_shadow_shadowmapvsdct; extern rtexture_t *r_shadow_shadowmap2ddepthbuffer; extern rtexture_t *r_shadow_shadowmap2ddepthtexture; extern rtexture_t *r_shadow_shadowmapvsdcttexture; extern matrix4x4_t r_shadow_shadowmapmatrix; extern int r_shadow_prepass_width; extern int r_shadow_prepass_height; extern rtexture_t *r_shadow_prepassgeometrydepthbuffer; extern rtexture_t *r_shadow_prepassgeometrynormalmaptexture; extern rtexture_t *r_shadow_prepasslightingdiffusetexture; extern rtexture_t *r_shadow_prepasslightingspeculartexture; #define BLENDFUNC_ALLOWS_COLORMOD 1 #define BLENDFUNC_ALLOWS_FOG 2 #define BLENDFUNC_ALLOWS_FOG_HACK0 4 #define BLENDFUNC_ALLOWS_FOG_HACKALPHA 8 #define BLENDFUNC_ALLOWS_ANYFOG (BLENDFUNC_ALLOWS_FOG | BLENDFUNC_ALLOWS_FOG_HACK0 | BLENDFUNC_ALLOWS_FOG_HACKALPHA) static int R_BlendFuncFlags(int src, int dst) { int r = 0; // a blendfunc allows colormod if: // a) it can never keep the destination pixel invariant, or // b) it can keep the destination pixel invariant, and still can do so if colormodded // this is to prevent unintended side effects from colormod // a blendfunc allows fog if: // blend(fog(src), fog(dst)) == fog(blend(src, dst)) // this is to prevent unintended side effects from fog // these checks are the output of fogeval.pl r |= BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; if(src == GL_DST_ALPHA && dst == GL_ONE_MINUS_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_DST_COLOR && dst == GL_ONE_MINUS_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_DST_COLOR && dst == GL_SRC_ALPHA) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_DST_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_DST_COLOR && dst == GL_ZERO) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_ONE && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; if(src == GL_ONE && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG_HACKALPHA; if(src == GL_ONE && dst == GL_ZERO) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_DST_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ONE_MINUS_DST_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; if(src == GL_ONE_MINUS_DST_COLOR && dst == GL_SRC_COLOR) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ONE_MINUS_SRC_ALPHA && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_ONE_MINUS_SRC_COLOR && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; if(src == GL_SRC_ALPHA && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG_HACK0; if(src == GL_SRC_ALPHA && dst == GL_ONE_MINUS_SRC_ALPHA) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ZERO && dst == GL_ONE) r |= BLENDFUNC_ALLOWS_FOG; if(src == GL_ZERO && dst == GL_SRC_COLOR) r &= ~BLENDFUNC_ALLOWS_COLORMOD; return r; } void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *surfacewaterplane, qboolean notrippy) { // select a permutation of the lighting shader appropriate to this // combination of texture, entity, light source, and fogging, only use the // minimum features necessary to avoid wasting rendering time in the // fragment shader on features that are not being used unsigned int permutation = 0; unsigned int mode = 0; int blendfuncflags; static float dummy_colormod[3] = {1, 1, 1}; float *colormod = rsurface.colormod; float m16f[16]; matrix4x4_t tempmatrix; r_waterstate_waterplane_t *waterplane = (r_waterstate_waterplane_t *)surfacewaterplane; if (r_trippy.integer && !notrippy) permutation |= SHADERPERMUTATION_TRIPPY; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) permutation |= SHADERPERMUTATION_ALPHAKILL; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_OCCLUDE) permutation |= SHADERPERMUTATION_OCCLUDE; if (rsurface.texture->r_water_waterscroll[0] && rsurface.texture->r_water_waterscroll[1]) permutation |= SHADERPERMUTATION_NORMALMAPSCROLLBLEND; // todo: make generic if (rsurfacepass == RSURFPASS_BACKGROUND) { // distorted background if (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERSHADER) { mode = SHADERMODE_WATER; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; if((r_wateralpha.value < 1) && (rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA)) { // this is the right thing to do for wateralpha GL_BlendFunc(GL_ONE, GL_ZERO); blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); } else { // this is the right thing to do for entity alpha GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } } else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFRACTION) { mode = SHADERMODE_REFRACTION; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { mode = SHADERMODE_GENERIC; permutation |= SHADERPERMUTATION_DIFFUSE | SHADERPERMUTATION_ALPHAKILL; GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); } else if (rsurfacepass == RSURFPASS_DEFERREDGEOMETRY) { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; // normalmap (deferred prepass), may use alpha test on diffuse mode = SHADERMODE_DEFERREDGEOMETRY; GL_BlendFunc(GL_ONE, GL_ZERO); blendfuncflags = R_BlendFuncFlags(GL_ONE, GL_ZERO); if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); } else if (rsurfacepass == RSURFPASS_RTLIGHT) { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; // light source mode = SHADERMODE_LIGHTSOURCE; if (rsurface.rtlight->currentcubemap != r_texture_whitecube) permutation |= SHADERPERMUTATION_CUBEFILTER; if (diffusescale > 0) permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0) permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; if (r_refdef.fogenabled) permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); if (rsurface.texture->colormapping) permutation |= SHADERPERMUTATION_COLORMAPPING; if (r_shadow_usingshadowmap2d) { permutation |= SHADERPERMUTATION_SHADOWMAP2D; if(r_shadow_shadowmapvsdct) permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (rsurface.texture->reflectmasktexture) permutation |= SHADERPERMUTATION_REFLECTCUBE; GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); blendfuncflags = R_BlendFuncFlags(GL_SRC_ALPHA, GL_ONE); if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); } else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; // unshaded geometry (fullbright or ambient model lighting) mode = SHADERMODE_FLATCOLOR; ambientscale = diffusescale = specularscale = 0; if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) permutation |= SHADERPERMUTATION_GLOW; if (r_refdef.fogenabled) permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); if (rsurface.texture->colormapping) permutation |= SHADERPERMUTATION_COLORMAPPING; if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) { permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; permutation |= SHADERPERMUTATION_SHADOWMAP2D; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) permutation |= SHADERPERMUTATION_REFLECTION; if (rsurface.texture->reflectmasktexture) permutation |= SHADERPERMUTATION_REFLECTCUBE; GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); // when using alphatocoverage, we don't need alphakill if (vid.allowalphatocoverage) { if (r_transparent_alphatocoverage.integer) { GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); permutation &= ~SHADERPERMUTATION_ALPHAKILL; } else GL_AlphaToCoverage(false); } } else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT_DIRECTIONAL) { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; // directional model lighting mode = SHADERMODE_LIGHTDIRECTION; if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) permutation |= SHADERPERMUTATION_GLOW; permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0) permutation |= SHADERPERMUTATION_SPECULAR; if (r_refdef.fogenabled) permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); if (rsurface.texture->colormapping) permutation |= SHADERPERMUTATION_COLORMAPPING; if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) { permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; permutation |= SHADERPERMUTATION_SHADOWMAP2D; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) permutation |= SHADERPERMUTATION_REFLECTION; if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; if (rsurface.texture->reflectmasktexture) permutation |= SHADERPERMUTATION_REFLECTCUBE; if (r_shadow_bouncegrid_state.texture && cl.csqc_vidvars.drawworld) { permutation |= SHADERPERMUTATION_BOUNCEGRID; if (r_shadow_bouncegrid_state.directional) permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; } GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); // when using alphatocoverage, we don't need alphakill if (vid.allowalphatocoverage) { if (r_transparent_alphatocoverage.integer) { GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); permutation &= ~SHADERPERMUTATION_ALPHAKILL; } else GL_AlphaToCoverage(false); } } else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; // ambient model lighting mode = SHADERMODE_LIGHTDIRECTION; if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) permutation |= SHADERPERMUTATION_GLOW; if (r_refdef.fogenabled) permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); if (rsurface.texture->colormapping) permutation |= SHADERPERMUTATION_COLORMAPPING; if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) { permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; permutation |= SHADERPERMUTATION_SHADOWMAP2D; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) permutation |= SHADERPERMUTATION_REFLECTION; if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; if (rsurface.texture->reflectmasktexture) permutation |= SHADERPERMUTATION_REFLECTCUBE; if (r_shadow_bouncegrid_state.texture && cl.csqc_vidvars.drawworld) { permutation |= SHADERPERMUTATION_BOUNCEGRID; if (r_shadow_bouncegrid_state.directional) permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; } GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); // when using alphatocoverage, we don't need alphakill if (vid.allowalphatocoverage) { if (r_transparent_alphatocoverage.integer) { GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); permutation &= ~SHADERPERMUTATION_ALPHAKILL; } else GL_AlphaToCoverage(false); } } else { if (r_glsl_offsetmapping.integer && ((R_TextureFlags(rsurface.texture->nmaptexture) & TEXF_ALPHA) || rsurface.texture->offsetbias != 0.0f)) { switch(rsurface.texture->offsetmapping) { case OFFSETMAPPING_LINEAR: permutation |= SHADERPERMUTATION_OFFSETMAPPING;break; case OFFSETMAPPING_RELIEF: permutation |= SHADERPERMUTATION_OFFSETMAPPING | SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_DEFAULT: permutation |= SHADERPERMUTATION_OFFSETMAPPING;if (r_glsl_offsetmapping_reliefmapping.integer) permutation |= SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING;break; case OFFSETMAPPING_OFF: break; } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_VERTEXTEXTUREBLEND) permutation |= SHADERPERMUTATION_VERTEXTEXTUREBLEND; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHAGEN_VERTEX) permutation |= SHADERPERMUTATION_ALPHAGEN_VERTEX; // lightmapped wall if ((rsurface.texture->glowtexture || rsurface.texture->backgroundglowtexture) && r_hdr_glowintensity.value > 0 && !gl_lightmaps.integer) permutation |= SHADERPERMUTATION_GLOW; if (r_refdef.fogenabled) permutation |= r_texture_fogheighttexture ? SHADERPERMUTATION_FOGHEIGHTTEXTURE : (r_refdef.fogplaneviewabove ? SHADERPERMUTATION_FOGOUTSIDE : SHADERPERMUTATION_FOGINSIDE); if (rsurface.texture->colormapping) permutation |= SHADERPERMUTATION_COLORMAPPING; if (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW)) { permutation |= SHADERPERMUTATION_SHADOWMAPORTHO; permutation |= SHADERPERMUTATION_SHADOWMAP2D; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION) permutation |= SHADERPERMUTATION_REFLECTION; if (r_shadow_usingdeferredprepass && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)) permutation |= SHADERPERMUTATION_DEFERREDLIGHTMAP; if (rsurface.texture->reflectmasktexture) permutation |= SHADERPERMUTATION_REFLECTCUBE; if (FAKELIGHT_ENABLED) { // fake lightmapping (q1bsp, q3bsp, fullbright map) mode = SHADERMODE_FAKELIGHT; permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0) permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; } else if (r_glsl_deluxemapping.integer >= 1 && rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping) { // deluxemapping (light direction texture) if (rsurface.uselightmaptexture && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brushq3.deluxemapping && r_refdef.scene.worldmodel->brushq3.deluxemapping_modelspace) mode = SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE; else mode = SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE; permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0) permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; } else if (r_glsl_deluxemapping.integer >= 2) { // fake deluxemapping (uniform light direction in tangentspace) if (rsurface.uselightmaptexture) mode = SHADERMODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP; else mode = SHADERMODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR; permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0) permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; } else if (rsurface.uselightmaptexture) { // ordinary lightmapping (q1bsp, q3bsp) mode = SHADERMODE_LIGHTMAP; } else { // ordinary vertex coloring (q3bsp) mode = SHADERMODE_VERTEXCOLOR; } if (r_shadow_bouncegrid_state.texture && cl.csqc_vidvars.drawworld) { permutation |= SHADERPERMUTATION_BOUNCEGRID; if (r_shadow_bouncegrid_state.directional) permutation |= SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL; } GL_BlendFunc(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); blendfuncflags = R_BlendFuncFlags(rsurface.texture->currentlayers[0].blendfunc1, rsurface.texture->currentlayers[0].blendfunc2); // when using alphatocoverage, we don't need alphakill if (vid.allowalphatocoverage) { if (r_transparent_alphatocoverage.integer) { GL_AlphaToCoverage((rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) != 0); permutation &= ~SHADERPERMUTATION_ALPHAKILL; } else GL_AlphaToCoverage(false); } } if(!(blendfuncflags & BLENDFUNC_ALLOWS_COLORMOD)) colormod = dummy_colormod; if(!(blendfuncflags & BLENDFUNC_ALLOWS_ANYFOG)) permutation &= ~(SHADERPERMUTATION_FOGHEIGHTTEXTURE | SHADERPERMUTATION_FOGOUTSIDE | SHADERPERMUTATION_FOGINSIDE); if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACKALPHA) permutation |= SHADERPERMUTATION_FOGALPHAHACK; switch(vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset); R_SetupShader_SetPermutationHLSL(mode, permutation); Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);hlslPSSetParameter16f(D3DPSREGISTER_ModelToReflectCube, m16f); if (mode == SHADERMODE_LIGHTSOURCE) { Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ModelToLight, m16f); hlslVSSetParameter3f(D3DVSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); } else { if (mode == SHADERMODE_LIGHTDIRECTION) { hlslVSSetParameter3f(D3DVSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); } } Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_TexMatrix, m16f); Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_BackgroundTexMatrix, m16f); Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);hlslVSSetParameter16f(D3DVSREGISTER_ShadowMapMatrix, m16f); hlslVSSetParameter3f(D3DVSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); hlslVSSetParameter4f(D3DVSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); if (mode == SHADERMODE_LIGHTSOURCE) { hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); hlslPSSetParameter3f(D3DPSREGISTER_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); // additive passes are only darkened by fog, not tinted hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); } else { if (mode == SHADERMODE_FLATCOLOR) { hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, colormod[0], colormod[1], colormod[2]); } else if (mode == SHADERMODE_LIGHTDIRECTION) { hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity) * colormod[2]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale, specularscale, specularscale); hlslPSSetParameter3f(D3DPSREGISTER_LightColor, rsurface.modellight_diffuse[0], rsurface.modellight_diffuse[1], rsurface.modellight_diffuse[2]); hlslPSSetParameter3f(D3DPSREGISTER_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); } else { hlslPSSetParameter3f(D3DPSREGISTER_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); hlslPSSetParameter3f(D3DPSREGISTER_DeferredMod_Specular, specularscale, specularscale, specularscale); } // additive passes are only darkened by fog, not tinted if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) hlslPSSetParameter3f(D3DPSREGISTER_FogColor, 0, 0, 0); else hlslPSSetParameter3f(D3DPSREGISTER_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); hlslPSSetParameter4f(D3DPSREGISTER_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); hlslPSSetParameter4f(D3DPSREGISTER_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); hlslPSSetParameter4f(D3DPSREGISTER_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); hlslPSSetParameter4f(D3DPSREGISTER_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); hlslPSSetParameter4f(D3DPSREGISTER_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); hlslPSSetParameter1f(D3DPSREGISTER_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); hlslPSSetParameter1f(D3DPSREGISTER_ReflectOffset, rsurface.texture->reflectmin); hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, (rsurface.texture->specularpower - 1.0f) * (r_shadow_glossexact.integer ? 0.25f : 1.0f)); if (mode == SHADERMODE_WATER) hlslPSSetParameter2f(D3DPSREGISTER_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); } hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); hlslPSSetParameter3f(D3DPSREGISTER_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); hlslPSSetParameter1f(D3DPSREGISTER_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); hlslPSSetParameter3f(D3DPSREGISTER_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); if (rsurface.texture->pantstexture) hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); else hlslPSSetParameter3f(D3DPSREGISTER_Color_Pants, 0, 0, 0); if (rsurface.texture->shirttexture) hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); else hlslPSSetParameter3f(D3DPSREGISTER_Color_Shirt, 0, 0, 0); hlslPSSetParameter4f(D3DPSREGISTER_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); hlslPSSetParameter1f(D3DPSREGISTER_FogPlaneViewDist, rsurface.fogplaneviewdist); hlslPSSetParameter1f(D3DPSREGISTER_FogRangeRecip, rsurface.fograngerecip); hlslPSSetParameter1f(D3DPSREGISTER_FogHeightFade, rsurface.fogheightfade); hlslPSSetParameter4f(D3DPSREGISTER_OffsetMapping_ScaleSteps, r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) ); hlslPSSetParameter1f(D3DPSREGISTER_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); hlslPSSetParameter1f(D3DPSREGISTER_OffsetMapping_Bias, rsurface.texture->offsetbias); hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); if (rsurfacepass == RSURFPASS_BACKGROUND) { R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } else { if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } // if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) { R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2ddepthtexture); if (rsurface.rtlight) { if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: if (!vid.useinterleavedarrays) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_VertexPointer( 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); R_Mesh_ColorPointer( 4, GL_FLOAT, sizeof(float[4]), rsurface.batchlightmapcolor4f, rsurface.batchlightmapcolor4f_vertexbuffer, rsurface.batchlightmapcolor4f_bufferoffset); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchsvector3f, rsurface.batchsvector3f_vertexbuffer, rsurface.batchsvector3f_bufferoffset); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchtvector3f, rsurface.batchtvector3f_vertexbuffer, rsurface.batchtvector3f_bufferoffset); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchnormal3f, rsurface.batchnormal3f_vertexbuffer, rsurface.batchnormal3f_bufferoffset); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, rsurface.batchskeletalindex4ub_vertexbuffer, rsurface.batchskeletalindex4ub_bufferoffset); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, rsurface.batchskeletalweight4ub_vertexbuffer, rsurface.batchskeletalweight4ub_bufferoffset); } else { RSurf_PrepareVerticesForBatch(BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_VERTEXMESH_VERTEXCOLOR : 0) | BATCHNEED_VERTEXMESH_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_VERTEXMESH_LIGHTMAP : 0) | (rsurface.entityskeletaltransform3x4 ? BATCHNEED_VERTEXMESH_SKELETAL : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Mesh(rsurface.batchnumvertices, rsurface.batchvertexmesh, rsurface.batchvertexmesh_vertexbuffer, rsurface.batchvertexmesh_bufferoffset); } // this has to be after RSurf_PrepareVerticesForBatch if (rsurface.batchskeletaltransform3x4buffer) permutation |= SHADERPERMUTATION_SKELETAL; R_SetupShader_SetPermutationGLSL(mode, permutation); #ifndef USE_GLES2 /* FIXME: GLES3 only */ if (r_glsl_permutation->ubiloc_Skeletal_Transform12_UniformBlock >= 0 && rsurface.batchskeletaltransform3x4buffer) qglBindBufferRange(GL_UNIFORM_BUFFER, r_glsl_permutation->ubibind_Skeletal_Transform12_UniformBlock, rsurface.batchskeletaltransform3x4buffer->bufferobject, rsurface.batchskeletaltransform3x4offset, rsurface.batchskeletaltransform3x4size); #endif if (r_glsl_permutation->loc_ModelToReflectCube >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToReflectCube, 1, false, m16f);} if (mode == SHADERMODE_LIGHTSOURCE) { if (r_glsl_permutation->loc_ModelToLight >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ModelToLight, 1, false, m16f);} if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f(r_glsl_permutation->loc_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); // additive passes are only darkened by fog, not tinted if (r_glsl_permutation->loc_FogColor >= 0) qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); } else { if (mode == SHADERMODE_FLATCOLOR) { if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, colormod[0], colormod[1], colormod[2]); } else if (mode == SHADERMODE_LIGHTDIRECTION) { if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale, specularscale, specularscale); if (r_glsl_permutation->loc_LightColor >= 0) qglUniform3f(r_glsl_permutation->loc_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); if (r_glsl_permutation->loc_LightDir >= 0) qglUniform3f(r_glsl_permutation->loc_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); } else { if (r_glsl_permutation->loc_Color_Ambient >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); if (r_glsl_permutation->loc_Color_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); if (r_glsl_permutation->loc_Color_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); if (r_glsl_permutation->loc_DeferredMod_Diffuse >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); if (r_glsl_permutation->loc_DeferredMod_Specular >= 0) qglUniform3f(r_glsl_permutation->loc_DeferredMod_Specular, specularscale, specularscale, specularscale); } // additive passes are only darkened by fog, not tinted if (r_glsl_permutation->loc_FogColor >= 0) { if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) qglUniform3f(r_glsl_permutation->loc_FogColor, 0, 0, 0); else qglUniform3f(r_glsl_permutation->loc_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); } if (r_glsl_permutation->loc_DistortScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); if (r_glsl_permutation->loc_ScreenScaleRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); if (r_glsl_permutation->loc_ScreenCenterRefractReflect >= 0) qglUniform4f(r_glsl_permutation->loc_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); if (r_glsl_permutation->loc_RefractColor >= 0) qglUniform4f(r_glsl_permutation->loc_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); if (r_glsl_permutation->loc_ReflectColor >= 0) qglUniform4f(r_glsl_permutation->loc_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); if (r_glsl_permutation->loc_ReflectFactor >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); if (r_glsl_permutation->loc_ReflectOffset >= 0) qglUniform1f(r_glsl_permutation->loc_ReflectOffset, rsurface.texture->reflectmin); if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f(r_glsl_permutation->loc_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); if (r_glsl_permutation->loc_NormalmapScrollBlend >= 0) qglUniform2f(r_glsl_permutation->loc_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); } if (r_glsl_permutation->loc_TexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_TexMatrix, 1, false, m16f);} if (r_glsl_permutation->loc_BackgroundTexMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BackgroundTexMatrix, 1, false, m16f);} if (r_glsl_permutation->loc_ShadowMapMatrix >= 0) {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_ShadowMapMatrix, 1, false, m16f);} if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f(r_glsl_permutation->loc_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f(r_glsl_permutation->loc_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); if (r_glsl_permutation->loc_Color_Glow >= 0) qglUniform3f(r_glsl_permutation->loc_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); if (r_glsl_permutation->loc_Alpha >= 0) qglUniform1f(r_glsl_permutation->loc_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); if (r_glsl_permutation->loc_EyePosition >= 0) qglUniform3f(r_glsl_permutation->loc_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); if (r_glsl_permutation->loc_Color_Pants >= 0) { if (rsurface.texture->pantstexture) qglUniform3f(r_glsl_permutation->loc_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); else qglUniform3f(r_glsl_permutation->loc_Color_Pants, 0, 0, 0); } if (r_glsl_permutation->loc_Color_Shirt >= 0) { if (rsurface.texture->shirttexture) qglUniform3f(r_glsl_permutation->loc_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); else qglUniform3f(r_glsl_permutation->loc_Color_Shirt, 0, 0, 0); } if (r_glsl_permutation->loc_FogPlane >= 0) qglUniform4f(r_glsl_permutation->loc_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); if (r_glsl_permutation->loc_FogPlaneViewDist >= 0) qglUniform1f(r_glsl_permutation->loc_FogPlaneViewDist, rsurface.fogplaneviewdist); if (r_glsl_permutation->loc_FogRangeRecip >= 0) qglUniform1f(r_glsl_permutation->loc_FogRangeRecip, rsurface.fograngerecip); if (r_glsl_permutation->loc_FogHeightFade >= 0) qglUniform1f(r_glsl_permutation->loc_FogHeightFade, rsurface.fogheightfade); if (r_glsl_permutation->loc_OffsetMapping_ScaleSteps >= 0) qglUniform4f(r_glsl_permutation->loc_OffsetMapping_ScaleSteps, r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) ); if (r_glsl_permutation->loc_OffsetMapping_LodDistance >= 0) qglUniform1f(r_glsl_permutation->loc_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); if (r_glsl_permutation->loc_OffsetMapping_Bias >= 0) qglUniform1f(r_glsl_permutation->loc_OffsetMapping_Bias, rsurface.texture->offsetbias); if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f(r_glsl_permutation->loc_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); if (r_glsl_permutation->loc_BounceGridMatrix >= 0) {Matrix4x4_Concat(&tempmatrix, &r_shadow_bouncegrid_state.matrix, &rsurface.matrix);Matrix4x4_ToArrayFloatGL(&tempmatrix, m16f);qglUniformMatrix4fv(r_glsl_permutation->loc_BounceGridMatrix, 1, false, m16f);} if (r_glsl_permutation->loc_BounceGridIntensity >= 0) qglUniform1f(r_glsl_permutation->loc_BounceGridIntensity, r_shadow_bouncegrid_state.intensity*r_refdef.view.colorscale); if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_texture_white ); if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_texture_white ); if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps , r_texture_gammaramps ); if (r_glsl_permutation->tex_Texture_Normal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Normal , rsurface.texture->nmaptexture ); if (r_glsl_permutation->tex_Texture_Color >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Color , rsurface.texture->basetexture ); if (r_glsl_permutation->tex_Texture_Gloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Gloss , rsurface.texture->glosstexture ); if (r_glsl_permutation->tex_Texture_Glow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Glow , rsurface.texture->glowtexture ); if (r_glsl_permutation->tex_Texture_SecondaryNormal >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryNormal , rsurface.texture->backgroundnmaptexture ); if (r_glsl_permutation->tex_Texture_SecondaryColor >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryColor , rsurface.texture->backgroundbasetexture ); if (r_glsl_permutation->tex_Texture_SecondaryGloss >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGloss , rsurface.texture->backgroundglosstexture ); if (r_glsl_permutation->tex_Texture_SecondaryGlow >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_SecondaryGlow , rsurface.texture->backgroundglowtexture ); if (r_glsl_permutation->tex_Texture_Pants >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Pants , rsurface.texture->pantstexture ); if (r_glsl_permutation->tex_Texture_Shirt >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Shirt , rsurface.texture->shirttexture ); if (r_glsl_permutation->tex_Texture_ReflectMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectMask , rsurface.texture->reflectmasktexture ); if (r_glsl_permutation->tex_Texture_ReflectCube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ReflectCube , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); if (r_glsl_permutation->tex_Texture_FogHeightTexture>= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogHeightTexture , r_texture_fogheighttexture ); if (r_glsl_permutation->tex_Texture_FogMask >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_FogMask , r_texture_fogattenuation ); if (r_glsl_permutation->tex_Texture_Lightmap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Lightmap , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); if (r_glsl_permutation->tex_Texture_Deluxemap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Deluxemap , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); if (rsurfacepass == RSURFPASS_BACKGROUND) { if (r_glsl_permutation->tex_Texture_Refraction >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Refraction , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); if (r_glsl_permutation->tex_Texture_Reflection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } else { if (r_glsl_permutation->tex_Texture_Reflection >= 0 && waterplane) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Reflection , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); if (r_glsl_permutation->tex_Texture_ScreenDiffuse >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenDiffuse , r_shadow_prepasslightingdiffusetexture ); if (r_glsl_permutation->tex_Texture_ScreenSpecular >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenSpecular , r_shadow_prepasslightingspeculartexture ); if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) { if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D, r_shadow_shadowmap2ddepthtexture ); if (rsurface.rtlight) { if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); } } if (r_glsl_permutation->tex_Texture_BounceGrid >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_BounceGrid, r_shadow_bouncegrid_state.texture); CHECKGLERROR break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: break; case RENDERPATH_SOFT: RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | (rsurface.modellightmapcolor4f ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.uselightmaptexture ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Mesh_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchsvector3f, rsurface.batchtvector3f, rsurface.batchnormal3f, rsurface.batchlightmapcolor4f, rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordlightmap2f); R_SetupShader_SetPermutationSoft(mode, permutation); {Matrix4x4_ToArrayFloatGL(&rsurface.matrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, 1, false, m16f);} if (mode == SHADERMODE_LIGHTSOURCE) { {Matrix4x4_ToArrayFloatGL(&rsurface.entitytolight, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelToLightM1, 1, false, m16f);} DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightPosition, rsurface.entitylightorigin[0], rsurface.entitylightorigin[1], rsurface.entitylightorigin[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, lightcolorbase[0], lightcolorbase[1], lightcolorbase[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0] * ambientscale, colormod[1] * ambientscale, colormod[2] * ambientscale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale, r_refdef.view.colorscale * specularscale); // additive passes are only darkened by fog, not tinted DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); } else { if (mode == SHADERMODE_FLATCOLOR) { DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, colormod[0], colormod[1], colormod[2]); } else if (mode == SHADERMODE_LIGHTDIRECTION) { DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, (r_refdef.scene.ambient + rsurface.modellight_ambient[0] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[0], (r_refdef.scene.ambient + rsurface.modellight_ambient[1] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[1], (r_refdef.scene.ambient + rsurface.modellight_ambient[2] * r_refdef.lightmapintensity * r_refdef.scene.rtlightstylevalue[0]) * colormod[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, r_refdef.lightmapintensity * colormod[0], r_refdef.lightmapintensity * colormod[1], r_refdef.lightmapintensity * colormod[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0], colormod[1], colormod[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale, specularscale, specularscale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightColor, rsurface.modellight_diffuse[0] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[1] * r_refdef.scene.rtlightstylevalue[0], rsurface.modellight_diffuse[2] * r_refdef.scene.rtlightstylevalue[0]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_LightDir, rsurface.modellight_lightdir[0], rsurface.modellight_lightdir[1], rsurface.modellight_lightdir[2]); } else { DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Ambient, r_refdef.scene.ambient * colormod[0], r_refdef.scene.ambient * colormod[1], r_refdef.scene.ambient * colormod[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Diffuse, rsurface.texture->lightmapcolor[0], rsurface.texture->lightmapcolor[1], rsurface.texture->lightmapcolor[2]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Specular, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale, r_refdef.lightmapintensity * r_refdef.view.colorscale * specularscale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, colormod[0] * diffusescale, colormod[1] * diffusescale, colormod[2] * diffusescale); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_DeferredMod_Specular, specularscale, specularscale, specularscale); } // additive passes are only darkened by fog, not tinted if(blendfuncflags & BLENDFUNC_ALLOWS_FOG_HACK0) DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, 0, 0, 0); else DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_FogColor, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_refractdistort.value * rsurface.texture->refractfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor, r_water_reflectdistort.value * rsurface.texture->reflectfactor); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, r_fb.water.screenscale[0], r_fb.water.screenscale[1], r_fb.water.screenscale[0], r_fb.water.screenscale[1]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, r_fb.water.screencenter[0], r_fb.water.screencenter[1], r_fb.water.screencenter[0], r_fb.water.screencenter[1]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_RefractColor, rsurface.texture->refractcolor4f[0], rsurface.texture->refractcolor4f[1], rsurface.texture->refractcolor4f[2], rsurface.texture->refractcolor4f[3] * rsurface.texture->lightmapcolor[3]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ReflectColor, rsurface.texture->reflectcolor4f[0], rsurface.texture->reflectcolor4f[1], rsurface.texture->reflectcolor4f[2], rsurface.texture->reflectcolor4f[3] * rsurface.texture->lightmapcolor[3]); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectFactor, rsurface.texture->reflectmax - rsurface.texture->reflectmin); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_ReflectOffset, rsurface.texture->reflectmin); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_SpecularPower, rsurface.texture->specularpower * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_NormalmapScrollBlend, rsurface.texture->r_water_waterscroll[0], rsurface.texture->r_water_waterscroll[1]); } {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currenttexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_TexMatrixM1, 1, false, m16f);} {Matrix4x4_ToArrayFloatGL(&rsurface.texture->currentbackgroundtexmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, 1, false, m16f);} {Matrix4x4_ToArrayFloatGL(&r_shadow_shadowmapmatrix, m16f);DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, 1, false, m16f);} DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Glow, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2]); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Alpha, rsurface.texture->lightmapcolor[3] * ((rsurface.texture->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) ? rsurface.texture->r_water_wateralpha : 1)); DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_EyePosition, rsurface.localvieworigin[0], rsurface.localvieworigin[1], rsurface.localvieworigin[2]); if (DPSOFTRAST_UNIFORM_Color_Pants >= 0) { if (rsurface.texture->pantstexture) DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, rsurface.colormap_pantscolor[0], rsurface.colormap_pantscolor[1], rsurface.colormap_pantscolor[2]); else DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Pants, 0, 0, 0); } if (DPSOFTRAST_UNIFORM_Color_Shirt >= 0) { if (rsurface.texture->shirttexture) DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, rsurface.colormap_shirtcolor[0], rsurface.colormap_shirtcolor[1], rsurface.colormap_shirtcolor[2]); else DPSOFTRAST_Uniform3f(DPSOFTRAST_UNIFORM_Color_Shirt, 0, 0, 0); } DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_FogPlane, rsurface.fogplane[0], rsurface.fogplane[1], rsurface.fogplane[2], rsurface.fogplane[3]); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogPlaneViewDist, rsurface.fogplaneviewdist); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogRangeRecip, rsurface.fograngerecip); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_FogHeightFade, rsurface.fogheightfade); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, r_glsl_offsetmapping_scale.value*rsurface.texture->offsetscale, max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), 1.0 / max(1, (permutation & SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING) ? r_glsl_offsetmapping_reliefmapping_steps.integer : r_glsl_offsetmapping_steps.integer), max(1, r_glsl_offsetmapping_reliefmapping_refinesteps.integer) ); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_OffsetMapping_LodDistance, r_glsl_offsetmapping_lod_distance.integer * r_refdef.view.quality); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_OffsetMapping_Bias, rsurface.texture->offsetbias); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); R_Mesh_TexBind(GL20TU_NORMAL , rsurface.texture->nmaptexture ); R_Mesh_TexBind(GL20TU_COLOR , rsurface.texture->basetexture ); R_Mesh_TexBind(GL20TU_GLOSS , rsurface.texture->glosstexture ); R_Mesh_TexBind(GL20TU_GLOW , rsurface.texture->glowtexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_NORMAL , rsurface.texture->backgroundnmaptexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_COLOR , rsurface.texture->backgroundbasetexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOSS , rsurface.texture->backgroundglosstexture ); if (permutation & SHADERPERMUTATION_VERTEXTEXTUREBLEND) R_Mesh_TexBind(GL20TU_SECONDARY_GLOW , rsurface.texture->backgroundglowtexture ); if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_PANTS , rsurface.texture->pantstexture ); if (permutation & SHADERPERMUTATION_COLORMAPPING) R_Mesh_TexBind(GL20TU_SHIRT , rsurface.texture->shirttexture ); if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTMASK , rsurface.texture->reflectmasktexture ); if (permutation & SHADERPERMUTATION_REFLECTCUBE) R_Mesh_TexBind(GL20TU_REFLECTCUBE , rsurface.texture->reflectcubetexture ? rsurface.texture->reflectcubetexture : r_texture_whitecube); if (permutation & SHADERPERMUTATION_FOGHEIGHTTEXTURE) R_Mesh_TexBind(GL20TU_FOGHEIGHTTEXTURE , r_texture_fogheighttexture ); if (permutation & (SHADERPERMUTATION_FOGINSIDE | SHADERPERMUTATION_FOGOUTSIDE)) R_Mesh_TexBind(GL20TU_FOGMASK , r_texture_fogattenuation ); R_Mesh_TexBind(GL20TU_LIGHTMAP , rsurface.lightmaptexture ? rsurface.lightmaptexture : r_texture_white); R_Mesh_TexBind(GL20TU_DELUXEMAP , rsurface.deluxemaptexture ? rsurface.deluxemaptexture : r_texture_blanknormalmap); if (rsurface.rtlight ) R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); if (rsurfacepass == RSURFPASS_BACKGROUND) { R_Mesh_TexBind(GL20TU_REFRACTION , waterplane->texture_refraction ? waterplane->texture_refraction : r_texture_black); if(mode == SHADERMODE_GENERIC) R_Mesh_TexBind(GL20TU_FIRST , waterplane->texture_camera ? waterplane->texture_camera : r_texture_black); R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } else { if (permutation & SHADERPERMUTATION_REFLECTION ) R_Mesh_TexBind(GL20TU_REFLECTION , waterplane->texture_reflection ? waterplane->texture_reflection : r_texture_black); } // if (rsurfacepass == RSURFPASS_DEFERREDLIGHT ) R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENDIFFUSE , r_shadow_prepasslightingdiffusetexture ); if (permutation & SHADERPERMUTATION_DEFERREDLIGHTMAP ) R_Mesh_TexBind(GL20TU_SCREENSPECULAR , r_shadow_prepasslightingspeculartexture ); if (rsurface.rtlight || (r_shadow_usingshadowmaportho && !(rsurface.ent_flags & RENDER_NOSELFSHADOW))) { R_Mesh_TexBind(GL20TU_SHADOWMAP2D, r_shadow_shadowmap2ddepthtexture); if (rsurface.rtlight) { if (permutation & SHADERPERMUTATION_CUBEFILTER ) R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); if (permutation & SHADERPERMUTATION_SHADOWMAPVSDCT ) R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); } } break; } } void R_SetupShader_DeferredLight(const rtlight_t *rtlight) { // select a permutation of the lighting shader appropriate to this // combination of texture, entity, light source, and fogging, only use the // minimum features necessary to avoid wasting rendering time in the // fragment shader on features that are not being used unsigned int permutation = 0; unsigned int mode = 0; const float *lightcolorbase = rtlight->currentcolor; float ambientscale = rtlight->ambientscale; float diffusescale = rtlight->diffusescale; float specularscale = rtlight->specularscale; // this is the location of the light in view space vec3_t viewlightorigin; // this transforms from view space (camera) to light space (cubemap) matrix4x4_t viewtolight; matrix4x4_t lighttoview; float viewtolight16f[16]; // light source mode = SHADERMODE_DEFERREDLIGHTSOURCE; if (rtlight->currentcubemap != r_texture_whitecube) permutation |= SHADERPERMUTATION_CUBEFILTER; if (diffusescale > 0) permutation |= SHADERPERMUTATION_DIFFUSE; if (specularscale > 0 && r_shadow_gloss.integer > 0) permutation |= SHADERPERMUTATION_SPECULAR | SHADERPERMUTATION_DIFFUSE; if (r_shadow_usingshadowmap2d) { permutation |= SHADERPERMUTATION_SHADOWMAP2D; if (r_shadow_shadowmapvsdct) permutation |= SHADERPERMUTATION_SHADOWMAPVSDCT; if (r_shadow_shadowmap2ddepthbuffer) permutation |= SHADERPERMUTATION_DEPTHRGB; } if (vid.allowalphatocoverage) GL_AlphaToCoverage(false); Matrix4x4_Transform(&r_refdef.view.viewport.viewmatrix, rtlight->shadoworigin, viewlightorigin); Matrix4x4_Concat(&lighttoview, &r_refdef.view.viewport.viewmatrix, &rtlight->matrix_lighttoworld); Matrix4x4_Invert_Full(&viewtolight, &lighttoview); Matrix4x4_ToArrayFloatGL(&viewtolight, viewtolight16f); switch(vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D R_SetupShader_SetPermutationHLSL(mode, permutation); hlslPSSetParameter3f(D3DPSREGISTER_LightPosition, viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); hlslPSSetParameter16f(D3DPSREGISTER_ViewToLight, viewtolight16f); hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); hlslPSSetParameter3f(D3DPSREGISTER_DeferredColor_Specular, lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); hlslPSSetParameter2f(D3DPSREGISTER_ShadowMap_TextureScale, r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); hlslPSSetParameter4f(D3DPSREGISTER_ShadowMap_Parameters, r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); hlslPSSetParameter1f(D3DPSREGISTER_SpecularPower, (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); hlslPSSetParameter2f(D3DPSREGISTER_ScreenToDepth, r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2ddepthtexture ); R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: R_SetupShader_SetPermutationGLSL(mode, permutation); if (r_glsl_permutation->loc_LightPosition >= 0) qglUniform3f( r_glsl_permutation->loc_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); if (r_glsl_permutation->loc_ViewToLight >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ViewToLight , 1, false, viewtolight16f); if (r_glsl_permutation->loc_DeferredColor_Ambient >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); if (r_glsl_permutation->loc_DeferredColor_Diffuse >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); if (r_glsl_permutation->loc_DeferredColor_Specular >= 0) qglUniform3f( r_glsl_permutation->loc_DeferredColor_Specular , lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); if (r_glsl_permutation->loc_ShadowMap_TextureScale >= 0) qglUniform2f( r_glsl_permutation->loc_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); if (r_glsl_permutation->loc_ShadowMap_Parameters >= 0) qglUniform4f( r_glsl_permutation->loc_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); if (r_glsl_permutation->loc_SpecularPower >= 0) qglUniform1f( r_glsl_permutation->loc_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); if (r_glsl_permutation->loc_ScreenToDepth >= 0) qglUniform2f( r_glsl_permutation->loc_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f( r_glsl_permutation->loc_PixelToScreenTexCoord , 1.0f/vid.width, 1.0f/vid.height); if (r_glsl_permutation->tex_Texture_Attenuation >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Attenuation , r_shadow_attenuationgradienttexture ); if (r_glsl_permutation->tex_Texture_ScreenNormalMap >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ScreenNormalMap , r_shadow_prepassgeometrynormalmaptexture ); if (r_glsl_permutation->tex_Texture_Cube >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Cube , rsurface.rtlight->currentcubemap ); if (r_glsl_permutation->tex_Texture_ShadowMap2D >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_ShadowMap2D , r_shadow_shadowmap2ddepthtexture ); if (r_glsl_permutation->tex_Texture_CubeProjection >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_CubeProjection , r_shadow_shadowmapvsdcttexture ); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: break; case RENDERPATH_SOFT: R_SetupShader_SetPermutationGLSL(mode, permutation); DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_LightPosition , viewlightorigin[0], viewlightorigin[1], viewlightorigin[2]); DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ViewToLightM1 , 1, false, viewtolight16f); DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Ambient , lightcolorbase[0] * ambientscale , lightcolorbase[1] * ambientscale , lightcolorbase[2] * ambientscale ); DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Diffuse , lightcolorbase[0] * diffusescale , lightcolorbase[1] * diffusescale , lightcolorbase[2] * diffusescale ); DPSOFTRAST_Uniform3f( DPSOFTRAST_UNIFORM_DeferredColor_Specular , lightcolorbase[0] * specularscale, lightcolorbase[1] * specularscale, lightcolorbase[2] * specularscale); DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ShadowMap_TextureScale , r_shadow_shadowmap_texturescale[0], r_shadow_shadowmap_texturescale[1]); DPSOFTRAST_Uniform4f( DPSOFTRAST_UNIFORM_ShadowMap_Parameters , r_shadow_shadowmap_parameters[0], r_shadow_shadowmap_parameters[1], r_shadow_shadowmap_parameters[2], r_shadow_shadowmap_parameters[3]); DPSOFTRAST_Uniform1f( DPSOFTRAST_UNIFORM_SpecularPower , (r_shadow_gloss.integer == 2 ? r_shadow_gloss2exponent.value : r_shadow_glossexponent.value) * (r_shadow_glossexact.integer ? 0.25f : 1.0f) - 1.0f); DPSOFTRAST_Uniform2f( DPSOFTRAST_UNIFORM_ScreenToDepth , r_refdef.view.viewport.screentodepth[0], r_refdef.view.viewport.screentodepth[1]); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); R_Mesh_TexBind(GL20TU_ATTENUATION , r_shadow_attenuationgradienttexture ); R_Mesh_TexBind(GL20TU_SCREENNORMALMAP , r_shadow_prepassgeometrynormalmaptexture ); R_Mesh_TexBind(GL20TU_CUBE , rsurface.rtlight->currentcubemap ); R_Mesh_TexBind(GL20TU_SHADOWMAP2D , r_shadow_shadowmap2ddepthtexture ); R_Mesh_TexBind(GL20TU_CUBEPROJECTION , r_shadow_shadowmapvsdcttexture ); break; } } #define SKINFRAME_HASH 1024 typedef struct { unsigned int loadsequence; // incremented each level change memexpandablearray_t array; skinframe_t *hash[SKINFRAME_HASH]; } r_skinframe_t; r_skinframe_t r_skinframe; void R_SkinFrame_PrepareForPurge(void) { r_skinframe.loadsequence++; // wrap it without hitting zero if (r_skinframe.loadsequence >= 200) r_skinframe.loadsequence = 1; } void R_SkinFrame_MarkUsed(skinframe_t *skinframe) { if (!skinframe) return; // mark the skinframe as used for the purging code skinframe->loadsequence = r_skinframe.loadsequence; } void R_SkinFrame_Purge(void) { int i; skinframe_t *s; for (i = 0;i < SKINFRAME_HASH;i++) { for (s = r_skinframe.hash[i];s;s = s->next) { if (s->loadsequence && s->loadsequence != r_skinframe.loadsequence) { if (s->merged == s->base) s->merged = NULL; // FIXME: maybe pass a pointer to the pointer to R_PurgeTexture and reset it to NULL inside? [11/29/2007 Black] R_PurgeTexture(s->stain );s->stain = NULL; R_PurgeTexture(s->merged);s->merged = NULL; R_PurgeTexture(s->base );s->base = NULL; R_PurgeTexture(s->pants );s->pants = NULL; R_PurgeTexture(s->shirt );s->shirt = NULL; R_PurgeTexture(s->nmap );s->nmap = NULL; R_PurgeTexture(s->gloss );s->gloss = NULL; R_PurgeTexture(s->glow );s->glow = NULL; R_PurgeTexture(s->fog );s->fog = NULL; R_PurgeTexture(s->reflect);s->reflect = NULL; s->loadsequence = 0; } } } } skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ) { skinframe_t *item; char basename[MAX_QPATH]; Image_StripImageExtension(name, basename, sizeof(basename)); if( last == NULL ) { int hashindex; hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); item = r_skinframe.hash[hashindex]; } else { item = last->next; } // linearly search through the hash bucket for( ; item ; item = item->next ) { if( !strcmp( item->basename, basename ) ) { return item; } } return NULL; } skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add) { skinframe_t *item; int hashindex; char basename[MAX_QPATH]; Image_StripImageExtension(name, basename, sizeof(basename)); hashindex = CRC_Block((unsigned char *)basename, strlen(basename)) & (SKINFRAME_HASH - 1); for (item = r_skinframe.hash[hashindex];item;item = item->next) if (!strcmp(item->basename, basename) && (comparecrc < 0 || (item->textureflags == textureflags && item->comparewidth == comparewidth && item->compareheight == compareheight && item->comparecrc == comparecrc))) break; if (!item) { rtexture_t *dyntexture; // check whether its a dynamic texture dyntexture = CL_GetDynTexture( basename ); if (!add && !dyntexture) return NULL; item = (skinframe_t *)Mem_ExpandableArray_AllocRecord(&r_skinframe.array); memset(item, 0, sizeof(*item)); strlcpy(item->basename, basename, sizeof(item->basename)); item->base = dyntexture; // either NULL or dyntexture handle item->textureflags = textureflags & ~TEXF_FORCE_RELOAD; item->comparewidth = comparewidth; item->compareheight = compareheight; item->comparecrc = comparecrc; item->next = r_skinframe.hash[hashindex]; r_skinframe.hash[hashindex] = item; } else if (textureflags & TEXF_FORCE_RELOAD) { rtexture_t *dyntexture; // check whether its a dynamic texture dyntexture = CL_GetDynTexture( basename ); if (!add && !dyntexture) return NULL; if (item->merged == item->base) item->merged = NULL; // FIXME: maybe pass a pointer to the pointer to R_PurgeTexture and reset it to NULL inside? [11/29/2007 Black] R_PurgeTexture(item->stain );item->stain = NULL; R_PurgeTexture(item->merged);item->merged = NULL; R_PurgeTexture(item->base );item->base = NULL; R_PurgeTexture(item->pants );item->pants = NULL; R_PurgeTexture(item->shirt );item->shirt = NULL; R_PurgeTexture(item->nmap );item->nmap = NULL; R_PurgeTexture(item->gloss );item->gloss = NULL; R_PurgeTexture(item->glow );item->glow = NULL; R_PurgeTexture(item->fog );item->fog = NULL; R_PurgeTexture(item->reflect);item->reflect = NULL; item->loadsequence = 0; } else if( item->base == NULL ) { rtexture_t *dyntexture; // check whether its a dynamic texture // this only needs to be done because Purge doesnt delete skinframes - only sets the texture pointers to NULL and we need to restore it before returing.. [11/29/2007 Black] dyntexture = CL_GetDynTexture( basename ); item->base = dyntexture; // either NULL or dyntexture handle } R_SkinFrame_MarkUsed(item); return item; } #define R_SKINFRAME_LOAD_AVERAGE_COLORS(cnt, getpixel) \ { \ unsigned long long avgcolor[5], wsum; \ int pix, comp, w; \ avgcolor[0] = 0; \ avgcolor[1] = 0; \ avgcolor[2] = 0; \ avgcolor[3] = 0; \ avgcolor[4] = 0; \ wsum = 0; \ for(pix = 0; pix < cnt; ++pix) \ { \ w = 0; \ for(comp = 0; comp < 3; ++comp) \ w += getpixel; \ if(w) /* ignore perfectly black pixels because that is better for model skins */ \ { \ ++wsum; \ /* comp = 3; -- not needed, comp is always 3 when we get here */ \ w = getpixel; \ for(comp = 0; comp < 3; ++comp) \ avgcolor[comp] += getpixel * w; \ avgcolor[3] += w; \ } \ /* comp = 3; -- not needed, comp is always 3 when we get here */ \ avgcolor[4] += getpixel; \ } \ if(avgcolor[3] == 0) /* no pixels seen? even worse */ \ avgcolor[3] = 1; \ skinframe->avgcolor[0] = avgcolor[2] / (255.0 * avgcolor[3]); \ skinframe->avgcolor[1] = avgcolor[1] / (255.0 * avgcolor[3]); \ skinframe->avgcolor[2] = avgcolor[0] / (255.0 * avgcolor[3]); \ skinframe->avgcolor[3] = avgcolor[4] / (255.0 * cnt); \ } extern cvar_t gl_picmip; skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain) { int j; unsigned char *pixels; unsigned char *bumppixels; unsigned char *basepixels = NULL; int basepixels_width = 0; int basepixels_height = 0; skinframe_t *skinframe; rtexture_t *ddsbase = NULL; qboolean ddshasalpha = false; float ddsavgcolor[4]; char basename[MAX_QPATH]; int miplevel = R_PicmipForFlags(textureflags); int savemiplevel = miplevel; int mymiplevel; char vabuf[1024]; if (cls.state == ca_dedicated) return NULL; // return an existing skinframe if already loaded // if loading of the first image fails, don't make a new skinframe as it // would cause all future lookups of this to be missing skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); if (skinframe && skinframe->base) return skinframe; Image_StripImageExtension(name, basename, sizeof(basename)); // check for DDS texture file first if (!r_loaddds || !(ddsbase = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", basename), vid.sRGB3D, textureflags, &ddshasalpha, ddsavgcolor, miplevel, false))) { basepixels = loadimagepixelsbgra(name, complain, true, false, &miplevel); if (basepixels == NULL) return NULL; } // FIXME handle miplevel if (developer_loading.integer) Con_Printf("loading skin \"%s\"\n", name); // we've got some pixels to store, so really allocate this new texture now if (!skinframe) skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, true); textureflags &= ~TEXF_FORCE_RELOAD; skinframe->stain = NULL; skinframe->merged = NULL; skinframe->base = NULL; skinframe->pants = NULL; skinframe->shirt = NULL; skinframe->nmap = NULL; skinframe->gloss = NULL; skinframe->glow = NULL; skinframe->fog = NULL; skinframe->reflect = NULL; skinframe->hasalpha = false; // we could store the q2animname here too if (ddsbase) { skinframe->base = ddsbase; skinframe->hasalpha = ddshasalpha; VectorCopy(ddsavgcolor, skinframe->avgcolor); if (r_loadfog && skinframe->hasalpha) skinframe->fog = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_mask.dds", skinframe->basename), false, textureflags | TEXF_ALPHA, NULL, NULL, miplevel, true); //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); } else { basepixels_width = image_width; basepixels_height = image_height; skinframe->base = R_LoadTexture2D (r_main_texturepool, skinframe->basename, basepixels_width, basepixels_height, basepixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); if (textureflags & TEXF_ALPHA) { for (j = 3;j < basepixels_width * basepixels_height * 4;j += 4) { if (basepixels[j] < 255) { skinframe->hasalpha = true; break; } } if (r_loadfog && skinframe->hasalpha) { // has transparent pixels pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); for (j = 0;j < image_width * image_height * 4;j += 4) { pixels[j+0] = 255; pixels[j+1] = 255; pixels[j+2] = 255; pixels[j+3] = basepixels[j+3]; } skinframe->fog = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_mask", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), miplevel, NULL); Mem_Free(pixels); } } R_SKINFRAME_LOAD_AVERAGE_COLORS(basepixels_width * basepixels_height, basepixels[4 * pix + comp]); #ifndef USE_GLES2 //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); if (r_savedds && qglGetCompressedTexImageARB && skinframe->base) R_SaveTextureDDSFile(skinframe->base, va(vabuf, sizeof(vabuf), "dds/%s.dds", skinframe->basename), r_texture_dds_save.integer < 2, skinframe->hasalpha); if (r_savedds && qglGetCompressedTexImageARB && skinframe->fog) R_SaveTextureDDSFile(skinframe->fog, va(vabuf, sizeof(vabuf), "dds/%s_mask.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); #endif } if (r_loaddds) { mymiplevel = savemiplevel; if (r_loadnormalmap) skinframe->nmap = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_norm.dds", skinframe->basename), false, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), NULL, NULL, mymiplevel, true); skinframe->glow = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_glow.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); if (r_loadgloss) skinframe->gloss = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_gloss.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); skinframe->pants = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_pants.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); skinframe->shirt = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_shirt.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); skinframe->reflect = R_LoadTextureDDSFile(r_main_texturepool, va(vabuf, sizeof(vabuf), "dds/%s_reflect.dds", skinframe->basename), vid.sRGB3D, textureflags, NULL, NULL, mymiplevel, true); } // _norm is the name used by tenebrae and has been adopted as standard if (r_loadnormalmap && skinframe->nmap == NULL) { mymiplevel = savemiplevel; if ((pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_norm", skinframe->basename), false, false, false, &mymiplevel)) != NULL) { skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); Mem_Free(pixels); pixels = NULL; } else if (r_shadow_bumpscale_bumpmap.value > 0 && (bumppixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_bump", skinframe->basename), false, false, false, &mymiplevel)) != NULL) { pixels = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); Image_HeightmapToNormalmap_BGRA(bumppixels, pixels, image_width, image_height, false, r_shadow_bumpscale_bumpmap.value); skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), image_width, image_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); Mem_Free(pixels); Mem_Free(bumppixels); } else if (r_shadow_bumpscale_basetexture.value > 0) { pixels = (unsigned char *)Mem_Alloc(tempmempool, basepixels_width * basepixels_height * 4); Image_HeightmapToNormalmap_BGRA(basepixels, pixels, basepixels_width, basepixels_height, false, r_shadow_bumpscale_basetexture.value); skinframe->nmap = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), basepixels_width, basepixels_height, pixels, TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP) & (gl_texturecompression_normal.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); Mem_Free(pixels); } #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->nmap) R_SaveTextureDDSFile(skinframe->nmap, va(vabuf, sizeof(vabuf), "dds/%s_norm.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); #endif } // _luma is supported only for tenebrae compatibility // _glow is the preferred name mymiplevel = savemiplevel; if (skinframe->glow == NULL && ((pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), false, false, false, &mymiplevel)) || (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_luma", skinframe->basename), false, false, false, &mymiplevel)))) { skinframe->glow = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_glow.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->glow) R_SaveTextureDDSFile(skinframe->glow, va(vabuf, sizeof(vabuf), "dds/%s_glow.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); #endif Mem_Free(pixels);pixels = NULL; } mymiplevel = savemiplevel; if (skinframe->gloss == NULL && r_loadgloss && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_gloss", skinframe->basename), false, false, false, &mymiplevel))) { skinframe->gloss = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_gloss", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, (TEXF_ALPHA | textureflags) & (gl_texturecompression_gloss.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->gloss) R_SaveTextureDDSFile(skinframe->gloss, va(vabuf, sizeof(vabuf), "dds/%s_gloss.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); #endif Mem_Free(pixels); pixels = NULL; } mymiplevel = savemiplevel; if (skinframe->pants == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), false, false, false, &mymiplevel))) { skinframe->pants = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->pants) R_SaveTextureDDSFile(skinframe->pants, va(vabuf, sizeof(vabuf), "dds/%s_pants.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); #endif Mem_Free(pixels); pixels = NULL; } mymiplevel = savemiplevel; if (skinframe->shirt == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), false, false, false, &mymiplevel))) { skinframe->shirt = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_color.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->shirt) R_SaveTextureDDSFile(skinframe->shirt, va(vabuf, sizeof(vabuf), "dds/%s_shirt.dds", skinframe->basename), r_texture_dds_save.integer < 2, false); #endif Mem_Free(pixels); pixels = NULL; } mymiplevel = savemiplevel; if (skinframe->reflect == NULL && (pixels = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s_reflect", skinframe->basename), false, false, false, &mymiplevel))) { skinframe->reflect = R_LoadTexture2D (r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_reflect", skinframe->basename), image_width, image_height, pixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags & (gl_texturecompression_reflectmask.integer && gl_texturecompression.integer ? ~0 : ~TEXF_COMPRESS), mymiplevel, NULL); #ifndef USE_GLES2 if (r_savedds && qglGetCompressedTexImageARB && skinframe->reflect) R_SaveTextureDDSFile(skinframe->reflect, va(vabuf, sizeof(vabuf), "dds/%s_reflect.dds", skinframe->basename), r_texture_dds_save.integer < 2, true); #endif Mem_Free(pixels); pixels = NULL; } if (basepixels) Mem_Free(basepixels); return skinframe; } // this is only used by .spr32 sprites, HL .spr files, HL .bsp files skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB) { int i; skinframe_t *skinframe; char vabuf[1024]; if (cls.state == ca_dedicated) return NULL; // if already loaded just return it, otherwise make a new skinframe skinframe = R_SkinFrame_Find(name, textureflags, width, height, (textureflags & TEXF_FORCE_RELOAD) ? -1 : skindata ? CRC_Block(skindata, width*height*4) : 0, true); if (skinframe->base) return skinframe; textureflags &= ~TEXF_FORCE_RELOAD; skinframe->stain = NULL; skinframe->merged = NULL; skinframe->base = NULL; skinframe->pants = NULL; skinframe->shirt = NULL; skinframe->nmap = NULL; skinframe->gloss = NULL; skinframe->glow = NULL; skinframe->fog = NULL; skinframe->reflect = NULL; skinframe->hasalpha = false; // if no data was provided, then clearly the caller wanted to get a blank skinframe if (!skindata) return NULL; if (developer_loading.integer) Con_Printf("loading 32bit skin \"%s\"\n", name); if (r_loadnormalmap && r_shadow_bumpscale_basetexture.value > 0) { unsigned char *a = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); unsigned char *b = a + width * height * 4; Image_HeightmapToNormalmap_BGRA(skindata, b, width, height, false, r_shadow_bumpscale_basetexture.value); skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), width, height, b, TEXTYPE_BGRA, (textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); Mem_Free(a); } skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, sRGB ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, textureflags, -1, NULL); if (textureflags & TEXF_ALPHA) { for (i = 3;i < width * height * 4;i += 4) { if (skindata[i] < 255) { skinframe->hasalpha = true; break; } } if (r_loadfog && skinframe->hasalpha) { unsigned char *fogpixels = (unsigned char *)Mem_Alloc(tempmempool, width * height * 4); memcpy(fogpixels, skindata, width * height * 4); for (i = 0;i < width * height * 4;i += 4) fogpixels[i] = fogpixels[i+1] = fogpixels[i+2] = 255; skinframe->fog = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_fog", skinframe->basename), width, height, fogpixels, TEXTYPE_BGRA, textureflags, -1, NULL); Mem_Free(fogpixels); } } R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, skindata[4 * pix + comp]); //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); return skinframe; } skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height) { int i; int featuresmask; skinframe_t *skinframe; if (cls.state == ca_dedicated) return NULL; // if already loaded just return it, otherwise make a new skinframe skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); if (skinframe->base) return skinframe; //textureflags &= ~TEXF_FORCE_RELOAD; skinframe->stain = NULL; skinframe->merged = NULL; skinframe->base = NULL; skinframe->pants = NULL; skinframe->shirt = NULL; skinframe->nmap = NULL; skinframe->gloss = NULL; skinframe->glow = NULL; skinframe->fog = NULL; skinframe->reflect = NULL; skinframe->hasalpha = false; // if no data was provided, then clearly the caller wanted to get a blank skinframe if (!skindata) return NULL; if (developer_loading.integer) Con_Printf("loading quake skin \"%s\"\n", name); // we actually don't upload anything until the first use, because mdl skins frequently go unused, and are almost never used in both modes (colormapped and non-colormapped) skinframe->qpixels = (unsigned char *)Mem_Alloc(r_main_mempool, width*height); // FIXME LEAK memcpy(skinframe->qpixels, skindata, width*height); skinframe->qwidth = width; skinframe->qheight = height; featuresmask = 0; for (i = 0;i < width * height;i++) featuresmask |= palette_featureflags[skindata[i]]; skinframe->hasalpha = false; // fence textures if (name[0] == '{') skinframe->hasalpha = true; skinframe->qhascolormapping = loadpantsandshirt && (featuresmask & (PALETTEFEATURE_PANTS | PALETTEFEATURE_SHIRT)); skinframe->qgeneratenmap = r_shadow_bumpscale_basetexture.value > 0; skinframe->qgeneratemerged = true; skinframe->qgeneratebase = skinframe->qhascolormapping; skinframe->qgenerateglow = loadglowtexture && (featuresmask & PALETTEFEATURE_GLOW); R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette_bgra_complete)[skindata[pix]*4 + comp]); //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); return skinframe; } static void R_SkinFrame_GenerateTexturesFromQPixels(skinframe_t *skinframe, qboolean colormapped) { int width; int height; unsigned char *skindata; char vabuf[1024]; if (!skinframe->qpixels) return; if (!skinframe->qhascolormapping) colormapped = false; if (colormapped) { if (!skinframe->qgeneratebase) return; } else { if (!skinframe->qgeneratemerged) return; } width = skinframe->qwidth; height = skinframe->qheight; skindata = skinframe->qpixels; if (skinframe->qgeneratenmap) { unsigned char *a, *b; skinframe->qgeneratenmap = false; a = (unsigned char *)Mem_Alloc(tempmempool, width * height * 8); b = a + width * height * 4; // use either a custom palette or the quake palette Image_Copy8bitBGRA(skindata, a, width * height, palette_bgra_complete); Image_HeightmapToNormalmap_BGRA(a, b, width, height, false, r_shadow_bumpscale_basetexture.value); skinframe->nmap = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nmap", skinframe->basename), width, height, b, TEXTYPE_BGRA, (skinframe->textureflags | TEXF_ALPHA) & (r_mipnormalmaps.integer ? ~0 : ~TEXF_MIPMAP), -1, NULL); Mem_Free(a); } if (skinframe->qgenerateglow) { skinframe->qgenerateglow = false; if (skinframe->hasalpha) // fence textures skinframe->glow = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags | TEXF_ALPHA, -1, palette_bgra_onlyfullbrights_transparent); // glow else skinframe->glow = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_glow", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_onlyfullbrights); // glow } if (colormapped) { skinframe->qgeneratebase = false; skinframe->base = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_nospecial", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nocolormapnofullbrights : palette_bgra_nocolormap); skinframe->pants = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_pants", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_pantsaswhite); skinframe->shirt = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_shirt", skinframe->basename), width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, palette_bgra_shirtaswhite); } else { skinframe->qgeneratemerged = false; if (skinframe->hasalpha) // fence textures skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags | TEXF_ALPHA, -1, skinframe->glow ? palette_bgra_nofullbrights_transparent : palette_bgra_transparent); else skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, vid.sRGB3D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, skinframe->textureflags, -1, skinframe->glow ? palette_bgra_nofullbrights : palette_bgra_complete); } if (!skinframe->qgeneratemerged && !skinframe->qgeneratebase) { Mem_Free(skinframe->qpixels); skinframe->qpixels = NULL; } } skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette) { int i; skinframe_t *skinframe; char vabuf[1024]; if (cls.state == ca_dedicated) return NULL; // if already loaded just return it, otherwise make a new skinframe skinframe = R_SkinFrame_Find(name, textureflags, width, height, skindata ? CRC_Block(skindata, width*height) : 0, true); if (skinframe->base) return skinframe; textureflags &= ~TEXF_FORCE_RELOAD; skinframe->stain = NULL; skinframe->merged = NULL; skinframe->base = NULL; skinframe->pants = NULL; skinframe->shirt = NULL; skinframe->nmap = NULL; skinframe->gloss = NULL; skinframe->glow = NULL; skinframe->fog = NULL; skinframe->reflect = NULL; skinframe->hasalpha = false; // if no data was provided, then clearly the caller wanted to get a blank skinframe if (!skindata) return NULL; if (developer_loading.integer) Con_Printf("loading embedded 8bit image \"%s\"\n", name); skinframe->base = skinframe->merged = R_LoadTexture2D(r_main_texturepool, skinframe->basename, width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, palette); if (textureflags & TEXF_ALPHA) { for (i = 0;i < width * height;i++) { if (((unsigned char *)palette)[skindata[i]*4+3] < 255) { skinframe->hasalpha = true; break; } } if (r_loadfog && skinframe->hasalpha) skinframe->fog = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "%s_fog", skinframe->basename), width, height, skindata, TEXTYPE_PALETTE, textureflags, -1, alphapalette); } R_SKINFRAME_LOAD_AVERAGE_COLORS(width * height, ((unsigned char *)palette)[skindata[pix]*4 + comp]); //Con_Printf("Texture %s has average colors %f %f %f alpha %f\n", name, skinframe->avgcolor[0], skinframe->avgcolor[1], skinframe->avgcolor[2], skinframe->avgcolor[3]); return skinframe; } skinframe_t *R_SkinFrame_LoadMissing(void) { skinframe_t *skinframe; if (cls.state == ca_dedicated) return NULL; skinframe = R_SkinFrame_Find("missing", TEXF_FORCENEAREST, 0, 0, 0, true); skinframe->stain = NULL; skinframe->merged = NULL; skinframe->base = NULL; skinframe->pants = NULL; skinframe->shirt = NULL; skinframe->nmap = NULL; skinframe->gloss = NULL; skinframe->glow = NULL; skinframe->fog = NULL; skinframe->reflect = NULL; skinframe->hasalpha = false; skinframe->avgcolor[0] = rand() / RAND_MAX; skinframe->avgcolor[1] = rand() / RAND_MAX; skinframe->avgcolor[2] = rand() / RAND_MAX; skinframe->avgcolor[3] = 1; return skinframe; } //static char *suffix[6] = {"ft", "bk", "rt", "lf", "up", "dn"}; typedef struct suffixinfo_s { const char *suffix; qboolean flipx, flipy, flipdiagonal; } suffixinfo_t; static suffixinfo_t suffix[3][6] = { { {"px", false, false, false}, {"nx", false, false, false}, {"py", false, false, false}, {"ny", false, false, false}, {"pz", false, false, false}, {"nz", false, false, false} }, { {"posx", false, false, false}, {"negx", false, false, false}, {"posy", false, false, false}, {"negy", false, false, false}, {"posz", false, false, false}, {"negz", false, false, false} }, { {"rt", true, false, true}, {"lf", false, true, true}, {"ft", true, true, false}, {"bk", false, false, false}, {"up", true, false, true}, {"dn", true, false, true} } }; static int componentorder[4] = {0, 1, 2, 3}; static rtexture_t *R_LoadCubemap(const char *basename) { int i, j, cubemapsize; unsigned char *cubemappixels, *image_buffer; rtexture_t *cubemaptexture; char name[256]; // must start 0 so the first loadimagepixels has no requested width/height cubemapsize = 0; cubemappixels = NULL; cubemaptexture = NULL; // keep trying different suffix groups (posx, px, rt) until one loads for (j = 0;j < 3 && !cubemappixels;j++) { // load the 6 images in the suffix group for (i = 0;i < 6;i++) { // generate an image name based on the base and and suffix dpsnprintf(name, sizeof(name), "%s%s", basename, suffix[j][i].suffix); // load it if ((image_buffer = loadimagepixelsbgra(name, false, false, false, NULL))) { // an image loaded, make sure width and height are equal if (image_width == image_height && (!cubemappixels || image_width == cubemapsize)) { // if this is the first image to load successfully, allocate the cubemap memory if (!cubemappixels && image_width >= 1) { cubemapsize = image_width; // note this clears to black, so unavailable sides are black cubemappixels = (unsigned char *)Mem_Alloc(tempmempool, 6*cubemapsize*cubemapsize*4); } // copy the image with any flipping needed by the suffix (px and posx types don't need flipping) if (cubemappixels) Image_CopyMux(cubemappixels+i*cubemapsize*cubemapsize*4, image_buffer, cubemapsize, cubemapsize, suffix[j][i].flipx, suffix[j][i].flipy, suffix[j][i].flipdiagonal, 4, 4, componentorder); } else Con_Printf("Cubemap image \"%s\" (%ix%i) is not square, OpenGL requires square cubemaps.\n", name, image_width, image_height); // free the image Mem_Free(image_buffer); } } } // if a cubemap loaded, upload it if (cubemappixels) { if (developer_loading.integer) Con_Printf("loading cubemap \"%s\"\n", basename); cubemaptexture = R_LoadTextureCubeMap(r_main_texturepool, basename, cubemapsize, cubemappixels, vid.sRGB3D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, (gl_texturecompression_lightcubemaps.integer && gl_texturecompression.integer ? TEXF_COMPRESS : 0) | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); Mem_Free(cubemappixels); } else { Con_DPrintf("failed to load cubemap \"%s\"\n", basename); if (developer_loading.integer) { Con_Printf("(tried tried images "); for (j = 0;j < 3;j++) for (i = 0;i < 6;i++) Con_Printf("%s\"%s%s.tga\"", j + i > 0 ? ", " : "", basename, suffix[j][i].suffix); Con_Print(" and was unable to find any of them).\n"); } } return cubemaptexture; } rtexture_t *R_GetCubemap(const char *basename) { int i; for (i = 0;i < r_texture_numcubemaps;i++) if (r_texture_cubemaps[i] != NULL) if (!strcasecmp(r_texture_cubemaps[i]->basename, basename)) return r_texture_cubemaps[i]->texture ? r_texture_cubemaps[i]->texture : r_texture_whitecube; if (i >= MAX_CUBEMAPS || !r_main_mempool) return r_texture_whitecube; r_texture_numcubemaps++; r_texture_cubemaps[i] = (cubemapinfo_t *)Mem_Alloc(r_main_mempool, sizeof(cubemapinfo_t)); strlcpy(r_texture_cubemaps[i]->basename, basename, sizeof(r_texture_cubemaps[i]->basename)); r_texture_cubemaps[i]->texture = R_LoadCubemap(r_texture_cubemaps[i]->basename); return r_texture_cubemaps[i]->texture; } static void R_Main_FreeViewCache(void) { if (r_refdef.viewcache.entityvisible) Mem_Free(r_refdef.viewcache.entityvisible); if (r_refdef.viewcache.world_pvsbits) Mem_Free(r_refdef.viewcache.world_pvsbits); if (r_refdef.viewcache.world_leafvisible) Mem_Free(r_refdef.viewcache.world_leafvisible); if (r_refdef.viewcache.world_surfacevisible) Mem_Free(r_refdef.viewcache.world_surfacevisible); memset(&r_refdef.viewcache, 0, sizeof(r_refdef.viewcache)); } static void R_Main_ResizeViewCache(void) { int numentities = r_refdef.scene.numentities; int numclusters = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusters : 1; int numclusterbytes = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_pvsclusterbytes : 1; int numleafs = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->brush.num_leafs : 1; int numsurfaces = r_refdef.scene.worldmodel ? r_refdef.scene.worldmodel->num_surfaces : 1; if (r_refdef.viewcache.maxentities < numentities) { r_refdef.viewcache.maxentities = numentities; if (r_refdef.viewcache.entityvisible) Mem_Free(r_refdef.viewcache.entityvisible); r_refdef.viewcache.entityvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.maxentities); } if (r_refdef.viewcache.world_numclusters != numclusters) { r_refdef.viewcache.world_numclusters = numclusters; r_refdef.viewcache.world_numclusterbytes = numclusterbytes; if (r_refdef.viewcache.world_pvsbits) Mem_Free(r_refdef.viewcache.world_pvsbits); r_refdef.viewcache.world_pvsbits = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numclusterbytes); } if (r_refdef.viewcache.world_numleafs != numleafs) { r_refdef.viewcache.world_numleafs = numleafs; if (r_refdef.viewcache.world_leafvisible) Mem_Free(r_refdef.viewcache.world_leafvisible); r_refdef.viewcache.world_leafvisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numleafs); } if (r_refdef.viewcache.world_numsurfaces != numsurfaces) { r_refdef.viewcache.world_numsurfaces = numsurfaces; if (r_refdef.viewcache.world_surfacevisible) Mem_Free(r_refdef.viewcache.world_surfacevisible); r_refdef.viewcache.world_surfacevisible = (unsigned char *)Mem_Alloc(r_main_mempool, r_refdef.viewcache.world_numsurfaces); } } extern rtexture_t *loadingscreentexture; static void gl_main_start(void) { loadingscreentexture = NULL; r_texture_blanknormalmap = NULL; r_texture_white = NULL; r_texture_grey128 = NULL; r_texture_black = NULL; r_texture_whitecube = NULL; r_texture_normalizationcube = NULL; r_texture_fogattenuation = NULL; r_texture_fogheighttexture = NULL; r_texture_gammaramps = NULL; r_texture_numcubemaps = 0; r_uniformbufferalignment = 32; r_loaddds = r_texture_dds_load.integer != 0; r_savedds = vid.support.arb_texture_compression && vid.support.ext_texture_compression_s3tc && r_texture_dds_save.integer; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: Cvar_SetValueQuick(&r_textureunits, vid.texunits); Cvar_SetValueQuick(&gl_combine, 1); Cvar_SetValueQuick(&r_glsl, 1); r_loadnormalmap = true; r_loadgloss = true; r_loadfog = false; #ifdef GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT if (vid.support.arb_uniform_buffer_object) qglGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &r_uniformbufferalignment); #endif break; case RENDERPATH_GL13: case RENDERPATH_GLES1: Cvar_SetValueQuick(&r_textureunits, vid.texunits); Cvar_SetValueQuick(&gl_combine, 1); Cvar_SetValueQuick(&r_glsl, 0); r_loadnormalmap = false; r_loadgloss = false; r_loadfog = true; break; case RENDERPATH_GL11: Cvar_SetValueQuick(&r_textureunits, vid.texunits); Cvar_SetValueQuick(&gl_combine, 0); Cvar_SetValueQuick(&r_glsl, 0); r_loadnormalmap = false; r_loadgloss = false; r_loadfog = true; break; } R_AnimCache_Free(); R_FrameData_Reset(); R_BufferData_Reset(); r_numqueries = 0; r_maxqueries = 0; memset(r_queries, 0, sizeof(r_queries)); r_qwskincache = NULL; r_qwskincache_size = 0; // due to caching of texture_t references, the collision cache must be reset Collision_Cache_Reset(true); // set up r_skinframe loading system for textures memset(&r_skinframe, 0, sizeof(r_skinframe)); r_skinframe.loadsequence = 1; Mem_ExpandableArray_NewArray(&r_skinframe.array, r_main_mempool, sizeof(skinframe_t), 256); r_main_texturepool = R_AllocTexturePool(); R_BuildBlankTextures(); R_BuildNoTexture(); if (vid.support.arb_texture_cube_map) { R_BuildWhiteCube(); R_BuildNormalizationCube(); } r_texture_fogattenuation = NULL; r_texture_fogheighttexture = NULL; r_texture_gammaramps = NULL; //r_texture_fogintensity = NULL; memset(&r_fb, 0, sizeof(r_fb)); r_glsl_permutation = NULL; memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); Mem_ExpandableArray_NewArray(&r_glsl_permutationarray, r_main_mempool, sizeof(r_glsl_permutation_t), 256); glslshaderstring = NULL; #ifdef SUPPORTD3D r_hlsl_permutation = NULL; memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); Mem_ExpandableArray_NewArray(&r_hlsl_permutationarray, r_main_mempool, sizeof(r_hlsl_permutation_t), 256); #endif hlslshaderstring = NULL; memset(&r_svbsp, 0, sizeof (r_svbsp)); memset(r_texture_cubemaps, 0, sizeof(r_texture_cubemaps)); r_texture_numcubemaps = 0; r_refdef.fogmasktable_density = 0; #ifdef __ANDROID__ // For Steelstorm Android // FIXME CACHE the program and reload // FIXME see possible combinations for SS:BR android Con_DPrintf("Compiling most used shaders for SS:BR android... START\n"); R_SetupShader_SetPermutationGLSL(0, 12); R_SetupShader_SetPermutationGLSL(0, 13); R_SetupShader_SetPermutationGLSL(0, 8388621); R_SetupShader_SetPermutationGLSL(3, 0); R_SetupShader_SetPermutationGLSL(3, 2048); R_SetupShader_SetPermutationGLSL(5, 0); R_SetupShader_SetPermutationGLSL(5, 2); R_SetupShader_SetPermutationGLSL(5, 2048); R_SetupShader_SetPermutationGLSL(5, 8388608); R_SetupShader_SetPermutationGLSL(11, 1); R_SetupShader_SetPermutationGLSL(11, 2049); R_SetupShader_SetPermutationGLSL(11, 8193); R_SetupShader_SetPermutationGLSL(11, 10241); Con_DPrintf("Compiling most used shaders for SS:BR android... END\n"); #endif } static void gl_main_shutdown(void) { R_AnimCache_Free(); R_FrameData_Reset(); R_BufferData_Reset(); R_Main_FreeViewCache(); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) if (r_maxqueries) qglDeleteQueriesARB(r_maxqueries, r_queries); #endif break; case RENDERPATH_D3D9: //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } r_numqueries = 0; r_maxqueries = 0; memset(r_queries, 0, sizeof(r_queries)); r_qwskincache = NULL; r_qwskincache_size = 0; // clear out the r_skinframe state Mem_ExpandableArray_FreeArray(&r_skinframe.array); memset(&r_skinframe, 0, sizeof(r_skinframe)); if (r_svbsp.nodes) Mem_Free(r_svbsp.nodes); memset(&r_svbsp, 0, sizeof (r_svbsp)); R_FreeTexturePool(&r_main_texturepool); loadingscreentexture = NULL; r_texture_blanknormalmap = NULL; r_texture_white = NULL; r_texture_grey128 = NULL; r_texture_black = NULL; r_texture_whitecube = NULL; r_texture_normalizationcube = NULL; r_texture_fogattenuation = NULL; r_texture_fogheighttexture = NULL; r_texture_gammaramps = NULL; r_texture_numcubemaps = 0; //r_texture_fogintensity = NULL; memset(&r_fb, 0, sizeof(r_fb)); R_GLSL_Restart_f(); r_glsl_permutation = NULL; memset(r_glsl_permutationhash, 0, sizeof(r_glsl_permutationhash)); Mem_ExpandableArray_FreeArray(&r_glsl_permutationarray); glslshaderstring = NULL; #ifdef SUPPORTD3D r_hlsl_permutation = NULL; memset(r_hlsl_permutationhash, 0, sizeof(r_hlsl_permutationhash)); Mem_ExpandableArray_FreeArray(&r_hlsl_permutationarray); #endif hlslshaderstring = NULL; } static void gl_main_newmap(void) { // FIXME: move this code to client char *entities, entname[MAX_QPATH]; if (r_qwskincache) Mem_Free(r_qwskincache); r_qwskincache = NULL; r_qwskincache_size = 0; if (cl.worldmodel) { dpsnprintf(entname, sizeof(entname), "%s.ent", cl.worldnamenoextension); if ((entities = (char *)FS_LoadFile(entname, tempmempool, true, NULL))) { CL_ParseEntityLump(entities); Mem_Free(entities); return; } if (cl.worldmodel->brush.entities) CL_ParseEntityLump(cl.worldmodel->brush.entities); } R_Main_FreeViewCache(); R_FrameData_Reset(); R_BufferData_Reset(); } void GL_Main_Init(void) { int i; r_main_mempool = Mem_AllocPool("Renderer", 0, NULL); Cmd_AddCommand("r_glsl_restart", R_GLSL_Restart_f, "unloads GLSL shaders, they will then be reloaded as needed"); Cmd_AddCommand("r_glsl_dumpshader", R_GLSL_DumpShader_f, "dumps the engine internal default.glsl shader into glsl/default.glsl"); // FIXME: the client should set up r_refdef.fog stuff including the fogmasktable if (gamemode == GAME_NEHAHRA) { Cvar_RegisterVariable (&gl_fogenable); Cvar_RegisterVariable (&gl_fogdensity); Cvar_RegisterVariable (&gl_fogred); Cvar_RegisterVariable (&gl_foggreen); Cvar_RegisterVariable (&gl_fogblue); Cvar_RegisterVariable (&gl_fogstart); Cvar_RegisterVariable (&gl_fogend); Cvar_RegisterVariable (&gl_skyclip); } Cvar_RegisterVariable(&r_motionblur); Cvar_RegisterVariable(&r_damageblur); Cvar_RegisterVariable(&r_motionblur_averaging); Cvar_RegisterVariable(&r_motionblur_randomize); Cvar_RegisterVariable(&r_motionblur_minblur); Cvar_RegisterVariable(&r_motionblur_maxblur); Cvar_RegisterVariable(&r_motionblur_velocityfactor); Cvar_RegisterVariable(&r_motionblur_velocityfactor_minspeed); Cvar_RegisterVariable(&r_motionblur_velocityfactor_maxspeed); Cvar_RegisterVariable(&r_motionblur_mousefactor); Cvar_RegisterVariable(&r_motionblur_mousefactor_minspeed); Cvar_RegisterVariable(&r_motionblur_mousefactor_maxspeed); Cvar_RegisterVariable(&r_equalize_entities_fullbright); Cvar_RegisterVariable(&r_equalize_entities_minambient); Cvar_RegisterVariable(&r_equalize_entities_by); Cvar_RegisterVariable(&r_equalize_entities_to); Cvar_RegisterVariable(&r_depthfirst); Cvar_RegisterVariable(&r_useinfinitefarclip); Cvar_RegisterVariable(&r_farclip_base); Cvar_RegisterVariable(&r_farclip_world); Cvar_RegisterVariable(&r_nearclip); Cvar_RegisterVariable(&r_deformvertexes); Cvar_RegisterVariable(&r_transparent); Cvar_RegisterVariable(&r_transparent_alphatocoverage); Cvar_RegisterVariable(&r_transparent_sortsurfacesbynearest); Cvar_RegisterVariable(&r_transparent_useplanardistance); Cvar_RegisterVariable(&r_showoverdraw); Cvar_RegisterVariable(&r_showbboxes); Cvar_RegisterVariable(&r_showsurfaces); Cvar_RegisterVariable(&r_showtris); Cvar_RegisterVariable(&r_shownormals); Cvar_RegisterVariable(&r_showlighting); Cvar_RegisterVariable(&r_showshadowvolumes); Cvar_RegisterVariable(&r_showcollisionbrushes); Cvar_RegisterVariable(&r_showcollisionbrushes_polygonfactor); Cvar_RegisterVariable(&r_showcollisionbrushes_polygonoffset); Cvar_RegisterVariable(&r_showdisabledepthtest); Cvar_RegisterVariable(&r_drawportals); Cvar_RegisterVariable(&r_drawentities); Cvar_RegisterVariable(&r_draw2d); Cvar_RegisterVariable(&r_drawworld); Cvar_RegisterVariable(&r_cullentities_trace); Cvar_RegisterVariable(&r_cullentities_trace_samples); Cvar_RegisterVariable(&r_cullentities_trace_tempentitysamples); Cvar_RegisterVariable(&r_cullentities_trace_enlarge); Cvar_RegisterVariable(&r_cullentities_trace_delay); Cvar_RegisterVariable(&r_sortentities); Cvar_RegisterVariable(&r_drawviewmodel); Cvar_RegisterVariable(&r_drawexteriormodel); Cvar_RegisterVariable(&r_speeds); Cvar_RegisterVariable(&r_fullbrights); Cvar_RegisterVariable(&r_wateralpha); Cvar_RegisterVariable(&r_dynamic); Cvar_RegisterVariable(&r_fakelight); Cvar_RegisterVariable(&r_fakelight_intensity); Cvar_RegisterVariable(&r_fullbright); Cvar_RegisterVariable(&r_shadows); Cvar_RegisterVariable(&r_shadows_darken); Cvar_RegisterVariable(&r_shadows_drawafterrtlighting); Cvar_RegisterVariable(&r_shadows_castfrombmodels); Cvar_RegisterVariable(&r_shadows_throwdistance); Cvar_RegisterVariable(&r_shadows_throwdirection); Cvar_RegisterVariable(&r_shadows_focus); Cvar_RegisterVariable(&r_shadows_shadowmapscale); Cvar_RegisterVariable(&r_shadows_shadowmapbias); Cvar_RegisterVariable(&r_q1bsp_skymasking); Cvar_RegisterVariable(&r_polygonoffset_submodel_factor); Cvar_RegisterVariable(&r_polygonoffset_submodel_offset); Cvar_RegisterVariable(&r_polygonoffset_decals_factor); Cvar_RegisterVariable(&r_polygonoffset_decals_offset); Cvar_RegisterVariable(&r_fog_exp2); Cvar_RegisterVariable(&r_fog_clear); Cvar_RegisterVariable(&r_drawfog); Cvar_RegisterVariable(&r_transparentdepthmasking); Cvar_RegisterVariable(&r_transparent_sortmindist); Cvar_RegisterVariable(&r_transparent_sortmaxdist); Cvar_RegisterVariable(&r_transparent_sortarraysize); Cvar_RegisterVariable(&r_texture_dds_load); Cvar_RegisterVariable(&r_texture_dds_save); Cvar_RegisterVariable(&r_textureunits); Cvar_RegisterVariable(&gl_combine); Cvar_RegisterVariable(&r_usedepthtextures); Cvar_RegisterVariable(&r_viewfbo); Cvar_RegisterVariable(&r_viewscale); Cvar_RegisterVariable(&r_viewscale_fpsscaling); Cvar_RegisterVariable(&r_viewscale_fpsscaling_min); Cvar_RegisterVariable(&r_viewscale_fpsscaling_multiply); Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepsize); Cvar_RegisterVariable(&r_viewscale_fpsscaling_stepmax); Cvar_RegisterVariable(&r_viewscale_fpsscaling_target); Cvar_RegisterVariable(&r_glsl); Cvar_RegisterVariable(&r_glsl_deluxemapping); Cvar_RegisterVariable(&r_glsl_offsetmapping); Cvar_RegisterVariable(&r_glsl_offsetmapping_steps); Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping); Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_steps); Cvar_RegisterVariable(&r_glsl_offsetmapping_reliefmapping_refinesteps); Cvar_RegisterVariable(&r_glsl_offsetmapping_scale); Cvar_RegisterVariable(&r_glsl_offsetmapping_lod); Cvar_RegisterVariable(&r_glsl_offsetmapping_lod_distance); Cvar_RegisterVariable(&r_glsl_postprocess); Cvar_RegisterVariable(&r_glsl_postprocess_uservec1); Cvar_RegisterVariable(&r_glsl_postprocess_uservec2); Cvar_RegisterVariable(&r_glsl_postprocess_uservec3); Cvar_RegisterVariable(&r_glsl_postprocess_uservec4); Cvar_RegisterVariable(&r_glsl_postprocess_uservec1_enable); Cvar_RegisterVariable(&r_glsl_postprocess_uservec2_enable); Cvar_RegisterVariable(&r_glsl_postprocess_uservec3_enable); Cvar_RegisterVariable(&r_glsl_postprocess_uservec4_enable); Cvar_RegisterVariable(&r_celshading); Cvar_RegisterVariable(&r_celoutlines); Cvar_RegisterVariable(&r_water); Cvar_RegisterVariable(&r_water_cameraentitiesonly); Cvar_RegisterVariable(&r_water_resolutionmultiplier); Cvar_RegisterVariable(&r_water_clippingplanebias); Cvar_RegisterVariable(&r_water_refractdistort); Cvar_RegisterVariable(&r_water_reflectdistort); Cvar_RegisterVariable(&r_water_scissormode); Cvar_RegisterVariable(&r_water_lowquality); Cvar_RegisterVariable(&r_water_hideplayer); Cvar_RegisterVariable(&r_water_fbo); Cvar_RegisterVariable(&r_lerpsprites); Cvar_RegisterVariable(&r_lerpmodels); Cvar_RegisterVariable(&r_lerplightstyles); Cvar_RegisterVariable(&r_waterscroll); Cvar_RegisterVariable(&r_bloom); Cvar_RegisterVariable(&r_bloom_colorscale); Cvar_RegisterVariable(&r_bloom_brighten); Cvar_RegisterVariable(&r_bloom_blur); Cvar_RegisterVariable(&r_bloom_resolution); Cvar_RegisterVariable(&r_bloom_colorexponent); Cvar_RegisterVariable(&r_bloom_colorsubtract); Cvar_RegisterVariable(&r_bloom_scenebrightness); Cvar_RegisterVariable(&r_hdr_scenebrightness); Cvar_RegisterVariable(&r_hdr_glowintensity); Cvar_RegisterVariable(&r_hdr_irisadaptation); Cvar_RegisterVariable(&r_hdr_irisadaptation_multiplier); Cvar_RegisterVariable(&r_hdr_irisadaptation_minvalue); Cvar_RegisterVariable(&r_hdr_irisadaptation_maxvalue); Cvar_RegisterVariable(&r_hdr_irisadaptation_value); Cvar_RegisterVariable(&r_hdr_irisadaptation_fade_up); Cvar_RegisterVariable(&r_hdr_irisadaptation_fade_down); Cvar_RegisterVariable(&r_hdr_irisadaptation_radius); Cvar_RegisterVariable(&r_smoothnormals_areaweighting); Cvar_RegisterVariable(&developer_texturelogging); Cvar_RegisterVariable(&gl_lightmaps); Cvar_RegisterVariable(&r_test); Cvar_RegisterVariable(&r_batch_multidraw); Cvar_RegisterVariable(&r_batch_multidraw_mintriangles); Cvar_RegisterVariable(&r_batch_debugdynamicvertexpath); Cvar_RegisterVariable(&r_glsl_skeletal); Cvar_RegisterVariable(&r_glsl_saturation); Cvar_RegisterVariable(&r_glsl_saturation_redcompensate); Cvar_RegisterVariable(&r_glsl_vertextextureblend_usebothalphas); Cvar_RegisterVariable(&r_framedatasize); for (i = 0;i < R_BUFFERDATA_COUNT;i++) Cvar_RegisterVariable(&r_buffermegs[i]); Cvar_RegisterVariable(&r_batch_dynamicbuffer); if (gamemode == GAME_NEHAHRA || gamemode == GAME_TENEBRAE) Cvar_SetValue("r_fullbrights", 0); #ifdef DP_MOBILETOUCH // GLES devices have terrible depth precision in general, so... Cvar_SetValueQuick(&r_nearclip, 4); Cvar_SetValueQuick(&r_farclip_base, 4096); Cvar_SetValueQuick(&r_farclip_world, 0); Cvar_SetValueQuick(&r_useinfinitefarclip, 0); #endif R_RegisterModule("GL_Main", gl_main_start, gl_main_shutdown, gl_main_newmap, NULL, NULL); } void Render_Init(void) { gl_backend_init(); R_Textures_Init(); GL_Main_Init(); Font_Init(); GL_Draw_Init(); R_Shadow_Init(); R_Sky_Init(); GL_Surf_Init(); Sbar_Init(); R_Particles_Init(); R_Explosion_Init(); R_LightningBeams_Init(); Mod_RenderInit(); } /* =============== GL_Init =============== */ #ifndef USE_GLES2 extern char *ENGINE_EXTENSIONS; void GL_Init (void) { gl_renderer = (const char *)qglGetString(GL_RENDERER); gl_vendor = (const char *)qglGetString(GL_VENDOR); gl_version = (const char *)qglGetString(GL_VERSION); gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); if (!gl_extensions) gl_extensions = ""; if (!gl_platformextensions) gl_platformextensions = ""; Con_Printf("GL_VENDOR: %s\n", gl_vendor); Con_Printf("GL_RENDERER: %s\n", gl_renderer); Con_Printf("GL_VERSION: %s\n", gl_version); Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); VID_CheckExtensions(); // LordHavoc: report supported extensions #ifdef CONFIG_MENU Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); #else Con_DPrintf("\nQuakeC extensions for server and client: %s\n", vm_sv_extensions ); #endif // clear to black (loading plaque will be seen over this) GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); } #endif int R_CullBox(const vec3_t mins, const vec3_t maxs) { int i; mplane_t *p; if (r_trippy.integer) return false; for (i = 0;i < r_refdef.view.numfrustumplanes;i++) { p = r_refdef.view.frustum + i; switch(p->signbits) { default: case 0: if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 1: if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 2: if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 3: if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 4: if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 5: if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 6: if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 7: if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) return true; break; } } return false; } int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes) { int i; const mplane_t *p; if (r_trippy.integer) return false; for (i = 0;i < numplanes;i++) { p = planes + i; switch(p->signbits) { default: case 0: if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 1: if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 2: if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 3: if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*maxs[2] < p->dist) return true; break; case 4: if (p->normal[0]*maxs[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 5: if (p->normal[0]*mins[0] + p->normal[1]*maxs[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 6: if (p->normal[0]*maxs[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) return true; break; case 7: if (p->normal[0]*mins[0] + p->normal[1]*mins[1] + p->normal[2]*mins[2] < p->dist) return true; break; } } return false; } //================================================================================== // LordHavoc: this stores temporary data used within the same frame typedef struct r_framedata_mem_s { struct r_framedata_mem_s *purge; // older mem block to free on next frame size_t size; // how much usable space size_t current; // how much space in use size_t mark; // last "mark" location, temporary memory can be freed by returning to this size_t wantedsize; // how much space was allocated unsigned char *data; // start of real data (16byte aligned) } r_framedata_mem_t; static r_framedata_mem_t *r_framedata_mem; void R_FrameData_Reset(void) { while (r_framedata_mem) { r_framedata_mem_t *next = r_framedata_mem->purge; Mem_Free(r_framedata_mem); r_framedata_mem = next; } } static void R_FrameData_Resize(qboolean mustgrow) { size_t wantedsize; wantedsize = (size_t)(r_framedatasize.value * 1024*1024); wantedsize = bound(65536, wantedsize, 1000*1024*1024); if (!r_framedata_mem || r_framedata_mem->wantedsize != wantedsize || mustgrow) { r_framedata_mem_t *newmem = (r_framedata_mem_t *)Mem_Alloc(r_main_mempool, wantedsize); newmem->wantedsize = wantedsize; newmem->data = (unsigned char *)(((size_t)(newmem+1) + 15) & ~15); newmem->size = (unsigned char *)newmem + wantedsize - newmem->data; newmem->current = 0; newmem->mark = 0; newmem->purge = r_framedata_mem; r_framedata_mem = newmem; } } void R_FrameData_NewFrame(void) { R_FrameData_Resize(false); if (!r_framedata_mem) return; // if we ran out of space on the last frame, free the old memory now while (r_framedata_mem->purge) { // repeatedly remove the second item in the list, leaving only head r_framedata_mem_t *next = r_framedata_mem->purge->purge; Mem_Free(r_framedata_mem->purge); r_framedata_mem->purge = next; } // reset the current mem pointer r_framedata_mem->current = 0; r_framedata_mem->mark = 0; } void *R_FrameData_Alloc(size_t size) { void *data; float newvalue; // align to 16 byte boundary - the data pointer is already aligned, so we // only need to ensure the size of every allocation is also aligned size = (size + 15) & ~15; while (!r_framedata_mem || r_framedata_mem->current + size > r_framedata_mem->size) { // emergency - we ran out of space, allocate more memory // note: this has no upper-bound, we'll fail to allocate memory eventually and just die newvalue = r_framedatasize.value * 2.0f; // upper bound based on architecture - if we try to allocate more than this we could overflow, better to loop until we error out on allocation failure if (sizeof(size_t) >= 8) newvalue = bound(0.25f, newvalue, (float)(1ll << 42)); else newvalue = bound(0.25f, newvalue, (float)(1 << 10)); // this might not be a growing it, but we'll allocate another buffer every time Cvar_SetValueQuick(&r_framedatasize, newvalue); R_FrameData_Resize(true); } data = r_framedata_mem->data + r_framedata_mem->current; r_framedata_mem->current += size; // count the usage for stats r_refdef.stats[r_stat_framedatacurrent] = max(r_refdef.stats[r_stat_framedatacurrent], (int)r_framedata_mem->current); r_refdef.stats[r_stat_framedatasize] = max(r_refdef.stats[r_stat_framedatasize], (int)r_framedata_mem->size); return (void *)data; } void *R_FrameData_Store(size_t size, void *data) { void *d = R_FrameData_Alloc(size); if (d && data) memcpy(d, data, size); return d; } void R_FrameData_SetMark(void) { if (!r_framedata_mem) return; r_framedata_mem->mark = r_framedata_mem->current; } void R_FrameData_ReturnToMark(void) { if (!r_framedata_mem) return; r_framedata_mem->current = r_framedata_mem->mark; } //================================================================================== // avoid reusing the same buffer objects on consecutive frames #define R_BUFFERDATA_CYCLE 3 typedef struct r_bufferdata_buffer_s { struct r_bufferdata_buffer_s *purge; // older buffer to free on next frame size_t size; // how much usable space size_t current; // how much space in use r_meshbuffer_t *buffer; // the buffer itself } r_bufferdata_buffer_t; static int r_bufferdata_cycle = 0; // incremented and wrapped each frame static r_bufferdata_buffer_t *r_bufferdata_buffer[R_BUFFERDATA_CYCLE][R_BUFFERDATA_COUNT]; /// frees all dynamic buffers void R_BufferData_Reset(void) { int cycle, type; r_bufferdata_buffer_t **p, *mem; for (cycle = 0;cycle < R_BUFFERDATA_CYCLE;cycle++) { for (type = 0;type < R_BUFFERDATA_COUNT;type++) { // free all buffers p = &r_bufferdata_buffer[cycle][type]; while (*p) { mem = *p; *p = (*p)->purge; if (mem->buffer) R_Mesh_DestroyMeshBuffer(mem->buffer); Mem_Free(mem); } } } } // resize buffer as needed (this actually makes a new one, the old one will be recycled next frame) static void R_BufferData_Resize(r_bufferdata_type_t type, qboolean mustgrow, size_t minsize) { r_bufferdata_buffer_t *mem = r_bufferdata_buffer[r_bufferdata_cycle][type]; size_t size; float newvalue = r_buffermegs[type].value; // increase the cvar if we have to (but only if we already have a mem) if (mustgrow && mem) newvalue *= 2.0f; newvalue = bound(0.25f, newvalue, 256.0f); while (newvalue * 1024*1024 < minsize) newvalue *= 2.0f; // clamp the cvar to valid range newvalue = bound(0.25f, newvalue, 256.0f); if (r_buffermegs[type].value != newvalue) Cvar_SetValueQuick(&r_buffermegs[type], newvalue); // calculate size in bytes size = (size_t)(newvalue * 1024*1024); size = bound(131072, size, 256*1024*1024); // allocate a new buffer if the size is different (purge old one later) // or if we were told we must grow the buffer if (!mem || mem->size != size || mustgrow) { mem = (r_bufferdata_buffer_t *)Mem_Alloc(r_main_mempool, sizeof(*mem)); mem->size = size; mem->current = 0; if (type == R_BUFFERDATA_VERTEX) mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbuffervertex", false, false, true, false); else if (type == R_BUFFERDATA_INDEX16) mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex16", true, false, true, true); else if (type == R_BUFFERDATA_INDEX32) mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferindex32", true, false, true, false); else if (type == R_BUFFERDATA_UNIFORM) mem->buffer = R_Mesh_CreateMeshBuffer(NULL, mem->size, "dynamicbufferuniform", false, true, true, false); mem->purge = r_bufferdata_buffer[r_bufferdata_cycle][type]; r_bufferdata_buffer[r_bufferdata_cycle][type] = mem; } } void R_BufferData_NewFrame(void) { int type; r_bufferdata_buffer_t **p, *mem; // cycle to the next frame's buffers r_bufferdata_cycle = (r_bufferdata_cycle + 1) % R_BUFFERDATA_CYCLE; // if we ran out of space on the last time we used these buffers, free the old memory now for (type = 0;type < R_BUFFERDATA_COUNT;type++) { if (r_bufferdata_buffer[r_bufferdata_cycle][type]) { R_BufferData_Resize((r_bufferdata_type_t)type, false, 131072); // free all but the head buffer, this is how we recycle obsolete // buffers after they are no longer in use p = &r_bufferdata_buffer[r_bufferdata_cycle][type]->purge; while (*p) { mem = *p; *p = (*p)->purge; if (mem->buffer) R_Mesh_DestroyMeshBuffer(mem->buffer); Mem_Free(mem); } // reset the current offset r_bufferdata_buffer[r_bufferdata_cycle][type]->current = 0; } } } r_meshbuffer_t *R_BufferData_Store(size_t datasize, const void *data, r_bufferdata_type_t type, int *returnbufferoffset) { r_bufferdata_buffer_t *mem; int offset = 0; int padsize; *returnbufferoffset = 0; // align size to a byte boundary appropriate for the buffer type, this // makes all allocations have aligned start offsets if (type == R_BUFFERDATA_UNIFORM) padsize = (datasize + r_uniformbufferalignment - 1) & ~(r_uniformbufferalignment - 1); else padsize = (datasize + 15) & ~15; // if we ran out of space in this buffer we must allocate a new one if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size) R_BufferData_Resize(type, true, padsize); // if the resize did not give us enough memory, fail if (!r_bufferdata_buffer[r_bufferdata_cycle][type] || r_bufferdata_buffer[r_bufferdata_cycle][type]->current + padsize > r_bufferdata_buffer[r_bufferdata_cycle][type]->size) Sys_Error("R_BufferData_Store: failed to create a new buffer of sufficient size\n"); mem = r_bufferdata_buffer[r_bufferdata_cycle][type]; offset = (int)mem->current; mem->current += padsize; // upload the data to the buffer at the chosen offset if (offset == 0) R_Mesh_UpdateMeshBuffer(mem->buffer, NULL, mem->size, false, 0); R_Mesh_UpdateMeshBuffer(mem->buffer, data, datasize, true, offset); // count the usage for stats r_refdef.stats[r_stat_bufferdatacurrent_vertex + type] = max(r_refdef.stats[r_stat_bufferdatacurrent_vertex + type], (int)mem->current); r_refdef.stats[r_stat_bufferdatasize_vertex + type] = max(r_refdef.stats[r_stat_bufferdatasize_vertex + type], (int)mem->size); // return the buffer offset *returnbufferoffset = offset; return mem->buffer; } //================================================================================== // LordHavoc: animcache originally written by Echon, rewritten since then /** * Animation cache prevents re-generating mesh data for an animated model * multiple times in one frame for lighting, shadowing, reflections, etc. */ void R_AnimCache_Free(void) { } void R_AnimCache_ClearCache(void) { int i; entity_render_t *ent; for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; ent->animcache_vertex3f = NULL; ent->animcache_vertex3f_vertexbuffer = NULL; ent->animcache_vertex3f_bufferoffset = 0; ent->animcache_normal3f = NULL; ent->animcache_normal3f_vertexbuffer = NULL; ent->animcache_normal3f_bufferoffset = 0; ent->animcache_svector3f = NULL; ent->animcache_svector3f_vertexbuffer = NULL; ent->animcache_svector3f_bufferoffset = 0; ent->animcache_tvector3f = NULL; ent->animcache_tvector3f_vertexbuffer = NULL; ent->animcache_tvector3f_bufferoffset = 0; ent->animcache_vertexmesh = NULL; ent->animcache_vertexmesh_vertexbuffer = NULL; ent->animcache_vertexmesh_bufferoffset = 0; ent->animcache_skeletaltransform3x4 = NULL; ent->animcache_skeletaltransform3x4buffer = NULL; ent->animcache_skeletaltransform3x4offset = 0; ent->animcache_skeletaltransform3x4size = 0; } } static void R_AnimCache_UpdateEntityMeshBuffers(entity_render_t *ent, int numvertices) { int i; // check if we need the meshbuffers if (!vid.useinterleavedarrays) return; if (!ent->animcache_vertexmesh && ent->animcache_normal3f) ent->animcache_vertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(sizeof(r_vertexmesh_t)*numvertices); // TODO: upload vertexbuffer? if (ent->animcache_vertexmesh) { r_refdef.stats[r_stat_animcache_vertexmesh_count] += 1; r_refdef.stats[r_stat_animcache_vertexmesh_vertices] += numvertices; r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices] = max(r_refdef.stats[r_stat_animcache_vertexmesh_maxvertices], numvertices); memcpy(ent->animcache_vertexmesh, ent->model->surfmesh.data_vertexmesh, sizeof(r_vertexmesh_t)*numvertices); for (i = 0;i < numvertices;i++) memcpy(ent->animcache_vertexmesh[i].vertex3f, ent->animcache_vertex3f + 3*i, sizeof(float[3])); if (ent->animcache_svector3f) for (i = 0;i < numvertices;i++) memcpy(ent->animcache_vertexmesh[i].svector3f, ent->animcache_svector3f + 3*i, sizeof(float[3])); if (ent->animcache_tvector3f) for (i = 0;i < numvertices;i++) memcpy(ent->animcache_vertexmesh[i].tvector3f, ent->animcache_tvector3f + 3*i, sizeof(float[3])); if (ent->animcache_normal3f) for (i = 0;i < numvertices;i++) memcpy(ent->animcache_vertexmesh[i].normal3f, ent->animcache_normal3f + 3*i, sizeof(float[3])); } } qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents) { dp_model_t *model = ent->model; int numvertices; // see if this ent is worth caching if (!model || !model->Draw || !model->AnimateVertices) return false; // nothing to cache if it contains no animations and has no skeleton if (!model->surfmesh.isanimated && !(model->num_bones && ent->skeleton && ent->skeleton->relativetransforms)) return false; // see if it is already cached for gpuskeletal if (ent->animcache_skeletaltransform3x4) return false; // see if it is already cached as a mesh if (ent->animcache_vertex3f) { // check if we need to add normals or tangents if (ent->animcache_normal3f) wantnormals = false; if (ent->animcache_svector3f) wanttangents = false; if (!wantnormals && !wanttangents) return false; } // check which kind of cache we need to generate if (r_gpuskeletal && model->num_bones > 0 && model->surfmesh.data_skeletalindex4ub) { // cache the skeleton so the vertex shader can use it r_refdef.stats[r_stat_animcache_skeletal_count] += 1; r_refdef.stats[r_stat_animcache_skeletal_bones] += model->num_bones; r_refdef.stats[r_stat_animcache_skeletal_maxbones] = max(r_refdef.stats[r_stat_animcache_skeletal_maxbones], model->num_bones); ent->animcache_skeletaltransform3x4 = (float *)R_FrameData_Alloc(sizeof(float[3][4]) * model->num_bones); Mod_Skeletal_BuildTransforms(model, ent->frameblend, ent->skeleton, NULL, ent->animcache_skeletaltransform3x4); // note: this can fail if the buffer is at the grow limit ent->animcache_skeletaltransform3x4size = sizeof(float[3][4]) * model->num_bones; ent->animcache_skeletaltransform3x4buffer = R_BufferData_Store(ent->animcache_skeletaltransform3x4size, ent->animcache_skeletaltransform3x4, R_BUFFERDATA_UNIFORM, &ent->animcache_skeletaltransform3x4offset); } else if (ent->animcache_vertex3f) { // mesh was already cached but we may need to add normals/tangents // (this only happens with multiple views, reflections, cameras, etc) if (wantnormals || wanttangents) { numvertices = model->surfmesh.num_vertices; if (wantnormals) ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); if (wanttangents) { ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); } model->AnimateVertices(model, ent->frameblend, ent->skeleton, NULL, wantnormals ? ent->animcache_normal3f : NULL, wanttangents ? ent->animcache_svector3f : NULL, wanttangents ? ent->animcache_tvector3f : NULL); R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); r_refdef.stats[r_stat_animcache_shade_count] += 1; r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices; r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices); } } else { // generate mesh cache numvertices = model->surfmesh.num_vertices; ent->animcache_vertex3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); if (wantnormals) ent->animcache_normal3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); if (wanttangents) { ent->animcache_svector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); ent->animcache_tvector3f = (float *)R_FrameData_Alloc(sizeof(float[3])*numvertices); } model->AnimateVertices(model, ent->frameblend, ent->skeleton, ent->animcache_vertex3f, ent->animcache_normal3f, ent->animcache_svector3f, ent->animcache_tvector3f); R_AnimCache_UpdateEntityMeshBuffers(ent, model->surfmesh.num_vertices); if (wantnormals || wanttangents) { r_refdef.stats[r_stat_animcache_shade_count] += 1; r_refdef.stats[r_stat_animcache_shade_vertices] += numvertices; r_refdef.stats[r_stat_animcache_shade_maxvertices] = max(r_refdef.stats[r_stat_animcache_shade_maxvertices], numvertices); } r_refdef.stats[r_stat_animcache_shape_count] += 1; r_refdef.stats[r_stat_animcache_shape_vertices] += numvertices; r_refdef.stats[r_stat_animcache_shape_maxvertices] = max(r_refdef.stats[r_stat_animcache_shape_maxvertices], numvertices); } return true; } void R_AnimCache_CacheVisibleEntities(void) { int i; qboolean wantnormals = true; qboolean wanttangents = !r_showsurfaces.integer; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_GLES2: break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: wanttangents = false; break; case RENDERPATH_SOFT: break; } if (r_shownormals.integer) wanttangents = wantnormals = true; // TODO: thread this // NOTE: R_PrepareRTLights() also caches entities for (i = 0;i < r_refdef.scene.numentities;i++) if (r_refdef.viewcache.entityvisible[i]) R_AnimCache_GetEntity(r_refdef.scene.entities[i], wantnormals, wanttangents); } //================================================================================== extern cvar_t r_overheadsprites_pushback; static void R_View_UpdateEntityLighting (void) { int i; entity_render_t *ent; vec3_t tempdiffusenormal, avg; vec_t f, fa, fd, fdd; qboolean skipunseen = r_shadows.integer != 1; //|| R_Shadow_ShadowMappingEnabled(); for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; // skip unseen models if ((!r_refdef.viewcache.entityvisible[i] && skipunseen)) continue; // skip bsp models if (ent->model && ent->model == cl.worldmodel) { // TODO: use modellight for r_ambient settings on world? VectorSet(ent->modellight_ambient, 0, 0, 0); VectorSet(ent->modellight_diffuse, 0, 0, 0); VectorSet(ent->modellight_lightdir, 0, 0, 1); continue; } if (ent->flags & RENDER_CUSTOMIZEDMODELLIGHT) { // aleady updated by CSQC // TODO: force modellight on BSP models in this case? VectorCopy(ent->modellight_lightdir, tempdiffusenormal); } else { // fetch the lighting from the worldmodel data VectorClear(ent->modellight_ambient); VectorClear(ent->modellight_diffuse); VectorClear(tempdiffusenormal); if (ent->flags & RENDER_LIGHT) { vec3_t org; Matrix4x4_OriginFromMatrix(&ent->matrix, org); // complete lightning for lit sprites // todo: make a EF_ field so small ents could be lit purely by modellight and skipping real rtlight pass (like EF_NORTLIGHT)? if (ent->model->type == mod_sprite && !(ent->model->data_textures[0].basematerialflags & MATERIALFLAG_FULLBRIGHT)) { if (ent->model->sprite.sprnum_type == SPR_OVERHEAD) // apply offset for overhead sprites org[2] = org[2] + r_overheadsprites_pushback.value; R_LightPoint(ent->modellight_ambient, org, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); } else R_CompleteLightPoint(ent->modellight_ambient, ent->modellight_diffuse, tempdiffusenormal, org, LP_LIGHTMAP); if(ent->flags & RENDER_EQUALIZE) { // first fix up ambient lighting... if(r_equalize_entities_minambient.value > 0) { fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; if(fd > 0) { fa = (0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]); if(fa < r_equalize_entities_minambient.value * fd) { // solve: // fa'/fd' = minambient // fa'+0.25*fd' = fa+0.25*fd // ... // fa' = fd' * minambient // fd'*(0.25+minambient) = fa+0.25*fd // ... // fd' = (fa+0.25*fd) * 1 / (0.25+minambient) // fa' = (fa+0.25*fd) * minambient / (0.25+minambient) // ... fdd = (fa + 0.25f * fd) / (0.25f + r_equalize_entities_minambient.value); f = fdd / fd; // f>0 because all this is additive; f<1 because fddmodellight_ambient, (1-f)*0.25f, ent->modellight_diffuse, ent->modellight_ambient); VectorScale(ent->modellight_diffuse, f, ent->modellight_diffuse); } } } if(r_equalize_entities_to.value > 0 && r_equalize_entities_by.value != 0) { fa = 0.299f * ent->modellight_ambient[0] + 0.587f * ent->modellight_ambient[1] + 0.114f * ent->modellight_ambient[2]; fd = 0.299f * ent->modellight_diffuse[0] + 0.587f * ent->modellight_diffuse[1] + 0.114f * ent->modellight_diffuse[2]; f = fa + 0.25 * fd; if(f > 0) { // adjust brightness and saturation to target avg[0] = avg[1] = avg[2] = fa / f; VectorLerp(ent->modellight_ambient, r_equalize_entities_by.value, avg, ent->modellight_ambient); avg[0] = avg[1] = avg[2] = fd / f; VectorLerp(ent->modellight_diffuse, r_equalize_entities_by.value, avg, ent->modellight_diffuse); } } } } else // highly rare VectorSet(ent->modellight_ambient, 1, 1, 1); } // move the light direction into modelspace coordinates for lighting code Matrix4x4_Transform3x3(&ent->inversematrix, tempdiffusenormal, ent->modellight_lightdir); if(VectorLength2(ent->modellight_lightdir) == 0) VectorSet(ent->modellight_lightdir, 0, 0, 1); // have to set SOME valid vector here VectorNormalize(ent->modellight_lightdir); } } #define MAX_LINEOFSIGHTTRACES 64 static qboolean R_CanSeeBox(int numsamples, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) { int i; vec3_t boxmins, boxmaxs; vec3_t start; vec3_t end; dp_model_t *model = r_refdef.scene.worldmodel; if (!model || !model->brush.TraceLineOfSight) return true; // expand the box a little boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; // return true if eye is inside enlarged box if (BoxesOverlap(boxmins, boxmaxs, eye, eye)) return true; // try center VectorCopy(eye, start); VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, end); if (model->brush.TraceLineOfSight(model, start, end)) return true; // try various random positions for (i = 0;i < numsamples;i++) { VectorSet(end, lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); if (model->brush.TraceLineOfSight(model, start, end)) return true; } return false; } static void R_View_UpdateEntityVisible (void) { int i; int renderimask; int samples; entity_render_t *ent; if (r_refdef.envmap || r_fb.water.hideplayer) renderimask = RENDER_EXTERIORMODEL | RENDER_VIEWMODEL; else if (chase_active.integer || r_fb.water.renderingscene) renderimask = RENDER_VIEWMODEL; else renderimask = RENDER_EXTERIORMODEL; if (!r_drawviewmodel.integer) renderimask |= RENDER_VIEWMODEL; if (!r_drawexteriormodel.integer) renderimask |= RENDER_EXTERIORMODEL; memset(r_refdef.viewcache.entityvisible, 0, r_refdef.scene.numentities); if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs) { // worldmodel can check visibility for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; if (!(ent->flags & renderimask)) if (!R_CullBox(ent->mins, ent->maxs) || (ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE))) if ((ent->flags & (RENDER_NODEPTHTEST | RENDER_WORLDOBJECT | RENDER_VIEWMODEL)) || r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, ent->mins, ent->maxs)) r_refdef.viewcache.entityvisible[i] = true; } } else { // no worldmodel or it can't check visibility for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; if (!(ent->flags & renderimask)) if (!R_CullBox(ent->mins, ent->maxs) || (ent->model && ent->model->type == mod_sprite && (ent->model->sprite.sprnum_type == SPR_LABEL || ent->model->sprite.sprnum_type == SPR_LABEL_SCALE))) r_refdef.viewcache.entityvisible[i] = true; } } if(r_cullentities_trace.integer && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.TraceLineOfSight && !r_refdef.view.useclipplane && !r_trippy.integer) // sorry, this check doesn't work for portal/reflection/refraction renders as the view origin is not useful for culling { for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if(!(ent->flags & (RENDER_VIEWMODEL | RENDER_WORLDOBJECT | RENDER_NODEPTHTEST)) && !(ent->model && (ent->model->name[0] == '*'))) { samples = ent->entitynumber ? r_cullentities_trace_samples.integer : r_cullentities_trace_tempentitysamples.integer; if (samples < 0) continue; // temp entities do pvs only if(R_CanSeeBox(samples, r_cullentities_trace_enlarge.value, r_refdef.view.origin, ent->mins, ent->maxs)) ent->last_trace_visibility = realtime; if(ent->last_trace_visibility < realtime - r_cullentities_trace_delay.value) r_refdef.viewcache.entityvisible[i] = 0; } } } } /// only used if skyrendermasked, and normally returns false static int R_DrawBrushModelsSky (void) { int i, sky; entity_render_t *ent; sky = false; for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if (!ent->model || !ent->model->DrawSky) continue; ent->model->DrawSky(ent); sky = true; } return sky; } static void R_DrawNoModel(entity_render_t *ent); static void R_DrawModels(void) { int i; entity_render_t *ent; for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; r_refdef.stats[r_stat_entities]++; /* if (ent->model && !strncmp(ent->model->name, "models/proto_", 13)) { vec3_t f, l, u, o; Matrix4x4_ToVectors(&ent->matrix, f, l, u, o); Con_Printf("R_DrawModels\n"); Con_Printf("model %s O %f %f %f F %f %f %f L %f %f %f U %f %f %f\n", ent->model->name, o[0], o[1], o[2], f[0], f[1], f[2], l[0], l[1], l[2], u[0], u[1], u[2]); Con_Printf("group: %i %f %i %f %i %f %i %f\n", ent->framegroupblend[0].frame, ent->framegroupblend[0].lerp, ent->framegroupblend[1].frame, ent->framegroupblend[1].lerp, ent->framegroupblend[2].frame, ent->framegroupblend[2].lerp, ent->framegroupblend[3].frame, ent->framegroupblend[3].lerp); Con_Printf("blend: %i %f %i %f %i %f %i %f %i %f %i %f %i %f %i %f\n", ent->frameblend[0].subframe, ent->frameblend[0].lerp, ent->frameblend[1].subframe, ent->frameblend[1].lerp, ent->frameblend[2].subframe, ent->frameblend[2].lerp, ent->frameblend[3].subframe, ent->frameblend[3].lerp, ent->frameblend[4].subframe, ent->frameblend[4].lerp, ent->frameblend[5].subframe, ent->frameblend[5].lerp, ent->frameblend[6].subframe, ent->frameblend[6].lerp, ent->frameblend[7].subframe, ent->frameblend[7].lerp); } */ if (ent->model && ent->model->Draw != NULL) ent->model->Draw(ent); else R_DrawNoModel(ent); } } static void R_DrawModelsDepth(void) { int i; entity_render_t *ent; for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if (ent->model && ent->model->DrawDepth != NULL) ent->model->DrawDepth(ent); } } static void R_DrawModelsDebug(void) { int i; entity_render_t *ent; for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if (ent->model && ent->model->DrawDebug != NULL) ent->model->DrawDebug(ent); } } static void R_DrawModelsAddWaterPlanes(void) { int i; entity_render_t *ent; for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if (ent->model && ent->model->DrawAddWaterPlanes != NULL) ent->model->DrawAddWaterPlanes(ent); } } static float irisvecs[7][3] = {{0, 0, 0}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}}; void R_HDR_UpdateIrisAdaptation(const vec3_t point) { if (r_hdr_irisadaptation.integer) { vec3_t p; vec3_t ambient; vec3_t diffuse; vec3_t diffusenormal; vec3_t forward; vec_t brightness = 0.0f; vec_t goal; vec_t current; vec_t d; int c; VectorCopy(r_refdef.view.forward, forward); for (c = 0;c < (int)(sizeof(irisvecs)/sizeof(irisvecs[0]));c++) { p[0] = point[0] + irisvecs[c][0] * r_hdr_irisadaptation_radius.value; p[1] = point[1] + irisvecs[c][1] * r_hdr_irisadaptation_radius.value; p[2] = point[2] + irisvecs[c][2] * r_hdr_irisadaptation_radius.value; R_CompleteLightPoint(ambient, diffuse, diffusenormal, p, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); d = DotProduct(forward, diffusenormal); brightness += VectorLength(ambient); if (d > 0) brightness += d * VectorLength(diffuse); } brightness *= 1.0f / c; brightness += 0.00001f; // make sure it's never zero goal = r_hdr_irisadaptation_multiplier.value / brightness; goal = bound(r_hdr_irisadaptation_minvalue.value, goal, r_hdr_irisadaptation_maxvalue.value); current = r_hdr_irisadaptation_value.value; if (current < goal) current = min(current + r_hdr_irisadaptation_fade_up.value * cl.realframetime, goal); else if (current > goal) current = max(current - r_hdr_irisadaptation_fade_down.value * cl.realframetime, goal); if (fabs(r_hdr_irisadaptation_value.value - current) > 0.0001f) Cvar_SetValueQuick(&r_hdr_irisadaptation_value, current); } else if (r_hdr_irisadaptation_value.value != 1.0f) Cvar_SetValueQuick(&r_hdr_irisadaptation_value, 1.0f); } static void R_View_SetFrustum(const int *scissor) { int i; double fpx = +1, fnx = -1, fpy = +1, fny = -1; vec3_t forward, left, up, origin, v; if(scissor) { // flipped x coordinates (because x points left here) fpx = 1.0 - 2.0 * (scissor[0] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); fnx = 1.0 - 2.0 * (scissor[0] + scissor[2] - r_refdef.view.viewport.x) / (double) (r_refdef.view.viewport.width); // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one switch(vid.renderpath) { case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: // non-flipped y coordinates fny = -1.0 + 2.0 * (vid.height - scissor[1] - scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); fpy = -1.0 + 2.0 * (vid.height - scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); break; case RENDERPATH_SOFT: case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // non-flipped y coordinates fny = -1.0 + 2.0 * (scissor[1] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); fpy = -1.0 + 2.0 * (scissor[1] + scissor[3] - r_refdef.view.viewport.y) / (double) (r_refdef.view.viewport.height); break; } } // we can't trust r_refdef.view.forward and friends in reflected scenes Matrix4x4_ToVectors(&r_refdef.view.matrix, forward, left, up, origin); #if 0 r_refdef.view.frustum[0].normal[0] = 0 - 1.0 / r_refdef.view.frustum_x; r_refdef.view.frustum[0].normal[1] = 0 - 0; r_refdef.view.frustum[0].normal[2] = -1 - 0; r_refdef.view.frustum[1].normal[0] = 0 + 1.0 / r_refdef.view.frustum_x; r_refdef.view.frustum[1].normal[1] = 0 + 0; r_refdef.view.frustum[1].normal[2] = -1 + 0; r_refdef.view.frustum[2].normal[0] = 0 - 0; r_refdef.view.frustum[2].normal[1] = 0 - 1.0 / r_refdef.view.frustum_y; r_refdef.view.frustum[2].normal[2] = -1 - 0; r_refdef.view.frustum[3].normal[0] = 0 + 0; r_refdef.view.frustum[3].normal[1] = 0 + 1.0 / r_refdef.view.frustum_y; r_refdef.view.frustum[3].normal[2] = -1 + 0; #endif #if 0 zNear = r_refdef.nearclip; nudge = 1.0 - 1.0 / (1<<23); r_refdef.view.frustum[4].normal[0] = 0 - 0; r_refdef.view.frustum[4].normal[1] = 0 - 0; r_refdef.view.frustum[4].normal[2] = -1 - -nudge; r_refdef.view.frustum[4].dist = 0 - -2 * zNear * nudge; r_refdef.view.frustum[5].normal[0] = 0 + 0; r_refdef.view.frustum[5].normal[1] = 0 + 0; r_refdef.view.frustum[5].normal[2] = -1 + -nudge; r_refdef.view.frustum[5].dist = 0 + -2 * zNear * nudge; #endif #if 0 r_refdef.view.frustum[0].normal[0] = m[3] - m[0]; r_refdef.view.frustum[0].normal[1] = m[7] - m[4]; r_refdef.view.frustum[0].normal[2] = m[11] - m[8]; r_refdef.view.frustum[0].dist = m[15] - m[12]; r_refdef.view.frustum[1].normal[0] = m[3] + m[0]; r_refdef.view.frustum[1].normal[1] = m[7] + m[4]; r_refdef.view.frustum[1].normal[2] = m[11] + m[8]; r_refdef.view.frustum[1].dist = m[15] + m[12]; r_refdef.view.frustum[2].normal[0] = m[3] - m[1]; r_refdef.view.frustum[2].normal[1] = m[7] - m[5]; r_refdef.view.frustum[2].normal[2] = m[11] - m[9]; r_refdef.view.frustum[2].dist = m[15] - m[13]; r_refdef.view.frustum[3].normal[0] = m[3] + m[1]; r_refdef.view.frustum[3].normal[1] = m[7] + m[5]; r_refdef.view.frustum[3].normal[2] = m[11] + m[9]; r_refdef.view.frustum[3].dist = m[15] + m[13]; r_refdef.view.frustum[4].normal[0] = m[3] - m[2]; r_refdef.view.frustum[4].normal[1] = m[7] - m[6]; r_refdef.view.frustum[4].normal[2] = m[11] - m[10]; r_refdef.view.frustum[4].dist = m[15] - m[14]; r_refdef.view.frustum[5].normal[0] = m[3] + m[2]; r_refdef.view.frustum[5].normal[1] = m[7] + m[6]; r_refdef.view.frustum[5].normal[2] = m[11] + m[10]; r_refdef.view.frustum[5].dist = m[15] + m[14]; #endif if (r_refdef.view.useperspective) { // calculate frustum corners, which are used to calculate deformed frustum planes for shadow caster culling VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[0]); VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fny * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[1]); VectorMAMAM(1024, forward, fnx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[2]); VectorMAMAM(1024, forward, fpx * 1024.0 * r_refdef.view.frustum_x, left, fpy * 1024.0 * r_refdef.view.frustum_y, up, r_refdef.view.frustumcorner[3]); // then the normals from the corners relative to origin CrossProduct(r_refdef.view.frustumcorner[2], r_refdef.view.frustumcorner[0], r_refdef.view.frustum[0].normal); CrossProduct(r_refdef.view.frustumcorner[1], r_refdef.view.frustumcorner[3], r_refdef.view.frustum[1].normal); CrossProduct(r_refdef.view.frustumcorner[0], r_refdef.view.frustumcorner[1], r_refdef.view.frustum[2].normal); CrossProduct(r_refdef.view.frustumcorner[3], r_refdef.view.frustumcorner[2], r_refdef.view.frustum[3].normal); // in a NORMAL view, forward cross left == up // in a REFLECTED view, forward cross left == down // so our cross products above need to be adjusted for a left handed coordinate system CrossProduct(forward, left, v); if(DotProduct(v, up) < 0) { VectorNegate(r_refdef.view.frustum[0].normal, r_refdef.view.frustum[0].normal); VectorNegate(r_refdef.view.frustum[1].normal, r_refdef.view.frustum[1].normal); VectorNegate(r_refdef.view.frustum[2].normal, r_refdef.view.frustum[2].normal); VectorNegate(r_refdef.view.frustum[3].normal, r_refdef.view.frustum[3].normal); } // Leaving those out was a mistake, those were in the old code, and they // fix a reproducable bug in this one: frustum culling got fucked up when viewmatrix was an identity matrix // I couldn't reproduce it after adding those normalizations. --blub VectorNormalize(r_refdef.view.frustum[0].normal); VectorNormalize(r_refdef.view.frustum[1].normal); VectorNormalize(r_refdef.view.frustum[2].normal); VectorNormalize(r_refdef.view.frustum[3].normal); // make the corners absolute VectorAdd(r_refdef.view.frustumcorner[0], r_refdef.view.origin, r_refdef.view.frustumcorner[0]); VectorAdd(r_refdef.view.frustumcorner[1], r_refdef.view.origin, r_refdef.view.frustumcorner[1]); VectorAdd(r_refdef.view.frustumcorner[2], r_refdef.view.origin, r_refdef.view.frustumcorner[2]); VectorAdd(r_refdef.view.frustumcorner[3], r_refdef.view.origin, r_refdef.view.frustumcorner[3]); // one more normal VectorCopy(forward, r_refdef.view.frustum[4].normal); r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal); r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal); r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal); r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal); r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; } else { VectorScale(left, -r_refdef.view.ortho_x, r_refdef.view.frustum[0].normal); VectorScale(left, r_refdef.view.ortho_x, r_refdef.view.frustum[1].normal); VectorScale(up, -r_refdef.view.ortho_y, r_refdef.view.frustum[2].normal); VectorScale(up, r_refdef.view.ortho_y, r_refdef.view.frustum[3].normal); VectorCopy(forward, r_refdef.view.frustum[4].normal); r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[0].normal) + r_refdef.view.ortho_x; r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[1].normal) + r_refdef.view.ortho_x; r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[2].normal) + r_refdef.view.ortho_y; r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[3].normal) + r_refdef.view.ortho_y; r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, r_refdef.view.frustum[4].normal) + r_refdef.nearclip; } r_refdef.view.numfrustumplanes = 5; if (r_refdef.view.useclipplane) { r_refdef.view.numfrustumplanes = 6; r_refdef.view.frustum[5] = r_refdef.view.clipplane; } for (i = 0;i < r_refdef.view.numfrustumplanes;i++) PlaneClassify(r_refdef.view.frustum + i); // LordHavoc: note to all quake engine coders, Quake had a special case // for 90 degrees which assumed a square view (wrong), so I removed it, // Quake2 has it disabled as well. // rotate R_VIEWFORWARD right by FOV_X/2 degrees //RotatePointAroundVector( r_refdef.view.frustum[0].normal, up, forward, -(90 - r_refdef.fov_x / 2)); //r_refdef.view.frustum[0].dist = DotProduct (r_refdef.view.origin, frustum[0].normal); //PlaneClassify(&frustum[0]); // rotate R_VIEWFORWARD left by FOV_X/2 degrees //RotatePointAroundVector( r_refdef.view.frustum[1].normal, up, forward, (90 - r_refdef.fov_x / 2)); //r_refdef.view.frustum[1].dist = DotProduct (r_refdef.view.origin, frustum[1].normal); //PlaneClassify(&frustum[1]); // rotate R_VIEWFORWARD up by FOV_X/2 degrees //RotatePointAroundVector( r_refdef.view.frustum[2].normal, left, forward, -(90 - r_refdef.fov_y / 2)); //r_refdef.view.frustum[2].dist = DotProduct (r_refdef.view.origin, frustum[2].normal); //PlaneClassify(&frustum[2]); // rotate R_VIEWFORWARD down by FOV_X/2 degrees //RotatePointAroundVector( r_refdef.view.frustum[3].normal, left, forward, (90 - r_refdef.fov_y / 2)); //r_refdef.view.frustum[3].dist = DotProduct (r_refdef.view.origin, frustum[3].normal); //PlaneClassify(&frustum[3]); // nearclip plane //VectorCopy(forward, r_refdef.view.frustum[4].normal); //r_refdef.view.frustum[4].dist = DotProduct (r_refdef.view.origin, frustum[4].normal) + r_nearclip.value; //PlaneClassify(&frustum[4]); } static void R_View_UpdateWithScissor(const int *myscissor) { R_Main_ResizeViewCache(); R_View_SetFrustum(myscissor); R_View_WorldVisibility(r_refdef.view.useclipplane); R_View_UpdateEntityVisible(); R_View_UpdateEntityLighting(); } static void R_View_Update(void) { R_Main_ResizeViewCache(); R_View_SetFrustum(NULL); R_View_WorldVisibility(r_refdef.view.useclipplane); R_View_UpdateEntityVisible(); R_View_UpdateEntityLighting(); } float viewscalefpsadjusted = 1.0f; static void R_GetScaledViewSize(int width, int height, int *outwidth, int *outheight) { float scale = r_viewscale.value * sqrt(viewscalefpsadjusted); scale = bound(0.03125f, scale, 1.0f); *outwidth = (int)ceil(width * scale); *outheight = (int)ceil(height * scale); } void R_SetupView(qboolean allowwaterclippingplane, int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { const float *customclipplane = NULL; float plane[4]; int /*rtwidth,*/ rtheight, scaledwidth, scaledheight; if (r_refdef.view.useclipplane && allowwaterclippingplane) { // LordHavoc: couldn't figure out how to make this approach the vec_t dist = r_refdef.view.clipplane.dist - r_water_clippingplanebias.value; vec_t viewdist = DotProduct(r_refdef.view.origin, r_refdef.view.clipplane.normal); if (viewdist < r_refdef.view.clipplane.dist + r_water_clippingplanebias.value) dist = r_refdef.view.clipplane.dist; plane[0] = r_refdef.view.clipplane.normal[0]; plane[1] = r_refdef.view.clipplane.normal[1]; plane[2] = r_refdef.view.clipplane.normal[2]; plane[3] = -dist; if(vid.renderpath != RENDERPATH_SOFT) customclipplane = plane; } //rtwidth = fbo ? R_TextureWidth(depthtexture ? depthtexture : colortexture) : vid.width; rtheight = fbo ? R_TextureHeight(depthtexture ? depthtexture : colortexture) : vid.height; R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &scaledwidth, &scaledheight); if (!r_refdef.view.useperspective) R_Viewport_InitOrtho(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, -r_refdef.view.ortho_x, -r_refdef.view.ortho_y, r_refdef.view.ortho_x, r_refdef.view.ortho_y, -r_refdef.farclip, r_refdef.farclip, customclipplane); else if (vid.stencil && r_useinfinitefarclip.integer) R_Viewport_InitPerspectiveInfinite(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, customclipplane); else R_Viewport_InitPerspective(&r_refdef.view.viewport, &r_refdef.view.matrix, r_refdef.view.x, rtheight - scaledheight - r_refdef.view.y, scaledwidth, scaledheight, r_refdef.view.frustum_x, r_refdef.view.frustum_y, r_refdef.nearclip, r_refdef.farclip, customclipplane); R_Mesh_SetRenderTargets(fbo, depthtexture, colortexture, NULL, NULL, NULL); R_SetViewport(&r_refdef.view.viewport); if (r_refdef.view.useclipplane && allowwaterclippingplane && vid.renderpath == RENDERPATH_SOFT) { matrix4x4_t mvpmatrix, invmvpmatrix, invtransmvpmatrix; float screenplane[4]; Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); Matrix4x4_Transpose(&invtransmvpmatrix, &invmvpmatrix); Matrix4x4_Transform4(&invtransmvpmatrix, plane, screenplane); DPSOFTRAST_ClipPlane(screenplane[0], screenplane[1], screenplane[2], screenplane[3]); } } void R_EntityMatrix(const matrix4x4_t *matrix) { if (gl_modelmatrixchanged || memcmp(matrix, &gl_modelmatrix, sizeof(matrix4x4_t))) { gl_modelmatrixchanged = false; gl_modelmatrix = *matrix; Matrix4x4_Concat(&gl_modelviewmatrix, &gl_viewmatrix, &gl_modelmatrix); Matrix4x4_Concat(&gl_modelviewprojectionmatrix, &gl_projectionmatrix, &gl_modelviewmatrix); Matrix4x4_ToArrayFloatGL(&gl_modelviewmatrix, gl_modelview16f); Matrix4x4_ToArrayFloatGL(&gl_modelviewprojectionmatrix, gl_modelviewprojection16f); CHECKGLERROR switch(vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D hlslVSSetParameter16f(D3DVSREGISTER_ModelViewProjectionMatrix, gl_modelviewprojection16f); hlslVSSetParameter16f(D3DVSREGISTER_ModelViewMatrix, gl_modelview16f); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 shader %s:%i\n", __FILE__, __LINE__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 shader %s:%i\n", __FILE__, __LINE__); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 qglLoadMatrixf(gl_modelview16f);CHECKGLERROR #endif break; case RENDERPATH_SOFT: DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, 1, false, gl_modelviewprojection16f); DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM_ModelViewMatrixM1, 1, false, gl_modelview16f); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewProjectionMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewProjectionMatrix, 1, false, gl_modelviewprojection16f); if (r_glsl_permutation && r_glsl_permutation->loc_ModelViewMatrix >= 0) qglUniformMatrix4fv(r_glsl_permutation->loc_ModelViewMatrix, 1, false, gl_modelview16f); break; } } } void R_ResetViewRendering2D_Common(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, float x2, float y2) { r_viewport_t viewport; CHECKGLERROR // GL is weird because it's bottom to top, r_refdef.view.y is top to bottom R_Viewport_InitOrtho(&viewport, &identitymatrix, r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height, 0, 0, x2, y2, -10, 100, NULL); R_Mesh_SetRenderTargets(fbo, depthtexture, colortexture, NULL, NULL, NULL); R_SetViewport(&viewport); GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); GL_Color(1, 1, 1, 1); GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); GL_BlendFunc(GL_ONE, GL_ZERO); GL_ScissorTest(false); GL_DepthMask(false); GL_DepthRange(0, 1); GL_DepthTest(false); GL_DepthFunc(GL_LEQUAL); R_EntityMatrix(&identitymatrix); R_Mesh_ResetTextureState(); GL_PolygonOffset(0, 0); R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } GL_CullFace(GL_NONE); CHECKGLERROR } void R_ResetViewRendering2D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { DrawQ_Finish(); R_ResetViewRendering2D_Common(fbo, depthtexture, colortexture, 1, 1); } void R_ResetViewRendering3D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { DrawQ_Finish(); R_SetupView(true, fbo, depthtexture, colortexture); GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); GL_Color(1, 1, 1, 1); GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); GL_BlendFunc(GL_ONE, GL_ZERO); GL_ScissorTest(true); GL_DepthMask(true); GL_DepthRange(0, 1); GL_DepthTest(true); GL_DepthFunc(GL_LEQUAL); R_EntityMatrix(&identitymatrix); R_Mesh_ResetTextureState(); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglEnable(GL_POLYGON_OFFSET_FILL);CHECKGLERROR break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } GL_CullFace(r_refdef.view.cullface_back); } /* ================ R_RenderView_UpdateViewVectors ================ */ void R_RenderView_UpdateViewVectors(void) { // break apart the view matrix into vectors for various purposes // it is important that this occurs outside the RenderScene function because that can be called from reflection renders, where the vectors come out wrong // however the r_refdef.view.origin IS updated in RenderScene intentionally - otherwise the sky renders at the wrong origin, etc Matrix4x4_ToVectors(&r_refdef.view.matrix, r_refdef.view.forward, r_refdef.view.left, r_refdef.view.up, r_refdef.view.origin); VectorNegate(r_refdef.view.left, r_refdef.view.right); // make an inverted copy of the view matrix for tracking sprites Matrix4x4_Invert_Full(&r_refdef.view.inverse_matrix, &r_refdef.view.matrix); } void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_RenderWaterPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); static void R_Water_StartFrame(void) { int i; int waterwidth, waterheight, texturewidth, textureheight, camerawidth, cameraheight; r_waterstate_waterplane_t *p; qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; if (vid.width > (int)vid.maxtexturesize_2d || vid.height > (int)vid.maxtexturesize_2d) return; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: return; } // set waterwidth and waterheight to the water resolution that will be // used (often less than the screen resolution for faster rendering) R_GetScaledViewSize(bound(1, vid.width * r_water_resolutionmultiplier.value, vid.width), bound(1, vid.height * r_water_resolutionmultiplier.value, vid.height), &waterwidth, &waterheight); // calculate desired texture sizes // can't use water if the card does not support the texture size if (!r_water.integer || r_showsurfaces.integer) texturewidth = textureheight = waterwidth = waterheight = camerawidth = cameraheight = 0; else if (vid.support.arb_texture_non_power_of_two) { texturewidth = waterwidth; textureheight = waterheight; camerawidth = waterwidth; cameraheight = waterheight; } else { for (texturewidth = 1;texturewidth < waterwidth ;texturewidth *= 2); for (textureheight = 1;textureheight < waterheight;textureheight *= 2); for (camerawidth = 1;camerawidth * 2 <= waterwidth ;camerawidth *= 2); for (cameraheight = 1;cameraheight * 2 <= waterheight;cameraheight *= 2); } // allocate textures as needed if (r_fb.water.texturewidth != texturewidth || r_fb.water.textureheight != textureheight || r_fb.water.camerawidth != camerawidth || r_fb.water.cameraheight != cameraheight || (r_fb.depthtexture && !usewaterfbo)) { r_fb.water.maxwaterplanes = MAX_WATERPLANES; for (i = 0, p = r_fb.water.waterplanes;i < r_fb.water.maxwaterplanes;i++, p++) { if (p->texture_refraction) R_FreeTexture(p->texture_refraction); p->texture_refraction = NULL; if (p->fbo_refraction) R_Mesh_DestroyFramebufferObject(p->fbo_refraction); p->fbo_refraction = 0; if (p->texture_reflection) R_FreeTexture(p->texture_reflection); p->texture_reflection = NULL; if (p->fbo_reflection) R_Mesh_DestroyFramebufferObject(p->fbo_reflection); p->fbo_reflection = 0; if (p->texture_camera) R_FreeTexture(p->texture_camera); p->texture_camera = NULL; if (p->fbo_camera) R_Mesh_DestroyFramebufferObject(p->fbo_camera); p->fbo_camera = 0; } memset(&r_fb.water, 0, sizeof(r_fb.water)); r_fb.water.texturewidth = texturewidth; r_fb.water.textureheight = textureheight; r_fb.water.camerawidth = camerawidth; r_fb.water.cameraheight = cameraheight; } if (r_fb.water.texturewidth) { int scaledwidth, scaledheight; r_fb.water.enabled = true; // water resolution is usually reduced r_fb.water.waterwidth = (int)bound(1, r_refdef.view.width * r_water_resolutionmultiplier.value, r_refdef.view.width); r_fb.water.waterheight = (int)bound(1, r_refdef.view.height * r_water_resolutionmultiplier.value, r_refdef.view.height); R_GetScaledViewSize(r_fb.water.waterwidth, r_fb.water.waterheight, &scaledwidth, &scaledheight); // set up variables that will be used in shader setup r_fb.water.screenscale[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth; r_fb.water.screenscale[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight; r_fb.water.screencenter[0] = 0.5f * (float)scaledwidth / (float)r_fb.water.texturewidth; r_fb.water.screencenter[1] = 0.5f * (float)scaledheight / (float)r_fb.water.textureheight; } r_fb.water.maxwaterplanes = MAX_WATERPLANES; r_fb.water.numwaterplanes = 0; } void R_Water_AddWaterPlane(msurface_t *surface, int entno) { int planeindex, bestplaneindex, vertexindex; vec3_t mins, maxs, normal, center, v, n; vec_t planescore, bestplanescore; mplane_t plane; r_waterstate_waterplane_t *p; texture_t *t = R_GetCurrentTexture(surface->texture); rsurface.texture = t; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, 1, ((const msurface_t **)&surface)); // if the model has no normals, it's probably off-screen and they were not generated, so don't add it anyway if (!rsurface.batchnormal3f || rsurface.batchnumvertices < 1) return; // average the vertex normals, find the surface bounds (after deformvertexes) Matrix4x4_Transform(&rsurface.matrix, rsurface.batchvertex3f, v); Matrix4x4_Transform3x3(&rsurface.matrix, rsurface.batchnormal3f, n); VectorCopy(n, normal); VectorCopy(v, mins); VectorCopy(v, maxs); for (vertexindex = 1;vertexindex < rsurface.batchnumvertices;vertexindex++) { Matrix4x4_Transform(&rsurface.matrix, rsurface.batchvertex3f + vertexindex*3, v); Matrix4x4_Transform3x3(&rsurface.matrix, rsurface.batchnormal3f + vertexindex*3, n); VectorAdd(normal, n, normal); mins[0] = min(mins[0], v[0]); mins[1] = min(mins[1], v[1]); mins[2] = min(mins[2], v[2]); maxs[0] = max(maxs[0], v[0]); maxs[1] = max(maxs[1], v[1]); maxs[2] = max(maxs[2], v[2]); } VectorNormalize(normal); VectorMAM(0.5f, mins, 0.5f, maxs, center); VectorCopy(normal, plane.normal); VectorNormalize(plane.normal); plane.dist = DotProduct(center, plane.normal); PlaneClassify(&plane); if (PlaneDiff(r_refdef.view.origin, &plane) < 0) { // skip backfaces (except if nocullface is set) // if (!(t->currentmaterialflags & MATERIALFLAG_NOCULLFACE)) // return; VectorNegate(plane.normal, plane.normal); plane.dist *= -1; PlaneClassify(&plane); } // find a matching plane if there is one bestplaneindex = -1; bestplanescore = 1048576.0f; for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) { if(p->camera_entity == t->camera_entity) { planescore = 1.0f - DotProduct(plane.normal, p->plane.normal) + fabs(plane.dist - p->plane.dist) * 0.001f; if (bestplaneindex < 0 || bestplanescore > planescore) { bestplaneindex = planeindex; bestplanescore = planescore; } } } planeindex = bestplaneindex; // if this surface does not fit any known plane rendered this frame, add one if (planeindex < 0 || bestplanescore > 0.001f) { if (r_fb.water.numwaterplanes < r_fb.water.maxwaterplanes) { // store the new plane planeindex = r_fb.water.numwaterplanes++; p = r_fb.water.waterplanes + planeindex; p->plane = plane; // clear materialflags and pvs p->materialflags = 0; p->pvsvalid = false; p->camera_entity = t->camera_entity; VectorCopy(mins, p->mins); VectorCopy(maxs, p->maxs); } else { // We're totally screwed. return; } } else { // merge mins/maxs when we're adding this surface to the plane p = r_fb.water.waterplanes + planeindex; p->mins[0] = min(p->mins[0], mins[0]); p->mins[1] = min(p->mins[1], mins[1]); p->mins[2] = min(p->mins[2], mins[2]); p->maxs[0] = max(p->maxs[0], maxs[0]); p->maxs[1] = max(p->maxs[1], maxs[1]); p->maxs[2] = max(p->maxs[2], maxs[2]); } // merge this surface's materialflags into the waterplane p->materialflags |= t->currentmaterialflags; if(!(p->materialflags & MATERIALFLAG_CAMERA)) { // merge this surface's PVS into the waterplane if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS && r_refdef.scene.worldmodel->brush.PointInLeaf && r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, center)->clusterindex >= 0) { r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, center, 2, p->pvsbits, sizeof(p->pvsbits), p->pvsvalid); p->pvsvalid = true; } } } extern cvar_t r_drawparticles; extern cvar_t r_drawdecals; static void R_Water_ProcessPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { int myscissor[4]; r_refdef_view_t originalview; r_refdef_view_t myview; int planeindex, qualityreduction = 0, old_r_dynamic = 0, old_r_shadows = 0, old_r_worldrtlight = 0, old_r_dlight = 0, old_r_particles = 0, old_r_decals = 0; r_waterstate_waterplane_t *p; vec3_t visorigin; qboolean usewaterfbo = (r_viewfbo.integer >= 1 || r_water_fbo.integer >= 1) && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; char vabuf[1024]; originalview = r_refdef.view; // lowquality hack, temporarily shut down some cvars and restore afterwards qualityreduction = r_water_lowquality.integer; if (qualityreduction > 0) { if (qualityreduction >= 1) { old_r_shadows = r_shadows.integer; old_r_worldrtlight = r_shadow_realtime_world.integer; old_r_dlight = r_shadow_realtime_dlight.integer; Cvar_SetValueQuick(&r_shadows, 0); Cvar_SetValueQuick(&r_shadow_realtime_world, 0); Cvar_SetValueQuick(&r_shadow_realtime_dlight, 0); } if (qualityreduction >= 2) { old_r_dynamic = r_dynamic.integer; old_r_particles = r_drawparticles.integer; old_r_decals = r_drawdecals.integer; Cvar_SetValueQuick(&r_dynamic, 0); Cvar_SetValueQuick(&r_drawparticles, 0); Cvar_SetValueQuick(&r_drawdecals, 0); } } // make sure enough textures are allocated for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) { if (r_water_cameraentitiesonly.value != 0 && !p->camera_entity) continue; if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) { if (!p->texture_refraction) p->texture_refraction = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_refraction", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); if (!p->texture_refraction) goto error; if (usewaterfbo) { if (r_fb.water.depthtexture == NULL) r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); if (p->fbo_refraction == 0) p->fbo_refraction = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_refraction, NULL, NULL, NULL); } } else if (p->materialflags & MATERIALFLAG_CAMERA) { if (!p->texture_camera) p->texture_camera = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_camera", planeindex), r_fb.water.camerawidth, r_fb.water.cameraheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR, -1, NULL); if (!p->texture_camera) goto error; if (usewaterfbo) { if (r_fb.water.depthtexture == NULL) r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); if (p->fbo_camera == 0) p->fbo_camera = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_camera, NULL, NULL, NULL); } } if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) { if (!p->texture_reflection) p->texture_reflection = R_LoadTexture2D(r_main_texturepool, va(vabuf, sizeof(vabuf), "waterplane%i_reflection", planeindex), r_fb.water.texturewidth, r_fb.water.textureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); if (!p->texture_reflection) goto error; if (usewaterfbo) { if (r_fb.water.depthtexture == NULL) r_fb.water.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "waterviewdepth", r_fb.water.texturewidth, r_fb.water.textureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); if (p->fbo_reflection == 0) p->fbo_reflection = R_Mesh_CreateFramebufferObject(r_fb.water.depthtexture, p->texture_reflection, NULL, NULL, NULL); } } } // render views r_refdef.view = originalview; r_refdef.view.showdebug = false; r_refdef.view.width = r_fb.water.waterwidth; r_refdef.view.height = r_fb.water.waterheight; r_refdef.view.useclipplane = true; myview = r_refdef.view; r_fb.water.renderingscene = true; for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) { if (r_water_cameraentitiesonly.value != 0 && !p->camera_entity) continue; if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION)) { r_refdef.view = myview; if(r_water_scissormode.integer) { R_SetupView(true, p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible } // render reflected scene and copy into texture Matrix4x4_Reflect(&r_refdef.view.matrix, p->plane.normal[0], p->plane.normal[1], p->plane.normal[2], p->plane.dist, -2); // update the r_refdef.view.origin because otherwise the sky renders at the wrong location (amongst other problems) Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, r_refdef.view.origin); r_refdef.view.clipplane = p->plane; // reverse the cullface settings for this render r_refdef.view.cullface_front = GL_FRONT; r_refdef.view.cullface_back = GL_BACK; if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.num_pvsclusterbytes) { r_refdef.view.usecustompvs = true; if (p->pvsvalid) memcpy(r_refdef.viewcache.world_pvsbits, p->pvsbits, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); else memset(r_refdef.viewcache.world_pvsbits, 0xFF, r_refdef.scene.worldmodel->brush.num_pvsclusterbytes); } r_fb.water.hideplayer = ((r_water_hideplayer.integer >= 2) && !chase_active.integer); R_ResetViewRendering3D(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); R_ClearScreen(r_refdef.fogenabled); if(r_water_scissormode.integer & 2) R_View_UpdateWithScissor(myscissor); else R_View_Update(); R_AnimCache_CacheVisibleEntities(); if(r_water_scissormode.integer & 1) GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); R_RenderScene(p->fbo_reflection, r_fb.water.depthtexture, p->texture_reflection); if (!p->fbo_reflection) R_Mesh_CopyToTexture(p->texture_reflection, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_fb.water.hideplayer = false; } // render the normal view scene and copy into texture // (except that a clipping plane should be used to hide everything on one side of the water, and the viewer's weapon model should be omitted) if (p->materialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) { r_refdef.view = myview; if(r_water_scissormode.integer) { R_SetupView(true, p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); if(R_ScissorForBBox(p->mins, p->maxs, myscissor)) continue; // FIXME the plane then still may get rendered but with broken texture, but it sure won't be visible } r_fb.water.hideplayer = ((r_water_hideplayer.integer >= 1) && !chase_active.integer); r_refdef.view.clipplane = p->plane; VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; if((p->materialflags & MATERIALFLAG_CAMERA) && p->camera_entity) { // we need to perform a matrix transform to render the view... so let's get the transformation matrix r_fb.water.hideplayer = false; // we don't want to hide the player model from these ones CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); R_RenderView_UpdateViewVectors(); if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) { r_refdef.view.usecustompvs = true; r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); } } PlaneClassify(&r_refdef.view.clipplane); R_ResetViewRendering3D(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); R_ClearScreen(r_refdef.fogenabled); if(r_water_scissormode.integer & 2) R_View_UpdateWithScissor(myscissor); else R_View_Update(); R_AnimCache_CacheVisibleEntities(); if(r_water_scissormode.integer & 1) GL_Scissor(myscissor[0], myscissor[1], myscissor[2], myscissor[3]); R_RenderScene(p->fbo_refraction, r_fb.water.depthtexture, p->texture_refraction); if (!p->fbo_refraction) R_Mesh_CopyToTexture(p->texture_refraction, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_fb.water.hideplayer = false; } else if (p->materialflags & MATERIALFLAG_CAMERA) { r_refdef.view = myview; r_refdef.view.clipplane = p->plane; VectorNegate(r_refdef.view.clipplane.normal, r_refdef.view.clipplane.normal); r_refdef.view.clipplane.dist = -r_refdef.view.clipplane.dist; r_refdef.view.width = r_fb.water.camerawidth; r_refdef.view.height = r_fb.water.cameraheight; r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); r_refdef.view.ortho_x = 90; // abused as angle by VM_CL_R_SetView r_refdef.view.ortho_y = 90; // abused as angle by VM_CL_R_SetView if(p->camera_entity) { // we need to perform a matrix transform to render the view... so let's get the transformation matrix CL_VM_TransformView(p->camera_entity - MAX_EDICTS, &r_refdef.view.matrix, &r_refdef.view.clipplane, visorigin); } // note: all of the view is used for displaying... so // there is no use in scissoring // reverse the cullface settings for this render r_refdef.view.cullface_front = GL_FRONT; r_refdef.view.cullface_back = GL_BACK; // also reverse the view matrix Matrix4x4_ConcatScale3(&r_refdef.view.matrix, 1, 1, -1); // this serves to invert texcoords in the result, as the copied texture is mapped the wrong way round R_RenderView_UpdateViewVectors(); if(p->camera_entity && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.FatPVS) { r_refdef.view.usecustompvs = true; r_refdef.scene.worldmodel->brush.FatPVS(r_refdef.scene.worldmodel, visorigin, 2, r_refdef.viewcache.world_pvsbits, (r_refdef.viewcache.world_numclusters+7)>>3, false); } // camera needs no clipplane r_refdef.view.useclipplane = false; PlaneClassify(&r_refdef.view.clipplane); r_fb.water.hideplayer = false; R_ResetViewRendering3D(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera); R_ClearScreen(r_refdef.fogenabled); R_View_Update(); R_AnimCache_CacheVisibleEntities(); R_RenderScene(p->fbo_camera, r_fb.water.depthtexture, p->texture_camera); if (!p->fbo_camera) R_Mesh_CopyToTexture(p->texture_camera, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_fb.water.hideplayer = false; } } if(vid.renderpath==RENDERPATH_SOFT) DPSOFTRAST_ClipPlane(0, 0, 0, 1); r_fb.water.renderingscene = false; r_refdef.view = originalview; R_ResetViewRendering3D(fbo, depthtexture, colortexture); if (!r_fb.water.depthtexture) R_ClearScreen(r_refdef.fogenabled); R_View_Update(); R_AnimCache_CacheVisibleEntities(); goto finish; error: r_refdef.view = originalview; r_fb.water.renderingscene = false; Cvar_SetValueQuick(&r_water, 0); Con_Printf("R_Water_ProcessPlanes: Error: texture creation failed! Turned off r_water.\n"); finish: // lowquality hack, restore cvars if (qualityreduction > 0) { if (qualityreduction >= 1) { Cvar_SetValueQuick(&r_shadows, old_r_shadows); Cvar_SetValueQuick(&r_shadow_realtime_world, old_r_worldrtlight); Cvar_SetValueQuick(&r_shadow_realtime_dlight, old_r_dlight); } if (qualityreduction >= 2) { Cvar_SetValueQuick(&r_dynamic, old_r_dynamic); Cvar_SetValueQuick(&r_drawparticles, old_r_particles); Cvar_SetValueQuick(&r_drawdecals, old_r_decals); } } } static void R_Bloom_StartFrame(void) { int i; int bloomtexturewidth, bloomtextureheight, screentexturewidth, screentextureheight; int viewwidth, viewheight; qboolean useviewfbo = r_viewfbo.integer >= 1 && vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two && vid.samples < 2; textype_t textype = TEXTYPE_COLORBUFFER; switch (vid.renderpath) { case RENDERPATH_GL20: r_fb.usedepthtextures = r_usedepthtextures.integer != 0; if (vid.support.ext_framebuffer_object && vid.support.arb_texture_non_power_of_two) { if (r_viewfbo.integer == 2) textype = TEXTYPE_COLORBUFFER16F; if (r_viewfbo.integer == 3) textype = TEXTYPE_COLORBUFFER32F; } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: case RENDERPATH_GLES2: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: r_fb.usedepthtextures = false; break; case RENDERPATH_SOFT: r_fb.usedepthtextures = true; break; } if (r_viewscale_fpsscaling.integer) { double actualframetime; double targetframetime; double adjust; actualframetime = r_refdef.lastdrawscreentime; targetframetime = (1.0 / r_viewscale_fpsscaling_target.value); adjust = (targetframetime - actualframetime) * r_viewscale_fpsscaling_multiply.value; adjust = bound(-r_viewscale_fpsscaling_stepmax.value, adjust, r_viewscale_fpsscaling_stepmax.value); if (r_viewscale_fpsscaling_stepsize.value > 0) adjust = (int)(adjust / r_viewscale_fpsscaling_stepsize.value) * r_viewscale_fpsscaling_stepsize.value; viewscalefpsadjusted += adjust; viewscalefpsadjusted = bound(r_viewscale_fpsscaling_min.value, viewscalefpsadjusted, 1.0f); } else viewscalefpsadjusted = 1.0f; R_GetScaledViewSize(r_refdef.view.width, r_refdef.view.height, &viewwidth, &viewheight); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: return; } // set bloomwidth and bloomheight to the bloom resolution that will be // used (often less than the screen resolution for faster rendering) r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, vid.width); r_fb.bloomheight = r_fb.bloomwidth * vid.height / vid.width; r_fb.bloomheight = bound(1, r_fb.bloomheight, vid.height); r_fb.bloomwidth = bound(1, r_fb.bloomwidth, (int)vid.maxtexturesize_2d); r_fb.bloomheight = bound(1, r_fb.bloomheight, (int)vid.maxtexturesize_2d); // calculate desired texture sizes if (vid.support.arb_texture_non_power_of_two) { screentexturewidth = vid.width; screentextureheight = vid.height; bloomtexturewidth = r_fb.bloomwidth; bloomtextureheight = r_fb.bloomheight; } else { for (screentexturewidth = 1;screentexturewidth < vid.width ;screentexturewidth *= 2); for (screentextureheight = 1;screentextureheight < vid.height ;screentextureheight *= 2); for (bloomtexturewidth = 1;bloomtexturewidth < r_fb.bloomwidth ;bloomtexturewidth *= 2); for (bloomtextureheight = 1;bloomtextureheight < r_fb.bloomheight;bloomtextureheight *= 2); } if ((r_bloom.integer || (!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0))) && ((r_bloom_resolution.integer < 4 || r_bloom_blur.value < 1 || r_bloom_blur.value >= 512) || r_refdef.view.width > (int)vid.maxtexturesize_2d || r_refdef.view.height > (int)vid.maxtexturesize_2d)) { Cvar_SetValueQuick(&r_bloom, 0); Cvar_SetValueQuick(&r_motionblur, 0); Cvar_SetValueQuick(&r_damageblur, 0); } if (!((r_glsl_postprocess.integer || r_fxaa.integer) || (!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) || (v_glslgamma.integer && !vid_gammatables_trivial)) && !r_bloom.integer && (R_Stereo_Active() || (r_motionblur.value <= 0 && r_damageblur.value <= 0)) && !useviewfbo && r_viewscale.value == 1.0f && !r_viewscale_fpsscaling.integer) screentexturewidth = screentextureheight = 0; if (!r_bloom.integer) bloomtexturewidth = bloomtextureheight = 0; // allocate textures as needed if (r_fb.screentexturewidth != screentexturewidth || r_fb.screentextureheight != screentextureheight || r_fb.bloomtexturewidth != bloomtexturewidth || r_fb.bloomtextureheight != bloomtextureheight || r_fb.textype != textype || useviewfbo != (r_fb.fbo != 0)) { for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++) { if (r_fb.bloomtexture[i]) R_FreeTexture(r_fb.bloomtexture[i]); r_fb.bloomtexture[i] = NULL; if (r_fb.bloomfbo[i]) R_Mesh_DestroyFramebufferObject(r_fb.bloomfbo[i]); r_fb.bloomfbo[i] = 0; } if (r_fb.fbo) R_Mesh_DestroyFramebufferObject(r_fb.fbo); r_fb.fbo = 0; if (r_fb.colortexture) R_FreeTexture(r_fb.colortexture); r_fb.colortexture = NULL; if (r_fb.depthtexture) R_FreeTexture(r_fb.depthtexture); r_fb.depthtexture = NULL; if (r_fb.ghosttexture) R_FreeTexture(r_fb.ghosttexture); r_fb.ghosttexture = NULL; r_fb.screentexturewidth = screentexturewidth; r_fb.screentextureheight = screentextureheight; r_fb.bloomtexturewidth = bloomtexturewidth; r_fb.bloomtextureheight = bloomtextureheight; r_fb.textype = textype; if (r_fb.screentexturewidth && r_fb.screentextureheight) { if (r_motionblur.value > 0 || r_damageblur.value > 0) r_fb.ghosttexture = R_LoadTexture2D(r_main_texturepool, "framebuffermotionblur", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); r_fb.ghosttexture_valid = false; r_fb.colortexture = R_LoadTexture2D(r_main_texturepool, "framebuffercolor", r_fb.screentexturewidth, r_fb.screentextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); if (useviewfbo) { r_fb.depthtexture = R_LoadTextureRenderBuffer(r_main_texturepool, "framebufferdepth", r_fb.screentexturewidth, r_fb.screentextureheight, TEXTYPE_DEPTHBUFFER24STENCIL8); r_fb.fbo = R_Mesh_CreateFramebufferObject(r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL); R_Mesh_SetRenderTargets(r_fb.fbo, r_fb.depthtexture, r_fb.colortexture, NULL, NULL, NULL); } } if (r_fb.bloomtexturewidth && r_fb.bloomtextureheight) { for (i = 0;i < (int)(sizeof(r_fb.bloomtexture)/sizeof(r_fb.bloomtexture[i]));i++) { r_fb.bloomtexture[i] = R_LoadTexture2D(r_main_texturepool, "framebufferbloom", r_fb.bloomtexturewidth, r_fb.bloomtextureheight, NULL, r_fb.textype, TEXF_RENDERTARGET | TEXF_FORCELINEAR | TEXF_CLAMP, -1, NULL); if (useviewfbo) r_fb.bloomfbo[i] = R_Mesh_CreateFramebufferObject(NULL, r_fb.bloomtexture[i], NULL, NULL, NULL); } } } // bloom texture is a different resolution r_fb.bloomwidth = bound(1, r_bloom_resolution.integer, r_refdef.view.width); r_fb.bloomheight = r_fb.bloomwidth * r_refdef.view.height / r_refdef.view.width; r_fb.bloomheight = bound(1, r_fb.bloomheight, r_refdef.view.height); r_fb.bloomwidth = bound(1, r_fb.bloomwidth, r_fb.bloomtexturewidth); r_fb.bloomheight = bound(1, r_fb.bloomheight, r_fb.bloomtextureheight); // set up a texcoord array for the full resolution screen image // (we have to keep this around to copy back during final render) r_fb.screentexcoord2f[0] = 0; r_fb.screentexcoord2f[1] = (float)viewheight / (float)r_fb.screentextureheight; r_fb.screentexcoord2f[2] = (float)viewwidth / (float)r_fb.screentexturewidth; r_fb.screentexcoord2f[3] = (float)viewheight / (float)r_fb.screentextureheight; r_fb.screentexcoord2f[4] = (float)viewwidth / (float)r_fb.screentexturewidth; r_fb.screentexcoord2f[5] = 0; r_fb.screentexcoord2f[6] = 0; r_fb.screentexcoord2f[7] = 0; if(r_fb.fbo) { for (i = 1;i < 8;i += 2) { r_fb.screentexcoord2f[i] += 1 - (float)(viewheight + r_refdef.view.y) / (float)r_fb.screentextureheight; } } // set up a texcoord array for the reduced resolution bloom image // (which will be additive blended over the screen image) r_fb.bloomtexcoord2f[0] = 0; r_fb.bloomtexcoord2f[1] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight; r_fb.bloomtexcoord2f[2] = (float)r_fb.bloomwidth / (float)r_fb.bloomtexturewidth; r_fb.bloomtexcoord2f[3] = (float)r_fb.bloomheight / (float)r_fb.bloomtextureheight; r_fb.bloomtexcoord2f[4] = (float)r_fb.bloomwidth / (float)r_fb.bloomtexturewidth; r_fb.bloomtexcoord2f[5] = 0; r_fb.bloomtexcoord2f[6] = 0; r_fb.bloomtexcoord2f[7] = 0; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: for (i = 0;i < 4;i++) { r_fb.screentexcoord2f[i*2+0] += 0.5f / (float)r_fb.screentexturewidth; r_fb.screentexcoord2f[i*2+1] += 0.5f / (float)r_fb.screentextureheight; r_fb.bloomtexcoord2f[i*2+0] += 0.5f / (float)r_fb.bloomtexturewidth; r_fb.bloomtexcoord2f[i*2+1] += 0.5f / (float)r_fb.bloomtextureheight; } break; } R_Viewport_InitOrtho(&r_fb.bloomviewport, &identitymatrix, 0, 0, r_fb.bloomwidth, r_fb.bloomheight, 0, 0, 1, 1, -10, 100, NULL); if (r_fb.fbo) r_refdef.view.clear = true; } static void R_Bloom_MakeTexture(void) { int x, range, dir; float xoffset, yoffset, r, brighten; rtexture_t *intex; float colorscale = r_bloom_colorscale.value; r_refdef.stats[r_stat_bloom]++; #if 0 // this copy is unnecessary since it happens in R_BlendView already if (!r_fb.fbo) { R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; } #endif // scale down screen texture to the bloom texture size CHECKGLERROR r_fb.bloomindex = 0; R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); R_SetViewport(&r_fb.bloomviewport); GL_DepthTest(false); GL_BlendFunc(GL_ONE, GL_ZERO); GL_Color(colorscale, colorscale, colorscale, 1); // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: case RENDERPATH_SOFT: R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f); break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f); break; } // TODO: do boxfilter scale-down in shader? R_SetupShader_Generic(r_fb.colortexture, NULL, GL_MODULATE, 1, false, true, true); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; // we now have a properly scaled bloom image if (!r_fb.bloomfbo[r_fb.bloomindex]) { // copy it into the bloom texture R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; } // multiply bloom image by itself as many times as desired for (x = 1;x < min(r_bloom_colorexponent.value, 32);) { intex = r_fb.bloomtexture[r_fb.bloomindex]; r_fb.bloomindex ^= 1; R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); x *= 2; r = bound(0, r_bloom_colorexponent.value / x, 1); // always 0.5 to 1 if (!r_fb.bloomfbo[r_fb.bloomindex]) { GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); // square it and multiply by two GL_Color(r,r,r,1); // apply fix factor } else { if(x <= 2) GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 128); GL_BlendFunc(GL_SRC_COLOR, GL_ZERO); // square it GL_Color(1,1,1,1); // no fix factor supported here } R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.bloomtexcoord2f); R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; if (!r_fb.bloomfbo[r_fb.bloomindex]) { // copy the darkened image to a texture R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; } } range = r_bloom_blur.integer * r_fb.bloomwidth / 320; brighten = r_bloom_brighten.value; brighten = sqrt(brighten); if(range >= 1) brighten *= (3 * range) / (2 * range - 1); // compensate for the "dot particle" for (dir = 0;dir < 2;dir++) { intex = r_fb.bloomtexture[r_fb.bloomindex]; r_fb.bloomindex ^= 1; R_Mesh_SetRenderTargets(r_fb.bloomfbo[r_fb.bloomindex], NULL, r_fb.bloomtexture[r_fb.bloomindex], NULL, NULL, NULL); // blend on at multiple vertical offsets to achieve a vertical blur // TODO: do offset blends using GLSL // TODO instead of changing the texcoords, change the target positions to prevent artifacts at edges GL_BlendFunc(GL_ONE, GL_ZERO); R_SetupShader_Generic(intex, NULL, GL_MODULATE, 1, false, true, false); for (x = -range;x <= range;x++) { if (!dir){xoffset = 0;yoffset = x;} else {xoffset = x;yoffset = 0;} xoffset /= (float)r_fb.bloomtexturewidth; yoffset /= (float)r_fb.bloomtextureheight; // compute a texcoord array with the specified x and y offset r_fb.offsettexcoord2f[0] = xoffset+r_fb.bloomtexcoord2f[0]; r_fb.offsettexcoord2f[1] = yoffset+r_fb.bloomtexcoord2f[1]; r_fb.offsettexcoord2f[2] = xoffset+r_fb.bloomtexcoord2f[2]; r_fb.offsettexcoord2f[3] = yoffset+r_fb.bloomtexcoord2f[3]; r_fb.offsettexcoord2f[4] = xoffset+r_fb.bloomtexcoord2f[4]; r_fb.offsettexcoord2f[5] = yoffset+r_fb.bloomtexcoord2f[5]; r_fb.offsettexcoord2f[6] = xoffset+r_fb.bloomtexcoord2f[6]; r_fb.offsettexcoord2f[7] = yoffset+r_fb.bloomtexcoord2f[7]; // this r value looks like a 'dot' particle, fading sharply to // black at the edges // (probably not realistic but looks good enough) //r = ((range*range+1)/((float)(x*x+1)))/(range*2+1); //r = brighten/(range*2+1); r = brighten / (range * 2 + 1); if(range >= 1) r *= (1 - x*x/(float)(range*range)); GL_Color(r, r, r, 1); R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.offsettexcoord2f); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); r_refdef.stats[r_stat_bloom_drawpixels] += r_fb.bloomwidth * r_fb.bloomheight; GL_BlendFunc(GL_ONE, GL_ONE); } if (!r_fb.bloomfbo[r_fb.bloomindex]) { // copy the vertically or horizontally blurred bloom view to a texture R_Mesh_CopyToTexture(r_fb.bloomtexture[r_fb.bloomindex], 0, 0, r_fb.bloomviewport.x, r_fb.bloomviewport.y, r_fb.bloomviewport.width, r_fb.bloomviewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_fb.bloomviewport.width * r_fb.bloomviewport.height; } } } static void R_BlendView(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { unsigned int permutation; float uservecs[4][4]; R_EntityMatrix(&identitymatrix); switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: permutation = (r_fb.bloomtexture[r_fb.bloomindex] ? SHADERPERMUTATION_BLOOM : 0) | (r_refdef.viewblend[3] > 0 ? SHADERPERMUTATION_VIEWTINT : 0) | ((v_glslgamma.value && !vid_gammatables_trivial) ? SHADERPERMUTATION_GAMMARAMPS : 0) | (r_glsl_postprocess.integer ? SHADERPERMUTATION_POSTPROCESSING : 0) | ((!R_Stereo_ColorMasking() && r_glsl_saturation.value != 1) ? SHADERPERMUTATION_SATURATION : 0); if (r_fb.colortexture) { if (!r_fb.fbo) { R_Mesh_CopyToTexture(r_fb.colortexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; } if(!R_Stereo_Active() && (r_motionblur.value > 0 || r_damageblur.value > 0) && r_fb.ghosttexture) { // declare variables float blur_factor, blur_mouseaccel, blur_velocity; static float blur_average; static vec3_t blur_oldangles; // used to see how quickly the mouse is moving // set a goal for the factoring blur_velocity = bound(0, (VectorLength(cl.movement_velocity) - r_motionblur_velocityfactor_minspeed.value) / max(1, r_motionblur_velocityfactor_maxspeed.value - r_motionblur_velocityfactor_minspeed.value), 1); blur_mouseaccel = bound(0, ((fabs(VectorLength(cl.viewangles) - VectorLength(blur_oldangles)) * 10) - r_motionblur_mousefactor_minspeed.value) / max(1, r_motionblur_mousefactor_maxspeed.value - r_motionblur_mousefactor_minspeed.value), 1); blur_factor = ((blur_velocity * r_motionblur_velocityfactor.value) + (blur_mouseaccel * r_motionblur_mousefactor.value)); // from the goal, pick an averaged value between goal and last value cl.motionbluralpha = bound(0, (cl.time - cl.oldtime) / max(0.001, r_motionblur_averaging.value), 1); blur_average = blur_average * (1 - cl.motionbluralpha) + blur_factor * cl.motionbluralpha; // enforce minimum amount of blur blur_factor = blur_average * (1 - r_motionblur_minblur.value) + r_motionblur_minblur.value; //Con_Printf("motionblur: direct factor: %f, averaged factor: %f, velocity: %f, mouse accel: %f \n", blur_factor, blur_average, blur_velocity, blur_mouseaccel); // calculate values into a standard alpha cl.motionbluralpha = 1 - exp(- ( (r_motionblur.value * blur_factor / 80) + (r_damageblur.value * (cl.cshifts[CSHIFT_DAMAGE].percent / 1600)) ) / max(0.0001, cl.time - cl.oldtime) // fps independent ); // randomization for the blur value to combat persistent ghosting cl.motionbluralpha *= lhrandom(1 - r_motionblur_randomize.value, 1 + r_motionblur_randomize.value); cl.motionbluralpha = bound(0, cl.motionbluralpha, r_motionblur_maxblur.value); // apply the blur R_ResetViewRendering2D(fbo, depthtexture, colortexture); if (cl.motionbluralpha > 0 && !r_refdef.envmap && r_fb.ghosttexture_valid) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_Color(1, 1, 1, cl.motionbluralpha); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: case RENDERPATH_SOFT: R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, r_fb.screentexcoord2f); break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: R_Mesh_PrepareVertices_Generic_Arrays(4, r_d3dscreenvertex3f, NULL, r_fb.screentexcoord2f); break; } R_SetupShader_Generic(r_fb.ghosttexture, NULL, GL_MODULATE, 1, false, true, true); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; } // updates old view angles for next pass VectorCopy(cl.viewangles, blur_oldangles); // copy view into the ghost texture R_Mesh_CopyToTexture(r_fb.ghosttexture, 0, 0, r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_refdef.stats[r_stat_bloom_copypixels] += r_refdef.view.viewport.width * r_refdef.view.viewport.height; r_fb.ghosttexture_valid = true; } } else { // no r_fb.colortexture means we're rendering to the real fb // we may still have to do view tint... if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) { // apply a color tint to the whole view R_ResetViewRendering2D(0, NULL, NULL); GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); R_SetupShader_Generic_NoTexture(false, true); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } break; // no screen processing, no bloom, skip it } if (r_fb.bloomtexture[0]) { // make the bloom texture R_Bloom_MakeTexture(); } #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif memset(uservecs, 0, sizeof(uservecs)); if (r_glsl_postprocess_uservec1_enable.integer) sscanf(r_glsl_postprocess_uservec1.string, "%f %f %f %f", &uservecs[0][0], &uservecs[0][1], &uservecs[0][2], &uservecs[0][3]); if (r_glsl_postprocess_uservec2_enable.integer) sscanf(r_glsl_postprocess_uservec2.string, "%f %f %f %f", &uservecs[1][0], &uservecs[1][1], &uservecs[1][2], &uservecs[1][3]); if (r_glsl_postprocess_uservec3_enable.integer) sscanf(r_glsl_postprocess_uservec3.string, "%f %f %f %f", &uservecs[2][0], &uservecs[2][1], &uservecs[2][2], &uservecs[2][3]); if (r_glsl_postprocess_uservec4_enable.integer) sscanf(r_glsl_postprocess_uservec4.string, "%f %f %f %f", &uservecs[3][0], &uservecs[3][1], &uservecs[3][2], &uservecs[3][3]); R_ResetViewRendering2D(0, NULL, NULL); // here we render to the real framebuffer! GL_Color(1, 1, 1, 1); GL_BlendFunc(GL_ONE, GL_ZERO); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); R_SetupShader_SetPermutationGLSL(SHADERMODE_POSTPROCESS, permutation); if (r_glsl_permutation->tex_Texture_First >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_First , r_fb.colortexture); if (r_glsl_permutation->tex_Texture_Second >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_Second , r_fb.bloomtexture[r_fb.bloomindex]); if (r_glsl_permutation->tex_Texture_GammaRamps >= 0) R_Mesh_TexBind(r_glsl_permutation->tex_Texture_GammaRamps, r_texture_gammaramps ); if (r_glsl_permutation->loc_ViewTintColor >= 0) qglUniform4f(r_glsl_permutation->loc_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); if (r_glsl_permutation->loc_PixelSize >= 0) qglUniform2f(r_glsl_permutation->loc_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); if (r_glsl_permutation->loc_UserVec1 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); if (r_glsl_permutation->loc_UserVec2 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); if (r_glsl_permutation->loc_UserVec3 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); if (r_glsl_permutation->loc_UserVec4 >= 0) qglUniform4f(r_glsl_permutation->loc_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); if (r_glsl_permutation->loc_Saturation >= 0) qglUniform1f(r_glsl_permutation->loc_Saturation , r_glsl_saturation.value); if (r_glsl_permutation->loc_PixelToScreenTexCoord >= 0) qglUniform2f(r_glsl_permutation->loc_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); if (r_glsl_permutation->loc_BloomColorSubtract >= 0) qglUniform4f(r_glsl_permutation->loc_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D // D3D has upside down Y coords, the easiest way to flip this is to flip the screen vertices rather than the texcoords, so we just use a different array for that... R_Mesh_PrepareVertices_Mesh_Arrays(4, r_d3dscreenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); R_SetupShader_SetPermutationHLSL(SHADERMODE_POSTPROCESS, permutation); R_Mesh_TexBind(GL20TU_FIRST , r_fb.colortexture); R_Mesh_TexBind(GL20TU_SECOND , r_fb.bloomtexture[r_fb.bloomindex]); R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); hlslPSSetParameter4f(D3DPSREGISTER_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); hlslPSSetParameter2f(D3DPSREGISTER_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); hlslPSSetParameter4f(D3DPSREGISTER_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); hlslPSSetParameter4f(D3DPSREGISTER_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); hlslPSSetParameter4f(D3DPSREGISTER_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); hlslPSSetParameter4f(D3DPSREGISTER_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); hlslPSSetParameter1f(D3DPSREGISTER_Saturation , r_glsl_saturation.value); hlslPSSetParameter2f(D3DPSREGISTER_PixelToScreenTexCoord, 1.0f/vid.width, 1.0/vid.height); hlslPSSetParameter4f(D3DPSREGISTER_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: R_Mesh_PrepareVertices_Mesh_Arrays(4, r_screenvertex3f, NULL, NULL, NULL, NULL, r_fb.screentexcoord2f, r_fb.bloomtexcoord2f); R_SetupShader_SetPermutationSoft(SHADERMODE_POSTPROCESS, permutation); R_Mesh_TexBind(GL20TU_FIRST , r_fb.colortexture); R_Mesh_TexBind(GL20TU_SECOND , r_fb.bloomtexture[r_fb.bloomindex]); R_Mesh_TexBind(GL20TU_GAMMARAMPS, r_texture_gammaramps ); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_ViewTintColor , r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelSize , 1.0/r_fb.screentexturewidth, 1.0/r_fb.screentextureheight); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec1 , uservecs[0][0], uservecs[0][1], uservecs[0][2], uservecs[0][3]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec2 , uservecs[1][0], uservecs[1][1], uservecs[1][2], uservecs[1][3]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec3 , uservecs[2][0], uservecs[2][1], uservecs[2][2], uservecs[2][3]); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_UserVec4 , uservecs[3][0], uservecs[3][1], uservecs[3][2], uservecs[3][3]); DPSOFTRAST_Uniform1f(DPSOFTRAST_UNIFORM_Saturation , r_glsl_saturation.value); DPSOFTRAST_Uniform2f(DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, 1.0f/vid.width, 1.0f/vid.height); DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM_BloomColorSubtract , r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, r_bloom_colorsubtract.value, 0.0f); break; default: break; } R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); r_refdef.stats[r_stat_bloom_drawpixels] += r_refdef.view.width * r_refdef.view.height; break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: if (r_refdef.viewblend[3] >= (1.0f / 256.0f)) { // apply a color tint to the whole view R_ResetViewRendering2D(0, NULL, NULL); GL_Color(r_refdef.viewblend[0], r_refdef.viewblend[1], r_refdef.viewblend[2], r_refdef.viewblend[3]); R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); R_SetupShader_Generic_NoTexture(false, true); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } break; } } matrix4x4_t r_waterscrollmatrix; void R_UpdateFog(void) { // Nehahra fog if (gamemode == GAME_NEHAHRA) { if (gl_fogenable.integer) { r_refdef.oldgl_fogenable = true; r_refdef.fog_density = gl_fogdensity.value; r_refdef.fog_red = gl_fogred.value; r_refdef.fog_green = gl_foggreen.value; r_refdef.fog_blue = gl_fogblue.value; r_refdef.fog_alpha = 1; r_refdef.fog_start = 0; r_refdef.fog_end = gl_skyclip.value; r_refdef.fog_height = 1<<30; r_refdef.fog_fadedepth = 128; } else if (r_refdef.oldgl_fogenable) { r_refdef.oldgl_fogenable = false; r_refdef.fog_density = 0; r_refdef.fog_red = 0; r_refdef.fog_green = 0; r_refdef.fog_blue = 0; r_refdef.fog_alpha = 0; r_refdef.fog_start = 0; r_refdef.fog_end = 0; r_refdef.fog_height = 1<<30; r_refdef.fog_fadedepth = 128; } } // fog parms r_refdef.fog_alpha = bound(0, r_refdef.fog_alpha, 1); r_refdef.fog_start = max(0, r_refdef.fog_start); r_refdef.fog_end = max(r_refdef.fog_start + 0.01, r_refdef.fog_end); if (r_refdef.fog_density && r_drawfog.integer) { r_refdef.fogenabled = true; // this is the point where the fog reaches 0.9986 alpha, which we // consider a good enough cutoff point for the texture // (0.9986 * 256 == 255.6) if (r_fog_exp2.integer) r_refdef.fogrange = 32 / (r_refdef.fog_density * r_refdef.fog_density) + r_refdef.fog_start; else r_refdef.fogrange = 2048 / r_refdef.fog_density + r_refdef.fog_start; r_refdef.fogrange = bound(r_refdef.fog_start, r_refdef.fogrange, r_refdef.fog_end); r_refdef.fograngerecip = 1.0f / r_refdef.fogrange; r_refdef.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * r_refdef.fograngerecip; if (strcmp(r_refdef.fogheighttexturename, r_refdef.fog_height_texturename)) R_BuildFogHeightTexture(); // fog color was already set // update the fog texture if (r_refdef.fogmasktable_start != r_refdef.fog_start || r_refdef.fogmasktable_alpha != r_refdef.fog_alpha || r_refdef.fogmasktable_density != r_refdef.fog_density || r_refdef.fogmasktable_range != r_refdef.fogrange) R_BuildFogTexture(); r_refdef.fog_height_texcoordscale = 1.0f / max(0.125f, r_refdef.fog_fadedepth); r_refdef.fog_height_tablescale = r_refdef.fog_height_tablesize * r_refdef.fog_height_texcoordscale; } else r_refdef.fogenabled = false; // fog color if (r_refdef.fog_density) { r_refdef.fogcolor[0] = r_refdef.fog_red; r_refdef.fogcolor[1] = r_refdef.fog_green; r_refdef.fogcolor[2] = r_refdef.fog_blue; Vector4Set(r_refdef.fogplane, 0, 0, 1, -r_refdef.fog_height); r_refdef.fogplaneviewdist = DotProduct(r_refdef.fogplane, r_refdef.view.origin) + r_refdef.fogplane[3]; r_refdef.fogplaneviewabove = r_refdef.fogplaneviewdist >= 0; r_refdef.fogheightfade = -0.5f/max(0.125f, r_refdef.fog_fadedepth); { vec3_t fogvec; VectorCopy(r_refdef.fogcolor, fogvec); // color.rgb *= ContrastBoost * SceneBrightness; VectorScale(fogvec, r_refdef.view.colorscale, fogvec); r_refdef.fogcolor[0] = bound(0.0f, fogvec[0], 1.0f); r_refdef.fogcolor[1] = bound(0.0f, fogvec[1], 1.0f); r_refdef.fogcolor[2] = bound(0.0f, fogvec[2], 1.0f); } } } void R_UpdateVariables(void) { R_Textures_Frame(); r_refdef.scene.ambient = r_ambient.value * (1.0f / 64.0f); r_refdef.farclip = r_farclip_base.value; if (r_refdef.scene.worldmodel) r_refdef.farclip += r_refdef.scene.worldmodel->radius * r_farclip_world.value * 2; r_refdef.nearclip = bound (0.001f, r_nearclip.value, r_refdef.farclip - 1.0f); if (r_shadow_frontsidecasting.integer < 0 || r_shadow_frontsidecasting.integer > 1) Cvar_SetValueQuick(&r_shadow_frontsidecasting, 1); r_refdef.polygonfactor = 0; r_refdef.polygonoffset = 0; r_refdef.shadowpolygonfactor = r_refdef.polygonfactor + r_shadow_polygonfactor.value * (r_shadow_frontsidecasting.integer ? 1 : -1); r_refdef.shadowpolygonoffset = r_refdef.polygonoffset + r_shadow_polygonoffset.value * (r_shadow_frontsidecasting.integer ? 1 : -1); r_refdef.scene.rtworld = r_shadow_realtime_world.integer != 0; r_refdef.scene.rtworldshadows = r_shadow_realtime_world_shadows.integer && vid.stencil; r_refdef.scene.rtdlight = r_shadow_realtime_dlight.integer != 0 && !gl_flashblend.integer && r_dynamic.integer; r_refdef.scene.rtdlightshadows = r_refdef.scene.rtdlight && r_shadow_realtime_dlight_shadows.integer && vid.stencil; r_refdef.lightmapintensity = r_refdef.scene.rtworld ? r_shadow_realtime_world_lightmaps.value : 1; if (FAKELIGHT_ENABLED) { r_refdef.lightmapintensity *= r_fakelight_intensity.value; } else if (r_refdef.scene.worldmodel) { r_refdef.lightmapintensity *= r_refdef.scene.worldmodel->lightmapscale; } if (r_showsurfaces.integer) { r_refdef.scene.rtworld = false; r_refdef.scene.rtworldshadows = false; r_refdef.scene.rtdlight = false; r_refdef.scene.rtdlightshadows = false; r_refdef.lightmapintensity = 0; } r_gpuskeletal = false; switch(vid.renderpath) { case RENDERPATH_GL20: r_gpuskeletal = vid.support.arb_uniform_buffer_object && r_glsl_skeletal.integer && !r_showsurfaces.integer; // FIXME add r_showsurfaces support to GLSL skeletal! case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: if(v_glslgamma.integer && !vid_gammatables_trivial) { if(!r_texture_gammaramps || vid_gammatables_serial != r_texture_gammaramps_serial) { // build GLSL gamma texture #define RAMPWIDTH 256 unsigned short ramp[RAMPWIDTH * 3]; unsigned char rampbgr[RAMPWIDTH][4]; int i; r_texture_gammaramps_serial = vid_gammatables_serial; VID_BuildGammaTables(&ramp[0], RAMPWIDTH); for(i = 0; i < RAMPWIDTH; ++i) { rampbgr[i][0] = (unsigned char) (ramp[i + 2 * RAMPWIDTH] * 255.0 / 65535.0 + 0.5); rampbgr[i][1] = (unsigned char) (ramp[i + RAMPWIDTH] * 255.0 / 65535.0 + 0.5); rampbgr[i][2] = (unsigned char) (ramp[i] * 255.0 / 65535.0 + 0.5); rampbgr[i][3] = 0; } if (r_texture_gammaramps) { R_UpdateTexture(r_texture_gammaramps, &rampbgr[0][0], 0, 0, 0, RAMPWIDTH, 1, 1); } else { r_texture_gammaramps = R_LoadTexture2D(r_main_texturepool, "gammaramps", RAMPWIDTH, 1, &rampbgr[0][0], TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_CLAMP | TEXF_PERSISTENT, -1, NULL); } } } else { // remove GLSL gamma texture } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: break; } } static r_refdef_scene_type_t r_currentscenetype = RST_CLIENT; static r_refdef_scene_t r_scenes_store[ RST_COUNT ]; /* ================ R_SelectScene ================ */ void R_SelectScene( r_refdef_scene_type_t scenetype ) { if( scenetype != r_currentscenetype ) { // store the old scenetype r_scenes_store[ r_currentscenetype ] = r_refdef.scene; r_currentscenetype = scenetype; // move in the new scene r_refdef.scene = r_scenes_store[ r_currentscenetype ]; } } /* ================ R_GetScenePointer ================ */ r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ) { // of course, we could also add a qboolean that provides a lock state and a ReleaseScenePointer function.. if( scenetype == r_currentscenetype ) { return &r_refdef.scene; } else { return &r_scenes_store[ scenetype ]; } } static int R_SortEntities_Compare(const void *ap, const void *bp) { const entity_render_t *a = *(const entity_render_t **)ap; const entity_render_t *b = *(const entity_render_t **)bp; // 1. compare model if(a->model < b->model) return -1; if(a->model > b->model) return +1; // 2. compare skin // TODO possibly calculate the REAL skinnum here first using // skinscenes? if(a->skinnum < b->skinnum) return -1; if(a->skinnum > b->skinnum) return +1; // everything we compared is equal return 0; } static void R_SortEntities(void) { // below or equal 2 ents, sorting never gains anything if(r_refdef.scene.numentities <= 2) return; // sort qsort(r_refdef.scene.entities, r_refdef.scene.numentities, sizeof(*r_refdef.scene.entities), R_SortEntities_Compare); } /* ================ R_RenderView ================ */ int dpsoftrast_test; extern cvar_t r_shadow_bouncegrid; void R_RenderView(void) { matrix4x4_t originalmatrix = r_refdef.view.matrix, offsetmatrix; int fbo; rtexture_t *depthtexture; rtexture_t *colortexture; dpsoftrast_test = r_test.integer; if (r_timereport_active) R_TimeReport("start"); r_textureframe++; // used only by R_GetCurrentTexture rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity if(R_CompileShader_CheckStaticParms()) R_GLSL_Restart_f(); if (!r_drawentities.integer) r_refdef.scene.numentities = 0; else if (r_sortentities.integer) R_SortEntities(); R_AnimCache_ClearCache(); /* adjust for stereo display */ if(R_Stereo_Active()) { Matrix4x4_CreateFromQuakeEntity(&offsetmatrix, 0, r_stereo_separation.value * (0.5f - r_stereo_side), 0, 0, r_stereo_angle.value * (0.5f - r_stereo_side), 0, 1); Matrix4x4_Concat(&r_refdef.view.matrix, &originalmatrix, &offsetmatrix); } if (r_refdef.view.isoverlay) { // TODO: FIXME: move this into its own backend function maybe? [2/5/2008 Andreas] R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); GL_Clear(GL_DEPTH_BUFFER_BIT, NULL, 1.0f, 0); R_TimeReport("depthclear"); r_refdef.view.showdebug = false; r_fb.water.enabled = false; r_fb.water.numwaterplanes = 0; R_RenderScene(0, NULL, NULL); r_refdef.view.matrix = originalmatrix; CHECKGLERROR return; } if (!r_refdef.scene.entities || r_refdef.view.width * r_refdef.view.height == 0 || !r_renderview.integer || cl_videoplaying/* || !r_refdef.scene.worldmodel*/) { r_refdef.view.matrix = originalmatrix; return; } r_refdef.view.colorscale = r_hdr_scenebrightness.value * r_hdr_irisadaptation_value.value; if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) // in sRGB fallback, behave similar to true sRGB: convert this // value from linear to sRGB r_refdef.view.colorscale = Image_sRGBFloatFromLinearFloat(r_refdef.view.colorscale); R_RenderView_UpdateViewVectors(); R_Shadow_UpdateWorldLightSelection(); R_Bloom_StartFrame(); // apply bloom brightness offset if(r_fb.bloomtexture[0]) r_refdef.view.colorscale *= r_bloom_scenebrightness.value; R_Water_StartFrame(); // now we probably have an fbo to render into fbo = r_fb.fbo; depthtexture = r_fb.depthtexture; colortexture = r_fb.colortexture; CHECKGLERROR if (r_timereport_active) R_TimeReport("viewsetup"); R_ResetViewRendering3D(fbo, depthtexture, colortexture); if (r_refdef.view.clear || r_refdef.fogenabled || fbo) { R_ClearScreen(r_refdef.fogenabled); if (r_timereport_active) R_TimeReport("viewclear"); } r_refdef.view.clear = true; r_refdef.view.showdebug = true; R_View_Update(); if (r_timereport_active) R_TimeReport("visibility"); R_AnimCache_CacheVisibleEntities(); if (r_timereport_active) R_TimeReport("animcache"); R_Shadow_UpdateBounceGridTexture(); if (r_timereport_active && r_shadow_bouncegrid.integer) R_TimeReport("bouncegrid"); r_fb.water.numwaterplanes = 0; if (r_fb.water.enabled) R_RenderWaterPlanes(fbo, depthtexture, colortexture); R_RenderScene(fbo, depthtexture, colortexture); r_fb.water.numwaterplanes = 0; R_BlendView(fbo, depthtexture, colortexture); if (r_timereport_active) R_TimeReport("blendview"); GL_Scissor(0, 0, vid.width, vid.height); GL_ScissorTest(false); r_refdef.view.matrix = originalmatrix; CHECKGLERROR } void R_RenderWaterPlanes(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawAddWaterPlanes) { r_refdef.scene.worldmodel->DrawAddWaterPlanes(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("waterworld"); } // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); R_DrawModelsAddWaterPlanes(); if (r_timereport_active) R_TimeReport("watermodels"); if (r_fb.water.numwaterplanes) { R_Water_ProcessPlanes(fbo, depthtexture, colortexture); if (r_timereport_active) R_TimeReport("waterscenes"); } } extern cvar_t cl_locs_show; static void R_DrawLocs(void); static void R_DrawEntityBBoxes(void); static void R_DrawModelDecals(void); extern cvar_t cl_decals_newsystem; extern qboolean r_shadow_usingdeferredprepass; void R_RenderScene(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { qboolean shadowmapping = false; if (r_timereport_active) R_TimeReport("beginscene"); r_refdef.stats[r_stat_renders]++; R_UpdateFog(); // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); R_MeshQueue_BeginScene(); R_SkyStartFrame(); Matrix4x4_CreateTranslate(&r_waterscrollmatrix, sin(r_refdef.scene.time) * 0.025 * r_waterscroll.value, sin(r_refdef.scene.time * 0.8f) * 0.025 * r_waterscroll.value, 0); if (r_timereport_active) R_TimeReport("skystartframe"); if (cl.csqc_vidvars.drawworld) { // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawSky) { r_refdef.scene.worldmodel->DrawSky(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("worldsky"); } if (R_DrawBrushModelsSky() && r_timereport_active) R_TimeReport("bmodelsky"); if (skyrendermasked && skyrenderlater) { // we have to force off the water clipping plane while rendering sky R_SetupView(false, fbo, depthtexture, colortexture); R_Sky(); R_SetupView(true, fbo, depthtexture, colortexture); if (r_timereport_active) R_TimeReport("sky"); } } R_Shadow_PrepareLights(fbo, depthtexture, colortexture); if (r_shadows.integer > 0 && r_refdef.lightmapintensity > 0) R_Shadow_PrepareModelShadows(); if (r_timereport_active) R_TimeReport("preparelights"); if (R_Shadow_ShadowMappingEnabled()) shadowmapping = true; if (r_shadow_usingdeferredprepass) R_Shadow_DrawPrepass(); if (r_depthfirst.integer >= 1 && cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDepth) { r_refdef.scene.worldmodel->DrawDepth(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("worlddepth"); } if (r_depthfirst.integer >= 2) { R_DrawModelsDepth(); if (r_timereport_active) R_TimeReport("modeldepth"); } if (r_shadows.integer >= 2 && shadowmapping && r_refdef.lightmapintensity > 0) { R_ResetViewRendering3D(fbo, depthtexture, colortexture); R_DrawModelShadowMaps(fbo, depthtexture, colortexture); R_ResetViewRendering3D(fbo, depthtexture, colortexture); // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); } if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->Draw) { r_refdef.scene.worldmodel->Draw(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("world"); } // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); R_DrawModels(); if (r_timereport_active) R_TimeReport("models"); // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && !r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) { R_ResetViewRendering3D(fbo, depthtexture, colortexture); R_DrawModelShadows(fbo, depthtexture, colortexture); R_ResetViewRendering3D(fbo, depthtexture, colortexture); // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); } if (!r_shadow_usingdeferredprepass) { R_Shadow_DrawLights(); if (r_timereport_active) R_TimeReport("rtlights"); } // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); if ((r_shadows.integer == 1 || (r_shadows.integer > 0 && !shadowmapping)) && r_shadows_drawafterrtlighting.integer && r_refdef.lightmapintensity > 0) { R_ResetViewRendering3D(fbo, depthtexture, colortexture); R_DrawModelShadows(fbo, depthtexture, colortexture); R_ResetViewRendering3D(fbo, depthtexture, colortexture); // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); } if (cl.csqc_vidvars.drawworld) { if (cl_decals_newsystem.integer) { R_DrawModelDecals(); if (r_timereport_active) R_TimeReport("modeldecals"); } else { R_DrawDecals(); if (r_timereport_active) R_TimeReport("decals"); } R_DrawParticles(); if (r_timereport_active) R_TimeReport("particles"); R_DrawExplosions(); if (r_timereport_active) R_TimeReport("explosions"); R_DrawLightningBeams(); if (r_timereport_active) R_TimeReport("lightning"); } if (cl.csqc_loaded) VM_CL_AddPolygonsToMeshQueue(CLVM_prog); if (r_refdef.view.showdebug) { if (cl_locs_show.integer) { R_DrawLocs(); if (r_timereport_active) R_TimeReport("showlocs"); } if (r_drawportals.integer) { R_DrawPortals(); if (r_timereport_active) R_TimeReport("portals"); } if (r_showbboxes.value > 0) { R_DrawEntityBBoxes(); if (r_timereport_active) R_TimeReport("bboxes"); } } if (r_transparent.integer) { R_MeshQueue_RenderTransparent(); if (r_timereport_active) R_TimeReport("drawtrans"); } if (r_refdef.view.showdebug && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawDebug && (r_showtris.value > 0 || r_shownormals.value != 0 || r_showcollisionbrushes.value > 0 || r_showoverdraw.value > 0)) { r_refdef.scene.worldmodel->DrawDebug(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("worlddebug"); R_DrawModelsDebug(); if (r_timereport_active) R_TimeReport("modeldebug"); } if (cl.csqc_vidvars.drawworld) { R_Shadow_DrawCoronas(); if (r_timereport_active) R_TimeReport("coronas"); } #if 0 { GL_DepthTest(false); qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE); GL_Color(1, 1, 1, 1); qglBegin(GL_POLYGON); qglVertex3f(r_refdef.view.frustumcorner[0][0], r_refdef.view.frustumcorner[0][1], r_refdef.view.frustumcorner[0][2]); qglVertex3f(r_refdef.view.frustumcorner[1][0], r_refdef.view.frustumcorner[1][1], r_refdef.view.frustumcorner[1][2]); qglVertex3f(r_refdef.view.frustumcorner[3][0], r_refdef.view.frustumcorner[3][1], r_refdef.view.frustumcorner[3][2]); qglVertex3f(r_refdef.view.frustumcorner[2][0], r_refdef.view.frustumcorner[2][1], r_refdef.view.frustumcorner[2][2]); qglEnd(); qglBegin(GL_POLYGON); qglVertex3f(r_refdef.view.frustumcorner[0][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[0][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[0][2] + 1000 * r_refdef.view.forward[2]); qglVertex3f(r_refdef.view.frustumcorner[1][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[1][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[1][2] + 1000 * r_refdef.view.forward[2]); qglVertex3f(r_refdef.view.frustumcorner[3][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[3][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[3][2] + 1000 * r_refdef.view.forward[2]); qglVertex3f(r_refdef.view.frustumcorner[2][0] + 1000 * r_refdef.view.forward[0], r_refdef.view.frustumcorner[2][1] + 1000 * r_refdef.view.forward[1], r_refdef.view.frustumcorner[2][2] + 1000 * r_refdef.view.forward[2]); qglEnd(); qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); } static const unsigned short bboxelements[36] = { 5, 1, 3, 5, 3, 7, 6, 2, 0, 6, 0, 4, 7, 3, 2, 7, 2, 6, 4, 0, 1, 4, 1, 5, 4, 5, 7, 4, 7, 6, 1, 0, 2, 1, 2, 3, }; static void R_DrawBBoxMesh(vec3_t mins, vec3_t maxs, float cr, float cg, float cb, float ca) { int i; float *v, *c, f1, f2, vertex3f[8*3], color4f[8*4]; RSurf_ActiveWorldEntity(); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); // R_Mesh_ResetTextureState(); vertex3f[ 0] = mins[0];vertex3f[ 1] = mins[1];vertex3f[ 2] = mins[2]; // vertex3f[ 3] = maxs[0];vertex3f[ 4] = mins[1];vertex3f[ 5] = mins[2]; vertex3f[ 6] = mins[0];vertex3f[ 7] = maxs[1];vertex3f[ 8] = mins[2]; vertex3f[ 9] = maxs[0];vertex3f[10] = maxs[1];vertex3f[11] = mins[2]; vertex3f[12] = mins[0];vertex3f[13] = mins[1];vertex3f[14] = maxs[2]; vertex3f[15] = maxs[0];vertex3f[16] = mins[1];vertex3f[17] = maxs[2]; vertex3f[18] = mins[0];vertex3f[19] = maxs[1];vertex3f[20] = maxs[2]; vertex3f[21] = maxs[0];vertex3f[22] = maxs[1];vertex3f[23] = maxs[2]; R_FillColors(color4f, 8, cr, cg, cb, ca); if (r_refdef.fogenabled) { for (i = 0, v = vertex3f, c = color4f;i < 8;i++, v += 3, c += 4) { f1 = RSurf_FogVertex(v); f2 = 1 - f1; c[0] = c[0] * f1 + r_refdef.fogcolor[0] * f2; c[1] = c[1] * f1 + r_refdef.fogcolor[1] * f2; c[2] = c[2] * f1 + r_refdef.fogcolor[2] * f2; } } R_Mesh_PrepareVertices_Generic_Arrays(8, vertex3f, color4f, NULL); R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(false, false); R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); } static void R_DrawEntityBBoxes_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { prvm_prog_t *prog = SVVM_prog; int i; float color[4]; prvm_edict_t *edict; // this function draws bounding boxes of server entities if (!sv.active) return; GL_CullFace(GL_NONE); R_SetupShader_Generic_NoTexture(false, false); for (i = 0;i < numsurfaces;i++) { edict = PRVM_EDICT_NUM(surfacelist[i]); switch ((int)PRVM_serveredictfloat(edict, solid)) { case SOLID_NOT: Vector4Set(color, 1, 1, 1, 0.05);break; case SOLID_TRIGGER: Vector4Set(color, 1, 0, 1, 0.10);break; case SOLID_BBOX: Vector4Set(color, 0, 1, 0, 0.10);break; case SOLID_SLIDEBOX: Vector4Set(color, 1, 0, 0, 0.10);break; case SOLID_BSP: Vector4Set(color, 0, 0, 1, 0.05);break; case SOLID_CORPSE: Vector4Set(color, 1, 0.5, 0, 0.05);break; default: Vector4Set(color, 0, 0, 0, 0.50);break; } color[3] *= r_showbboxes.value; color[3] = bound(0, color[3], 1); GL_DepthTest(!r_showdisabledepthtest.integer); GL_CullFace(r_refdef.view.cullface_front); R_DrawBBoxMesh(edict->priv.server->areamins, edict->priv.server->areamaxs, color[0], color[1], color[2], color[3]); } } static void R_DrawEntityBBoxes(void) { int i; prvm_edict_t *edict; vec3_t center; prvm_prog_t *prog = SVVM_prog; // this function draws bounding boxes of server entities if (!sv.active) return; for (i = 0;i < prog->num_edicts;i++) { edict = PRVM_EDICT_NUM(i); if (edict->priv.server->free) continue; // exclude the following for now, as they don't live in world coordinate space and can't be solid: if(PRVM_serveredictedict(edict, tag_entity) != 0) continue; if(PRVM_serveredictedict(edict, viewmodelforclient) != 0) continue; VectorLerp(edict->priv.server->areamins, 0.5f, edict->priv.server->areamaxs, center); R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawEntityBBoxes_Callback, (entity_render_t *)NULL, i, (rtlight_t *)NULL); } } static const int nomodelelement3i[24] = { 5, 2, 0, 5, 1, 2, 5, 0, 3, 5, 3, 1, 0, 2, 4, 2, 1, 4, 3, 0, 4, 1, 3, 4 }; static const unsigned short nomodelelement3s[24] = { 5, 2, 0, 5, 1, 2, 5, 0, 3, 5, 3, 1, 0, 2, 4, 2, 1, 4, 3, 0, 4, 1, 3, 4 }; static const float nomodelvertex3f[6*3] = { -16, 0, 0, 16, 0, 0, 0, -16, 0, 0, 16, 0, 0, 0, -16, 0, 0, 16 }; static const float nomodelcolor4f[6*4] = { 0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.0f, 1.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.0f, 0.0f, 1.0f }; static void R_DrawNoModel_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i; float f1, f2, *c; float color4f[6*4]; RSurf_ActiveCustomEntity(&ent->matrix, &ent->inversematrix, ent->flags, ent->shadertime, ent->colormod[0], ent->colormod[1], ent->colormod[2], ent->alpha, 6, nomodelvertex3f, NULL, NULL, NULL, NULL, nomodelcolor4f, 8, nomodelelement3i, nomodelelement3s, false, false); // this is only called once per entity so numsurfaces is always 1, and // surfacelist is always {0}, so this code does not handle batches if (rsurface.ent_flags & RENDER_ADDITIVE) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); GL_DepthMask(false); } else if (rsurface.colormod[3] < 1) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); } else { GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(true); } GL_DepthRange(0, (rsurface.ent_flags & RENDER_VIEWMODEL) ? 0.0625 : 1); GL_PolygonOffset(rsurface.basepolygonfactor, rsurface.basepolygonoffset); GL_DepthTest(!(rsurface.ent_flags & RENDER_NODEPTHTEST)); GL_CullFace((rsurface.ent_flags & RENDER_DOUBLESIDED) ? GL_NONE : r_refdef.view.cullface_back); memcpy(color4f, nomodelcolor4f, sizeof(float[6*4])); for (i = 0, c = color4f;i < 6;i++, c += 4) { c[0] *= rsurface.colormod[0]; c[1] *= rsurface.colormod[1]; c[2] *= rsurface.colormod[2]; c[3] *= rsurface.colormod[3]; } if (r_refdef.fogenabled) { for (i = 0, c = color4f;i < 6;i++, c += 4) { f1 = RSurf_FogVertex(nomodelvertex3f + 3*i); f2 = 1 - f1; c[0] = (c[0] * f1 + r_refdef.fogcolor[0] * f2); c[1] = (c[1] * f1 + r_refdef.fogcolor[1] * f2); c[2] = (c[2] * f1 + r_refdef.fogcolor[2] * f2); } } // R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(false, false); R_Mesh_PrepareVertices_Generic_Arrays(6, nomodelvertex3f, color4f, NULL); R_Mesh_Draw(0, 6, 0, 8, nomodelelement3i, NULL, 0, nomodelelement3s, NULL, 0); } void R_DrawNoModel(entity_render_t *ent) { vec3_t org; Matrix4x4_OriginFromMatrix(&ent->matrix, org); if ((ent->flags & RENDER_ADDITIVE) || (ent->alpha < 1)) R_MeshQueue_AddTransparent((ent->flags & RENDER_NODEPTHTEST) ? TRANSPARENTSORT_HUD : TRANSPARENTSORT_DISTANCE, org, R_DrawNoModel_TransparentCallback, ent, 0, rsurface.rtlight); else R_DrawNoModel_TransparentCallback(ent, rsurface.rtlight, 0, NULL); } void R_CalcBeam_Vertex3f (float *vert, const float *org1, const float *org2, float width) { vec3_t right1, right2, diff, normal; VectorSubtract (org2, org1, normal); // calculate 'right' vector for start VectorSubtract (r_refdef.view.origin, org1, diff); CrossProduct (normal, diff, right1); VectorNormalize (right1); // calculate 'right' vector for end VectorSubtract (r_refdef.view.origin, org2, diff); CrossProduct (normal, diff, right2); VectorNormalize (right2); vert[ 0] = org1[0] + width * right1[0]; vert[ 1] = org1[1] + width * right1[1]; vert[ 2] = org1[2] + width * right1[2]; vert[ 3] = org1[0] - width * right1[0]; vert[ 4] = org1[1] - width * right1[1]; vert[ 5] = org1[2] - width * right1[2]; vert[ 6] = org2[0] - width * right2[0]; vert[ 7] = org2[1] - width * right2[1]; vert[ 8] = org2[2] - width * right2[2]; vert[ 9] = org2[0] + width * right2[0]; vert[10] = org2[1] + width * right2[1]; vert[11] = org2[2] + width * right2[2]; } void R_CalcSprite_Vertex3f(float *vertex3f, const vec3_t origin, const vec3_t left, const vec3_t up, float scalex1, float scalex2, float scaley1, float scaley2) { vertex3f[ 0] = origin[0] + left[0] * scalex2 + up[0] * scaley1; vertex3f[ 1] = origin[1] + left[1] * scalex2 + up[1] * scaley1; vertex3f[ 2] = origin[2] + left[2] * scalex2 + up[2] * scaley1; vertex3f[ 3] = origin[0] + left[0] * scalex2 + up[0] * scaley2; vertex3f[ 4] = origin[1] + left[1] * scalex2 + up[1] * scaley2; vertex3f[ 5] = origin[2] + left[2] * scalex2 + up[2] * scaley2; vertex3f[ 6] = origin[0] + left[0] * scalex1 + up[0] * scaley2; vertex3f[ 7] = origin[1] + left[1] * scalex1 + up[1] * scaley2; vertex3f[ 8] = origin[2] + left[2] * scalex1 + up[2] * scaley2; vertex3f[ 9] = origin[0] + left[0] * scalex1 + up[0] * scaley1; vertex3f[10] = origin[1] + left[1] * scalex1 + up[1] * scaley1; vertex3f[11] = origin[2] + left[2] * scalex1 + up[2] * scaley1; } static int R_Mesh_AddVertex(rmesh_t *mesh, float x, float y, float z) { int i; float *vertex3f; float v[3]; VectorSet(v, x, y, z); for (i = 0, vertex3f = mesh->vertex3f;i < mesh->numvertices;i++, vertex3f += 3) if (VectorDistance2(v, vertex3f) < mesh->epsilon2) break; if (i == mesh->numvertices) { if (mesh->numvertices < mesh->maxvertices) { VectorCopy(v, vertex3f); mesh->numvertices++; } return mesh->numvertices; } else return i; } void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f) { int i; int *e, element[3]; element[0] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; element[1] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]);vertex3f += 3; e = mesh->element3i + mesh->numtriangles * 3; for (i = 0;i < numvertices - 2;i++, vertex3f += 3) { element[2] = R_Mesh_AddVertex(mesh, vertex3f[0], vertex3f[1], vertex3f[2]); if (mesh->numtriangles < mesh->maxtriangles) { *e++ = element[0]; *e++ = element[1]; *e++ = element[2]; mesh->numtriangles++; } element[1] = element[2]; } } static void R_Mesh_AddPolygon3d(rmesh_t *mesh, int numvertices, double *vertex3d) { int i; int *e, element[3]; element[0] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; element[1] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]);vertex3d += 3; e = mesh->element3i + mesh->numtriangles * 3; for (i = 0;i < numvertices - 2;i++, vertex3d += 3) { element[2] = R_Mesh_AddVertex(mesh, vertex3d[0], vertex3d[1], vertex3d[2]); if (mesh->numtriangles < mesh->maxtriangles) { *e++ = element[0]; *e++ = element[1]; *e++ = element[2]; mesh->numtriangles++; } element[1] = element[2]; } } #define R_MESH_PLANE_DIST_EPSILON (1.0 / 32.0) void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes) { int planenum, planenum2; int w; int tempnumpoints; mplane_t *plane, *plane2; double maxdist; double temppoints[2][256*3]; // figure out how large a bounding box we need to properly compute this brush maxdist = 0; for (w = 0;w < numplanes;w++) maxdist = max(maxdist, fabs(planes[w].dist)); // now make it large enough to enclose the entire brush, and round it off to a reasonable multiple of 1024 maxdist = floor(maxdist * (4.0 / 1024.0) + 1) * 1024.0; for (planenum = 0, plane = planes;planenum < numplanes;planenum++, plane++) { w = 0; tempnumpoints = 4; PolygonD_QuadForPlane(temppoints[w], plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, maxdist); for (planenum2 = 0, plane2 = planes;planenum2 < numplanes && tempnumpoints >= 3;planenum2++, plane2++) { if (planenum2 == planenum) continue; PolygonD_Divide(tempnumpoints, temppoints[w], plane2->normal[0], plane2->normal[1], plane2->normal[2], plane2->dist, R_MESH_PLANE_DIST_EPSILON, 0, NULL, NULL, 256, temppoints[!w], &tempnumpoints, NULL); w = !w; } if (tempnumpoints < 3) continue; // generate elements forming a triangle fan for this polygon R_Mesh_AddPolygon3d(mesh, tempnumpoints, temppoints[w]); } } static void R_Texture_AddLayer(texture_t *t, qboolean depthmask, int blendfunc1, int blendfunc2, texturelayertype_t type, rtexture_t *texture, const matrix4x4_t *matrix, float r, float g, float b, float a) { texturelayer_t *layer; layer = t->currentlayers + t->currentnumlayers++; layer->type = type; layer->depthmask = depthmask; layer->blendfunc1 = blendfunc1; layer->blendfunc2 = blendfunc2; layer->texture = texture; layer->texmatrix = *matrix; layer->color[0] = r; layer->color[1] = g; layer->color[2] = b; layer->color[3] = a; } static qboolean R_TestQ3WaveFunc(q3wavefunc_t func, const float *parms) { if(parms[0] == 0 && parms[1] == 0) return false; if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! if(rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT - 1)] == 0) return false; return true; } static float R_EvaluateQ3WaveFunc(q3wavefunc_t func, const float *parms) { double index, f; index = parms[2] + rsurface.shadertime * parms[3]; index -= floor(index); switch (func & ((1 << Q3WAVEFUNC_USER_SHIFT) - 1)) { default: case Q3WAVEFUNC_NONE: case Q3WAVEFUNC_NOISE: case Q3WAVEFUNC_COUNT: f = 0; break; case Q3WAVEFUNC_SIN: f = sin(index * M_PI * 2);break; case Q3WAVEFUNC_SQUARE: f = index < 0.5 ? 1 : -1;break; case Q3WAVEFUNC_SAWTOOTH: f = index;break; case Q3WAVEFUNC_INVERSESAWTOOTH: f = 1 - index;break; case Q3WAVEFUNC_TRIANGLE: index *= 4; f = index - floor(index); if (index < 1) { // f = f; } else if (index < 2) f = 1 - f; else if (index < 3) f = -f; else f = -(1 - f); break; } f = parms[0] + parms[1] * f; if(func >> Q3WAVEFUNC_USER_SHIFT) // assumes rsurface to be set! f *= rsurface.userwavefunc_param[bound(0, (func >> Q3WAVEFUNC_USER_SHIFT) - 1, Q3WAVEFUNC_USER_COUNT - 1)]; return (float) f; } static void R_tcMod_ApplyToMatrix(matrix4x4_t *texmatrix, q3shaderinfo_layer_tcmod_t *tcmod, int currentmaterialflags) { int w, h, idx; float shadertime; float f; float offsetd[2]; float tcmat[12]; matrix4x4_t matrix, temp; // if shadertime exceeds about 9 hours (32768 seconds), just wrap it, // it's better to have one huge fixup every 9 hours than gradual // degradation over time which looks consistently bad after many hours. // // tcmod scroll in particular suffers from this degradation which can't be // effectively worked around even with floor() tricks because we don't // know if tcmod scroll is the last tcmod being applied, and for clampmap // a workaround involving floor() would be incorrect anyway... shadertime = rsurface.shadertime; if (shadertime >= 32768.0f) shadertime -= floor(rsurface.shadertime * (1.0f / 32768.0f)) * 32768.0f; switch(tcmod->tcmod) { case Q3TCMOD_COUNT: case Q3TCMOD_NONE: if (currentmaterialflags & MATERIALFLAG_WATERSCROLL) matrix = r_waterscrollmatrix; else matrix = identitymatrix; break; case Q3TCMOD_ENTITYTRANSLATE: // this is used in Q3 to allow the gamecode to control texcoord // scrolling on the entity, which is not supported in darkplaces yet. Matrix4x4_CreateTranslate(&matrix, 0, 0, 0); break; case Q3TCMOD_ROTATE: Matrix4x4_CreateTranslate(&matrix, 0.5, 0.5, 0); Matrix4x4_ConcatRotate(&matrix, tcmod->parms[0] * rsurface.shadertime, 0, 0, 1); Matrix4x4_ConcatTranslate(&matrix, -0.5, -0.5, 0); break; case Q3TCMOD_SCALE: Matrix4x4_CreateScale3(&matrix, tcmod->parms[0], tcmod->parms[1], 1); break; case Q3TCMOD_SCROLL: // this particular tcmod is a "bug for bug" compatible one with regards to // Quake3, the wrapping is unnecessary with our shadetime fix but quake3 // specifically did the wrapping and so we must mimic that... offsetd[0] = tcmod->parms[0] * rsurface.shadertime; offsetd[1] = tcmod->parms[1] * rsurface.shadertime; Matrix4x4_CreateTranslate(&matrix, offsetd[0] - floor(offsetd[0]), offsetd[1] - floor(offsetd[1]), 0); break; case Q3TCMOD_PAGE: // poor man's animmap (to store animations into a single file, useful for HTTP downloaded textures) w = (int) tcmod->parms[0]; h = (int) tcmod->parms[1]; f = rsurface.shadertime / (tcmod->parms[2] * w * h); f = f - floor(f); idx = (int) floor(f * w * h); Matrix4x4_CreateTranslate(&matrix, (idx % w) / tcmod->parms[0], (idx / w) / tcmod->parms[1], 0); break; case Q3TCMOD_STRETCH: f = 1.0f / R_EvaluateQ3WaveFunc(tcmod->wavefunc, tcmod->waveparms); Matrix4x4_CreateFromQuakeEntity(&matrix, 0.5f * (1 - f), 0.5 * (1 - f), 0, 0, 0, 0, f); break; case Q3TCMOD_TRANSFORM: VectorSet(tcmat + 0, tcmod->parms[0], tcmod->parms[1], 0); VectorSet(tcmat + 3, tcmod->parms[2], tcmod->parms[3], 0); VectorSet(tcmat + 6, 0 , 0 , 1); VectorSet(tcmat + 9, tcmod->parms[4], tcmod->parms[5], 0); Matrix4x4_FromArray12FloatGL(&matrix, tcmat); break; case Q3TCMOD_TURBULENT: // this is handled in the RSurf_PrepareVertices function matrix = identitymatrix; break; } temp = *texmatrix; Matrix4x4_Concat(texmatrix, &matrix, &temp); } static void R_LoadQWSkin(r_qwskincache_t *cache, const char *skinname) { int textureflags = (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP; char name[MAX_QPATH]; skinframe_t *skinframe; unsigned char pixels[296*194]; strlcpy(cache->name, skinname, sizeof(cache->name)); dpsnprintf(name, sizeof(name), "skins/%s.pcx", cache->name); if (developer_loading.integer) Con_Printf("loading %s\n", name); skinframe = R_SkinFrame_Find(name, textureflags, 0, 0, 0, false); if (!skinframe || !skinframe->base) { unsigned char *f; fs_offset_t filesize; skinframe = NULL; f = FS_LoadFile(name, tempmempool, true, &filesize); if (f) { if (LoadPCX_QWSkin(f, (int)filesize, pixels, 296, 194)) skinframe = R_SkinFrame_LoadInternalQuake(name, textureflags, true, r_fullbrights.integer, pixels, image_width, image_height); Mem_Free(f); } } cache->skinframe = skinframe; } texture_t *R_GetCurrentTexture(texture_t *t) { int i; const entity_render_t *ent = rsurface.entity; dp_model_t *model = ent->model; // when calling this, ent must not be NULL q3shaderinfo_layer_tcmod_t *tcmod; if (t->update_lastrenderframe == r_textureframe && t->update_lastrenderentity == (void *)ent && !rsurface.forcecurrenttextureupdate) return t->currentframe; t->update_lastrenderframe = r_textureframe; t->update_lastrenderentity = (void *)ent; if(ent->entitynumber >= MAX_EDICTS && ent->entitynumber < 2 * MAX_EDICTS) t->camera_entity = ent->entitynumber; else t->camera_entity = 0; // switch to an alternate material if this is a q1bsp animated material { texture_t *texture = t; int s = rsurface.ent_skinnum; if ((unsigned int)s >= (unsigned int)model->numskins) s = 0; if (model->skinscenes) { if (model->skinscenes[s].framecount > 1) s = model->skinscenes[s].firstframe + (unsigned int) (rsurface.shadertime * model->skinscenes[s].framerate) % model->skinscenes[s].framecount; else s = model->skinscenes[s].firstframe; } if (s > 0) t = t + s * model->num_surfaces; if (t->animated) { // use an alternate animation if the entity's frame is not 0, // and only if the texture has an alternate animation if (t->animated == 2) // q2bsp t = t->anim_frames[0][ent->framegroupblend[0].frame % t->anim_total[0]]; else if (rsurface.ent_alttextures && t->anim_total[1]) t = t->anim_frames[1][(t->anim_total[1] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[1]) : 0]; else t = t->anim_frames[0][(t->anim_total[0] >= 2) ? ((int)(rsurface.shadertime * 5.0f) % t->anim_total[0]) : 0]; } texture->currentframe = t; } // update currentskinframe to be a qw skin or animation frame if (rsurface.ent_qwskin >= 0) { i = rsurface.ent_qwskin; if (!r_qwskincache || r_qwskincache_size != cl.maxclients) { r_qwskincache_size = cl.maxclients; if (r_qwskincache) Mem_Free(r_qwskincache); r_qwskincache = (r_qwskincache_t *)Mem_Alloc(r_main_mempool, sizeof(*r_qwskincache) * r_qwskincache_size); } if (strcmp(r_qwskincache[i].name, cl.scores[i].qw_skin)) R_LoadQWSkin(&r_qwskincache[i], cl.scores[i].qw_skin); t->currentskinframe = r_qwskincache[i].skinframe; if (t->currentskinframe == NULL) t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; } else if (t->numskinframes >= 2) t->currentskinframe = t->skinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->skinframerate, t->numskinframes)]; if (t->backgroundnumskinframes >= 2) t->backgroundcurrentskinframe = t->backgroundskinframes[LoopingFrameNumberFromDouble(rsurface.shadertime * t->backgroundskinframerate, t->backgroundnumskinframes)]; t->currentmaterialflags = t->basematerialflags; t->currentalpha = rsurface.colormod[3] * t->basealpha; if (t->basematerialflags & MATERIALFLAG_WATERALPHA && (model->brush.supportwateralpha || r_novis.integer || r_trippy.integer)) t->currentalpha *= r_wateralpha.value; if(t->basematerialflags & MATERIALFLAG_WATERSHADER && r_fb.water.enabled && !r_refdef.view.isoverlay) t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; // we apply wateralpha later if(!r_fb.water.enabled || r_refdef.view.isoverlay) t->currentmaterialflags &= ~(MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA); if (!(rsurface.ent_flags & RENDER_LIGHT)) t->currentmaterialflags |= MATERIALFLAG_FULLBRIGHT; else if (FAKELIGHT_ENABLED) { // no modellight if using fakelight for the map } else if ((rsurface.modeltexcoordlightmap2f == NULL || (rsurface.ent_flags & (RENDER_DYNAMICMODELLIGHT | RENDER_CUSTOMIZEDMODELLIGHT))) && !(t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) { // pick a model lighting mode if (VectorLength2(rsurface.modellight_diffuse) >= (1.0f / 256.0f)) t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL; else t->currentmaterialflags |= MATERIALFLAG_MODELLIGHT; } if (rsurface.ent_flags & RENDER_ADDITIVE) t->currentmaterialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; else if (t->currentalpha < 1) t->currentmaterialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; // LordHavoc: prevent bugs where code checks add or alpha at higher priority than customblend by clearing these flags if (t->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) t->currentmaterialflags &= ~(MATERIALFLAG_ADD | MATERIALFLAG_ALPHA); if (rsurface.ent_flags & RENDER_DOUBLESIDED) t->currentmaterialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; if (rsurface.ent_flags & (RENDER_NODEPTHTEST | RENDER_VIEWMODEL)) t->currentmaterialflags |= MATERIALFLAG_SHORTDEPTHRANGE; if (t->backgroundnumskinframes) t->currentmaterialflags |= MATERIALFLAG_VERTEXTEXTUREBLEND; if (t->currentmaterialflags & MATERIALFLAG_BLENDED) { if (t->currentmaterialflags & (MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA)) t->currentmaterialflags &= ~MATERIALFLAG_BLENDED; } else t->currentmaterialflags &= ~(MATERIALFLAG_REFRACTION | MATERIALFLAG_WATERSHADER | MATERIALFLAG_CAMERA); if (vid.allowalphatocoverage && r_transparent_alphatocoverage.integer >= 2 && ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA | MATERIALFLAG_ADD | MATERIALFLAG_CUSTOMBLEND)) == (MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA))) { // promote alphablend to alphatocoverage (a type of alphatest) if antialiasing is on t->currentmaterialflags = (t->currentmaterialflags & ~(MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHA)) | MATERIALFLAG_ALPHATEST; } if ((t->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST)) == MATERIALFLAG_BLENDED && r_transparentdepthmasking.integer && !(t->basematerialflags & MATERIALFLAG_BLENDED)) t->currentmaterialflags |= MATERIALFLAG_TRANSDEPTH; // there is no tcmod if (t->currentmaterialflags & MATERIALFLAG_WATERSCROLL) { t->currenttexmatrix = r_waterscrollmatrix; t->currentbackgroundtexmatrix = r_waterscrollmatrix; } else if (!(t->currentmaterialflags & MATERIALFLAG_CUSTOMSURFACE)) { Matrix4x4_CreateIdentity(&t->currenttexmatrix); Matrix4x4_CreateIdentity(&t->currentbackgroundtexmatrix); } for (i = 0, tcmod = t->tcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) R_tcMod_ApplyToMatrix(&t->currenttexmatrix, tcmod, t->currentmaterialflags); for (i = 0, tcmod = t->backgroundtcmods;i < Q3MAXTCMODS && tcmod->tcmod;i++, tcmod++) R_tcMod_ApplyToMatrix(&t->currentbackgroundtexmatrix, tcmod, t->currentmaterialflags); t->colormapping = VectorLength2(rsurface.colormap_pantscolor) + VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f); if (t->currentskinframe->qpixels) R_SkinFrame_GenerateTexturesFromQPixels(t->currentskinframe, t->colormapping); t->basetexture = (!t->colormapping && t->currentskinframe->merged) ? t->currentskinframe->merged : t->currentskinframe->base; if (!t->basetexture) t->basetexture = r_texture_notexture; t->pantstexture = t->colormapping ? t->currentskinframe->pants : NULL; t->shirttexture = t->colormapping ? t->currentskinframe->shirt : NULL; t->nmaptexture = t->currentskinframe->nmap; if (!t->nmaptexture) t->nmaptexture = r_texture_blanknormalmap; t->glosstexture = r_texture_black; t->glowtexture = t->currentskinframe->glow; t->fogtexture = t->currentskinframe->fog; t->reflectmasktexture = t->currentskinframe->reflect; if (t->backgroundnumskinframes) { t->backgroundbasetexture = (!t->colormapping && t->backgroundcurrentskinframe->merged) ? t->backgroundcurrentskinframe->merged : t->backgroundcurrentskinframe->base; t->backgroundnmaptexture = t->backgroundcurrentskinframe->nmap; t->backgroundglosstexture = r_texture_black; t->backgroundglowtexture = t->backgroundcurrentskinframe->glow; if (!t->backgroundnmaptexture) t->backgroundnmaptexture = r_texture_blanknormalmap; // make sure that if glow is going to be used, both textures are not NULL if (!t->backgroundglowtexture && t->glowtexture) t->backgroundglowtexture = r_texture_black; if (!t->glowtexture && t->backgroundglowtexture) t->glowtexture = r_texture_black; } else { t->backgroundbasetexture = r_texture_white; t->backgroundnmaptexture = r_texture_blanknormalmap; t->backgroundglosstexture = r_texture_black; t->backgroundglowtexture = NULL; } t->specularpower = r_shadow_glossexponent.value; // TODO: store reference values for these in the texture? t->specularscale = 0; if (r_shadow_gloss.integer > 0) { if (t->currentskinframe->gloss || (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss)) { if (r_shadow_glossintensity.value > 0) { t->glosstexture = t->currentskinframe->gloss ? t->currentskinframe->gloss : r_texture_white; t->backgroundglosstexture = (t->backgroundcurrentskinframe && t->backgroundcurrentskinframe->gloss) ? t->backgroundcurrentskinframe->gloss : r_texture_white; t->specularscale = r_shadow_glossintensity.value; } } else if (r_shadow_gloss.integer >= 2 && r_shadow_gloss2intensity.value > 0) { t->glosstexture = r_texture_white; t->backgroundglosstexture = r_texture_white; t->specularscale = r_shadow_gloss2intensity.value; t->specularpower = r_shadow_gloss2exponent.value; } } t->specularscale *= t->specularscalemod; t->specularpower *= t->specularpowermod; t->rtlightambient = 0; // lightmaps mode looks bad with dlights using actual texturing, so turn // off the colormap and glossmap, but leave the normalmap on as it still // accurately represents the shading involved if (gl_lightmaps.integer) { t->basetexture = r_texture_grey128; t->pantstexture = r_texture_black; t->shirttexture = r_texture_black; if (gl_lightmaps.integer < 2) t->nmaptexture = r_texture_blanknormalmap; t->glosstexture = r_texture_black; t->glowtexture = NULL; t->fogtexture = NULL; t->reflectmasktexture = NULL; t->backgroundbasetexture = NULL; if (gl_lightmaps.integer < 2) t->backgroundnmaptexture = r_texture_blanknormalmap; t->backgroundglosstexture = r_texture_black; t->backgroundglowtexture = NULL; t->specularscale = 0; t->currentmaterialflags = MATERIALFLAG_WALL | (t->currentmaterialflags & (MATERIALFLAG_NOCULLFACE | MATERIALFLAG_MODELLIGHT | MATERIALFLAG_MODELLIGHT_DIRECTIONAL | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SHORTDEPTHRANGE)); } Vector4Set(t->lightmapcolor, rsurface.colormod[0], rsurface.colormod[1], rsurface.colormod[2], t->currentalpha); VectorClear(t->dlightcolor); t->currentnumlayers = 0; if (t->currentmaterialflags & MATERIALFLAG_WALL) { int blendfunc1, blendfunc2; qboolean depthmask; if (t->currentmaterialflags & MATERIALFLAG_ADD) { blendfunc1 = GL_SRC_ALPHA; blendfunc2 = GL_ONE; } else if (t->currentmaterialflags & MATERIALFLAG_ALPHA) { blendfunc1 = GL_SRC_ALPHA; blendfunc2 = GL_ONE_MINUS_SRC_ALPHA; } else if (t->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) { blendfunc1 = t->customblendfunc[0]; blendfunc2 = t->customblendfunc[1]; } else { blendfunc1 = GL_ONE; blendfunc2 = GL_ZERO; } // don't colormod evilblend textures if(!(R_BlendFuncFlags(blendfunc1, blendfunc2) & BLENDFUNC_ALLOWS_COLORMOD)) VectorSet(t->lightmapcolor, 1, 1, 1); depthmask = !(t->currentmaterialflags & MATERIALFLAG_BLENDED); if (t->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) { // fullbright is not affected by r_refdef.lightmapintensity R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); } else { vec3_t ambientcolor; float colorscale; // set the color tint used for lights affecting this surface VectorSet(t->dlightcolor, t->lightmapcolor[0] * t->lightmapcolor[3], t->lightmapcolor[1] * t->lightmapcolor[3], t->lightmapcolor[2] * t->lightmapcolor[3]); colorscale = 2; // q3bsp has no lightmap updates, so the lightstylevalue that // would normally be baked into the lightmap must be // applied to the color // FIXME: r_glsl 1 rendering doesn't support overbright lightstyles with this (the default light style is not overbright) if (model->type == mod_brushq3) colorscale *= r_refdef.scene.rtlightstylevalue[0]; colorscale *= r_refdef.lightmapintensity; VectorScale(t->lightmapcolor, r_refdef.scene.ambient, ambientcolor); VectorScale(t->lightmapcolor, colorscale, t->lightmapcolor); // basic lit geometry R_Texture_AddLayer(t, depthmask, blendfunc1, blendfunc2, TEXTURELAYERTYPE_LITTEXTURE, t->basetexture, &t->currenttexmatrix, t->lightmapcolor[0], t->lightmapcolor[1], t->lightmapcolor[2], t->lightmapcolor[3]); // add pants/shirt if needed if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * t->lightmapcolor[0], rsurface.colormap_pantscolor[1] * t->lightmapcolor[1], rsurface.colormap_pantscolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_LITTEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * t->lightmapcolor[0], rsurface.colormap_shirtcolor[1] * t->lightmapcolor[1], rsurface.colormap_shirtcolor[2] * t->lightmapcolor[2], t->lightmapcolor[3]); // now add ambient passes if needed if (VectorLength2(ambientcolor) >= (1.0f/1048576.0f)) { R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->basetexture, &t->currenttexmatrix, ambientcolor[0], ambientcolor[1], ambientcolor[2], t->lightmapcolor[3]); if (VectorLength2(rsurface.colormap_pantscolor) >= (1.0f / 1048576.0f) && t->pantstexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->pantstexture, &t->currenttexmatrix, rsurface.colormap_pantscolor[0] * ambientcolor[0], rsurface.colormap_pantscolor[1] * ambientcolor[1], rsurface.colormap_pantscolor[2] * ambientcolor[2], t->lightmapcolor[3]); if (VectorLength2(rsurface.colormap_shirtcolor) >= (1.0f / 1048576.0f) && t->shirttexture) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->shirttexture, &t->currenttexmatrix, rsurface.colormap_shirtcolor[0] * ambientcolor[0], rsurface.colormap_shirtcolor[1] * ambientcolor[1], rsurface.colormap_shirtcolor[2] * ambientcolor[2], t->lightmapcolor[3]); } } if (t->glowtexture != NULL && !gl_lightmaps.integer) R_Texture_AddLayer(t, false, GL_SRC_ALPHA, GL_ONE, TEXTURELAYERTYPE_TEXTURE, t->glowtexture, &t->currenttexmatrix, rsurface.glowmod[0], rsurface.glowmod[1], rsurface.glowmod[2], t->lightmapcolor[3]); if (r_refdef.fogenabled && !(t->currentmaterialflags & MATERIALFLAG_ADD)) { // if this is opaque use alpha blend which will darken the earlier // passes cheaply. // // if this is an alpha blended material, all the earlier passes // were darkened by fog already, so we only need to add the fog // color ontop through the fog mask texture // // if this is an additive blended material, all the earlier passes // were darkened by fog already, and we should not add fog color // (because the background was not darkened, there is no fog color // that was lost behind it). R_Texture_AddLayer(t, false, GL_SRC_ALPHA, (t->currentmaterialflags & MATERIALFLAG_BLENDED) ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA, TEXTURELAYERTYPE_FOG, t->fogtexture, &t->currenttexmatrix, r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], t->lightmapcolor[3]); } } return t; } rsurfacestate_t rsurface; void RSurf_ActiveWorldEntity(void) { dp_model_t *model = r_refdef.scene.worldmodel; //if (rsurface.entity == r_refdef.scene.worldentity) // return; rsurface.entity = r_refdef.scene.worldentity; rsurface.skeleton = NULL; memset(rsurface.userwavefunc_param, 0, sizeof(rsurface.userwavefunc_param)); rsurface.ent_skinnum = 0; rsurface.ent_qwskin = -1; rsurface.ent_flags = r_refdef.scene.worldentity->flags; rsurface.shadertime = r_refdef.scene.time; rsurface.matrix = identitymatrix; rsurface.inversematrix = identitymatrix; rsurface.matrixscale = 1; rsurface.inversematrixscale = 1; R_EntityMatrix(&identitymatrix); VectorCopy(r_refdef.view.origin, rsurface.localvieworigin); Vector4Copy(r_refdef.fogplane, rsurface.fogplane); rsurface.fograngerecip = r_refdef.fograngerecip; rsurface.fogheightfade = r_refdef.fogheightfade; rsurface.fogplaneviewdist = r_refdef.fogplaneviewdist; rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; VectorSet(rsurface.modellight_ambient, 0, 0, 0); VectorSet(rsurface.modellight_diffuse, 0, 0, 0); VectorSet(rsurface.modellight_lightdir, 0, 0, 1); VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); VectorSet(rsurface.colormod, r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale); rsurface.colormod[3] = 1; VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); rsurface.frameblend[0].lerp = 1; rsurface.ent_alttextures = false; rsurface.basepolygonfactor = r_refdef.polygonfactor; rsurface.basepolygonoffset = r_refdef.polygonoffset; rsurface.entityskeletaltransform3x4 = NULL; rsurface.entityskeletaltransform3x4buffer = NULL; rsurface.entityskeletaltransform3x4offset = 0; rsurface.entityskeletaltransform3x4size = 0;; rsurface.entityskeletalnumtransforms = 0; rsurface.modelvertex3f = model->surfmesh.data_vertex3f; rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; rsurface.modelsvector3f = model->surfmesh.data_svector3f; rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; rsurface.modeltvector3f = model->surfmesh.data_tvector3f; rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; rsurface.modelnormal3f = model->surfmesh.data_normal3f; rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub; rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub; rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub; rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub; rsurface.modelelement3i = model->surfmesh.data_element3i; rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; rsurface.modelelement3s = model->surfmesh.data_element3s; rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; rsurface.modelnumvertices = model->surfmesh.num_vertices; rsurface.modelnumtriangles = model->surfmesh.num_triangles; rsurface.modelsurfaces = model->data_surfaces; rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh; rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f; rsurface.modelgeneratedvertex = false; rsurface.batchgeneratedvertex = false; rsurface.batchfirstvertex = 0; rsurface.batchnumvertices = 0; rsurface.batchfirsttriangle = 0; rsurface.batchnumtriangles = 0; rsurface.batchvertex3f = NULL; rsurface.batchvertex3f_vertexbuffer = NULL; rsurface.batchvertex3f_bufferoffset = 0; rsurface.batchsvector3f = NULL; rsurface.batchsvector3f_vertexbuffer = NULL; rsurface.batchsvector3f_bufferoffset = 0; rsurface.batchtvector3f = NULL; rsurface.batchtvector3f_vertexbuffer = NULL; rsurface.batchtvector3f_bufferoffset = 0; rsurface.batchnormal3f = NULL; rsurface.batchnormal3f_vertexbuffer = NULL; rsurface.batchnormal3f_bufferoffset = 0; rsurface.batchlightmapcolor4f = NULL; rsurface.batchlightmapcolor4f_vertexbuffer = NULL; rsurface.batchlightmapcolor4f_bufferoffset = 0; rsurface.batchtexcoordtexture2f = NULL; rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; rsurface.batchtexcoordtexture2f_bufferoffset = 0; rsurface.batchtexcoordlightmap2f = NULL; rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; rsurface.batchtexcoordlightmap2f_bufferoffset = 0; rsurface.batchskeletalindex4ub = NULL; rsurface.batchskeletalindex4ub_vertexbuffer = NULL; rsurface.batchskeletalindex4ub_bufferoffset = 0; rsurface.batchskeletalweight4ub = NULL; rsurface.batchskeletalweight4ub_vertexbuffer = NULL; rsurface.batchskeletalweight4ub_bufferoffset = 0; rsurface.batchvertexmesh = NULL; rsurface.batchvertexmesh_vertexbuffer = NULL; rsurface.batchvertexmesh_bufferoffset = 0; rsurface.batchelement3i = NULL; rsurface.batchelement3i_indexbuffer = NULL; rsurface.batchelement3i_bufferoffset = 0; rsurface.batchelement3s = NULL; rsurface.batchelement3s_indexbuffer = NULL; rsurface.batchelement3s_bufferoffset = 0; rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = NULL; rsurface.passcolor4f_bufferoffset = 0; rsurface.forcecurrenttextureupdate = false; } void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass) { dp_model_t *model = ent->model; //if (rsurface.entity == ent && (!model->surfmesh.isanimated || (!wantnormals && !wanttangents))) // return; rsurface.entity = (entity_render_t *)ent; rsurface.skeleton = ent->skeleton; memcpy(rsurface.userwavefunc_param, ent->userwavefunc_param, sizeof(rsurface.userwavefunc_param)); rsurface.ent_skinnum = ent->skinnum; rsurface.ent_qwskin = (ent->entitynumber <= cl.maxclients && ent->entitynumber >= 1 && cls.protocol == PROTOCOL_QUAKEWORLD && cl.scores[ent->entitynumber - 1].qw_skin[0] && !strcmp(ent->model->name, "progs/player.mdl")) ? (ent->entitynumber - 1) : -1; rsurface.ent_flags = ent->flags; rsurface.shadertime = r_refdef.scene.time - ent->shadertime; rsurface.matrix = ent->matrix; rsurface.inversematrix = ent->inversematrix; rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; R_EntityMatrix(&rsurface.matrix); Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); rsurface.fogplaneviewdist *= rsurface.inversematrixscale; rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; VectorCopy(ent->modellight_ambient, rsurface.modellight_ambient); VectorCopy(ent->modellight_diffuse, rsurface.modellight_diffuse); VectorCopy(ent->modellight_lightdir, rsurface.modellight_lightdir); VectorCopy(ent->colormap_pantscolor, rsurface.colormap_pantscolor); VectorCopy(ent->colormap_shirtcolor, rsurface.colormap_shirtcolor); VectorScale(ent->colormod, r_refdef.view.colorscale, rsurface.colormod); rsurface.colormod[3] = ent->alpha; VectorScale(ent->glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, rsurface.glowmod); memcpy(rsurface.frameblend, ent->frameblend, sizeof(ent->frameblend)); rsurface.ent_alttextures = ent->framegroupblend[0].frame != 0; rsurface.basepolygonfactor = r_refdef.polygonfactor; rsurface.basepolygonoffset = r_refdef.polygonoffset; if (ent->model->brush.submodel && !prepass) { rsurface.basepolygonfactor += r_polygonoffset_submodel_factor.value; rsurface.basepolygonoffset += r_polygonoffset_submodel_offset.value; } // if the animcache code decided it should use the shader path, skip the deform step rsurface.entityskeletaltransform3x4 = ent->animcache_skeletaltransform3x4; rsurface.entityskeletaltransform3x4buffer = ent->animcache_skeletaltransform3x4buffer; rsurface.entityskeletaltransform3x4offset = ent->animcache_skeletaltransform3x4offset; rsurface.entityskeletaltransform3x4size = ent->animcache_skeletaltransform3x4size; rsurface.entityskeletalnumtransforms = rsurface.entityskeletaltransform3x4 ? model->num_bones : 0; if (model->surfmesh.isanimated && model->AnimateVertices && !rsurface.entityskeletaltransform3x4) { if (ent->animcache_vertex3f) { r_refdef.stats[r_stat_batch_entitycache_count]++; r_refdef.stats[r_stat_batch_entitycache_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entitycache_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entitycache_triangles] += model->surfmesh.num_triangles; rsurface.modelvertex3f = ent->animcache_vertex3f; rsurface.modelvertex3f_vertexbuffer = ent->animcache_vertex3f_vertexbuffer; rsurface.modelvertex3f_bufferoffset = ent->animcache_vertex3f_bufferoffset; rsurface.modelsvector3f = wanttangents ? ent->animcache_svector3f : NULL; rsurface.modelsvector3f_vertexbuffer = wanttangents ? ent->animcache_svector3f_vertexbuffer : NULL; rsurface.modelsvector3f_bufferoffset = wanttangents ? ent->animcache_svector3f_bufferoffset : 0; rsurface.modeltvector3f = wanttangents ? ent->animcache_tvector3f : NULL; rsurface.modeltvector3f_vertexbuffer = wanttangents ? ent->animcache_tvector3f_vertexbuffer : NULL; rsurface.modeltvector3f_bufferoffset = wanttangents ? ent->animcache_tvector3f_bufferoffset : 0; rsurface.modelnormal3f = wantnormals ? ent->animcache_normal3f : NULL; rsurface.modelnormal3f_vertexbuffer = wantnormals ? ent->animcache_normal3f_vertexbuffer : NULL; rsurface.modelnormal3f_bufferoffset = wantnormals ? ent->animcache_normal3f_bufferoffset : 0; rsurface.modelvertexmesh = ent->animcache_vertexmesh; rsurface.modelvertexmesh_vertexbuffer = ent->animcache_vertexmesh_vertexbuffer; rsurface.modelvertexmesh_bufferoffset = ent->animcache_vertexmesh_bufferoffset; } else if (wanttangents) { r_refdef.stats[r_stat_batch_entityanimate_count]++; r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); rsurface.modelsvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); rsurface.modeltvector3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, rsurface.modelsvector3f, rsurface.modeltvector3f); rsurface.modelvertexmesh = NULL; rsurface.modelvertexmesh_vertexbuffer = NULL; rsurface.modelvertexmesh_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = NULL; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = 0; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelsvector3f_vertexbuffer = 0; rsurface.modelsvector3f_bufferoffset = 0; rsurface.modeltvector3f_vertexbuffer = 0; rsurface.modeltvector3f_bufferoffset = 0; rsurface.modelnormal3f_vertexbuffer = 0; rsurface.modelnormal3f_bufferoffset = 0; } else if (wantnormals) { r_refdef.stats[r_stat_batch_entityanimate_count]++; r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); rsurface.modelsvector3f = NULL; rsurface.modeltvector3f = NULL; rsurface.modelnormal3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, rsurface.modelnormal3f, NULL, NULL); rsurface.modelvertexmesh = NULL; rsurface.modelvertexmesh_vertexbuffer = NULL; rsurface.modelvertexmesh_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = NULL; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = 0; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelsvector3f_vertexbuffer = 0; rsurface.modelsvector3f_bufferoffset = 0; rsurface.modeltvector3f_vertexbuffer = 0; rsurface.modeltvector3f_bufferoffset = 0; rsurface.modelnormal3f_vertexbuffer = 0; rsurface.modelnormal3f_bufferoffset = 0; } else { r_refdef.stats[r_stat_batch_entityanimate_count]++; r_refdef.stats[r_stat_batch_entityanimate_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entityanimate_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entityanimate_triangles] += model->surfmesh.num_triangles; rsurface.modelvertex3f = (float *)R_FrameData_Alloc(model->surfmesh.num_vertices * sizeof(float[3])); rsurface.modelsvector3f = NULL; rsurface.modeltvector3f = NULL; rsurface.modelnormal3f = NULL; model->AnimateVertices(model, rsurface.frameblend, rsurface.skeleton, rsurface.modelvertex3f, NULL, NULL, NULL); rsurface.modelvertexmesh = NULL; rsurface.modelvertexmesh_vertexbuffer = NULL; rsurface.modelvertexmesh_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = NULL; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = 0; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelsvector3f_vertexbuffer = 0; rsurface.modelsvector3f_bufferoffset = 0; rsurface.modeltvector3f_vertexbuffer = 0; rsurface.modeltvector3f_bufferoffset = 0; rsurface.modelnormal3f_vertexbuffer = 0; rsurface.modelnormal3f_bufferoffset = 0; } rsurface.modelgeneratedvertex = true; } else { if (rsurface.entityskeletaltransform3x4) { r_refdef.stats[r_stat_batch_entityskeletal_count]++; r_refdef.stats[r_stat_batch_entityskeletal_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entityskeletal_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entityskeletal_triangles] += model->surfmesh.num_triangles; } else { r_refdef.stats[r_stat_batch_entitystatic_count]++; r_refdef.stats[r_stat_batch_entitystatic_surfaces] += model->num_surfaces; r_refdef.stats[r_stat_batch_entitystatic_vertices] += model->surfmesh.num_vertices; r_refdef.stats[r_stat_batch_entitystatic_triangles] += model->surfmesh.num_triangles; } rsurface.modelvertex3f = model->surfmesh.data_vertex3f; rsurface.modelvertex3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelvertex3f_bufferoffset = model->surfmesh.vbooffset_vertex3f; rsurface.modelsvector3f = model->surfmesh.data_svector3f; rsurface.modelsvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelsvector3f_bufferoffset = model->surfmesh.vbooffset_svector3f; rsurface.modeltvector3f = model->surfmesh.data_tvector3f; rsurface.modeltvector3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltvector3f_bufferoffset = model->surfmesh.vbooffset_tvector3f; rsurface.modelnormal3f = model->surfmesh.data_normal3f; rsurface.modelnormal3f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelnormal3f_bufferoffset = model->surfmesh.vbooffset_normal3f; rsurface.modelvertexmesh = model->surfmesh.data_vertexmesh; rsurface.modelvertexmesh_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelvertexmesh_bufferoffset = model->surfmesh.vbooffset_vertex3f; rsurface.modelgeneratedvertex = false; } rsurface.modellightmapcolor4f = model->surfmesh.data_lightmapcolor4f; rsurface.modellightmapcolor4f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modellightmapcolor4f_bufferoffset = model->surfmesh.vbooffset_lightmapcolor4f; rsurface.modeltexcoordtexture2f = model->surfmesh.data_texcoordtexture2f; rsurface.modeltexcoordtexture2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltexcoordtexture2f_bufferoffset = model->surfmesh.vbooffset_texcoordtexture2f; rsurface.modeltexcoordlightmap2f = model->surfmesh.data_texcoordlightmap2f; rsurface.modeltexcoordlightmap2f_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modeltexcoordlightmap2f_bufferoffset = model->surfmesh.vbooffset_texcoordlightmap2f; rsurface.modelskeletalindex4ub = model->surfmesh.data_skeletalindex4ub; rsurface.modelskeletalindex4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelskeletalindex4ub_bufferoffset = model->surfmesh.vbooffset_skeletalindex4ub; rsurface.modelskeletalweight4ub = model->surfmesh.data_skeletalweight4ub; rsurface.modelskeletalweight4ub_vertexbuffer = model->surfmesh.vbo_vertexbuffer; rsurface.modelskeletalweight4ub_bufferoffset = model->surfmesh.vbooffset_skeletalweight4ub; rsurface.modelelement3i = model->surfmesh.data_element3i; rsurface.modelelement3i_indexbuffer = model->surfmesh.data_element3i_indexbuffer; rsurface.modelelement3i_bufferoffset = model->surfmesh.data_element3i_bufferoffset; rsurface.modelelement3s = model->surfmesh.data_element3s; rsurface.modelelement3s_indexbuffer = model->surfmesh.data_element3s_indexbuffer; rsurface.modelelement3s_bufferoffset = model->surfmesh.data_element3s_bufferoffset; rsurface.modellightmapoffsets = model->surfmesh.data_lightmapoffsets; rsurface.modelnumvertices = model->surfmesh.num_vertices; rsurface.modelnumtriangles = model->surfmesh.num_triangles; rsurface.modelsurfaces = model->data_surfaces; rsurface.batchgeneratedvertex = false; rsurface.batchfirstvertex = 0; rsurface.batchnumvertices = 0; rsurface.batchfirsttriangle = 0; rsurface.batchnumtriangles = 0; rsurface.batchvertex3f = NULL; rsurface.batchvertex3f_vertexbuffer = NULL; rsurface.batchvertex3f_bufferoffset = 0; rsurface.batchsvector3f = NULL; rsurface.batchsvector3f_vertexbuffer = NULL; rsurface.batchsvector3f_bufferoffset = 0; rsurface.batchtvector3f = NULL; rsurface.batchtvector3f_vertexbuffer = NULL; rsurface.batchtvector3f_bufferoffset = 0; rsurface.batchnormal3f = NULL; rsurface.batchnormal3f_vertexbuffer = NULL; rsurface.batchnormal3f_bufferoffset = 0; rsurface.batchlightmapcolor4f = NULL; rsurface.batchlightmapcolor4f_vertexbuffer = NULL; rsurface.batchlightmapcolor4f_bufferoffset = 0; rsurface.batchtexcoordtexture2f = NULL; rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; rsurface.batchtexcoordtexture2f_bufferoffset = 0; rsurface.batchtexcoordlightmap2f = NULL; rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; rsurface.batchtexcoordlightmap2f_bufferoffset = 0; rsurface.batchskeletalindex4ub = NULL; rsurface.batchskeletalindex4ub_vertexbuffer = NULL; rsurface.batchskeletalindex4ub_bufferoffset = 0; rsurface.batchskeletalweight4ub = NULL; rsurface.batchskeletalweight4ub_vertexbuffer = NULL; rsurface.batchskeletalweight4ub_bufferoffset = 0; rsurface.batchvertexmesh = NULL; rsurface.batchvertexmesh_vertexbuffer = NULL; rsurface.batchvertexmesh_bufferoffset = 0; rsurface.batchelement3i = NULL; rsurface.batchelement3i_indexbuffer = NULL; rsurface.batchelement3i_bufferoffset = 0; rsurface.batchelement3s = NULL; rsurface.batchelement3s_indexbuffer = NULL; rsurface.batchelement3s_bufferoffset = 0; rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = NULL; rsurface.passcolor4f_bufferoffset = 0; rsurface.forcecurrenttextureupdate = false; } void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents) { rsurface.entity = r_refdef.scene.worldentity; rsurface.skeleton = NULL; rsurface.ent_skinnum = 0; rsurface.ent_qwskin = -1; rsurface.ent_flags = entflags; rsurface.shadertime = r_refdef.scene.time - shadertime; rsurface.modelnumvertices = numvertices; rsurface.modelnumtriangles = numtriangles; rsurface.matrix = *matrix; rsurface.inversematrix = *inversematrix; rsurface.matrixscale = Matrix4x4_ScaleFromMatrix(&rsurface.matrix); rsurface.inversematrixscale = 1.0f / rsurface.matrixscale; R_EntityMatrix(&rsurface.matrix); Matrix4x4_Transform(&rsurface.inversematrix, r_refdef.view.origin, rsurface.localvieworigin); Matrix4x4_TransformStandardPlane(&rsurface.inversematrix, r_refdef.fogplane[0], r_refdef.fogplane[1], r_refdef.fogplane[2], r_refdef.fogplane[3], rsurface.fogplane); rsurface.fogplaneviewdist *= rsurface.inversematrixscale; rsurface.fograngerecip = r_refdef.fograngerecip * rsurface.matrixscale; rsurface.fogheightfade = r_refdef.fogheightfade * rsurface.matrixscale; rsurface.fogmasktabledistmultiplier = FOGMASKTABLEWIDTH * rsurface.fograngerecip; VectorSet(rsurface.modellight_ambient, 0, 0, 0); VectorSet(rsurface.modellight_diffuse, 0, 0, 0); VectorSet(rsurface.modellight_lightdir, 0, 0, 1); VectorSet(rsurface.colormap_pantscolor, 0, 0, 0); VectorSet(rsurface.colormap_shirtcolor, 0, 0, 0); Vector4Set(rsurface.colormod, r * r_refdef.view.colorscale, g * r_refdef.view.colorscale, b * r_refdef.view.colorscale, a); VectorSet(rsurface.glowmod, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value, r_refdef.view.colorscale * r_hdr_glowintensity.value); memset(rsurface.frameblend, 0, sizeof(rsurface.frameblend)); rsurface.frameblend[0].lerp = 1; rsurface.ent_alttextures = false; rsurface.basepolygonfactor = r_refdef.polygonfactor; rsurface.basepolygonoffset = r_refdef.polygonoffset; rsurface.entityskeletaltransform3x4 = NULL; rsurface.entityskeletaltransform3x4buffer = NULL; rsurface.entityskeletaltransform3x4offset = 0; rsurface.entityskeletaltransform3x4size = 0; rsurface.entityskeletalnumtransforms = 0; r_refdef.stats[r_stat_batch_entitycustom_count]++; r_refdef.stats[r_stat_batch_entitycustom_surfaces] += 1; r_refdef.stats[r_stat_batch_entitycustom_vertices] += rsurface.modelnumvertices; r_refdef.stats[r_stat_batch_entitycustom_triangles] += rsurface.modelnumtriangles; if (wanttangents) { rsurface.modelvertex3f = (float *)vertex3f; rsurface.modelsvector3f = svector3f ? (float *)svector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); rsurface.modeltvector3f = tvector3f ? (float *)tvector3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); } else if (wantnormals) { rsurface.modelvertex3f = (float *)vertex3f; rsurface.modelsvector3f = NULL; rsurface.modeltvector3f = NULL; rsurface.modelnormal3f = normal3f ? (float *)normal3f : (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); } else { rsurface.modelvertex3f = (float *)vertex3f; rsurface.modelsvector3f = NULL; rsurface.modeltvector3f = NULL; rsurface.modelnormal3f = NULL; } rsurface.modelvertexmesh = NULL; rsurface.modelvertexmesh_vertexbuffer = NULL; rsurface.modelvertexmesh_bufferoffset = 0; rsurface.modelvertex3f_vertexbuffer = 0; rsurface.modelvertex3f_bufferoffset = 0; rsurface.modelsvector3f_vertexbuffer = 0; rsurface.modelsvector3f_bufferoffset = 0; rsurface.modeltvector3f_vertexbuffer = 0; rsurface.modeltvector3f_bufferoffset = 0; rsurface.modelnormal3f_vertexbuffer = 0; rsurface.modelnormal3f_bufferoffset = 0; rsurface.modelgeneratedvertex = true; rsurface.modellightmapcolor4f = (float *)color4f; rsurface.modellightmapcolor4f_vertexbuffer = 0; rsurface.modellightmapcolor4f_bufferoffset = 0; rsurface.modeltexcoordtexture2f = (float *)texcoord2f; rsurface.modeltexcoordtexture2f_vertexbuffer = 0; rsurface.modeltexcoordtexture2f_bufferoffset = 0; rsurface.modeltexcoordlightmap2f = NULL; rsurface.modeltexcoordlightmap2f_vertexbuffer = 0; rsurface.modeltexcoordlightmap2f_bufferoffset = 0; rsurface.modelskeletalindex4ub = NULL; rsurface.modelskeletalindex4ub_vertexbuffer = NULL; rsurface.modelskeletalindex4ub_bufferoffset = 0; rsurface.modelskeletalweight4ub = NULL; rsurface.modelskeletalweight4ub_vertexbuffer = NULL; rsurface.modelskeletalweight4ub_bufferoffset = 0; rsurface.modelelement3i = (int *)element3i; rsurface.modelelement3i_indexbuffer = NULL; rsurface.modelelement3i_bufferoffset = 0; rsurface.modelelement3s = (unsigned short *)element3s; rsurface.modelelement3s_indexbuffer = NULL; rsurface.modelelement3s_bufferoffset = 0; rsurface.modellightmapoffsets = NULL; rsurface.modelsurfaces = NULL; rsurface.batchgeneratedvertex = false; rsurface.batchfirstvertex = 0; rsurface.batchnumvertices = 0; rsurface.batchfirsttriangle = 0; rsurface.batchnumtriangles = 0; rsurface.batchvertex3f = NULL; rsurface.batchvertex3f_vertexbuffer = NULL; rsurface.batchvertex3f_bufferoffset = 0; rsurface.batchsvector3f = NULL; rsurface.batchsvector3f_vertexbuffer = NULL; rsurface.batchsvector3f_bufferoffset = 0; rsurface.batchtvector3f = NULL; rsurface.batchtvector3f_vertexbuffer = NULL; rsurface.batchtvector3f_bufferoffset = 0; rsurface.batchnormal3f = NULL; rsurface.batchnormal3f_vertexbuffer = NULL; rsurface.batchnormal3f_bufferoffset = 0; rsurface.batchlightmapcolor4f = NULL; rsurface.batchlightmapcolor4f_vertexbuffer = NULL; rsurface.batchlightmapcolor4f_bufferoffset = 0; rsurface.batchtexcoordtexture2f = NULL; rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; rsurface.batchtexcoordtexture2f_bufferoffset = 0; rsurface.batchtexcoordlightmap2f = NULL; rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; rsurface.batchtexcoordlightmap2f_bufferoffset = 0; rsurface.batchskeletalindex4ub = NULL; rsurface.batchskeletalindex4ub_vertexbuffer = NULL; rsurface.batchskeletalindex4ub_bufferoffset = 0; rsurface.batchskeletalweight4ub = NULL; rsurface.batchskeletalweight4ub_vertexbuffer = NULL; rsurface.batchskeletalweight4ub_bufferoffset = 0; rsurface.batchvertexmesh = NULL; rsurface.batchvertexmesh_vertexbuffer = NULL; rsurface.batchvertexmesh_bufferoffset = 0; rsurface.batchelement3i = NULL; rsurface.batchelement3i_indexbuffer = NULL; rsurface.batchelement3i_bufferoffset = 0; rsurface.batchelement3s = NULL; rsurface.batchelement3s_indexbuffer = NULL; rsurface.batchelement3s_bufferoffset = 0; rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = NULL; rsurface.passcolor4f_bufferoffset = 0; rsurface.forcecurrenttextureupdate = true; if (rsurface.modelnumvertices && rsurface.modelelement3i) { if ((wantnormals || wanttangents) && !normal3f) { rsurface.modelnormal3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); Mod_BuildNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modelelement3i, rsurface.modelnormal3f, r_smoothnormals_areaweighting.integer != 0); } if (wanttangents && !svector3f) { rsurface.modelsvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); rsurface.modeltvector3f = (float *)R_FrameData_Alloc(rsurface.modelnumvertices * sizeof(float[3])); Mod_BuildTextureVectorsFromNormals(0, rsurface.modelnumvertices, rsurface.modelnumtriangles, rsurface.modelvertex3f, rsurface.modeltexcoordtexture2f, rsurface.modelnormal3f, rsurface.modelelement3i, rsurface.modelsvector3f, rsurface.modeltvector3f, r_smoothnormals_areaweighting.integer != 0); } } } float RSurf_FogPoint(const float *v) { // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader float FogPlaneViewDist = r_refdef.fogplaneviewdist; float FogPlaneVertexDist = DotProduct(r_refdef.fogplane, v) + r_refdef.fogplane[3]; float FogHeightFade = r_refdef.fogheightfade; float fogfrac; unsigned int fogmasktableindex; if (r_refdef.fogplaneviewabove) fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); else fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); fogmasktableindex = (unsigned int)(VectorDistance(r_refdef.view.origin, v) * fogfrac * r_refdef.fogmasktabledistmultiplier); return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; } float RSurf_FogVertex(const float *v) { // this code is identical to the USEFOGINSIDE/USEFOGOUTSIDE code in the shader float FogPlaneViewDist = rsurface.fogplaneviewdist; float FogPlaneVertexDist = DotProduct(rsurface.fogplane, v) + rsurface.fogplane[3]; float FogHeightFade = rsurface.fogheightfade; float fogfrac; unsigned int fogmasktableindex; if (r_refdef.fogplaneviewabove) fogfrac = min(0.0f, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0f, min(0.0f, FogPlaneVertexDist) * FogHeightFade); else fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0f, FogPlaneVertexDist)) * min(1.0f, (min(0.0f, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade); fogmasktableindex = (unsigned int)(VectorDistance(rsurface.localvieworigin, v) * fogfrac * rsurface.fogmasktabledistmultiplier); return r_refdef.fogmasktable[min(fogmasktableindex, FOGMASKTABLEWIDTH - 1)]; } static void RSurf_RenumberElements(const int *inelement3i, int *outelement3i, int numelements, int adjust) { int i; for (i = 0;i < numelements;i++) outelement3i[i] = inelement3i[i] + adjust; } static const int quadedges[6][2] = {{0, 1}, {0, 2}, {0, 3}, {1, 2}, {1, 3}, {2, 3}}; extern cvar_t gl_vbo; void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist) { int deformindex; int firsttriangle; int numtriangles; int firstvertex; int endvertex; int numvertices; int surfacefirsttriangle; int surfacenumtriangles; int surfacefirstvertex; int surfaceendvertex; int surfacenumvertices; int batchnumsurfaces = texturenumsurfaces; int batchnumvertices; int batchnumtriangles; int needsupdate; int i, j; qboolean gaps; qboolean dynamicvertex; float amplitude; float animpos; float center[3], forward[3], right[3], up[3], v[3], newforward[3], newright[3], newup[3]; float waveparms[4]; unsigned char *ub; q3shaderinfo_deform_t *deform; const msurface_t *surface, *firstsurface; r_vertexmesh_t *vertexmesh; if (!texturenumsurfaces) return; // find vertex range of this surface batch gaps = false; firstsurface = texturesurfacelist[0]; firsttriangle = firstsurface->num_firsttriangle; batchnumvertices = 0; batchnumtriangles = 0; firstvertex = endvertex = firstsurface->num_firstvertex; for (i = 0;i < texturenumsurfaces;i++) { surface = texturesurfacelist[i]; if (surface != firstsurface + i) gaps = true; surfacefirstvertex = surface->num_firstvertex; surfaceendvertex = surfacefirstvertex + surface->num_vertices; surfacenumvertices = surface->num_vertices; surfacenumtriangles = surface->num_triangles; if (firstvertex > surfacefirstvertex) firstvertex = surfacefirstvertex; if (endvertex < surfaceendvertex) endvertex = surfaceendvertex; batchnumvertices += surfacenumvertices; batchnumtriangles += surfacenumtriangles; } r_refdef.stats[r_stat_batch_batches]++; if (gaps) r_refdef.stats[r_stat_batch_withgaps]++; r_refdef.stats[r_stat_batch_surfaces] += batchnumsurfaces; r_refdef.stats[r_stat_batch_vertices] += batchnumvertices; r_refdef.stats[r_stat_batch_triangles] += batchnumtriangles; // we now know the vertex range used, and if there are any gaps in it rsurface.batchfirstvertex = firstvertex; rsurface.batchnumvertices = endvertex - firstvertex; rsurface.batchfirsttriangle = firsttriangle; rsurface.batchnumtriangles = batchnumtriangles; // this variable holds flags for which properties have been updated that // may require regenerating vertexmesh array... needsupdate = 0; // check if any dynamic vertex processing must occur dynamicvertex = false; // a cvar to force the dynamic vertex path to be taken, for debugging if (r_batch_debugdynamicvertexpath.integer) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_cvar] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_cvar] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_cvar] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_cvar] += batchnumtriangles; } dynamicvertex = true; } // if there is a chance of animated vertex colors, it's a dynamic batch if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_lightmapvertex] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_lightmapvertex] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_lightmapvertex] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_lightmapvertex] += batchnumtriangles; } dynamicvertex = true; needsupdate |= BATCHNEED_VERTEXMESH_VERTEXCOLOR; } for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) { switch (deform->deform) { default: case Q3DEFORM_PROJECTIONSHADOW: case Q3DEFORM_TEXT0: case Q3DEFORM_TEXT1: case Q3DEFORM_TEXT2: case Q3DEFORM_TEXT3: case Q3DEFORM_TEXT4: case Q3DEFORM_TEXT5: case Q3DEFORM_TEXT6: case Q3DEFORM_TEXT7: case Q3DEFORM_NONE: break; case Q3DEFORM_AUTOSPRITE: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; break; case Q3DEFORM_AUTOSPRITE2: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_autosprite2] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_autosprite2] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_autosprite2] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_autosprite2] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; break; case Q3DEFORM_NORMAL: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_normal] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_normal] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_normal] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_normal] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; break; case Q3DEFORM_WAVE: if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) break; // if wavefunc is a nop, ignore this transform if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_wave] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_wave] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_wave] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_wave] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; break; case Q3DEFORM_BULGE: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_bulge] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_bulge] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_bulge] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_bulge] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR; break; case Q3DEFORM_MOVE: if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) break; // if wavefunc is a nop, ignore this transform if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_deformvertexes_move] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_deformvertexes_move] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_deformvertexes_move] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_deformvertexes_move] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX; needsupdate |= BATCHNEED_VERTEXMESH_VERTEX; break; } } switch(rsurface.texture->tcgen.tcgen) { default: case Q3TCGEN_TEXTURE: break; case Q3TCGEN_LIGHTMAP: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_lightmap] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_lightmap] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_lightmap] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_lightmap] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_LIGHTMAP; needsupdate |= BATCHNEED_VERTEXMESH_LIGHTMAP; break; case Q3TCGEN_VECTOR: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_vector] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_vector] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_vector] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_vector] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX; needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; break; case Q3TCGEN_ENVIRONMENT: if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_tcgen_environment] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcgen_environment] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcgen_environment] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcgen_environment] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL; needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; break; } if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_tcmod_turbulent] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_tcmod_turbulent] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_tcmod_turbulent] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_tcmod_turbulent] += batchnumtriangles; } dynamicvertex = true; batchneed |= BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_TEXCOORD; needsupdate |= BATCHNEED_VERTEXMESH_TEXCOORD; } if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles; } dynamicvertex = true; needsupdate |= (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)); } // when the model data has no vertex buffer (dynamic mesh), we need to // eliminate gaps if (vid.useinterleavedarrays && !rsurface.modelvertexmesh_vertexbuffer) batchneed |= BATCHNEED_NOGAPS; // the caller can specify BATCHNEED_NOGAPS to force a batch with // firstvertex = 0 and endvertex = numvertices (no gaps, no firstvertex), // we ensure this by treating the vertex batch as dynamic... if ((batchneed & BATCHNEED_NOGAPS) && (gaps || firstvertex > 0)) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_nogaps] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_nogaps] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_nogaps] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_nogaps] += batchnumtriangles; } dynamicvertex = true; } if (dynamicvertex) { // when copying, we need to consider the regeneration of vertexmesh, any dependencies it may have must be set... if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) batchneed |= BATCHNEED_ARRAY_VERTEX; if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) batchneed |= BATCHNEED_ARRAY_NORMAL; if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) batchneed |= BATCHNEED_ARRAY_VECTOR; if (batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) batchneed |= BATCHNEED_ARRAY_VERTEXCOLOR; if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) batchneed |= BATCHNEED_ARRAY_TEXCOORD; if (batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) batchneed |= BATCHNEED_ARRAY_LIGHTMAP; if (batchneed & BATCHNEED_VERTEXMESH_SKELETAL) batchneed |= BATCHNEED_ARRAY_SKELETAL; } // if needsupdate, we have to do a dynamic vertex batch for sure if (needsupdate & batchneed) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_derived] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_derived] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_derived] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_derived] += batchnumtriangles; } dynamicvertex = true; } // see if we need to build vertexmesh from arrays if (!rsurface.modelvertexmesh && (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP))) { if (!dynamicvertex) { r_refdef.stats[r_stat_batch_dynamic_batches_because_interleavedarrays] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces_because_interleavedarrays] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices_because_interleavedarrays] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles_because_interleavedarrays] += batchnumtriangles; } dynamicvertex = true; } // if we're going to have to apply the skeletal transform manually, we need to batch the skeletal data if (dynamicvertex && rsurface.entityskeletaltransform3x4) batchneed |= BATCHNEED_ARRAY_SKELETAL; rsurface.batchvertex3f = rsurface.modelvertex3f; rsurface.batchvertex3f_vertexbuffer = rsurface.modelvertex3f_vertexbuffer; rsurface.batchvertex3f_bufferoffset = rsurface.modelvertex3f_bufferoffset; rsurface.batchsvector3f = rsurface.modelsvector3f; rsurface.batchsvector3f_vertexbuffer = rsurface.modelsvector3f_vertexbuffer; rsurface.batchsvector3f_bufferoffset = rsurface.modelsvector3f_bufferoffset; rsurface.batchtvector3f = rsurface.modeltvector3f; rsurface.batchtvector3f_vertexbuffer = rsurface.modeltvector3f_vertexbuffer; rsurface.batchtvector3f_bufferoffset = rsurface.modeltvector3f_bufferoffset; rsurface.batchnormal3f = rsurface.modelnormal3f; rsurface.batchnormal3f_vertexbuffer = rsurface.modelnormal3f_vertexbuffer; rsurface.batchnormal3f_bufferoffset = rsurface.modelnormal3f_bufferoffset; rsurface.batchlightmapcolor4f = rsurface.modellightmapcolor4f; rsurface.batchlightmapcolor4f_vertexbuffer = rsurface.modellightmapcolor4f_vertexbuffer; rsurface.batchlightmapcolor4f_bufferoffset = rsurface.modellightmapcolor4f_bufferoffset; rsurface.batchtexcoordtexture2f = rsurface.modeltexcoordtexture2f; rsurface.batchtexcoordtexture2f_vertexbuffer = rsurface.modeltexcoordtexture2f_vertexbuffer; rsurface.batchtexcoordtexture2f_bufferoffset = rsurface.modeltexcoordtexture2f_bufferoffset; rsurface.batchtexcoordlightmap2f = rsurface.modeltexcoordlightmap2f; rsurface.batchtexcoordlightmap2f_vertexbuffer = rsurface.modeltexcoordlightmap2f_vertexbuffer; rsurface.batchtexcoordlightmap2f_bufferoffset = rsurface.modeltexcoordlightmap2f_bufferoffset; rsurface.batchskeletalindex4ub = rsurface.modelskeletalindex4ub; rsurface.batchskeletalindex4ub_vertexbuffer = rsurface.modelskeletalindex4ub_vertexbuffer; rsurface.batchskeletalindex4ub_bufferoffset = rsurface.modelskeletalindex4ub_bufferoffset; rsurface.batchskeletalweight4ub = rsurface.modelskeletalweight4ub; rsurface.batchskeletalweight4ub_vertexbuffer = rsurface.modelskeletalweight4ub_vertexbuffer; rsurface.batchskeletalweight4ub_bufferoffset = rsurface.modelskeletalweight4ub_bufferoffset; rsurface.batchvertexmesh = rsurface.modelvertexmesh; rsurface.batchvertexmesh_vertexbuffer = rsurface.modelvertexmesh_vertexbuffer; rsurface.batchvertexmesh_bufferoffset = rsurface.modelvertexmesh_bufferoffset; rsurface.batchelement3i = rsurface.modelelement3i; rsurface.batchelement3i_indexbuffer = rsurface.modelelement3i_indexbuffer; rsurface.batchelement3i_bufferoffset = rsurface.modelelement3i_bufferoffset; rsurface.batchelement3s = rsurface.modelelement3s; rsurface.batchelement3s_indexbuffer = rsurface.modelelement3s_indexbuffer; rsurface.batchelement3s_bufferoffset = rsurface.modelelement3s_bufferoffset; rsurface.batchskeletaltransform3x4 = rsurface.entityskeletaltransform3x4; rsurface.batchskeletaltransform3x4buffer = rsurface.entityskeletaltransform3x4buffer; rsurface.batchskeletaltransform3x4offset = rsurface.entityskeletaltransform3x4offset; rsurface.batchskeletaltransform3x4size = rsurface.entityskeletaltransform3x4size; rsurface.batchskeletalnumtransforms = rsurface.entityskeletalnumtransforms; // if any dynamic vertex processing has to occur in software, we copy the // entire surface list together before processing to rebase the vertices // to start at 0 (otherwise we waste a lot of room in a vertex buffer). // // if any gaps exist and we do not have a static vertex buffer, we have to // copy the surface list together to avoid wasting upload bandwidth on the // vertices in the gaps. // // if gaps exist and we have a static vertex buffer, we can choose whether // to combine the index buffer ranges into one dynamic index buffer or // simply issue multiple glDrawElements calls (BATCHNEED_ALLOWMULTIDRAW). // // in many cases the batch is reduced to one draw call. rsurface.batchmultidraw = false; rsurface.batchmultidrawnumsurfaces = 0; rsurface.batchmultidrawsurfacelist = NULL; if (!dynamicvertex) { // static vertex data, just set pointers... rsurface.batchgeneratedvertex = false; // if there are gaps, we want to build a combined index buffer, // otherwise use the original static buffer with an appropriate offset if (gaps) { r_refdef.stats[r_stat_batch_copytriangles_batches] += 1; r_refdef.stats[r_stat_batch_copytriangles_surfaces] += batchnumsurfaces; r_refdef.stats[r_stat_batch_copytriangles_vertices] += batchnumvertices; r_refdef.stats[r_stat_batch_copytriangles_triangles] += batchnumtriangles; if ((batchneed & BATCHNEED_ALLOWMULTIDRAW) && r_batch_multidraw.integer && batchnumtriangles >= r_batch_multidraw_mintriangles.integer) { rsurface.batchmultidraw = true; rsurface.batchmultidrawnumsurfaces = texturenumsurfaces; rsurface.batchmultidrawsurfacelist = texturesurfacelist; return; } // build a new triangle elements array for this batch rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); rsurface.batchfirsttriangle = 0; numtriangles = 0; for (i = 0;i < texturenumsurfaces;i++) { surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; surfacenumtriangles = texturesurfacelist[i]->num_triangles; memcpy(rsurface.batchelement3i + 3*numtriangles, rsurface.modelelement3i + 3*surfacefirsttriangle, surfacenumtriangles*sizeof(int[3])); numtriangles += surfacenumtriangles; } rsurface.batchelement3i_indexbuffer = NULL; rsurface.batchelement3i_bufferoffset = 0; rsurface.batchelement3s = NULL; rsurface.batchelement3s_indexbuffer = NULL; rsurface.batchelement3s_bufferoffset = 0; if (endvertex <= 65536) { // make a 16bit (unsigned short) index array if possible rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); for (i = 0;i < numtriangles*3;i++) rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; } // upload buffer data for the copytriangles batch if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo) { if (rsurface.batchelement3s) rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset); else if (rsurface.batchelement3i) rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset); } } else { r_refdef.stats[r_stat_batch_fast_batches] += 1; r_refdef.stats[r_stat_batch_fast_surfaces] += batchnumsurfaces; r_refdef.stats[r_stat_batch_fast_vertices] += batchnumvertices; r_refdef.stats[r_stat_batch_fast_triangles] += batchnumtriangles; } return; } // something needs software processing, do it for real... // we only directly handle separate array data in this case and then // generate interleaved data if needed... rsurface.batchgeneratedvertex = true; r_refdef.stats[r_stat_batch_dynamic_batches] += 1; r_refdef.stats[r_stat_batch_dynamic_surfaces] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamic_vertices] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamic_triangles] += batchnumtriangles; // now copy the vertex data into a combined array and make an index array // (this is what Quake3 does all the time) // we also apply any skeletal animation here that would have been done in // the vertex shader, because most of the dynamic vertex animation cases // need actual vertex positions and normals //if (dynamicvertex) { rsurface.batchvertexmesh = NULL; rsurface.batchvertexmesh_vertexbuffer = NULL; rsurface.batchvertexmesh_bufferoffset = 0; rsurface.batchvertex3f = NULL; rsurface.batchvertex3f_vertexbuffer = NULL; rsurface.batchvertex3f_bufferoffset = 0; rsurface.batchsvector3f = NULL; rsurface.batchsvector3f_vertexbuffer = NULL; rsurface.batchsvector3f_bufferoffset = 0; rsurface.batchtvector3f = NULL; rsurface.batchtvector3f_vertexbuffer = NULL; rsurface.batchtvector3f_bufferoffset = 0; rsurface.batchnormal3f = NULL; rsurface.batchnormal3f_vertexbuffer = NULL; rsurface.batchnormal3f_bufferoffset = 0; rsurface.batchlightmapcolor4f = NULL; rsurface.batchlightmapcolor4f_vertexbuffer = NULL; rsurface.batchlightmapcolor4f_bufferoffset = 0; rsurface.batchtexcoordtexture2f = NULL; rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; rsurface.batchtexcoordtexture2f_bufferoffset = 0; rsurface.batchtexcoordlightmap2f = NULL; rsurface.batchtexcoordlightmap2f_vertexbuffer = NULL; rsurface.batchtexcoordlightmap2f_bufferoffset = 0; rsurface.batchskeletalindex4ub = NULL; rsurface.batchskeletalindex4ub_vertexbuffer = NULL; rsurface.batchskeletalindex4ub_bufferoffset = 0; rsurface.batchskeletalweight4ub = NULL; rsurface.batchskeletalweight4ub_vertexbuffer = NULL; rsurface.batchskeletalweight4ub_bufferoffset = 0; rsurface.batchelement3i = (int *)R_FrameData_Alloc(batchnumtriangles * sizeof(int[3])); rsurface.batchelement3i_indexbuffer = NULL; rsurface.batchelement3i_bufferoffset = 0; rsurface.batchelement3s = NULL; rsurface.batchelement3s_indexbuffer = NULL; rsurface.batchelement3s_bufferoffset = 0; rsurface.batchskeletaltransform3x4buffer = NULL; rsurface.batchskeletaltransform3x4offset = 0; rsurface.batchskeletaltransform3x4size = 0; // we'll only be setting up certain arrays as needed if (batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) rsurface.batchvertexmesh = (r_vertexmesh_t *)R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); if (batchneed & BATCHNEED_ARRAY_VERTEX) rsurface.batchvertex3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); if (batchneed & BATCHNEED_ARRAY_NORMAL) rsurface.batchnormal3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); if (batchneed & BATCHNEED_ARRAY_VECTOR) { rsurface.batchsvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); rsurface.batchtvector3f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); } if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); if (batchneed & BATCHNEED_ARRAY_TEXCOORD) rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) rsurface.batchtexcoordlightmap2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); if (batchneed & BATCHNEED_ARRAY_SKELETAL) { rsurface.batchskeletalindex4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4])); rsurface.batchskeletalweight4ub = (unsigned char *)R_FrameData_Alloc(batchnumvertices * sizeof(unsigned char[4])); } numvertices = 0; numtriangles = 0; for (i = 0;i < texturenumsurfaces;i++) { surfacefirstvertex = texturesurfacelist[i]->num_firstvertex; surfacenumvertices = texturesurfacelist[i]->num_vertices; surfacefirsttriangle = texturesurfacelist[i]->num_firsttriangle; surfacenumtriangles = texturesurfacelist[i]->num_triangles; // copy only the data requested if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) && rsurface.modelvertexmesh) memcpy(rsurface.batchvertexmesh + numvertices, rsurface.modelvertexmesh + surfacefirstvertex, surfacenumvertices * sizeof(rsurface.batchvertexmesh[0])); if (batchneed & (BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_ARRAY_LIGHTMAP)) { if (batchneed & BATCHNEED_ARRAY_VERTEX) { if (rsurface.batchvertex3f) memcpy(rsurface.batchvertex3f + 3*numvertices, rsurface.modelvertex3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); else memset(rsurface.batchvertex3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); } if (batchneed & BATCHNEED_ARRAY_NORMAL) { if (rsurface.modelnormal3f) memcpy(rsurface.batchnormal3f + 3*numvertices, rsurface.modelnormal3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); else memset(rsurface.batchnormal3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); } if (batchneed & BATCHNEED_ARRAY_VECTOR) { if (rsurface.modelsvector3f) { memcpy(rsurface.batchsvector3f + 3*numvertices, rsurface.modelsvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); memcpy(rsurface.batchtvector3f + 3*numvertices, rsurface.modeltvector3f + 3*surfacefirstvertex, surfacenumvertices * sizeof(float[3])); } else { memset(rsurface.batchsvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); memset(rsurface.batchtvector3f + 3*numvertices, 0, surfacenumvertices * sizeof(float[3])); } } if (batchneed & BATCHNEED_ARRAY_VERTEXCOLOR) { if (rsurface.modellightmapcolor4f) memcpy(rsurface.batchlightmapcolor4f + 4*numvertices, rsurface.modellightmapcolor4f + 4*surfacefirstvertex, surfacenumvertices * sizeof(float[4])); else memset(rsurface.batchlightmapcolor4f + 4*numvertices, 0, surfacenumvertices * sizeof(float[4])); } if (batchneed & BATCHNEED_ARRAY_TEXCOORD) { if (rsurface.modeltexcoordtexture2f) memcpy(rsurface.batchtexcoordtexture2f + 2*numvertices, rsurface.modeltexcoordtexture2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); else memset(rsurface.batchtexcoordtexture2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); } if (batchneed & BATCHNEED_ARRAY_LIGHTMAP) { if (rsurface.modeltexcoordlightmap2f) memcpy(rsurface.batchtexcoordlightmap2f + 2*numvertices, rsurface.modeltexcoordlightmap2f + 2*surfacefirstvertex, surfacenumvertices * sizeof(float[2])); else memset(rsurface.batchtexcoordlightmap2f + 2*numvertices, 0, surfacenumvertices * sizeof(float[2])); } if (batchneed & BATCHNEED_ARRAY_SKELETAL) { if (rsurface.modelskeletalindex4ub) { memcpy(rsurface.batchskeletalindex4ub + 4*numvertices, rsurface.modelskeletalindex4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4])); memcpy(rsurface.batchskeletalweight4ub + 4*numvertices, rsurface.modelskeletalweight4ub + 4*surfacefirstvertex, surfacenumvertices * sizeof(unsigned char[4])); } else { memset(rsurface.batchskeletalindex4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4])); memset(rsurface.batchskeletalweight4ub + 4*numvertices, 0, surfacenumvertices * sizeof(unsigned char[4])); ub = rsurface.batchskeletalweight4ub + 4*numvertices; for (j = 0;j < surfacenumvertices;j++) ub[j*4] = 255; } } } RSurf_RenumberElements(rsurface.modelelement3i + 3*surfacefirsttriangle, rsurface.batchelement3i + 3*numtriangles, 3*surfacenumtriangles, numvertices - surfacefirstvertex); numvertices += surfacenumvertices; numtriangles += surfacenumtriangles; } // generate a 16bit index array as well if possible // (in general, dynamic batches fit) if (numvertices <= 65536) { rsurface.batchelement3s = (unsigned short *)R_FrameData_Alloc(batchnumtriangles * sizeof(unsigned short[3])); for (i = 0;i < numtriangles*3;i++) rsurface.batchelement3s[i] = rsurface.batchelement3i[i]; } // since we've copied everything, the batch now starts at 0 rsurface.batchfirstvertex = 0; rsurface.batchnumvertices = batchnumvertices; rsurface.batchfirsttriangle = 0; rsurface.batchnumtriangles = batchnumtriangles; } // apply skeletal animation that would have been done in the vertex shader if (rsurface.batchskeletaltransform3x4) { const unsigned char *si; const unsigned char *sw; const float *t[4]; const float *b = rsurface.batchskeletaltransform3x4; float *vp, *vs, *vt, *vn; float w[4]; float m[3][4], n[3][4]; float tp[3], ts[3], tt[3], tn[3]; r_refdef.stats[r_stat_batch_dynamicskeletal_batches] += 1; r_refdef.stats[r_stat_batch_dynamicskeletal_surfaces] += batchnumsurfaces; r_refdef.stats[r_stat_batch_dynamicskeletal_vertices] += batchnumvertices; r_refdef.stats[r_stat_batch_dynamicskeletal_triangles] += batchnumtriangles; si = rsurface.batchskeletalindex4ub; sw = rsurface.batchskeletalweight4ub; vp = rsurface.batchvertex3f; vs = rsurface.batchsvector3f; vt = rsurface.batchtvector3f; vn = rsurface.batchnormal3f; memset(m[0], 0, sizeof(m)); memset(n[0], 0, sizeof(n)); for (i = 0;i < batchnumvertices;i++) { t[0] = b + si[0]*12; if (sw[0] == 255) { // common case - only one matrix m[0][0] = t[0][ 0]; m[0][1] = t[0][ 1]; m[0][2] = t[0][ 2]; m[0][3] = t[0][ 3]; m[1][0] = t[0][ 4]; m[1][1] = t[0][ 5]; m[1][2] = t[0][ 6]; m[1][3] = t[0][ 7]; m[2][0] = t[0][ 8]; m[2][1] = t[0][ 9]; m[2][2] = t[0][10]; m[2][3] = t[0][11]; } else if (sw[2] + sw[3]) { // blend 4 matrices t[1] = b + si[1]*12; t[2] = b + si[2]*12; t[3] = b + si[3]*12; w[0] = sw[0] * (1.0f / 255.0f); w[1] = sw[1] * (1.0f / 255.0f); w[2] = sw[2] * (1.0f / 255.0f); w[3] = sw[3] * (1.0f / 255.0f); // blend the matrices m[0][0] = t[0][ 0] * w[0] + t[1][ 0] * w[1] + t[2][ 0] * w[2] + t[3][ 0] * w[3]; m[0][1] = t[0][ 1] * w[0] + t[1][ 1] * w[1] + t[2][ 1] * w[2] + t[3][ 1] * w[3]; m[0][2] = t[0][ 2] * w[0] + t[1][ 2] * w[1] + t[2][ 2] * w[2] + t[3][ 2] * w[3]; m[0][3] = t[0][ 3] * w[0] + t[1][ 3] * w[1] + t[2][ 3] * w[2] + t[3][ 3] * w[3]; m[1][0] = t[0][ 4] * w[0] + t[1][ 4] * w[1] + t[2][ 4] * w[2] + t[3][ 4] * w[3]; m[1][1] = t[0][ 5] * w[0] + t[1][ 5] * w[1] + t[2][ 5] * w[2] + t[3][ 5] * w[3]; m[1][2] = t[0][ 6] * w[0] + t[1][ 6] * w[1] + t[2][ 6] * w[2] + t[3][ 6] * w[3]; m[1][3] = t[0][ 7] * w[0] + t[1][ 7] * w[1] + t[2][ 7] * w[2] + t[3][ 7] * w[3]; m[2][0] = t[0][ 8] * w[0] + t[1][ 8] * w[1] + t[2][ 8] * w[2] + t[3][ 8] * w[3]; m[2][1] = t[0][ 9] * w[0] + t[1][ 9] * w[1] + t[2][ 9] * w[2] + t[3][ 9] * w[3]; m[2][2] = t[0][10] * w[0] + t[1][10] * w[1] + t[2][10] * w[2] + t[3][10] * w[3]; m[2][3] = t[0][11] * w[0] + t[1][11] * w[1] + t[2][11] * w[2] + t[3][11] * w[3]; } else { // blend 2 matrices t[1] = b + si[1]*12; w[0] = sw[0] * (1.0f / 255.0f); w[1] = sw[1] * (1.0f / 255.0f); // blend the matrices m[0][0] = t[0][ 0] * w[0] + t[1][ 0] * w[1]; m[0][1] = t[0][ 1] * w[0] + t[1][ 1] * w[1]; m[0][2] = t[0][ 2] * w[0] + t[1][ 2] * w[1]; m[0][3] = t[0][ 3] * w[0] + t[1][ 3] * w[1]; m[1][0] = t[0][ 4] * w[0] + t[1][ 4] * w[1]; m[1][1] = t[0][ 5] * w[0] + t[1][ 5] * w[1]; m[1][2] = t[0][ 6] * w[0] + t[1][ 6] * w[1]; m[1][3] = t[0][ 7] * w[0] + t[1][ 7] * w[1]; m[2][0] = t[0][ 8] * w[0] + t[1][ 8] * w[1]; m[2][1] = t[0][ 9] * w[0] + t[1][ 9] * w[1]; m[2][2] = t[0][10] * w[0] + t[1][10] * w[1]; m[2][3] = t[0][11] * w[0] + t[1][11] * w[1]; } si += 4; sw += 4; // modify the vertex VectorCopy(vp, tp); vp[0] = tp[0] * m[0][0] + tp[1] * m[0][1] + tp[2] * m[0][2] + m[0][3]; vp[1] = tp[0] * m[1][0] + tp[1] * m[1][1] + tp[2] * m[1][2] + m[1][3]; vp[2] = tp[0] * m[2][0] + tp[1] * m[2][1] + tp[2] * m[2][2] + m[2][3]; vp += 3; if (vn) { // the normal transformation matrix is a set of cross products... CrossProduct(m[1], m[2], n[0]); CrossProduct(m[2], m[0], n[1]); CrossProduct(m[0], m[1], n[2]); // is actually transpose(inverse(m)) * det(m) VectorCopy(vn, tn); vn[0] = tn[0] * n[0][0] + tn[1] * n[0][1] + tn[2] * n[0][2]; vn[1] = tn[0] * n[1][0] + tn[1] * n[1][1] + tn[2] * n[1][2]; vn[2] = tn[0] * n[2][0] + tn[1] * n[2][1] + tn[2] * n[2][2]; VectorNormalize(vn); vn += 3; if (vs) { VectorCopy(vs, ts); vs[0] = ts[0] * n[0][0] + ts[1] * n[0][1] + ts[2] * n[0][2]; vs[1] = ts[0] * n[1][0] + ts[1] * n[1][1] + ts[2] * n[1][2]; vs[2] = ts[0] * n[2][0] + ts[1] * n[2][1] + ts[2] * n[2][2]; VectorNormalize(vs); vs += 3; VectorCopy(vt, tt); vt[0] = tt[0] * n[0][0] + tt[1] * n[0][1] + tt[2] * n[0][2]; vt[1] = tt[0] * n[1][0] + tt[1] * n[1][1] + tt[2] * n[1][2]; vt[2] = tt[0] * n[2][0] + tt[1] * n[2][1] + tt[2] * n[2][2]; VectorNormalize(vt); vt += 3; } } } rsurface.batchskeletaltransform3x4 = NULL; rsurface.batchskeletalnumtransforms = 0; } // q1bsp surfaces rendered in vertex color mode have to have colors // calculated based on lightstyles if ((batchneed & (BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_ARRAY_VERTEXCOLOR)) && texturesurfacelist[0]->lightmapinfo) { // generate color arrays for the surfaces in this list int c[4]; int scale; int size3; const int *offsets; const unsigned char *lm; rsurface.batchlightmapcolor4f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[4])); rsurface.batchlightmapcolor4f_vertexbuffer = NULL; rsurface.batchlightmapcolor4f_bufferoffset = 0; numvertices = 0; for (i = 0;i < texturenumsurfaces;i++) { surface = texturesurfacelist[i]; offsets = rsurface.modellightmapoffsets + surface->num_firstvertex; surfacenumvertices = surface->num_vertices; if (surface->lightmapinfo->samples) { for (j = 0;j < surfacenumvertices;j++) { lm = surface->lightmapinfo->samples + offsets[j]; scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[0]]; VectorScale(lm, scale, c); if (surface->lightmapinfo->styles[1] != 255) { size3 = ((surface->lightmapinfo->extents[0]>>4)+1)*((surface->lightmapinfo->extents[1]>>4)+1)*3; lm += size3; scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[1]]; VectorMA(c, scale, lm, c); if (surface->lightmapinfo->styles[2] != 255) { lm += size3; scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[2]]; VectorMA(c, scale, lm, c); if (surface->lightmapinfo->styles[3] != 255) { lm += size3; scale = r_refdef.scene.lightstylevalue[surface->lightmapinfo->styles[3]]; VectorMA(c, scale, lm, c); } } } c[0] >>= 7; c[1] >>= 7; c[2] >>= 7; Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, min(c[0], 255) * (1.0f / 255.0f), min(c[1], 255) * (1.0f / 255.0f), min(c[2], 255) * (1.0f / 255.0f), 1); numvertices++; } } else { for (j = 0;j < surfacenumvertices;j++) { Vector4Set(rsurface.batchlightmapcolor4f + 4*numvertices, 0, 0, 0, 1); numvertices++; } } } } // if vertices are deformed (sprite flares and things in maps, possibly // water waves, bulges and other deformations), modify the copied vertices // in place for (deformindex = 0, deform = rsurface.texture->deforms;deformindex < Q3MAXDEFORMS && deform->deform && r_deformvertexes.integer;deformindex++, deform++) { float scale; switch (deform->deform) { default: case Q3DEFORM_PROJECTIONSHADOW: case Q3DEFORM_TEXT0: case Q3DEFORM_TEXT1: case Q3DEFORM_TEXT2: case Q3DEFORM_TEXT3: case Q3DEFORM_TEXT4: case Q3DEFORM_TEXT5: case Q3DEFORM_TEXT6: case Q3DEFORM_TEXT7: case Q3DEFORM_NONE: break; case Q3DEFORM_AUTOSPRITE: Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); VectorNormalize(newforward); VectorNormalize(newright); VectorNormalize(newup); // rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); // rsurface.batchvertex3f_vertexbuffer = NULL; // rsurface.batchvertex3f_bufferoffset = 0; // rsurface.batchsvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f); // rsurface.batchsvector3f_vertexbuffer = NULL; // rsurface.batchsvector3f_bufferoffset = 0; // rsurface.batchtvector3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f); // rsurface.batchtvector3f_vertexbuffer = NULL; // rsurface.batchtvector3f_bufferoffset = 0; // rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); // rsurface.batchnormal3f_vertexbuffer = NULL; // rsurface.batchnormal3f_bufferoffset = 0; // sometimes we're on a renderpath that does not use vectors (GL11/GL13/GLES1) if (!VectorLength2(rsurface.batchnormal3f + 3*rsurface.batchfirstvertex)) Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); if (!VectorLength2(rsurface.batchsvector3f + 3*rsurface.batchfirstvertex)) Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); // a single autosprite surface can contain multiple sprites... for (j = 0;j < batchnumvertices - 3;j += 4) { VectorClear(center); for (i = 0;i < 4;i++) VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); VectorScale(center, 0.25f, center); VectorCopy(rsurface.batchnormal3f + 3*j, forward); VectorCopy(rsurface.batchsvector3f + 3*j, right); VectorCopy(rsurface.batchtvector3f + 3*j, up); for (i = 0;i < 4;i++) { VectorSubtract(rsurface.batchvertex3f + 3*(j+i), center, v); VectorMAMAMAM(1, center, DotProduct(forward, v), newforward, DotProduct(right, v), newright, DotProduct(up, v), newup, rsurface.batchvertex3f + 3*(j+i)); } } // if we get here, BATCHNEED_ARRAY_NORMAL and BATCHNEED_ARRAY_VECTOR are in batchneed, so no need to check Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); break; case Q3DEFORM_AUTOSPRITE2: Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, newforward); Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.right, newright); Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.up, newup); VectorNormalize(newforward); VectorNormalize(newright); VectorNormalize(newup); // rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); // rsurface.batchvertex3f_vertexbuffer = NULL; // rsurface.batchvertex3f_bufferoffset = 0; { const float *v1, *v2; vec3_t start, end; float f, l; struct { float length2; const float *v1; const float *v2; } shortest[2]; memset(shortest, 0, sizeof(shortest)); // a single autosprite surface can contain multiple sprites... for (j = 0;j < batchnumvertices - 3;j += 4) { VectorClear(center); for (i = 0;i < 4;i++) VectorAdd(center, rsurface.batchvertex3f + 3*(j+i), center); VectorScale(center, 0.25f, center); // find the two shortest edges, then use them to define the // axis vectors for rotating around the central axis for (i = 0;i < 6;i++) { v1 = rsurface.batchvertex3f + 3*(j+quadedges[i][0]); v2 = rsurface.batchvertex3f + 3*(j+quadedges[i][1]); l = VectorDistance2(v1, v2); // this length bias tries to make sense of square polygons, assuming they are meant to be upright if (v1[2] != v2[2]) l += (1.0f / 1024.0f); if (shortest[0].length2 > l || i == 0) { shortest[1] = shortest[0]; shortest[0].length2 = l; shortest[0].v1 = v1; shortest[0].v2 = v2; } else if (shortest[1].length2 > l || i == 1) { shortest[1].length2 = l; shortest[1].v1 = v1; shortest[1].v2 = v2; } } VectorLerp(shortest[0].v1, 0.5f, shortest[0].v2, start); VectorLerp(shortest[1].v1, 0.5f, shortest[1].v2, end); // this calculates the right vector from the shortest edge // and the up vector from the edge midpoints VectorSubtract(shortest[0].v1, shortest[0].v2, right); VectorNormalize(right); VectorSubtract(end, start, up); VectorNormalize(up); // calculate a forward vector to use instead of the original plane normal (this is how we get a new right vector) VectorSubtract(rsurface.localvieworigin, center, forward); //Matrix4x4_Transform3x3(&rsurface.inversematrix, r_refdef.view.forward, forward); VectorNegate(forward, forward); VectorReflect(forward, 0, up, forward); VectorNormalize(forward); CrossProduct(up, forward, newright); VectorNormalize(newright); // rotate the quad around the up axis vector, this is made // especially easy by the fact we know the quad is flat, // so we only have to subtract the center position and // measure distance along the right vector, and then // multiply that by the newright vector and add back the // center position // we also need to subtract the old position to undo the // displacement from the center, which we do with a // DotProduct, the subtraction/addition of center is also // optimized into DotProducts here l = DotProduct(right, center); for (i = 0;i < 4;i++) { v1 = rsurface.batchvertex3f + 3*(j+i); f = DotProduct(right, v1) - l; VectorMAMAM(1, v1, -f, right, f, newright, rsurface.batchvertex3f + 3*(j+i)); } } } if(batchneed & (BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR)) // otherwise these can stay NULL { // rsurface.batchnormal3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchnormal3f_vertexbuffer = NULL; // rsurface.batchnormal3f_bufferoffset = 0; Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); } if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL { // rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchsvector3f_vertexbuffer = NULL; // rsurface.batchsvector3f_bufferoffset = 0; // rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchtvector3f_vertexbuffer = NULL; // rsurface.batchtvector3f_bufferoffset = 0; Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); } break; case Q3DEFORM_NORMAL: // deform the normals to make reflections wavey rsurface.batchnormal3f = (float *)R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); rsurface.batchnormal3f_vertexbuffer = NULL; rsurface.batchnormal3f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { float vertex[3]; float *normal = rsurface.batchnormal3f + 3*j; VectorScale(rsurface.batchvertex3f + 3*j, 0.98f, vertex); normal[0] = rsurface.batchnormal3f[j*3+0] + deform->parms[0] * noise4f( vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); normal[1] = rsurface.batchnormal3f[j*3+1] + deform->parms[0] * noise4f( 98 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); normal[2] = rsurface.batchnormal3f[j*3+2] + deform->parms[0] * noise4f(196 + vertex[0], vertex[1], vertex[2], rsurface.shadertime * deform->parms[1]); VectorNormalize(normal); } if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL { // rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchsvector3f_vertexbuffer = NULL; // rsurface.batchsvector3f_bufferoffset = 0; // rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchtvector3f_vertexbuffer = NULL; // rsurface.batchtvector3f_bufferoffset = 0; Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); } break; case Q3DEFORM_WAVE: // deform vertex array to make wavey water and flags and such waveparms[0] = deform->waveparms[0]; waveparms[1] = deform->waveparms[1]; waveparms[2] = deform->waveparms[2]; waveparms[3] = deform->waveparms[3]; if(!R_TestQ3WaveFunc(deform->wavefunc, waveparms)) break; // if wavefunc is a nop, don't make a dynamic vertex array // this is how a divisor of vertex influence on deformation animpos = deform->parms[0] ? 1.0f / deform->parms[0] : 100.0f; scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); // rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); // rsurface.batchvertex3f_vertexbuffer = NULL; // rsurface.batchvertex3f_bufferoffset = 0; // rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); // rsurface.batchnormal3f_vertexbuffer = NULL; // rsurface.batchnormal3f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { // if the wavefunc depends on time, evaluate it per-vertex if (waveparms[3]) { waveparms[2] = deform->waveparms[2] + (rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+1] + rsurface.batchvertex3f[j*3+2]) * animpos; scale = R_EvaluateQ3WaveFunc(deform->wavefunc, waveparms); } VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); } // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL { // rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchsvector3f_vertexbuffer = NULL; // rsurface.batchsvector3f_bufferoffset = 0; // rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchtvector3f_vertexbuffer = NULL; // rsurface.batchtvector3f_bufferoffset = 0; Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); } break; case Q3DEFORM_BULGE: // deform vertex array to make the surface have moving bulges // rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); // rsurface.batchvertex3f_vertexbuffer = NULL; // rsurface.batchvertex3f_bufferoffset = 0; // rsurface.batchnormal3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f); // rsurface.batchnormal3f_vertexbuffer = NULL; // rsurface.batchnormal3f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { scale = sin(rsurface.batchtexcoordtexture2f[j*2+0] * deform->parms[0] + rsurface.shadertime * deform->parms[2]) * deform->parms[1]; VectorMA(rsurface.batchvertex3f + 3*j, scale, rsurface.batchnormal3f + 3*j, rsurface.batchvertex3f + 3*j); } // if we get here, BATCHNEED_ARRAY_NORMAL is in batchneed, so no need to check Mod_BuildNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchnormal3f, r_smoothnormals_areaweighting.integer != 0); if(batchneed & BATCHNEED_ARRAY_VECTOR) // otherwise these can stay NULL { // rsurface.batchsvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchsvector3f_vertexbuffer = NULL; // rsurface.batchsvector3f_bufferoffset = 0; // rsurface.batchtvector3f = R_FrameData_Alloc(batchnumvertices * sizeof(float[3])); // rsurface.batchtvector3f_vertexbuffer = NULL; // rsurface.batchtvector3f_bufferoffset = 0; Mod_BuildTextureVectorsFromNormals(rsurface.batchfirstvertex, batchnumvertices, batchnumtriangles, rsurface.batchvertex3f, rsurface.batchtexcoordtexture2f, rsurface.batchnormal3f, rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle, rsurface.batchsvector3f, rsurface.batchtvector3f, r_smoothnormals_areaweighting.integer != 0); } break; case Q3DEFORM_MOVE: // deform vertex array if(!R_TestQ3WaveFunc(deform->wavefunc, deform->waveparms)) break; // if wavefunc is a nop, don't make a dynamic vertex array scale = R_EvaluateQ3WaveFunc(deform->wavefunc, deform->waveparms); VectorScale(deform->parms, scale, waveparms); // rsurface.batchvertex3f = R_FrameData_Store(batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f); // rsurface.batchvertex3f_vertexbuffer = NULL; // rsurface.batchvertex3f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) VectorAdd(rsurface.batchvertex3f + 3*j, waveparms, rsurface.batchvertex3f + 3*j); break; } } if (rsurface.batchtexcoordtexture2f) { // generate texcoords based on the chosen texcoord source switch(rsurface.texture->tcgen.tcgen) { default: case Q3TCGEN_TEXTURE: break; case Q3TCGEN_LIGHTMAP: // rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); // rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; // rsurface.batchtexcoordtexture2f_bufferoffset = 0; if (rsurface.batchtexcoordlightmap2f) memcpy(rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordlightmap2f, batchnumvertices * sizeof(float[2])); break; case Q3TCGEN_VECTOR: // rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); // rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; // rsurface.batchtexcoordtexture2f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { rsurface.batchtexcoordtexture2f[j*2+0] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms); rsurface.batchtexcoordtexture2f[j*2+1] = DotProduct(rsurface.batchvertex3f + 3*j, rsurface.texture->tcgen.parms + 3); } break; case Q3TCGEN_ENVIRONMENT: // make environment reflections using a spheremap rsurface.batchtexcoordtexture2f = (float *)R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; rsurface.batchtexcoordtexture2f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { // identical to Q3A's method, but executed in worldspace so // carried models can be shiny too float viewer[3], d, reflected[3], worldreflected[3]; VectorSubtract(rsurface.localvieworigin, rsurface.batchvertex3f + 3*j, viewer); // VectorNormalize(viewer); d = DotProduct(rsurface.batchnormal3f + 3*j, viewer); reflected[0] = rsurface.batchnormal3f[j*3+0]*2*d - viewer[0]; reflected[1] = rsurface.batchnormal3f[j*3+1]*2*d - viewer[1]; reflected[2] = rsurface.batchnormal3f[j*3+2]*2*d - viewer[2]; // note: this is proportinal to viewer, so we can normalize later Matrix4x4_Transform3x3(&rsurface.matrix, reflected, worldreflected); VectorNormalize(worldreflected); // note: this sphere map only uses world x and z! // so positive and negative y will LOOK THE SAME. rsurface.batchtexcoordtexture2f[j*2+0] = 0.5 + 0.5 * worldreflected[1]; rsurface.batchtexcoordtexture2f[j*2+1] = 0.5 - 0.5 * worldreflected[2]; } break; } // the only tcmod that needs software vertex processing is turbulent, so // check for it here and apply the changes if needed // and we only support that as the first one // (handling a mixture of turbulent and other tcmods would be problematic // without punting it entirely to a software path) if (rsurface.texture->tcmods[0].tcmod == Q3TCMOD_TURBULENT) { amplitude = rsurface.texture->tcmods[0].parms[1]; animpos = rsurface.texture->tcmods[0].parms[2] + rsurface.shadertime * rsurface.texture->tcmods[0].parms[3]; // rsurface.batchtexcoordtexture2f = R_FrameData_Alloc(batchnumvertices * sizeof(float[2])); // rsurface.batchtexcoordtexture2f_vertexbuffer = NULL; // rsurface.batchtexcoordtexture2f_bufferoffset = 0; for (j = 0;j < batchnumvertices;j++) { rsurface.batchtexcoordtexture2f[j*2+0] += amplitude * sin(((rsurface.batchvertex3f[j*3+0] + rsurface.batchvertex3f[j*3+2]) * 1.0 / 1024.0f + animpos) * M_PI * 2); rsurface.batchtexcoordtexture2f[j*2+1] += amplitude * sin(((rsurface.batchvertex3f[j*3+1] ) * 1.0 / 1024.0f + animpos) * M_PI * 2); } } } if (needsupdate & batchneed & (BATCHNEED_VERTEXMESH_VERTEX | BATCHNEED_VERTEXMESH_NORMAL | BATCHNEED_VERTEXMESH_VECTOR | BATCHNEED_VERTEXMESH_VERTEXCOLOR | BATCHNEED_VERTEXMESH_TEXCOORD | BATCHNEED_VERTEXMESH_LIGHTMAP)) { // convert the modified arrays to vertex structs // rsurface.batchvertexmesh = R_FrameData_Alloc(batchnumvertices * sizeof(r_vertexmesh_t)); // rsurface.batchvertexmesh_vertexbuffer = NULL; // rsurface.batchvertexmesh_bufferoffset = 0; if (batchneed & BATCHNEED_VERTEXMESH_VERTEX) for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) VectorCopy(rsurface.batchvertex3f + 3*j, vertexmesh->vertex3f); if (batchneed & BATCHNEED_VERTEXMESH_NORMAL) for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) VectorCopy(rsurface.batchnormal3f + 3*j, vertexmesh->normal3f); if (batchneed & BATCHNEED_VERTEXMESH_VECTOR) { for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) { VectorCopy(rsurface.batchsvector3f + 3*j, vertexmesh->svector3f); VectorCopy(rsurface.batchtvector3f + 3*j, vertexmesh->tvector3f); } } if ((batchneed & BATCHNEED_VERTEXMESH_VERTEXCOLOR) && rsurface.batchlightmapcolor4f) for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) Vector4Copy(rsurface.batchlightmapcolor4f + 4*j, vertexmesh->color4f); if (batchneed & BATCHNEED_VERTEXMESH_TEXCOORD) for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) Vector2Copy(rsurface.batchtexcoordtexture2f + 2*j, vertexmesh->texcoordtexture2f); if ((batchneed & BATCHNEED_VERTEXMESH_LIGHTMAP) && rsurface.batchtexcoordlightmap2f) for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) Vector2Copy(rsurface.batchtexcoordlightmap2f + 2*j, vertexmesh->texcoordlightmap2f); if ((batchneed & BATCHNEED_VERTEXMESH_SKELETAL) && rsurface.batchskeletalindex4ub) { for (j = 0, vertexmesh = rsurface.batchvertexmesh;j < batchnumvertices;j++, vertexmesh++) { Vector4Copy(rsurface.batchskeletalindex4ub + 4*j, vertexmesh->skeletalindex4ub); Vector4Copy(rsurface.batchskeletalweight4ub + 4*j, vertexmesh->skeletalweight4ub); } } } // upload buffer data for the dynamic batch if (((r_batch_dynamicbuffer.integer || gl_vbo_dynamicvertex.integer || gl_vbo_dynamicindex.integer) && vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo) { if (rsurface.batchvertexmesh) rsurface.batchvertexmesh_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(r_vertexmesh_t), rsurface.batchvertexmesh, R_BUFFERDATA_VERTEX, &rsurface.batchvertexmesh_bufferoffset); else { if (rsurface.batchvertex3f) rsurface.batchvertex3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchvertex3f, R_BUFFERDATA_VERTEX, &rsurface.batchvertex3f_bufferoffset); if (rsurface.batchsvector3f) rsurface.batchsvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchsvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchsvector3f_bufferoffset); if (rsurface.batchtvector3f) rsurface.batchtvector3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchtvector3f, R_BUFFERDATA_VERTEX, &rsurface.batchtvector3f_bufferoffset); if (rsurface.batchnormal3f) rsurface.batchnormal3f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[3]), rsurface.batchnormal3f, R_BUFFERDATA_VERTEX, &rsurface.batchnormal3f_bufferoffset); if (rsurface.batchlightmapcolor4f) rsurface.batchlightmapcolor4f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[4]), rsurface.batchlightmapcolor4f, R_BUFFERDATA_VERTEX, &rsurface.batchlightmapcolor4f_bufferoffset); if (rsurface.batchtexcoordtexture2f) rsurface.batchtexcoordtexture2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordtexture2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordtexture2f_bufferoffset); if (rsurface.batchtexcoordlightmap2f) rsurface.batchtexcoordlightmap2f_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(float[2]), rsurface.batchtexcoordlightmap2f, R_BUFFERDATA_VERTEX, &rsurface.batchtexcoordlightmap2f_bufferoffset); if (rsurface.batchskeletalindex4ub) rsurface.batchskeletalindex4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalindex4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalindex4ub_bufferoffset); if (rsurface.batchskeletalweight4ub) rsurface.batchskeletalweight4ub_vertexbuffer = R_BufferData_Store(rsurface.batchnumvertices * sizeof(unsigned char[4]), rsurface.batchskeletalweight4ub, R_BUFFERDATA_VERTEX, &rsurface.batchskeletalweight4ub_bufferoffset); } if (rsurface.batchelement3s) rsurface.batchelement3s_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(short[3]), rsurface.batchelement3s, R_BUFFERDATA_INDEX16, &rsurface.batchelement3s_bufferoffset); else if (rsurface.batchelement3i) rsurface.batchelement3i_indexbuffer = R_BufferData_Store(rsurface.batchnumtriangles * sizeof(int[3]), rsurface.batchelement3i, R_BUFFERDATA_INDEX32, &rsurface.batchelement3i_bufferoffset); } } void RSurf_DrawBatch(void) { // sometimes a zero triangle surface (usually a degenerate patch) makes it // through the pipeline, killing it earlier in the pipeline would have // per-surface overhead rather than per-batch overhead, so it's best to // reject it here, before it hits glDraw. if (rsurface.batchnumtriangles == 0) return; #if 0 // batch debugging code if (r_test.integer && rsurface.entity == r_refdef.scene.worldentity && rsurface.batchvertex3f == r_refdef.scene.worldentity->model->surfmesh.data_vertex3f) { int i; int j; int c; const int *e; e = rsurface.batchelement3i + rsurface.batchfirsttriangle*3; for (i = 0;i < rsurface.batchnumtriangles*3;i++) { c = e[i]; for (j = 0;j < rsurface.entity->model->num_surfaces;j++) { if (c >= rsurface.modelsurfaces[j].num_firstvertex && c < (rsurface.modelsurfaces[j].num_firstvertex + rsurface.modelsurfaces[j].num_vertices)) { if (rsurface.modelsurfaces[j].texture != rsurface.texture) Sys_Error("RSurf_DrawBatch: index %i uses different texture (%s) than surface %i which it belongs to (which uses %s)\n", c, rsurface.texture->name, j, rsurface.modelsurfaces[j].texture->name); break; } } } } #endif if (rsurface.batchmultidraw) { // issue multiple draws rather than copying index data int numsurfaces = rsurface.batchmultidrawnumsurfaces; const msurface_t **surfacelist = rsurface.batchmultidrawsurfacelist; int i, j, k, firstvertex, endvertex, firsttriangle, endtriangle; for (i = 0;i < numsurfaces;) { // combine consecutive surfaces as one draw for (k = i, j = i + 1;j < numsurfaces;k = j, j++) if (surfacelist[j] != surfacelist[k] + 1) break; firstvertex = surfacelist[i]->num_firstvertex; endvertex = surfacelist[k]->num_firstvertex + surfacelist[k]->num_vertices; firsttriangle = surfacelist[i]->num_firsttriangle; endtriangle = surfacelist[k]->num_firsttriangle + surfacelist[k]->num_triangles; R_Mesh_Draw(firstvertex, endvertex - firstvertex, firsttriangle, endtriangle - firsttriangle, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset); i = j; } } else { // there is only one consecutive run of index data (may have been combined) R_Mesh_Draw(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchfirsttriangle, rsurface.batchnumtriangles, rsurface.batchelement3i, rsurface.batchelement3i_indexbuffer, rsurface.batchelement3i_bufferoffset, rsurface.batchelement3s, rsurface.batchelement3s_indexbuffer, rsurface.batchelement3s_bufferoffset); } } static int RSurf_FindWaterPlaneForSurface(const msurface_t *surface) { // pick the closest matching water plane int planeindex, vertexindex, bestplaneindex = -1; float d, bestd; vec3_t vert; const float *v; r_waterstate_waterplane_t *p; qboolean prepared = false; bestd = 0; for (planeindex = 0, p = r_fb.water.waterplanes;planeindex < r_fb.water.numwaterplanes;planeindex++, p++) { if(p->camera_entity != rsurface.texture->camera_entity) continue; d = 0; if(!prepared) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX, 1, &surface); prepared = true; if(rsurface.batchnumvertices == 0) break; } for (vertexindex = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3;vertexindex < rsurface.batchnumvertices;vertexindex++, v += 3) { Matrix4x4_Transform(&rsurface.matrix, v, vert); d += fabs(PlaneDiff(vert, &p->plane)); } if (bestd > d || bestplaneindex < 0) { bestd = d; bestplaneindex = planeindex; } } return bestplaneindex; // NOTE: this MAY return a totally unrelated water plane; we can ignore // this situation though, as it might be better to render single larger // batches with useless stuff (backface culled for example) than to // render multiple smaller batches } static void RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(void) { int i; rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0;i < rsurface.batchnumvertices;i++) Vector4Set(rsurface.passcolor4f + 4*i, 0.5f, 0.5f, 0.5f, 1.0f); } static void RSurf_DrawBatch_GL11_ApplyFog(void) { int i; float f; const float *v; const float *c; float *c2; if (rsurface.passcolor4f) { // generate color arrays c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) { f = RSurf_FogVertex(v); c2[0] = c[0] * f; c2[1] = c[1] * f; c2[2] = c[2] * f; c2[3] = c[3]; } } else { rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c2 += 4) { f = RSurf_FogVertex(v); c2[0] = f; c2[1] = f; c2[2] = f; c2[3] = 1; } } } static void RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(void) { int i; float f; const float *v; const float *c; float *c2; if (!rsurface.passcolor4f) return; c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4, c2 += 4) { f = RSurf_FogVertex(v); c2[0] = c[0] * f + r_refdef.fogcolor[0] * (1 - f); c2[1] = c[1] * f + r_refdef.fogcolor[1] * (1 - f); c2[2] = c[2] * f + r_refdef.fogcolor[2] * (1 - f); c2[3] = c[3]; } } static void RSurf_DrawBatch_GL11_ApplyColor(float r, float g, float b, float a) { int i; const float *c; float *c2; if (!rsurface.passcolor4f) return; c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) { c2[0] = c[0] * r; c2[1] = c[1] * g; c2[2] = c[2] * b; c2[3] = c[3] * a; } } static void RSurf_DrawBatch_GL11_ApplyAmbient(void) { int i; const float *c; float *c2; if (!rsurface.passcolor4f) return; c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4; rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, c2 = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, c += 4, c2 += 4) { c2[0] = c[0] + r_refdef.scene.ambient; c2[1] = c[1] + r_refdef.scene.ambient; c2[2] = c[2] + r_refdef.scene.ambient; c2[3] = c[3]; } } static void RSurf_DrawBatch_GL11_Lightmap(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) { // TODO: optimize rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); GL_Color(r, g, b, a); R_Mesh_TexBind(0, rsurface.lightmaptexture); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexMatrix(0, NULL); RSurf_DrawBatch(); } static void RSurf_DrawBatch_GL11_Unlit(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) { // TODO: optimize applyfog && applycolor case // just apply fog if necessary, and tint the fog color array if necessary rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); GL_Color(r, g, b, a); RSurf_DrawBatch(); } static void RSurf_DrawBatch_GL11_VertexColor(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) { // TODO: optimize rsurface.passcolor4f = rsurface.batchlightmapcolor4f; rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); GL_Color(r, g, b, a); RSurf_DrawBatch(); } static void RSurf_DrawBatch_GL11_ClampColor(void) { int i; const float *c1; float *c2; if (!rsurface.passcolor4f) return; for (i = 0, c1 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex, c2 = rsurface.passcolor4f + 4*rsurface.batchfirstvertex;i < rsurface.batchnumvertices;i++, c1 += 4, c2 += 4) { c2[0] = bound(0.0f, c1[0], 1.0f); c2[1] = bound(0.0f, c1[1], 1.0f); c2[2] = bound(0.0f, c1[2], 1.0f); c2[3] = bound(0.0f, c1[3], 1.0f); } } static void RSurf_DrawBatch_GL11_ApplyFakeLight(void) { int i; float f; const float *v; const float *n; float *c; //vec3_t eyedir; // fake shading rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) { f = -DotProduct(r_refdef.view.forward, n); f = max(0, f); f = f * 0.85 + 0.15; // work around so stuff won't get black f *= r_refdef.lightmapintensity; Vector4Set(c, f, f, f, 1); } } static void RSurf_DrawBatch_GL11_FakeLight(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) { RSurf_DrawBatch_GL11_ApplyFakeLight(); if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); GL_Color(r, g, b, a); RSurf_DrawBatch(); } static void RSurf_DrawBatch_GL11_ApplyVertexShade(float *r, float *g, float *b, float *a, qboolean *applycolor) { int i; float f; float alpha; const float *v; const float *n; float *c; vec3_t ambientcolor; vec3_t diffusecolor; vec3_t lightdir; // TODO: optimize // model lighting VectorCopy(rsurface.modellight_lightdir, lightdir); f = 0.5f * r_refdef.lightmapintensity; ambientcolor[0] = rsurface.modellight_ambient[0] * *r * f; ambientcolor[1] = rsurface.modellight_ambient[1] * *g * f; ambientcolor[2] = rsurface.modellight_ambient[2] * *b * f; diffusecolor[0] = rsurface.modellight_diffuse[0] * *r * f; diffusecolor[1] = rsurface.modellight_diffuse[1] * *g * f; diffusecolor[2] = rsurface.modellight_diffuse[2] * *b * f; alpha = *a; if (VectorLength2(diffusecolor) > 0) { // q3-style directional shading rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, n = rsurface.batchnormal3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, n += 3, c += 4) { if ((f = DotProduct(n, lightdir)) > 0) VectorMA(ambientcolor, f, diffusecolor, c); else VectorCopy(ambientcolor, c); c[3] = alpha; } *r = 1; *g = 1; *b = 1; *a = 1; *applycolor = false; } else { *r = ambientcolor[0]; *g = ambientcolor[1]; *b = ambientcolor[2]; rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; } } static void RSurf_DrawBatch_GL11_VertexShade(float r, float g, float b, float a, qboolean applycolor, qboolean applyfog) { RSurf_DrawBatch_GL11_ApplyVertexShade(&r, &g, &b, &a, &applycolor); if (applyfog) RSurf_DrawBatch_GL11_ApplyFog(); if (applycolor) RSurf_DrawBatch_GL11_ApplyColor(r, g, b, a); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, rsurface.passcolor4f_vertexbuffer, rsurface.passcolor4f_bufferoffset); GL_Color(r, g, b, a); RSurf_DrawBatch(); } static void RSurf_DrawBatch_GL11_MakeFogColor(float r, float g, float b, float a) { int i; float f; const float *v; float *c; // fake shading rsurface.passcolor4f = (float *)R_FrameData_Alloc(rsurface.batchnumvertices * sizeof(float[4])); rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; for (i = 0, v = rsurface.batchvertex3f + rsurface.batchfirstvertex * 3, c = rsurface.passcolor4f + rsurface.batchfirstvertex * 4;i < rsurface.batchnumvertices;i++, v += 3, c += 4) { f = 1 - RSurf_FogVertex(v); c[0] = r; c[1] = g; c[2] = b; c[3] = f * a; } } void RSurf_SetupDepthAndCulling(void) { // submodels are biased to avoid z-fighting with world surfaces that they // may be exactly overlapping (avoids z-fighting artifacts on certain // doors and things in Quake maps) GL_DepthRange(0, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SHORTDEPTHRANGE) ? 0.0625 : 1); GL_PolygonOffset(rsurface.basepolygonfactor + rsurface.texture->biaspolygonfactor, rsurface.basepolygonoffset + rsurface.texture->biaspolygonoffset); GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST)); GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); } static void R_DrawTextureSurfaceList_Sky(int texturenumsurfaces, const msurface_t **texturesurfacelist) { // transparent sky would be ridiculous if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) return; R_SetupShader_Generic_NoTexture(false, false); skyrenderlater = true; RSurf_SetupDepthAndCulling(); GL_DepthMask(true); // LordHavoc: HalfLife maps have freaky skypolys so don't use // skymasking on them, and Quake3 never did sky masking (unlike // software Quake and software Quake2), so disable the sky masking // in Quake3 maps as it causes problems with q3map2 sky tricks, // and skymasking also looks very bad when noclipping outside the // level, so don't use it then either. if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.skymasking && r_q1bsp_skymasking.integer && !r_refdef.viewcache.world_novis && !r_trippy.integer) { R_Mesh_ResetTextureState(); if (skyrendermasked) { R_SetupShader_DepthOrShadow(false, false, false); // depth-only (masking) GL_ColorMask(0,0,0,0); // just to make sure that braindead drivers don't draw // anything despite that colormask... GL_BlendFunc(GL_ZERO, GL_ONE); RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); } else { R_SetupShader_Generic_NoTexture(false, false); // fog sky GL_BlendFunc(GL_ONE, GL_ZERO); RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); GL_Color(r_refdef.fogcolor[0], r_refdef.fogcolor[1], r_refdef.fogcolor[2], 1); R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); } RSurf_DrawBatch(); if (skyrendermasked) GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); } R_Mesh_ResetTextureState(); GL_Color(1, 1, 1, 1); } extern rtexture_t *r_shadow_prepasslightingdiffusetexture; extern rtexture_t *r_shadow_prepasslightingspeculartexture; static void R_DrawTextureSurfaceList_GL20(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) { if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA))) return; if (prepass) { // render screenspace normalmap to texture GL_DepthMask(true); R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_DEFERREDGEOMETRY, texturenumsurfaces, texturesurfacelist, NULL, false); RSurf_DrawBatch(); return; } // bind lightmap texture // water/refraction/reflection/camera surfaces have to be handled specially if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA | MATERIALFLAG_REFLECTION))) { int start, end, startplaneindex; for (start = 0;start < texturenumsurfaces;start = end) { startplaneindex = RSurf_FindWaterPlaneForSurface(texturesurfacelist[start]); if(startplaneindex < 0) { // this happens if the plane e.g. got backface culled and thus didn't get a water plane. We can just ignore this. // Con_Printf("No matching water plane for surface with material flags 0x%08x - PLEASE DEBUG THIS\n", rsurface.texture->currentmaterialflags); end = start + 1; continue; } for (end = start + 1;end < texturenumsurfaces && startplaneindex == RSurf_FindWaterPlaneForSurface(texturesurfacelist[end]);end++) ; // now that we have a batch using the same planeindex, render it if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_CAMERA))) { // render water or distortion background GL_DepthMask(true); R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BACKGROUND, end-start, texturesurfacelist + start, (void *)(r_fb.water.waterplanes + startplaneindex), false); RSurf_DrawBatch(); // blend surface on top GL_DepthMask(false); R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, NULL, false); RSurf_DrawBatch(); } else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_REFLECTION)) { // render surface with reflection texture as input GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, end-start, texturesurfacelist + start, (void *)(r_fb.water.waterplanes + startplaneindex), false); RSurf_DrawBatch(); } } return; } // render surface batch normally GL_DepthMask(writedepth && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED)); R_SetupShader_Surface(vec3_origin, (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) != 0, 1, 1, rsurface.texture->specularscale, RSURFPASS_BASE, texturenumsurfaces, texturesurfacelist, NULL, (rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) != 0); RSurf_DrawBatch(); } static void R_DrawTextureSurfaceList_GL13(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) { // OpenGL 1.3 path - anything not completely ancient qboolean applycolor; qboolean applyfog; int layerindex; const texturelayer_t *layer; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) { vec4_t layercolor; int layertexrgbscale; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) { if (layerindex == 0) GL_AlphaTest(true); else { GL_AlphaTest(false); GL_DepthFunc(GL_EQUAL); } } GL_DepthMask(layer->depthmask && writedepth); GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); if (layer->color[0] > 2 || layer->color[1] > 2 || layer->color[2] > 2) { layertexrgbscale = 4; VectorScale(layer->color, 0.25f, layercolor); } else if (layer->color[0] > 1 || layer->color[1] > 1 || layer->color[2] > 1) { layertexrgbscale = 2; VectorScale(layer->color, 0.5f, layercolor); } else { layertexrgbscale = 1; VectorScale(layer->color, 1.0f, layercolor); } layercolor[3] = layer->color[3]; applycolor = layercolor[0] != 1 || layercolor[1] != 1 || layercolor[2] != 1 || layercolor[3] != 1; R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); switch (layer->type) { case TEXTURELAYERTYPE_LITTEXTURE: // single-pass lightmapped texture with 2x rgbscale R_Mesh_TexBind(0, r_texture_white); R_Mesh_TexMatrix(0, NULL); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); R_Mesh_TexBind(1, layer->texture); R_Mesh_TexMatrix(1, &layer->texmatrix); R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) RSurf_DrawBatch_GL11_VertexShade(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); else if (FAKELIGHT_ENABLED) RSurf_DrawBatch_GL11_FakeLight(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); else if (rsurface.uselightmaptexture) RSurf_DrawBatch_GL11_Lightmap(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); else RSurf_DrawBatch_GL11_VertexColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); break; case TEXTURELAYERTYPE_TEXTURE: // singletexture unlit texture with transparency support R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); R_Mesh_TexBind(1, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); RSurf_DrawBatch_GL11_Unlit(layercolor[0], layercolor[1], layercolor[2], layercolor[3], applycolor, applyfog); break; case TEXTURELAYERTYPE_FOG: // singletexture fogging if (layer->texture) { R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, layertexrgbscale, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); } else { R_Mesh_TexBind(0, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); } R_Mesh_TexBind(1, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); // generate a color array for the fog pass RSurf_DrawBatch_GL11_MakeFogColor(layercolor[0], layercolor[1], layercolor[2], layercolor[3]); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); RSurf_DrawBatch(); break; default: Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) { GL_DepthFunc(GL_LEQUAL); GL_AlphaTest(false); } } static void R_DrawTextureSurfaceList_GL11(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) { // OpenGL 1.1 - crusty old voodoo path qboolean applyfog; int layerindex; const texturelayer_t *layer; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | ((!rsurface.uselightmaptexture && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT)) ? BATCHNEED_ARRAY_VERTEXCOLOR : 0) | BATCHNEED_ARRAY_TEXCOORD | (rsurface.modeltexcoordlightmap2f ? BATCHNEED_ARRAY_LIGHTMAP : 0) | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); for (layerindex = 0, layer = rsurface.texture->currentlayers;layerindex < rsurface.texture->currentnumlayers;layerindex++, layer++) { if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) { if (layerindex == 0) GL_AlphaTest(true); else { GL_AlphaTest(false); GL_DepthFunc(GL_EQUAL); } } GL_DepthMask(layer->depthmask && writedepth); GL_BlendFunc(layer->blendfunc1, layer->blendfunc2); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, 0, 0); applyfog = r_refdef.fogenabled && (rsurface.texture->currentmaterialflags & MATERIALFLAG_BLENDED); switch (layer->type) { case TEXTURELAYERTYPE_LITTEXTURE: if (layer->blendfunc1 == GL_ONE && layer->blendfunc2 == GL_ZERO && !(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)) { // two-pass lit texture with 2x rgbscale // first the lightmap pass R_Mesh_TexBind(0, r_texture_white); R_Mesh_TexMatrix(0, NULL); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordlightmap2f, rsurface.batchtexcoordlightmap2f_vertexbuffer, rsurface.batchtexcoordlightmap2f_bufferoffset); if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) RSurf_DrawBatch_GL11_VertexShade(1, 1, 1, 1, false, false); else if (FAKELIGHT_ENABLED) RSurf_DrawBatch_GL11_FakeLight(1, 1, 1, 1, false, false); else if (rsurface.uselightmaptexture) RSurf_DrawBatch_GL11_Lightmap(1, 1, 1, 1, false, false); else RSurf_DrawBatch_GL11_VertexColor(1, 1, 1, 1, false, false); // then apply the texture to it GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); RSurf_DrawBatch_GL11_Unlit(layer->color[0] * 0.5f, layer->color[1] * 0.5f, layer->color[2] * 0.5f, layer->color[3], layer->color[0] != 2 || layer->color[1] != 2 || layer->color[2] != 2 || layer->color[3] != 1, false); } else { // single pass vertex-lighting-only texture with 1x rgbscale and transparency support R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) RSurf_DrawBatch_GL11_VertexShade(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); else if (FAKELIGHT_ENABLED) RSurf_DrawBatch_GL11_FakeLight(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); else RSurf_DrawBatch_GL11_VertexColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); } break; case TEXTURELAYERTYPE_TEXTURE: // singletexture unlit texture with transparency support R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); RSurf_DrawBatch_GL11_Unlit(layer->color[0], layer->color[1], layer->color[2], layer->color[3], layer->color[0] != 1 || layer->color[1] != 1 || layer->color[2] != 1 || layer->color[3] != 1, applyfog); break; case TEXTURELAYERTYPE_FOG: // singletexture fogging if (layer->texture) { R_Mesh_TexBind(0, layer->texture); R_Mesh_TexMatrix(0, &layer->texmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); } else { R_Mesh_TexBind(0, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, 0, 0); } // generate a color array for the fog pass RSurf_DrawBatch_GL11_MakeFogColor(layer->color[0], layer->color[1], layer->color[2], layer->color[3]); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); RSurf_DrawBatch(); break; default: Con_Printf("R_DrawTextureSurfaceList: unknown layer type %i\n", layer->type); } } if (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) { GL_DepthFunc(GL_LEQUAL); GL_AlphaTest(false); } } static void R_DrawTextureSurfaceList_ShowSurfaces(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth) { int vi; int j; r_vertexgeneric_t *batchvertex; float c[4]; // R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(false, false); if(rsurface.texture && rsurface.texture->currentskinframe) { memcpy(c, rsurface.texture->currentskinframe->avgcolor, sizeof(c)); c[3] *= rsurface.texture->currentalpha; } else { c[0] = 1; c[1] = 0; c[2] = 1; c[3] = 1; } if (rsurface.texture->pantstexture || rsurface.texture->shirttexture) { c[0] = 0.5 * (rsurface.colormap_pantscolor[0] * 0.3 + rsurface.colormap_shirtcolor[0] * 0.7); c[1] = 0.5 * (rsurface.colormap_pantscolor[1] * 0.3 + rsurface.colormap_shirtcolor[1] * 0.7); c[2] = 0.5 * (rsurface.colormap_pantscolor[2] * 0.3 + rsurface.colormap_shirtcolor[2] * 0.7); } // brighten it up (as texture value 127 means "unlit") c[0] *= 2 * r_refdef.view.colorscale; c[1] *= 2 * r_refdef.view.colorscale; c[2] *= 2 * r_refdef.view.colorscale; if(rsurface.texture->currentmaterialflags & MATERIALFLAG_WATERALPHA) c[3] *= r_wateralpha.value; if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHA && c[3] != 1) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); } else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ADD) { GL_BlendFunc(GL_ONE, GL_ONE); GL_DepthMask(false); } else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // can't do alpha test without texture, so let's blend instead GL_DepthMask(false); } else if(rsurface.texture->currentmaterialflags & MATERIALFLAG_CUSTOMBLEND) { GL_BlendFunc(rsurface.texture->customblendfunc[0], rsurface.texture->customblendfunc[1]); GL_DepthMask(false); } else { GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(writedepth); } if (r_showsurfaces.integer == 3) { rsurface.passcolor4f = NULL; if (rsurface.texture->currentmaterialflags & MATERIALFLAG_FULLBRIGHT) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); rsurface.passcolor4f = NULL; rsurface.passcolor4f_vertexbuffer = 0; rsurface.passcolor4f_bufferoffset = 0; } else if (rsurface.texture->currentmaterialflags & MATERIALFLAG_MODELLIGHT) { qboolean applycolor = true; float one = 1.0; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); r_refdef.lightmapintensity = 1; RSurf_DrawBatch_GL11_ApplyVertexShade(&one, &one, &one, &one, &applycolor); r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all } else if (FAKELIGHT_ENABLED) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); r_refdef.lightmapintensity = r_fakelight_intensity.value; RSurf_DrawBatch_GL11_ApplyFakeLight(); r_refdef.lightmapintensity = 0; // we're in showsurfaces, after all } else { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_VERTEXCOLOR | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); rsurface.passcolor4f = rsurface.batchlightmapcolor4f; rsurface.passcolor4f_vertexbuffer = rsurface.batchlightmapcolor4f_vertexbuffer; rsurface.passcolor4f_bufferoffset = rsurface.batchlightmapcolor4f_bufferoffset; } if(!rsurface.passcolor4f) RSurf_DrawBatch_GL11_MakeFullbrightLightmapColorArray(); RSurf_DrawBatch_GL11_ApplyAmbient(); RSurf_DrawBatch_GL11_ApplyColor(c[0], c[1], c[2], c[3]); if(r_refdef.fogenabled) RSurf_DrawBatch_GL11_ApplyFogToFinishedVertexColors(); RSurf_DrawBatch_GL11_ClampColor(); R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.passcolor4f, NULL); R_SetupShader_Generic_NoTexture(false, false); RSurf_DrawBatch(); } else if (!r_refdef.view.showdebug) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++) { VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); Vector4Set(batchvertex[vi].color4f, 0, 0, 0, 1); } R_Mesh_PrepareVertices_Generic_Unlock(); RSurf_DrawBatch(); } else if (r_showsurfaces.integer == 4) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchnumvertices); for (j = 0, vi = 0;j < rsurface.batchnumvertices;j++, vi++) { unsigned char d = (vi << 3) * (1.0f / 256.0f); VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); Vector4Set(batchvertex[vi].color4f, d, d, d, 1); } R_Mesh_PrepareVertices_Generic_Unlock(); RSurf_DrawBatch(); } else if (r_showsurfaces.integer == 2) { const int *e; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); batchvertex = R_Mesh_PrepareVertices_Generic_Lock(3*rsurface.batchnumtriangles); for (j = 0, e = rsurface.batchelement3i + 3 * rsurface.batchfirsttriangle;j < rsurface.batchnumtriangles;j++, e += 3) { unsigned char d = ((j + rsurface.batchfirsttriangle) << 3) * (1.0f / 256.0f); VectorCopy(rsurface.batchvertex3f + 3*e[0], batchvertex[j*3+0].vertex3f); VectorCopy(rsurface.batchvertex3f + 3*e[1], batchvertex[j*3+1].vertex3f); VectorCopy(rsurface.batchvertex3f + 3*e[2], batchvertex[j*3+2].vertex3f); Vector4Set(batchvertex[j*3+0].color4f, d, d, d, 1); Vector4Set(batchvertex[j*3+1].color4f, d, d, d, 1); Vector4Set(batchvertex[j*3+2].color4f, d, d, d, 1); } R_Mesh_PrepareVertices_Generic_Unlock(); R_Mesh_Draw(0, rsurface.batchnumtriangles*3, 0, rsurface.batchnumtriangles, NULL, NULL, 0, NULL, NULL, 0); } else { int texturesurfaceindex; int k; const msurface_t *surface; float surfacecolor4f[4]; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); batchvertex = R_Mesh_PrepareVertices_Generic_Lock(rsurface.batchfirstvertex + rsurface.batchnumvertices); vi = 0; for (texturesurfaceindex = 0;texturesurfaceindex < texturenumsurfaces;texturesurfaceindex++) { surface = texturesurfacelist[texturesurfaceindex]; k = (int)(((size_t)surface) / sizeof(msurface_t)); Vector4Set(surfacecolor4f, (k & 0xF) * (1.0f / 16.0f), (k & 0xF0) * (1.0f / 256.0f), (k & 0xF00) * (1.0f / 4096.0f), 1); for (j = 0;j < surface->num_vertices;j++) { VectorCopy(rsurface.batchvertex3f + 3*vi, batchvertex[vi].vertex3f); Vector4Copy(surfacecolor4f, batchvertex[vi].color4f); vi++; } } R_Mesh_PrepareVertices_Generic_Unlock(); RSurf_DrawBatch(); } } static void R_DrawWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) { CHECKGLERROR RSurf_SetupDepthAndCulling(); if (r_showsurfaces.integer) { R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); return; } switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); break; case RENDERPATH_GL13: case RENDERPATH_GLES1: R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); break; case RENDERPATH_GL11: R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); break; } CHECKGLERROR } static void R_DrawModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean prepass) { CHECKGLERROR RSurf_SetupDepthAndCulling(); if (r_showsurfaces.integer) { R_DrawTextureSurfaceList_ShowSurfaces(texturenumsurfaces, texturesurfacelist, writedepth); return; } switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: R_DrawTextureSurfaceList_GL20(texturenumsurfaces, texturesurfacelist, writedepth, prepass); break; case RENDERPATH_GL13: case RENDERPATH_GLES1: R_DrawTextureSurfaceList_GL13(texturenumsurfaces, texturesurfacelist, writedepth); break; case RENDERPATH_GL11: R_DrawTextureSurfaceList_GL11(texturenumsurfaces, texturesurfacelist, writedepth); break; } CHECKGLERROR } static void R_DrawSurface_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i, j; int texturenumsurfaces, endsurface; texture_t *texture; const msurface_t *surface; const msurface_t *texturesurfacelist[MESHQUEUE_TRANSPARENT_BATCHSIZE]; // if the model is static it doesn't matter what value we give for // wantnormals and wanttangents, so this logic uses only rules applicable // to a model, knowing that they are meaningless otherwise if (ent == r_refdef.scene.worldentity) RSurf_ActiveWorldEntity(); else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) RSurf_ActiveModelEntity(ent, false, false, false); else { switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: RSurf_ActiveModelEntity(ent, true, true, false); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: RSurf_ActiveModelEntity(ent, true, false, false); break; } } if (r_transparentdepthmasking.integer) { qboolean setup = false; for (i = 0;i < numsurfaces;i = j) { j = i + 1; surface = rsurface.modelsurfaces + surfacelist[i]; texture = surface->texture; rsurface.texture = R_GetCurrentTexture(texture); rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; // scan ahead until we find a different texture endsurface = min(i + 1024, numsurfaces); texturenumsurfaces = 0; texturesurfacelist[texturenumsurfaces++] = surface; for (;j < endsurface;j++) { surface = rsurface.modelsurfaces + surfacelist[j]; if (texture != surface->texture) break; texturesurfacelist[texturenumsurfaces++] = surface; } if (!(rsurface.texture->currentmaterialflags & MATERIALFLAG_TRANSDEPTH)) continue; // render the range of surfaces as depth if (!setup) { setup = true; GL_ColorMask(0,0,0,0); GL_Color(1,1,1,1); GL_DepthTest(true); GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(true); // R_Mesh_ResetTextureState(); } RSurf_SetupDepthAndCulling(); RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4); R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); RSurf_DrawBatch(); } if (setup) GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); } for (i = 0;i < numsurfaces;i = j) { j = i + 1; surface = rsurface.modelsurfaces + surfacelist[i]; texture = surface->texture; rsurface.texture = R_GetCurrentTexture(texture); // scan ahead until we find a different texture endsurface = min(i + MESHQUEUE_TRANSPARENT_BATCHSIZE, numsurfaces); texturenumsurfaces = 0; texturesurfacelist[texturenumsurfaces++] = surface; if(FAKELIGHT_ENABLED) { rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; for (;j < endsurface;j++) { surface = rsurface.modelsurfaces + surfacelist[j]; if (texture != surface->texture) break; texturesurfacelist[texturenumsurfaces++] = surface; } } else { rsurface.lightmaptexture = surface->lightmaptexture; rsurface.deluxemaptexture = surface->deluxemaptexture; rsurface.uselightmaptexture = surface->lightmaptexture != NULL; for (;j < endsurface;j++) { surface = rsurface.modelsurfaces + surfacelist[j]; if (texture != surface->texture || rsurface.lightmaptexture != surface->lightmaptexture) break; texturesurfacelist[texturenumsurfaces++] = surface; } } // render the range of surfaces if (ent == r_refdef.scene.worldentity) R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); else R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, false, false); } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } static void R_ProcessTransparentTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist) { // transparent surfaces get pushed off into the transparent queue int surfacelistindex; const msurface_t *surface; vec3_t tempcenter, center; for (surfacelistindex = 0;surfacelistindex < texturenumsurfaces;surfacelistindex++) { surface = texturesurfacelist[surfacelistindex]; if (r_transparent_sortsurfacesbynearest.integer) { tempcenter[0] = bound(surface->mins[0], rsurface.localvieworigin[0], surface->maxs[0]); tempcenter[1] = bound(surface->mins[1], rsurface.localvieworigin[1], surface->maxs[1]); tempcenter[2] = bound(surface->mins[2], rsurface.localvieworigin[2], surface->maxs[2]); } else { tempcenter[0] = (surface->mins[0] + surface->maxs[0]) * 0.5f; tempcenter[1] = (surface->mins[1] + surface->maxs[1]) * 0.5f; tempcenter[2] = (surface->mins[2] + surface->maxs[2]) * 0.5f; } Matrix4x4_Transform(&rsurface.matrix, tempcenter, center); if (rsurface.entity->transparent_offset) // transparent offset { center[0] += r_refdef.view.forward[0]*rsurface.entity->transparent_offset; center[1] += r_refdef.view.forward[1]*rsurface.entity->transparent_offset; center[2] += r_refdef.view.forward[2]*rsurface.entity->transparent_offset; } R_MeshQueue_AddTransparent((rsurface.entity->flags & RENDER_WORLDOBJECT) ? TRANSPARENTSORT_SKY : (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) ? TRANSPARENTSORT_HUD : rsurface.texture->transparentsort, center, R_DrawSurface_TransparentCallback, rsurface.entity, surface - rsurface.modelsurfaces, rsurface.rtlight); } } static void R_DrawTextureSurfaceList_DepthOnly(int texturenumsurfaces, const msurface_t **texturesurfacelist) { if ((rsurface.texture->currentmaterialflags & (MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_BLENDED | MATERIALFLAG_ALPHATEST))) return; if (r_fb.water.renderingscene && (rsurface.texture->currentmaterialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFLECTION))) return; RSurf_SetupDepthAndCulling(); RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ALLOWMULTIDRAW, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Vertex3f(rsurface.batchnumvertices, rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); R_SetupShader_DepthOrShadow(false, false, !!rsurface.batchskeletaltransform3x4); RSurf_DrawBatch(); } static void R_ProcessWorldTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, qboolean prepass) { CHECKGLERROR if (depthonly) R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); else if (prepass) { if (!rsurface.texture->currentnumlayers) return; if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); else R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); } else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); else if (!rsurface.texture->currentnumlayers) return; else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)))) { // in the deferred case, transparent surfaces were queued during prepass if (!r_shadow_usingdeferredprepass) R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); } else { // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier R_DrawWorldTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); } CHECKGLERROR } static void R_QueueWorldSurfaceList(int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) { int i, j; texture_t *texture; R_FrameData_SetMark(); // break the surface list down into batches by texture and use of lightmapping for (i = 0;i < numsurfaces;i = j) { j = i + 1; // texture is the base texture pointer, rsurface.texture is the // current frame/skin the texture is directing us to use (for example // if a model has 2 skins and it is on skin 1, then skin 0 tells us to // use skin 1 instead) texture = surfacelist[i]->texture; rsurface.texture = R_GetCurrentTexture(texture); if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) { // if this texture is not the kind we want, skip ahead to the next one for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) ; continue; } if(FAKELIGHT_ENABLED || depthonly || prepass) { rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; // simply scan ahead until we find a different texture or lightmap state for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) ; } else { rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; // simply scan ahead until we find a different texture or lightmap state for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) ; } // render the range of surfaces R_ProcessWorldTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, prepass); } R_FrameData_ReturnToMark(); } static void R_ProcessModelTextureSurfaceList(int texturenumsurfaces, const msurface_t **texturesurfacelist, qboolean writedepth, qboolean depthonly, qboolean prepass) { CHECKGLERROR if (depthonly) R_DrawTextureSurfaceList_DepthOnly(texturenumsurfaces, texturesurfacelist); else if (prepass) { if (!rsurface.texture->currentnumlayers) return; if (rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); else R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth, prepass); } else if ((rsurface.texture->currentmaterialflags & MATERIALFLAG_SKY) && (!r_showsurfaces.integer || r_showsurfaces.integer == 3)) R_DrawTextureSurfaceList_Sky(texturenumsurfaces, texturesurfacelist); else if (!rsurface.texture->currentnumlayers) return; else if (((rsurface.texture->currentmaterialflags & MATERIALFLAGMASK_DEPTHSORTED) || (r_showsurfaces.integer == 3 && (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST)))) { // in the deferred case, transparent surfaces were queued during prepass if (!r_shadow_usingdeferredprepass) R_ProcessTransparentTextureSurfaceList(texturenumsurfaces, texturesurfacelist); } else { // the alphatest check is to make sure we write depth for anything we skipped on the depth-only pass earlier R_DrawModelTextureSurfaceList(texturenumsurfaces, texturesurfacelist, writedepth || (rsurface.texture->currentmaterialflags & MATERIALFLAG_ALPHATEST), prepass); } CHECKGLERROR } static void R_QueueModelSurfaceList(entity_render_t *ent, int numsurfaces, const msurface_t **surfacelist, int flagsmask, qboolean writedepth, qboolean depthonly, qboolean prepass) { int i, j; texture_t *texture; R_FrameData_SetMark(); // break the surface list down into batches by texture and use of lightmapping for (i = 0;i < numsurfaces;i = j) { j = i + 1; // texture is the base texture pointer, rsurface.texture is the // current frame/skin the texture is directing us to use (for example // if a model has 2 skins and it is on skin 1, then skin 0 tells us to // use skin 1 instead) texture = surfacelist[i]->texture; rsurface.texture = R_GetCurrentTexture(texture); if (!(rsurface.texture->currentmaterialflags & flagsmask) || (rsurface.texture->currentmaterialflags & MATERIALFLAG_NODRAW)) { // if this texture is not the kind we want, skip ahead to the next one for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) ; continue; } if(FAKELIGHT_ENABLED || depthonly || prepass) { rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; // simply scan ahead until we find a different texture or lightmap state for (;j < numsurfaces && texture == surfacelist[j]->texture;j++) ; } else { rsurface.lightmaptexture = surfacelist[i]->lightmaptexture; rsurface.deluxemaptexture = surfacelist[i]->deluxemaptexture; rsurface.uselightmaptexture = surfacelist[i]->lightmaptexture != NULL; // simply scan ahead until we find a different texture or lightmap state for (;j < numsurfaces && texture == surfacelist[j]->texture && rsurface.lightmaptexture == surfacelist[j]->lightmaptexture;j++) ; } // render the range of surfaces R_ProcessModelTextureSurfaceList(j - i, surfacelist + i, writedepth, depthonly, prepass); } R_FrameData_ReturnToMark(); } float locboxvertex3f[6*4*3] = { 1,0,1, 1,0,0, 1,1,0, 1,1,1, 0,1,1, 0,1,0, 0,0,0, 0,0,1, 1,1,1, 1,1,0, 0,1,0, 0,1,1, 0,0,1, 0,0,0, 1,0,0, 1,0,1, 0,0,1, 1,0,1, 1,1,1, 0,1,1, 1,0,0, 0,0,0, 0,1,0, 1,1,0 }; unsigned short locboxelements[6*2*3] = { 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23 }; static void R_DrawLoc_Callback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int i, j; cl_locnode_t *loc = (cl_locnode_t *)ent; vec3_t mins, size; float vertex3f[6*4*3]; CHECKGLERROR GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); GL_DepthTest(true); GL_CullFace(GL_NONE); R_EntityMatrix(&identitymatrix); // R_Mesh_ResetTextureState(); i = surfacelist[0]; GL_Color(((i & 0x0007) >> 0) * (1.0f / 7.0f) * r_refdef.view.colorscale, ((i & 0x0038) >> 3) * (1.0f / 7.0f) * r_refdef.view.colorscale, ((i & 0x01C0) >> 6) * (1.0f / 7.0f) * r_refdef.view.colorscale, surfacelist[0] < 0 ? 0.5f : 0.125f); if (VectorCompare(loc->mins, loc->maxs)) { VectorSet(size, 2, 2, 2); VectorMA(loc->mins, -0.5f, size, mins); } else { VectorCopy(loc->mins, mins); VectorSubtract(loc->maxs, loc->mins, size); } for (i = 0;i < 6*4*3;) for (j = 0;j < 3;j++, i++) vertex3f[i] = mins[j] + size[j] * locboxvertex3f[i]; R_Mesh_PrepareVertices_Generic_Arrays(6*4, vertex3f, NULL, NULL); R_SetupShader_Generic_NoTexture(false, false); R_Mesh_Draw(0, 6*4, 0, 6*2, NULL, NULL, 0, locboxelements, NULL, 0); } void R_DrawLocs(void) { int index; cl_locnode_t *loc, *nearestloc; vec3_t center; nearestloc = CL_Locs_FindNearest(cl.movement_origin); for (loc = cl.locnodes, index = 0;loc;loc = loc->next, index++) { VectorLerp(loc->mins, 0.5f, loc->maxs, center); R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, center, R_DrawLoc_Callback, (entity_render_t *)loc, loc == nearestloc ? -1 : index, NULL); } } void R_DecalSystem_Reset(decalsystem_t *decalsystem) { if (decalsystem->decals) Mem_Free(decalsystem->decals); memset(decalsystem, 0, sizeof(*decalsystem)); } static void R_DecalSystem_SpawnTriangle(decalsystem_t *decalsystem, const float *v0, const float *v1, const float *v2, const float *t0, const float *t1, const float *t2, const float *c0, const float *c1, const float *c2, int triangleindex, int surfaceindex, unsigned int decalsequence) { tridecal_t *decal; tridecal_t *decals; int i; // expand or initialize the system if (decalsystem->maxdecals <= decalsystem->numdecals) { decalsystem_t old = *decalsystem; qboolean useshortelements; decalsystem->maxdecals = max(16, decalsystem->maxdecals * 2); useshortelements = decalsystem->maxdecals * 3 <= 65536; decalsystem->decals = (tridecal_t *)Mem_Alloc(cls.levelmempool, decalsystem->maxdecals * (sizeof(tridecal_t) + sizeof(float[3][3]) + sizeof(float[3][2]) + sizeof(float[3][4]) + sizeof(int[3]) + (useshortelements ? sizeof(unsigned short[3]) : 0))); decalsystem->color4f = (float *)(decalsystem->decals + decalsystem->maxdecals); decalsystem->texcoord2f = (float *)(decalsystem->color4f + decalsystem->maxdecals*12); decalsystem->vertex3f = (float *)(decalsystem->texcoord2f + decalsystem->maxdecals*6); decalsystem->element3i = (int *)(decalsystem->vertex3f + decalsystem->maxdecals*9); decalsystem->element3s = (useshortelements ? ((unsigned short *)(decalsystem->element3i + decalsystem->maxdecals*3)) : NULL); if (decalsystem->numdecals) memcpy(decalsystem->decals, old.decals, decalsystem->numdecals * sizeof(tridecal_t)); if (old.decals) Mem_Free(old.decals); for (i = 0;i < decalsystem->maxdecals*3;i++) decalsystem->element3i[i] = i; if (useshortelements) for (i = 0;i < decalsystem->maxdecals*3;i++) decalsystem->element3s[i] = i; } // grab a decal and search for another free slot for the next one decals = decalsystem->decals; decal = decalsystem->decals + (i = decalsystem->freedecal++); for (i = decalsystem->freedecal;i < decalsystem->numdecals && decals[i].color4f[0][3];i++) ; decalsystem->freedecal = i; if (decalsystem->numdecals <= i) decalsystem->numdecals = i + 1; // initialize the decal decal->lived = 0; decal->triangleindex = triangleindex; decal->surfaceindex = surfaceindex; decal->decalsequence = decalsequence; decal->color4f[0][0] = c0[0]; decal->color4f[0][1] = c0[1]; decal->color4f[0][2] = c0[2]; decal->color4f[0][3] = 1; decal->color4f[1][0] = c1[0]; decal->color4f[1][1] = c1[1]; decal->color4f[1][2] = c1[2]; decal->color4f[1][3] = 1; decal->color4f[2][0] = c2[0]; decal->color4f[2][1] = c2[1]; decal->color4f[2][2] = c2[2]; decal->color4f[2][3] = 1; decal->vertex3f[0][0] = v0[0]; decal->vertex3f[0][1] = v0[1]; decal->vertex3f[0][2] = v0[2]; decal->vertex3f[1][0] = v1[0]; decal->vertex3f[1][1] = v1[1]; decal->vertex3f[1][2] = v1[2]; decal->vertex3f[2][0] = v2[0]; decal->vertex3f[2][1] = v2[1]; decal->vertex3f[2][2] = v2[2]; decal->texcoord2f[0][0] = t0[0]; decal->texcoord2f[0][1] = t0[1]; decal->texcoord2f[1][0] = t1[0]; decal->texcoord2f[1][1] = t1[1]; decal->texcoord2f[2][0] = t2[0]; decal->texcoord2f[2][1] = t2[1]; TriangleNormal(v0, v1, v2, decal->plane); VectorNormalize(decal->plane); decal->plane[3] = DotProduct(v0, decal->plane); } extern cvar_t cl_decals_bias; extern cvar_t cl_decals_models; extern cvar_t cl_decals_newsystem_intensitymultiplier; // baseparms, parms, temps static void R_DecalSystem_SplatTriangle(decalsystem_t *decalsystem, float r, float g, float b, float a, float s1, float t1, float s2, float t2, unsigned int decalsequence, qboolean dynamic, float (*planes)[4], matrix4x4_t *projection, int triangleindex, int surfaceindex) { int cornerindex; int index; float v[9][3]; const float *vertex3f; const float *normal3f; int numpoints; float points[2][9][3]; float temp[3]; float tc[9][2]; float f; float c[9][4]; const int *e; e = rsurface.modelelement3i + 3*triangleindex; vertex3f = rsurface.modelvertex3f; normal3f = rsurface.modelnormal3f; if (normal3f) { for (cornerindex = 0;cornerindex < 3;cornerindex++) { index = 3*e[cornerindex]; VectorMA(vertex3f + index, cl_decals_bias.value, normal3f + index, v[cornerindex]); } } else { for (cornerindex = 0;cornerindex < 3;cornerindex++) { index = 3*e[cornerindex]; VectorCopy(vertex3f + index, v[cornerindex]); } } // cull backfaces //TriangleNormal(v[0], v[1], v[2], normal); //if (DotProduct(normal, localnormal) < 0.0f) // continue; // clip by each of the box planes formed from the projection matrix // if anything survives, we emit the decal numpoints = PolygonF_Clip(3 , v[0] , planes[0][0], planes[0][1], planes[0][2], planes[0][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); if (numpoints < 3) return; numpoints = PolygonF_Clip(numpoints, points[1][0], planes[1][0], planes[1][1], planes[1][2], planes[1][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); if (numpoints < 3) return; numpoints = PolygonF_Clip(numpoints, points[0][0], planes[2][0], planes[2][1], planes[2][2], planes[2][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); if (numpoints < 3) return; numpoints = PolygonF_Clip(numpoints, points[1][0], planes[3][0], planes[3][1], planes[3][2], planes[3][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[0][0]); if (numpoints < 3) return; numpoints = PolygonF_Clip(numpoints, points[0][0], planes[4][0], planes[4][1], planes[4][2], planes[4][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), points[1][0]); if (numpoints < 3) return; numpoints = PolygonF_Clip(numpoints, points[1][0], planes[5][0], planes[5][1], planes[5][2], planes[5][3], 1.0f/64.0f, sizeof(points[0])/sizeof(points[0][0]), v[0]); if (numpoints < 3) return; // some part of the triangle survived, so we have to accept it... if (dynamic) { // dynamic always uses the original triangle numpoints = 3; for (cornerindex = 0;cornerindex < 3;cornerindex++) { index = 3*e[cornerindex]; VectorCopy(vertex3f + index, v[cornerindex]); } } for (cornerindex = 0;cornerindex < numpoints;cornerindex++) { // convert vertex positions to texcoords Matrix4x4_Transform(projection, v[cornerindex], temp); tc[cornerindex][0] = (temp[1]+1.0f)*0.5f * (s2-s1) + s1; tc[cornerindex][1] = (temp[2]+1.0f)*0.5f * (t2-t1) + t1; // calculate distance fade from the projection origin f = a * (1.0f-fabs(temp[0])) * cl_decals_newsystem_intensitymultiplier.value; f = bound(0.0f, f, 1.0f); c[cornerindex][0] = r * f; c[cornerindex][1] = g * f; c[cornerindex][2] = b * f; c[cornerindex][3] = 1.0f; //VectorMA(v[cornerindex], cl_decals_bias.value, localnormal, v[cornerindex]); } if (dynamic) R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[1], v[2], tc[0], tc[1], tc[2], c[0], c[1], c[2], triangleindex, surfaceindex, decalsequence); else for (cornerindex = 0;cornerindex < numpoints-2;cornerindex++) R_DecalSystem_SpawnTriangle(decalsystem, v[0], v[cornerindex+1], v[cornerindex+2], tc[0], tc[cornerindex+1], tc[cornerindex+2], c[0], c[cornerindex+1], c[cornerindex+2], -1, surfaceindex, decalsequence); } static void R_DecalSystem_SplatEntity(entity_render_t *ent, const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, unsigned int decalsequence) { matrix4x4_t projection; decalsystem_t *decalsystem; qboolean dynamic; dp_model_t *model; const msurface_t *surface; const msurface_t *surfaces; const int *surfacelist; const texture_t *texture; int numtriangles; int numsurfacelist; int surfacelistindex; int surfaceindex; int triangleindex; float localorigin[3]; float localnormal[3]; float localmins[3]; float localmaxs[3]; float localsize; //float normal[3]; float planes[6][4]; float angles[3]; bih_t *bih; int bih_triangles_count; int bih_triangles[256]; int bih_surfaces[256]; decalsystem = &ent->decalsystem; model = ent->model; if (!model || !ent->allowdecals || ent->alpha < 1 || (ent->flags & (RENDER_ADDITIVE | RENDER_NODEPTHTEST))) { R_DecalSystem_Reset(&ent->decalsystem); return; } if (!model->brush.data_leafs && !cl_decals_models.integer) { if (decalsystem->model) R_DecalSystem_Reset(decalsystem); return; } if (decalsystem->model != model) R_DecalSystem_Reset(decalsystem); decalsystem->model = model; RSurf_ActiveModelEntity(ent, true, false, false); Matrix4x4_Transform(&rsurface.inversematrix, worldorigin, localorigin); Matrix4x4_Transform3x3(&rsurface.inversematrix, worldnormal, localnormal); VectorNormalize(localnormal); localsize = worldsize*rsurface.inversematrixscale; localmins[0] = localorigin[0] - localsize; localmins[1] = localorigin[1] - localsize; localmins[2] = localorigin[2] - localsize; localmaxs[0] = localorigin[0] + localsize; localmaxs[1] = localorigin[1] + localsize; localmaxs[2] = localorigin[2] + localsize; //VectorCopy(localnormal, planes[4]); //VectorVectors(planes[4], planes[2], planes[0]); AnglesFromVectors(angles, localnormal, NULL, false); AngleVectors(angles, planes[0], planes[2], planes[4]); VectorNegate(planes[0], planes[1]); VectorNegate(planes[2], planes[3]); VectorNegate(planes[4], planes[5]); planes[0][3] = DotProduct(planes[0], localorigin) - localsize; planes[1][3] = DotProduct(planes[1], localorigin) - localsize; planes[2][3] = DotProduct(planes[2], localorigin) - localsize; planes[3][3] = DotProduct(planes[3], localorigin) - localsize; planes[4][3] = DotProduct(planes[4], localorigin) - localsize; planes[5][3] = DotProduct(planes[5], localorigin) - localsize; #if 1 // works { matrix4x4_t forwardprojection; Matrix4x4_CreateFromQuakeEntity(&forwardprojection, localorigin[0], localorigin[1], localorigin[2], angles[0], angles[1], angles[2], localsize); Matrix4x4_Invert_Simple(&projection, &forwardprojection); } #else // broken { float projectionvector[4][3]; VectorScale(planes[0], ilocalsize, projectionvector[0]); VectorScale(planes[2], ilocalsize, projectionvector[1]); VectorScale(planes[4], ilocalsize, projectionvector[2]); projectionvector[0][0] = planes[0][0] * ilocalsize; projectionvector[0][1] = planes[1][0] * ilocalsize; projectionvector[0][2] = planes[2][0] * ilocalsize; projectionvector[1][0] = planes[0][1] * ilocalsize; projectionvector[1][1] = planes[1][1] * ilocalsize; projectionvector[1][2] = planes[2][1] * ilocalsize; projectionvector[2][0] = planes[0][2] * ilocalsize; projectionvector[2][1] = planes[1][2] * ilocalsize; projectionvector[2][2] = planes[2][2] * ilocalsize; projectionvector[3][0] = -(localorigin[0]*projectionvector[0][0]+localorigin[1]*projectionvector[1][0]+localorigin[2]*projectionvector[2][0]); projectionvector[3][1] = -(localorigin[0]*projectionvector[0][1]+localorigin[1]*projectionvector[1][1]+localorigin[2]*projectionvector[2][1]); projectionvector[3][2] = -(localorigin[0]*projectionvector[0][2]+localorigin[1]*projectionvector[1][2]+localorigin[2]*projectionvector[2][2]); Matrix4x4_FromVectors(&projection, projectionvector[0], projectionvector[1], projectionvector[2], projectionvector[3]); } #endif dynamic = model->surfmesh.isanimated; numsurfacelist = model->nummodelsurfaces; surfacelist = model->sortedmodelsurfaces; surfaces = model->data_surfaces; bih = NULL; bih_triangles_count = -1; if(!dynamic) { if(model->render_bih.numleafs) bih = &model->render_bih; else if(model->collision_bih.numleafs) bih = &model->collision_bih; } if(bih) bih_triangles_count = BIH_GetTriangleListForBox(bih, sizeof(bih_triangles) / sizeof(*bih_triangles), bih_triangles, bih_surfaces, localmins, localmaxs); if(bih_triangles_count == 0) return; if(bih_triangles_count > (int) (sizeof(bih_triangles) / sizeof(*bih_triangles))) // hit too many, likely bad anyway return; if(bih_triangles_count > 0) { for (triangleindex = 0; triangleindex < bih_triangles_count; ++triangleindex) { surfaceindex = bih_surfaces[triangleindex]; surface = surfaces + surfaceindex; texture = surface->texture; if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) continue; if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) continue; R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, bih_triangles[triangleindex], surfaceindex); } } else { for (surfacelistindex = 0;surfacelistindex < numsurfacelist;surfacelistindex++) { surfaceindex = surfacelist[surfacelistindex]; surface = surfaces + surfaceindex; // check cull box first because it rejects more than any other check if (!dynamic && !BoxesOverlap(surface->mins, surface->maxs, localmins, localmaxs)) continue; // skip transparent surfaces texture = surface->texture; if (texture->currentmaterialflags & (MATERIALFLAG_BLENDED | MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_SKY | MATERIALFLAG_SHORTDEPTHRANGE | MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION)) continue; if (texture->surfaceflags & Q3SURFACEFLAG_NOMARKS) continue; numtriangles = surface->num_triangles; for (triangleindex = 0; triangleindex < numtriangles; triangleindex++) R_DecalSystem_SplatTriangle(decalsystem, r, g, b, a, s1, t1, s2, t2, decalsequence, dynamic, planes, &projection, triangleindex + surface->num_firsttriangle, surfaceindex); } } } // do not call this outside of rendering code - use R_DecalSystem_SplatEntities instead static void R_DecalSystem_ApplySplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize, unsigned int decalsequence) { int renderentityindex; float worldmins[3]; float worldmaxs[3]; entity_render_t *ent; if (!cl_decals_newsystem.integer) return; worldmins[0] = worldorigin[0] - worldsize; worldmins[1] = worldorigin[1] - worldsize; worldmins[2] = worldorigin[2] - worldsize; worldmaxs[0] = worldorigin[0] + worldsize; worldmaxs[1] = worldorigin[1] + worldsize; worldmaxs[2] = worldorigin[2] + worldsize; R_DecalSystem_SplatEntity(r_refdef.scene.worldentity, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); for (renderentityindex = 0;renderentityindex < r_refdef.scene.numentities;renderentityindex++) { ent = r_refdef.scene.entities[renderentityindex]; if (!BoxesOverlap(ent->mins, ent->maxs, worldmins, worldmaxs)) continue; R_DecalSystem_SplatEntity(ent, worldorigin, worldnormal, r, g, b, a, s1, t1, s2, t2, worldsize, decalsequence); } } typedef struct r_decalsystem_splatqueue_s { vec3_t worldorigin; vec3_t worldnormal; float color[4]; float tcrange[4]; float worldsize; unsigned int decalsequence; } r_decalsystem_splatqueue_t; int r_decalsystem_numqueued = 0; r_decalsystem_splatqueue_t r_decalsystem_queue[MAX_DECALSYSTEM_QUEUE]; void R_DecalSystem_SplatEntities(const vec3_t worldorigin, const vec3_t worldnormal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float worldsize) { r_decalsystem_splatqueue_t *queue; if (!cl_decals_newsystem.integer || r_decalsystem_numqueued == MAX_DECALSYSTEM_QUEUE) return; queue = &r_decalsystem_queue[r_decalsystem_numqueued++]; VectorCopy(worldorigin, queue->worldorigin); VectorCopy(worldnormal, queue->worldnormal); Vector4Set(queue->color, r, g, b, a); Vector4Set(queue->tcrange, s1, t1, s2, t2); queue->worldsize = worldsize; queue->decalsequence = cl.decalsequence++; } static void R_DecalSystem_ApplySplatEntitiesQueue(void) { int i; r_decalsystem_splatqueue_t *queue; for (i = 0, queue = r_decalsystem_queue;i < r_decalsystem_numqueued;i++, queue++) R_DecalSystem_ApplySplatEntities(queue->worldorigin, queue->worldnormal, queue->color[0], queue->color[1], queue->color[2], queue->color[3], queue->tcrange[0], queue->tcrange[1], queue->tcrange[2], queue->tcrange[3], queue->worldsize, queue->decalsequence); r_decalsystem_numqueued = 0; } extern cvar_t cl_decals_max; static void R_DrawModelDecals_FadeEntity(entity_render_t *ent) { int i; decalsystem_t *decalsystem = &ent->decalsystem; int numdecals; unsigned int killsequence; tridecal_t *decal; float frametime; float lifetime; if (!decalsystem->numdecals) return; if (r_showsurfaces.integer) return; if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) { R_DecalSystem_Reset(decalsystem); return; } killsequence = cl.decalsequence - bound(1, (unsigned int) cl_decals_max.integer, cl.decalsequence); lifetime = cl_decals_time.value + cl_decals_fadetime.value; if (decalsystem->lastupdatetime) frametime = (r_refdef.scene.time - decalsystem->lastupdatetime); else frametime = 0; decalsystem->lastupdatetime = r_refdef.scene.time; numdecals = decalsystem->numdecals; for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) { if (decal->color4f[0][3]) { decal->lived += frametime; if (killsequence > decal->decalsequence || decal->lived >= lifetime) { memset(decal, 0, sizeof(*decal)); if (decalsystem->freedecal > i) decalsystem->freedecal = i; } } } decal = decalsystem->decals; while (numdecals > 0 && !decal[numdecals-1].color4f[0][3]) numdecals--; // collapse the array by shuffling the tail decals into the gaps for (;;) { while (decalsystem->freedecal < numdecals && decal[decalsystem->freedecal].color4f[0][3]) decalsystem->freedecal++; if (decalsystem->freedecal == numdecals) break; decal[decalsystem->freedecal] = decal[--numdecals]; } decalsystem->numdecals = numdecals; if (numdecals <= 0) { // if there are no decals left, reset decalsystem R_DecalSystem_Reset(decalsystem); } } extern skinframe_t *decalskinframe; static void R_DrawModelDecals_Entity(entity_render_t *ent) { int i; decalsystem_t *decalsystem = &ent->decalsystem; int numdecals; tridecal_t *decal; float faderate; float alpha; float *v3f; float *c4f; float *t2f; const int *e; const unsigned char *surfacevisible = ent == r_refdef.scene.worldentity ? r_refdef.viewcache.world_surfacevisible : NULL; int numtris = 0; numdecals = decalsystem->numdecals; if (!numdecals) return; if (r_showsurfaces.integer) return; if (ent->model != decalsystem->model || ent->alpha < 1 || (ent->flags & RENDER_ADDITIVE)) { R_DecalSystem_Reset(decalsystem); return; } // if the model is static it doesn't matter what value we give for // wantnormals and wanttangents, so this logic uses only rules applicable // to a model, knowing that they are meaningless otherwise if (ent == r_refdef.scene.worldentity) RSurf_ActiveWorldEntity(); else RSurf_ActiveModelEntity(ent, false, false, false); decalsystem->lastupdatetime = r_refdef.scene.time; faderate = 1.0f / max(0.001f, cl_decals_fadetime.value); // update vertex positions for animated models v3f = decalsystem->vertex3f; c4f = decalsystem->color4f; t2f = decalsystem->texcoord2f; for (i = 0, decal = decalsystem->decals;i < numdecals;i++, decal++) { if (!decal->color4f[0][3]) continue; if (surfacevisible && !surfacevisible[decal->surfaceindex]) continue; // skip backfaces if (decal->triangleindex < 0 && DotProduct(r_refdef.view.origin, decal->plane) < decal->plane[3]) continue; // update color values for fading decals if (decal->lived >= cl_decals_time.value) alpha = 1 - faderate * (decal->lived - cl_decals_time.value); else alpha = 1.0f; c4f[ 0] = decal->color4f[0][0] * alpha; c4f[ 1] = decal->color4f[0][1] * alpha; c4f[ 2] = decal->color4f[0][2] * alpha; c4f[ 3] = 1; c4f[ 4] = decal->color4f[1][0] * alpha; c4f[ 5] = decal->color4f[1][1] * alpha; c4f[ 6] = decal->color4f[1][2] * alpha; c4f[ 7] = 1; c4f[ 8] = decal->color4f[2][0] * alpha; c4f[ 9] = decal->color4f[2][1] * alpha; c4f[10] = decal->color4f[2][2] * alpha; c4f[11] = 1; t2f[0] = decal->texcoord2f[0][0]; t2f[1] = decal->texcoord2f[0][1]; t2f[2] = decal->texcoord2f[1][0]; t2f[3] = decal->texcoord2f[1][1]; t2f[4] = decal->texcoord2f[2][0]; t2f[5] = decal->texcoord2f[2][1]; // update vertex positions for animated models if (decal->triangleindex >= 0 && decal->triangleindex < rsurface.modelnumtriangles) { e = rsurface.modelelement3i + 3*decal->triangleindex; VectorCopy(rsurface.modelvertex3f + 3*e[0], v3f); VectorCopy(rsurface.modelvertex3f + 3*e[1], v3f + 3); VectorCopy(rsurface.modelvertex3f + 3*e[2], v3f + 6); } else { VectorCopy(decal->vertex3f[0], v3f); VectorCopy(decal->vertex3f[1], v3f + 3); VectorCopy(decal->vertex3f[2], v3f + 6); } if (r_refdef.fogenabled) { alpha = RSurf_FogVertex(v3f); VectorScale(c4f, alpha, c4f); alpha = RSurf_FogVertex(v3f + 3); VectorScale(c4f + 4, alpha, c4f + 4); alpha = RSurf_FogVertex(v3f + 6); VectorScale(c4f + 8, alpha, c4f + 8); } v3f += 9; c4f += 12; t2f += 6; numtris++; } if (numtris > 0) { r_refdef.stats[r_stat_drawndecals] += numtris; // now render the decals all at once // (this assumes they all use one particle font texture!) RSurf_ActiveCustomEntity(&rsurface.matrix, &rsurface.inversematrix, rsurface.ent_flags, ent->shadertime, 1, 1, 1, 1, numdecals*3, decalsystem->vertex3f, decalsystem->texcoord2f, NULL, NULL, NULL, decalsystem->color4f, numtris, decalsystem->element3i, decalsystem->element3s, false, false); // R_Mesh_ResetTextureState(); R_Mesh_PrepareVertices_Generic_Arrays(numtris * 3, decalsystem->vertex3f, decalsystem->color4f, decalsystem->texcoord2f); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(rsurface.basepolygonfactor + r_polygonoffset_decals_factor.value, rsurface.basepolygonoffset + r_polygonoffset_decals_offset.value); GL_DepthTest(true); GL_CullFace(GL_NONE); GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); R_SetupShader_Generic(decalskinframe->base, NULL, GL_MODULATE, 1, false, false, false); R_Mesh_Draw(0, numtris * 3, 0, numtris, decalsystem->element3i, NULL, 0, decalsystem->element3s, NULL, 0); } } static void R_DrawModelDecals(void) { int i, numdecals; // fade faster when there are too many decals numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; for (i = 0;i < r_refdef.scene.numentities;i++) numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; R_DrawModelDecals_FadeEntity(r_refdef.scene.worldentity); for (i = 0;i < r_refdef.scene.numentities;i++) if (r_refdef.scene.entities[i]->decalsystem.numdecals) R_DrawModelDecals_FadeEntity(r_refdef.scene.entities[i]); R_DecalSystem_ApplySplatEntitiesQueue(); numdecals = r_refdef.scene.worldentity->decalsystem.numdecals; for (i = 0;i < r_refdef.scene.numentities;i++) numdecals += r_refdef.scene.entities[i]->decalsystem.numdecals; r_refdef.stats[r_stat_totaldecals] += numdecals; if (r_showsurfaces.integer) return; R_DrawModelDecals_Entity(r_refdef.scene.worldentity); for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; if (r_refdef.scene.entities[i]->decalsystem.numdecals) R_DrawModelDecals_Entity(r_refdef.scene.entities[i]); } } extern cvar_t mod_collision_bih; static void R_DrawDebugModel(void) { entity_render_t *ent = rsurface.entity; int i, j, flagsmask; const msurface_t *surface; dp_model_t *model = ent->model; if (!sv.active && !cls.demoplayback && ent != r_refdef.scene.worldentity) return; if (r_showoverdraw.value > 0) { float c = r_refdef.view.colorscale * r_showoverdraw.value * 0.125f; flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; R_SetupShader_Generic_NoTexture(false, false); GL_DepthTest(false); GL_DepthMask(false); GL_DepthRange(0, 1); GL_BlendFunc(GL_ONE, GL_ONE); for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) { if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) continue; rsurface.texture = R_GetCurrentTexture(surface->texture); if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, 1, &surface); GL_CullFace((rsurface.texture->currentmaterialflags & MATERIALFLAG_NOCULLFACE) ? GL_NONE : r_refdef.view.cullface_back); if (!rsurface.texture->currentlayers->depthmask) GL_Color(c, 0, 0, 1.0f); else if (ent == r_refdef.scene.worldentity) GL_Color(c, c, c, 1.0f); else GL_Color(0, c, 0, 1.0f); R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); RSurf_DrawBatch(); } } rsurface.texture = NULL; } flagsmask = MATERIALFLAG_SKY | MATERIALFLAG_WALL; // R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(false, false); GL_DepthRange(0, 1); GL_DepthTest(!r_showdisabledepthtest.integer); GL_DepthMask(false); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (r_showcollisionbrushes.value > 0 && model->collision_bih.numleafs) { int triangleindex; int bihleafindex; qboolean cullbox = false; const q3mbrush_t *brush; const bih_t *bih = &model->collision_bih; const bih_leaf_t *bihleaf; float vertex3f[3][3]; GL_PolygonOffset(r_refdef.polygonfactor + r_showcollisionbrushes_polygonfactor.value, r_refdef.polygonoffset + r_showcollisionbrushes_polygonoffset.value); for (bihleafindex = 0, bihleaf = bih->leafs;bihleafindex < bih->numleafs;bihleafindex++, bihleaf++) { if (cullbox && R_CullBox(bihleaf->mins, bihleaf->maxs)) continue; switch (bihleaf->type) { case BIH_BRUSH: brush = model->brush.data_brushes + bihleaf->itemindex; if (brush->colbrushf && brush->colbrushf->numtriangles) { GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); R_Mesh_PrepareVertices_Generic_Arrays(brush->colbrushf->numpoints, brush->colbrushf->points->v, NULL, NULL); R_Mesh_Draw(0, brush->colbrushf->numpoints, 0, brush->colbrushf->numtriangles, brush->colbrushf->elements, NULL, 0, NULL, NULL, 0); } break; case BIH_COLLISIONTRIANGLE: triangleindex = bihleaf->itemindex; VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+0], vertex3f[0]); VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+1], vertex3f[1]); VectorCopy(model->brush.data_collisionvertex3f + 3*model->brush.data_collisionelement3i[triangleindex*3+2], vertex3f[2]); GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); break; case BIH_RENDERTRIANGLE: triangleindex = bihleaf->itemindex; VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+0], vertex3f[0]); VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+1], vertex3f[1]); VectorCopy(model->surfmesh.data_vertex3f + 3*model->surfmesh.data_element3i[triangleindex*3+2], vertex3f[2]); GL_Color((bihleafindex & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 5) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, ((bihleafindex >> 10) & 31) * (1.0f / 32.0f) * r_refdef.view.colorscale, r_showcollisionbrushes.value); R_Mesh_PrepareVertices_Generic_Arrays(3, vertex3f[0], NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); break; } } } GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); #ifndef USE_GLES2 if (r_showtris.integer && qglPolygonMode) { if (r_showdisabledepthtest.integer) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); } else { GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(true); } qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);CHECKGLERROR for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) { if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) continue; rsurface.texture = R_GetCurrentTexture(surface->texture); if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); if (!rsurface.texture->currentlayers->depthmask) GL_Color(r_refdef.view.colorscale, 0, 0, r_showtris.value); else if (ent == r_refdef.scene.worldentity) GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, r_showtris.value); else GL_Color(0, r_refdef.view.colorscale, 0, r_showtris.value); R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); RSurf_DrawBatch(); } } qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);CHECKGLERROR rsurface.texture = NULL; } if (r_shownormals.value != 0 && qglBegin) { int l, k; vec3_t v; if (r_showdisabledepthtest.integer) { GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthMask(false); } else { GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthMask(true); } for (i = 0, j = model->firstmodelsurface, surface = model->data_surfaces + j;i < model->nummodelsurfaces;i++, j++, surface++) { if (ent == r_refdef.scene.worldentity && !r_refdef.viewcache.world_surfacevisible[j]) continue; rsurface.texture = R_GetCurrentTexture(surface->texture); if ((rsurface.texture->currentmaterialflags & flagsmask) && surface->num_triangles) { RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_ARRAY_NORMAL | BATCHNEED_ARRAY_VECTOR | BATCHNEED_NOGAPS, 1, &surface); qglBegin(GL_LINES); if (r_shownormals.value < 0 && rsurface.batchnormal3f) { for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) { VectorCopy(rsurface.batchvertex3f + l * 3, v); GL_Color(0, 0, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); VectorMA(v, -r_shownormals.value, rsurface.batchnormal3f + l * 3, v); GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); } } if (r_shownormals.value > 0 && rsurface.batchsvector3f) { for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) { VectorCopy(rsurface.batchvertex3f + l * 3, v); GL_Color(r_refdef.view.colorscale, 0, 0, 1); qglVertex3f(v[0], v[1], v[2]); VectorMA(v, r_shownormals.value, rsurface.batchsvector3f + l * 3, v); GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); } } if (r_shownormals.value > 0 && rsurface.batchtvector3f) { for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) { VectorCopy(rsurface.batchvertex3f + l * 3, v); GL_Color(0, r_refdef.view.colorscale, 0, 1); qglVertex3f(v[0], v[1], v[2]); VectorMA(v, r_shownormals.value, rsurface.batchtvector3f + l * 3, v); GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); } } if (r_shownormals.value > 0 && rsurface.batchnormal3f) { for (k = 0, l = rsurface.batchfirstvertex;k < rsurface.batchnumvertices;k++, l++) { VectorCopy(rsurface.batchvertex3f + l * 3, v); GL_Color(0, 0, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); VectorMA(v, r_shownormals.value, rsurface.batchnormal3f + l * 3, v); GL_Color(r_refdef.view.colorscale, r_refdef.view.colorscale, r_refdef.view.colorscale, 1); qglVertex3f(v[0], v[1], v[2]); } } qglEnd(); CHECKGLERROR } } rsurface.texture = NULL; } #endif } int r_maxsurfacelist = 0; const msurface_t **r_surfacelist = NULL; void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) { int i, j, endj, flagsmask; dp_model_t *model = r_refdef.scene.worldmodel; msurface_t *surfaces; unsigned char *update; int numsurfacelist = 0; if (model == NULL) return; if (r_maxsurfacelist < model->num_surfaces) { r_maxsurfacelist = model->num_surfaces; if (r_surfacelist) Mem_Free((msurface_t**)r_surfacelist); r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); } RSurf_ActiveWorldEntity(); surfaces = model->data_surfaces; update = model->brushq1.lightmapupdateflags; // update light styles on this submodel if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) { model_brush_lightstyleinfo_t *style; for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) { if (style->value != r_refdef.scene.lightstylevalue[style->style]) { int *list = style->surfacelist; style->value = r_refdef.scene.lightstylevalue[style->style]; for (j = 0;j < style->numsurfaces;j++) update[list[j]] = true; } } } flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; if (debug) { R_DrawDebugModel(); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity return; } rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; rsurface.texture = NULL; rsurface.rtlight = NULL; numsurfacelist = 0; // add visible surfaces to draw list for (i = 0;i < model->nummodelsurfaces;i++) { j = model->sortedmodelsurfaces[i]; if (r_refdef.viewcache.world_surfacevisible[j]) r_surfacelist[numsurfacelist++] = surfaces + j; } // update lightmaps if needed if (model->brushq1.firstrender) { model->brushq1.firstrender = false; for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) if (update[j]) R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); } else if (update) { for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) if (r_refdef.viewcache.world_surfacevisible[j]) if (update[j]) R_BuildLightMap(r_refdef.scene.worldentity, surfaces + j); } // don't do anything if there were no surfaces if (!numsurfacelist) { rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity return; } R_QueueWorldSurfaceList(numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); // add to stats if desired if (r_speeds.integer && !skysurfaces && !depthonly) { r_refdef.stats[r_stat_world_surfaces] += numsurfacelist; for (j = 0;j < numsurfacelist;j++) r_refdef.stats[r_stat_world_triangles] += r_surfacelist[j]->num_triangles; } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass) { int i, j, endj, flagsmask; dp_model_t *model = ent->model; msurface_t *surfaces; unsigned char *update; int numsurfacelist = 0; if (model == NULL) return; if (r_maxsurfacelist < model->num_surfaces) { r_maxsurfacelist = model->num_surfaces; if (r_surfacelist) Mem_Free((msurface_t **)r_surfacelist); r_surfacelist = (const msurface_t **) Mem_Alloc(r_main_mempool, r_maxsurfacelist * sizeof(*r_surfacelist)); } // if the model is static it doesn't matter what value we give for // wantnormals and wanttangents, so this logic uses only rules applicable // to a model, knowing that they are meaningless otherwise if (ent == r_refdef.scene.worldentity) RSurf_ActiveWorldEntity(); else if (r_showsurfaces.integer && r_showsurfaces.integer != 3) RSurf_ActiveModelEntity(ent, false, false, false); else if (prepass) RSurf_ActiveModelEntity(ent, true, true, true); else if (depthonly) { switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: RSurf_ActiveModelEntity(ent, model->wantnormals, model->wanttangents, false); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: RSurf_ActiveModelEntity(ent, model->wantnormals, false, false); break; } } else { switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: RSurf_ActiveModelEntity(ent, true, true, false); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: RSurf_ActiveModelEntity(ent, true, false, false); break; } } surfaces = model->data_surfaces; update = model->brushq1.lightmapupdateflags; // update light styles if (!skysurfaces && !depthonly && !prepass && model->brushq1.num_lightstyles && r_refdef.lightmapintensity > 0) { model_brush_lightstyleinfo_t *style; for (i = 0, style = model->brushq1.data_lightstyleinfo;i < model->brushq1.num_lightstyles;i++, style++) { if (style->value != r_refdef.scene.lightstylevalue[style->style]) { int *list = style->surfacelist; style->value = r_refdef.scene.lightstylevalue[style->style]; for (j = 0;j < style->numsurfaces;j++) update[list[j]] = true; } } } flagsmask = skysurfaces ? MATERIALFLAG_SKY : MATERIALFLAG_WALL; if (debug) { R_DrawDebugModel(); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity return; } rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; rsurface.texture = NULL; rsurface.rtlight = NULL; numsurfacelist = 0; // add visible surfaces to draw list for (i = 0;i < model->nummodelsurfaces;i++) r_surfacelist[numsurfacelist++] = surfaces + model->sortedmodelsurfaces[i]; // don't do anything if there were no surfaces if (!numsurfacelist) { rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity return; } // update lightmaps if needed if (update) { int updated = 0; for (j = model->firstmodelsurface, endj = model->firstmodelsurface + model->nummodelsurfaces;j < endj;j++) { if (update[j]) { updated++; R_BuildLightMap(ent, surfaces + j); } } } R_QueueModelSurfaceList(ent, numsurfacelist, r_surfacelist, flagsmask, writedepth, depthonly, prepass); // add to stats if desired if (r_speeds.integer && !skysurfaces && !depthonly) { r_refdef.stats[r_stat_entities_surfaces] += numsurfacelist; for (j = 0;j < numsurfacelist;j++) r_refdef.stats[r_stat_entities_triangles] += r_surfacelist[j]->num_triangles; } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) { static texture_t texture; static msurface_t surface; const msurface_t *surfacelist = &surface; // fake enough texture and surface state to render this geometry texture.update_lastrenderframe = -1; // regenerate this texture texture.basematerialflags = materialflags | MATERIALFLAG_CUSTOMSURFACE | MATERIALFLAG_WALL; texture.basealpha = 1.0f; texture.currentskinframe = skinframe; texture.currenttexmatrix = *texmatrix; // requires MATERIALFLAG_CUSTOMSURFACE texture.offsetmapping = OFFSETMAPPING_OFF; texture.offsetscale = 1; texture.specularscalemod = 1; texture.specularpowermod = 1; texture.transparentsort = TRANSPARENTSORT_DISTANCE; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". surface.texture = &texture; surface.num_triangles = numtriangles; surface.num_firsttriangle = firsttriangle; surface.num_vertices = numvertices; surface.num_firstvertex = firstvertex; // now render it rsurface.texture = R_GetCurrentTexture(surface.texture); rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); } void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass) { static msurface_t surface; const msurface_t *surfacelist = &surface; // fake enough texture and surface state to render this geometry surface.texture = texture; surface.num_triangles = numtriangles; surface.num_firsttriangle = firsttriangle; surface.num_vertices = numvertices; surface.num_firstvertex = firstvertex; // now render it rsurface.texture = R_GetCurrentTexture(surface.texture); rsurface.lightmaptexture = NULL; rsurface.deluxemaptexture = NULL; rsurface.uselightmaptexture = false; R_DrawModelTextureSurfaceList(1, &surfacelist, writedepth, prepass); } darkplaces/portals.h0000664000175000017500000000121013067716222014014 0ustar kalevkalev #ifndef PORTALS_H #define PORTALS_H int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints); int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b); void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs); #endif darkplaces/makefile.inc0000664000175000017500000004335313067716220014443 0ustar kalevkalev# Invalid call detection CHECKLEVEL1 = @if [ "$(LEVEL)" != 1 ]; then $(MAKE) help; false; fi CHECKLEVEL2 = @if [ "$(LEVEL)" != 2 ]; then $(MAKE) help; false; fi # Choose the compiler you want to use CC?=gcc # athlon optimizations #CPUOPTIMIZATIONS?=-march=athlon # athlon xp optimizations #CPUOPTIMIZATIONS?=-march=athlon-xp # athlon 64 optimizations #CPUOPTIMIZATIONS?=-march=athlon64 -m32 # Pentium 3 optimizations #CPUOPTIMIZATIONS?=-march=pentium3 # Pentium 4 optimizations #CPUOPTIMIZATIONS?=-march=pentium4 # 686 (Pentium Pro/II) optimizations #CPUOPTIMIZATIONS?=-march=i686 # No specific CPU (386 compatible) #CPUOPTIMIZATIONS?= # Experimental #CPUOPTIMIZATIONS?=-fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fassociative-math -freciprocal-math -fno-signed-zeros -fno-trapping-math # Normal CPUOPTIMIZATIONS?=-fno-math-errno -ffinite-math-only -fno-rounding-math -fno-signaling-nans -fno-trapping-math # NOTE: *never* *ever* use the -ffast-math or -funsafe-math-optimizations flag SDL_CONFIG?=sdl2-config SDLCONFIG_UNIXCFLAGS?=`$(SDL_CONFIG) --cflags` SDLCONFIG_UNIXCFLAGS_X11?= SDLCONFIG_UNIXLIBS?=`$(SDL_CONFIG) --libs` SDLCONFIG_UNIXLIBS_X11?=-lX11 SDLCONFIG_UNIXSTATICLIBS?=`$(SDL_CONFIG) --static-libs` SDLCONFIG_UNIXSTATICLIBS_X11?=-lX11 SDLCONFIG_MACOSXCFLAGS=-I/Library/Frameworks/SDL2.framework/Headers -I$(HOME)/Library/Frameworks/SDL2.framework/Headers SDLCONFIG_MACOSXLIBS=-F$(HOME)/Library/Frameworks/ -framework SDL2 -framework Cocoa $(SDLCONFIG_MACOSXCFLAGS) SDLCONFIG_MACOSXSTATICLIBS=-F$(HOME)/Library/Frameworks/ -framework SDL2 -framework Cocoa $(SDLCONFIG_MACOSXCFLAGS) STRIP?=strip ###### Sound and audio CD ##### OBJ_SND_COMMON=snd_main.o snd_mem.o snd_mix.o snd_ogg.o snd_wav.o # No sound OBJ_SND_NULL=snd_null.o LIB_SND_NULL= # Open Sound System (Linux, FreeBSD and Solaris) OBJ_SND_OSS=$(OBJ_SND_COMMON) snd_oss.o LIB_SND_OSS= # Advanced Linux Sound Architecture (Linux) OBJ_SND_ALSA=$(OBJ_SND_COMMON) snd_alsa.o LIB_SND_ALSA=-lasound # Core Audio (Mac OS X) OBJ_SND_COREAUDIO=$(OBJ_SND_COMMON) snd_coreaudio.o LIB_SND_COREAUDIO=-framework CoreAudio # BSD / Sun audio API (NetBSD and OpenBSD) OBJ_SND_BSD=$(OBJ_SND_COMMON) snd_bsd.o LIB_SND_BSD= # DirectX and Win32 WAVE output (Win32) OBJ_SND_WIN=$(OBJ_SND_COMMON) snd_win.o LIB_SND_WIN= # Qantourisc's 3D Realtime Acoustic Lib (3D RAS) OBJ_SND_3DRAS=snd_3dras.o LIB_SND_3DRAS= # CD objects OBJ_CD_COMMON=cd_shared.o OBJ_NOCD=cd_null.o ###### Common objects and flags ##### # Common objects OBJ_COMMON= \ bih.o \ crypto.o \ cl_collision.o \ cl_demo.o \ cl_dyntexture.o \ cl_input.o \ cl_main.o \ cl_parse.o \ cl_particles.o \ cl_screen.o \ cl_video.o \ clvm_cmds.o \ cmd.o \ collision.o \ common.o \ console.o \ csprogs.o \ curves.o \ cvar.o \ dpsoftrast.o \ dpvsimpledecode.o \ filematch.o \ fractalnoise.o \ fs.o \ ft2.o \ utf8lib.o \ gl_backend.o \ gl_draw.o \ gl_rmain.o \ gl_rsurf.o \ gl_textures.o \ hmac.o \ host.o \ host_cmd.o \ image.o \ image_png.o \ jpeg.o \ keys.o \ lhnet.o \ libcurl.o \ mathlib.o \ matrixlib.o \ mdfour.o \ meshqueue.o \ mod_skeletal_animatevertices_sse.o \ mod_skeletal_animatevertices_generic.o \ model_alias.o \ model_brush.o \ model_shared.o \ model_sprite.o \ netconn.o \ palette.o \ polygon.o \ portals.o \ protocol.o \ prvm_cmds.o \ prvm_edict.o \ prvm_exec.o \ r_explosion.o \ r_lerpanim.o \ r_lightning.o \ r_modules.o \ r_shadow.o \ r_sky.o \ r_sprites.o \ sbar.o \ sv_demo.o \ sv_main.o \ sv_move.o \ sv_phys.o \ sv_user.o \ svbsp.o \ svvm_cmds.o \ sys_shared.o \ vid_shared.o \ view.o \ wad.o \ world.o \ zone.o OBJ_MENU= \ menu.o \ mvm_cmds.o # note that builddate.c is very intentionally not compiled to a .o before # being linked, because it should be recompiled every time an executable is # built to give the executable a proper date string OBJ_SV= builddate.c sys_linux.o vid_null.o thread_null.o $(OBJ_SND_NULL) $(OBJ_COMMON) OBJ_SDL= builddate.c sys_sdl.o vid_sdl.o thread_sdl.o $(OBJ_MENU) $(OBJ_SND_COMMON) snd_sdl.o $(OBJ_SDLCD) $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) # Compilation CFLAGS_COMMON=$(CFLAGS_MAKEDEP) $(CFLAGS_PRELOAD) $(CFLAGS_FS) $(CFLAGS_WARNINGS) $(CFLAGS_LIBZ) $(CFLAGS_LIBJPEG) $(CFLAGS_D3D) $(CFLAGS_NET) $(CFLAGS_SDL) -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES -I../../../ CFLAGS_CLIENT=-DCONFIG_MENU -DCONFIG_CD $(CFLAGS_VIDEO_CAPTURE) CFLAGS_SERVER= CFLAGS_DEBUG=-ggdb CFLAGS_PROFILE=-g -pg -ggdb -fprofile-arcs CFLAGS_RELEASE= CFLAGS_RELEASE_PROFILE=-fbranch-probabilities CFLAGS_SDL= ifeq ($(DP_SSE),1) CFLAGS_SSE=-msse CFLAGS_SSE2=-msse2 else CFLAGS_SSE= CFLAGS_SSE2= endif # ifeq ($(DP_SSE),1) OPTIM_DEBUG=$(CPUOPTIMIZATIONS) #OPTIM_RELEASE=-O2 -fno-strict-aliasing -ffast-math -funroll-loops $(CPUOPTIMIZATIONS) #OPTIM_RELEASE=-O2 -fno-strict-aliasing -fno-math-errno -fno-trapping-math -ffinite-math-only -fno-signaling-nans -fcx-limited-range -funroll-loops $(CPUOPTIMIZATIONS) #OPTIM_RELEASE=-O2 -fno-strict-aliasing -funroll-loops $(CPUOPTIMIZATIONS) #OPTIM_RELEASE=-O2 -fno-strict-aliasing $(CPUOPTIMIZATIONS) OPTIM_RELEASE=-O3 -fno-strict-aliasing $(CPUOPTIMIZATIONS) # NOTE: *never* *ever* use the -ffast-math or -funsafe-math-optimizations flag DO_CC=$(CC) $(CFLAGS) -c $< -o $@ # Link LDFLAGS_DEBUG=-g -ggdb $(OPTIM_DEBUG) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=debug LDFLAGS_PROFILE=-g -pg -fprofile-arcs $(OPTIM_RELEASE) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=profile LDFLAGS_RELEASE=$(OPTIM_RELEASE) -DSVNREVISION=`{ test -d .svn && svnversion; } || { test -d .git && git describe --always; } || echo -` -DBUILDTYPE=release ##### UNIX specific variables ##### OBJ_GLX= builddate.c sys_linux.o vid_glx.o thread_pthread.o keysym2ucs.o $(OBJ_MENU) $(OBJ_SOUND) $(OBJ_CD) $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) LDFLAGS_UNIXCOMMON=-lm $(LIB_ODE) $(LIB_Z) $(LIB_JPEG) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) LDFLAGS_UNIXCL=-L$(UNIX_X11LIBPATH) -lX11 -lXpm -lXext -lXxf86vm -pthread $(LIB_SOUND) LDFLAGS_UNIXCL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl LDFLAGS_UNIXSV_PRELOAD=-lz -ljpeg -lpng -lcurl LDFLAGS_UNIXSDL_PRELOAD=-lz -ljpeg -lpng -logg -ltheora -lvorbis -lvorbisenc -lvorbisfile -lcurl CFLAGS_UNIX_PRELOAD=-DPREFER_PRELOAD LDFLAGS_UNIXSDL=$(SDLCONFIG_LIBS) EXE_UNIXCL=darkplaces-glx EXE_UNIXSV=darkplaces-dedicated EXE_UNIXSDL=darkplaces-sdl EXE_UNIXCLNEXUIZ=nexuiz-glx EXE_UNIXSVNEXUIZ=nexuiz-dedicated EXE_UNIXSDLNEXUIZ=nexuiz-sdl CMD_UNIXRM=rm -rf CMD_UNIXCP=cp -f CMD_UNIXMKDIR=mkdir -p ##### Linux specific variables ##### # Link LDFLAGS_LINUXCL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl $(LDFLAGS_UNIXCL) LDFLAGS_LINUXSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl LDFLAGS_LINUXSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl $(LDFLAGS_UNIXSDL) ##### Mac OS X specific variables ##### # No CD support available OBJ_MACOSXCD=$(OBJ_NOCD) # Link LDFLAGS_MACOSXCL=$(LDFLAGS_UNIXCOMMON) -ldl -framework IOKit -framework Carbon $(LIB_SOUND) LDFLAGS_MACOSXSV=$(LDFLAGS_UNIXCOMMON) -ldl LDFLAGS_MACOSXSDL=$(LDFLAGS_UNIXCOMMON) -ldl -framework IOKit $(SDLCONFIG_STATICLIBS) ../../../SDLMain.m OBJ_AGL= builddate.c sys_linux.o vid_agl.o thread_null.o $(OBJ_MENU) $(OBJ_SOUND) $(OBJ_CD) $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) EXE_MACOSXCL=darkplaces-agl EXE_MACOSXCLNEXUIZ=nexuiz-agl ##### SunOS specific variables ##### # No CD support available OBJ_SUNOSCD=$(OBJ_NOCD) CFLAGS_SUNOS=-I/usr/lib/oss/include -DBSD_COMP -DSUNOS # Link LDFLAGS_SUNOSCL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl -R$(UNIX_X11LIBPATH) -L$(UNIX_X11LIBPATH) -lX11 -lXpm -lXext -lXxf86vm $(LIB_SOUND) LDFLAGS_SUNOSSV=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl LDFLAGS_SUNOSSDL=$(LDFLAGS_UNIXCOMMON) -lrt -ldl -lsocket -lnsl $(LDFLAGS_UNIXSDL) ##### BSD specific variables ##### # Link LDFLAGS_BSDCL=$(LDFLAGS_UNIXCOMMON) -lutil $(LDFLAGS_UNIXCL) LDFLAGS_BSDSV=$(LDFLAGS_UNIXCOMMON) LDFLAGS_BSDSDL=$(LDFLAGS_UNIXCOMMON) $(LDFLAGS_UNIXSDL) ##### Win32 specific variables ##### WINDRES ?= windres OBJ_WGL= builddate.c sys_win.o vid_wgl.o thread_null.o $(OBJ_MENU) $(OBJ_SND_WIN) $(OBJ_WINCD) $(OBJ_VIDEO_CAPTURE) $(OBJ_COMMON) # Link # see LDFLAGS_WINCOMMON in makefile LDFLAGS_WINCL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mwindows -lwinmm -luser32 -lgdi32 -ldxguid -ldinput -lcomctl32 -lws2_32 $(LDFLAGS_D3D) $(LIB_Z) $(LIB_JPEG) LDFLAGS_WINSV=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) -mconsole -lwinmm -lws2_32 $(LIB_Z) $(LIB_JPEG) LDFLAGS_WINSDL=$(LDFLAGS_WINCOMMON) $(LIB_CRYPTO) $(LIB_CRYPTO_RIJNDAEL) $(SDLCONFIG_LIBS) -lwinmm -lws2_32 $(LIB_Z) $(LIB_JPEG) EXE_WINCL=darkplaces.exe EXE_WINSV=darkplaces-dedicated.exe EXE_WINSDL=darkplaces-sdl.exe EXE_WINCLNEXUIZ=nexuiz.exe EXE_WINSVNEXUIZ=nexuiz-dedicated.exe EXE_WINSDLNEXUIZ=nexuiz-sdl.exe VPATH := ../../../ ##### Commands ##### .PHONY : clean clean-profile help \ debug profile release \ cl-debug cl-profile cl-release \ sv-debug sv-profile sv-release \ sdl-debug sdl-profile sdl-release help: @echo @echo "===== Choose one =====" @echo "* $(MAKE) clean : delete all files produced by the build except" @echo " profiling information" @echo "* $(MAKE) clean-profile : delete all files produced by the build, including" @echo " profiling informaiton" @echo "* $(MAKE) help : this help" @echo "* $(MAKE) debug : make client and server binaries (debug versions)" @echo "* $(MAKE) profile : make client and server binaries (profile versions)" @echo "* $(MAKE) release : make client and server binaries (release versions)" @echo "* $(MAKE) release-profile : make client and server binaries (release versions)" @echo " (with profiling optimizations) The profiled" @echo " version of the program must have been" @echo " previously compiled" @echo "* $(MAKE) nexuiz : make client and server binaries with nexuiz icon" @echo " (release versions)" @echo "* $(MAKE) cl-debug : make client (debug version)" @echo "* $(MAKE) cl-profile : make client (profile version)" @echo "* $(MAKE) cl-release-profile : make client (release profile version)" @echo "* $(MAKE) cl-release : make client (release version)" @echo "* $(MAKE) cl-nexuiz : make client with nexuiz icon (release version)" @echo "* $(MAKE) sv-debug : make dedicated server (debug version)" @echo "* $(MAKE) sv-profile : make dedicated server (profile version)" @echo "* $(MAKE) sv-release-profile : make dedicated server (release profile version)" @echo "* $(MAKE) sv-release : make dedicated server (release version)" @echo "* $(MAKE) sv-nexuiz : make dedicated server with nexuiz icon" @echo " (release version)" @echo "* $(MAKE) sdl-debug : make SDL client (debug version)" @echo "* $(MAKE) sdl-profile : make SDL client (profile version)" @echo "* $(MAKE) sdl-release-profile : make SDL client (release version)" @echo "* $(MAKE) sdl-release : make SDL client (release version)" @echo "* $(MAKE) sdl-nexuiz : make SDL client with nexuiz icon (release version)" @echo debug : $(MAKE) $(TARGETS_DEBUG) profile : $(MAKE) $(TARGETS_PROFILE) release : $(MAKE) $(TARGETS_RELEASE) release-profile : $(MAKE) $(TARGETS_RELEASE_PROFILE) nexuiz : $(MAKE) $(TARGETS_NEXUIZ) cl-debug : $(MAKE) bin-debug \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_CL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' LDFLAGS_COMMON='$(LDFLAGS_CL)' LEVEL=1 cl-profile : $(MAKE) bin-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_CL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' LDFLAGS_COMMON='$(LDFLAGS_CL)' LEVEL=1 cl-release : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_CL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' LDFLAGS_COMMON='$(LDFLAGS_CL)' LEVEL=1 cl-release-profile : $(MAKE) bin-release-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_CL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' LDFLAGS_COMMON='$(LDFLAGS_CL)' LEVEL=1 cl-nexuiz : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_CLNEXUIZ)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' LDFLAGS_COMMON='$(LDFLAGS_CL)' LEVEL=1 sv-debug : $(MAKE) bin-debug \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SV)' CFLAGS_FEATURES='$(CFLAGS_SERVER)' LDFLAGS_COMMON='$(LDFLAGS_SV)' LEVEL=1 sv-profile : $(MAKE) bin-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SV)' CFLAGS_FEATURES='$(CFLAGS_SERVER)' LDFLAGS_COMMON='$(LDFLAGS_SV)' LEVEL=1 sv-release : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SV)' CFLAGS_FEATURES='$(CFLAGS_SERVER)' LDFLAGS_COMMON='$(LDFLAGS_SV)' LEVEL=1 sv-release-profile : $(MAKE) bin-release-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SV)' CFLAGS_FEATURES='$(CFLAGS_SERVER)' LDFLAGS_COMMON='$(LDFLAGS_SV)' LEVEL=1 sv-nexuiz : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SVNEXUIZ)' CFLAGS_FEATURES='$(CFLAGS_SERVER)' LDFLAGS_COMMON='$(LDFLAGS_SV)' LEVEL=1 sdl-debug : $(MAKE) bin-debug \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SDL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1 sdl-profile : $(MAKE) bin-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SDL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1 sdl-release : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SDL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1 sdl-release-profile : $(MAKE) bin-release-profile \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SDL)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1 sdl-nexuiz : $(MAKE) bin-release \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ EXE='$(EXE_SDLNEXUIZ)' CFLAGS_FEATURES='$(CFLAGS_CLIENT)' CFLAGS_SDL='$(SDLCONFIG_CFLAGS)' LDFLAGS_COMMON='$(LDFLAGS_SDL)' LEVEL=1 bin-debug : $(CHECKLEVEL1) @echo @echo '========== $(EXE) (debug) ==========' $(MAKE) prepare BUILD_DIR=build-obj/debug/$(EXE) $(MAKE) -C build-obj/debug/$(EXE) $(EXE) \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ CFLAGS='$(CFLAGS_COMMON) $(CFLAGS_FEATURES) $(CFLAGS_EXTRA) $(CFLAGS_DEBUG) $(OPTIM_DEBUG)'\ LDFLAGS='$(LDFLAGS_DEBUG) $(LDFLAGS_COMMON)' LEVEL=2 bin-profile : $(CHECKLEVEL1) @echo @echo '========== $(EXE) (profile) ==========' $(MAKE) prepare BUILD_DIR=build-obj/profile/$(EXE) $(MAKE) -C build-obj/profile/$(EXE) $(EXE) \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ CFLAGS='$(CFLAGS_COMMON) $(CFLAGS_FEATURES) $(CFLAGS_EXTRA) $(CFLAGS_PROFILE) $(OPTIM_RELEASE)'\ LDFLAGS='$(LDFLAGS_PROFILE) $(LDFLAGS_COMMON)' LEVEL=2 bin-release : $(CHECKLEVEL1) @echo @echo '========== $(EXE) (release) ==========' $(MAKE) prepare BUILD_DIR=build-obj/release/$(EXE) $(MAKE) -C build-obj/release/$(EXE) $(EXE) \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ CFLAGS='$(CFLAGS_COMMON) $(CFLAGS_FEATURES) $(CFLAGS_EXTRA) $(CFLAGS_RELEASE) $(OPTIM_RELEASE)'\ LDFLAGS='$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)' LEVEL=2 $(STRIP) $(EXE) bin-release-profile : $(CHECKLEVEL1) @echo @echo '========== $(EXE) (release) ==========' $(MAKE) prepare BUILD_DIR=build-obj/release-profile/$(EXE) $(MAKE) -C build-obj/release-profile/$(EXE) $(EXE) \ DP_MAKE_TARGET=$(DP_MAKE_TARGET) DP_SOUND_API=$(DP_SOUND_API) \ CFLAGS='$(CFLAGS_COMMON) $(CFLAGS_FEATURES) $(CFLAGS_EXTRA) $(CFLAGS_RELEASE_PROFILE) $(OPTIM_RELEASE)'\ LDFLAGS='$(LDFLAGS_RELEASE) $(LDFLAGS_COMMON)' LEVEL=2 $(STRIP) $(EXE) prepare : $(CMD_MKDIR) $(BUILD_DIR) $(CMD_CP) makefile.inc $(BUILD_DIR)/ $(CMD_CP) $(MAKEFILE) $(BUILD_DIR)/ #this checks USEODE when compiling so it needs the ODE flags as well prvm_cmds.o: prvm_cmds.c $(CHECKLEVEL2) $(DO_CC) $(CFLAGS_ODE) world.o: world.c $(CHECKLEVEL2) $(DO_CC) $(CFLAGS_ODE) vid_glx.o: vid_glx.c $(CHECKLEVEL2) $(DO_CC) -I/usr/X11R6/include keysym2ucs.o: keysym2ucs.c $(CHECKLEVEL2) $(DO_CC) -I/usr/X11R6/include crypto.o: crypto.c $(CHECKLEVEL2) $(DO_CC) $(CFLAGS_CRYPTO) $(CFLAGS_CRYPTO_RIJNDAEL) mod_skeletal_animatevertices_sse.o: mod_skeletal_animatevertices_sse.c $(CHECKLEVEL2) $(DO_CC) $(CFLAGS_SSE) dpsoftrast.o: dpsoftrast.c $(CHECKLEVEL2) $(DO_CC) $(CFLAGS_SSE2) darkplaces.o: %.o : %.rc $(CHECKLEVEL2) $(WINDRES) -o $@ $< nexuiz.o: %.o : %.rc $(CHECKLEVEL2) $(WINDRES) -o $@ $< .c.o: $(CHECKLEVEL2) $(DO_CC) $(EXE_CL): $(OBJ_CL) $(OBJ_ICON) $(CHECKLEVEL2) $(DO_LD) $(EXE_SV): $(OBJ_SV) $(OBJ_ICON) $(CHECKLEVEL2) $(DO_LD) $(EXE_SDL): $(OBJ_SDL) $(OBJ_ICON) $(CHECKLEVEL2) $(DO_LD) $(EXE_CLNEXUIZ): $(OBJ_CL) $(OBJ_ICON_NEXUIZ) $(CHECKLEVEL2) $(DO_LD) $(EXE_SVNEXUIZ): $(OBJ_SV) $(OBJ_ICON_NEXUIZ) $(CHECKLEVEL2) $(DO_LD) $(EXE_SDLNEXUIZ): $(OBJ_SDL) $(OBJ_ICON_NEXUIZ) $(CHECKLEVEL2) $(DO_LD) clean: -$(CMD_RM) $(EXE_CL) -$(CMD_RM) $(EXE_SV) -$(CMD_RM) $(EXE_SDL) -$(CMD_RM) $(EXE_CLNEXUIZ) -$(CMD_RM) $(EXE_SVNEXUIZ) -$(CMD_RM) $(EXE_SDLNEXUIZ) -$(CMD_RM) *.o -$(CMD_RM) *.d -$(CMD_RM) build-obj/ clean-profile: clean -$(CMD_RM) *.gcda -$(CMD_RM) *.gcno darkplaces/snd_wav.c0000664000175000017500000001561313067716222014000 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "quakedef.h" #include "snd_main.h" #include "snd_wav.h" typedef struct wavinfo_s { int rate; int width; int channels; int loopstart; int samples; int dataofs; // chunk starts this many bytes from file start } wavinfo_t; static unsigned char *data_p; static unsigned char *iff_end; static unsigned char *last_chunk; static unsigned char *iff_data; static int iff_chunk_len; static short GetLittleShort(void) { short val; val = BuffLittleShort (data_p); data_p += 2; return val; } static int GetLittleLong(void) { int val = 0; val = BuffLittleLong (data_p); data_p += 4; return val; } static void FindNextChunk(const char *name) { while (1) { data_p=last_chunk; if (data_p >= iff_end) { // didn't find the chunk data_p = NULL; return; } data_p += 4; iff_chunk_len = GetLittleLong(); if (iff_chunk_len < 0) { data_p = NULL; return; } if (data_p + iff_chunk_len > iff_end) { // truncated chunk! data_p = NULL; return; } data_p -= 8; last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 ); if (!strncmp((const char *)data_p, name, 4)) return; } } static void FindChunk(const char *name) { last_chunk = iff_data; FindNextChunk (name); } /* static void DumpChunks(void) { char str[5]; str[4] = 0; data_p=iff_data; do { memcpy (str, data_p, 4); data_p += 4; iff_chunk_len = GetLittleLong(); Con_Printf("0x%x : %s (%d)\n", (int)(data_p - 4), str, iff_chunk_len); data_p += (iff_chunk_len + 1) & ~1; } while (data_p < iff_end); } */ /* ============ GetWavinfo ============ */ static wavinfo_t GetWavinfo (char *name, unsigned char *wav, int wavlength) { wavinfo_t info; int i; int format; int samples; memset (&info, 0, sizeof(info)); if (!wav) return info; iff_data = wav; iff_end = wav + wavlength; // find "RIFF" chunk FindChunk("RIFF"); if (!(data_p && !strncmp((const char *)data_p+8, "WAVE", 4))) { Con_Print("Missing RIFF/WAVE chunks\n"); return info; } // get "fmt " chunk iff_data = data_p + 12; //DumpChunks (); FindChunk("fmt "); if (!data_p) { Con_Print("Missing fmt chunk\n"); return info; } data_p += 8; format = GetLittleShort(); if (format != 1) { Con_Print("Microsoft PCM format only\n"); return info; } info.channels = GetLittleShort(); info.rate = GetLittleLong(); data_p += 4+2; info.width = GetLittleShort() / 8; // get cue chunk FindChunk("cue "); if (data_p) { data_p += 32; info.loopstart = GetLittleLong(); // if the next chunk is a LIST chunk, look for a cue length marker FindNextChunk ("LIST"); if (data_p) { if (!strncmp ((const char *)data_p + 28, "mark", 4)) { // this is not a proper parse, but it works with cooledit... data_p += 24; i = GetLittleLong (); // samples in loop info.samples = info.loopstart + i; } } } else info.loopstart = -1; // find data chunk FindChunk("data"); if (!data_p) { Con_Print("Missing data chunk\n"); return info; } data_p += 4; samples = GetLittleLong () / info.width / info.channels; if (info.samples) { if (samples < info.samples) { Con_Printf ("Sound %s has a bad loop length\n", name); info.samples = samples; } } else info.samples = samples; info.dataofs = data_p - wav; return info; } /* ==================== WAV_GetSamplesFloat ==================== */ static void WAV_GetSamplesFloat(channel_t *ch, sfx_t *sfx, int firstsampleframe, int numsampleframes, float *outsamplesfloat) { int i, len = numsampleframes * sfx->format.channels; if (sfx->format.width == 2) { const short *bufs = (const short *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; for (i = 0;i < len;i++) outsamplesfloat[i] = bufs[i] * (1.0f / 32768.0f); } else { const signed char *bufb = (const signed char *)sfx->fetcher_data + firstsampleframe * sfx->format.channels; for (i = 0;i < len;i++) outsamplesfloat[i] = bufb[i] * (1.0f / 128.0f); } } /* ==================== WAV_FreeSfx ==================== */ static void WAV_FreeSfx(sfx_t *sfx) { // free the loaded sound data Mem_Free(sfx->fetcher_data); } const snd_fetcher_t wav_fetcher = { WAV_GetSamplesFloat, NULL, WAV_FreeSfx }; /* ============== S_LoadWavFile ============== */ qboolean S_LoadWavFile (const char *filename, sfx_t *sfx) { fs_offset_t filesize; unsigned char *data; wavinfo_t info; int i, len; const unsigned char *inb; unsigned char *outb; // Already loaded? if (sfx->fetcher != NULL) return true; // Load the file data = FS_LoadFile(filename, snd_mempool, false, &filesize); if (!data) return false; // Don't try to load it if it's not a WAV file if (memcmp (data, "RIFF", 4) || memcmp (data + 8, "WAVE", 4)) { Mem_Free(data); return false; } if (developer_loading.integer >= 2) Con_Printf ("Loading WAV file \"%s\"\n", filename); info = GetWavinfo (sfx->name, data, (int)filesize); if (info.channels < 1 || info.channels > 2) // Stereo sounds are allowed (intended for music) { Con_Printf("%s has an unsupported number of channels (%i)\n",sfx->name, info.channels); Mem_Free(data); return false; } //if (info.channels == 2) // Log_Printf("stereosounds.log", "%s\n", sfx->name); sfx->format.speed = info.rate; sfx->format.width = info.width; sfx->format.channels = info.channels; sfx->fetcher = &wav_fetcher; sfx->fetcher_data = Mem_Alloc(snd_mempool, info.samples * sfx->format.width * sfx->format.channels); sfx->total_length = info.samples; sfx->memsize += filesize; len = info.samples * sfx->format.channels * sfx->format.width; inb = data + info.dataofs; outb = (unsigned char *)sfx->fetcher_data; if (info.width == 2) { if (mem_bigendian) { // we have to byteswap the data at load (better than doing it while mixing) for (i = 0;i < len;i += 2) { outb[i] = inb[i+1]; outb[i+1] = inb[i]; } } else { // we can just copy it straight memcpy(outb, inb, len); } } else { // convert unsigned byte sound data to signed bytes for quicker mixing for (i = 0;i < len;i++) outb[i] = inb[i] - 0x80; } if (info.loopstart < 0) sfx->loopstart = sfx->total_length; else sfx->loopstart = info.loopstart; sfx->loopstart = min(sfx->loopstart, sfx->total_length); sfx->flags &= ~SFXFLAG_STREAMED; return true; } darkplaces/darkplaces-dedicated-vs2013.vcxproj0000664000175000017500000004371013067716216020563 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {389AE334-D907-4069-90B3-F0551B3EFDE9} darkplacesdedicated Win32Proj darkplaces-dedicated-vs2013 Application v120 MultiByte true Application v120 MultiByte Application v120 MultiByte true Application v120 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/mdfour.c0000664000175000017500000001267113067716220013632 0ustar kalevkalev/* mdfour.c An implementation of MD4 designed for use in the samba SMB authentication protocol Copyright (C) 1997-1998 Andrew Tridgell This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #include "quakedef.h" #include /* XoXus: needed for memset call */ #include "mdfour.h" /* NOTE: This code makes no attempt to be fast! It assumes that a int is at least 32 bits long */ #define F(X,Y,Z) (((X)&(Y)) | ((~(X))&(Z))) #define G(X,Y,Z) (((X)&(Y)) | ((X)&(Z)) | ((Y)&(Z))) #define H(X,Y,Z) ((X)^(Y)^(Z)) #ifdef LARGE_INT32 #define lshift(x,s) ((((x)<<(s))&0xFFFFFFFF) | (((x)>>(32-(s)))&0xFFFFFFFF)) #else #define lshift(x,s) (((x)<<(s)) | ((x)>>(32-(s)))) #endif #define ROUND1(a,b,c,d,k,s) a = lshift(a + F(b,c,d) + X[k], s) #define ROUND2(a,b,c,d,k,s) a = lshift(a + G(b,c,d) + X[k] + 0x5A827999,s) #define ROUND3(a,b,c,d,k,s) a = lshift(a + H(b,c,d) + X[k] + 0x6ED9EBA1,s) /* this applies md4 to 64 byte chunks */ static void mdfour64(struct mdfour *md, uint32 *M) { int j; uint32 AA, BB, CC, DD; uint32 X[16]; uint32 A,B,C,D; for (j=0;j<16;j++) X[j] = M[j]; A = md->A; B = md->B; C = md->C; D = md->D; AA = A; BB = B; CC = C; DD = D; ROUND1(A,B,C,D, 0, 3); ROUND1(D,A,B,C, 1, 7); ROUND1(C,D,A,B, 2, 11); ROUND1(B,C,D,A, 3, 19); ROUND1(A,B,C,D, 4, 3); ROUND1(D,A,B,C, 5, 7); ROUND1(C,D,A,B, 6, 11); ROUND1(B,C,D,A, 7, 19); ROUND1(A,B,C,D, 8, 3); ROUND1(D,A,B,C, 9, 7); ROUND1(C,D,A,B, 10, 11); ROUND1(B,C,D,A, 11, 19); ROUND1(A,B,C,D, 12, 3); ROUND1(D,A,B,C, 13, 7); ROUND1(C,D,A,B, 14, 11); ROUND1(B,C,D,A, 15, 19); ROUND2(A,B,C,D, 0, 3); ROUND2(D,A,B,C, 4, 5); ROUND2(C,D,A,B, 8, 9); ROUND2(B,C,D,A, 12, 13); ROUND2(A,B,C,D, 1, 3); ROUND2(D,A,B,C, 5, 5); ROUND2(C,D,A,B, 9, 9); ROUND2(B,C,D,A, 13, 13); ROUND2(A,B,C,D, 2, 3); ROUND2(D,A,B,C, 6, 5); ROUND2(C,D,A,B, 10, 9); ROUND2(B,C,D,A, 14, 13); ROUND2(A,B,C,D, 3, 3); ROUND2(D,A,B,C, 7, 5); ROUND2(C,D,A,B, 11, 9); ROUND2(B,C,D,A, 15, 13); ROUND3(A,B,C,D, 0, 3); ROUND3(D,A,B,C, 8, 9); ROUND3(C,D,A,B, 4, 11); ROUND3(B,C,D,A, 12, 15); ROUND3(A,B,C,D, 2, 3); ROUND3(D,A,B,C, 10, 9); ROUND3(C,D,A,B, 6, 11); ROUND3(B,C,D,A, 14, 15); ROUND3(A,B,C,D, 1, 3); ROUND3(D,A,B,C, 9, 9); ROUND3(C,D,A,B, 5, 11); ROUND3(B,C,D,A, 13, 15); ROUND3(A,B,C,D, 3, 3); ROUND3(D,A,B,C, 11, 9); ROUND3(C,D,A,B, 7, 11); ROUND3(B,C,D,A, 15, 15); A += AA; B += BB; C += CC; D += DD; #ifdef LARGE_INT32 A &= 0xFFFFFFFF; B &= 0xFFFFFFFF; C &= 0xFFFFFFFF; D &= 0xFFFFFFFF; #endif for (j=0;j<16;j++) X[j] = 0; md->A = A; md->B = B; md->C = C; md->D = D; } static void copy64(uint32 *M, const unsigned char *in) { int i; for (i=0;i<16;i++) M[i] = (in[i*4+3]<<24) | (in[i*4+2]<<16) | (in[i*4+1]<<8) | (in[i*4+0]<<0); } static void copy4(unsigned char *out,uint32 x) { out[0] = x&0xFF; out[1] = (x>>8)&0xFF; out[2] = (x>>16)&0xFF; out[3] = (x>>24)&0xFF; } void mdfour_begin(struct mdfour *md) { md->A = 0x67452301; md->B = 0xefcdab89; md->C = 0x98badcfe; md->D = 0x10325476; md->totalN = 0; } static void mdfour_tail(struct mdfour *md, const unsigned char *in, int n) { unsigned char buf[128]; uint32 M[16]; uint32 b; md->totalN += n; b = md->totalN * 8; memset(buf, 0, 128); if (n) memcpy(buf, in, n); buf[n] = 0x80; if (n <= 55) { copy4(buf+56, b); copy64(M, buf); mdfour64(md, M); } else { copy4(buf+120, b); copy64(M, buf); mdfour64(md, M); copy64(M, buf+64); mdfour64(md, M); } } void mdfour_update(struct mdfour *md, const unsigned char *in, int n) { uint32 M[16]; // start of edit by Forest 'LordHavoc' Hale // commented out to prevent crashing when length is 0 // if (n == 0) mdfour_tail(in, n); // end of edit by Forest 'LordHavoc' Hale while (n >= 64) { copy64(M, in); mdfour64(md, M); in += 64; n -= 64; md->totalN += 64; } mdfour_tail(md, in, n); } void mdfour_result(struct mdfour *md, unsigned char *out) { copy4(out, md->A); copy4(out+4, md->B); copy4(out+8, md->C); copy4(out+12, md->D); } void mdfour(unsigned char *out, const unsigned char *in, int n) { struct mdfour md; mdfour_begin(&md); mdfour_update(&md, in, n); mdfour_result(&md, out); } /////////////////////////////////////////////////////////////// // MD4-based checksum utility functions // // Copyright (C) 2000 Jeff Teunissen // // Author: Jeff Teunissen // Date: 01 Jan 2000 unsigned Com_BlockChecksum (void *buffer, int length) { int digest[4]; unsigned val; mdfour ( (unsigned char *) digest, (unsigned char *) buffer, length ); val = digest[0] ^ digest[1] ^ digest[2] ^ digest[3]; return val; } void Com_BlockFullChecksum (void *buffer, int len, unsigned char *outbuf) { mdfour ( outbuf, (unsigned char *) buffer, len ); } darkplaces/model_shared.c0000664000175000017500000054575713067716222015006 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // models.c -- model loading and caching // models are the only shared resource between a client and server running // on the same machine. #include "quakedef.h" #include "image.h" #include "r_shadow.h" #include "polygon.h" cvar_t r_enableshadowvolumes = {CVAR_SAVE, "r_enableshadowvolumes", "1", "Enables use of Stencil Shadow Volume shadowing methods, saves some memory if turned off"}; cvar_t r_mipskins = {CVAR_SAVE, "r_mipskins", "0", "mipmaps model skins so they render faster in the distance and do not display noise artifacts, can cause discoloration of skins if they contain undesirable border colors"}; cvar_t r_mipnormalmaps = {CVAR_SAVE, "r_mipnormalmaps", "1", "mipmaps normalmaps (turning it off looks sharper but may have aliasing)"}; cvar_t mod_generatelightmaps_unitspersample = {CVAR_SAVE, "mod_generatelightmaps_unitspersample", "8", "lightmap resolution"}; cvar_t mod_generatelightmaps_borderpixels = {CVAR_SAVE, "mod_generatelightmaps_borderpixels", "2", "extra space around polygons to prevent sampling artifacts"}; cvar_t mod_generatelightmaps_texturesize = {CVAR_SAVE, "mod_generatelightmaps_texturesize", "1024", "size of lightmap textures"}; cvar_t mod_generatelightmaps_lightmapsamples = {CVAR_SAVE, "mod_generatelightmaps_lightmapsamples", "16", "number of shadow tests done per lightmap pixel"}; cvar_t mod_generatelightmaps_vertexsamples = {CVAR_SAVE, "mod_generatelightmaps_vertexsamples", "16", "number of shadow tests done per vertex"}; cvar_t mod_generatelightmaps_gridsamples = {CVAR_SAVE, "mod_generatelightmaps_gridsamples", "64", "number of shadow tests done per lightgrid cell"}; cvar_t mod_generatelightmaps_lightmapradius = {CVAR_SAVE, "mod_generatelightmaps_lightmapradius", "16", "sampling area around each lightmap pixel"}; cvar_t mod_generatelightmaps_vertexradius = {CVAR_SAVE, "mod_generatelightmaps_vertexradius", "16", "sampling area around each vertex"}; cvar_t mod_generatelightmaps_gridradius = {CVAR_SAVE, "mod_generatelightmaps_gridradius", "64", "sampling area around each lightgrid cell center"}; dp_model_t *loadmodel; static mempool_t *mod_mempool; static memexpandablearray_t models; static mempool_t* q3shaders_mem; typedef struct q3shader_hash_entry_s { q3shaderinfo_t shader; struct q3shader_hash_entry_s* chain; } q3shader_hash_entry_t; #define Q3SHADER_HASH_SIZE 1021 typedef struct q3shader_data_s { memexpandablearray_t hash_entries; q3shader_hash_entry_t hash[Q3SHADER_HASH_SIZE]; memexpandablearray_t char_ptrs; } q3shader_data_t; static q3shader_data_t* q3shader_data; static void mod_start(void) { int i, count; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; SCR_PushLoadingScreen(false, "Loading models", 1.0); count = 0; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') if (mod->used) ++count; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') if (mod->used) { SCR_PushLoadingScreen(true, mod->name, 1.0 / count); Mod_LoadModel(mod, true, false); SCR_PopLoadingScreen(false); } SCR_PopLoadingScreen(false); } static void mod_shutdown(void) { int i; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && (mod->loaded || mod->mempool)) Mod_UnloadModel(mod); Mod_FreeQ3Shaders(); Mod_Skeletal_FreeBuffers(); } static void mod_newmap(void) { msurface_t *surface; int i, j, k, surfacenum, ssize, tsize; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; for (i = 0;i < nummodels;i++) { if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool) { for (j = 0;j < mod->num_textures && mod->data_textures;j++) { for (k = 0;k < mod->data_textures[j].numskinframes;k++) R_SkinFrame_MarkUsed(mod->data_textures[j].skinframes[k]); for (k = 0;k < mod->data_textures[j].backgroundnumskinframes;k++) R_SkinFrame_MarkUsed(mod->data_textures[j].backgroundskinframes[k]); } if (mod->brush.solidskyskinframe) R_SkinFrame_MarkUsed(mod->brush.solidskyskinframe); if (mod->brush.alphaskyskinframe) R_SkinFrame_MarkUsed(mod->brush.alphaskyskinframe); } } if (!cl_stainmaps_clearonload.integer) return; for (i = 0;i < nummodels;i++) { if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->mempool && mod->data_surfaces) { for (surfacenum = 0, surface = mod->data_surfaces;surfacenum < mod->num_surfaces;surfacenum++, surface++) { if (surface->lightmapinfo && surface->lightmapinfo->stainsamples) { ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; memset(surface->lightmapinfo->stainsamples, 255, ssize * tsize * 3); mod->brushq1.lightmapupdateflags[surfacenum] = true; } } } } } /* =============== Mod_Init =============== */ static void Mod_Print(void); static void Mod_Precache (void); static void Mod_Decompile_f(void); static void Mod_GenerateLightmaps_f(void); void Mod_Init (void) { mod_mempool = Mem_AllocPool("modelinfo", 0, NULL); Mem_ExpandableArray_NewArray(&models, mod_mempool, sizeof(dp_model_t), 16); Mod_BrushInit(); Mod_AliasInit(); Mod_SpriteInit(); Cvar_RegisterVariable(&r_enableshadowvolumes); Cvar_RegisterVariable(&r_mipskins); Cvar_RegisterVariable(&r_mipnormalmaps); Cvar_RegisterVariable(&mod_generatelightmaps_unitspersample); Cvar_RegisterVariable(&mod_generatelightmaps_borderpixels); Cvar_RegisterVariable(&mod_generatelightmaps_texturesize); Cvar_RegisterVariable(&mod_generatelightmaps_lightmapsamples); Cvar_RegisterVariable(&mod_generatelightmaps_vertexsamples); Cvar_RegisterVariable(&mod_generatelightmaps_gridsamples); Cvar_RegisterVariable(&mod_generatelightmaps_lightmapradius); Cvar_RegisterVariable(&mod_generatelightmaps_vertexradius); Cvar_RegisterVariable(&mod_generatelightmaps_gridradius); Cmd_AddCommand ("modellist", Mod_Print, "prints a list of loaded models"); Cmd_AddCommand ("modelprecache", Mod_Precache, "load a model"); Cmd_AddCommand ("modeldecompile", Mod_Decompile_f, "exports a model in several formats for editing purposes"); Cmd_AddCommand ("mod_generatelightmaps", Mod_GenerateLightmaps_f, "rebuilds lighting on current worldmodel"); } void Mod_RenderInit(void) { R_RegisterModule("Models", mod_start, mod_shutdown, mod_newmap, NULL, NULL); } void Mod_UnloadModel (dp_model_t *mod) { char name[MAX_QPATH]; qboolean used; dp_model_t *parentmodel; if (developer_loading.integer) Con_Printf("unloading model %s\n", mod->name); strlcpy(name, mod->name, sizeof(name)); parentmodel = mod->brush.parentmodel; used = mod->used; if (mod->mempool) { if (mod->surfmesh.data_element3i_indexbuffer) R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3i_indexbuffer); mod->surfmesh.data_element3i_indexbuffer = NULL; if (mod->surfmesh.data_element3s_indexbuffer) R_Mesh_DestroyMeshBuffer(mod->surfmesh.data_element3s_indexbuffer); mod->surfmesh.data_element3s_indexbuffer = NULL; if (mod->surfmesh.vbo_vertexbuffer) R_Mesh_DestroyMeshBuffer(mod->surfmesh.vbo_vertexbuffer); mod->surfmesh.vbo_vertexbuffer = NULL; } // free textures/memory attached to the model R_FreeTexturePool(&mod->texturepool); Mem_FreePool(&mod->mempool); // clear the struct to make it available memset(mod, 0, sizeof(dp_model_t)); // restore the fields we want to preserve strlcpy(mod->name, name, sizeof(mod->name)); mod->brush.parentmodel = parentmodel; mod->used = used; mod->loaded = false; } static void R_Model_Null_Draw(entity_render_t *ent) { return; } typedef void (*mod_framegroupify_parsegroups_t) (unsigned int i, int start, int len, float fps, qboolean loop, const char *name, void *pass); static int Mod_FrameGroupify_ParseGroups(const char *buf, mod_framegroupify_parsegroups_t cb, void *pass) { const char *bufptr; int start, len; float fps; unsigned int i; qboolean loop; char name[64]; bufptr = buf; i = 0; while(bufptr) { // an anim scene! // REQUIRED: fetch start COM_ParseToken_Simple(&bufptr, true, false, true); if (!bufptr) break; // end of file if (!strcmp(com_token, "\n")) continue; // empty line start = atoi(com_token); // REQUIRED: fetch length COM_ParseToken_Simple(&bufptr, true, false, true); if (!bufptr || !strcmp(com_token, "\n")) { Con_Printf("framegroups file: missing number of frames\n"); continue; } len = atoi(com_token); // OPTIONAL args start COM_ParseToken_Simple(&bufptr, true, false, true); // OPTIONAL: fetch fps fps = 20; if (bufptr && strcmp(com_token, "\n")) { fps = atof(com_token); COM_ParseToken_Simple(&bufptr, true, false, true); } // OPTIONAL: fetch loopflag loop = true; if (bufptr && strcmp(com_token, "\n")) { loop = (atoi(com_token) != 0); COM_ParseToken_Simple(&bufptr, true, false, true); } // OPTIONAL: fetch name name[0] = 0; if (bufptr && strcmp(com_token, "\n")) { strlcpy(name, com_token, sizeof(name)); COM_ParseToken_Simple(&bufptr, true, false, true); } // OPTIONAL: remaining unsupported tokens (eat them) while (bufptr && strcmp(com_token, "\n")) COM_ParseToken_Simple(&bufptr, true, false, true); //Con_Printf("data: %d %d %d %f %d (%s)\n", i, start, len, fps, loop, name); if(cb) cb(i, start, len, fps, loop, (name[0] ? name : NULL), pass); ++i; } return i; } static void Mod_FrameGroupify_ParseGroups_Store (unsigned int i, int start, int len, float fps, qboolean loop, const char *name, void *pass) { dp_model_t *mod = (dp_model_t *) pass; animscene_t *anim = &mod->animscenes[i]; if(name) strlcpy(anim->name, name, sizeof(anim[i].name)); else dpsnprintf(anim->name, sizeof(anim[i].name), "groupified_%d_anim", i); anim->firstframe = bound(0, start, mod->num_poses - 1); anim->framecount = bound(1, len, mod->num_poses - anim->firstframe); anim->framerate = max(1, fps); anim->loop = !!loop; //Con_Printf("frame group %d is %d %d %f %d\n", i, start, len, fps, loop); } static void Mod_FrameGroupify(dp_model_t *mod, const char *buf) { unsigned int cnt; // 0. count cnt = Mod_FrameGroupify_ParseGroups(buf, NULL, NULL); if(!cnt) { Con_Printf("no scene found in framegroups file, aborting\n"); return; } mod->numframes = cnt; // 1. reallocate // (we do not free the previous animscenes, but model unloading will free the pool owning them, so it's okay) mod->animscenes = (animscene_t *) Mem_Alloc(mod->mempool, sizeof(animscene_t) * mod->numframes); // 2. parse Mod_FrameGroupify_ParseGroups(buf, Mod_FrameGroupify_ParseGroups_Store, mod); } static void Mod_FindPotentialDeforms(dp_model_t *mod) { int i, j; texture_t *texture; mod->wantnormals = false; mod->wanttangents = false; for (i = 0;i < mod->num_textures;i++) { texture = mod->data_textures + i; if (texture->tcgen.tcgen == Q3TCGEN_ENVIRONMENT) mod->wantnormals = true; for (j = 0;j < Q3MAXDEFORMS;j++) { if (texture->deforms[j].deform == Q3DEFORM_AUTOSPRITE) { mod->wanttangents = true; mod->wantnormals = true; break; } if (texture->deforms[j].deform != Q3DEFORM_NONE) mod->wantnormals = true; } } } /* ================== Mod_LoadModel Loads a model ================== */ dp_model_t *Mod_LoadModel(dp_model_t *mod, qboolean crash, qboolean checkdisk) { int num; unsigned int crc; void *buf; fs_offset_t filesize = 0; char vabuf[1024]; mod->used = true; if (mod->name[0] == '*') // submodel return mod; if (!strcmp(mod->name, "null")) { if(mod->loaded) return mod; if (mod->loaded || mod->mempool) Mod_UnloadModel(mod); if (developer_loading.integer) Con_Printf("loading model %s\n", mod->name); mod->used = true; mod->crc = (unsigned int)-1; mod->loaded = false; VectorClear(mod->normalmins); VectorClear(mod->normalmaxs); VectorClear(mod->yawmins); VectorClear(mod->yawmaxs); VectorClear(mod->rotatedmins); VectorClear(mod->rotatedmaxs); mod->modeldatatypestring = "null"; mod->type = mod_null; mod->Draw = R_Model_Null_Draw; mod->numframes = 2; mod->numskins = 1; // no fatal errors occurred, so this model is ready to use. mod->loaded = true; return mod; } crc = 0; buf = NULL; // even if the model is loaded it still may need reloading... // if it is not loaded or checkdisk is true we need to calculate the crc if (!mod->loaded || checkdisk) { if (checkdisk && mod->loaded) Con_DPrintf("checking model %s\n", mod->name); buf = FS_LoadFile (mod->name, tempmempool, false, &filesize); if (buf) { crc = CRC_Block((unsigned char *)buf, filesize); // we need to reload the model if the crc does not match if (mod->crc != crc) mod->loaded = false; } } // if the model is already loaded and checks passed, just return if (mod->loaded) { if (buf) Mem_Free(buf); return mod; } if (developer_loading.integer) Con_Printf("loading model %s\n", mod->name); SCR_PushLoadingScreen(true, mod->name, 1); // LordHavoc: unload the existing model in this slot (if there is one) if (mod->loaded || mod->mempool) Mod_UnloadModel(mod); // load the model mod->used = true; mod->crc = crc; // errors can prevent the corresponding mod->loaded = true; mod->loaded = false; // default lightmap scale mod->lightmapscale = 1; // default model radius and bounding box (mainly for missing models) mod->radius = 16; VectorSet(mod->normalmins, -mod->radius, -mod->radius, -mod->radius); VectorSet(mod->normalmaxs, mod->radius, mod->radius, mod->radius); VectorSet(mod->yawmins, -mod->radius, -mod->radius, -mod->radius); VectorSet(mod->yawmaxs, mod->radius, mod->radius, mod->radius); VectorSet(mod->rotatedmins, -mod->radius, -mod->radius, -mod->radius); VectorSet(mod->rotatedmaxs, mod->radius, mod->radius, mod->radius); if (!q3shaders_mem) { // load q3 shaders for the first time, or after a level change Mod_LoadQ3Shaders(); } if (buf) { char *bufend = (char *)buf + filesize; // all models use memory, so allocate a memory pool mod->mempool = Mem_AllocPool(mod->name, 0, NULL); num = LittleLong(*((int *)buf)); // call the apropriate loader loadmodel = mod; if (!strcasecmp(FS_FileExtension(mod->name), "obj")) Mod_OBJ_Load(mod, buf, bufend); else if (!memcmp(buf, "IDPO", 4)) Mod_IDP0_Load(mod, buf, bufend); else if (!memcmp(buf, "IDP2", 4)) Mod_IDP2_Load(mod, buf, bufend); else if (!memcmp(buf, "IDP3", 4)) Mod_IDP3_Load(mod, buf, bufend); else if (!memcmp(buf, "IDSP", 4)) Mod_IDSP_Load(mod, buf, bufend); else if (!memcmp(buf, "IDS2", 4)) Mod_IDS2_Load(mod, buf, bufend); else if (!memcmp(buf, "IBSP", 4)) Mod_IBSP_Load(mod, buf, bufend); else if (!memcmp(buf, "ZYMOTICMODEL", 12)) Mod_ZYMOTICMODEL_Load(mod, buf, bufend); else if (!memcmp(buf, "DARKPLACESMODEL", 16)) Mod_DARKPLACESMODEL_Load(mod, buf, bufend); else if (!memcmp(buf, "ACTRHEAD", 8)) Mod_PSKMODEL_Load(mod, buf, bufend); else if (!memcmp(buf, "INTERQUAKEMODEL", 16)) Mod_INTERQUAKEMODEL_Load(mod, buf, bufend); else if (strlen(mod->name) >= 4 && !strcmp(mod->name + strlen(mod->name) - 4, ".map")) Mod_MAP_Load(mod, buf, bufend); else if (num == BSPVERSION || num == 30 || !memcmp(buf, "BSP2", 4) || !memcmp(buf, "2PSB", 4)) Mod_Q1BSP_Load(mod, buf, bufend); else Con_Printf("Mod_LoadModel: model \"%s\" is of unknown/unsupported type\n", mod->name); Mem_Free(buf); Mod_FindPotentialDeforms(mod); buf = FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.framegroups", mod->name), tempmempool, false, &filesize); if(buf) { Mod_FrameGroupify(mod, (const char *)buf); Mem_Free(buf); } Mod_BuildVBOs(); } else if (crash) { // LordHavoc: Sys_Error was *ANNOYING* Con_Printf ("Mod_LoadModel: %s not found\n", mod->name); } // no fatal errors occurred, so this model is ready to use. mod->loaded = true; SCR_PopLoadingScreen(false); return mod; } void Mod_ClearUsed(void) { int i; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0]) mod->used = false; } void Mod_PurgeUnused(void) { int i; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; for (i = 0;i < nummodels;i++) { if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !mod->used) { Mod_UnloadModel(mod); Mem_ExpandableArray_FreeRecord(&models, mod); } } } /* ================== Mod_FindName ================== */ dp_model_t *Mod_FindName(const char *name, const char *parentname) { int i; int nummodels; dp_model_t *mod; if (!parentname) parentname = ""; // if we're not dedicatd, the renderer calls will crash without video Host_StartVideo(); nummodels = (int)Mem_ExpandableArray_IndexRange(&models); if (!name[0]) Host_Error ("Mod_ForName: empty name"); // search the currently loaded models for (i = 0;i < nummodels;i++) { if ((mod = (dp_model_t*) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && !strcmp(mod->name, name) && ((!mod->brush.parentmodel && !parentname[0]) || (mod->brush.parentmodel && parentname[0] && !strcmp(mod->brush.parentmodel->name, parentname)))) { mod->used = true; return mod; } } // no match found, create a new one mod = (dp_model_t *) Mem_ExpandableArray_AllocRecord(&models); strlcpy(mod->name, name, sizeof(mod->name)); if (parentname[0]) mod->brush.parentmodel = Mod_FindName(parentname, NULL); else mod->brush.parentmodel = NULL; mod->loaded = false; mod->used = true; return mod; } /* ================== Mod_ForName Loads in a model for the given name ================== */ dp_model_t *Mod_ForName(const char *name, qboolean crash, qboolean checkdisk, const char *parentname) { dp_model_t *model; model = Mod_FindName(name, parentname); if (!model->loaded || checkdisk) Mod_LoadModel(model, crash, checkdisk); return model; } /* ================== Mod_Reload Reloads all models if they have changed ================== */ void Mod_Reload(void) { int i, count; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; SCR_PushLoadingScreen(false, "Reloading models", 1.0); count = 0; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) ++count; for (i = 0;i < nummodels;i++) if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*' && mod->used) { SCR_PushLoadingScreen(true, mod->name, 1.0 / count); Mod_LoadModel(mod, true, true); SCR_PopLoadingScreen(false); } SCR_PopLoadingScreen(false); } unsigned char *mod_base; //============================================================================= /* ================ Mod_Print ================ */ static void Mod_Print(void) { int i; int nummodels = (int)Mem_ExpandableArray_IndexRange(&models); dp_model_t *mod; Con_Print("Loaded models:\n"); for (i = 0;i < nummodels;i++) { if ((mod = (dp_model_t *) Mem_ExpandableArray_RecordAtIndex(&models, i)) && mod->name[0] && mod->name[0] != '*') { if (mod->brush.numsubmodels) Con_Printf("%4iK %s (%i submodels)\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name, mod->brush.numsubmodels); else Con_Printf("%4iK %s\n", mod->mempool ? (int)((mod->mempool->totalsize + 1023) / 1024) : 0, mod->name); } } } /* ================ Mod_Precache ================ */ static void Mod_Precache(void) { if (Cmd_Argc() == 2) Mod_ForName(Cmd_Argv(1), false, true, Cmd_Argv(1)[0] == '*' ? cl.model_name[1] : NULL); else Con_Print("usage: modelprecache \n"); } int Mod_BuildVertexRemapTableFromElements(int numelements, const int *elements, int numvertices, int *remapvertices) { int i, count; unsigned char *used; used = (unsigned char *)Mem_Alloc(tempmempool, numvertices); memset(used, 0, numvertices); for (i = 0;i < numelements;i++) used[elements[i]] = 1; for (i = 0, count = 0;i < numvertices;i++) remapvertices[i] = used[i] ? count++ : -1; Mem_Free(used); return count; } #if 1 // fast way, using an edge hash #define TRIANGLEEDGEHASH 8192 void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) { int i, j, p, e1, e2, *n, hashindex, count, match; const int *e; typedef struct edgehashentry_s { struct edgehashentry_s *next; int triangle; int element[2]; } edgehashentry_t; static edgehashentry_t **edgehash; edgehashentry_t *edgehashentries, *hash; if (!numtriangles) return; edgehash = (edgehashentry_t **)Mem_Alloc(tempmempool, TRIANGLEEDGEHASH * sizeof(*edgehash)); // if there are too many triangles for the stack array, allocate larger buffer edgehashentries = (edgehashentry_t *)Mem_Alloc(tempmempool, numtriangles * 3 * sizeof(edgehashentry_t)); // find neighboring triangles for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) { for (j = 0, p = 2;j < 3;p = j, j++) { e1 = e[p]; e2 = e[j]; // this hash index works for both forward and backward edges hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; hash = edgehashentries + i * 3 + j; hash->next = edgehash[hashindex]; edgehash[hashindex] = hash; hash->triangle = i; hash->element[0] = e1; hash->element[1] = e2; } } for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) { for (j = 0, p = 2;j < 3;p = j, j++) { e1 = e[p]; e2 = e[j]; // this hash index works for both forward and backward edges hashindex = (unsigned int)(e1 + e2) % TRIANGLEEDGEHASH; count = 0; match = -1; for (hash = edgehash[hashindex];hash;hash = hash->next) { if (hash->element[0] == e2 && hash->element[1] == e1) { if (hash->triangle != i) match = hash->triangle; count++; } else if ((hash->element[0] == e1 && hash->element[1] == e2)) count++; } // detect edges shared by three triangles and make them seams if (count > 2) match = -1; n[p] = match; } // also send a keepalive here (this can take a while too!) CL_KeepaliveMessage(false); } // free the allocated buffer Mem_Free(edgehashentries); Mem_Free(edgehash); } #else // very slow but simple way static int Mod_FindTriangleWithEdge(const int *elements, int numtriangles, int start, int end, int ignore) { int i, match, count; count = 0; match = -1; for (i = 0;i < numtriangles;i++, elements += 3) { if ((elements[0] == start && elements[1] == end) || (elements[1] == start && elements[2] == end) || (elements[2] == start && elements[0] == end)) { if (i != ignore) match = i; count++; } else if ((elements[1] == start && elements[0] == end) || (elements[2] == start && elements[1] == end) || (elements[0] == start && elements[2] == end)) count++; } // detect edges shared by three triangles and make them seams if (count > 2) match = -1; return match; } void Mod_BuildTriangleNeighbors(int *neighbors, const int *elements, int numtriangles) { int i, *n; const int *e; for (i = 0, e = elements, n = neighbors;i < numtriangles;i++, e += 3, n += 3) { n[0] = Mod_FindTriangleWithEdge(elements, numtriangles, e[1], e[0], i); n[1] = Mod_FindTriangleWithEdge(elements, numtriangles, e[2], e[1], i); n[2] = Mod_FindTriangleWithEdge(elements, numtriangles, e[0], e[2], i); } } #endif void Mod_ValidateElements(int *elements, int numtriangles, int firstvertex, int numverts, const char *filename, int fileline) { int i, warned = false, endvertex = firstvertex + numverts; for (i = 0;i < numtriangles * 3;i++) { if (elements[i] < firstvertex || elements[i] >= endvertex) { if (!warned) { warned = true; Con_Printf("Mod_ValidateElements: out of bounds elements detected at %s:%d\n", filename, fileline); } elements[i] = firstvertex; } } } // warning: this is an expensive function! void Mod_BuildNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const int *elements, float *normal3f, qboolean areaweighting) { int i, j; const int *element; float *vectorNormal; float areaNormal[3]; // clear the vectors memset(normal3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); // process each vertex of each triangle and accumulate the results // use area-averaging, to make triangles with a big area have a bigger // weighting on the vertex normal than triangles with a small area // to do so, just add the 'normals' together (the bigger the area // the greater the length of the normal is element = elements; for (i = 0; i < numtriangles; i++, element += 3) { TriangleNormal( vertex3f + element[0] * 3, vertex3f + element[1] * 3, vertex3f + element[2] * 3, areaNormal ); if (!areaweighting) VectorNormalize(areaNormal); for (j = 0;j < 3;j++) { vectorNormal = normal3f + element[j] * 3; vectorNormal[0] += areaNormal[0]; vectorNormal[1] += areaNormal[1]; vectorNormal[2] += areaNormal[2]; } } // and just normalize the accumulated vertex normal in the end vectorNormal = normal3f + 3 * firstvertex; for (i = 0; i < numvertices; i++, vectorNormal += 3) VectorNormalize(vectorNormal); } #if 0 static void Mod_BuildBumpVectors(const float *v0, const float *v1, const float *v2, const float *tc0, const float *tc1, const float *tc2, float *svector3f, float *tvector3f, float *normal3f) { float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates // 6 multiply, 9 subtract VectorSubtract(v1, v0, v10); VectorSubtract(v2, v0, v20); normal3f[0] = v20[1] * v10[2] - v20[2] * v10[1]; normal3f[1] = v20[2] * v10[0] - v20[0] * v10[2]; normal3f[2] = v20[0] * v10[1] - v20[1] * v10[0]; // 12 multiply, 10 subtract tc10[1] = tc1[1] - tc0[1]; tc20[1] = tc2[1] - tc0[1]; svector3f[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; svector3f[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; svector3f[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; tc10[0] = tc1[0] - tc0[0]; tc20[0] = tc2[0] - tc0[0]; tvector3f[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; tvector3f[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; tvector3f[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; // 12 multiply, 4 add, 6 subtract f = DotProduct(svector3f, normal3f); svector3f[0] -= f * normal3f[0]; svector3f[1] -= f * normal3f[1]; svector3f[2] -= f * normal3f[2]; f = DotProduct(tvector3f, normal3f); tvector3f[0] -= f * normal3f[0]; tvector3f[1] -= f * normal3f[1]; tvector3f[2] -= f * normal3f[2]; // if texture is mapped the wrong way (counterclockwise), the tangents // have to be flipped, this is detected by calculating a normal from the // two tangents, and seeing if it is opposite the surface normal // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates CrossProduct(tvector3f, svector3f, tangentcross); if (DotProduct(tangentcross, normal3f) < 0) { VectorNegate(svector3f, svector3f); VectorNegate(tvector3f, tvector3f); } } #endif // warning: this is a very expensive function! void Mod_BuildTextureVectorsFromNormals(int firstvertex, int numvertices, int numtriangles, const float *vertex3f, const float *texcoord2f, const float *normal3f, const int *elements, float *svector3f, float *tvector3f, qboolean areaweighting) { int i, tnum; float sdir[3], tdir[3], normal[3], *svec, *tvec; const float *v0, *v1, *v2, *tc0, *tc1, *tc2, *n; float f, tangentcross[3], v10[3], v20[3], tc10[2], tc20[2]; const int *e; // clear the vectors memset(svector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); memset(tvector3f + 3 * firstvertex, 0, numvertices * sizeof(float[3])); // process each vertex of each triangle and accumulate the results for (tnum = 0, e = elements;tnum < numtriangles;tnum++, e += 3) { v0 = vertex3f + e[0] * 3; v1 = vertex3f + e[1] * 3; v2 = vertex3f + e[2] * 3; tc0 = texcoord2f + e[0] * 2; tc1 = texcoord2f + e[1] * 2; tc2 = texcoord2f + e[2] * 2; // 79 add/sub/negate/multiply (1 cycle), 1 compare (3 cycle?), total cycles not counting load/store/exchange roughly 82 cycles // 6 add, 28 subtract, 39 multiply, 1 compare, 50% chance of 6 negates // calculate the edge directions and surface normal // 6 multiply, 9 subtract VectorSubtract(v1, v0, v10); VectorSubtract(v2, v0, v20); normal[0] = v20[1] * v10[2] - v20[2] * v10[1]; normal[1] = v20[2] * v10[0] - v20[0] * v10[2]; normal[2] = v20[0] * v10[1] - v20[1] * v10[0]; // calculate the tangents // 12 multiply, 10 subtract tc10[1] = tc1[1] - tc0[1]; tc20[1] = tc2[1] - tc0[1]; sdir[0] = tc10[1] * v20[0] - tc20[1] * v10[0]; sdir[1] = tc10[1] * v20[1] - tc20[1] * v10[1]; sdir[2] = tc10[1] * v20[2] - tc20[1] * v10[2]; tc10[0] = tc1[0] - tc0[0]; tc20[0] = tc2[0] - tc0[0]; tdir[0] = tc10[0] * v20[0] - tc20[0] * v10[0]; tdir[1] = tc10[0] * v20[1] - tc20[0] * v10[1]; tdir[2] = tc10[0] * v20[2] - tc20[0] * v10[2]; // if texture is mapped the wrong way (counterclockwise), the tangents // have to be flipped, this is detected by calculating a normal from the // two tangents, and seeing if it is opposite the surface normal // 9 multiply, 2 add, 3 subtract, 1 compare, 50% chance of: 6 negates CrossProduct(tdir, sdir, tangentcross); if (DotProduct(tangentcross, normal) < 0) { VectorNegate(sdir, sdir); VectorNegate(tdir, tdir); } if (!areaweighting) { VectorNormalize(sdir); VectorNormalize(tdir); } for (i = 0;i < 3;i++) { VectorAdd(svector3f + e[i]*3, sdir, svector3f + e[i]*3); VectorAdd(tvector3f + e[i]*3, tdir, tvector3f + e[i]*3); } } // make the tangents completely perpendicular to the surface normal, and // then normalize them // 16 assignments, 2 divide, 2 sqrt, 2 negates, 14 adds, 24 multiplies for (i = 0, svec = svector3f + 3 * firstvertex, tvec = tvector3f + 3 * firstvertex, n = normal3f + 3 * firstvertex;i < numvertices;i++, svec += 3, tvec += 3, n += 3) { f = -DotProduct(svec, n); VectorMA(svec, f, n, svec); VectorNormalize(svec); f = -DotProduct(tvec, n); VectorMA(tvec, f, n, tvec); VectorNormalize(tvec); } } void Mod_AllocSurfMesh(mempool_t *mempool, int numvertices, int numtriangles, qboolean lightmapoffsets, qboolean vertexcolors, qboolean neighbors) { unsigned char *data; data = (unsigned char *)Mem_Alloc(mempool, numvertices * (3 + 3 + 3 + 3 + 2 + 2 + (vertexcolors ? 4 : 0)) * sizeof(float) + numvertices * (lightmapoffsets ? 1 : 0) * sizeof(int) + numtriangles * (3 + (neighbors ? 3 : 0)) * sizeof(int) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0)); loadmodel->surfmesh.num_vertices = numvertices; loadmodel->surfmesh.num_triangles = numtriangles; if (loadmodel->surfmesh.num_vertices) { loadmodel->surfmesh.data_vertex3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; loadmodel->surfmesh.data_svector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; loadmodel->surfmesh.data_tvector3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; loadmodel->surfmesh.data_normal3f = (float *)data, data += sizeof(float[3]) * loadmodel->surfmesh.num_vertices; loadmodel->surfmesh.data_texcoordtexture2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; loadmodel->surfmesh.data_texcoordlightmap2f = (float *)data, data += sizeof(float[2]) * loadmodel->surfmesh.num_vertices; if (vertexcolors) loadmodel->surfmesh.data_lightmapcolor4f = (float *)data, data += sizeof(float[4]) * loadmodel->surfmesh.num_vertices; if (lightmapoffsets) loadmodel->surfmesh.data_lightmapoffsets = (int *)data, data += sizeof(int) * loadmodel->surfmesh.num_vertices; } if (loadmodel->surfmesh.num_triangles) { loadmodel->surfmesh.data_element3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; if (neighbors) loadmodel->surfmesh.data_neighbor3i = (int *)data, data += sizeof(int[3]) * loadmodel->surfmesh.num_triangles; if (loadmodel->surfmesh.num_vertices <= 65536) loadmodel->surfmesh.data_element3s = (unsigned short *)data, data += sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles; } } shadowmesh_t *Mod_ShadowMesh_Alloc(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) { shadowmesh_t *newmesh; unsigned char *data; int size; size = sizeof(shadowmesh_t); size += maxverts * sizeof(float[3]); if (light) size += maxverts * sizeof(float[11]); size += maxtriangles * sizeof(int[3]); if (maxverts <= 65536) size += maxtriangles * sizeof(unsigned short[3]); if (neighbors) size += maxtriangles * sizeof(int[3]); if (expandable) size += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *) + maxverts * sizeof(shadowmeshvertexhash_t); data = (unsigned char *)Mem_Alloc(mempool, size); newmesh = (shadowmesh_t *)data;data += sizeof(*newmesh); newmesh->map_diffuse = map_diffuse; newmesh->map_specular = map_specular; newmesh->map_normal = map_normal; newmesh->maxverts = maxverts; newmesh->maxtriangles = maxtriangles; newmesh->numverts = 0; newmesh->numtriangles = 0; memset(newmesh->sideoffsets, 0, sizeof(newmesh->sideoffsets)); memset(newmesh->sidetotals, 0, sizeof(newmesh->sidetotals)); newmesh->vertex3f = (float *)data;data += maxverts * sizeof(float[3]); if (light) { newmesh->svector3f = (float *)data;data += maxverts * sizeof(float[3]); newmesh->tvector3f = (float *)data;data += maxverts * sizeof(float[3]); newmesh->normal3f = (float *)data;data += maxverts * sizeof(float[3]); newmesh->texcoord2f = (float *)data;data += maxverts * sizeof(float[2]); } newmesh->element3i = (int *)data;data += maxtriangles * sizeof(int[3]); if (neighbors) { newmesh->neighbor3i = (int *)data;data += maxtriangles * sizeof(int[3]); } if (expandable) { newmesh->vertexhashtable = (shadowmeshvertexhash_t **)data;data += SHADOWMESHVERTEXHASH * sizeof(shadowmeshvertexhash_t *); newmesh->vertexhashentries = (shadowmeshvertexhash_t *)data;data += maxverts * sizeof(shadowmeshvertexhash_t); } if (maxverts <= 65536) newmesh->element3s = (unsigned short *)data;data += maxtriangles * sizeof(unsigned short[3]); return newmesh; } shadowmesh_t *Mod_ShadowMesh_ReAlloc(mempool_t *mempool, shadowmesh_t *oldmesh, int light, int neighbors) { shadowmesh_t *newmesh; newmesh = Mod_ShadowMesh_Alloc(mempool, oldmesh->numverts, oldmesh->numtriangles, oldmesh->map_diffuse, oldmesh->map_specular, oldmesh->map_normal, light, neighbors, false); newmesh->numverts = oldmesh->numverts; newmesh->numtriangles = oldmesh->numtriangles; memcpy(newmesh->sideoffsets, oldmesh->sideoffsets, sizeof(oldmesh->sideoffsets)); memcpy(newmesh->sidetotals, oldmesh->sidetotals, sizeof(oldmesh->sidetotals)); memcpy(newmesh->vertex3f, oldmesh->vertex3f, oldmesh->numverts * sizeof(float[3])); if (newmesh->svector3f && oldmesh->svector3f) { memcpy(newmesh->svector3f, oldmesh->svector3f, oldmesh->numverts * sizeof(float[3])); memcpy(newmesh->tvector3f, oldmesh->tvector3f, oldmesh->numverts * sizeof(float[3])); memcpy(newmesh->normal3f, oldmesh->normal3f, oldmesh->numverts * sizeof(float[3])); memcpy(newmesh->texcoord2f, oldmesh->texcoord2f, oldmesh->numverts * sizeof(float[2])); } memcpy(newmesh->element3i, oldmesh->element3i, oldmesh->numtriangles * sizeof(int[3])); if (newmesh->neighbor3i && oldmesh->neighbor3i) memcpy(newmesh->neighbor3i, oldmesh->neighbor3i, oldmesh->numtriangles * sizeof(int[3])); return newmesh; } int Mod_ShadowMesh_AddVertex(shadowmesh_t *mesh, float *vertex14f) { int hashindex, vnum; shadowmeshvertexhash_t *hash; // this uses prime numbers intentionally hashindex = (unsigned int) (vertex14f[0] * 2003 + vertex14f[1] * 4001 + vertex14f[2] * 7919) % SHADOWMESHVERTEXHASH; for (hash = mesh->vertexhashtable[hashindex];hash;hash = hash->next) { vnum = (hash - mesh->vertexhashentries); if ((mesh->vertex3f == NULL || (mesh->vertex3f[vnum * 3 + 0] == vertex14f[0] && mesh->vertex3f[vnum * 3 + 1] == vertex14f[1] && mesh->vertex3f[vnum * 3 + 2] == vertex14f[2])) && (mesh->svector3f == NULL || (mesh->svector3f[vnum * 3 + 0] == vertex14f[3] && mesh->svector3f[vnum * 3 + 1] == vertex14f[4] && mesh->svector3f[vnum * 3 + 2] == vertex14f[5])) && (mesh->tvector3f == NULL || (mesh->tvector3f[vnum * 3 + 0] == vertex14f[6] && mesh->tvector3f[vnum * 3 + 1] == vertex14f[7] && mesh->tvector3f[vnum * 3 + 2] == vertex14f[8])) && (mesh->normal3f == NULL || (mesh->normal3f[vnum * 3 + 0] == vertex14f[9] && mesh->normal3f[vnum * 3 + 1] == vertex14f[10] && mesh->normal3f[vnum * 3 + 2] == vertex14f[11])) && (mesh->texcoord2f == NULL || (mesh->texcoord2f[vnum * 2 + 0] == vertex14f[12] && mesh->texcoord2f[vnum * 2 + 1] == vertex14f[13]))) return hash - mesh->vertexhashentries; } vnum = mesh->numverts++; hash = mesh->vertexhashentries + vnum; hash->next = mesh->vertexhashtable[hashindex]; mesh->vertexhashtable[hashindex] = hash; if (mesh->vertex3f) {mesh->vertex3f[vnum * 3 + 0] = vertex14f[0];mesh->vertex3f[vnum * 3 + 1] = vertex14f[1];mesh->vertex3f[vnum * 3 + 2] = vertex14f[2];} if (mesh->svector3f) {mesh->svector3f[vnum * 3 + 0] = vertex14f[3];mesh->svector3f[vnum * 3 + 1] = vertex14f[4];mesh->svector3f[vnum * 3 + 2] = vertex14f[5];} if (mesh->tvector3f) {mesh->tvector3f[vnum * 3 + 0] = vertex14f[6];mesh->tvector3f[vnum * 3 + 1] = vertex14f[7];mesh->tvector3f[vnum * 3 + 2] = vertex14f[8];} if (mesh->normal3f) {mesh->normal3f[vnum * 3 + 0] = vertex14f[9];mesh->normal3f[vnum * 3 + 1] = vertex14f[10];mesh->normal3f[vnum * 3 + 2] = vertex14f[11];} if (mesh->texcoord2f) {mesh->texcoord2f[vnum * 2 + 0] = vertex14f[12];mesh->texcoord2f[vnum * 2 + 1] = vertex14f[13];} return vnum; } void Mod_ShadowMesh_AddTriangle(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, float *vertex14f) { if (mesh->numtriangles == 0) { // set the properties on this empty mesh to be more favorable... // (note: this case only occurs for the first triangle added to a new mesh chain) mesh->map_diffuse = map_diffuse; mesh->map_specular = map_specular; mesh->map_normal = map_normal; } while (mesh->map_diffuse != map_diffuse || mesh->map_specular != map_specular || mesh->map_normal != map_normal || mesh->numverts + 3 > mesh->maxverts || mesh->numtriangles + 1 > mesh->maxtriangles) { if (mesh->next == NULL) mesh->next = Mod_ShadowMesh_Alloc(mempool, max(mesh->maxverts, 300), max(mesh->maxtriangles, 100), map_diffuse, map_specular, map_normal, mesh->svector3f != NULL, mesh->neighbor3i != NULL, true); mesh = mesh->next; } mesh->element3i[mesh->numtriangles * 3 + 0] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 0); mesh->element3i[mesh->numtriangles * 3 + 1] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 1); mesh->element3i[mesh->numtriangles * 3 + 2] = Mod_ShadowMesh_AddVertex(mesh, vertex14f + 14 * 2); mesh->numtriangles++; } void Mod_ShadowMesh_AddMesh(mempool_t *mempool, shadowmesh_t *mesh, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *texcoord2f, int numtris, const int *element3i) { int i, j, e; float vbuf[3*14], *v; memset(vbuf, 0, sizeof(vbuf)); for (i = 0;i < numtris;i++) { for (j = 0, v = vbuf;j < 3;j++, v += 14) { e = *element3i++; if (vertex3f) { v[0] = vertex3f[e * 3 + 0]; v[1] = vertex3f[e * 3 + 1]; v[2] = vertex3f[e * 3 + 2]; } if (svector3f) { v[3] = svector3f[e * 3 + 0]; v[4] = svector3f[e * 3 + 1]; v[5] = svector3f[e * 3 + 2]; } if (tvector3f) { v[6] = tvector3f[e * 3 + 0]; v[7] = tvector3f[e * 3 + 1]; v[8] = tvector3f[e * 3 + 2]; } if (normal3f) { v[9] = normal3f[e * 3 + 0]; v[10] = normal3f[e * 3 + 1]; v[11] = normal3f[e * 3 + 2]; } if (texcoord2f) { v[12] = texcoord2f[e * 2 + 0]; v[13] = texcoord2f[e * 2 + 1]; } } Mod_ShadowMesh_AddTriangle(mempool, mesh, map_diffuse, map_specular, map_normal, vbuf); } // the triangle calculation can take a while, so let's do a keepalive here CL_KeepaliveMessage(false); } shadowmesh_t *Mod_ShadowMesh_Begin(mempool_t *mempool, int maxverts, int maxtriangles, rtexture_t *map_diffuse, rtexture_t *map_specular, rtexture_t *map_normal, int light, int neighbors, int expandable) { // the preparation before shadow mesh initialization can take a while, so let's do a keepalive here CL_KeepaliveMessage(false); return Mod_ShadowMesh_Alloc(mempool, maxverts, maxtriangles, map_diffuse, map_specular, map_normal, light, neighbors, expandable); } static void Mod_ShadowMesh_CreateVBOs(shadowmesh_t *mesh, mempool_t *mempool) { if (!mesh->numverts) return; // build r_vertexmesh_t array // (compressed interleaved array for D3D) if (!mesh->vertexmesh && mesh->texcoord2f && vid.useinterleavedarrays) { int vertexindex; int numvertices = mesh->numverts; r_vertexmesh_t *vertexmesh; mesh->vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(mempool, numvertices * sizeof(*mesh->vertexmesh)); for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) { VectorCopy(mesh->vertex3f + 3*vertexindex, vertexmesh->vertex3f); VectorScale(mesh->svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); VectorScale(mesh->tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); VectorScale(mesh->normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); Vector2Copy(mesh->texcoord2f + 2*vertexindex, vertexmesh->texcoordtexture2f); } } // upload short indices as a buffer if (mesh->element3s && !mesh->element3s_indexbuffer) mesh->element3s_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3s, mesh->numtriangles * sizeof(short[3]), loadmodel->name, true, false, false, true); // upload int indices as a buffer if (mesh->element3i && !mesh->element3i_indexbuffer && !mesh->element3s) mesh->element3i_indexbuffer = R_Mesh_CreateMeshBuffer(mesh->element3i, mesh->numtriangles * sizeof(int[3]), loadmodel->name, true, false, false, false); // vertex buffer is several arrays and we put them in the same buffer // // is this wise? the texcoordtexture2f array is used with dynamic // vertex/svector/tvector/normal when rendering animated models, on the // other hand animated models don't use a lot of vertices anyway... if (!mesh->vbo_vertexbuffer && !vid.useinterleavedarrays) { int size; unsigned char *mem; size = 0; mesh->vbooffset_vertexmesh = size;if (mesh->vertexmesh ) size += mesh->numverts * sizeof(r_vertexmesh_t); mesh->vbooffset_vertex3f = size;if (mesh->vertex3f ) size += mesh->numverts * sizeof(float[3]); mesh->vbooffset_svector3f = size;if (mesh->svector3f ) size += mesh->numverts * sizeof(float[3]); mesh->vbooffset_tvector3f = size;if (mesh->tvector3f ) size += mesh->numverts * sizeof(float[3]); mesh->vbooffset_normal3f = size;if (mesh->normal3f ) size += mesh->numverts * sizeof(float[3]); mesh->vbooffset_texcoord2f = size;if (mesh->texcoord2f ) size += mesh->numverts * sizeof(float[2]); mem = (unsigned char *)Mem_Alloc(tempmempool, size); if (mesh->vertexmesh ) memcpy(mem + mesh->vbooffset_vertexmesh , mesh->vertexmesh , mesh->numverts * sizeof(r_vertexmesh_t)); if (mesh->vertex3f ) memcpy(mem + mesh->vbooffset_vertex3f , mesh->vertex3f , mesh->numverts * sizeof(float[3])); if (mesh->svector3f ) memcpy(mem + mesh->vbooffset_svector3f , mesh->svector3f , mesh->numverts * sizeof(float[3])); if (mesh->tvector3f ) memcpy(mem + mesh->vbooffset_tvector3f , mesh->tvector3f , mesh->numverts * sizeof(float[3])); if (mesh->normal3f ) memcpy(mem + mesh->vbooffset_normal3f , mesh->normal3f , mesh->numverts * sizeof(float[3])); if (mesh->texcoord2f ) memcpy(mem + mesh->vbooffset_texcoord2f , mesh->texcoord2f , mesh->numverts * sizeof(float[2])); mesh->vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, "shadowmesh", false, false, false, false); Mem_Free(mem); } } shadowmesh_t *Mod_ShadowMesh_Finish(mempool_t *mempool, shadowmesh_t *firstmesh, qboolean light, qboolean neighbors, qboolean createvbo) { shadowmesh_t *mesh, *newmesh, *nextmesh; // reallocate meshs to conserve space for (mesh = firstmesh, firstmesh = NULL;mesh;mesh = nextmesh) { nextmesh = mesh->next; if (mesh->numverts >= 3 && mesh->numtriangles >= 1) { newmesh = Mod_ShadowMesh_ReAlloc(mempool, mesh, light, neighbors); newmesh->next = firstmesh; firstmesh = newmesh; if (newmesh->element3s) { int i; for (i = 0;i < newmesh->numtriangles*3;i++) newmesh->element3s[i] = newmesh->element3i[i]; } if (createvbo) Mod_ShadowMesh_CreateVBOs(newmesh, mempool); } Mem_Free(mesh); } // this can take a while, so let's do a keepalive here CL_KeepaliveMessage(false); return firstmesh; } void Mod_ShadowMesh_CalcBBox(shadowmesh_t *firstmesh, vec3_t mins, vec3_t maxs, vec3_t center, float *radius) { int i; shadowmesh_t *mesh; vec3_t nmins, nmaxs, ncenter, temp; float nradius2, dist2, *v; VectorClear(nmins); VectorClear(nmaxs); // calculate bbox for (mesh = firstmesh;mesh;mesh = mesh->next) { if (mesh == firstmesh) { VectorCopy(mesh->vertex3f, nmins); VectorCopy(mesh->vertex3f, nmaxs); } for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) { if (nmins[0] > v[0]) nmins[0] = v[0];if (nmaxs[0] < v[0]) nmaxs[0] = v[0]; if (nmins[1] > v[1]) nmins[1] = v[1];if (nmaxs[1] < v[1]) nmaxs[1] = v[1]; if (nmins[2] > v[2]) nmins[2] = v[2];if (nmaxs[2] < v[2]) nmaxs[2] = v[2]; } } // calculate center and radius ncenter[0] = (nmins[0] + nmaxs[0]) * 0.5f; ncenter[1] = (nmins[1] + nmaxs[1]) * 0.5f; ncenter[2] = (nmins[2] + nmaxs[2]) * 0.5f; nradius2 = 0; for (mesh = firstmesh;mesh;mesh = mesh->next) { for (i = 0, v = mesh->vertex3f;i < mesh->numverts;i++, v += 3) { VectorSubtract(v, ncenter, temp); dist2 = DotProduct(temp, temp); if (nradius2 < dist2) nradius2 = dist2; } } // return data if (mins) VectorCopy(nmins, mins); if (maxs) VectorCopy(nmaxs, maxs); if (center) VectorCopy(ncenter, center); if (radius) *radius = sqrt(nradius2); } void Mod_ShadowMesh_Free(shadowmesh_t *mesh) { shadowmesh_t *nextmesh; for (;mesh;mesh = nextmesh) { if (mesh->element3i_indexbuffer) R_Mesh_DestroyMeshBuffer(mesh->element3i_indexbuffer); if (mesh->element3s_indexbuffer) R_Mesh_DestroyMeshBuffer(mesh->element3s_indexbuffer); if (mesh->vbo_vertexbuffer) R_Mesh_DestroyMeshBuffer(mesh->vbo_vertexbuffer); nextmesh = mesh->next; Mem_Free(mesh); } } void Mod_CreateCollisionMesh(dp_model_t *mod) { int k, numcollisionmeshtriangles; qboolean usesinglecollisionmesh = false; const msurface_t *surface = NULL; mempool_t *mempool = mod->mempool; if (!mempool && mod->brush.parentmodel) mempool = mod->brush.parentmodel->mempool; // make a single combined collision mesh for physics engine use // TODO rewrite this to use the collision brushes as source, to fix issues with e.g. common/caulk which creates no drawsurface numcollisionmeshtriangles = 0; for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; if (!strcmp(surface->texture->name, "collision") || !strcmp(surface->texture->name, "collisionconvex")) // found collision mesh { usesinglecollisionmesh = true; numcollisionmeshtriangles = surface->num_triangles; break; } if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) continue; numcollisionmeshtriangles += surface->num_triangles; } mod->brush.collisionmesh = Mod_ShadowMesh_Begin(mempool, numcollisionmeshtriangles * 3, numcollisionmeshtriangles, NULL, NULL, NULL, false, false, true); if (usesinglecollisionmesh) Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); else { for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; if (!(surface->texture->supercontents & SUPERCONTENTS_SOLID)) continue; Mod_ShadowMesh_AddMesh(mempool, mod->brush.collisionmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); } } mod->brush.collisionmesh = Mod_ShadowMesh_Finish(mempool, mod->brush.collisionmesh, false, false, false); } #if 0 static void Mod_GetTerrainVertex3fTexCoord2fFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) { float v[3], tc[3]; v[0] = ix; v[1] = iy; if (ix >= 0 && iy >= 0 && ix < imagewidth && iy < imageheight) v[2] = (imagepixels[((iy*imagewidth)+ix)*4+0] + imagepixels[((iy*imagewidth)+ix)*4+1] + imagepixels[((iy*imagewidth)+ix)*4+2]) * (1.0f / 765.0f); else v[2] = 0; Matrix4x4_Transform(pixelstepmatrix, v, vertex3f); Matrix4x4_Transform(pixeltexturestepmatrix, v, tc); texcoord2f[0] = tc[0]; texcoord2f[1] = tc[1]; } static void Mod_GetTerrainVertexFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int ix, int iy, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) { float vup[3], vdown[3], vleft[3], vright[3]; float tcup[3], tcdown[3], tcleft[3], tcright[3]; float sv[3], tv[3], nl[3]; Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, pixelstepmatrix, pixeltexturestepmatrix); Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy - 1, vup, tcup, pixelstepmatrix, pixeltexturestepmatrix); Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix, iy + 1, vdown, tcdown, pixelstepmatrix, pixeltexturestepmatrix); Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix - 1, iy, vleft, tcleft, pixelstepmatrix, pixeltexturestepmatrix); Mod_GetTerrainVertex3fTexCoord2fFromBGRA(imagepixels, imagewidth, imageheight, ix + 1, iy, vright, tcright, pixelstepmatrix, pixeltexturestepmatrix); Mod_BuildBumpVectors(vertex3f, vup, vright, texcoord2f, tcup, tcright, svector3f, tvector3f, normal3f); Mod_BuildBumpVectors(vertex3f, vright, vdown, texcoord2f, tcright, tcdown, sv, tv, nl); VectorAdd(svector3f, sv, svector3f); VectorAdd(tvector3f, tv, tvector3f); VectorAdd(normal3f, nl, normal3f); Mod_BuildBumpVectors(vertex3f, vdown, vleft, texcoord2f, tcdown, tcleft, sv, tv, nl); VectorAdd(svector3f, sv, svector3f); VectorAdd(tvector3f, tv, tvector3f); VectorAdd(normal3f, nl, normal3f); Mod_BuildBumpVectors(vertex3f, vleft, vup, texcoord2f, tcleft, tcup, sv, tv, nl); VectorAdd(svector3f, sv, svector3f); VectorAdd(tvector3f, tv, tvector3f); VectorAdd(normal3f, nl, normal3f); } static void Mod_ConstructTerrainPatchFromBGRA(const unsigned char *imagepixels, int imagewidth, int imageheight, int x1, int y1, int width, int height, int *element3i, int *neighbor3i, float *vertex3f, float *svector3f, float *tvector3f, float *normal3f, float *texcoord2f, matrix4x4_t *pixelstepmatrix, matrix4x4_t *pixeltexturestepmatrix) { int x, y, ix, iy, *e; e = element3i; for (y = 0;y < height;y++) { for (x = 0;x < width;x++) { e[0] = (y + 1) * (width + 1) + (x + 0); e[1] = (y + 0) * (width + 1) + (x + 0); e[2] = (y + 1) * (width + 1) + (x + 1); e[3] = (y + 0) * (width + 1) + (x + 0); e[4] = (y + 0) * (width + 1) + (x + 1); e[5] = (y + 1) * (width + 1) + (x + 1); e += 6; } } Mod_BuildTriangleNeighbors(neighbor3i, element3i, width*height*2); for (y = 0, iy = y1;y < height + 1;y++, iy++) for (x = 0, ix = x1;x < width + 1;x++, ix++, vertex3f += 3, texcoord2f += 2, svector3f += 3, tvector3f += 3, normal3f += 3) Mod_GetTerrainVertexFromBGRA(imagepixels, imagewidth, imageheight, ix, iy, vertex3f, texcoord2f, svector3f, tvector3f, normal3f, pixelstepmatrix, pixeltexturestepmatrix); } #endif #if 0 void Mod_Terrain_SurfaceRecurseChunk(dp_model_t *model, int stepsize, int x, int y) { float mins[3]; float maxs[3]; float chunkwidth = min(stepsize, model->terrain.width - 1 - x); float chunkheight = min(stepsize, model->terrain.height - 1 - y); float viewvector[3]; unsigned int firstvertex; unsigned int *e; float *v; if (chunkwidth < 2 || chunkheight < 2) return; VectorSet(mins, model->terrain.mins[0] + x * stepsize * model->terrain.scale[0], model->terrain.mins[1] + y * stepsize * model->terrain.scale[1], model->terrain.mins[2]); VectorSet(maxs, model->terrain.mins[0] + (x+1) * stepsize * model->terrain.scale[0], model->terrain.mins[1] + (y+1) * stepsize * model->terrain.scale[1], model->terrain.maxs[2]); viewvector[0] = bound(mins[0], localvieworigin, maxs[0]) - model->terrain.vieworigin[0]; viewvector[1] = bound(mins[1], localvieworigin, maxs[1]) - model->terrain.vieworigin[1]; viewvector[2] = bound(mins[2], localvieworigin, maxs[2]) - model->terrain.vieworigin[2]; if (stepsize > 1 && VectorLength(viewvector) < stepsize*model->terrain.scale[0]*r_terrain_lodscale.value) { // too close for this stepsize, emit as 4 chunks instead stepsize /= 2; Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y); Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y); Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x, y+stepsize); Mod_Terrain_SurfaceRecurseChunk(model, stepsize, x+stepsize, y+stepsize); return; } // emit the geometry at stepsize into our vertex buffer / index buffer // we add two columns and two rows for skirt outwidth = chunkwidth+2; outheight = chunkheight+2; outwidth2 = outwidth-1; outheight2 = outheight-1; outwidth3 = outwidth+1; outheight3 = outheight+1; firstvertex = numvertices; e = model->terrain.element3i + numtriangles; numtriangles += chunkwidth*chunkheight*2+chunkwidth*2*2+chunkheight*2*2; v = model->terrain.vertex3f + numvertices; numvertices += (chunkwidth+1)*(chunkheight+1)+(chunkwidth+1)*2+(chunkheight+1)*2; // emit the triangles (note: the skirt is treated as two extra rows and two extra columns) for (ty = 0;ty < outheight;ty++) { for (tx = 0;tx < outwidth;tx++) { *e++ = firstvertex + (ty )*outwidth3+(tx ); *e++ = firstvertex + (ty )*outwidth3+(tx+1); *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); *e++ = firstvertex + (ty )*outwidth3+(tx ); *e++ = firstvertex + (ty+1)*outwidth3+(tx+1); *e++ = firstvertex + (ty+1)*outwidth3+(tx ); } } // TODO: emit surface vertices (x+tx*stepsize, y+ty*stepsize) for (ty = 0;ty <= outheight;ty++) { skirtrow = ty == 0 || ty == outheight; ry = y+bound(1, ty, outheight)*stepsize; for (tx = 0;tx <= outwidth;tx++) { skirt = skirtrow || tx == 0 || tx == outwidth; rx = x+bound(1, tx, outwidth)*stepsize; v[0] = rx*scale[0]; v[1] = ry*scale[1]; v[2] = heightmap[ry*terrainwidth+rx]*scale[2]; v += 3; } } // TODO: emit skirt vertices } void Mod_Terrain_UpdateSurfacesForViewOrigin(dp_model_t *model) { for (y = 0;y < model->terrain.size[1];y += model->terrain. Mod_Terrain_SurfaceRecurseChunk(model, model->terrain.maxstepsize, x, y); Mod_Terrain_BuildChunk(model, } #endif static int Mod_LoadQ3Shaders_EnumerateWaveFunc(const char *s) { int offset = 0; if (!strncasecmp(s, "user", 4)) // parse stuff like "user1sin", always userfunc { offset = bound(0, s[4] - '0', 9); offset = (offset + 1) << Q3WAVEFUNC_USER_SHIFT; s += 4; if(*s) ++s; } if (!strcasecmp(s, "sin")) return offset | Q3WAVEFUNC_SIN; if (!strcasecmp(s, "square")) return offset | Q3WAVEFUNC_SQUARE; if (!strcasecmp(s, "triangle")) return offset | Q3WAVEFUNC_TRIANGLE; if (!strcasecmp(s, "sawtooth")) return offset | Q3WAVEFUNC_SAWTOOTH; if (!strcasecmp(s, "inversesawtooth")) return offset | Q3WAVEFUNC_INVERSESAWTOOTH; if (!strcasecmp(s, "noise")) return offset | Q3WAVEFUNC_NOISE; if (!strcasecmp(s, "none")) return offset | Q3WAVEFUNC_NONE; Con_DPrintf("Mod_LoadQ3Shaders: unknown wavefunc %s\n", s); return offset | Q3WAVEFUNC_NONE; } void Mod_FreeQ3Shaders(void) { Mem_FreePool(&q3shaders_mem); } static void Q3Shader_AddToHash (q3shaderinfo_t* shader) { unsigned short hash = CRC_Block_CaseInsensitive ((const unsigned char *)shader->name, strlen (shader->name)); q3shader_hash_entry_t* entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); q3shader_hash_entry_t* lastEntry = NULL; do { if (strcasecmp (entry->shader.name, shader->name) == 0) { // redeclaration if(shader->dpshaderkill) { // killed shader is a redeclarion? we can safely ignore it return; } else if(entry->shader.dpshaderkill) { // replace the old shader! // this will skip the entry allocating part // below and just replace the shader break; } else { unsigned char *start, *end, *start2; start = (unsigned char *) (&shader->Q3SHADERINFO_COMPARE_START); end = ((unsigned char *) (&shader->Q3SHADERINFO_COMPARE_END)) + sizeof(shader->Q3SHADERINFO_COMPARE_END); start2 = (unsigned char *) (&entry->shader.Q3SHADERINFO_COMPARE_START); if(memcmp(start, start2, end - start)) Con_DPrintf("Shader '%s' already defined, ignoring mismatching redeclaration\n", shader->name); else Con_DPrintf("Shader '%s' already defined\n", shader->name); return; } } lastEntry = entry; entry = entry->chain; } while (entry != NULL); if (entry == NULL) { if (lastEntry->shader.name[0] != 0) { /* Add to chain */ q3shader_hash_entry_t* newEntry = (q3shader_hash_entry_t*) Mem_ExpandableArray_AllocRecord (&q3shader_data->hash_entries); while (lastEntry->chain != NULL) lastEntry = lastEntry->chain; lastEntry->chain = newEntry; newEntry->chain = NULL; lastEntry = newEntry; } /* else: head of chain, in hash entry array */ entry = lastEntry; } memcpy (&entry->shader, shader, sizeof (q3shaderinfo_t)); } extern cvar_t mod_noshader_default_offsetmapping; extern cvar_t mod_q3shader_default_offsetmapping; extern cvar_t mod_q3shader_default_offsetmapping_scale; extern cvar_t mod_q3shader_default_offsetmapping_bias; extern cvar_t mod_q3shader_default_polygonoffset; extern cvar_t mod_q3shader_default_polygonfactor; extern cvar_t mod_q3shader_force_addalpha; extern cvar_t mod_q3shader_force_terrain_alphaflag; void Mod_LoadQ3Shaders(void) { int j; int fileindex; fssearch_t *search; char *f; const char *text; q3shaderinfo_t shader; q3shaderinfo_layer_t *layer; int numparameters; char parameter[TEXTURE_MAXFRAMES + 4][Q3PATHLENGTH]; char *custsurfaceparmnames[256]; // VorteX: q3map2 has 64 but well, someone will need more unsigned long custsurfaceflags[256]; int numcustsurfaceflags; qboolean dpshaderkill; Mod_FreeQ3Shaders(); q3shaders_mem = Mem_AllocPool("q3shaders", 0, NULL); q3shader_data = (q3shader_data_t*)Mem_Alloc (q3shaders_mem, sizeof (q3shader_data_t)); Mem_ExpandableArray_NewArray (&q3shader_data->hash_entries, q3shaders_mem, sizeof (q3shader_hash_entry_t), 256); Mem_ExpandableArray_NewArray (&q3shader_data->char_ptrs, q3shaders_mem, sizeof (char**), 256); // parse custinfoparms.txt numcustsurfaceflags = 0; if ((text = f = (char *)FS_LoadFile("scripts/custinfoparms.txt", tempmempool, false, NULL)) != NULL) { if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) Con_DPrintf("scripts/custinfoparms.txt: contentflags section parsing error - expected \"{\", found \"%s\"\n", com_token); else { while (COM_ParseToken_QuakeC(&text, false)) if (!strcasecmp(com_token, "}")) break; // custom surfaceflags section if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) Con_DPrintf("scripts/custinfoparms.txt: surfaceflags section parsing error - expected \"{\", found \"%s\"\n", com_token); else { while(COM_ParseToken_QuakeC(&text, false)) { if (!strcasecmp(com_token, "}")) break; // register surfaceflag if (numcustsurfaceflags >= 256) { Con_Printf("scripts/custinfoparms.txt: surfaceflags section parsing error - max 256 surfaceflags exceeded\n"); break; } // name j = (int)strlen(com_token)+1; custsurfaceparmnames[numcustsurfaceflags] = (char *)Mem_Alloc(tempmempool, j); strlcpy(custsurfaceparmnames[numcustsurfaceflags], com_token, j+1); // value if (COM_ParseToken_QuakeC(&text, false)) custsurfaceflags[numcustsurfaceflags] = strtol(com_token, NULL, 0); else custsurfaceflags[numcustsurfaceflags] = 0; numcustsurfaceflags++; } } } Mem_Free(f); } // parse shaders search = FS_Search("scripts/*.shader", true, false); if (!search) return; for (fileindex = 0;fileindex < search->numfilenames;fileindex++) { text = f = (char *)FS_LoadFile(search->filenames[fileindex], tempmempool, false, NULL); if (!f) continue; while (COM_ParseToken_QuakeC(&text, false)) { memset (&shader, 0, sizeof(shader)); shader.name[0] = 0; shader.surfaceparms = 0; shader.surfaceflags = 0; shader.textureflags = 0; shader.numlayers = 0; shader.lighting = false; shader.vertexalpha = false; shader.textureblendalpha = false; shader.primarylayer = 0; shader.backgroundlayer = 0; shader.skyboxname[0] = 0; shader.deforms[0].deform = Q3DEFORM_NONE; shader.dpnortlight = false; shader.dpshadow = false; shader.dpnoshadow = false; shader.dpmeshcollisions = false; shader.dpshaderkill = false; shader.dpreflectcube[0] = 0; shader.reflectmin = 0; shader.reflectmax = 1; shader.refractfactor = 1; Vector4Set(shader.refractcolor4f, 1, 1, 1, 1); shader.reflectfactor = 1; Vector4Set(shader.reflectcolor4f, 1, 1, 1, 1); shader.r_water_wateralpha = 1; shader.r_water_waterscroll[0] = 0; shader.r_water_waterscroll[1] = 0; shader.offsetmapping = (mod_q3shader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; shader.offsetscale = mod_q3shader_default_offsetmapping_scale.value; shader.offsetbias = mod_q3shader_default_offsetmapping_bias.value; shader.biaspolygonoffset = mod_q3shader_default_polygonoffset.value; shader.biaspolygonfactor = mod_q3shader_default_polygonfactor.value; shader.transparentsort = TRANSPARENTSORT_DISTANCE; shader.specularscalemod = 1; shader.specularpowermod = 1; shader.rtlightambient = 0; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". strlcpy(shader.name, com_token, sizeof(shader.name)); if (!COM_ParseToken_QuakeC(&text, false) || strcasecmp(com_token, "{")) { Con_DPrintf("%s parsing error - expected \"{\", found \"%s\"\n", search->filenames[fileindex], com_token); break; } while (COM_ParseToken_QuakeC(&text, false)) { if (!strcasecmp(com_token, "}")) break; if (!strcasecmp(com_token, "{")) { static q3shaderinfo_layer_t dummy; if (shader.numlayers < Q3SHADER_MAXLAYERS) { layer = shader.layers + shader.numlayers++; } else { // parse and process it anyway, just don't store it (so a map $lightmap or such stuff still is found) memset(&dummy, 0, sizeof(dummy)); layer = &dummy; } layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; layer->tcgen.tcgen = Q3TCGEN_TEXTURE; layer->blendfunc[0] = GL_ONE; layer->blendfunc[1] = GL_ZERO; while (COM_ParseToken_QuakeC(&text, false)) { if (!strcasecmp(com_token, "}")) break; if (!strcasecmp(com_token, "\n")) continue; numparameters = 0; for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) { if (j < TEXTURE_MAXFRAMES + 4) { // remap dp_water to dpwater, dp_reflect to dpreflect, etc. if(j == 0 && !strncasecmp(com_token, "dp_", 3)) dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); else strlcpy(parameter[j], com_token, sizeof(parameter[j])); numparameters = j + 1; } if (!COM_ParseToken_QuakeC(&text, true)) break; } //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) // parameter[j][0] = 0; if (developer_insane.integer) { Con_DPrintf("%s %i: ", shader.name, shader.numlayers - 1); for (j = 0;j < numparameters;j++) Con_DPrintf(" %s", parameter[j]); Con_DPrint("\n"); } if (numparameters >= 2 && !strcasecmp(parameter[0], "blendfunc")) { if (numparameters == 2) { if (!strcasecmp(parameter[1], "add")) { layer->blendfunc[0] = GL_ONE; layer->blendfunc[1] = GL_ONE; } else if (!strcasecmp(parameter[1], "addalpha")) { layer->blendfunc[0] = GL_SRC_ALPHA; layer->blendfunc[1] = GL_ONE; } else if (!strcasecmp(parameter[1], "filter")) { layer->blendfunc[0] = GL_DST_COLOR; layer->blendfunc[1] = GL_ZERO; } else if (!strcasecmp(parameter[1], "blend")) { layer->blendfunc[0] = GL_SRC_ALPHA; layer->blendfunc[1] = GL_ONE_MINUS_SRC_ALPHA; } } else if (numparameters == 3) { int k; for (k = 0;k < 2;k++) { if (!strcasecmp(parameter[k+1], "GL_ONE")) layer->blendfunc[k] = GL_ONE; else if (!strcasecmp(parameter[k+1], "GL_ZERO")) layer->blendfunc[k] = GL_ZERO; else if (!strcasecmp(parameter[k+1], "GL_SRC_COLOR")) layer->blendfunc[k] = GL_SRC_COLOR; else if (!strcasecmp(parameter[k+1], "GL_SRC_ALPHA")) layer->blendfunc[k] = GL_SRC_ALPHA; else if (!strcasecmp(parameter[k+1], "GL_DST_COLOR")) layer->blendfunc[k] = GL_DST_COLOR; else if (!strcasecmp(parameter[k+1], "GL_DST_ALPHA")) layer->blendfunc[k] = GL_DST_ALPHA; else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_COLOR")) layer->blendfunc[k] = GL_ONE_MINUS_SRC_COLOR; else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_SRC_ALPHA")) layer->blendfunc[k] = GL_ONE_MINUS_SRC_ALPHA; else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_COLOR")) layer->blendfunc[k] = GL_ONE_MINUS_DST_COLOR; else if (!strcasecmp(parameter[k+1], "GL_ONE_MINUS_DST_ALPHA")) layer->blendfunc[k] = GL_ONE_MINUS_DST_ALPHA; else layer->blendfunc[k] = GL_ONE; // default in case of parsing error } } } if (numparameters >= 2 && !strcasecmp(parameter[0], "alphafunc")) layer->alphatest = true; if (numparameters >= 2 && (!strcasecmp(parameter[0], "map") || !strcasecmp(parameter[0], "clampmap"))) { if (!strcasecmp(parameter[0], "clampmap")) layer->clampmap = true; layer->numframes = 1; layer->framerate = 1; layer->texturename = (char**)Mem_ExpandableArray_AllocRecord ( &q3shader_data->char_ptrs); layer->texturename[0] = Mem_strdup (q3shaders_mem, parameter[1]); if (!strcasecmp(parameter[1], "$lightmap")) shader.lighting = true; } else if (numparameters >= 3 && (!strcasecmp(parameter[0], "animmap") || !strcasecmp(parameter[0], "animclampmap"))) { int i; layer->numframes = min(numparameters - 2, TEXTURE_MAXFRAMES); layer->framerate = atof(parameter[1]); layer->texturename = (char **) Mem_Alloc (q3shaders_mem, sizeof (char*) * layer->numframes); for (i = 0;i < layer->numframes;i++) layer->texturename[i] = Mem_strdup (q3shaders_mem, parameter[i + 2]); } else if (numparameters >= 2 && !strcasecmp(parameter[0], "rgbgen")) { int i; for (i = 0;i < numparameters - 2 && i < Q3RGBGEN_MAXPARMS;i++) layer->rgbgen.parms[i] = atof(parameter[i+2]); if (!strcasecmp(parameter[1], "identity")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITY; else if (!strcasecmp(parameter[1], "const")) layer->rgbgen.rgbgen = Q3RGBGEN_CONST; else if (!strcasecmp(parameter[1], "entity")) layer->rgbgen.rgbgen = Q3RGBGEN_ENTITY; else if (!strcasecmp(parameter[1], "exactvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_EXACTVERTEX; else if (!strcasecmp(parameter[1], "identitylighting")) layer->rgbgen.rgbgen = Q3RGBGEN_IDENTITYLIGHTING; else if (!strcasecmp(parameter[1], "lightingdiffuse")) layer->rgbgen.rgbgen = Q3RGBGEN_LIGHTINGDIFFUSE; else if (!strcasecmp(parameter[1], "oneminusentity")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSENTITY; else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->rgbgen.rgbgen = Q3RGBGEN_ONEMINUSVERTEX; else if (!strcasecmp(parameter[1], "vertex")) layer->rgbgen.rgbgen = Q3RGBGEN_VERTEX; else if (!strcasecmp(parameter[1], "wave")) { layer->rgbgen.rgbgen = Q3RGBGEN_WAVE; layer->rgbgen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) layer->rgbgen.waveparms[i] = atof(parameter[i+3]); } else Con_DPrintf("%s parsing warning: unknown rgbgen %s\n", search->filenames[fileindex], parameter[1]); } else if (numparameters >= 2 && !strcasecmp(parameter[0], "alphagen")) { int i; for (i = 0;i < numparameters - 2 && i < Q3ALPHAGEN_MAXPARMS;i++) layer->alphagen.parms[i] = atof(parameter[i+2]); if (!strcasecmp(parameter[1], "identity")) layer->alphagen.alphagen = Q3ALPHAGEN_IDENTITY; else if (!strcasecmp(parameter[1], "const")) layer->alphagen.alphagen = Q3ALPHAGEN_CONST; else if (!strcasecmp(parameter[1], "entity")) layer->alphagen.alphagen = Q3ALPHAGEN_ENTITY; else if (!strcasecmp(parameter[1], "lightingspecular")) layer->alphagen.alphagen = Q3ALPHAGEN_LIGHTINGSPECULAR; else if (!strcasecmp(parameter[1], "oneminusentity")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSENTITY; else if (!strcasecmp(parameter[1], "oneminusvertex")) layer->alphagen.alphagen = Q3ALPHAGEN_ONEMINUSVERTEX; else if (!strcasecmp(parameter[1], "portal")) layer->alphagen.alphagen = Q3ALPHAGEN_PORTAL; else if (!strcasecmp(parameter[1], "vertex")) layer->alphagen.alphagen = Q3ALPHAGEN_VERTEX; else if (!strcasecmp(parameter[1], "wave")) { layer->alphagen.alphagen = Q3ALPHAGEN_WAVE; layer->alphagen.wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) layer->alphagen.waveparms[i] = atof(parameter[i+3]); } else Con_DPrintf("%s parsing warning: unknown alphagen %s\n", search->filenames[fileindex], parameter[1]); } else if (numparameters >= 2 && (!strcasecmp(parameter[0], "texgen") || !strcasecmp(parameter[0], "tcgen"))) { int i; // observed values: tcgen environment // no other values have been observed in real shaders for (i = 0;i < numparameters - 2 && i < Q3TCGEN_MAXPARMS;i++) layer->tcgen.parms[i] = atof(parameter[i+2]); if (!strcasecmp(parameter[1], "base")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; else if (!strcasecmp(parameter[1], "texture")) layer->tcgen.tcgen = Q3TCGEN_TEXTURE; else if (!strcasecmp(parameter[1], "environment")) layer->tcgen.tcgen = Q3TCGEN_ENVIRONMENT; else if (!strcasecmp(parameter[1], "lightmap")) layer->tcgen.tcgen = Q3TCGEN_LIGHTMAP; else if (!strcasecmp(parameter[1], "vector")) layer->tcgen.tcgen = Q3TCGEN_VECTOR; else Con_DPrintf("%s parsing warning: unknown tcgen mode %s\n", search->filenames[fileindex], parameter[1]); } else if (numparameters >= 2 && !strcasecmp(parameter[0], "tcmod")) { int i, tcmodindex; // observed values: // tcmod rotate # // tcmod scale # # // tcmod scroll # # // tcmod stretch sin # # # # // tcmod stretch triangle # # # # // tcmod transform # # # # # # // tcmod turb # # # # // tcmod turb sin # # # # (this is bogus) // no other values have been observed in real shaders for (tcmodindex = 0;tcmodindex < Q3MAXTCMODS;tcmodindex++) if (!layer->tcmods[tcmodindex].tcmod) break; if (tcmodindex < Q3MAXTCMODS) { for (i = 0;i < numparameters - 2 && i < Q3TCMOD_MAXPARMS;i++) layer->tcmods[tcmodindex].parms[i] = atof(parameter[i+2]); if (!strcasecmp(parameter[1], "entitytranslate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ENTITYTRANSLATE; else if (!strcasecmp(parameter[1], "rotate")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_ROTATE; else if (!strcasecmp(parameter[1], "scale")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCALE; else if (!strcasecmp(parameter[1], "scroll")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_SCROLL; else if (!strcasecmp(parameter[1], "page")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_PAGE; else if (!strcasecmp(parameter[1], "stretch")) { layer->tcmods[tcmodindex].tcmod = Q3TCMOD_STRETCH; layer->tcmods[tcmodindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[2]); for (i = 0;i < numparameters - 3 && i < Q3WAVEPARMS;i++) layer->tcmods[tcmodindex].waveparms[i] = atof(parameter[i+3]); } else if (!strcasecmp(parameter[1], "transform")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TRANSFORM; else if (!strcasecmp(parameter[1], "turb")) layer->tcmods[tcmodindex].tcmod = Q3TCMOD_TURBULENT; else Con_DPrintf("%s parsing warning: unknown tcmod mode %s\n", search->filenames[fileindex], parameter[1]); } else Con_DPrintf("%s parsing warning: too many tcmods on one layer\n", search->filenames[fileindex]); } // break out a level if it was a closing brace (not using the character here to not confuse vim) if (!strcasecmp(com_token, "}")) break; } if (layer->rgbgen.rgbgen == Q3RGBGEN_LIGHTINGDIFFUSE || layer->rgbgen.rgbgen == Q3RGBGEN_VERTEX) shader.lighting = true; if (layer->alphagen.alphagen == Q3ALPHAGEN_VERTEX) { if (layer == shader.layers + 0) { // vertex controlled transparency shader.vertexalpha = true; } else { // multilayer terrain shader or similar shader.textureblendalpha = true; if (mod_q3shader_force_terrain_alphaflag.integer) shader.layers[0].texflags |= TEXF_ALPHA; } } if(mod_q3shader_force_addalpha.integer) { // for a long while, DP treated GL_ONE GL_ONE as GL_SRC_ALPHA GL_ONE // this cvar brings back this behaviour if(layer->blendfunc[0] == GL_ONE && layer->blendfunc[1] == GL_ONE) layer->blendfunc[0] = GL_SRC_ALPHA; } layer->texflags = 0; if (layer->alphatest) layer->texflags |= TEXF_ALPHA; switch(layer->blendfunc[0]) { case GL_SRC_ALPHA: case GL_ONE_MINUS_SRC_ALPHA: layer->texflags |= TEXF_ALPHA; break; } switch(layer->blendfunc[1]) { case GL_SRC_ALPHA: case GL_ONE_MINUS_SRC_ALPHA: layer->texflags |= TEXF_ALPHA; break; } if (!(shader.surfaceparms & Q3SURFACEPARM_NOMIPMAPS)) layer->texflags |= TEXF_MIPMAP; if (!(shader.textureflags & Q3TEXTUREFLAG_NOPICMIP)) layer->texflags |= TEXF_PICMIP | TEXF_COMPRESS; if (layer->clampmap) layer->texflags |= TEXF_CLAMP; continue; } numparameters = 0; for (j = 0;strcasecmp(com_token, "\n") && strcasecmp(com_token, "}");j++) { if (j < TEXTURE_MAXFRAMES + 4) { // remap dp_water to dpwater, dp_reflect to dpreflect, etc. if(j == 0 && !strncasecmp(com_token, "dp_", 3)) dpsnprintf(parameter[j], sizeof(parameter[j]), "dp%s", &com_token[3]); else strlcpy(parameter[j], com_token, sizeof(parameter[j])); numparameters = j + 1; } if (!COM_ParseToken_QuakeC(&text, true)) break; } //for (j = numparameters;j < TEXTURE_MAXFRAMES + 4;j++) // parameter[j][0] = 0; if (fileindex == 0 && !strcasecmp(com_token, "}")) break; if (developer_insane.integer) { Con_DPrintf("%s: ", shader.name); for (j = 0;j < numparameters;j++) Con_DPrintf(" %s", parameter[j]); Con_DPrint("\n"); } if (numparameters < 1) continue; if (!strcasecmp(parameter[0], "surfaceparm") && numparameters >= 2) { if (!strcasecmp(parameter[1], "alphashadow")) shader.surfaceparms |= Q3SURFACEPARM_ALPHASHADOW; else if (!strcasecmp(parameter[1], "areaportal")) shader.surfaceparms |= Q3SURFACEPARM_AREAPORTAL; else if (!strcasecmp(parameter[1], "botclip")) shader.surfaceparms |= Q3SURFACEPARM_BOTCLIP; else if (!strcasecmp(parameter[1], "clusterportal")) shader.surfaceparms |= Q3SURFACEPARM_CLUSTERPORTAL; else if (!strcasecmp(parameter[1], "detail")) shader.surfaceparms |= Q3SURFACEPARM_DETAIL; else if (!strcasecmp(parameter[1], "donotenter")) shader.surfaceparms |= Q3SURFACEPARM_DONOTENTER; else if (!strcasecmp(parameter[1], "dust")) shader.surfaceparms |= Q3SURFACEPARM_DUST; else if (!strcasecmp(parameter[1], "hint")) shader.surfaceparms |= Q3SURFACEPARM_HINT; else if (!strcasecmp(parameter[1], "fog")) shader.surfaceparms |= Q3SURFACEPARM_FOG; else if (!strcasecmp(parameter[1], "lava")) shader.surfaceparms |= Q3SURFACEPARM_LAVA; else if (!strcasecmp(parameter[1], "lightfilter")) shader.surfaceparms |= Q3SURFACEPARM_LIGHTFILTER; else if (!strcasecmp(parameter[1], "lightgrid")) shader.surfaceparms |= Q3SURFACEPARM_LIGHTGRID; else if (!strcasecmp(parameter[1], "metalsteps")) shader.surfaceparms |= Q3SURFACEPARM_METALSTEPS; else if (!strcasecmp(parameter[1], "nodamage")) shader.surfaceparms |= Q3SURFACEPARM_NODAMAGE; else if (!strcasecmp(parameter[1], "nodlight")) shader.surfaceparms |= Q3SURFACEPARM_NODLIGHT; else if (!strcasecmp(parameter[1], "nodraw")) shader.surfaceparms |= Q3SURFACEPARM_NODRAW; else if (!strcasecmp(parameter[1], "nodrop")) shader.surfaceparms |= Q3SURFACEPARM_NODROP; else if (!strcasecmp(parameter[1], "noimpact")) shader.surfaceparms |= Q3SURFACEPARM_NOIMPACT; else if (!strcasecmp(parameter[1], "nolightmap")) shader.surfaceparms |= Q3SURFACEPARM_NOLIGHTMAP; else if (!strcasecmp(parameter[1], "nomarks")) shader.surfaceparms |= Q3SURFACEPARM_NOMARKS; else if (!strcasecmp(parameter[1], "nomipmaps")) shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; else if (!strcasecmp(parameter[1], "nonsolid")) shader.surfaceparms |= Q3SURFACEPARM_NONSOLID; else if (!strcasecmp(parameter[1], "origin")) shader.surfaceparms |= Q3SURFACEPARM_ORIGIN; else if (!strcasecmp(parameter[1], "playerclip")) shader.surfaceparms |= Q3SURFACEPARM_PLAYERCLIP; else if (!strcasecmp(parameter[1], "sky")) shader.surfaceparms |= Q3SURFACEPARM_SKY; else if (!strcasecmp(parameter[1], "slick")) shader.surfaceparms |= Q3SURFACEPARM_SLICK; else if (!strcasecmp(parameter[1], "slime")) shader.surfaceparms |= Q3SURFACEPARM_SLIME; else if (!strcasecmp(parameter[1], "structural")) shader.surfaceparms |= Q3SURFACEPARM_STRUCTURAL; else if (!strcasecmp(parameter[1], "trans")) shader.surfaceparms |= Q3SURFACEPARM_TRANS; else if (!strcasecmp(parameter[1], "water")) shader.surfaceparms |= Q3SURFACEPARM_WATER; else if (!strcasecmp(parameter[1], "pointlight")) shader.surfaceparms |= Q3SURFACEPARM_POINTLIGHT; else if (!strcasecmp(parameter[1], "antiportal")) shader.surfaceparms |= Q3SURFACEPARM_ANTIPORTAL; else if (!strcasecmp(parameter[1], "skip")) ; // shader.surfaceparms |= Q3SURFACEPARM_SKIP; FIXME we don't have enough #defines for this any more, and the engine doesn't need this one anyway else { // try custom surfaceparms for (j = 0; j < numcustsurfaceflags; j++) { if (!strcasecmp(custsurfaceparmnames[j], parameter[1])) { shader.surfaceflags |= custsurfaceflags[j]; break; } } // failed all if (j == numcustsurfaceflags) Con_DPrintf("%s parsing warning: unknown surfaceparm \"%s\"\n", search->filenames[fileindex], parameter[1]); } } else if (!strcasecmp(parameter[0], "dpshadow")) shader.dpshadow = true; else if (!strcasecmp(parameter[0], "dpnoshadow")) shader.dpnoshadow = true; else if (!strcasecmp(parameter[0], "dpnortlight")) shader.dpnortlight = true; else if (!strcasecmp(parameter[0], "dpreflectcube")) strlcpy(shader.dpreflectcube, parameter[1], sizeof(shader.dpreflectcube)); else if (!strcasecmp(parameter[0], "dpmeshcollisions")) shader.dpmeshcollisions = true; // this sets dpshaderkill to true if dpshaderkillifcvarzero was used, and to false if dpnoshaderkillifcvarzero was used else if (((dpshaderkill = !strcasecmp(parameter[0], "dpshaderkillifcvarzero")) || !strcasecmp(parameter[0], "dpnoshaderkillifcvarzero")) && numparameters >= 2) { if (Cvar_VariableValue(parameter[1]) == 0.0f) shader.dpshaderkill = dpshaderkill; } // this sets dpshaderkill to true if dpshaderkillifcvar was used, and to false if dpnoshaderkillifcvar was used else if (((dpshaderkill = !strcasecmp(parameter[0], "dpshaderkillifcvar")) || !strcasecmp(parameter[0], "dpnoshaderkillifcvar")) && numparameters >= 2) { const char *op = NULL; if (numparameters >= 3) op = parameter[2]; if(!op) { if (Cvar_VariableValue(parameter[1]) != 0.0f) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, "==")) { if (Cvar_VariableValue(parameter[1]) == atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, "!=")) { if (Cvar_VariableValue(parameter[1]) != atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, ">")) { if (Cvar_VariableValue(parameter[1]) > atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, "<")) { if (Cvar_VariableValue(parameter[1]) < atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, ">=")) { if (Cvar_VariableValue(parameter[1]) >= atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else if (numparameters >= 4 && !strcmp(op, "<=")) { if (Cvar_VariableValue(parameter[1]) <= atof(parameter[3])) shader.dpshaderkill = dpshaderkill; } else { Con_DPrintf("%s parsing warning: unknown dpshaderkillifcvar op \"%s\", or not enough arguments\n", search->filenames[fileindex], op); } } else if (!strcasecmp(parameter[0], "sky") && numparameters >= 2) { // some q3 skies don't have the sky parm set shader.surfaceparms |= Q3SURFACEPARM_SKY; strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); } else if (!strcasecmp(parameter[0], "skyparms") && numparameters >= 2) { // some q3 skies don't have the sky parm set shader.surfaceparms |= Q3SURFACEPARM_SKY; if (!atoi(parameter[1]) && strcasecmp(parameter[1], "-")) strlcpy(shader.skyboxname, parameter[1], sizeof(shader.skyboxname)); } else if (!strcasecmp(parameter[0], "cull") && numparameters >= 2) { if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "twosided")) shader.textureflags |= Q3TEXTUREFLAG_TWOSIDED; } else if (!strcasecmp(parameter[0], "nomipmaps")) shader.surfaceparms |= Q3SURFACEPARM_NOMIPMAPS; else if (!strcasecmp(parameter[0], "nopicmip")) shader.textureflags |= Q3TEXTUREFLAG_NOPICMIP; else if (!strcasecmp(parameter[0], "polygonoffset")) shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; else if (!strcasecmp(parameter[0], "dppolygonoffset")) { shader.textureflags |= Q3TEXTUREFLAG_POLYGONOFFSET; if(numparameters >= 2) { shader.biaspolygonfactor = atof(parameter[1]); if(numparameters >= 3) shader.biaspolygonoffset = atof(parameter[2]); else shader.biaspolygonoffset = 0; } } else if (!strcasecmp(parameter[0], "dptransparentsort") && numparameters >= 2) { shader.textureflags |= Q3TEXTUREFLAG_TRANSPARENTSORT; if (!strcasecmp(parameter[1], "sky")) shader.transparentsort = TRANSPARENTSORT_SKY; else if (!strcasecmp(parameter[1], "distance")) shader.transparentsort = TRANSPARENTSORT_DISTANCE; else if (!strcasecmp(parameter[1], "hud")) shader.transparentsort = TRANSPARENTSORT_HUD; else Con_DPrintf("%s parsing warning: unknown dptransparentsort category \"%s\", or not enough arguments\n", search->filenames[fileindex], parameter[1]); } else if (!strcasecmp(parameter[0], "dprefract") && numparameters >= 5) { shader.textureflags |= Q3TEXTUREFLAG_REFRACTION; shader.refractfactor = atof(parameter[1]); Vector4Set(shader.refractcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), 1); } else if (!strcasecmp(parameter[0], "dpreflect") && numparameters >= 6) { shader.textureflags |= Q3TEXTUREFLAG_REFLECTION; shader.reflectfactor = atof(parameter[1]); Vector4Set(shader.reflectcolor4f, atof(parameter[2]), atof(parameter[3]), atof(parameter[4]), atof(parameter[5])); } else if (!strcasecmp(parameter[0], "dpcamera")) { shader.textureflags |= Q3TEXTUREFLAG_CAMERA; } else if (!strcasecmp(parameter[0], "dpwater") && numparameters >= 12) { shader.textureflags |= Q3TEXTUREFLAG_WATERSHADER; shader.reflectmin = atof(parameter[1]); shader.reflectmax = atof(parameter[2]); shader.refractfactor = atof(parameter[3]); shader.reflectfactor = atof(parameter[4]); Vector4Set(shader.refractcolor4f, atof(parameter[5]), atof(parameter[6]), atof(parameter[7]), 1); Vector4Set(shader.reflectcolor4f, atof(parameter[8]), atof(parameter[9]), atof(parameter[10]), 1); shader.r_water_wateralpha = atof(parameter[11]); } else if (!strcasecmp(parameter[0], "dpwaterscroll") && numparameters >= 3) { shader.r_water_waterscroll[0] = 1/atof(parameter[1]); shader.r_water_waterscroll[1] = 1/atof(parameter[2]); } else if (!strcasecmp(parameter[0], "dpglossintensitymod") && numparameters >= 2) { shader.specularscalemod = atof(parameter[1]); } else if (!strcasecmp(parameter[0], "dpglossexponentmod") && numparameters >= 2) { shader.specularpowermod = atof(parameter[1]); } else if (!strcasecmp(parameter[0], "dprtlightambient") && numparameters >= 2) { shader.rtlightambient = atof(parameter[1]); } else if (!strcasecmp(parameter[0], "dpoffsetmapping") && numparameters >= 2) { if (!strcasecmp(parameter[1], "disable") || !strcasecmp(parameter[1], "none") || !strcasecmp(parameter[1], "off")) shader.offsetmapping = OFFSETMAPPING_OFF; else if (!strcasecmp(parameter[1], "default") || !strcasecmp(parameter[1], "normal")) shader.offsetmapping = OFFSETMAPPING_DEFAULT; else if (!strcasecmp(parameter[1], "linear")) shader.offsetmapping = OFFSETMAPPING_LINEAR; else if (!strcasecmp(parameter[1], "relief")) shader.offsetmapping = OFFSETMAPPING_RELIEF; if (numparameters >= 3) shader.offsetscale = atof(parameter[2]); if (numparameters >= 5) { if(!strcasecmp(parameter[3], "bias")) shader.offsetbias = atof(parameter[4]); else if(!strcasecmp(parameter[3], "match")) shader.offsetbias = 1.0f - atof(parameter[4]); else if(!strcasecmp(parameter[3], "match8")) shader.offsetbias = 1.0f - atof(parameter[4]) / 255.0f; else if(!strcasecmp(parameter[3], "match16")) shader.offsetbias = 1.0f - atof(parameter[4]) / 65535.0f; } } else if (!strcasecmp(parameter[0], "deformvertexes") && numparameters >= 2) { int i, deformindex; for (deformindex = 0;deformindex < Q3MAXDEFORMS;deformindex++) if (!shader.deforms[deformindex].deform) break; if (deformindex < Q3MAXDEFORMS) { for (i = 0;i < numparameters - 2 && i < Q3DEFORM_MAXPARMS;i++) shader.deforms[deformindex].parms[i] = atof(parameter[i+2]); if (!strcasecmp(parameter[1], "projectionshadow")) shader.deforms[deformindex].deform = Q3DEFORM_PROJECTIONSHADOW; else if (!strcasecmp(parameter[1], "autosprite" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE; else if (!strcasecmp(parameter[1], "autosprite2" )) shader.deforms[deformindex].deform = Q3DEFORM_AUTOSPRITE2; else if (!strcasecmp(parameter[1], "text0" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT0; else if (!strcasecmp(parameter[1], "text1" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT1; else if (!strcasecmp(parameter[1], "text2" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT2; else if (!strcasecmp(parameter[1], "text3" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT3; else if (!strcasecmp(parameter[1], "text4" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT4; else if (!strcasecmp(parameter[1], "text5" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT5; else if (!strcasecmp(parameter[1], "text6" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT6; else if (!strcasecmp(parameter[1], "text7" )) shader.deforms[deformindex].deform = Q3DEFORM_TEXT7; else if (!strcasecmp(parameter[1], "bulge" )) shader.deforms[deformindex].deform = Q3DEFORM_BULGE; else if (!strcasecmp(parameter[1], "normal" )) shader.deforms[deformindex].deform = Q3DEFORM_NORMAL; else if (!strcasecmp(parameter[1], "wave" )) { shader.deforms[deformindex].deform = Q3DEFORM_WAVE; shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[3]); for (i = 0;i < numparameters - 4 && i < Q3WAVEPARMS;i++) shader.deforms[deformindex].waveparms[i] = atof(parameter[i+4]); } else if (!strcasecmp(parameter[1], "move" )) { shader.deforms[deformindex].deform = Q3DEFORM_MOVE; shader.deforms[deformindex].wavefunc = Mod_LoadQ3Shaders_EnumerateWaveFunc(parameter[5]); for (i = 0;i < numparameters - 6 && i < Q3WAVEPARMS;i++) shader.deforms[deformindex].waveparms[i] = atof(parameter[i+6]); } } } } // hide this shader if a cvar said it should be killed if (shader.dpshaderkill) shader.numlayers = 0; // pick the primary layer to render with if (shader.numlayers) { shader.backgroundlayer = -1; shader.primarylayer = 0; // if lightmap comes first this is definitely an ordinary texture // if the first two layers have the correct blendfuncs and use vertex alpha, it is a blended terrain shader if ((shader.layers[shader.primarylayer].texturename != NULL) && !strcasecmp(shader.layers[shader.primarylayer].texturename[0], "$lightmap")) { shader.backgroundlayer = -1; shader.primarylayer = 1; } else if (shader.numlayers >= 2 && shader.layers[1].alphagen.alphagen == Q3ALPHAGEN_VERTEX && (shader.layers[0].blendfunc[0] == GL_ONE && shader.layers[0].blendfunc[1] == GL_ZERO && !shader.layers[0].alphatest) && ((shader.layers[1].blendfunc[0] == GL_SRC_ALPHA && shader.layers[1].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) || (shader.layers[1].blendfunc[0] == GL_ONE && shader.layers[1].blendfunc[1] == GL_ZERO && shader.layers[1].alphatest))) { // terrain blending or other effects shader.backgroundlayer = 0; shader.primarylayer = 1; } } // fix up multiple reflection types if(shader.textureflags & Q3TEXTUREFLAG_WATERSHADER) shader.textureflags &= ~(Q3TEXTUREFLAG_REFRACTION | Q3TEXTUREFLAG_REFLECTION | Q3TEXTUREFLAG_CAMERA); Q3Shader_AddToHash (&shader); } Mem_Free(f); } FS_FreeSearch(search); // free custinfoparm values for (j = 0; j < numcustsurfaceflags; j++) Mem_Free(custsurfaceparmnames[j]); } q3shaderinfo_t *Mod_LookupQ3Shader(const char *name) { unsigned short hash; q3shader_hash_entry_t* entry; if (!q3shaders_mem) Mod_LoadQ3Shaders(); hash = CRC_Block_CaseInsensitive ((const unsigned char *)name, strlen (name)); entry = q3shader_data->hash + (hash % Q3SHADER_HASH_SIZE); while (entry != NULL) { if (strcasecmp (entry->shader.name, name) == 0) return &entry->shader; entry = entry->chain; } return NULL; } qboolean Mod_LoadTextureFromQ3Shader(texture_t *texture, const char *name, qboolean warnmissing, qboolean fallback, int defaulttexflags) { int j; int texflagsmask, texflagsor; qboolean success = true; q3shaderinfo_t *shader; if (!name) name = ""; strlcpy(texture->name, name, sizeof(texture->name)); texture->basealpha = 1.0f; shader = name[0] ? Mod_LookupQ3Shader(name) : NULL; texflagsmask = ~0; if(!(defaulttexflags & TEXF_PICMIP)) texflagsmask &= ~TEXF_PICMIP; if(!(defaulttexflags & TEXF_COMPRESS)) texflagsmask &= ~TEXF_COMPRESS; texflagsor = 0; if(defaulttexflags & TEXF_ISWORLD) texflagsor |= TEXF_ISWORLD; if(defaulttexflags & TEXF_ISSPRITE) texflagsor |= TEXF_ISSPRITE; // unless later loaded from the shader texture->offsetmapping = (mod_noshader_default_offsetmapping.value) ? OFFSETMAPPING_DEFAULT : OFFSETMAPPING_OFF; texture->offsetscale = 1; texture->offsetbias = 0; texture->specularscalemod = 1; texture->specularpowermod = 1; texture->rtlightambient = 0; texture->transparentsort = TRANSPARENTSORT_DISTANCE; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". if (shader) { if (developer_loading.integer) Con_Printf("%s: loaded shader for %s\n", loadmodel->name, name); // allow disabling of picmip or compression by defaulttexflags texture->textureflags = (shader->textureflags & texflagsmask) | texflagsor; if (shader->surfaceparms & Q3SURFACEPARM_SKY) { texture->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; if (shader->skyboxname[0]) { // quake3 seems to append a _ to the skybox name, so this must do so as well dpsnprintf(loadmodel->brush.skybox, sizeof(loadmodel->brush.skybox), "%s_", shader->skyboxname); } } else if ((texture->surfaceflags & Q3SURFACEFLAG_NODRAW) || shader->numlayers == 0) texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; else texture->basematerialflags = MATERIALFLAG_WALL; if (shader->layers[0].alphatest) texture->basematerialflags |= MATERIALFLAG_ALPHATEST | MATERIALFLAG_NOSHADOW; if (shader->textureflags & Q3TEXTUREFLAG_TWOSIDED) texture->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_NOCULLFACE; if (shader->textureflags & Q3TEXTUREFLAG_POLYGONOFFSET) { texture->biaspolygonoffset += shader->biaspolygonoffset; texture->biaspolygonfactor += shader->biaspolygonfactor; } if (shader->textureflags & Q3TEXTUREFLAG_REFRACTION) texture->basematerialflags |= MATERIALFLAG_REFRACTION; if (shader->textureflags & Q3TEXTUREFLAG_REFLECTION) texture->basematerialflags |= MATERIALFLAG_REFLECTION; if (shader->textureflags & Q3TEXTUREFLAG_WATERSHADER) texture->basematerialflags |= MATERIALFLAG_WATERSHADER; if (shader->textureflags & Q3TEXTUREFLAG_CAMERA) texture->basematerialflags |= MATERIALFLAG_CAMERA; texture->customblendfunc[0] = GL_ONE; texture->customblendfunc[1] = GL_ZERO; texture->transparentsort = shader->transparentsort; if (shader->numlayers > 0) { texture->customblendfunc[0] = shader->layers[0].blendfunc[0]; texture->customblendfunc[1] = shader->layers[0].blendfunc[1]; /* Q3 shader blendfuncs actually used in the game (* = supported by DP) * additive GL_ONE GL_ONE additive weird GL_ONE GL_SRC_ALPHA additive weird 2 GL_ONE GL_ONE_MINUS_SRC_ALPHA * alpha GL_SRC_ALPHA GL_ONE_MINUS_SRC_ALPHA alpha inverse GL_ONE_MINUS_SRC_ALPHA GL_SRC_ALPHA brighten GL_DST_COLOR GL_ONE brighten GL_ONE GL_SRC_COLOR brighten weird GL_DST_COLOR GL_ONE_MINUS_DST_ALPHA brighten weird 2 GL_DST_COLOR GL_SRC_ALPHA * modulate GL_DST_COLOR GL_ZERO * modulate GL_ZERO GL_SRC_COLOR modulate inverse GL_ZERO GL_ONE_MINUS_SRC_COLOR modulate inverse alpha GL_ZERO GL_SRC_ALPHA modulate weird inverse GL_ONE_MINUS_DST_COLOR GL_ZERO * modulate x2 GL_DST_COLOR GL_SRC_COLOR * no blend GL_ONE GL_ZERO nothing GL_ZERO GL_ONE */ // if not opaque, figure out what blendfunc to use if (shader->layers[0].blendfunc[0] != GL_ONE || shader->layers[0].blendfunc[1] != GL_ZERO) { if (shader->layers[0].blendfunc[0] == GL_ONE && shader->layers[0].blendfunc[1] == GL_ONE) texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE) texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; else if (shader->layers[0].blendfunc[0] == GL_SRC_ALPHA && shader->layers[0].blendfunc[1] == GL_ONE_MINUS_SRC_ALPHA) texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; else texture->basematerialflags |= MATERIALFLAG_CUSTOMBLEND | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; } } if (!shader->lighting) texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; if (shader->primarylayer >= 0) { q3shaderinfo_layer_t* primarylayer = shader->layers + shader->primarylayer; // copy over many primarylayer parameters texture->rgbgen = primarylayer->rgbgen; texture->alphagen = primarylayer->alphagen; texture->tcgen = primarylayer->tcgen; memcpy(texture->tcmods, primarylayer->tcmods, sizeof(texture->tcmods)); // load the textures texture->numskinframes = primarylayer->numframes; texture->skinframerate = primarylayer->framerate; for (j = 0;j < primarylayer->numframes;j++) { if(cls.state == ca_dedicated) { texture->skinframes[j] = NULL; } else if (!(texture->skinframes[j] = R_SkinFrame_LoadExternal(primarylayer->texturename[j], (primarylayer->texflags & texflagsmask) | texflagsor, false))) { Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (frame %i) for shader ^2\"%s\"\n", loadmodel->name, primarylayer->texturename[j], j, texture->name); texture->skinframes[j] = R_SkinFrame_LoadMissing(); } } } if (shader->backgroundlayer >= 0) { q3shaderinfo_layer_t* backgroundlayer = shader->layers + shader->backgroundlayer; // copy over one secondarylayer parameter memcpy(texture->backgroundtcmods, backgroundlayer->tcmods, sizeof(texture->backgroundtcmods)); // load the textures texture->backgroundnumskinframes = backgroundlayer->numframes; texture->backgroundskinframerate = backgroundlayer->framerate; for (j = 0;j < backgroundlayer->numframes;j++) { if(cls.state == ca_dedicated) { texture->skinframes[j] = NULL; } else if (!(texture->backgroundskinframes[j] = R_SkinFrame_LoadExternal(backgroundlayer->texturename[j], (backgroundlayer->texflags & texflagsmask) | texflagsor, false))) { Con_Printf("^1%s:^7 could not load texture ^3\"%s\"^7 (background frame %i) for shader ^2\"%s\"\n", loadmodel->name, backgroundlayer->texturename[j], j, texture->name); texture->backgroundskinframes[j] = R_SkinFrame_LoadMissing(); } } } if (shader->dpshadow) texture->basematerialflags &= ~MATERIALFLAG_NOSHADOW; if (shader->dpnoshadow) texture->basematerialflags |= MATERIALFLAG_NOSHADOW; if (shader->dpnortlight) texture->basematerialflags |= MATERIALFLAG_NORTLIGHT; if (shader->vertexalpha) texture->basematerialflags |= MATERIALFLAG_ALPHAGEN_VERTEX; memcpy(texture->deforms, shader->deforms, sizeof(texture->deforms)); texture->reflectmin = shader->reflectmin; texture->reflectmax = shader->reflectmax; texture->refractfactor = shader->refractfactor; Vector4Copy(shader->refractcolor4f, texture->refractcolor4f); texture->reflectfactor = shader->reflectfactor; Vector4Copy(shader->reflectcolor4f, texture->reflectcolor4f); texture->r_water_wateralpha = shader->r_water_wateralpha; Vector2Copy(shader->r_water_waterscroll, texture->r_water_waterscroll); texture->offsetmapping = shader->offsetmapping; texture->offsetscale = shader->offsetscale; texture->offsetbias = shader->offsetbias; texture->specularscalemod = shader->specularscalemod; texture->specularpowermod = shader->specularpowermod; texture->rtlightambient = shader->rtlightambient; if (shader->dpreflectcube[0]) texture->reflectcubetexture = R_GetCubemap(shader->dpreflectcube); // set up default supercontents (on q3bsp this is overridden by the q3bsp loader) texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents = SUPERCONTENTS_LAVA ; if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents = SUPERCONTENTS_SLIME ; if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents = SUPERCONTENTS_WATER ; if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents = 0 ; if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents = SUPERCONTENTS_PLAYERCLIP ; if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents = SUPERCONTENTS_MONSTERCLIP ; if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents = SUPERCONTENTS_SKY ; // if (shader->surfaceparms & Q3SURFACEPARM_ALPHASHADOW ) texture->supercontents |= SUPERCONTENTS_ALPHASHADOW ; // if (shader->surfaceparms & Q3SURFACEPARM_AREAPORTAL ) texture->supercontents |= SUPERCONTENTS_AREAPORTAL ; // if (shader->surfaceparms & Q3SURFACEPARM_CLUSTERPORTAL) texture->supercontents |= SUPERCONTENTS_CLUSTERPORTAL; // if (shader->surfaceparms & Q3SURFACEPARM_DETAIL ) texture->supercontents |= SUPERCONTENTS_DETAIL ; if (shader->surfaceparms & Q3SURFACEPARM_DONOTENTER ) texture->supercontents |= SUPERCONTENTS_DONOTENTER ; // if (shader->surfaceparms & Q3SURFACEPARM_FOG ) texture->supercontents |= SUPERCONTENTS_FOG ; if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->supercontents |= SUPERCONTENTS_LAVA ; // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTFILTER ) texture->supercontents |= SUPERCONTENTS_LIGHTFILTER ; // if (shader->surfaceparms & Q3SURFACEPARM_METALSTEPS ) texture->supercontents |= SUPERCONTENTS_METALSTEPS ; // if (shader->surfaceparms & Q3SURFACEPARM_NODAMAGE ) texture->supercontents |= SUPERCONTENTS_NODAMAGE ; // if (shader->surfaceparms & Q3SURFACEPARM_NODLIGHT ) texture->supercontents |= SUPERCONTENTS_NODLIGHT ; // if (shader->surfaceparms & Q3SURFACEPARM_NODRAW ) texture->supercontents |= SUPERCONTENTS_NODRAW ; if (shader->surfaceparms & Q3SURFACEPARM_NODROP ) texture->supercontents |= SUPERCONTENTS_NODROP ; // if (shader->surfaceparms & Q3SURFACEPARM_NOIMPACT ) texture->supercontents |= SUPERCONTENTS_NOIMPACT ; // if (shader->surfaceparms & Q3SURFACEPARM_NOLIGHTMAP ) texture->supercontents |= SUPERCONTENTS_NOLIGHTMAP ; // if (shader->surfaceparms & Q3SURFACEPARM_NOMARKS ) texture->supercontents |= SUPERCONTENTS_NOMARKS ; // if (shader->surfaceparms & Q3SURFACEPARM_NOMIPMAPS ) texture->supercontents |= SUPERCONTENTS_NOMIPMAPS ; if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->supercontents &=~SUPERCONTENTS_SOLID ; // if (shader->surfaceparms & Q3SURFACEPARM_ORIGIN ) texture->supercontents |= SUPERCONTENTS_ORIGIN ; if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->supercontents |= SUPERCONTENTS_PLAYERCLIP ; if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->supercontents |= SUPERCONTENTS_SKY ; // if (shader->surfaceparms & Q3SURFACEPARM_SLICK ) texture->supercontents |= SUPERCONTENTS_SLICK ; if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->supercontents |= SUPERCONTENTS_SLIME ; // if (shader->surfaceparms & Q3SURFACEPARM_STRUCTURAL ) texture->supercontents |= SUPERCONTENTS_STRUCTURAL ; // if (shader->surfaceparms & Q3SURFACEPARM_TRANS ) texture->supercontents |= SUPERCONTENTS_TRANS ; if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->supercontents |= SUPERCONTENTS_WATER ; // if (shader->surfaceparms & Q3SURFACEPARM_POINTLIGHT ) texture->supercontents |= SUPERCONTENTS_POINTLIGHT ; // if (shader->surfaceparms & Q3SURFACEPARM_HINT ) texture->supercontents |= SUPERCONTENTS_HINT ; // if (shader->surfaceparms & Q3SURFACEPARM_DUST ) texture->supercontents |= SUPERCONTENTS_DUST ; if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->supercontents |= SUPERCONTENTS_BOTCLIP | SUPERCONTENTS_MONSTERCLIP; // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTGRID ) texture->supercontents |= SUPERCONTENTS_LIGHTGRID ; // if (shader->surfaceparms & Q3SURFACEPARM_ANTIPORTAL ) texture->supercontents |= SUPERCONTENTS_ANTIPORTAL ; texture->surfaceflags = shader->surfaceflags; if (shader->surfaceparms & Q3SURFACEPARM_ALPHASHADOW ) texture->surfaceflags |= Q3SURFACEFLAG_ALPHASHADOW ; // if (shader->surfaceparms & Q3SURFACEPARM_AREAPORTAL ) texture->surfaceflags |= Q3SURFACEFLAG_AREAPORTAL ; // if (shader->surfaceparms & Q3SURFACEPARM_CLUSTERPORTAL) texture->surfaceflags |= Q3SURFACEFLAG_CLUSTERPORTAL; // if (shader->surfaceparms & Q3SURFACEPARM_DETAIL ) texture->surfaceflags |= Q3SURFACEFLAG_DETAIL ; // if (shader->surfaceparms & Q3SURFACEPARM_DONOTENTER ) texture->surfaceflags |= Q3SURFACEFLAG_DONOTENTER ; // if (shader->surfaceparms & Q3SURFACEPARM_FOG ) texture->surfaceflags |= Q3SURFACEFLAG_FOG ; // if (shader->surfaceparms & Q3SURFACEPARM_LAVA ) texture->surfaceflags |= Q3SURFACEFLAG_LAVA ; if (shader->surfaceparms & Q3SURFACEPARM_LIGHTFILTER ) texture->surfaceflags |= Q3SURFACEFLAG_LIGHTFILTER ; if (shader->surfaceparms & Q3SURFACEPARM_METALSTEPS ) texture->surfaceflags |= Q3SURFACEFLAG_METALSTEPS ; if (shader->surfaceparms & Q3SURFACEPARM_NODAMAGE ) texture->surfaceflags |= Q3SURFACEFLAG_NODAMAGE ; if (shader->surfaceparms & Q3SURFACEPARM_NODLIGHT ) texture->surfaceflags |= Q3SURFACEFLAG_NODLIGHT ; if (shader->surfaceparms & Q3SURFACEPARM_NODRAW ) texture->surfaceflags |= Q3SURFACEFLAG_NODRAW ; // if (shader->surfaceparms & Q3SURFACEPARM_NODROP ) texture->surfaceflags |= Q3SURFACEFLAG_NODROP ; if (shader->surfaceparms & Q3SURFACEPARM_NOIMPACT ) texture->surfaceflags |= Q3SURFACEFLAG_NOIMPACT ; if (shader->surfaceparms & Q3SURFACEPARM_NOLIGHTMAP ) texture->surfaceflags |= Q3SURFACEFLAG_NOLIGHTMAP ; if (shader->surfaceparms & Q3SURFACEPARM_NOMARKS ) texture->surfaceflags |= Q3SURFACEFLAG_NOMARKS ; // if (shader->surfaceparms & Q3SURFACEPARM_NOMIPMAPS ) texture->surfaceflags |= Q3SURFACEFLAG_NOMIPMAPS ; if (shader->surfaceparms & Q3SURFACEPARM_NONSOLID ) texture->surfaceflags |= Q3SURFACEFLAG_NONSOLID ; // if (shader->surfaceparms & Q3SURFACEPARM_ORIGIN ) texture->surfaceflags |= Q3SURFACEFLAG_ORIGIN ; // if (shader->surfaceparms & Q3SURFACEPARM_PLAYERCLIP ) texture->surfaceflags |= Q3SURFACEFLAG_PLAYERCLIP ; if (shader->surfaceparms & Q3SURFACEPARM_SKY ) texture->surfaceflags |= Q3SURFACEFLAG_SKY ; if (shader->surfaceparms & Q3SURFACEPARM_SLICK ) texture->surfaceflags |= Q3SURFACEFLAG_SLICK ; // if (shader->surfaceparms & Q3SURFACEPARM_SLIME ) texture->surfaceflags |= Q3SURFACEFLAG_SLIME ; // if (shader->surfaceparms & Q3SURFACEPARM_STRUCTURAL ) texture->surfaceflags |= Q3SURFACEFLAG_STRUCTURAL ; // if (shader->surfaceparms & Q3SURFACEPARM_TRANS ) texture->surfaceflags |= Q3SURFACEFLAG_TRANS ; // if (shader->surfaceparms & Q3SURFACEPARM_WATER ) texture->surfaceflags |= Q3SURFACEFLAG_WATER ; if (shader->surfaceparms & Q3SURFACEPARM_POINTLIGHT ) texture->surfaceflags |= Q3SURFACEFLAG_POINTLIGHT ; if (shader->surfaceparms & Q3SURFACEPARM_HINT ) texture->surfaceflags |= Q3SURFACEFLAG_HINT ; if (shader->surfaceparms & Q3SURFACEPARM_DUST ) texture->surfaceflags |= Q3SURFACEFLAG_DUST ; // if (shader->surfaceparms & Q3SURFACEPARM_BOTCLIP ) texture->surfaceflags |= Q3SURFACEFLAG_BOTCLIP ; // if (shader->surfaceparms & Q3SURFACEPARM_LIGHTGRID ) texture->surfaceflags |= Q3SURFACEFLAG_LIGHTGRID ; // if (shader->surfaceparms & Q3SURFACEPARM_ANTIPORTAL ) texture->surfaceflags |= Q3SURFACEFLAG_ANTIPORTAL ; if (shader->dpmeshcollisions) texture->basematerialflags |= MATERIALFLAG_MESHCOLLISIONS; if (shader->dpshaderkill && developer_extra.integer) Con_DPrintf("^1%s:^7 killing shader ^3\"%s\" because of cvar\n", loadmodel->name, name); } else if (!strcmp(texture->name, "noshader") || !texture->name[0]) { if (developer_extra.integer) Con_DPrintf("^1%s:^7 using fallback noshader material for ^3\"%s\"\n", loadmodel->name, name); texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; } else if (!strcmp(texture->name, "common/nodraw") || !strcmp(texture->name, "textures/common/nodraw")) { if (developer_extra.integer) Con_DPrintf("^1%s:^7 using fallback nodraw material for ^3\"%s\"\n", loadmodel->name, name); texture->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; texture->supercontents = SUPERCONTENTS_SOLID; } else { if (developer_extra.integer) Con_DPrintf("^1%s:^7 No shader found for texture ^3\"%s\"\n", loadmodel->name, texture->name); if (texture->surfaceflags & Q3SURFACEFLAG_NODRAW) { texture->basematerialflags |= MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; texture->supercontents = SUPERCONTENTS_SOLID; } else if (texture->surfaceflags & Q3SURFACEFLAG_SKY) { texture->basematerialflags |= MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; texture->supercontents = SUPERCONTENTS_SKY; } else { texture->basematerialflags |= MATERIALFLAG_WALL; texture->supercontents = SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; } texture->numskinframes = 1; if(cls.state == ca_dedicated) { texture->skinframes[0] = NULL; success = false; } else { if (fallback) { if ((texture->skinframes[0] = R_SkinFrame_LoadExternal(texture->name, defaulttexflags, false))) { if(texture->skinframes[0]->hasalpha) texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; if (texture->q2contents) texture->supercontents = Mod_Q2BSP_SuperContentsFromNativeContents(loadmodel, texture->q2contents); } else success = false; } else success = false; if (!success && warnmissing) Con_Printf("^1%s:^7 could not load texture ^3\"%s\"\n", loadmodel->name, texture->name); } } // init the animation variables texture->currentframe = texture; if (texture->numskinframes < 1) texture->numskinframes = 1; if (!texture->skinframes[0]) texture->skinframes[0] = R_SkinFrame_LoadMissing(); texture->currentskinframe = texture->skinframes[0]; texture->backgroundcurrentskinframe = texture->backgroundskinframes[0]; return success; } skinfile_t *Mod_LoadSkinFiles(void) { int i, words, line, wordsoverflow; char *text; const char *data; skinfile_t *skinfile = NULL, *first = NULL; skinfileitem_t *skinfileitem; char word[10][MAX_QPATH]; char vabuf[1024]; /* sample file: U_bodyBox,models/players/Legoman/BikerA2.tga U_RArm,models/players/Legoman/BikerA1.tga U_LArm,models/players/Legoman/BikerA1.tga U_armor,common/nodraw U_sword,common/nodraw U_shield,common/nodraw U_homb,common/nodraw U_backpack,common/nodraw U_colcha,common/nodraw tag_head, tag_weapon, tag_torso, */ memset(word, 0, sizeof(word)); for (i = 0;i < 256 && (data = text = (char *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s_%i.skin", loadmodel->name, i), tempmempool, true, NULL));i++) { // If it's the first file we parse if (skinfile == NULL) { skinfile = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); first = skinfile; } else { skinfile->next = (skinfile_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfile_t)); skinfile = skinfile->next; } skinfile->next = NULL; for(line = 0;;line++) { // parse line if (!COM_ParseToken_QuakeC(&data, true)) break; if (!strcmp(com_token, "\n")) continue; words = 0; wordsoverflow = false; do { if (words < 10) strlcpy(word[words++], com_token, sizeof (word[0])); else wordsoverflow = true; } while (COM_ParseToken_QuakeC(&data, true) && strcmp(com_token, "\n")); if (wordsoverflow) { Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: line with too many statements, skipping\n", loadmodel->name, i, line); continue; } // words is always >= 1 if (!strcmp(word[0], "replace")) { if (words == 3) { if (developer_loading.integer) Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[1], word[2]); skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); skinfileitem->next = skinfile->items; skinfile->items = skinfileitem; strlcpy (skinfileitem->name, word[1], sizeof (skinfileitem->name)); strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); } else Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: wrong number of parameters to command \"%s\", see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line, word[0]); } else if (words >= 2 && !strncmp(word[0], "tag_", 4)) { // tag name, like "tag_weapon," // not used for anything (not even in Quake3) } else if (words >= 2 && !strcmp(word[1], ",")) { // mesh shader name, like "U_RArm,models/players/Legoman/BikerA1.tga" if (developer_loading.integer) Con_Printf("Mod_LoadSkinFiles: parsed mesh \"%s\" shader replacement \"%s\"\n", word[0], word[2]); skinfileitem = (skinfileitem_t *)Mem_Alloc(loadmodel->mempool, sizeof(skinfileitem_t)); skinfileitem->next = skinfile->items; skinfile->items = skinfileitem; strlcpy (skinfileitem->name, word[0], sizeof (skinfileitem->name)); strlcpy (skinfileitem->replacement, word[2], sizeof (skinfileitem->replacement)); } else Con_Printf("Mod_LoadSkinFiles: parsing error in file \"%s_%i.skin\" on line #%i: does not look like tag or mesh specification, or replace command, see documentation in DP_GFX_SKINFILES extension in dpextensions.qc\n", loadmodel->name, i, line); } Mem_Free(text); } if (i) loadmodel->numskins = i; return first; } void Mod_FreeSkinFiles(skinfile_t *skinfile) { skinfile_t *next; skinfileitem_t *skinfileitem, *nextitem; for (;skinfile;skinfile = next) { next = skinfile->next; for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = nextitem) { nextitem = skinfileitem->next; Mem_Free(skinfileitem); } Mem_Free(skinfile); } } int Mod_CountSkinFiles(skinfile_t *skinfile) { int i; for (i = 0;skinfile;skinfile = skinfile->next, i++); return i; } void Mod_SnapVertices(int numcomponents, int numvertices, float *vertices, float snap) { int i; double isnap = 1.0 / snap; for (i = 0;i < numvertices*numcomponents;i++) vertices[i] = floor(vertices[i]*isnap)*snap; } int Mod_RemoveDegenerateTriangles(int numtriangles, const int *inelement3i, int *outelement3i, const float *vertex3f) { int i, outtriangles; float edgedir1[3], edgedir2[3], temp[3]; // a degenerate triangle is one with no width (thickness, surface area) // these are characterized by having all 3 points colinear (along a line) // or having two points identical // the simplest check is to calculate the triangle's area for (i = 0, outtriangles = 0;i < numtriangles;i++, inelement3i += 3) { // calculate first edge VectorSubtract(vertex3f + inelement3i[1] * 3, vertex3f + inelement3i[0] * 3, edgedir1); VectorSubtract(vertex3f + inelement3i[2] * 3, vertex3f + inelement3i[0] * 3, edgedir2); CrossProduct(edgedir1, edgedir2, temp); if (VectorLength2(temp) < 0.001f) continue; // degenerate triangle (no area) // valid triangle (has area) VectorCopy(inelement3i, outelement3i); outelement3i += 3; outtriangles++; } return outtriangles; } void Mod_VertexRangeFromElements(int numelements, const int *elements, int *firstvertexpointer, int *lastvertexpointer) { int i, e; int firstvertex, lastvertex; if (numelements > 0 && elements) { firstvertex = lastvertex = elements[0]; for (i = 1;i < numelements;i++) { e = elements[i]; firstvertex = min(firstvertex, e); lastvertex = max(lastvertex, e); } } else firstvertex = lastvertex = 0; if (firstvertexpointer) *firstvertexpointer = firstvertex; if (lastvertexpointer) *lastvertexpointer = lastvertex; } void Mod_MakeSortedSurfaces(dp_model_t *mod) { // make an optimal set of texture-sorted batches to draw... int j, t; int *firstsurfacefortexture; int *numsurfacesfortexture; if (!mod->sortedmodelsurfaces) mod->sortedmodelsurfaces = (int *) Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); firstsurfacefortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*firstsurfacefortexture)); numsurfacesfortexture = (int *) Mem_Alloc(tempmempool, mod->num_textures * sizeof(*numsurfacesfortexture)); memset(numsurfacesfortexture, 0, mod->num_textures * sizeof(*numsurfacesfortexture)); for (j = 0;j < mod->nummodelsurfaces;j++) { const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; t = (int)(surface->texture - mod->data_textures); numsurfacesfortexture[t]++; } j = 0; for (t = 0;t < mod->num_textures;t++) { firstsurfacefortexture[t] = j; j += numsurfacesfortexture[t]; } for (j = 0;j < mod->nummodelsurfaces;j++) { const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; t = (int)(surface->texture - mod->data_textures); mod->sortedmodelsurfaces[firstsurfacefortexture[t]++] = j + mod->firstmodelsurface; } Mem_Free(firstsurfacefortexture); Mem_Free(numsurfacesfortexture); } void Mod_BuildVBOs(void) { if (!loadmodel->surfmesh.num_vertices) return; if (gl_paranoid.integer && loadmodel->surfmesh.data_element3s && loadmodel->surfmesh.data_element3i) { int i; for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) { if (loadmodel->surfmesh.data_element3s[i] != loadmodel->surfmesh.data_element3i[i]) { Con_Printf("Mod_BuildVBOs: element %u is incorrect (%u should be %u)\n", i, loadmodel->surfmesh.data_element3s[i], loadmodel->surfmesh.data_element3i[i]); loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; } } } // build r_vertexmesh_t array // (compressed interleaved array for D3D) if (!loadmodel->surfmesh.data_vertexmesh && vid.useinterleavedarrays) { int vertexindex; int numvertices = loadmodel->surfmesh.num_vertices; r_vertexmesh_t *vertexmesh; loadmodel->surfmesh.data_vertexmesh = vertexmesh = (r_vertexmesh_t*)Mem_Alloc(loadmodel->mempool, numvertices * sizeof(r_vertexmesh_t)); for (vertexindex = 0;vertexindex < numvertices;vertexindex++, vertexmesh++) { VectorCopy(loadmodel->surfmesh.data_vertex3f + 3*vertexindex, vertexmesh->vertex3f); VectorScale(loadmodel->surfmesh.data_svector3f + 3*vertexindex, 1.0f, vertexmesh->svector3f); VectorScale(loadmodel->surfmesh.data_tvector3f + 3*vertexindex, 1.0f, vertexmesh->tvector3f); VectorScale(loadmodel->surfmesh.data_normal3f + 3*vertexindex, 1.0f, vertexmesh->normal3f); if (loadmodel->surfmesh.data_lightmapcolor4f) Vector4Copy(loadmodel->surfmesh.data_lightmapcolor4f + 4*vertexindex, vertexmesh->color4f); Vector2Copy(loadmodel->surfmesh.data_texcoordtexture2f + 2*vertexindex, vertexmesh->texcoordtexture2f); if (loadmodel->surfmesh.data_texcoordlightmap2f) Vector2Scale(loadmodel->surfmesh.data_texcoordlightmap2f + 2*vertexindex, 1.0f, vertexmesh->texcoordlightmap2f); if (loadmodel->surfmesh.data_skeletalindex4ub) Vector4Copy(loadmodel->surfmesh.data_skeletalindex4ub + 4*vertexindex, vertexmesh->skeletalindex4ub); if (loadmodel->surfmesh.data_skeletalweight4ub) Vector4Copy(loadmodel->surfmesh.data_skeletalweight4ub + 4*vertexindex, vertexmesh->skeletalweight4ub); } } // upload short indices as a buffer if (loadmodel->surfmesh.data_element3s && !loadmodel->surfmesh.data_element3s_indexbuffer) loadmodel->surfmesh.data_element3s_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3s, loadmodel->surfmesh.num_triangles * sizeof(short[3]), loadmodel->name, true, false, false, true); // upload int indices as a buffer if (loadmodel->surfmesh.data_element3i && !loadmodel->surfmesh.data_element3i_indexbuffer && !loadmodel->surfmesh.data_element3s) loadmodel->surfmesh.data_element3i_indexbuffer = R_Mesh_CreateMeshBuffer(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles * sizeof(int[3]), loadmodel->name, true, false, false, false); // only build a vbo if one has not already been created (this is important for brush models which load specially) // vertex buffer is several arrays and we put them in the same buffer // // is this wise? the texcoordtexture2f array is used with dynamic // vertex/svector/tvector/normal when rendering animated models, on the // other hand animated models don't use a lot of vertices anyway... if (!loadmodel->surfmesh.vbo_vertexbuffer && !vid.useinterleavedarrays) { int size; unsigned char *mem; size = 0; loadmodel->surfmesh.vbooffset_vertexmesh = size;if (loadmodel->surfmesh.data_vertexmesh ) size += loadmodel->surfmesh.num_vertices * sizeof(r_vertexmesh_t); loadmodel->surfmesh.vbooffset_vertex3f = size;if (loadmodel->surfmesh.data_vertex3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.vbooffset_svector3f = size;if (loadmodel->surfmesh.data_svector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.vbooffset_tvector3f = size;if (loadmodel->surfmesh.data_tvector3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.vbooffset_normal3f = size;if (loadmodel->surfmesh.data_normal3f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.vbooffset_texcoordtexture2f = size;if (loadmodel->surfmesh.data_texcoordtexture2f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); loadmodel->surfmesh.vbooffset_texcoordlightmap2f = size;if (loadmodel->surfmesh.data_texcoordlightmap2f) size += loadmodel->surfmesh.num_vertices * sizeof(float[2]); loadmodel->surfmesh.vbooffset_lightmapcolor4f = size;if (loadmodel->surfmesh.data_lightmapcolor4f ) size += loadmodel->surfmesh.num_vertices * sizeof(float[4]); loadmodel->surfmesh.vbooffset_skeletalindex4ub = size;if (loadmodel->surfmesh.data_skeletalindex4ub ) size += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); loadmodel->surfmesh.vbooffset_skeletalweight4ub = size;if (loadmodel->surfmesh.data_skeletalweight4ub ) size += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); mem = (unsigned char *)Mem_Alloc(tempmempool, size); if (loadmodel->surfmesh.data_vertexmesh ) memcpy(mem + loadmodel->surfmesh.vbooffset_vertexmesh , loadmodel->surfmesh.data_vertexmesh , loadmodel->surfmesh.num_vertices * sizeof(r_vertexmesh_t)); if (loadmodel->surfmesh.data_vertex3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_vertex3f , loadmodel->surfmesh.data_vertex3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); if (loadmodel->surfmesh.data_svector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_svector3f , loadmodel->surfmesh.data_svector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); if (loadmodel->surfmesh.data_tvector3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_tvector3f , loadmodel->surfmesh.data_tvector3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); if (loadmodel->surfmesh.data_normal3f ) memcpy(mem + loadmodel->surfmesh.vbooffset_normal3f , loadmodel->surfmesh.data_normal3f , loadmodel->surfmesh.num_vertices * sizeof(float[3])); if (loadmodel->surfmesh.data_texcoordtexture2f ) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordtexture2f , loadmodel->surfmesh.data_texcoordtexture2f , loadmodel->surfmesh.num_vertices * sizeof(float[2])); if (loadmodel->surfmesh.data_texcoordlightmap2f) memcpy(mem + loadmodel->surfmesh.vbooffset_texcoordlightmap2f, loadmodel->surfmesh.data_texcoordlightmap2f, loadmodel->surfmesh.num_vertices * sizeof(float[2])); if (loadmodel->surfmesh.data_lightmapcolor4f ) memcpy(mem + loadmodel->surfmesh.vbooffset_lightmapcolor4f , loadmodel->surfmesh.data_lightmapcolor4f , loadmodel->surfmesh.num_vertices * sizeof(float[4])); if (loadmodel->surfmesh.data_skeletalindex4ub ) memcpy(mem + loadmodel->surfmesh.vbooffset_skeletalindex4ub , loadmodel->surfmesh.data_skeletalindex4ub , loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4])); if (loadmodel->surfmesh.data_skeletalweight4ub ) memcpy(mem + loadmodel->surfmesh.vbooffset_skeletalweight4ub , loadmodel->surfmesh.data_skeletalweight4ub , loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4])); loadmodel->surfmesh.vbo_vertexbuffer = R_Mesh_CreateMeshBuffer(mem, size, loadmodel->name, false, false, false, false); Mem_Free(mem); } } extern cvar_t mod_obj_orientation; static void Mod_Decompile_OBJ(dp_model_t *model, const char *filename, const char *mtlfilename, const char *originalfilename) { int submodelindex, vertexindex, surfaceindex, triangleindex, textureindex, countvertices = 0, countsurfaces = 0, countfaces = 0, counttextures = 0; int a, b, c; const char *texname; const int *e; const float *v, *vn, *vt; size_t l; size_t outbufferpos = 0; size_t outbuffermax = 0x100000; char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; const msurface_t *surface; const int maxtextures = 256; char *texturenames = (char *) Z_Malloc(maxtextures * MAX_QPATH); dp_model_t *submodel; // construct the mtllib file l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# mtllib for %s exported by darkplaces engine\n", originalfilename); if (l > 0) outbufferpos += l; for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) { countsurfaces++; countvertices += surface->num_vertices; countfaces += surface->num_triangles; texname = (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"; for (textureindex = 0;textureindex < counttextures;textureindex++) if (!strcmp(texturenames + textureindex * MAX_QPATH, texname)) break; if (textureindex < counttextures) continue; // already wrote this material entry if (textureindex >= maxtextures) continue; // just a precaution textureindex = counttextures++; strlcpy(texturenames + textureindex * MAX_QPATH, texname, MAX_QPATH); if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "newmtl %s\nNs 96.078431\nKa 0 0 0\nKd 0.64 0.64 0.64\nKs 0.5 0.5 0.5\nNi 1\nd 1\nillum 2\nmap_Kd %s%s\n\n", texname, texname, strstr(texname, ".tga") ? "" : ".tga"); if (l > 0) outbufferpos += l; } // write the mtllib file FS_WriteFile(mtlfilename, outbuffer, outbufferpos); // construct the obj file outbufferpos = 0; l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "# model exported from %s by darkplaces engine\n# %i vertices, %i faces, %i surfaces\nmtllib %s\n", originalfilename, countvertices, countfaces, countsurfaces, mtlfilename); if (l > 0) outbufferpos += l; for (vertexindex = 0, v = model->surfmesh.data_vertex3f, vn = model->surfmesh.data_normal3f, vt = model->surfmesh.data_texcoordtexture2f;vertexindex < model->surfmesh.num_vertices;vertexindex++, v += 3, vn += 3, vt += 2) { if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } if(mod_obj_orientation.integer) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "v %f %f %f\nvn %f %f %f\nvt %f %f\n", v[0], v[2], v[1], vn[0], vn[2], vn[1], vt[0], 1-vt[1]); else l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "v %f %f %f\nvn %f %f %f\nvt %f %f\n", v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1-vt[1]); if (l > 0) outbufferpos += l; } for (submodelindex = 0;submodelindex < max(1, model->brush.numsubmodels);submodelindex++) { l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "o %i\n", submodelindex); if (l > 0) outbufferpos += l; submodel = model->brush.numsubmodels ? model->brush.submodels[submodelindex] : model; for (surfaceindex = 0;surfaceindex < submodel->nummodelsurfaces;surfaceindex++) { surface = model->data_surfaces + submodel->sortedmodelsurfaces[surfaceindex]; l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "usemtl %s\n", (surface->texture && surface->texture->name[0]) ? surface->texture->name : "default"); if (l > 0) outbufferpos += l; for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) { if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } a = e[0]+1; b = e[1]+1; c = e[2]+1; if(mod_obj_orientation.integer) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", a,a,a,b,b,b,c,c,c); else l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", a,a,a,c,c,c,b,b,b); if (l > 0) outbufferpos += l; } } } // write the obj file FS_WriteFile(filename, outbuffer, outbufferpos); // clean up Z_Free(outbuffer); Z_Free(texturenames); // print some stats Con_Printf("Wrote %s (%i bytes, %i vertices, %i faces, %i surfaces with %i distinct textures)\n", filename, (int)outbufferpos, countvertices, countfaces, countsurfaces, counttextures); } static void Mod_Decompile_SMD(dp_model_t *model, const char *filename, int firstpose, int numposes, qboolean writetriangles) { int countnodes = 0, counttriangles = 0, countframes = 0; int surfaceindex; int triangleindex; int transformindex; int poseindex; int cornerindex; const int *e; size_t l; size_t outbufferpos = 0; size_t outbuffermax = 0x100000; char *outbuffer = (char *) Z_Malloc(outbuffermax), *oldbuffer; const msurface_t *surface; l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "version 1\nnodes\n"); if (l > 0) outbufferpos += l; for (transformindex = 0;transformindex < model->num_bones;transformindex++) { if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } countnodes++; l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i \"%s\" %3i\n", transformindex, model->data_bones[transformindex].name, model->data_bones[transformindex].parent); if (l > 0) outbufferpos += l; } l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\nskeleton\n"); if (l > 0) outbufferpos += l; for (poseindex = 0;poseindex < numposes;poseindex++) { countframes++; l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "time %i\n", poseindex); if (l > 0) outbufferpos += l; for (transformindex = 0;transformindex < model->num_bones;transformindex++) { float angles[3]; float mtest[4][3]; matrix4x4_t posematrix; if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } // strangely the smd angles are for a transposed matrix, so we // have to generate a transposed matrix, then convert that... Matrix4x4_FromBonePose7s(&posematrix, model->num_posescale, model->data_poses7s + 7*(model->num_bones * poseindex + transformindex)); Matrix4x4_ToArray12FloatGL(&posematrix, mtest[0]); AnglesFromVectors(angles, mtest[0], mtest[2], false); if (angles[0] >= 180) angles[0] -= 360; if (angles[1] >= 180) angles[1] -= 360; if (angles[2] >= 180) angles[2] -= 360; #if 0 { float a = DEG2RAD(angles[ROLL]); float b = DEG2RAD(angles[PITCH]); float c = DEG2RAD(angles[YAW]); float cy, sy, cp, sp, cr, sr; float test[4][3]; // smd matrix construction, for comparing sy = sin(c); cy = cos(c); sp = sin(b); cp = cos(b); sr = sin(a); cr = cos(a); test[0][0] = cp*cy; test[0][1] = cp*sy; test[0][2] = -sp; test[1][0] = sr*sp*cy+cr*-sy; test[1][1] = sr*sp*sy+cr*cy; test[1][2] = sr*cp; test[2][0] = (cr*sp*cy+-sr*-sy); test[2][1] = (cr*sp*sy+-sr*cy); test[2][2] = cr*cp; test[3][0] = pose[9]; test[3][1] = pose[10]; test[3][2] = pose[11]; } #endif l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f\n", transformindex, mtest[3][0], mtest[3][1], mtest[3][2], DEG2RAD(angles[ROLL]), DEG2RAD(angles[PITCH]), DEG2RAD(angles[YAW])); if (l > 0) outbufferpos += l; } } l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); if (l > 0) outbufferpos += l; if (writetriangles) { l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "triangles\n"); if (l > 0) outbufferpos += l; for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->num_surfaces;surfaceindex++, surface++) { for (triangleindex = 0, e = model->surfmesh.data_element3i + surface->num_firsttriangle * 3;triangleindex < surface->num_triangles;triangleindex++, e += 3) { counttriangles++; if (outbufferpos >= outbuffermax >> 1) { outbuffermax *= 2; oldbuffer = outbuffer; outbuffer = (char *) Z_Malloc(outbuffermax); memcpy(outbuffer, oldbuffer, outbufferpos); Z_Free(oldbuffer); } l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%s\n", surface->texture && surface->texture->name[0] ? surface->texture->name : "default.bmp"); if (l > 0) outbufferpos += l; for (cornerindex = 0;cornerindex < 3;cornerindex++) { const int index = e[2-cornerindex]; const float *v = model->surfmesh.data_vertex3f + index * 3; const float *vn = model->surfmesh.data_normal3f + index * 3; const float *vt = model->surfmesh.data_texcoordtexture2f + index * 2; const int b = model->surfmesh.blends[index]; if (b < model->num_bones) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , b, v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); else { const blendweights_t *w = model->surfmesh.data_blendweights + b - model->num_bones; const unsigned char *wi = w->index; const unsigned char *wf = w->influence; if (wf[3]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 4 %i %f %i %f %i %f %i %f\n", wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f, wi[3], wf[3]/255.0f); else if (wf[2]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 3 %i %f %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f, wi[2], wf[2]/255.0f); else if (wf[1]) l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f 2 %i %f %i %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1], wi[0], wf[0]/255.0f, wi[1], wf[1]/255.0f); else l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "%3i %f %f %f %f %f %f %f %f\n" , wi[0], v[0], v[1], v[2], vn[0], vn[1], vn[2], vt[0], 1 - vt[1]); } if (l > 0) outbufferpos += l; } } } l = dpsnprintf(outbuffer + outbufferpos, outbuffermax - outbufferpos, "end\n"); if (l > 0) outbufferpos += l; } FS_WriteFile(filename, outbuffer, outbufferpos); Z_Free(outbuffer); Con_Printf("Wrote %s (%i bytes, %i nodes, %i frames, %i triangles)\n", filename, (int)outbufferpos, countnodes, countframes, counttriangles); } /* ================ Mod_Decompile_f decompiles a model to editable files ================ */ static void Mod_Decompile_f(void) { int i, j, k, l, first, count; dp_model_t *mod; char inname[MAX_QPATH]; char outname[MAX_QPATH]; char mtlname[MAX_QPATH]; char basename[MAX_QPATH]; char animname[MAX_QPATH]; char animname2[MAX_QPATH]; char zymtextbuffer[16384]; char dpmtextbuffer[16384]; char framegroupstextbuffer[16384]; int zymtextsize = 0; int dpmtextsize = 0; int framegroupstextsize = 0; char vabuf[1024]; if (Cmd_Argc() != 2) { Con_Print("usage: modeldecompile \n"); return; } strlcpy(inname, Cmd_Argv(1), sizeof(inname)); FS_StripExtension(inname, basename, sizeof(basename)); mod = Mod_ForName(inname, false, true, inname[0] == '*' ? cl.model_name[1] : NULL); if (!mod) { Con_Print("No such model\n"); return; } if (mod->brush.submodel) { // if we're decompiling a submodel, be sure to give it a proper name based on its parent FS_StripExtension(cl.model_name[1], outname, sizeof(outname)); dpsnprintf(basename, sizeof(basename), "%s/%s", outname, mod->name); outname[0] = 0; } if (!mod->surfmesh.num_triangles) { Con_Print("Empty model (or sprite)\n"); return; } // export OBJ if possible (not on sprites) if (mod->surfmesh.num_triangles) { dpsnprintf(outname, sizeof(outname), "%s_decompiled.obj", basename); dpsnprintf(mtlname, sizeof(mtlname), "%s_decompiled.mtl", basename); Mod_Decompile_OBJ(mod, outname, mtlname, inname); } // export SMD if possible (only for skeletal models) if (mod->surfmesh.num_triangles && mod->num_bones) { dpsnprintf(outname, sizeof(outname), "%s_decompiled/ref1.smd", basename); Mod_Decompile_SMD(mod, outname, 0, 1, true); l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "output out.zym\nscale 1\norigin 0 0 0\nmesh ref1.smd\n"); if (l > 0) zymtextsize += l; l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "outputdir .\nmodel out\nscale 1\norigin 0 0 0\nscene ref1.smd\n"); if (l > 0) dpmtextsize += l; for (i = 0;i < mod->numframes;i = j) { strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); first = mod->animscenes[i].firstframe; if (mod->animscenes[i].framecount > 1) { // framegroup anim count = mod->animscenes[i].framecount; j = i + 1; } else { // individual frame // check for additional frames with same name for (l = 0, k = (int)strlen(animname);animname[l];l++) if(animname[l] < '0' || animname[l] > '9') k = l + 1; if(k > 0 && animname[k-1] == '_') --k; animname[k] = 0; count = mod->num_poses - first; for (j = i + 1;j < mod->numframes;j++) { strlcpy(animname2, mod->animscenes[j].name, sizeof(animname2)); for (l = 0, k = (int)strlen(animname2);animname2[l];l++) if(animname2[l] < '0' || animname2[l] > '9') k = l + 1; if(k > 0 && animname[k-1] == '_') --k; animname2[k] = 0; if (strcmp(animname2, animname) || mod->animscenes[j].framecount > 1) { count = mod->animscenes[j].firstframe - first; break; } } // if it's only one frame, use the original frame name if (j == i + 1) strlcpy(animname, mod->animscenes[i].name, sizeof(animname)); } dpsnprintf(outname, sizeof(outname), "%s_decompiled/%s.smd", basename, animname); Mod_Decompile_SMD(mod, outname, first, count, false); if (zymtextsize < (int)sizeof(zymtextbuffer) - 100) { l = dpsnprintf(zymtextbuffer + zymtextsize, sizeof(zymtextbuffer) - zymtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); if (l > 0) zymtextsize += l; } if (dpmtextsize < (int)sizeof(dpmtextbuffer) - 100) { l = dpsnprintf(dpmtextbuffer + dpmtextsize, sizeof(dpmtextbuffer) - dpmtextsize, "scene %s.smd fps %g %s\n", animname, mod->animscenes[i].framerate, mod->animscenes[i].loop ? "" : " noloop"); if (l > 0) dpmtextsize += l; } if (framegroupstextsize < (int)sizeof(framegroupstextbuffer) - 100) { l = dpsnprintf(framegroupstextbuffer + framegroupstextsize, sizeof(framegroupstextbuffer) - framegroupstextsize, "%d %d %f %d // %s\n", first, count, mod->animscenes[i].framerate, mod->animscenes[i].loop, animname); if (l > 0) framegroupstextsize += l; } } if (zymtextsize) FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled/out_zym.txt", basename), zymtextbuffer, (fs_offset_t)zymtextsize); if (dpmtextsize) FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled/out_dpm.txt", basename), dpmtextbuffer, (fs_offset_t)dpmtextsize); if (framegroupstextsize) FS_WriteFile(va(vabuf, sizeof(vabuf), "%s_decompiled.framegroups", basename), framegroupstextbuffer, (fs_offset_t)framegroupstextsize); } } void Mod_AllocLightmap_Init(mod_alloclightmap_state_t *state, int width, int height) { int y; memset(state, 0, sizeof(*state)); state->width = width; state->height = height; state->currentY = 0; state->rows = (mod_alloclightmap_row_t *)Mem_Alloc(loadmodel->mempool, state->height * sizeof(*state->rows)); for (y = 0;y < state->height;y++) { state->rows[y].currentX = 0; state->rows[y].rowY = -1; } } void Mod_AllocLightmap_Reset(mod_alloclightmap_state_t *state) { int y; state->currentY = 0; for (y = 0;y < state->height;y++) { state->rows[y].currentX = 0; state->rows[y].rowY = -1; } } void Mod_AllocLightmap_Free(mod_alloclightmap_state_t *state) { if (state->rows) Mem_Free(state->rows); memset(state, 0, sizeof(*state)); } qboolean Mod_AllocLightmap_Block(mod_alloclightmap_state_t *state, int blockwidth, int blockheight, int *outx, int *outy) { mod_alloclightmap_row_t *row; int y; row = state->rows + blockheight; if ((row->rowY < 0) || (row->currentX + blockwidth > state->width)) { if (state->currentY + blockheight <= state->height) { // use the current allocation position row->rowY = state->currentY; row->currentX = 0; state->currentY += blockheight; } else { // find another position for (y = blockheight;y < state->height;y++) { if ((state->rows[y].rowY >= 0) && (state->rows[y].currentX + blockwidth <= state->width)) { row = state->rows + y; break; } } if (y == state->height) return false; } } *outy = row->rowY; *outx = row->currentX; row->currentX += blockwidth; return true; } typedef struct lightmapsample_s { float pos[3]; float sh1[4][3]; float *vertex_color; unsigned char *lm_bgr; unsigned char *lm_dir; } lightmapsample_t; typedef struct lightmapvertex_s { int index; float pos[3]; float normal[3]; float texcoordbase[2]; float texcoordlightmap[2]; float lightcolor[4]; } lightmapvertex_t; typedef struct lightmaptriangle_s { int triangleindex; int surfaceindex; int lightmapindex; int axis; int lmoffset[2]; int lmsize[2]; // 2D modelspace coordinates of min corner // snapped to lightmap grid but not in grid coordinates float lmbase[2]; // 2D modelspace to lightmap coordinate scale float lmscale[2]; float vertex[3][3]; float mins[3]; float maxs[3]; } lightmaptriangle_t; typedef struct lightmaplight_s { float origin[3]; float radius; float iradius; float radius2; float color[3]; svbsp_t svbsp; } lightmaplight_t; lightmaptriangle_t *mod_generatelightmaps_lightmaptriangles; #define MAX_LIGHTMAPSAMPLES 64 static int mod_generatelightmaps_numoffsets[3]; static float mod_generatelightmaps_offsets[3][MAX_LIGHTMAPSAMPLES][3]; static int mod_generatelightmaps_numlights; static lightmaplight_t *mod_generatelightmaps_lightinfo; extern cvar_t r_shadow_lightattenuationdividebias; extern cvar_t r_shadow_lightattenuationlinearscale; static void Mod_GenerateLightmaps_LightPoint(dp_model_t *model, const vec3_t pos, vec3_t ambient, vec3_t diffuse, vec3_t lightdir) { int i; int index; int result; float relativepoint[3]; float color[3]; float dir[3]; float dist; float dist2; float intensity; float sample[5*3]; float lightorigin[3]; float lightradius; float lightradius2; float lightiradius; float lightcolor[3]; trace_t trace; for (i = 0;i < 5*3;i++) sample[i] = 0.0f; for (index = 0;;index++) { result = R_Shadow_GetRTLightInfo(index, lightorigin, &lightradius, lightcolor); if (result < 0) break; if (result == 0) continue; lightradius2 = lightradius * lightradius; VectorSubtract(lightorigin, pos, relativepoint); dist2 = VectorLength2(relativepoint); if (dist2 >= lightradius2) continue; lightiradius = 1.0f / lightradius; dist = sqrt(dist2) * lightiradius; intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); if (intensity <= 0.0f) continue; if (model && model->TraceLine) { model->TraceLine(model, NULL, NULL, &trace, pos, lightorigin, SUPERCONTENTS_VISBLOCKERMASK, SUPERCONTENTS_SKY); if (trace.fraction < 1) continue; } // scale down intensity to add to both ambient and diffuse //intensity *= 0.5f; VectorNormalize(relativepoint); VectorScale(lightcolor, intensity, color); VectorMA(sample , 0.5f , color, sample ); VectorMA(sample + 3, relativepoint[0], color, sample + 3); VectorMA(sample + 6, relativepoint[1], color, sample + 6); VectorMA(sample + 9, relativepoint[2], color, sample + 9); // calculate a weighted average light direction as well intensity *= VectorLength(color); VectorMA(sample + 12, intensity, relativepoint, sample + 12); } // calculate the direction we'll use to reduce the sample to a directional light source VectorCopy(sample + 12, dir); //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); VectorNormalize(dir); // extract the diffuse color along the chosen direction and scale it diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); // subtract some of diffuse from ambient VectorMA(sample, -0.333f, diffuse, ambient); // store the normalized lightdir VectorCopy(dir, lightdir); } static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(const dp_model_t *model, svbsp_t *svbsp, const float *mins, const float *maxs) { int surfaceindex; int triangleindex; const msurface_t *surface; const float *vertex3f = model->surfmesh.data_vertex3f; const int *element3i = model->surfmesh.data_element3i; const int *e; float v2[3][3]; for (surfaceindex = 0, surface = model->data_surfaces;surfaceindex < model->nummodelsurfaces;surfaceindex++, surface++) { if (!BoxesOverlap(surface->mins, surface->maxs, mins, maxs)) continue; if (surface->texture->basematerialflags & MATERIALFLAG_NOSHADOW) continue; for (triangleindex = 0, e = element3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) { VectorCopy(vertex3f + 3*e[0], v2[0]); VectorCopy(vertex3f + 3*e[1], v2[1]); VectorCopy(vertex3f + 3*e[2], v2[2]); SVBSP_AddPolygon(svbsp, 3, v2[0], true, NULL, NULL, 0); } } } static void Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(dp_model_t *model, lightmaplight_t *lightinfo) { int maxnodes = 1<<14; svbsp_node_t *nodes; float origin[3]; float mins[3]; float maxs[3]; svbsp_t svbsp; VectorSet(mins, lightinfo->origin[0] - lightinfo->radius, lightinfo->origin[1] - lightinfo->radius, lightinfo->origin[2] - lightinfo->radius); VectorSet(maxs, lightinfo->origin[0] + lightinfo->radius, lightinfo->origin[1] + lightinfo->radius, lightinfo->origin[2] + lightinfo->radius); VectorCopy(lightinfo->origin, origin); nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); for (;;) { SVBSP_Init(&svbsp, origin, maxnodes, nodes); Mod_GenerateLightmaps_CreateLights_ComputeSVBSP_InsertSurfaces(model, &svbsp, mins, maxs); if (svbsp.ranoutofnodes) { maxnodes *= 16; if (maxnodes > 1<<22) { Mem_Free(nodes); return; } Mem_Free(nodes); nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, maxnodes * sizeof(*nodes)); } else break; } if (svbsp.numnodes > 0) { svbsp.nodes = (svbsp_node_t *)Mem_Alloc(tempmempool, svbsp.numnodes * sizeof(*nodes)); memcpy(svbsp.nodes, nodes, svbsp.numnodes * sizeof(*nodes)); lightinfo->svbsp = svbsp; } Mem_Free(nodes); } static void Mod_GenerateLightmaps_CreateLights(dp_model_t *model) { int index; int result; lightmaplight_t *lightinfo; float origin[3]; float radius; float color[3]; mod_generatelightmaps_numlights = 0; for (index = 0;;index++) { result = R_Shadow_GetRTLightInfo(index, origin, &radius, color); if (result < 0) break; if (result > 0) mod_generatelightmaps_numlights++; } if (mod_generatelightmaps_numlights > 0) { mod_generatelightmaps_lightinfo = (lightmaplight_t *)Mem_Alloc(tempmempool, mod_generatelightmaps_numlights * sizeof(*mod_generatelightmaps_lightinfo)); lightinfo = mod_generatelightmaps_lightinfo; for (index = 0;;index++) { result = R_Shadow_GetRTLightInfo(index, lightinfo->origin, &lightinfo->radius, lightinfo->color); if (result < 0) break; if (result > 0) lightinfo++; } } for (index = 0, lightinfo = mod_generatelightmaps_lightinfo;index < mod_generatelightmaps_numlights;index++, lightinfo++) { lightinfo->iradius = 1.0f / lightinfo->radius; lightinfo->radius2 = lightinfo->radius * lightinfo->radius; // TODO: compute svbsp Mod_GenerateLightmaps_CreateLights_ComputeSVBSP(model, lightinfo); } } static void Mod_GenerateLightmaps_DestroyLights(dp_model_t *model) { int i; if (mod_generatelightmaps_lightinfo) { for (i = 0;i < mod_generatelightmaps_numlights;i++) if (mod_generatelightmaps_lightinfo[i].svbsp.nodes) Mem_Free(mod_generatelightmaps_lightinfo[i].svbsp.nodes); Mem_Free(mod_generatelightmaps_lightinfo); } mod_generatelightmaps_lightinfo = NULL; mod_generatelightmaps_numlights = 0; } static qboolean Mod_GenerateLightmaps_SamplePoint_SVBSP(const svbsp_t *svbsp, const float *pos) { const svbsp_node_t *node; const svbsp_node_t *nodes = svbsp->nodes; int num = 0; while (num >= 0) { node = nodes + num; num = node->children[DotProduct(node->plane, pos) < node->plane[3]]; } return num == -1; // true if empty, false if solid (shadowed) } static void Mod_GenerateLightmaps_SamplePoint(const float *pos, const float *normal, float *sample, int numoffsets, const float *offsets) { int i; float relativepoint[3]; float color[3]; float offsetpos[3]; float dist; float dist2; float intensity; int offsetindex; int hits; int tests; const lightmaplight_t *lightinfo; trace_t trace; for (i = 0;i < 5*3;i++) sample[i] = 0.0f; for (i = 0, lightinfo = mod_generatelightmaps_lightinfo;i < mod_generatelightmaps_numlights;i++, lightinfo++) { //R_SampleRTLights(pos, sample, numoffsets, offsets); VectorSubtract(lightinfo->origin, pos, relativepoint); // don't accept light from behind a surface, it causes bad shading if (normal && DotProduct(relativepoint, normal) <= 0) continue; dist2 = VectorLength2(relativepoint); if (dist2 >= lightinfo->radius2) continue; dist = sqrt(dist2) * lightinfo->iradius; intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; if (intensity <= 0) continue; if (cl.worldmodel && cl.worldmodel->TraceLine && numoffsets > 0) { hits = 0; tests = 1; if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, pos)) hits++; for (offsetindex = 1;offsetindex < numoffsets;offsetindex++) { VectorAdd(pos, offsets + 3*offsetindex, offsetpos); if (!normal) { // for light grid we'd better check visibility of the offset point cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, pos, offsetpos, SUPERCONTENTS_VISBLOCKERMASK, SUPERCONTENTS_SKY); if (trace.fraction < 1) VectorLerp(pos, trace.fraction, offsetpos, offsetpos); } tests++; if (Mod_GenerateLightmaps_SamplePoint_SVBSP(&lightinfo->svbsp, offsetpos)) hits++; } if (!hits) continue; // scale intensity according to how many rays succeeded // we know one test is valid, half of the rest will fail... //if (normal && tests > 1) // intensity *= (tests - 1.0f) / tests; intensity *= (float)hits / tests; } // scale down intensity to add to both ambient and diffuse //intensity *= 0.5f; VectorNormalize(relativepoint); VectorScale(lightinfo->color, intensity, color); VectorMA(sample , 0.5f , color, sample ); VectorMA(sample + 3, relativepoint[0], color, sample + 3); VectorMA(sample + 6, relativepoint[1], color, sample + 6); VectorMA(sample + 9, relativepoint[2], color, sample + 9); // calculate a weighted average light direction as well intensity *= VectorLength(color); VectorMA(sample + 12, intensity, relativepoint, sample + 12); } } static void Mod_GenerateLightmaps_LightmapSample(const float *pos, const float *normal, unsigned char *lm_bgr, unsigned char *lm_dir) { float sample[5*3]; float color[3]; float dir[3]; float f; Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[0], mod_generatelightmaps_offsets[0][0]); //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); VectorCopy(sample + 12, dir); VectorNormalize(dir); //VectorAdd(dir, normal, dir); //VectorNormalize(dir); f = DotProduct(dir, normal); f = max(0, f) * 255.0f; VectorScale(sample, f, color); //VectorCopy(normal, dir); VectorSet(dir, (dir[0]+1.0f)*127.5f, (dir[1]+1.0f)*127.5f, (dir[2]+1.0f)*127.5f); lm_bgr[0] = (unsigned char)bound(0.0f, color[2], 255.0f); lm_bgr[1] = (unsigned char)bound(0.0f, color[1], 255.0f); lm_bgr[2] = (unsigned char)bound(0.0f, color[0], 255.0f); lm_bgr[3] = 255; lm_dir[0] = (unsigned char)dir[2]; lm_dir[1] = (unsigned char)dir[1]; lm_dir[2] = (unsigned char)dir[0]; lm_dir[3] = 255; } static void Mod_GenerateLightmaps_VertexSample(const float *pos, const float *normal, float *vertex_color) { float sample[5*3]; Mod_GenerateLightmaps_SamplePoint(pos, normal, sample, mod_generatelightmaps_numoffsets[1], mod_generatelightmaps_offsets[1][0]); VectorCopy(sample, vertex_color); } static void Mod_GenerateLightmaps_GridSample(const float *pos, q3dlightgrid_t *s) { float sample[5*3]; float ambient[3]; float diffuse[3]; float dir[3]; Mod_GenerateLightmaps_SamplePoint(pos, NULL, sample, mod_generatelightmaps_numoffsets[2], mod_generatelightmaps_offsets[2][0]); // calculate the direction we'll use to reduce the sample to a directional light source VectorCopy(sample + 12, dir); //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); VectorNormalize(dir); // extract the diffuse color along the chosen direction and scale it diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]) * 127.5f; diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]) * 127.5f; diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]) * 127.5f; // scale the ambient from 0-2 to 0-255 and subtract some of diffuse VectorScale(sample, 127.5f, ambient); VectorMA(ambient, -0.333f, diffuse, ambient); // encode to the grid format s->ambientrgb[0] = (unsigned char)bound(0.0f, ambient[0], 255.0f); s->ambientrgb[1] = (unsigned char)bound(0.0f, ambient[1], 255.0f); s->ambientrgb[2] = (unsigned char)bound(0.0f, ambient[2], 255.0f); s->diffusergb[0] = (unsigned char)bound(0.0f, diffuse[0], 255.0f); s->diffusergb[1] = (unsigned char)bound(0.0f, diffuse[1], 255.0f); s->diffusergb[2] = (unsigned char)bound(0.0f, diffuse[2], 255.0f); if (dir[2] >= 0.99f) {s->diffusepitch = 0;s->diffuseyaw = 0;} else if (dir[2] <= -0.99f) {s->diffusepitch = 128;s->diffuseyaw = 0;} else {s->diffusepitch = (unsigned char)(acos(dir[2]) * (127.5f/M_PI));s->diffuseyaw = (unsigned char)(atan2(dir[1], dir[0]) * (127.5f/M_PI));} } static void Mod_GenerateLightmaps_InitSampleOffsets(dp_model_t *model) { float radius[3]; float temp[3]; int i, j; memset(mod_generatelightmaps_offsets, 0, sizeof(mod_generatelightmaps_offsets)); mod_generatelightmaps_numoffsets[0] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_lightmapsamples.integer); mod_generatelightmaps_numoffsets[1] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_vertexsamples.integer); mod_generatelightmaps_numoffsets[2] = min(MAX_LIGHTMAPSAMPLES, mod_generatelightmaps_gridsamples.integer); radius[0] = mod_generatelightmaps_lightmapradius.value; radius[1] = mod_generatelightmaps_vertexradius.value; radius[2] = mod_generatelightmaps_gridradius.value; for (i = 0;i < 3;i++) { for (j = 1;j < mod_generatelightmaps_numoffsets[i];j++) { VectorRandom(temp); VectorScale(temp, radius[i], mod_generatelightmaps_offsets[i][j]); } } } static void Mod_GenerateLightmaps_DestroyLightmaps(dp_model_t *model) { msurface_t *surface; int surfaceindex; int i; for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; surface->lightmaptexture = NULL; surface->deluxemaptexture = NULL; } if (model->brushq3.data_lightmaps) { for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) if (model->brushq3.data_lightmaps[i]) R_FreeTexture(model->brushq3.data_lightmaps[i]); Mem_Free(model->brushq3.data_lightmaps); model->brushq3.data_lightmaps = NULL; } if (model->brushq3.data_deluxemaps) { for (i = 0;i < model->brushq3.num_mergedlightmaps;i++) if (model->brushq3.data_deluxemaps[i]) R_FreeTexture(model->brushq3.data_deluxemaps[i]); Mem_Free(model->brushq3.data_deluxemaps); model->brushq3.data_deluxemaps = NULL; } } static void Mod_GenerateLightmaps_UnweldTriangles(dp_model_t *model) { msurface_t *surface; int surfaceindex; int vertexindex; int outvertexindex; int i; const int *e; surfmesh_t oldsurfmesh; size_t size; unsigned char *data; oldsurfmesh = model->surfmesh; model->surfmesh.num_triangles = oldsurfmesh.num_triangles; model->surfmesh.num_vertices = oldsurfmesh.num_triangles * 3; size = 0; size += model->surfmesh.num_vertices * sizeof(float[3]); size += model->surfmesh.num_vertices * sizeof(float[3]); size += model->surfmesh.num_vertices * sizeof(float[3]); size += model->surfmesh.num_vertices * sizeof(float[3]); size += model->surfmesh.num_vertices * sizeof(float[2]); size += model->surfmesh.num_vertices * sizeof(float[2]); size += model->surfmesh.num_vertices * sizeof(float[4]); data = (unsigned char *)Mem_Alloc(model->mempool, size); model->surfmesh.data_vertex3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); model->surfmesh.data_normal3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); model->surfmesh.data_svector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); model->surfmesh.data_tvector3f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[3]); model->surfmesh.data_texcoordtexture2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); model->surfmesh.data_texcoordlightmap2f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[2]); model->surfmesh.data_lightmapcolor4f = (float *)data;data += model->surfmesh.num_vertices * sizeof(float[4]); if (model->surfmesh.num_vertices > 65536) model->surfmesh.data_element3s = NULL; if (model->surfmesh.data_element3i_indexbuffer) R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3i_indexbuffer); model->surfmesh.data_element3i_indexbuffer = NULL; if (model->surfmesh.data_element3s_indexbuffer) R_Mesh_DestroyMeshBuffer(model->surfmesh.data_element3s_indexbuffer); model->surfmesh.data_element3s_indexbuffer = NULL; if (model->surfmesh.vbo_vertexbuffer) R_Mesh_DestroyMeshBuffer(model->surfmesh.vbo_vertexbuffer); model->surfmesh.vbo_vertexbuffer = 0; // convert all triangles to unique vertex data outvertexindex = 0; for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; surface->num_firstvertex = outvertexindex; surface->num_vertices = surface->num_triangles*3; e = oldsurfmesh.data_element3i + surface->num_firsttriangle*3; for (i = 0;i < surface->num_triangles*3;i++) { vertexindex = e[i]; model->surfmesh.data_vertex3f[outvertexindex*3+0] = oldsurfmesh.data_vertex3f[vertexindex*3+0]; model->surfmesh.data_vertex3f[outvertexindex*3+1] = oldsurfmesh.data_vertex3f[vertexindex*3+1]; model->surfmesh.data_vertex3f[outvertexindex*3+2] = oldsurfmesh.data_vertex3f[vertexindex*3+2]; model->surfmesh.data_normal3f[outvertexindex*3+0] = oldsurfmesh.data_normal3f[vertexindex*3+0]; model->surfmesh.data_normal3f[outvertexindex*3+1] = oldsurfmesh.data_normal3f[vertexindex*3+1]; model->surfmesh.data_normal3f[outvertexindex*3+2] = oldsurfmesh.data_normal3f[vertexindex*3+2]; model->surfmesh.data_svector3f[outvertexindex*3+0] = oldsurfmesh.data_svector3f[vertexindex*3+0]; model->surfmesh.data_svector3f[outvertexindex*3+1] = oldsurfmesh.data_svector3f[vertexindex*3+1]; model->surfmesh.data_svector3f[outvertexindex*3+2] = oldsurfmesh.data_svector3f[vertexindex*3+2]; model->surfmesh.data_tvector3f[outvertexindex*3+0] = oldsurfmesh.data_tvector3f[vertexindex*3+0]; model->surfmesh.data_tvector3f[outvertexindex*3+1] = oldsurfmesh.data_tvector3f[vertexindex*3+1]; model->surfmesh.data_tvector3f[outvertexindex*3+2] = oldsurfmesh.data_tvector3f[vertexindex*3+2]; model->surfmesh.data_texcoordtexture2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+0]; model->surfmesh.data_texcoordtexture2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordtexture2f[vertexindex*2+1]; if (oldsurfmesh.data_texcoordlightmap2f) { model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+0] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+0]; model->surfmesh.data_texcoordlightmap2f[outvertexindex*2+1] = oldsurfmesh.data_texcoordlightmap2f[vertexindex*2+1]; } if (oldsurfmesh.data_lightmapcolor4f) { model->surfmesh.data_lightmapcolor4f[outvertexindex*4+0] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+0]; model->surfmesh.data_lightmapcolor4f[outvertexindex*4+1] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+1]; model->surfmesh.data_lightmapcolor4f[outvertexindex*4+2] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+2]; model->surfmesh.data_lightmapcolor4f[outvertexindex*4+3] = oldsurfmesh.data_lightmapcolor4f[vertexindex*4+3]; } else Vector4Set(model->surfmesh.data_lightmapcolor4f + 4*outvertexindex, 1, 1, 1, 1); model->surfmesh.data_element3i[surface->num_firsttriangle*3+i] = outvertexindex; outvertexindex++; } } if (model->surfmesh.data_element3s) for (i = 0;i < model->surfmesh.num_triangles*3;i++) model->surfmesh.data_element3s[i] = model->surfmesh.data_element3i[i]; // find and update all submodels to use this new surfmesh data for (i = 0;i < model->brush.numsubmodels;i++) model->brush.submodels[i]->surfmesh = model->surfmesh; } static void Mod_GenerateLightmaps_CreateTriangleInformation(dp_model_t *model) { msurface_t *surface; int surfaceindex; int i; int axis; float normal[3]; const int *e; lightmaptriangle_t *triangle; // generate lightmap triangle structs mod_generatelightmaps_lightmaptriangles = (lightmaptriangle_t *)Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; for (i = 0;i < surface->num_triangles;i++) { triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; triangle->triangleindex = surface->num_firsttriangle+i; triangle->surfaceindex = surfaceindex; VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+0], triangle->vertex[0]); VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+1], triangle->vertex[1]); VectorCopy(model->surfmesh.data_vertex3f + 3*e[i*3+2], triangle->vertex[2]); // calculate bounds of triangle triangle->mins[0] = min(triangle->vertex[0][0], min(triangle->vertex[1][0], triangle->vertex[2][0])); triangle->mins[1] = min(triangle->vertex[0][1], min(triangle->vertex[1][1], triangle->vertex[2][1])); triangle->mins[2] = min(triangle->vertex[0][2], min(triangle->vertex[1][2], triangle->vertex[2][2])); triangle->maxs[0] = max(triangle->vertex[0][0], max(triangle->vertex[1][0], triangle->vertex[2][0])); triangle->maxs[1] = max(triangle->vertex[0][1], max(triangle->vertex[1][1], triangle->vertex[2][1])); triangle->maxs[2] = max(triangle->vertex[0][2], max(triangle->vertex[1][2], triangle->vertex[2][2])); // pick an axial projection based on the triangle normal TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], normal); axis = 0; if (fabs(normal[1]) > fabs(normal[axis])) axis = 1; if (fabs(normal[2]) > fabs(normal[axis])) axis = 2; triangle->axis = axis; } } } static void Mod_GenerateLightmaps_DestroyTriangleInformation(dp_model_t *model) { if (mod_generatelightmaps_lightmaptriangles) Mem_Free(mod_generatelightmaps_lightmaptriangles); mod_generatelightmaps_lightmaptriangles = NULL; } float lmaxis[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; static void Mod_GenerateLightmaps_CreateLightmaps(dp_model_t *model) { msurface_t *surface; int surfaceindex; int lightmapindex; int lightmapnumber; int i; int j; int k; int x; int y; int axis; int axis1; int axis2; int retry; int pixeloffset; float trianglenormal[3]; float samplecenter[3]; float samplenormal[3]; float temp[3]; float lmiscale[2]; float slopex; float slopey; float slopebase; float lmscalepixels; float lmmins; float lmmaxs; float lm_basescalepixels; int lm_borderpixels; int lm_texturesize; //int lm_maxpixels; const int *e; lightmaptriangle_t *triangle; unsigned char *lightmappixels; unsigned char *deluxemappixels; mod_alloclightmap_state_t lmstate; char vabuf[1024]; // generate lightmap projection information for all triangles if (model->texturepool == NULL) model->texturepool = R_AllocTexturePool(); lm_basescalepixels = 1.0f / max(0.0001f, mod_generatelightmaps_unitspersample.value); lm_borderpixels = mod_generatelightmaps_borderpixels.integer; lm_texturesize = bound(lm_borderpixels*2+1, 64, (int)vid.maxtexturesize_2d); //lm_maxpixels = lm_texturesize-(lm_borderpixels*2+1); Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); lightmapnumber = 0; for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; lmscalepixels = lm_basescalepixels; for (retry = 0;retry < 30;retry++) { // after a couple failed attempts, degrade quality to make it fit if (retry > 1) lmscalepixels *= 0.5f; for (i = 0;i < surface->num_triangles;i++) { triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; triangle->lightmapindex = lightmapnumber; // calculate lightmap bounds in 3D pixel coordinates, limit size, // pick two planar axes for projection // lightmap coordinates here are in pixels // lightmap projections are snapped to pixel grid explicitly, such // that two neighboring triangles sharing an edge and projection // axis will have identical sampl espacing along their shared edge k = 0; for (j = 0;j < 3;j++) { if (j == triangle->axis) continue; lmmins = floor(triangle->mins[j]*lmscalepixels)-lm_borderpixels; lmmaxs = floor(triangle->maxs[j]*lmscalepixels)+lm_borderpixels; triangle->lmsize[k] = (int)(lmmaxs-lmmins); triangle->lmbase[k] = lmmins/lmscalepixels; triangle->lmscale[k] = lmscalepixels; k++; } if (!Mod_AllocLightmap_Block(&lmstate, triangle->lmsize[0], triangle->lmsize[1], &triangle->lmoffset[0], &triangle->lmoffset[1])) break; } // if all fit in this texture, we're done with this surface if (i == surface->num_triangles) break; // if we haven't maxed out the lightmap size yet, we retry the // entire surface batch... if (lm_texturesize * 2 <= min(mod_generatelightmaps_texturesize.integer, (int)vid.maxtexturesize_2d)) { lm_texturesize *= 2; surfaceindex = -1; lightmapnumber = 0; Mod_AllocLightmap_Free(&lmstate); Mod_AllocLightmap_Init(&lmstate, lm_texturesize, lm_texturesize); break; } // if we have maxed out the lightmap size, and this triangle does // not fit in the same texture as the rest of the surface, we have // to retry the entire surface in a new texture (can only use one) // with multiple retries, the lightmap quality degrades until it // fits (or gives up) if (surfaceindex > 0) lightmapnumber++; Mod_AllocLightmap_Reset(&lmstate); } } lightmapnumber++; Mod_AllocLightmap_Free(&lmstate); // now put triangles together into lightmap textures, and do not allow // triangles of a surface to go into different textures (as that would // require rewriting the surface list) model->brushq3.deluxemapping_modelspace = true; model->brushq3.deluxemapping = true; model->brushq3.num_mergedlightmaps = lightmapnumber; model->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); model->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(model->mempool, model->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); lightmappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); deluxemappixels = (unsigned char *)Mem_Alloc(tempmempool, model->brushq3.num_mergedlightmaps * lm_texturesize * lm_texturesize * 4); for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; e = model->surfmesh.data_element3i + surface->num_firsttriangle*3; for (i = 0;i < surface->num_triangles;i++) { triangle = &mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle+i]; TriangleNormal(triangle->vertex[0], triangle->vertex[1], triangle->vertex[2], trianglenormal); VectorNormalize(trianglenormal); VectorCopy(trianglenormal, samplenormal); // FIXME: this is supposed to be interpolated per pixel from vertices axis = triangle->axis; axis1 = axis == 0 ? 1 : 0; axis2 = axis == 2 ? 1 : 2; lmiscale[0] = 1.0f / triangle->lmscale[0]; lmiscale[1] = 1.0f / triangle->lmscale[1]; if (trianglenormal[axis] < 0) VectorNegate(trianglenormal, trianglenormal); CrossProduct(lmaxis[axis2], trianglenormal, temp);slopex = temp[axis] / temp[axis1]; CrossProduct(lmaxis[axis1], trianglenormal, temp);slopey = temp[axis] / temp[axis2]; slopebase = triangle->vertex[0][axis] - triangle->vertex[0][axis1]*slopex - triangle->vertex[0][axis2]*slopey; for (j = 0;j < 3;j++) { float *t2f = model->surfmesh.data_texcoordlightmap2f + e[i*3+j]*2; t2f[0] = ((triangle->vertex[j][axis1] - triangle->lmbase[0]) * triangle->lmscale[0] + triangle->lmoffset[0]) / lm_texturesize; t2f[1] = ((triangle->vertex[j][axis2] - triangle->lmbase[1]) * triangle->lmscale[1] + triangle->lmoffset[1]) / lm_texturesize; #if 0 samplecenter[axis1] = (t2f[0]*lm_texturesize-triangle->lmoffset[0])*lmiscale[0] + triangle->lmbase[0]; samplecenter[axis2] = (t2f[1]*lm_texturesize-triangle->lmoffset[1])*lmiscale[1] + triangle->lmbase[1]; samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; Con_Printf("%f:%f %f:%f %f:%f = %f %f\n", triangle->vertex[j][axis1], samplecenter[axis1], triangle->vertex[j][axis2], samplecenter[axis2], triangle->vertex[j][axis], samplecenter[axis], t2f[0], t2f[1]); #endif } #if 0 switch (axis) { default: case 0: forward[0] = 0; forward[1] = 1.0f / triangle->lmscale[0]; forward[2] = 0; left[0] = 0; left[1] = 0; left[2] = 1.0f / triangle->lmscale[1]; up[0] = 1.0f; up[1] = 0; up[2] = 0; origin[0] = 0; origin[1] = triangle->lmbase[0]; origin[2] = triangle->lmbase[1]; break; case 1: forward[0] = 1.0f / triangle->lmscale[0]; forward[1] = 0; forward[2] = 0; left[0] = 0; left[1] = 0; left[2] = 1.0f / triangle->lmscale[1]; up[0] = 0; up[1] = 1.0f; up[2] = 0; origin[0] = triangle->lmbase[0]; origin[1] = 0; origin[2] = triangle->lmbase[1]; break; case 2: forward[0] = 1.0f / triangle->lmscale[0]; forward[1] = 0; forward[2] = 0; left[0] = 0; left[1] = 1.0f / triangle->lmscale[1]; left[2] = 0; up[0] = 0; up[1] = 0; up[2] = 1.0f; origin[0] = triangle->lmbase[0]; origin[1] = triangle->lmbase[1]; origin[2] = 0; break; } Matrix4x4_FromVectors(&backmatrix, forward, left, up, origin); #endif #define LM_DIST_EPSILON (1.0f / 32.0f) for (y = 0;y < triangle->lmsize[1];y++) { pixeloffset = ((triangle->lightmapindex * lm_texturesize + y + triangle->lmoffset[1]) * lm_texturesize + triangle->lmoffset[0]) * 4; for (x = 0;x < triangle->lmsize[0];x++, pixeloffset += 4) { samplecenter[axis1] = (x+0.5f)*lmiscale[0] + triangle->lmbase[0]; samplecenter[axis2] = (y+0.5f)*lmiscale[1] + triangle->lmbase[1]; samplecenter[axis] = samplecenter[axis1]*slopex + samplecenter[axis2]*slopey + slopebase; VectorMA(samplecenter, 0.125f, samplenormal, samplecenter); Mod_GenerateLightmaps_LightmapSample(samplecenter, samplenormal, lightmappixels + pixeloffset, deluxemappixels + pixeloffset); } } } } for (lightmapindex = 0;lightmapindex < model->brushq3.num_mergedlightmaps;lightmapindex++) { model->brushq3.data_lightmaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "lightmap%i", lightmapindex), lm_texturesize, lm_texturesize, lightmappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); model->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(model->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%i", lightmapindex), lm_texturesize, lm_texturesize, deluxemappixels + lightmapindex * lm_texturesize * lm_texturesize * 4, TEXTYPE_BGRA, TEXF_FORCELINEAR, -1, NULL); } if (lightmappixels) Mem_Free(lightmappixels); if (deluxemappixels) Mem_Free(deluxemappixels); for (surfaceindex = 0;surfaceindex < model->num_surfaces;surfaceindex++) { surface = model->data_surfaces + surfaceindex; if (!surface->num_triangles) continue; lightmapindex = mod_generatelightmaps_lightmaptriangles[surface->num_firsttriangle].lightmapindex; surface->lightmaptexture = model->brushq3.data_lightmaps[lightmapindex]; surface->deluxemaptexture = model->brushq3.data_deluxemaps[lightmapindex]; surface->lightmapinfo = NULL; } model->brush.LightPoint = Mod_GenerateLightmaps_LightPoint; model->brushq1.lightdata = NULL; model->brushq1.lightmapupdateflags = NULL; model->brushq1.firstrender = false; model->brushq1.num_lightstyles = 0; model->brushq1.data_lightstyleinfo = NULL; for (i = 0;i < model->brush.numsubmodels;i++) { model->brush.submodels[i]->brushq1.lightmapupdateflags = NULL; model->brush.submodels[i]->brushq1.firstrender = false; model->brush.submodels[i]->brushq1.num_lightstyles = 0; model->brush.submodels[i]->brushq1.data_lightstyleinfo = NULL; } } static void Mod_GenerateLightmaps_UpdateVertexColors(dp_model_t *model) { int i; for (i = 0;i < model->surfmesh.num_vertices;i++) Mod_GenerateLightmaps_VertexSample(model->surfmesh.data_vertex3f + 3*i, model->surfmesh.data_normal3f + 3*i, model->surfmesh.data_lightmapcolor4f + 4*i); } static void Mod_GenerateLightmaps_UpdateLightGrid(dp_model_t *model) { int x; int y; int z; int index = 0; float pos[3]; for (z = 0;z < model->brushq3.num_lightgrid_isize[2];z++) { pos[2] = (model->brushq3.num_lightgrid_imins[2] + z + 0.5f) * model->brushq3.num_lightgrid_cellsize[2]; for (y = 0;y < model->brushq3.num_lightgrid_isize[1];y++) { pos[1] = (model->brushq3.num_lightgrid_imins[1] + y + 0.5f) * model->brushq3.num_lightgrid_cellsize[1]; for (x = 0;x < model->brushq3.num_lightgrid_isize[0];x++, index++) { pos[0] = (model->brushq3.num_lightgrid_imins[0] + x + 0.5f) * model->brushq3.num_lightgrid_cellsize[0]; Mod_GenerateLightmaps_GridSample(pos, model->brushq3.data_lightgrid + index); } } } } extern cvar_t mod_q3bsp_nolightmaps; static void Mod_GenerateLightmaps(dp_model_t *model) { //lightmaptriangle_t *lightmaptriangles = Mem_Alloc(model->mempool, model->surfmesh.num_triangles * sizeof(lightmaptriangle_t)); dp_model_t *oldloadmodel = loadmodel; loadmodel = model; Mod_GenerateLightmaps_InitSampleOffsets(model); Mod_GenerateLightmaps_DestroyLightmaps(model); Mod_GenerateLightmaps_UnweldTriangles(model); Mod_GenerateLightmaps_CreateTriangleInformation(model); Mod_GenerateLightmaps_CreateLights(model); if(!mod_q3bsp_nolightmaps.integer) Mod_GenerateLightmaps_CreateLightmaps(model); Mod_GenerateLightmaps_UpdateVertexColors(model); Mod_GenerateLightmaps_UpdateLightGrid(model); Mod_GenerateLightmaps_DestroyLights(model); Mod_GenerateLightmaps_DestroyTriangleInformation(model); loadmodel = oldloadmodel; } static void Mod_GenerateLightmaps_f(void) { if (Cmd_Argc() != 1) { Con_Printf("usage: mod_generatelightmaps\n"); return; } if (!cl.worldmodel) { Con_Printf("no worldmodel loaded\n"); return; } Mod_GenerateLightmaps(cl.worldmodel); } darkplaces/crypto-keygen-standalone-brute.sh0000775000175000017500000000143313067716216020575 0ustar kalevkalev#!/bin/sh outfile=$1; shift hosts=$1; shift on() { case "$1" in localhost) shift exec "$@" ;; *) exec ssh "$@" ;; esac } pids= mainpid=$$ trap 'kill $pids' EXIT trap 'exit 1' INT USR1 n=0 for h in $hosts; do nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` n=$(($nn + $n)) done rm -f bruteforce-* i=0 for h in $hosts; do nn=`on "$h" cat /proc/cpuinfo | grep -c '^processor[ :]'` ii=$(($nn + $i)) while [ $i -lt $ii ]; do i=$(($i+1)) ( on "$h" ./crypto-keygen-standalone -n $n -o /dev/stdout "$@" > bruteforce-$i & pid=$! trap 'kill $pid' TERM wait if [ -s "bruteforce-$i" ]; then trap - TERM mv "bruteforce-$i" "$outfile" kill -USR1 $mainpid else rm -f "bruteforce-$i" fi ) & pids="$pids $!" done done wait darkplaces/.travis-id_rsa-xonotic0000664000175000017500000000626013067716216016425 0ustar kalevkalevÔU&ñ´ \Õ‰™–!VŸ¯Y,öˆåÇ^JÕ[³eM¬Y³÷Ëu,,‘ÉY¥¢~}ìÄÞ¦Îù ìšaâÕêYµ£Õ¡þ9l"óg>UÑïžÇ'V Çb*¥rþP:Î(G?Þx›×Á‰vºÿ"#© òJ§Ö§`íå´g!w9>Ú3ÒmïeË)ÐÄ<§råÊöGŽ=ËO¾D@ÁbéGÕQù›5›±p°ÃCb;å;Rß 2bül:NäJâ×&¦©¡°˜­]ž½£>É€Çý¨¬Oöìéÿ„wìPËA¼jWËI§RþÌ×3œÏw(ÂT¶Ã÷t_R¾tlë òÃ!@ð’(¹j ìÌß.i,çKU\FôxÃILºøýCJbRŸ]™Û’F`¸Ugé Jñ{ÄPZ ³vÿ¶/v,˜‰êûR%ÄJ‘ÃÂbVRÌ\ ŸÚ X„ ÇÉøÏÒʲì«Æ þÝpÊfÉ7±Ö”ô%hE°ëå n­†¥@` ›ýæÜ®b·–i£k[ßßõÃíPˆsDt\Íl.íšùVø…asÝt88ÎÃdêñª (ÿ~HÚ¿®²`“tn\Ö”åÖ+ðþUÆè2,ˆ )Âh”çÏ–-–Í9TwH¬AM|‹,ŒOÙU²êÄ{éò@PÊ/Õ9WNúQà„yïsP3ÓFMP©U"å/ÛÀòâ9{¥•CkÒ'´ûRnƒüè²Q£,Ï\ÙÍ ÝÊý\×/"þ‚ü—n' ³ü¹ñ†µ'ùçÚª—ì‰g^iK…¢œLËú±ü‘Ó·z ªl lÎ=“|‘NÕÒ3Ÿ+Q¾"K鶊9’ÅT˜7Î)Ø*ïД\E,™ èŽ¶Œ›‰íÆuV ©WQ~xCœº‡MÚ—*§+ÊjD¤wýþ_’s SdõyŒÅt}n;äÈ'@A]³½íÕùú²Ø;΀wÛZ¦œ0èMW³Ú$c_ðÑ"»J¬žrð´ƒAd¨±…ÈÕA’_駘rG½ýÞ[ÜŸ 7ìCnÕ¨œ\sþžroÍÄÍåáÛ õ¬]br0‹&na°è{î‹R>½’¶m©!“g‘kâêÜ;‘ÃpHÔ§fð$UÅÞvdçpئ&¬½JåU–5ê„ô‚¿ë|Ü ,C7Bj®ÄÁ”ŒD»è† »wåE$@)!šî/”x&"»dµˆJ:ÑÖŲ¤×‹m£ƒd­Õ°»P\yÝéÚhFfºb)îÎkŒ-.C·Qc6àŠ7oÅô$ƒƒuÓ4þÞó§´.)cIddôá¥Ò@Ã9y££oò¹O!¼7á5±òª×§’¦}*)êÈ-Eµœõ$yîe‹W¬OöϪ‰@U;ÙàÑd5v¸£]®ÀpP$€¤yq]ç·^Œ 7S‹ñ­­ê˜ƒ/¹Æ‡¨¸'òb2<.¿üT_‹åJr<´oþ8›^ßça£I3Ú’Z{iÇž¯!@ ç^Çáf 5¿&æ ®ìÁv}¢oâVõYF ½,WZ±ƒmðhò3ª‡ýuí™èÙåÔœ;t¸#%¯ .€ßv',ªt“žÖJ ³sHî9:‡€1V@§ÝäœÉOª](šåaÉž1²ÎÇð¤~„é¼. óŒÈ0uv`ÈÆr„"Ø·BÙ¹ÖúžÃÊ…³]˜ˆCÌf,bü7Bþ*jÔàñ¨[Ìî㜹JDz9%·|A±fÉkK¯9ÿŒ1 _ÏV+e¶4„ø €%q±M,e–^µ~tÙÑÃêöÔ%Ðdœ/Ôãn¸xåKü‰CÈs‹Fݧ³ÏQñ~ÃÕÁTž¢`ù½6_¼/Ñ+&â-s^¥´N¯ÈTãOdVè#`m`ÀÕ ’58Þ£~”™`3Žl¢&\MÇt%ÖÆöçÇçmSë‰òQuØw,÷±ý·cy·„œoº5q™Ÿ¢¸+§Æ$£[%^\Öá¦áÒýb̦æyT‰Ñûs˼žd÷æ´¬Ÿ*…+Ü—+ÊÇà¦çaRBkh:1ÚÄÕPŠ§ã.‚„œ€³h°>“Ù"•èbMœE]üïkìNY`zªLM!nh€¹:ÇÚ^ªY ‰1 ËJ×¾Eg)' d¿™à<שvárëÔP Yöõd®Š´ÜMå{œ§ä¨Ó6%„Í%¾{áøä× ¯=þïÛ0á'èp7äœaŸÅ”òÖ_ Z>œN.·Ù'}”fy¨Ì{¨FÚš4u%!jY²‰Pàô¸™¡n Ñ#¿Ð«vjß,%l͹î¶0iÎìÂbÛŸÓÝÜHõ°YH@V.ŒgóÝ‚öå~;t€øÛwSì¸ÏŒÏÍ{íÙ-P†ù=æƒh¸fN;`ˆJøi8ö«.u”ˆ€÷Ä×Gy¨Ò}²”–€bwãœdªÊª 2ŽoRL™à¬l…Lìü&Ÿÿâ®·ËpU‹Éí~·tjæé©êë…ð#ÏSY5µqå÷C…»Òàòt<\KùÀÃd½ B™ú´C"âNæWö½zÆ`EGÊhÏðnÌSXn)lv;°«$òB¾›Þb ’57ò¡dN]´n†ÊÞºƒ9›öÀW 7Šm¤å%CX‰wl-âxl…ÍÈ<<ã4I‘lôiOY5¯uw7ÕÜçÈm‡L  1{cÒ¹æ»;² „:„þP3º%ÃnašRmïPɉòŽ{™R6Êú";8ÀR¦«Â´+e›‰Ha5| A Ìþ Ó{y“ïö~,ñ¤_ÿU§º±üG%3—\W¢'Š>ÓÛÕ-ÀŽöÄÎÄ•!5 o‡åò°sVYª÷ ®vmÔ >&™Ö؆û¹¢âÜÈ‹Ó»qþ^€ð„GlbrpCkv3º8¦¿Ô^‘#`xdý‡ß²|§vG¤ Ú[tNò”v[j¬2ÚsR¯bQÐéôn¹þ m5ÑUú5#!ãuNi\<ß.Ò¨XT™LxÎ6‹‚è—¯Úã#›¤w~}× ÊE"Ä à4ûç0Ñ–—÷,†É©ùEªó^#ÚóFÖLx*Òʱø…ÝÁw_ã˧(z= #‚ Å…Ü‹£úÏ¢úî«e-O¯·p¿ò¦™Å¿BZ‹ž\è·—Y™3‰„“ù·¾ÙÖ-Sì½darkplaces/SDLMain.h0000664000175000017500000000046313067716216013573 0ustar kalevkalev/* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Feel free to customize this file to suit your needs */ #import @interface SDLMain : NSObject @end darkplaces/todo0000664000175000017500000053043313067716222013065 0ustar kalevkalev- todo: difficulty ratings are: 0 = trivial, 1 = easy, 2 = easy-moderate, 3 = moderate, 4 = moderate-hard, 5 = hard, 6 = hard++, 7 = nightmare, d = done, -d = done but have not notified the people who asked for it, f = failed, -f = failed but have not notified the people who asked for it -d bug darkplaces d3d9: drawsetcliparea not working right - seems to be Y-flipped, this also affects menus in Steelstorm (VorteX) -d bug darkplaces d3d9: overbright particles get weird colors (VorteX) 0 bug darkplaces server browser: scrolling wraps weirdly (causing duplicates of first entry) when there are fewer servers than a full screen can show (LordHavoc) 0 bug darkplaces renderer: r_showbboxes breaks updates of the bloom texture (Supa) 0 bug darkplaces android: make sure android:theme is set reasonably to avoid having performance issues on Adreno (it may need to be android:theme="@android:style/Theme.Translucent" or maybe some other value?) (banshee21) 0 bug darkplaces client csqc: CSQC_InputEvent is supposed to handle mouse movement, compare to FTEQW code (avirox) 0 bug darkplaces client csqc: engine prediction function is not implemented - could just return the engine's current cl.movement_origin (Spike) 0 bug darkplaces client csqc: entities not being drawn with VF_PERSPECTIVE 0? (daemon) 0 bug darkplaces client csqc: input queue functions needed for csqc prediction aren't implemented (Spike) 0 bug darkplaces client csqc: precaches on client don't work, have to precache on server - what's going wrong here? (daemon, Urre) 0 bug darkplaces client csqc: string stats should be sent as a single stat with WriteString (Spike) 0 bug darkplaces client csqc: there is no WriteFloat, making ReadFloat useless (Urre) 0 bug darkplaces client csqc: unproject Z handling differs in DP and FTEQW, project also must be directly compatible with unproject (avirox) 0 bug darkplaces client csqc: world not being drawn with VF_PERSPECTIVE 0? (VorteX) 0 bug darkplaces client net: proquake server compatibility broke between the last build of 2008 and the first of 2009 (Tomas Vredeveld) 0 bug darkplaces client qc: trace_dphittexturename should be a static buffer rather than a tempstring, it produces unnecessary warnings currently (div0) 0 bug darkplaces client qw: add .mvd demo support 0 bug darkplaces client qw: add .qwd demo support 0 bug darkplaces client qw: add spectator cvar (Plague Monkey Rat) 0 bug darkplaces client qw: add spectator prediction 0 bug darkplaces client qw: inactive player entities are showing up at 0 0 0 (Plague Monkey Rat) 0 bug darkplaces client qw: qw skins should only be active on progs/player.mdl (Plague Monkey Rat) 0 bug darkplaces client qw: qw/skins/*.pcx need to be cropped to 296x194 and loaded as internal textures so they are split into multiple layers (Plague Monkey Rat) 0 bug darkplaces client qw: restrict wateralpha and such cvars according to what is permitted in qw serverinfo? 0 bug darkplaces client sound: -nocdaudio disables emulated music tracks (Tomas Vredeveld) 0 bug darkplaces client timedemo: investigate bogus results of one-second min/avg/max, clearly not working properly (Willis) 0 bug darkplaces client win32: add some kind of workaround for Windows Firewall prompt killing the OpenGL context (motorsep) 0 bug darkplaces client: can't move mouse around in nexuiz menu if vid_mouse is 0 0 bug darkplaces client: if you press 1 during the demo loop when quake starts, escape doesn't do anything until you hit some other key (daemon) 0 bug darkplaces d3d9: alphatest not working (VorteX) 0 bug darkplaces d3d9: gamma not working (VorteX) 0 bug darkplaces d3d9: shadowmaps are clipped - does this mean r_shadows 2 or light shadowmaps? (VorteX) 0 bug darkplaces d3d9: water/refraction/reflection/warp render scissor is wrong (SavageX) 0 bug darkplaces effects: add a cvar to disable colored dlights (leileilol) 0 bug darkplaces effects: dlights don't look anything like quake ones, bring back lightmap dlights as a cvar - with both classic and old dp falloff modes (leileilol) 0 bug darkplaces effects: quake mode blood trails don't have the appropriate slight gravity - there may be other effects missing this also (leileilol) 0 bug darkplaces filesystem: -game nehahra does not load properly; menu does not work properly - probably not activating gamemode 0 bug darkplaces loader: crash when a mdl model has more replacement skins than the model contains (Lardarse) 0 bug darkplaces loader: make rtlight entity loader support q3map/q3map2 lights properly, they use a spawnflag for LINEAR mode, by default they use 1/(x*x) falloff (Carni, motorsep) 0 bug darkplaces loader: mcbsp hull selection is ignoring the custom hulls supported by mcbsp (div0) 0 bug darkplaces loader: png loading crashes if the image is transparent (Urre) 0 bug darkplaces loader: q1bsp loader computes wrong submodel size for submodels with no surfaces, such as a func_wall comprised entirely of SKIP or CAULK brushes (neg|ke) 0 bug darkplaces memory: memstats doesn't account for memory used by VBO/EBO buffers in models 0 bug darkplaces mobile: add a command to lock in current accelerometer axes as default orientation (calibrate), which the menuqc/csqc can call 0 bug darkplaces mobile: add a landscape mode for mobile devices where width is > height, which changes 2D and 3D rendering orientation 0 bug darkplaces qw: tf skins not working (xavior) 0 bug darkplaces readme: it would be a very good idea to add documentation of sv_gameplayfix_* cvars in the readme as a means to run broken mods (xaGe) 0 bug darkplaces readme: readme says that q3 shaders are not supported, this is not true, describe the working features in detail (qqshka) 0 bug darkplaces renderer deferred: scissoring artifacts are very obvious on shadowless lights, turn off scissor (Rock) 0 bug darkplaces renderer: GL13 path has broken handling of unlit surfaces in Nexuiz toxic.bsp - the small red light surfaces are black in GL13 path (m0rfar) 0 bug darkplaces renderer: coronas are not affected by fog (div0) 0 bug darkplaces renderer: if an animated model has transparent surfaces, each one calls RSurf_ActiveModelEntity, recomputing all vertices 0 bug darkplaces renderer: if an animated model is entirely transparent, the RSurf_ActiveModelEntity call updating vertices is completely wasted 0 bug darkplaces renderer: r_speeds counts entities twice with r_hdr 1, the whole counting code probably needs auditing (Morphed, and jim on inside3d) 0 bug darkplaces renderer: rtworld seems to mess up player pants/shirt colors 0 bug darkplaces renderer: sprites with ! in the filename are supposed to receive lighting (Asaki) 0 bug darkplaces server csqc networking: csqc entity sending code does not currently detect packet loss and repeat lost entities (FrikaC, Chris Page, div0) 0 bug darkplaces server qc: trace_dphittexturename should be a static buffer rather than a tempstring, it produces unnecessary warnings currently (div0) 0 bug darkplaces server: SV_PushMove is ignoring model type in its angles_x handling, where as the renderer checks only model type to determine angles_x handling (Urre) 0 bug darkplaces server: SV_PushMove's call to SV_ClipMoveToEntity should do a trace, not just a point test, to support hollow pusher models (Urre) 0 bug darkplaces server: X-Men mod has flying enemies that tend to go through the floor, sometimes preventing a level from being completed. x1m1 has two alcoves that open to reveal Storm and Archangel but often Storm disappears through the ground of hear alcove and then the level can't be completed. A similar problem with Storm apears in x1m3 in an alcove. (ewwfq yahoo com) 0 bug darkplaces server: savegames do not contain parms for multiple players, restoring a savegame leaves parms for other players as they were at the time of loading the savegame, clearly wrong (daemon) 0 bug darkplaces server: savegames do not save precaches, which means that automatic precaching frequently results in invalid modelindex values when reloading the savegame, and this bug also exists in many quake mods that randomly choose multiple variants of a monster, each with separate precaches, resulting in a different precache order when reloading the savegame 0 bug darkplaces wgl client: during video mode setup, sometimes another application's window becomes permanently top most, not darkplaces' fullscreen window, why? (tZork) 0 bug darkplaces windows sound: freezing on exit sometimes when freeing sound buffer during sound shutdown (Black) 0 bug darkplaces windows sound: surround sound fails in windows client, falls back to stereo (greenmarine) 0 bug dpmaster: if server does not reply to queries in a reasonable period of time it should be removed from the list, this is necessary for the server to be removed when it shuts down and sends out two heartbeats to let the master know that it is shutting down, and isn't there to reply to queries (jitspoe, div0) 0 bug dpmaster: if server does not reply within 5 seconds to a query it should be removed (jitspoe, div0) 0 bug dpmod: LinkDoors seems to ignore .origin on door entities when comparing if they overlap 0 bug dpmod: allow selection of weapons with secondary ammo but no primary ammo, and switch away if trying to fire primary ammo you don't have (romi) 0 bug dpmod: chthon stops attacking in coop if shot enough 0 bug dpmod: crash when dog attacks you in dpdm2 deathmatch 7 with bots present (Zombie13) 0 bug dpmod: figure out what's wrong with the bots 0 bug dpmod: go through http://www.inside3d.com/qip/q1/bugsmap.htm and fix map bugs by using replacement .ent files (Lardarse) 0 bug dpmod: identify what could cause huge angles values (1187488512.0000) on a dog entity, may be related to anglemod, FacingIdeal, ai_run, or dog_run2 (Zombie13) 0 bug dpmod: impulse 102 isn't removing the bots 0 bug dpmod: in dpmod_qcphysics_casings the two main particles have the same mass, realistically speaking the rear one should have more mass than the front one 0 bug dpmod: it is (rarely) possible to die and be unable to respawn 0 bug dpmod: monsters don't properly chase you around corners, this is not an engine bug because they work fine in id1 but not dpmod, the knight at the start of e2m2 makes for good testing by shooting him and then hiding behind the crates (StrangerThanYou) 0 bug dpmod: monsters falling out of level? 0 bug dpmod: monsters shouldn't constantly do sightsounds on a slain player, it's annoying and silly 0 bug dpmod: monsters that spawn underwater shouldn't drown, just because it somewhat breaks the intended behavior of maps 0 bug dpmod: test for any unnamed death messages that might be happening 0 bug dpmod: the id1 map e2m4 has a bug where 4 scrags don't spawn in normal/hard skills because of a mislinked trigger, the Quake Done Quick team figured this out and a trigger_relay with target "t58" should be changed to target "t66" (Lardarse) 0 bug hmap2 -qbsp: errors still occur in mrinsane's newmap.map file, tyrqbsp does not have these problems (mrinsane) 0 bug hmap2 -qbsp: portal clipped away errors and a crash occur in vaccui (Epicenter) 0 bug hmap2 -qbsp: various errors still occur for Predator's Transfusion maps (Predator) 0 bug hmap2 -vis: test that bitlongs code works properly on big endian systems such as Mac (LordHavoc) 0 bug hmap2: figure out why there is a subtle difference between bmodel lighting and wall lighting, something to do with nudges causing different attenuation? (Urre) 0 bug hmap2: handle \" properly in hmap2 cmdlib.c COM_Parse (sort) 0 bug hmap2: the plane-distance based projection size for polygons doesn't work in certain wedge cases where two sides of a wedge brush are very near origin but the entirety of the wedge brush is much larger 0 bug hmap: strip .map extension from filename if present 0 change csqc client: add float in_mouse_centered which indicates the engine should lock the mouse at the center of the window, only gathering relative motion (in_mouse_window_position stops being modified - it is not set to the center as that would be useless) 0 change csqc client: add float in_mouse_keep_in_window which indicates whether engine should be currently restricting the mouse pointer to the window (useful in point and click games such as RTS games) 0 change csqc client: add float in_mouse_show_system_cursor which indicates whether the system mouse pointer should be visible while over the window 0 change csqc client: add svc_updatestatstring, svc_updatestatfloat, and keep 3 cl.stats arrays, of int, float, and string types respectively, store svc_updatestatstring in the string one, store all others in both int and float formats (KrimZon, Chris, Spike) 0 change csqc client: add vector in_mouse_motion which indicates mouse movement since previous frame (even if in_mouse_window_position did not change, such as when in_mouse_centered is on) 0 change csqc client: add vector in_mouse_window_position which indicates cursor position over the game window (real position of mouse in the window) - not updated if in_mouse_centered is true 0 change csqc client: replace input_buttons with in_button0 and similar (matching server qc fields), auto-detect these at load 0 change csqc server: change SendEntity to take the client as "other" rather than a parameter 0 change csqc server: implement packet logging of csqc entities to deal with packet loss 0 change csqc server: replace AddStat with RegisterStat and RegisterStatString, make them send float or string only, with string taking up only one stat slot and being sent using svc_updatestatstring, make it automatically send integer values as svc_updatestat/svc_updatestatubyte and non-integer values as svc_updatestatfloat (KrimZon, Chris, Spike) 0 change csqc server: replace per-entity SendEntity function with a global SV_SendEntity function, because it does not make it sufficiently clear to modders that the .SendEntity function has no special meaning, that an ENT_ constant must be set up, and so on, it would be more clear if there was only a single global SV_SendEntity function, which can still call out to a .SendEntity or whatever if it wishes, but is more likely to do an if else on a .net_type field or similar (Urre) 0 change darkplaces client: disable all possible 'cheat' things unless -developer is given on commandline, this includes r_show*, r_test, gl_lightmaps, r_fullbright, and would require changing -developer to only set developer to 1 rather than 100, as 100 is too annoying 0 change darkplaces client: modify cl_particles_quake to make all the engine dlights be white and look as much like quake as possible (Jago) 0 change darkplaces client: particles shouldn't be using contents checks to decide whether to die, they should use movement traces 0 change darkplaces client: turn off coronas on dlights (Jago) 0 change darkplaces extensions: edit FRIK_FILE documentation to mention that fgets uses its own separate buffer, so only one fgets can be done at a time without uzing strzone, but that this is not true with DP_QC_UNLIMITEDTEMPSTRINGS (FrikaC) 0 change darkplaces extensions: edit FRIK_FILE documentation to mention that strcat uses its own separate buffer, and that a = strcat(a, b);a = strcat(a, c); works correctly despite this, but that strcat is far more flexible with DP_QC_UNLIMITEDTEMPSTRINGS (FrikaC) 0 change darkplaces general: make r_speeds 2 show timings for other subsystems such as client, sound, server, network (Carni) 0 change darkplaces loader: load *lava and *teleport and *rift textures as a black diffuse texture with all the color put into a glow layer, and remove the MATERIALFLAG_FULLBRIGHT accordingly, this way replacement textures can make it not glow (Kedhrin) 0 change darkplaces memory: optimize model loaders to use less individual allocations, especially the q1bsp loader, it is responsible for a lot of very small (1-8 byte) allocations in the memlist all report 0 change darkplaces menu: move all options into a submenu so that people won't keep ignoring the other submenus 0 change darkplaces networking: make darkplaces detect its *public* client port from master server and send that in nq connect messages (wallace) 0 change darkplaces protocol: PRYDON_CLIENTCURSOR should use a stat and .prydoncursor field instead of the cl_prydoncursor cvar, because stuffcmd is a bit icky (FrikaC) 0 change darkplaces protocol: in dp8 protocol change clc_move to include a separate want-prediction flag rather than sending 0 movesequence for unpredicted moves, this will allow the client to know its own ping regardless of whether prediction is used. 0 change darkplaces protocol: increase maxplayers limit to 65535, send maxplayers as an int in the serverinfo packet, send colormap indices as a short, send svc_update* using short player indices (discoloda) 0 change darkplaces protocol: make svc_cdtrack use strings rather than a numbers in next DP protocol, so that named tracks can be used by maps 0 change darkplaces protocol: send origin and angle updates as individual components in entities like Quake did, rather than combined ORIGIN/ANGLE bits like DP5/6/7 do 0 change darkplaces protocol: use q3 print "print message" command packet instead of qw print 'nmessage' command packet? (div0, KadaverJack) 0 change darkplaces prvm: prvm_globals should print values of globals like entity fields do, not just names 0 change darkplaces readme: make sure that cl_capturevideo is documented 0 change darkplaces renderer: add a polygonoffset for rtlight passes to try to work around zfighting issues on mac, this would also change the depthfunc to LEQUAL rather than EQUAL (div0) 0 change darkplaces renderer: rename r_drawportals to r_showportals, and move its declaration to gl_rmain.c, and make it work with r_showdisabledepthtest 0 change darkplaces server: make "viewmodel" command code precache a new model and set it, rather than changing the meaning of the player model 0 change darkplaces server: support sys_ticrate 0 as variable framerate mode 0 change darkplaces: make vid_mouse 0 mouse movement work with menu.dat 0 change dpmod: add a ghostly cvar and change skill 5 code to check it 0 change dpmod: make shamblers be worth 5 frags, shalrath be worth 3 frags, and fiends be worth 2 frags (Zenex) 0 change dpmod: stop using playernogun/playergun models, go back to ordinary player.mdl, this saves a bit of memory 0 change hmap2: qbsp should do tjunc fixing on leaky maps 0 change revelation: change the wabbit kill message to " was hunting wabbit but shot " " instead" 0 change zmodel: include the example script in the build zips, not just in the files directory 0 feature darkplaces client csqc: DP_CSQC_SOUNDLENGTH extension which defines a builtin float(float f) soundlength = #??; which returns the sound length in seconds, or 0 if the sound is not loaded, or the engine was started with -nosound (ChrisP) 0 feature darkplaces client particles: effectinfo.txt should have a "particlefont" command specifying a filename, number of cells per row, number of rows, number of bottom rows that are beams, this would allow more particle images to be used (ChrisP) 0 feature darkplaces client rtlights: ChrisP has a suggestion of selecting rtlight properties using the number keys, and increasing/decreasing their value using the mouse wheel (cvars needed for amounts to adjust by), and ability to right click a light to delete it, left click to select a light, left drag to move a light on the XY plane it is on (ChrisP) 0 feature darkplaces client text: support for font rasterizing at pixel-accurate sizes and caching them to disk, support freetype2 for this purpose if it is present (ChrisP) 0 feature darkplaces client: add DP_GFX_EFFECTINFO_TXT to extensions and document it, the feature has been in for a long time, also update wiki.quakesrc.org accordingly 0 feature darkplaces client: add a cl_showspeed cvar to display a hud overlay of your current velocity, speed as length of velocity, and speed along forward vector (Spike) 0 feature darkplaces client: add a cvar to make the renderer use a different entity for pvs than for viewing, this might be useful for a third person camera that should only see what the player sees (Urre) 0 feature darkplaces client: add an alias to control whether the nexuiz logo.dpv splash video plays at startup and whether the menu opens, this way the config can change it before it happens, for instance to disable it, or to set up a similar thing in other games (green) 0 feature darkplaces client: add cvar r_fonttexturenearest to force font textures to be GL_NEAREST sampled and disable the squished-character boxes, although that should be a different cvar (leilei) 0 feature darkplaces client: add ezQuake-style hud layout as an option - http://quake.tek.pl/_sshots/candy/redfog.png 0 feature darkplaces client: add test and test2 commands, test queries a server and prints player list, (Yellow No. 5) 0 feature darkplaces client: somehow handle alt-enter on windows, and any other equivilant toggle-maximize shortcut on other platforms (Spirit) 0 feature darkplaces console: centerprint and notify text printing should not count color codes in their length estimation (Dresk) 0 feature darkplaces dpv playback: when video ends, execute a console command, perhaps "endvideo", this could be an alias set by a mod before it began playing the video (SavageX, motorsep) 0 feature darkplaces editlights: add r_editlights_edit commands for turn/turnx/turny/turnz to allow angle adjustments using binds (Kedhrin) 0 feature darkplaces editlights: split rtlight drawshadows option into drawworldshadows and drawentityshadows options, this allows combinations like no world shadows (for speed) but still having entity shadows (Mitchell, romi, Kedhrin) 0 feature darkplaces loader: add hud_clearprecache and hud_precachepic commands to preload pics by name, these get reloaded by r_restart as well, mods can put a lot of these commands in their default.cfg to precache needed hud art (Tomaz) 0 feature darkplaces loader: load .vis files produced by hmap2 0 feature darkplaces menu: add r_shadow_glsl_offsetmapping cvars to menu (Kedhrin) 0 feature darkplaces menu: map selection menu for singleplayer 0 feature darkplaces networking: download updated csprogs.dat if crc mismatches (div0, Dresk, Chris) 0 feature darkplaces protocol: add DP_GFX_QUAKE3MODELTAGS_ATTACHMENTTYPE extension to allow attachments to affect only origin or only orientation, and enable/disable client camera turning while attached (Urre) 0 feature darkplaces protocol: add DP_SENSITIVITYSCALE extension which scales sensitivity on client like viewzoom does, but without affecting fov, note if this is non-zero it overrides viewzoom sensitivity entirely, it does not scale it (Urre) 0 feature darkplaces protocol: add DP_TE_BUBBLES to make a burst of bubbles underwater (Supa, shadowalker) 0 feature darkplaces protocol: add DP_WEAPONSKIN extension which would add a .float weaponskin field (protoplasmatic, Kazashi) 0 feature darkplaces protocol: add EF_NOLERP effect for mods to use to prevent interpolation across teleports (Kinn, CuBe0wL) 0 feature darkplaces protocol: add TE_LASER effect with values Entity owner Vector start Vector end Byte palettecolor Byte startalpha Byte endalpha Byte beam width (in quake units) Byte lifetime (0-255 for 0-1 seconds), skin index (looking up a gfx/particles/beam%03i.tga), it fades from startalpha to endalpha over time (Wazat, Vermeulen) 0 feature darkplaces protocol: add a .fatness field which enlarges models along their vertex normals, this won't work on bsp models or sprites, label this as FTE_PEXT_FATNESS in the extensions string (Dresk) 0 feature darkplaces protocol: add a .scalexyz field which would allow non-uniform scaling of models, and may requires upgrading some lighting code but I am not sure (Dresk) 0 feature darkplaces protocol: add a new TE_ explosion effect with RGB color choice for particle color and another choice for light color (VorteX) 0 feature darkplaces protocol: add an EF_BULLETTRACER trail effect which can be used to make an invisible entity into a tracer, a bright streak leaving a faint smoke trail (Carni) 0 feature darkplaces protocol: add lava-steam particle puff effect for bursting lava bubbles (Zombie) 0 feature darkplaces protocol: add support for .float corona and corona_radius to control corona intensity and radius on dlights 0 feature darkplaces prvm: if developer is >= 100, list unfreed strzone strings when level ends (div0) 0 feature darkplaces prvm: modify PRVM_ExecuteProgram to be able to call builtins directly, and add prog->argc = before every PRVM_ExecuteProgram call (div0) 0 feature darkplaces qc: add FTE_STRINGS extension, and specifically support -1 for strncmp/strncasecmp to compare whole string (div0) 0 feature darkplaces qc: add support for ZQ_QC_TOKENIZE so that csqc can use tokenize too (KrimZon) 0 feature darkplaces quakec: DP_QC_STRFTIME extension providing strftime function to find out what the current time is with a format string (FrikaC) 0 feature darkplaces quakec: add a DP_QC_STRCATREPEAT extension providing string(float atimes, string a[, float btimes, string b, [float ctimes, string c, [float dtimes, string d]]]) strcatrepeat = #???; which repeats the given strings a given number of times and concatenates them together (like many strcat calls), can be given 2, 4, 6, or 8 parameters, stores it into a temp buffer, and returns the temp buffer (FA-Zalon) 0 feature darkplaces quakec: add crossproduct builtin, as DP_QC_CROSSPRODUCT, and suggest that this is a viable alternative to vectorvectors when you have two vectors already 0 feature darkplaces readme: add documentation about r_lockpvs, r_lockvisibility, r_useportalculling, r_drawportals, r_drawcollisionbrushes, r_showtris, r_speeds, r_shadow_visiblevolumes, and r_shadow_visiblelighting. 0 feature darkplaces readme: add log_file and log_sync documentation (Edward Holness) 0 feature darkplaces readme: document the ctrl-escape hotkey for toggleconsole (LordHavoc) 0 feature darkplaces renderer: add a rtlight flag to disable vis culling, for ambient area lights and such (Kaz) 0 feature darkplaces renderer: add a water caustics lightstyle for use on rtlights and to someday be supported on multi-pass lightmapped rendering (skite2001) 0 feature darkplaces renderer: add cubemap support to low quality rtlighting path for cards that support >= 2 TMUs and cubemap 0 feature darkplaces renderer: add optional filename parameter to screenshot command (Chris) 0 feature darkplaces renderer: add per-entity PolygonOffset to renderer, to allow zfighting bmodel/world glitches to be fixed, this has to affect all rendering involving the entity, including light/shadow (Tomaz) 0 feature darkplaces renderer: add procedural ripple distortion texture of some sort for use with envmap reflections (FrikaC) 0 feature darkplaces renderer: add r_shadow_light_polygonoffset and polygonfactor cvars for lighting polygons (Diablo-D3) 0 feature darkplaces renderer: add r_showsurfaces cvar which adds an extra texture layer of additive flat color, based on the surface number, similar to r_drawflat in software quake (div0) 0 feature darkplaces renderer: add rtlight "avelocity" parameter to make lights that spin, useful with cubemaps (romi) 0 feature darkplaces renderer: document dp_water, dp_reflect, dp_refract .shader commands, add extension indicating this feature and document it in dpextensions.qc (leileilol) 0 feature darkplaces renderer: gl_picmip -1 and -2 settings based on twilight code (Harout Darmanchyan) 0 feature darkplaces renderer: make showfps display GL renderer string and CPU - figure out how to detect this from the OS 0 feature darkplaces renderer: save r_shadow_glsl* cvars (and possibly a few others) to config because they are useful user settings (SavageX) 0 feature darkplaces renderer: support tcgen in q3 shaders (ob3lisk) 0 feature darkplaces server: DP_SV_FINDPVS 0 feature darkplaces server: add .maxspeed field to control player movement speed in engine code, call it QW_SV_MAXSPEED (Carni) 0 feature darkplaces server: add DP_QC_STRTOKEN extension with these functions: float strtokens(string s, string separator) = #;string strtoken(string s, string separator, float index) = #; (FrikaC) 0 feature darkplaces server: add DP_SV_DRAWONLYTOTEAM extension (Supajoe) 0 feature darkplaces server: add DP_SV_TRAIL_EFFECT extension - .float trail_effect; field which can be networked as an automatic trailparticles() associated with an entity 0 feature darkplaces server: add PF_tokenizeseparator function and DP_QC_TOKENIZESEPARATOR extension 0 feature darkplaces server: add a .collision_cancollide QC function call to decide if an entity should collide with another, or pass through it (Uffe) 0 feature darkplaces server: add a DP_QC_WARNING extension which has a "warning" builtin that does a PF_WARNING just to print the requested message, opcode dump, and stack trace (FrikaC) 0 feature darkplaces server: add a clipmask thingy to allow QC to mask off collisions as it wishes (Uffe) 0 feature darkplaces server: add a different progs.dat defs for darkplaces-based games to use instead of the quake defs, which would force use of csqc entity networking and disable legacy stuff like quake physics, and enable use of ODE physics perhaps 0 feature darkplaces server: add a sv_gameplayfix_slidewhenstandingonmonster cvar to allow the FL_ONGROUND when ontop of a SOLID_BBOX/SOLID_SLIDEBOX to be disabled 0 feature darkplaces server: add a sv_netticrate cvar which allows less frequent network updates (Urre) 0 feature darkplaces server: add back edict, edicts and edictset commands (just as stubs that call the prvm_edict/edicts/edictset server commands) for convenience and compatibility with quake modding practices 0 feature darkplaces server: add cl_prydoncursor_centeredcursor cvar and PRYDON_CLIENTCURSOR_CENTEREDCURSOR extension (Wazat) 0 feature darkplaces server: add sv_antilag cvar which would upgrade the aim() builtin to aim at the creature the player's prydon cursor trace was hitting (Spike) 0 feature darkplaces server: float(vector viewpos, entity viewee) checkpvs = #240; //(FTE_QC_CHECKPVS) (Urre, Spike) 0 feature darkplaces server: make SV_PushMove check .owner on pusher and pushee to ignore related entities, needed for Urre's stick physics code, make sure this is cvar controlled (Urre) 0 feature darkplaces server: make fopen builtin have the ability to disable fopen builtin access to read /, read data/, write data/, or disable fopen builtin entirely 0 feature darkplaces server: make noclip/fly cheats use MOVETYPE_CHEATNOCLIP/MOVETYPE_CHEATFLY which would have the nicer movement interface (Spikester) 0 feature darkplaces server: when "exec config.cfg" is encountered, add on an automatic "exec server.cfg" if running a dedicated server (mortenoesterlundjoer) 0 feature darkplaces sound: Lordhavoc needs to talk to fuh about snd_macos.c (fuh) 0 feature darkplaces sound: add extension info for mod music playback (leileilol) 0 feature darkplaces sound: the new sound engine should have a cvar for random variations of pitch on sounds like in doom (RenegadeC) 0 feature darkplaces website: add download link for deluxemaps_id1.pk3 0 feature darkplaces website: add q1source.zip to downloads page and suggest that mingw/Dev-C++ users may need the dx headers from it (cpuforbrain) 0 feature darkplaces windows: include P3, P4, Xeon, AthlonXP, and Athlon64-32bit optimized windows dedicated server binaries for windows server admins to use, http://www.alientrap.org/forum/viewtopic.php?t=522 (PoWaZ) 0 feature darkplaces: .vis files - like .lit but replacement vis data, note this also requires .leaf files (knghtbrd) 0 feature darkplaces: add DP_GFX_EFFECTINFO extension to indicate that effectinfo.txt is supported, and thoroughly document its syntax (Morphed) 0 feature darkplaces: add a progress bar to the loading screen, this would be updated by the model/sound loading process 0 feature dpmaster: add back qw and q2 server support, with records not matching for different protocols to prevent qw/q2 protocols being used to poison q3 protocol records 0 feature dpmaster: don't filter by protocol version if query is for protocol version 0, this way server browsers can see multiple versions of a single game, should probably also add a 'any' gamename that disables gamename filtering (Angst, Elric) 0 feature dpmaster: release an example /etc/init.d/dpdmasterd script based on the one LordHavoc is using 0 feature dpmod: add a g_spawnprotectiontime cvar which would default to 1 (invulnerable for 1 second after spawn) to prevent mining the spawn points 0 feature dpmod: add a gravity gun weapon, able to grab and launch monsters 0 feature dpmod: add a summon command using KRIMZON_SV_PARSECLIENTCOMMAND which would summon the specified monster infront of you (Kedhrin) 0 feature dpmod: add higher quality replacement sbar images 0 feature dpmod: add knight/hell knight swords as player weapons (TimeServ) 0 feature dpmod: add mode with respawning monsters 0 feature dpmod: change kill awards to use DP_SV_CLIENTFLASHPIC (Tomaz) 0 feature dpmod: find a way to make deathmatch 7 get more difficult as kills increase? (Zombie13) 0 feature dpmod: make a editlights.cfg containing a lot of light editing binds as bindmap 5, also aliases to turn on/off that bindmap, so users can bind one key to the switch command to switch to a light editing mode and back again any time they want (HReaper) 0 feature dpmod: make rocket launcher ai code use altfire to detonate rockets near players (Vermeulen) 0 feature dpmod: make run animation play back according to movement speed (along v_forward), instead of just playing a continuous loop based on time (Urre) 0 feature dpmod: make spawning use viewzoom to start zoomed out 2.0 and then zoom in to 1.0 (Urre) 0 feature dpmod: make teleport leave an EF_ADDITIVE clone of the player which fades out 0 feature dpmod: merge alieninfestation speedmod into dpmod, especially the map 0 feature dpmod: replacement flame models using md3 and q3 shaders with animmap (skite2001) 0 feature dpmod: try making ball lightning mortar shamblers (scar3crow) 0 feature dpmod: try not adding gravity when onground to see if it gets rid of ramp sliding (Midgar) 0 feature dpmod: update stats to count monster kills in dm 7 and such 0 feature dpmodel: add support for compiling a model relative to a bone, for exporting head/upper/legs md3 player models more easily (Morphed) 0 feature dpzoo.map: flame jet 0 feature dpzoo.map: func_train with sky brushes 0 feature dpzoo.map: particlecube 0 feature dpzoo.map: rotating fan bmodels 0 feature dpzoo.map: rtlights in normal mode (when that extension is made) 0 feature dpzoo.map: thief-like area to sneak past a guard who can easily kill you (shambler?) to demonstrate lightlevel checking 0 feature hmap2 -light: add a new light type which matches the old hlight behavior, and a -hlight option to make lights default to this type (Crusader, Urre) 0 feature hmap2 -qbsp: add ORIGIN brush support in bmodel compilation (Carni) 0 feature hmap2 -visdump: save bsp vis lump to .vis and .leaf files (knghtbrd) 0 feature hmap2 -vispatch: replace bsp vis lump with .vis and .leaf file contents (knghtbrd) 0 feature hmap2: add CAULK texture support - delete surfaces using this texture, or at least don't link them (Tomaz) 0 feature hmap2: produce .vis files which contain a numvisleafs, an array of visofs values for all the leafs of the bsp, and the new data for the visibility lump, this would avoid the need for vispatch 0 feature lhfire: make a lhfire.txt and move the scripting info to it, add some more general explanation and tips 0 optimization darkplaces client: add cl_null.c so that dedicated server doesn't need client 0 optimization darkplaces collision: Collision_TraceBrushBrush should compare enterfrac changes to realfraction and skip out if further, also leavefrac (Vic) 0 optimization darkplaces collision: Mod_Q3BSP_TraceBrush_RecursiveBSPNode can be optimized to take a clipflags parameter like R_Q3BSP_RecursiveWorldNode (Vic) 0 optimization darkplaces collision: put patches on a delayed queue in q3bsp collision code so the trace is first clipped by brushes 0 optimization darkplaces collisions: modify q3bsp tracebox to scan the cluster bsp first (which should cut short a lot of traces), and make a list of brushes/curves encountered along the way, and then check them in roughly sorted order 0 optimization darkplaces loader: make loadimagepixels take a string of space separated path patterns to check for a file in, to cut down on the number of wasted file checks for things that never occur (Spike, Up2nOgOoD) 0 optimization darkplaces loader: remove the loop unrolling in Image_Resample32LerpLine and Image_Resample24LerpLine and related functions, as the loop is really too long to get much benefit, and it might even not fit in the L1 instruction cache on Pentium1 (fuh) 0 optimization darkplaces particles: make pt_rain spawning do a single downward trace and set a timer for the impact, to reduce collision load (LordHavoc) 0 optimization darkplaces renderer: add r_null.c so that dedicated server doesn't need renderer 0 optimization darkplaces renderer: store p->past->clusterindex into p->clusterindex to speed up R_WorldVisibility, also store p->past - data_leafs index into p->leafindex, and move the CullBox check to last (Vic) 0 optimization darkplaces renderer: support GL_ATI_separate_stencil since ATI does not support GL_EXT_stencil_two_side and GL_ATI_separate_stencil was integrated in OpenGL2.0 (romi) 0 optimization darkplaces server: implement first unused/last used entity range optimization on entity spawn/remove similar to the client particles (LordHavoc) 0 optimization darkplaces visibility: R_Q1BSP_BoxTouchingPVS and R_Q3BSP_BoxTouchingPVS should check pvsframe on nodes as well as leafs (Vic) 0 optimization darkplaces: calculate worldmodel farclip (corner to corner radius) at load 0 optimize darkplaces renderer: get rid of attenuation texture on lights because math is faster, add fastpath for no normalmap (Lava_Croft) 1 bug darkplaces WGL client: figure out why for some people GDI input has stuttering problems with gl_finish 0 mode (Kinn, Urre, romi, Spike, Black) 1 bug darkplaces WGL/GLX/SDL client bug: if sound is unavailable (causing a freeze waiting for it to become available), the config is reset (SavageX) 1 bug darkplaces bsd filesystem: read() is failing (not returning the requested amount) on freebsd when reading files, whether actual files or in a pk3 - somehow it is still able to read the pk3 zip directory though (suminigashi, Elric) 1 bug darkplaces collisions: curve collisions sometimes catch on the lip of the edge, pushing into the curved back wall around certain jumppads in Nexuiz for example consistently gets stuck just below the ledge (HReaper) 1 bug darkplaces command: "rate", "playermodel", "playerskin", "pmodel" commands can spam server console with usage statements (Spike) 1 bug darkplaces console: when logging using log_file and log_sync 0, setting log_file back to "" does not close the file until another message is posted? 1 bug darkplaces fs: invalid pk3 archives prevent engine from starting (Willis) 1 bug darkplaces input: fix stuck buttons during a level change (mercury82, tkimmet@ezworks.net) (further note: this is from the console becoming active temporarily and catching the key release when the player lets go during the loading stage, make it possible to release a button that was pressed before the console was activated, or make it execute -commands for all pressed binds when level starts) 1 bug darkplaces linux client: when clicked in a file manager it does not find the data directories as the working directory is not set, do a proper search for valid data directories in multiple paths, such as working directory, executable path, etc (Randy) 1 bug darkplaces loader: check for out of bounds lump data ranges in maps (FrikaC) 1 bug darkplaces loader: check for truncated sound files (FrikaC) 1 bug darkplaces makefile: add icon to windows sdl builds (RenegadeC) 1 bug darkplaces nexuiz: can't open console when in nexuiz menu (stahl) 1 bug darkplaces protocol: GAME_NEXUIZ: view sometimes stuck on its side while playing (update: this is not related to cl.viewangles, it's something else): http://www.digitalfunk.org/brewdles/Nexuiz/nexuiz000001.jpg (Brewdles) 1 bug darkplaces prvm: add back the leak checking http://cvs.icculus.org/cvs/twilight/darkplaces/prvm_cmds.c?r1=1.67&r2=1.68 (Black) 1 bug darkplaces prvm: bounds check function statement values to prevent qc from jumping to arbitrary memory (Spike) 1 bug darkplaces renderer or server: EF_NODEPTHTEST sometimes disappears when not in pvs (Urre) 1 bug darkplaces renderer: VP oriented sprites are not using the left/right vectors correctly as demonstrated in dpspbug.avi (Cheapy) 1 bug darkplaces renderer: add r_shadow_light_polygonoffset and r_shadow_light_polygonfactor variables to work around multitexture depth issues on TNT cards (Urre) 1 bug darkplaces renderer: do bloom effect before world crosshair and coronas and things (KrimZon) 1 bug darkplaces renderer: lit sprites (which use R_CompleteLightPoint) are being lit blue by glow_color 108 dlights (Cheapy) 1 bug darkplaces renderer: make sure that the texture fragment allocator can upload a full size block that uses the entire image, this may involve width/height comparisons needing a + 1 (fuh) 1 bug darkplaces server: PF_vectorvectors is broken, given a v_forward from makevectors (not using roll) it does not give the same v_right and v_up vectors (VorteX) 1 bug darkplaces server: add DP_QC_ENDFRAME extension/documentation and post it on wiki (tell Uffe) 1 bug darkplaces server: apparently MOVETYPE_WALK on non-players is frequently resetting origin to oldorigin, why does it think it's in solid? (Wazat) 1 bug darkplaces server: change maxplayers to be a cvar, and post a warning if it changes during a game saying it won't take effect until the next map command 1 bug darkplaces server: decide on an extension name for .ent loading and report it, also document in dpextensions (FrikaC, Gleeb, wiki) 1 bug darkplaces server: figure out what's breaking RenegadeC's TAOV monster jump code (RenegadeC) 1 bug darkplaces server: silver key missing in the map Menkalinan at http://quakemaps.nm.ru/maps2.html (zarquon) 1 bug darkplaces: if progdefs.h is out of date it causes a Sys_Error, after clicking OK on the message box the engine crashes, probably something to do with partially loaded progs 1 bug hmap: qbsp dies from runaway allocations if a duplicate plane is found on a brush (Tomaz) 1 change darkplaces protocol: document the TEI stuff used in Nexuiz 1 change darkplaces protocol: send additional stats for movement parameters and slowmo and gravity, replacing the rather silly cl_movement and cl_slowmo and cl_gravity cvars (Vermeulen) 1 change darkplaces renderer: add gunshot (shotgun pellet) and nail (spike) decals to the particlefont so that these can look different from explosions (Morphed) 1 change darkplaces renderer: add r_shadow_realtime_world 2 mode (and update menu), make 1 only render rtlights if they came from a .rtlights file and mode 2 render them even if they were imported from the map, always render imported ones if editing mode is turned on (Willis) 1 change darkplaces renderer: make "sky" keys in worldspawn override q3 sky shaders (Vermeulen) 1 change darkplaces renderer: make attachments use their parent's origin for model lighting 1 change darkplaces renderer: use 16th power specular instead of 8th power (LordHavoc) 1 cleanup darkplaces loader: add palette conversion capabilities to Image_CopyMux 1 cleanup darkplaces memory: add Mem_AllocNoClear function, and use it where possible, if developer is on it should clear with random garbage 1 cleanup darkplaces renderer: make Host_Error call error reset functions on renderer subsystems? (models are already flushed) 1 feature darkplaces client: add cl_particles_blood_color_r and g and b cvars to control blood color (Asaki) 1 feature darkplaces client: add cvars to control lighting quality to allow performance tradeoffs; r_shadow_ options for use of dot3 shading, etc 1 feature darkplaces client: change sort key in server browser using left/right arrows 1 feature darkplaces docs: fix lots of bugs and then retitle the website to get more publicity: DarkPlaces: Re-live Quake again... 1 feature darkplaces docs: write docs about in_bind/in_bindmap in readme (shadowalker) 1 feature darkplaces input: finish porting Quake2 keyboard stuff (Rick, FrikaC) 1 feature darkplaces loader: load separate _lower.tga and _upper.tga sky textures to allow resolutions other than 128x128 per layer (Idot) 1 feature darkplaces loader: make the rtlight entity loader support a rtlight entity to be used for lightstyles in q3bsp, these would render in both normalmode and realtime mode, but not actually be dlights (Kazashi) 1 feature darkplaces menu: add in_bindmap support to bind menu; a selector for which bindmap is actively being shown and bound in the menu, and add bind entries for some bindmap commands 1 feature darkplaces networking: hexdump the replies to the packet command? (Spike) 1 feature darkplaces particles: add a vertical splash effect to raindrop splashes, not just the ring (Stribbs) 1 feature darkplaces protocol: add DP_EF_CLIENTLOCKANGLES extension (prevents client from turning view, takes angles from entity) (Wazat for Battlemech) 1 feature darkplaces protocol: add DP_SV_CLIENTFLASHPIC extension: a function void svc_clientflashpic(entity cl, float alpha, float fadetime, string picname), to flash a pic in the center of the client's screen, useful for 'Double Kill' awards and such (Tomaz) 1 feature darkplaces protocol: add DP_SV_READCLIENTINPUT extension (.vector clientinput; works like .movement but for mouse or any other similar controllers) (Wazat for Battlemech, FrikaC, Urre) 1 feature darkplaces protocol: add a .modelflags variable which if non-zero overrides model flags - note, LordHavoc would prefer just adding more EF_ values (Arwing, frightfan) 1 feature darkplaces renderer: check out QMB lightning and lava effects (jeremy janzen) 1 feature darkplaces renderer: make r_fogsky cvar to control how much fog is rendered infront of the sky (Deej, C0burn) 1 feature darkplaces server: add DP_CLIENTCAMERA extension (.entity clientcamera; sets which entity the client views from) (Wazat for Battlemech, [TACO]) 1 feature darkplaces server: add EndGame function (called on server shutdown or level change) (Vermeulen) 1 feature darkplaces server: add a string function that returns a character value from a string, mainly for csqc printing its own text 1 feature darkplaces server: add contents reporting to qc somehow when traceline does model tracing and hits the model, this could also fix q3bsp sky collisions? 1 feature darkplaces server: add gettimestamp builtin (returns a string) for logging purposes 1 feature darkplaces server: add md3 mesh name reporting to qc somehow when traceline does model tracing and hits the model 1 feature darkplaces video: add r_displayrefresh cvar for windows video refresh settings (Willis, Judas Judas, Michael Miller, Vondur) 1 feature dpmod: add a Treasure Hunt mode (inspired by preview of Will Rock) - a team wins when they hold all the artifacts 1 feature dpmod: add func_crate (NotoriousRay) 1 feature dpmod: dm 7 monster spawns should occasionally be a crowd of Diablo2 style powered up monsters (Rick) 1 feature dpmod: dm 7 monster spawns should occasionally fail in a shower of gibs (Rick) 1 feature dpmod: dm 7 super monsters should glow and have a name which shows up when in crosshairs (Rick) 1 feature dpmod: dm 7 super scrag should fire spiral acid (Rick) 1 feature dpmod: make ogres start up their chainsaw when first seeing an enemy (scar3crow) 1 feature hmap2: add .mip loading support 1 feature lhfire: add percentage and estimated time reporting to console output (daniel_hansson@telia.com) 1 feature lhfire: post lhfire_gui build 1 feature lhfire: prepare example scripts for release 2 bug darkplaces collision: bmodel bounding boxes need to be calculated to account for clip brushes, which are not in the drawing hull (metlslime) 2 bug darkplaces collision: modify Collision_ClipTrace_Line_Sphere to have a slight backoff on impact, like all other collision functions (tell TheBurningRed) 2 bug darkplaces collision: use larger of model box or collision box for linking into areagrid so that bullet tracing can use the model bounding box instead of the collision one? (Urre) 2 bug darkplaces console: figure out how to prevent "alias a a" - infinite loop when executed, this should be detected when executing it (Vicious) 2 bug darkplaces console: review the whole set of console commands and cvars carefully and identify interactions, known interactions include sequences such as +sv_cheats 1 +map e1m1, or +maxplayers 8 +deathmatch 7 +map dpdm2, but for some reason this works with the cvar after the map command, and also if you do -window it does not affect the value saved to config, because the configs are executed again after the -window, perhaps it is not executing the commandline a second time? apparently also -dedicated without +map does not load a map automatically in transfusion (Wazat, Willis) 2 bug darkplaces csqc: does csqc entity player movement prediction work? what does the EXT_CSQC spec say about retrieving move history for a replay in csqc code? (Spike) 2 bug darkplaces loader: implement r_shadow_bumpscale_basetexture support in hl maps (CheapAlert) 2 bug darkplaces renderer: some polygons are not being lit by compiled rtlights in start.bsp, uncompiled rtlights work fine 2 bug darkplaces rtlights: light entity import should support spotlights and generate cubemaps for their cone angles as needed 2 bug darkplaces server: getlight builtin should consider lightstyles 2 bug darkplaces testing: make sure Zerstoerer works (Chris Kemp, Kaotic)) 2 change darkplaces client: PRYDON_CLIENTCURSOR should be able to click on sprites, make sure it collides with the polygons in the current orientation (FrikaC) 2 change darkplaces renderer: use GLSL for bloom to improve color quality (Justin Thyme) 2 change darkplaces renderer: use GLSL for gamma correction when hardware gamma is not working 2 cleanup darkplaces console: add cvar callbacks and make net cvars have callbacks 2 cleanup darkplaces console: make commandline parsing use COM_ParseToken - why? 2 cleanup darkplaces console: make sure the engine uses only the first 32 special chars, so the high set can be replaced, this means player messages should not be shifted up, and the 'shift down' printing in dedicated server consoles should be removed, etc (Urre) 2 cleanup darkplaces filesystem: add fs_datapath and fs_userpath cvars to better support Linux, this can be done by making each gamedir add both the basepath and userpath variants of the gamedir, and making sure the userpath one is last so it is used for writing (Mercury) 2 cleanup darkplaces memory: memory pools should be able to be nested multiple levels (Vic) 2 cleanup darkplaces menuvm: change menu qc key input to using string key names instead of numbers (the bind alias names should be able to do this) (Mercury, Black, Vermeulen) 2 feature darkplaces client font: cvar for console text size (Vermeulen) 2 feature darkplaces client font: variable width font support using a character width file (FrikaC) 2 feature darkplaces client: 'status' command player ip logging by nickname (sublim3) 2 feature darkplaces client: add a cl_identifyplayer cvar to show the scoreboard name for the current cursor trace entity's .colormap (green) 2 feature darkplaces client: add a message history for messagemode to allow you to retrieve old ones (green) 2 feature darkplaces client: add a net_graph cvar which would show incoming and outgoing packet ping times, packet sizes, dropped packets, etc (avirox) 2 feature darkplaces client: add cl_censor cvar which would replace 'swearing' with humorous messages (Deej) 2 feature darkplaces client: add support for stereo shutter glasses 2 feature darkplaces client: cl_particles_maximum cvar (default 32768) which would change number of particles allowed at once (TheBeast) 2 feature darkplaces client: decal clipping (romi) 2 feature darkplaces client: http download and parse http://www.gameaholic.com/servers/qspy-quake for nq servers (Spike) 2 feature darkplaces client: interpolate scale and alpha changes (Cheapy) 2 feature darkplaces client: make CL_Video use TEXF_FRAGMENT again by adding general, transparent support for it in all drawqueue functions (so you dont need to call FragmentLocation) (Black) 2 feature darkplaces image: add scaling capabilities to Image_CopyMux 2 feature darkplaces loader: add support for fuhquake naming of map textures (textures/start/quake.tga style) 2 feature darkplaces loader: implement vertex cache optimization of models during loading, see this paper: http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html (Dresk) 2 feature darkplaces menu: implement menu_clearkeyconfig and menu_keyconfig and the corresponding menu (diGGer) 2 feature darkplaces menu: new game custom level/mod menu, which allows you to choose a mod and browse through maps and choose starting skill, by default it would have the current mod selected and start selected (Jago) 2 feature darkplaces model: add model_exportobj console command to allow exporting a specified model as .obj (Randy) 2 feature darkplaces physics: DP_SV_QCPHYSICS extension, calls SV_PhysicsQC function, which replaces the entire SV_Physics C function, calling all thinks and physics and everything (Urre) 2 feature darkplaces protocol: .float bulge; field which makes an entity larger by moving vertices along their normals, well known as the fatboy mutator in Unreal Tournament (Wazat) 2 feature darkplaces protocol: add effects.txt file which would describe a bunch of numbered effects usable with a .effectindex field on entities, these would range from point effects, to continuous emitters, to beams with jitter and other properties, each effect would have various info like dlight and particle spawning and beam rendering (CheapAlert, Supa, FrikaC, [TACO], Urre, Vermeulen) 2 feature darkplaces protocol: add rcon_password system similar to quakeworld server (II`cyan) 2 feature darkplaces protocol: svc_spawnstatic should use a delta from defaultstate, instead of its outdated custom protocol (Spike, Dresk) 2 feature darkplaces release: add KDE/gnome icons somehow using darkplaces72x72.png (de-we) 2 feature darkplaces renderer: dpshader should support corona-model shaders somehow (equation: pow(normalizationcubemap(transform(eye, vertexmatrix)) dot3 '0 0 1', 8)), which are normally used around unusually shaped lights instead of flat coronas (Mitchell) 2 feature darkplaces renderer: q3 fog brush shaders (tZork) 2 feature darkplaces renderer: use occlusion query extension (if supported) for testing corona visibility instead of traceline - curve collisions are dragging down corona performance in some maps (Vermeulen, Riot) 2 feature darkplaces sdl: add joystick support 2 feature darkplaces server: add DP_SV_COLLISIONCONTENTMASK extension (Urre, Spike) 2 feature darkplaces server: add EXT_BITSHIFT extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) 2 feature darkplaces server: add EXT_DIMENSION_GHOST extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) 2 feature darkplaces server: add EXT_DIMENSION_HITMODEL extension (Urre, Spike) 2 feature darkplaces server: add EXT_DIMENSION_PHYSICS extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) 2 feature darkplaces server: add EXT_DIMENSION_VISIBLE extension, documented at http://sourceforge.net/docman/display_doc.php?docid=24607&group_id=116842 (Spike) 2 feature darkplaces server: add the ability to minimize the windows dedicated server console to a tray icon (div0) 2 feature darkplaces sound: add FLAC sound support (Anton Romanov) 2 feature darkplaces sound: add mapmusic command ( perhaps, with a blank music name it would simply remove the map from the list of overrides) to manipulate a list of per-map music overrides, mapmusic alone should print the list (Joseph Caporale, tell Static_Fiend) 2 feature dpmod: add observer mode and a best N of (N-1)*teams+1 match system (carni) 2 feature dpmod: write a readme for the menu progs code to get people started with it, and know what is and is not possible, what builtins do, etc (Urre) 2 feature dpzoo.map: swinging doors 2 feature hmap2: add "_minlight" "red green blue" and "_ambientlight" "red green blue" fields to worldspawn parsing (Harb) 2 feature hmap2: add .hlwad and .hlw support to hqbsp (convert to quake palette, and check for colormap.lmp to see how many fullbrights are involved, also add -fullbrights option to control this) (Todd, FrikaC) 2 optimization darkplaces collision: do trace against patch bbox before testing triangles 2 optimization darkplaces renderer: move skybox/skysphere render to after sky polygons, so that they can do a depth comparison to draw only on the sky polygons, using DepthRange(1,1) and DepthFunc(GL_GREATER), note though that portal renders will need half depth range (Mercury) 3 bug darkplaces collision: add edge bevels in collision code, by trying all crossproduct combinations of an edge with planes from the other brush, and choosing only the ones which are valid 3 bug darkplaces compatibility: quakerally does not work, figure out why (Spike) 3 bug darkplaces renderer: add stainmaps to realtime lighting mode 3 bug darkplaces renderer: crash when using rtworld mode and reloading a map that has been modified and now has less surfaces, this means the map name is the same, but the surface indices/pointers are no longer valid 3 bug dpmodel: add support for unnamed bones (Mitchell) 3 bug dpmodel: fix dpmodel to compile v_HKmp5-sd (tell Riot) 3 change darkplaces client: GAME_NEXUIZ: implement new hud and scoreboard based on http://www.quirkybastards.net/qmods/scoreboard.jpg except with deaths instead of lives, and map name instead of "be the last one alive" and remove the time string and map string at the bottom, instead showing the hud (Vermeulen) 3 change darkplaces renderer: change q3 shader system to use fingerprinting of shader passes to identify what kind of shader it is (for example "OPAQUE reflection ALPHA texture MODULATE $lightmap" metal shaders, and "OPAQUE texture1 ALPHA texture2 MODULATE $lightmap" terrain blending shaders), this would allow any identifiable q3 shader to have per pixel lighting, with full rendering capability 3 change darkplaces renderer: change texture manager to use a flat array with sequence purging on level change after stale models are unloaded, sequence marking and a flat array would allow reuse of textures by multiple models, multiple skins within a model, or even multiple texinfo structures in q3bsp 3 change darkplaces renderer: load q3 shader information at level load, and allow all models to use them 3 change darkplaces sound/render: change r_refdef calculations to happen before CL_UpdateScreen, and then move S_Update before CL_UpdateScreen to slightly improve sound latency issues, and then eliminate sound_spatialized variable 3 cleanup darkplaces loader: make q1bsp surfaces have vertex color arrays like q3bsp to make things more consistent, note these need light styles 3 cleanup darkplaces menu: rearrange menus - make Graphics Options submenu and move video and renderer stuff there, add Apply button to video section (tell Elric) 3 feature darkplaces client: .loc support and other team messaging capabilities (sublim3) 3 feature darkplaces client: add an httpdownload command which can download pk3 archives (Paul Gagnon) 3 feature darkplaces client: add back r_waterripple (Vermeulen) 3 feature darkplaces client: add direct xvid recording using the xvid library (Error, Vermeulen) 3 feature darkplaces docs: add short and long documentation string to each cvar/command (QorpsE) 3 feature darkplaces docs: write a documentation string in engine, and a command to dump documentation to a darkplaces.txt file (QorpsE) 3 feature darkplaces editlights: add a custom light style string to rtlights, if empty it uses a normal server controlled light style (Stribbs) 3 feature darkplaces filesystem: add fs_reload command to allow restarting the filesystem module, this would mean that it could check for new paks and such (Mercury) 3 feature darkplaces loader: add _color.tga support (realtime lighting would use this instead of the .tga for diffuse layer if available) (Urre) 3 feature darkplaces loader: load .skin files for bsp files to allow per-map texture overrides (Spike) 3 feature darkplaces loader: load .skin files for sprite files to allow per-frame texture overrides (Spike) 3 feature darkplaces loader: support md5mesh/md5anim model files (Supa, Kazashi, Swifty) 3 feature darkplaces menu: add OpenGL Extensions menu to enable/disable various features (zombie_13) 3 feature darkplaces physics: DP_SV_TRACEMOVE extension, adds a qc builtin which traces an entity through the world (using origin/angles/mins/maxs/velocity/avelocity) for a specified amount of time (frametime typically), and sets trace results accordingly, this would greatly help out QC physics (Urre) 3 feature darkplaces physics: add DP_SV_CRATEPHYSICS (NotoriousRay) 3 feature darkplaces protocol: .string drawtext; displays the specified message (up to 31 characters) centered at the origin of this entity, for qc based menus and titles on things inside the world, also .float drawtextscale which defines character height and .float drawtextflags with DRAWTEXTFLAG_ORIENTED DRAWTEXTFLAG_UPRIGHTFACING DRAWTEXTFLAG_ALIGNTOP DRAWTEXTFLAG_ALIGNBOTTOM DRAWTEXTFLAG_ALIGNLEFT DRAWTEXTFLAG_ALIGNRIGHT, defaults to view parallel (like typical sprites) and centered alignment horizontally and vertically, also note colormod, and the effects flags EF_ADDITIVE EF_FULLBRIGHT (it is lit by default) EF_NODEPTHTEST EF_SELECTABLE apply to this (Wazat) 3 feature darkplaces protocol: add "GetPK3URLList" and "PK3URLListResponse" messages to allow a client to query a server for what pk3 archives to httpdownload before joining (Paul Gagnon) 3 feature darkplaces protocol: add DP_ENT_COLORSHELL which puts a Q2-style colored shell on a model (Supajoe) 3 feature darkplaces protocol: add a "box" effect controllable by QC somehow, for highlighting usable items (buttons and such) like in Red Faction, preferably with multiple colors supported (Mitchell) 3 feature darkplaces protocol: make server send ping time to client for prediction 3 feature darkplaces renderer/loader: add RBSP map support (modified q3bsp with lightstyles) (Kazashi) 3 feature darkplaces renderer: add Draw2D function to model struct to make it easy to draw models without an entity (Tomaz) 3 feature darkplaces renderer: add a command to replace a texture in the running map, should only work in singleplayer; for testing only (Randi) 3 feature darkplaces renderer: add antialiasing options (Zombie_13) 3 feature darkplaces renderer: add support for Color Code 3D anaglyph stereo glasses which are purple-blue/amber, this would require rendering the two views to a texture and then blending them with color modulation, some research will be needed to find out the specific colors to use, which should probably be cvar controlled (Francis Siefken) 3 feature darkplaces renderer: create collision brushes from q1bsp clip hulls, and make them display when using r_drawcollisionbrushes (Aardappel) 3 feature darkplaces renderer: directional lighting from the q3bsp lightgrid should use diffuse and specular lighting if available (Vermeulen) 3 feature darkplaces renderer: dpshaders (when supported) should have support for envmaps, and should support being lit by diffuse lighting as a fake gloss effect for normal mode (Vermeulen) 3 feature darkplaces renderer: implement steep parallax mapping with self shadowing and depth options: http://graphics.cs.brown.edu/games/SteepParallax/ (Kazashi, Randy) 3 feature darkplaces renderer: need to make a standalone minimod to test darkplaces rtlights code, which Diablo-D3 can throw at the ATI driver team to test with, 3D txtures with GL_CLAMP_TO_EDGE wrapping are broken (Diablo-D3) 3 feature darkplaces renderer: skyroom needs to be added ("info_skyroom" entity sets view origin, scanned by client at load, and by server to send all entities in skyroom) 3 feature darkplaces renderer: try to achieve perfect transparency sorting by encoding them as a BSP tree to cause all to be split by eachothers' planes, or at least try recursively splitting each one by all of the previous accepted transparent polygons, the BSP encode method is still probably too slow to use but is worth trying, and the other method is even slower (Kazashi) 3 feature darkplaces renderer: try two-cubemap approach to specular lighting math on GF3 path (Black) 3 feature darkplaces sdl: make and submit a patch to add vsync control to libSDL 3 feature darkplaces server: add DP_GFX_QUAKE3MODELTAGS, DP_GFX_SKINFILES, and any other new extensions to the wiki (wiki) 3 feature darkplaces server: add a DP_SV_PUSHMOVE extension with a pushmove builtin that does basically what MOVETYPE_PUSH does, but with controllable end position, not time based (Zombie) 3 feature darkplaces server: add traceboxwithcontents function (same as tracebox but adds the startcontents parameter) (LTH, http://forums.inside3d.com/showflat.pl?Board=Engine&Number=909 ) 3 feature darkplaces server: hub save support, one file indicating active map, and then for each map it saves a quake savegame 3 feature darkplaces server: make an event message queue for each client, so TE_ effects and sounds and can be stuffed into successive packets if they don't all fit at once, currently a large number of explosions at once are never sent because they don't fit in one size limited packet (romi) 3 feature darkplaces sound: add snd_rate cvar and make it changable during game (RenegadeC) 3 feature darkplaces sound: sound shaders; mindistance, maxdistance, list of sounds to choose from (Supa) 3 feature dpmod: code a func_swinging entity which takes a starting angle and swing time and swings back and forth, each time reaching that angle, and swinging through '0 0 0' (Zombie) 3 feature dpmod: code an alternate ending for shub being killed by normal weapons (scar3crow) 3 feature dpmod: use FRIK_FILE extension to allow passing objects between levels, such as crates (NotoriousRay) 3 feature dpzoo.map: remote cameras (to demonstrate DP_CLIENTCAMERA, DP_EF_CLIENTLOCKANGLES, and precise angles) 3 feature hmap2: add support for shadow casting bmodels (Urre, Arwing) 3 feature hmap2: add tga support to qbsp (load base texture and _glow/_luma) 3 feature nexuiz qc: add clanring-style menus and match settings 3 optimization darkplaces client: make a new caching system with handles (which can be purged) and give every entity a cache handle to a model instance, which contains cache handles for each mesh/array 3 optimization darkplaces renderer: implement some sort of areaportals system to let doors shut off portals, this needs to clip the portals by all bmodels in the area because in e1m1 the doors are two part and can't hide the portal individually (Vic) 3 optimization darkplaces renderer: use zpass (without front/back caps) shadowing for a speed gain if entity box is outside a frustum made from the light to the view's nearclip polygon edges, which determines whether the shadow would be casting directly onto the view surface (Tomaz) 3 optimization darkplaces server: make SV_ClipMoveToWorld cache trace results somehow for a speed gain in masque.bsp 4 bug darkplaces client: figure out why intermission camera pitch changes after a moment (Tomaz) 4 bug darkplaces networking: use getaddrinfo to support ipv6, add support for winsock2 (or require it), check if winsock2 has ipv6 functions (getaddrinfo)... (|Rain|) 4 bug darkplaces physics: rotating MOVETYPE_PUSH code calls blocked when it's just a touch, it isn't even trying to push 4 cleanup darkplaces memory: use the memory pool nesting feature ! (Black[,Vicious]) 4 feature darkplaces client: add decals on models (Urre) 4 feature darkplaces client: proquake secure protocol support for playing on proquake servers (sublim3) 4 feature darkplaces console: add setlock command which marks a cvar as locked, and sends it over network to connected clients as a setlock command, the clients will not allow the user to modify the cvars while locked (and will only accept setlock commands over the network), and cvars are unlocked when level ends - the server will send the locks again on next level (VorteX) 4 feature darkplaces csqc: add builtin to clientside qc for reading triangles of model meshes (useful to orient a ui along a triangle of a model mesh) 4 feature darkplaces demo: ability to record demos while already connected (green) 4 feature darkplaces loader: add SKM model support (Vermeulen, Vic) 4 feature darkplaces loader: load .map file if present to get collision brushes for q1bsp (Transfusion) 4 feature darkplaces protocol: add capability for qc entities to act as bones in a model, and send them as compressed origins in the parent entity's updates, with perhaps a limit of 16 bones, this would allow some simple serverside ragdoll (Mitchell, Deej) 4 feature darkplaces protocol: multianimation support using multiple .frame fields on the entities for model formats with multianim capability such as SKM (Vermeulen) 4 feature darkplaces renderer: add _reflect textures which filter use of skybox as a cubemap reflection (FrikaC) 4 feature darkplaces rtlights: add a directional flag which makes the light cast along X only, rather than spherically, angles can be used to make it point at things, this will also affect shadow volumes, so portal clipping enhancements are needed, and non-uniform scaling is needed (Zombie) 4 feature darkplaces server: add DP_QC_MODELINFO extension to allow QC to ask about the range of frames owned by an animation base name, and other things like how many skins it has (Vermeulen, Supajoe) 4 feature darkplaces sound: rewrite sound system! (FrikaC, Static_Fiend) 4 feature dpzoo.map: a drivable vehicle (using same technique as remote cameras, plus DP_SV_READCLIENTINPUT) 4 feature hmap2: add hint brushes (HINT texture) 4 optimization darkplaces renderer: add cubemap light filter texture culling based on how much of the cubemap is used somehow (Vermeulen) 5 feature darkplaces client: add a "edictedit" command to open up a dialog to edit an edict (allow multiple dialogs to be open at once) 5 feature darkplaces client: add qc debugger, which would have its own very simple fullscreen console, this would be called directly from the qc interpreter, not from client (painQuin) 5 feature darkplaces console: r_textutf8 cvar (to parse UTF-8 codes), should affect all text rendering, using multiple conchar images for different groups of 256 characters (VorteX) 5 feature darkplaces renderer: add dpshader support 5 feature darkplaces renderer: add some kind of sun flare support, possibly stored in a dpshader (CheapAlert) 5 feature darkplaces renderer: do a minimap that works by simply using nearclip to sheer off anything above the eye, and draws anything below normally, or via a cvar as height coloring (Supajoe) 5 feature darkplaces renderer: implement transparent triangle sorting within each entity (FrikaC) 5 feature darkplaces renderer: lightshader files (probably loaded by the cubemap field already present in rtlights handling), these would indicate what attenuation textures to use for the light, what cubemap filter, which corona texture and size and so on, and all textures can be animated (romi, Urre) 5 feature darkplaces server: split server into a separate thread when running listen mode so that a host running too slow won't spoil the game (Toddd) 5 feature dpzoo.map: make some things that make the player bigger/smaller to demonstrate scaling and better viewheight handling and brush collisions (depends on brush collisions) 5 feature hmap2: fix tjunctions on leaky maps 6 feature darkplaces client: add a "edit" command that can edit text files (I.E. .qc and progs.src) using a dialog window (allow multiple), and then add a "frikqcc" command to run it on the current mod (if it's in the command path at least) 6 feature darkplaces renderer: figure out an LOD scheme for really large outdoor environments (Uffe) 7 feature darkplaces protocol,renderer: add DP_EF_REFRACT which turns an entity into a refraction effect similar to doom3 shockwave/heathaze (Supajoe) 7 feature darkplaces renderer: make it work on Savage4 again (Ender) 7 feature darkplaces renderer: mirrors 7 feature darkplaces renderer: shadow volume clipping (romi) d bug darkplaces Mac filesystem: on Mac dlopen is not finding dylib files alongside the executable, do a more thorough search (Zenex) d bug darkplaces Mac filesystem: on Mac init the basedir to argv[0] truncated to not include the *.app/ part of the path onward (Zenex) d bug darkplaces SDL input: changing video mode causes it to ignore all character events from then on d bug darkplaces WGL client: default WGL input back to GDI, the DirectInput driver is malfunctioning, losing key release messages, stuttering mouse input, and lacks mouse wheel support (Wazat, Kinn) d bug darkplaces WGL client: fix GDI input init/shutdown, it is using weird mouse acceleration and not restoring it on exit (innovati) d bug darkplaces X11 keyboard: make sure that the XLookupString code is not little endian specific (Elric, jitspoe) d bug darkplaces client qw: cl_netinputpacketspersecond 20 is too low for QW physics to behave properly, add a separate cl_netinputpacketspersecond_qw cvar which defaults to 72 (Plague Monkey Rat) d bug darkplaces client sbar: when playing demos recorded from singleplayer, the deathmatch hud seems to be used, perhaps we have to force it off for maxplayers 1? (Spirit) d bug darkplaces client win64: crash in R_DrawRTLight due to stack overflow, change the pointer arrays to indexes into r_refdef.scene.entities, or increase projects to build with 4MB stack instead of 2MB - also clean up these warnings: http://dp.deathmask.net/log/dp_x64_buildlog_r8078.txt (Willis) d bug darkplaces client/server: unable to control player in TAoV multiplayer (RenegadeC) d bug darkplaces client/server: viewzoom values above 1 are not working properly d bug darkplaces client: GAME_NEHAHRA: make sure cutscenes and movies work, got a report of seeing a black screen (NightFright) d bug darkplaces client: GAME_NEXUIZ spews a number of warnings about gfx/ images not being found (Vermeulen) d bug darkplaces client: cl.sfx sounds aren't playing (romi) d bug darkplaces client: cl_beams_relative is behaving really badly with cl_movement prediction d bug darkplaces client: cl_beams_relative is sometimes drawing beams from '0 0 0' (VorteX) d bug darkplaces client: cl_movement 0 shouldn't be doing an input replay (SavageX) d bug darkplaces client: cl_movement_airaccelerate missing? d bug darkplaces client: color codes are not supported in centerprint messages (Wazat) d bug darkplaces client: corona on your own muzzleflash is annoying when looking down because it can be seen, disable corona on all muzzleflashes (flum) d bug darkplaces client: crosshair_static 0 breaks if self is EF_NODRAW (NecroPhil) d bug darkplaces client: disable vsync when doing a timedemo (Randy) d bug darkplaces client: do replay cl_movement queue each time a move is added, as this is called from the server packet parser, which can overwhelm the client with several packets in one frame, leading to a complete lockup until the level changes (Black) d bug darkplaces client: figure out why multimap demos are skipping the later portions, it's probably related to the time seeking, probably not being reset (Urre) d bug darkplaces client: finale text during episode-end intermissions shows briefly in its entirety and all as one line (going off the screen), then disappears and begins typing slowly as it should (Sajt) d bug darkplaces client: fix cl_bobmodel bug which momentarily jolts the gun when you pass through a trigger, pick up an item, etc, Sajt thinks this is related to console prints as well as centerprint (Sajt) d bug darkplaces client: fix gl_flashblend, it's still drawing rtdlights even when gl_flashblend is on (Toddd) d bug darkplaces client: hipnotic: health is one character to the right on the sbar, covering up the key icons (M`Shacron) d bug darkplaces client: loading savegames while a demo is playing does not stop the demo, causing a broken state and ultimately a disconnect before the client enters the loaded game (Bill Pickett) d bug darkplaces client: make "wait" command wait fornext network frame somehow when connected, to make frikbot .way files load properly (Transfusion, FrikaC) d bug darkplaces client: make envmap command work with the corrected layout d bug darkplaces client: make server queries use a queue to avoid flooding out queries too fast (Willis) d bug darkplaces client: missing bolt/beam models should not produce warnings d bug darkplaces client: name (and probably other userinfo properties) are not being set when entering a qw server? d bug darkplaces client: on crctf proquake servers the scoreboard does not contain exactly matching player names (READY is sometimes appended), the ping report and status parsing should ignore text after the player name d bug darkplaces client: prydon cursor highlighting of EF_SELECTABLE entities flickers with lower server framerate than client framerate (carni) d bug darkplaces client: quakeworld servers often stuffcmd the cvars topcolor, bottomcolor, pants, team, skin, noaim, so commands for these need to be added (topcolor/bottomcolor will modify _cl_color, the others can be real cvars) d bug darkplaces client: seta commands create cvars that are not saved to config because they match their 'default' value d bug darkplaces client: svc_effect should post a warning and do nothing if given a framerate below 1 (Willis) d bug darkplaces client: te_customflash isn't working? (Wazat) d bug darkplaces client: userinfo strings are not being updated by name/color commands d bug darkplaces client: when playing back a demo in slow motion it is very noticable that the camera rotates into position on the first frame (Stribbs) d bug darkplaces client: when playing back nehahra demos they often contain large time skips at scene cuts which are interpolating slowly over several seconds (Stribbs) d bug darkplaces collision: check Urre's sltest.bsp and slopestuck.dem and fix the sticking bug, which only happens with sv_newflymove 1 (Urre) d bug darkplaces collision: frikbots are falling through the map (Sajt) d bug darkplaces commands: say command is not posting to server console (Vermeulen) d bug darkplaces compatibility: targetquake does not work, figure out why d bug darkplaces console: $* expansion should not include $0 (Black) d bug darkplaces console: $variable expansion is not working on forwarded commands like "say I'm $_cl_name", it does work on local commands like set (esteel, Black) d bug darkplaces console: alias test "echo 1";test;echo 2 should print 1 then 2, not 2 then 1 or an error (div0, FrikaC) d bug darkplaces console: chat messages are showing up in brown quake characters and having ^7 and such printed literally d bug darkplaces console: commandline history won't scroll back past a blank line - the blank line should not be entered into history (Elric) d bug darkplaces console: console script lines that are too long (1024+ characters) crash (NecroPhil, Black) d bug darkplaces console: don't save cvars to config.cfg if their current value matches their default value (div0) d bug darkplaces console: first character is missing on quake brown-text lines, but not consistently, resolved: stripping off the chat prefix character on prints was stripping other characters sometimes due to signed comparison (Spirit) d bug darkplaces console: inserting characters in the commandline is not adding a nul terminator to the commandline, resulting in lots of trash from older commandlines suddenly showing up (Spike) d bug darkplaces console: make map listing read the .ent files for map names d bug darkplaces console: rapid printing (like cvarlist) is somehow being truncated when printing to the terminal (div0) d bug darkplaces console: when cursoring up and down through command history, shorter lines sometimes contain some text from the previous line d bug darkplaces csqc: after the drawqueue was eliminated, the CSQC probably can't draw 2D polygons the same way, so it may need fixing ([515]) d bug darkplaces csqc: engine-based rocket entities have a trail but they don't glow if csqc is used d bug darkplaces csqc: implement lerpfrac/frame2 fields (Spike) d bug darkplaces csqc: it's broken! d bug darkplaces csqc: network entity positions seem to be incorrectly updated while csqc is active, this is best tested with cl_nolerp 1 on a sys_ticrate 0.1 server, which makes the jumps in rocket movement quite noticable d bug darkplaces csqc: when playing back a demo, the csqc does not seem to be getting the cl.viewangles right d bug darkplaces docs: host_maxfps is gone, correct the darkplaces.txt and host.c cvar description for host_framerate d bug darkplaces general: make all text parsing routines support Mac newlines; \r with no \n (Zenex) d bug darkplaces gl: turning r_shadow_bouncegrid off and back on left a messed up texture binding state d bug darkplaces hud: sometimes texture borders wrap, causing annoying seams at the edges of pics, use TEXF_CLAMP d bug darkplaces init: only print "Playing shareware version." notice if running GAME_QUAKE (MrBIOS) d bug darkplaces input: buttons 4 and 5 on a mouse are acting like mwheel (Kedhrin) d bug darkplaces input: centerview command isn't doing anything until console is activated, it should begin the pitch drift immediately as in quake (Sajt) d bug darkplaces input: figure out what's wrong with ctrl key in Linux, hitting character keys tends to do nothing, and holding a character key and then hitting ctrl tends to leave the character key stuck on, this sounds like a window manager issue, but somehow quake3 works around it (Baalz) d bug darkplaces input: fix the mouse move when console is raised in glx, probably by ignoring the first move after console raise (mashakos) d bug darkplaces input: ignore first mouse move in windows fullscreen when coming back from an alt-tab (sublim3) d bug darkplaces loader: AliasSkinFiles stuff is crashing because of the changed skin indexing, it needs to step in multiples of num_surfaces not 1 (Willis) d bug darkplaces loader: fix hlbsp transparent surface support (mrinsane) d bug darkplaces loader: halflife wad loading is unable to seek to lump table (ryan[sg], Elric) d bug darkplaces loader: nexuiz loading a level often loops part of the map's music during loading, this is probably an extra Host_Frame being executed during loading, where it shouldn't be (Vermeulen) d bug darkplaces loader: only load .lit if the file size matches lumpsize * 3 + 8, as a rough check that the lit is for the correct bsp file (Spike, Urre) d bug darkplaces loader: q3bsp deluxemap detection can fail on some files, thinking they have deluxemaps even though they don't? (jimmmy) d bug darkplaces loader: q3bsp lightgrid loading seems to be ignoring the "gridsize" key of worldspawn, but how? d bug darkplaces loader: unlit q1bsp maps are showing as black rather than fullbright... again. d bug darkplaces loader: zym models are not loading some of their meshes? this is causing the striped part of the nexuiz RL to disappear (div0, SavageX) d bug darkplaces loading: test zlib support with entirely pk3'd id1 data (should crash because of zlib not being setup early enough - fix this) (Mabus) d bug darkplaces loading: when gamedir (or -game) contains a directory which listdirectory() fails on, do a Host_Error with an appropriate message, rather than running with a non-existent directory d bug darkplaces makefile: build nexuiz.exe using nexuiz.rc (Vermeulen) d bug darkplaces menu: if no data is found, the menu should be text d bug darkplaces menu: if no data is found, there are only 3 menu options selectable, even though that is not enough to be able to select quit... perhaps some nexuiz-specific logic is malfunctioning? d bug darkplaces menu: load/save game menus show files that are not in the highest priority gamedir, such as id1 saves showing up when playing dpmod (Dooomer, WodahsEht) d bug darkplaces menuvm: menu input focus is lost if a map command occurs while in menu, even if it fails the menu still lost focus and is unusable until closed and reopened with escape key (Black, Vermeulen) d bug darkplaces model: don't Host_Error when a model is unknown/unsupported type (SavageX, Vermeulen) d bug darkplaces model: ignore attempts to load "" (SavageX, Vermeulen) d bug darkplaces particles: cl_particles_quality is affecting lifetime of decals, it should not d bug darkplaces physics: corpses/gibs are not riding down e1m1 lift (scar3crow) d bug darkplaces physics: in Prydon Gate the func_door2 entities are stuck in eachother, causing a continuous spew of warnings and causing one of them to be teleported slightly upward which looks bad (FrikaC) d bug darkplaces physics: q3bsp collisions are still glitchy, particularly gunshots hitting thin air near brushes, even with mod_q3bsp_optimizedtraceline 0, test in dpdm2r by shooting down through gaps in the architecture around the top platform (Vermeulen) d bug darkplaces physics: test TecnoX and find the frikbot crash in SV_Physics (KrimZon) d bug darkplaces physics: the zombie lift in e3m2 before the gold key is not working (scar3crow) d bug darkplaces physics: when riding a lift down (such as near the start of e1m1), the player is not being pulled down, unlike in quake, this can cause repeated fall damage on very fast lifts (scar3crow) d bug darkplaces protocol: fix cl_nodelta 1, it's halting updates after you move away from an area (Tomaz, sublim3) d bug darkplaces protocol: fix signon error when starting prydon without +map curig2 (FrikaC) d bug darkplaces protocol: getting packetlog overflow warnings again, but WHY? (daemon, SavageX) d bug darkplaces protocol: it's possible to get a "received signon 1 when at 1" error in singleplayer when restarting level, perhaps this relates to very low framerate d bug darkplaces protocol: models sometimes staying in nexuiz after a big battle, entities that don't exist on the server anymore (Spike) d bug darkplaces protocol: something is causing things like tracers to sometimes stay indefinitely (Vermeulen) d bug darkplaces protocol: sometimes players are invisible in nexuiz, showing only their gun model, this may be related to svc_precache messages not being sent during signon (Vermeulen) d bug darkplaces prvm: VM_remove does not print a stack trace (RenegadeC) d bug darkplaces prvm: all warnings should print a stack trace (RenegadeC) d bug darkplaces prvm: assignment to world is not producing an error after world spawn stage (Spike) d bug darkplaces prvm: engine calls to qc functions are not counting as function calls in the prvm_profile results, so for example SV_PlayerPhysics is being shown with a 0 call count d bug darkplaces prvm: findchain/findchainfloat are corrupting things when trying to write to the .chain field (Kedhrin) d bug darkplaces prvm: findflags/findchainflags are server-specific, these should be moved into the generic progs commands d bug darkplaces prvm: the merged remove is causing a Host_Error on already removed entities, which happens in id1 start.bsp (RenegadeC) d bug darkplaces prvm: unknown opcode warnings are missing a \n d bug darkplaces qc FRIK_FILE: when opening a file for writing that already has the data/ prefix in its path, it should not add another data/ prefix (daemon) d bug darkplaces qc: document the wasfreed() builtin from EXT_CSQC and entitybyindex() builtins, the latter is DP_QC_EDICT_NUM (Urre) d bug darkplaces quakec: to stop crashing on 64bit the quakec vm needs a string manager that can allocate/free negative integer indices to the strzone strings, and also automatically add engine strings d bug darkplaces readme: commandline options are slightly out of date, update them (Baker) d bug darkplaces renderer/server: scaled sprites (or possibly all models) are getting culled as if they were not scaled (KrimZon) d bug darkplaces renderer: Morphed's colormapping experiments in nexuiz show a difference in gloss color with GLSL vs dot3 path, http://img494.imageshack.us/img494/8745/nexuiz0000258lf.jpg http://www.nexuiz.com/forums/index.php?showtopic=1531 - and apparently it looks right or wrong depending on view point, suddenly switching (Morphed) d bug darkplaces renderer: add r_shadow_glsl_geforcefxlowquality cvar to make make GLSL shaders use "half" data type, automatically set this if on GFFX (MauveBib, SavageX) d bug darkplaces renderer: alternate anims are not showing up in wall renderer - they are working for rtlights however (LordHavoc) d bug darkplaces renderer: animated textures are not being lit by static rtlights d bug darkplaces renderer: audit all text drawing to make color codes work properly everywhere, right now they are even managing to mess up death message printing if someone has a color code in their name (Vermeulen) d bug darkplaces renderer: audit rtlight ambient rendering, apparently scissor is clipping away parts of lights that have ambientscale but not ones that have diffusescale or specularscale (Zenex) d bug darkplaces renderer: colormap rendering not working on rtlighting passes, resulting in black pants/shirt d bug darkplaces renderer: colormod is not affecting bmodels (Urre) d bug darkplaces renderer: compiled rtlights aren't working in modeltest.bsp which is a one cluster map (LordHavoc) d bug darkplaces renderer: deluxemaps are not detected in some maps that do have them? (SavageX) d bug darkplaces renderer: don't shut off gl_combine when r_textureunits goes below 2, and don't save gl_combine either d bug darkplaces renderer: entity culling is ignoring entity scale (daemon) d bug darkplaces renderer: envmap command includes the hud in the screenshots, bad! d bug darkplaces renderer: fix fogging in realtime lighting mode, need to split the shaders into two stages, this will also fix decal bugs with fog (Mitchell) d bug darkplaces renderer: fix q3bsp fogging (Sajt) d bug darkplaces renderer: fix rtlighting of viewmodel, it should not be performing lighting on a model outside the light radius (LordHavoc) d bug darkplaces renderer: fix the delayed lightmap updates on bmodels, they're lagging behind one frame, very noticable on flickering light d bug darkplaces renderer: fix vis problems when outside the level in q1bsp d bug darkplaces renderer: gl_max_size is affecting bloom (causing a black screen when gl_max_size is less than the screen dimensions) (Willis) d bug darkplaces renderer: glsl lighting path is not using GL_SRC_ALPHA, GL_ONE d bug darkplaces renderer: if a texture has the NOLIGHTMAP flag set, disable deluxemapping on the batch, this is needed to fix the glowing stuff in nexuiz maps like the stairs in Glow Arena or the slime pipes in Slime Pit (SavageX) d bug darkplaces renderer: in full rtlighting mode, deluxemapping gloss still shows up (the diffuse and ambient does not) d bug darkplaces renderer: make rtlights properly affect transparent models (romi) d bug darkplaces renderer: make sure that unlit maps show up fullbright (Wazat) d bug darkplaces renderer: monsters teleporting in really slow down rendering, perhaps the teleport light is casting huge shadows? new information suggests it is the particles. (romi, lcatlnx) d bug darkplaces renderer: opaque water (r_wateralpha 1) is not being lit by rtlights (Sajt) d bug darkplaces renderer: q3bsp alpha shaders are not being lit? (Cheapy) d bug darkplaces renderer: q3bsp ignoring EF_ADDITIVE on opaque surfaces such as Nexuiz teleporters? (Vermeulen) d bug darkplaces renderer: r_drawcollisionbrushes 2 is broken (LordHavoc) d bug darkplaces renderer: r_glsl 1 mode has black grapple beam in nexuiz (SavageX) d bug darkplaces renderer: r_wateralpha 0.9 is invisible on r_glsl 0;gl_combine 0 path (Lardarse] d bug darkplaces renderer: r_wateralpha 1 water that has lightmapping is black in r_shadow_realtime_world 1 mode, but only if the map was loaded in r_shadow_realtime_world 1 mode, if started in 0 and then going to 1 it does not have black water, this is probably lightmap updates not occurring in rtworld mode (mrinsane) d bug darkplaces renderer: r_wateralpha on maps that are not watervised shows sky, this is a known glquake bug but it is fixable in darkplaces at load time by marking opposite-content (water-empty, empty-water) leafs as visible in their pvs sets, this involves checking the portal flow... (knghtbrd) d bug darkplaces renderer: reverse corona traceline direction so that a player in solid can see coronas (Urre) d bug darkplaces renderer: shadow volumes from q3bsp brush models are broken, maybe inverted or something (Vermeulen) d bug darkplaces renderer: text coloring is only affecting the first line of messagemode text (LordHavoc) d bug darkplaces renderer: there's some sort of bug with GL_CullFace, it is sometimes rendering the map using GL_CullFace(GL_NONE) depending on viewpoint d bug darkplaces renderer: transparent entities are not being lit by rtlights, where as transparent water belonging to an opaque entity (world) is being lit by rtlights (SavageX) d bug darkplaces renderer: transparent surfaces are not being lit by rtlights (Vermeulen) d bug darkplaces renderer: vertex normals seem to be generated backwards d bug darkplaces renderer: vid_restart and r_restart are both crashing (Tomaz) d bug darkplaces rtlights: light entity import should spawn lights at torch origin so that it does not cast a shadow d bug darkplaces sdl client: gamma is being lost after a vid_restart d bug darkplaces server: .colormap is not being set on DP_SV_BOTCLIENT entities the first time, but if removed and spawned again it is set (Urre) d bug darkplaces server: Blood Mage monsters are stuck in place apparently (steven a) d bug darkplaces server: SV_SpawnServer should send reconnect command using per-client reliable messages, because sv.reliable_datagram is being cleared d bug darkplaces server: add TE_FLAMEJET builtin and add extension (Supajoe) d bug darkplaces server: add \" support to Com_ParseTokenConsole (div0) d bug darkplaces server: add color code to start of chat message to prevent nick colors from messing up the text color d bug darkplaces server: call checkvelocity (to clear NaNs) every time velocity is set in physics, to fix frikbot (tell FrikaC) d bug darkplaces server: cl_movement 0 clients can pogostick jump and do quake2 style double jumps... why? answer: id1 qc move was clearing self.button2 each time it jumped, which causes it to pogostick when client input rate is lower than server framerate, fixed. (div) d bug darkplaces server: client ping times are often negative after a level change, which shows up in the console on the client because the ping report parser doesn't like negative pings d bug darkplaces server: don't clear player entity when loading a savegame d bug darkplaces server: dropclient() is not calling ClientDisconnect on bots during the first level they exist in, it is called on later levels (Urre) d bug darkplaces server: effect() builtin should post a warning and do nothing if given a framerate below 1 (Willis) d bug darkplaces server: entity unsticking code should try 1 unit horizontal offsets, then diagonals, then vertical, not diagonal + vertical d bug darkplaces server: error() qc builtin does not print error message, just Host_Error: Program error or something similar (evilfrog) d bug darkplaces server: having a csprogs.dat file installed can crash dedicated servers (esteel) d bug darkplaces server: if sv_fixedframeratesingleplayer is 0 and cl_maxfps is something like 10, the server still runs every frame, consuming massive amounts of cpu and resulting in very small frametime values d bug darkplaces server: in X-Men: Ravages of Apocalypse the weapon part in x1m3 fails to follow the platform it is on, it is probably spawning inside the ceiling and for some reason not associating with the platform as its groundentity? (qwerasdf) d bug darkplaces server: in X-Men: Ravages of Apocalypse the weapon part in x2m4 falls out of the level, along with a few other items in the same secret (qwerasdf) d bug darkplaces server: inconsistent packet timing produces jerky movement (constantly pausing every other frame or so), this is probably the dedicated server's sleep pattern, fixable by using the client's sleep pattern which wastes more cpu time but is more accurate (green`marine) d bug darkplaces server: local server is not being killed when you join another server (Vermeulen, suminigashi, Willis) d bug darkplaces server: losing clientcolors somehow during connect in dpmod d bug darkplaces server: ping should work from server console d bug darkplaces server: projectiles spawned during client physics called by SV_ReadClientMove are moved on the same server frame, causing them to appear in midair, unlike the normal physics which refuses to move projectiles on their first frame (m0rfar) d bug darkplaces server: running only one server frame per host frame is really bad in listen servers where the client may be too slow to keep up the server framerate d bug darkplaces server: self.fixangle is being cleared each frame even if no client move has been performed this frame, which means that predicted clients often see fixangle = 0 due to irregular moves causing PlayerPreThink/PlayerPostThink to not be called every frame d bug darkplaces server: sending unused lightstyles in serverinfo packet is silly (Spike) d bug darkplaces server: stats[TOTAL_MONSTERS] should be networked as a stat d bug darkplaces server: stepping while jumping is setting FL_GROUND (allowing the quake2 doublejump bug) d bug darkplaces server: sv_jumpstep should be defaulted to off because it makes it possible to reach places one should not be able to reach in quake, it can be turned on by particular games if desired (div0, SavageX, Kazashi) d bug darkplaces server: the lava+func_trains room of r1m5 is leaving items floating in the air - r1m5 is Towers of Wrath, in episode of Dissolution of Eternity, aka rogue (maichal) d bug darkplaces server: when server quits, it does not notify the master that it is quitting, it should send out a couple forced heartbeats and assume the master server will remove it because it does not respond (jitspoe, div0) d bug darkplaces server: when trying to load a map that is missing the model is still precached permanently, causing 'not found' warnings every time r_restart/vid_restart are used d bug darkplaces sound: sound is sometimes coming from the wrong side apparently (lcatlnx) d bug darkplaces sound: spatialization bug occurs in The Ascension of Vigil, making all player and monster sounds far to the right (RenegadeC) d bug darkplaces sound: svc_staticsound messages are being received before sounds are precached, causing no ambient sounds to work (esteel) d bug darkplaces video: generate 1024 color gamma ramps for glx on Quadro, right now hardware gamma is being disabled on these cards because they use 1024 color ramps, not 256 (div0) d bug darkplaces wgl client: hardware gamma is being retried every frame for unknown reasons, this is SEVERELY impacting framerates on win2k/xp (Jago) d bug darkplaces windows general: include libcurl dll from Nexuiz 2.0 in future releases (Baker) d bug dpmod: air control doesn't seem to be working (Kedhrin) d bug dpmod: fix sv_user.qc noclip movement when looking straight up/down (Electro) d bug dpmod: fix the 'shell casing spawning at wrong player' bug somehow d bug dpmod: items aren't respawning in coop, they should d bug dpmod: nailgun mine launching doesn't trigger a player animation (sng one does) d bug dpmod: respawning still on fire (innovati) d bug dpmod: shouldn't get double kill for killing something and its corpse (Sajt) d bug dpmodel: md3 exporting is broken on complex models (Morphed) d bug dpmodel: scale parameter isn't affecting animations (Ghostface) d bug hmap2: make sure seconds reports in all tools don't print secondssss when they're printing shorter and shorter updates (FrikaC) d bug hmap2: strip .map extension from filename if present d bug zmodel: makefile should support mingw d change darkplaces client: add a swinging weapon motion to replace the removed forward/back movement of the weapon, should be controllable with cl_bob_* cvars (Joel Murdoch) d change darkplaces client: add some particles to teleportsplash (Uffe) d change darkplaces client: change timedemo minfps/maxfps to be the lowest and highest fps in one second segments, similar to the showfps display, this should solve the precision problems resulting in stupidly high/low fps reports (m0rfar) d change darkplaces client: get image sizes from .lmp files if present d change darkplaces client: implement inversion of non-uniform scaling in Matrix4x4_Invert_Simple or replace it with a full featured matrix inverter d change darkplaces client: reduce number of particles used, and particle limit, to save some memory (LordHavoc) d change darkplaces client: tone down scrag and hell knight shot trails d change darkplaces extensions: add DP_QUAKE3MAP extension to indicate that the engine supports Q3BSP files d change darkplaces menu: remove gl_combine from menu as it's not saved to config and really shouldn't be changed except when debugging drivers (QuakeMatt) d change darkplaces model system: change model animations back to their original compressed format (not float[3]), decode them as needed d change darkplaces prvm: disable the unknown opcode error in progs loading so that fteqcc fastarrays progs will load (Spike) d change darkplaces prvm: make strzone able to take multiple varargs strings like strcat does (KrimZon) d change darkplaces renderer: add a r_show_disabledepthtest cvar which defaults to 0 (and could be considered a cheat), and r_show_polygonoffset cvars, rename r_shadow_visible* cvars to r_show*, rename r_drawcollisionbrushes to r_showbrushes, and make all the r_show* cvars control brightness d change darkplaces renderer: build a temporary msurface_t struct in model renderer and call map surface list renderer, eliminating model surface renderer d change darkplaces renderer: get rid of DSDT texture support in texture manager, it was only used for the geforce3 water shader which has been removed d change darkplaces renderer: make meshqueue transparent sorting take a farclip instead of using 4096 d change darkplaces renderer: make r_showtris only affect ingame view d change darkplaces renderer: make sprites use skinframe_t instead of their own texture/fogtexture pointers d change darkplaces renderer: remove GL_NV_texture_shader detection d change darkplaces renderer: write rendering functions that take msurface_t * lists and migrate most of renderer to them (LordHavoc) d change darkplaces server: make dedicated server not load images (maybe all fail?) d change darkplaces server: remove upper limit on sv_maxrate, there's no reason to limit it d change darkplaces: enable deathmatch scoreboard stuff in coop! (Monster) d change dpmod: make cells only 30 damage, they're too powerful now (hyenur) d change dpmod: use sv_maxairspeed cvar (engine) rather than sv_airmaxspeed (qc) cvar in playermovement.qc and default.cfg d change dpmodel: include the example script in the build zips, not just in the files directory d change dpmodel: keep all bones instead of removing unused ones (Ghostface) d change hmap2: increase MAXTOKEN from 1024 to 16384 (FrikaC) d cleanup darkplaces cleanup: remove cgame* files and any references d cleanup darkplaces cleanup: remove ui.* files and any references d cleanup darkplaces console: look at Black's recent console args changes and clean it up as he requested, particularly removing a commented block (Black) d cleanup darkplaces csqc: cl.csqcentities/cl.num_csqcentities/cl.max_csqcentities are probably entirely unnecessary d cleanup darkplaces general: get rid of fs_filesize, use parameters/local variables instead (Randy) d cleanup darkplaces general: replace qbyte with unsigned char (Randy) d cleanup darkplaces loader: merge msurface_t/q3mface_t, mleaf_t/q3mleaf_t, and mnode_t/q3mnode_t d cleanup darkplaces prvm: move vm_files and vm_fssearchlist arrays in prvm_cmds.c to prvm_prog_t struct d cleanup darkplaces renderer: split GLSL program compilation code into shaderobject and programobject functions to reduce code d cleanup darkplaces server: modify q3bsp traceline code to eliminate unused cases like point testing in traceline d darkplaces GLX client: make sure that vid_vsync is taking effect immediately d darkplaces SDL client: add key repeat d darkplaces WGL client: if gamma setting fails, restore system gamma (RenegadeC) d darkplaces cleanup: clean up Collision_TraceBrushBrush to have another temp variable besides f and clean up the enterfrac2 handling (Vic) d darkplaces cleanup: make memory pools have a flag to print them as temporary pools (I.E. consider them leaks if anything is in them) (Vicious) d darkplaces cleanup: make sure engine dumps log file to disk if there is a Sys_Error (VorteX) d darkplaces cleanup: merge model format handling (mdl/md2/md3/zym) d darkplaces cleanup: nodestack[nodestackindex++] = node->children[0]; and similar things should skip the node if stack is full (Vic) d darkplaces cleanup: port DarkWar polygon.c to darkplaces, as it is more optimized than winding.c d darkplaces cleanup: rename QuadraticSpline code in curves.c to QuadraticBSpline d darkplaces client bug: CL_Video_Init is allocating a texture pool, this is not allowed in init functions, it must be moved to cl_video_start and the corresponding free must be moved to cl_video_shutdown, and an empty cl_video_newmap added, these need to be registered with an R_RegisterModule call (Black, Randy) d darkplaces client bug: ignore modelflags on view weaponmodel - in Malice the double barreled shotgun leaves a smoke trail, and the hellfire rotates, also in Zerstorer the riot shotgun rotates (Hidayat) d darkplaces client bug: make sure QuakeDoneQuick works (Chris Kemp) d darkplaces client: (goodvsbad2) increase chase_stevie height to 2048 (yummyluv) d darkplaces client: Draw_CachePic: failed to load gfx/net.lmp (LordHavoc) d darkplaces client: GAME_NEXUIZ: don't show monsters/secrets on scores sbar (Vermeulen) d darkplaces client: GAME_NEXUIZ: horizontally center notify lines (Vermeulen) d darkplaces client: ValidateState should not error out about colormap > maxclients, only warn (Static_Fiend) d darkplaces client: add DP_LITSPRITES extension to document the fact that any sprite with a ! in its filename is lit rather than fullbright d darkplaces client: add GAME_PRYDON mode which would make vore spike trails blue as they're used for ice (Urre, Harb, FrikaC) d darkplaces client: add a config saving command (Speeds) d darkplaces client: add ability to load gfx/particlefont.tga (Vermeulen, frightfan, Error) d darkplaces client: add back random framegroup animation sync for sprites and models so torch flames don't play in sync (Elric) d darkplaces client: add cl_netlag (ping, like cl_netlocalping_* but no range) and cl_netpacketloss/sv_netpacketloss cvars (packetloss percentage, half of this each way) d darkplaces client: add cl_particles_particleffect_bloodhack cvar to enable converting id1 blood effects to TE_BLOOD style (Alex Boveri) d darkplaces client: add color code support to console printing (Vermeulen) d darkplaces client: add color codes to console, but first need to decide on a prefix character, this can be used to color code stuff in the engine too, but the prefix must be chosen carefully not to mess up most text (Up2nOgOoD) d darkplaces client: add cvars for sbar alpha (background and foreground) (Throvold@uboot.com) d darkplaces client: add gl_polyblend cvar to control amount of viewblend effect (Andrew A. Gilevsky) d darkplaces client: add r_waterwarp cvar to control amount of viewwarping underwater (Andrew A. Gilevsky) d darkplaces client: add to .rtlights these fields: flags (ambient, diffuse, specular, normalmode, realtimemode), coronascale (0-1), the normalmode flag allows rtlights to forcibly exist in normal mode which is mainly useful for decorative coronas in nexuiz maps (Vermeulen) d darkplaces client: add two cvars to replace sbar_alpha, one would control background as 0-1, and one would control everything else as 0-1 d darkplaces client: add video playback handles to the cl_video code so that other systems can use streaming video textures, and allow the menu qc to use these (Black) d darkplaces client: all glow trails are bright blue (Kinn) d darkplaces client: change server browser listing structures to store the real data returned from the server, rather than the current processed strings suited only to the menu, menu qc needs to look at the original data (Black) d darkplaces client: cl_particles_size isn't working, it should affect rendering (Chillo) d darkplaces client: crosshair traceline should not hit your own model (Willis) d darkplaces client: don't disconnect before attempting to connect to another server, so if it fails you remain on the current one (RenegadeC, Urre) d darkplaces client: don't draw entities which are tagged to the camera entity; exterior view models and such d darkplaces client: don't query servers twice in slist, only add new unique servers when parsing a server list (Willis) d darkplaces client: dynamic allocation of entity_t structures to save memory (LordHavoc) d darkplaces client: entities not being removed in quake protocol demos? (MoALTz) d darkplaces client: figure out why r_editlights_edit cubemap is not setting the light's cubemap name d darkplaces client: figure out why startdemos is listed twice (once as 0 demos and once as 3 demos) in startup of id1 (Kazashi) d darkplaces client: figure out why tenebrae style dlights are brilliantly blue, PLUS their color d darkplaces client: fix 'no such frame 0' warnings in prydon when examining wands in stores d darkplaces client: fix disappearing decals bug, it seems that when the smoke disappears so do the decals (Urre) d darkplaces client: got an error: Protocol: Runaway loop recursing tagentity links on entity -7763899 (LordHavoc) d darkplaces client: increase resolution of particlefont to 512x512 (Chillo) d darkplaces client: interpolate punchangle and punchvector from network (Sajt) d darkplaces client: lerp lightstyles (Mitchell) d darkplaces client: locked console scrollback (sublim3) d darkplaces client: make CL_RocketTrail2 use the entity to keep track of trail spacing like CL_RocketTrail does (Vic) d darkplaces client: make blood decals a bit lighter as they're nearly black (ashridah) d darkplaces client: make cl_beams_relative only affect view-attached beams d darkplaces client: make colormap > cl.maxclients error be only a warning, to play QDDQ demo of end map (Stribbs) d darkplaces client: make sure that menu sounds are ATTN_NONE (DarkSnow) d darkplaces client: make sure the join game menu shows status on a connection attempt, such as connecting or failed (RenegadeC) d darkplaces client: mini scoreboard (the deathmatch overlay) shows player names multiple times in some cases, fix this! d darkplaces client: rain drops should splash when they hit (Tomaz, Carni) d darkplaces client: remove GAME_NETHERWORLD cvars, as they have been added to the default.cfg (VorteX) d darkplaces client: reset cl.viewzoom on connect (Rick) d darkplaces client: send one input message per server message instead of using sys_ticrate (LordHavoc) d darkplaces client: skybox should not be reset by r_restart (Stribbs) d darkplaces client: when video restarts, set the vsync status variable back to false so it will set it on the first VID_Finish if it is supposed to be on (Tomaz) d darkplaces collision: mod_q3bsp_optimizedtraceline going through brushes? (Vermeulen) d darkplaces commandline: make commandline parser ignore + and - if they were not directly following a space, so that + and - can be used in map names and such, also ignore if - or + is start of a number (Urre) d darkplaces compatibility: add a sv_gameplayfix_setmodeluserealbox to allow people to use the +-16 box hack for running some mods (Spike) d darkplaces console: add "set" and "seta" commands (DP_CONSOLE_SET and DP_CONSOLE_SETA extensions) to create a cvar and set its value (seta makes a saved cvar) (VorteX) d darkplaces console: add DP_CON_SET and DP_CON_SETA extensions to describe the set and seta commands d darkplaces console: add a cvar which sets the start map name so that mods can set their own instead of using "start" or needing to modify the engine (Urre, Elric, Vermeulen) d darkplaces console: allow typing characters > 126 (LordHavoc) d darkplaces console: default to insert mode (LordHavoc) d darkplaces console: exec is broken, it requires two newlines around it, which is breaking the prydon quake.rc (FrikaC) d darkplaces console: get rid of stupid cursor right at end of line behavior in console, repeating characters from the previous edit line is just annoying (LordHavoc) d darkplaces console: make typing "; quit " in messagemode NOT quit the game (jitspoe) d darkplaces console: redesign startup script handling to scan scripts for cvars (ignoring commands) and then init video and then run the scripts for real d darkplaces docs: add de-we to credits page for the great icons (de-we) d darkplaces docs: write a readme (Antti) d darkplaces editlights: add r_editlights_copyinfo and r_editlights_pasteinfo commands to clone the properties of a light, all except for origin (Stribbs) d darkplaces editlights: add r_editlights_editall command, same as _edit but affects all lights (mashakos) d darkplaces editlights: fix positioning of light editing display, it's not following the console properly d darkplaces editlights: light entity loader is broken, it ends up with scrambled colors (avirox, Tomaz) d darkplaces editlights: r_shadow should load .ent when importing light entities d darkplaces filesystem: darkplaces-glx -path transfusion crashes, fix the crash even though it's not going to work anyway (Todd) d darkplaces filesystem: make FS_Open fail to open paths that contain parent directory links (/../ and such, depending on platform) to prevent console commands from doing damage, this is similar to the FRIK_FILE qc extension checking the path (FrikaC, Spike) d darkplaces game: GAME_FNIGGIUM: "data" directory (not "id1" at all) d darkplaces game: GAME_FNIGGIUM: 22050/44100 khz sound default d darkplaces game: GAME_FNIGGIUM: console doesn't show unless you manually pull it down (Sajt) d darkplaces game: GAME_FNIGGIUM: minimum resolution: 640x480 d darkplaces general: reduce cl_entities memory usage (LordHavoc) d darkplaces general: reduce entityframe5_database_t memory usage to reduce per-client memory (LordHavoc) d darkplaces general: reduce particle_t struct size (LordHavoc) d darkplaces init: if quake.rc is missing, run the commandline options anyway (LordHavoc) d darkplaces input: CTRL-V clipboard paste feature in windows (Rick, FrikaC) d darkplaces input: add more joystick buttons, 3 isn't enough (Static_Fiend) d darkplaces input: allow typing characters > 128 into console to allow Latin1 fonts to be used properly, already works in text messages (Urre) d darkplaces input: key repeat should work in menus, for example scrolling quickly through options (Up2nOgOoD) d darkplaces loader q3bsp: remove snapping to integer on patch vertices (HReaper) d darkplaces loader: LoadTGA needs to read a palette whether the image uses it or not, and should ignore alpha if there are no attribute bits, to comply with the TGA spec http://netghost.narod.ru/gff/graphics/summary/tga.htm (LordHavoc) d darkplaces loader: add q2 sprite support sometime d darkplaces loader: don't report texture loading failure warnings when in dedicated server (Biomass) d darkplaces loading: clear stainmaps on map restart/change based on cl_stainmapsclearonload cvar (John Truex) d darkplaces loading: fix bumpmapping, there's something quite mixed up about the svectors and tvectors (Randi) d darkplaces loading: make files override pak and pk3 archives, as it's really what people expect, hopefully this won't break any broken mods (Stribbs) d darkplaces loading: make hl map loading halve the lightmap samples, to fit hl's 0-1 range into quake's 0-2 range (KrimZon) d darkplaces loading: make it only reload rtlights when current map changes, not when restarting renderer or reloading same map (Stribbs) d darkplaces loading: make sky work without a valid size (just treat it as single layer clouds or non-animated) (tell Vermeulen) d darkplaces loading: make sure .skin files work on md3 models that have no default shaders but do have mesh names (VorteX) d darkplaces loading: make sure startup script code executes aliases when doing the cvar scan d darkplaces loading: missing triangles in q3bsp patches, appears only one of the two triangles per cell is being rendered (Zombie) d darkplaces makefile: enable sdl builds by default (Spirit_of_85) d darkplaces math: make portals.c use Polygon_Divide to reduce redundent code (LordHavoc) d darkplaces menu: add cl_particles_particleffect_bloodhack cvar to menu (Alex Boveri) d darkplaces menu: add confirm question to "Reset to Defaults" option, with No selected (Sajt) d darkplaces menu: add graphics options menu and put realtime lighting stuff in it (Antti) d darkplaces menu: add slowmo to options menu (Cristian Beltramo) d darkplaces menu: add sv_maxrate cvar to server setup menu d darkplaces menu: player setup menu network speed is never applying to rate (Mitchell) d darkplaces menu: r_shadow_realtime_world_lightmaps cvar should be a slider, not a checkbox (Diablo-D3) d darkplaces parse: support " as an end token for words in Com_Parse d darkplaces parse: support ' quoted strings d darkplaces physics: bmodels (doors, etc) hurt player if player pushes against it, and sometimes gets stuck for a frame when falling onto it (Andrew A. Gilevsky) d darkplaces physics: disable sv_gameplayfix_stepdown while underwater (Sajt) d darkplaces physics: make players step down stairs rather than just flying off (Riot) d darkplaces physics: repeatedly jumping against a wall can cause a fall to your death (MoALTz) d darkplaces physics: some pushers aren't affecting entities standing on them, such as the e3m2 key on a falling platform, monsters on moving floors, and players riding down the e1m1 lift (scar3crow) d darkplaces physics: standing on a slope that slopes into an obstacle causes a 'falling' condition, velocity keeps increasing (VorteX) d darkplaces protocol bug: model colormap is showing white on client, is it even being sent? d darkplaces protocol: GAME_NEXUIZ: add a NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN extension which would add playermodel and playerskin commands and set them up like the Host_Name_f stuff works, this means a command for each, saved _cl_playermodel and _cl_playerskin cvars, playermodel and playerskin fields in the client_t struct, and quakec fields to allow access to these similarly to .clientcolors (Vermeulen, Black, VorteX) d darkplaces protocol: MSG_ReadAngle functions should return +-180 range, not 0-360 (Carni) d darkplaces protocol: PROTOCOL_DARKPLACES4 malfunctioning after a few seconds, probably not acknowledging packets properly (Sajt) d darkplaces protocol: add DP_SV_BUTTONCHAT extension to document the addition of .buttonchat to indicate if someone is not in key_game mode, so mods can show a talk bubble if they wish (Vermeulen) d darkplaces protocol: add DP_SV_BUTTONUSE extension to document the addition of .buttonuse and +use/-use commands (Kazashi) d darkplaces protocol: add rate command and sv_maxrate cvar (and _cl_rate cvar to save to config) to control client rate (rate is sent to server on connect as a command, like other properties) (protoplasmatic) d darkplaces protocol: expand viewzoom to two bytes (8bit.8bit fixedpoint instead of 0.8bit like it is now) (Urre) d darkplaces protocol: make a DP_EF_NODEPTHTEST extension which causes an entity to show through walls, useful for navigation markers (Urre, CheapAlert, Supajoe) d darkplaces protocol: rename PreciseAngle stuff to Angle16, add Angle8 functions (for EF_LOWPRECISION code), upgrade Angle functions to use Angle16 or Angle8 depending on protocol version, upgrade ammo/armor stats to 16bit (Urre, FrikaC, Sajt, mashakos, RenegadeC, scar3crow) d darkplaces release: add windres stuff to makefile to compile darkplaces icon into win32 builds (tell de-we) d darkplaces renderer: .skin loading for models (override skins - not exactly shaders, but adequate, missing replacements are nodraw, this allows q3 player models with optional accessories) (Electro) d darkplaces renderer: 12bit color textures in 16bit mode?? (Tomaz) d darkplaces renderer: bloom effect (Vermeulen) d darkplaces renderer: bmodels all rendering as water in malefic (Harbish) d darkplaces renderer: check out qe1 textures and make sure they load in all the e1 maps, report of crashing in most but not all maps (Linny Amore) d darkplaces renderer: check that surface has triangles before adding it to the texturesurfacelist (LordHavoc) d darkplaces renderer: examine the surface rendering code to make sure it has no bugs regarding texture selection for any of the passes (sublim3) d darkplaces renderer: figure out the 'inverted bumps' bug on some texture orientations (see crate tops at end of e1m1, tenebrae1 does not suffer this problem somehow) (U8Poo) d darkplaces renderer: figure out what's wrong with gloss rendering vertex calculations, which may be GF2 related (QorpsE) d darkplaces renderer: figure out why glow rendering on models seems to get brighter/darker when gl_combine is turned on/off (LordHavoc) d darkplaces renderer: fix EF_ADDITIVE alias model entities not appearing in realtime lighting mode (VorteX) d darkplaces renderer: fix a crash when changing level while using qe1 textures (Todd) d darkplaces renderer: fix loadsky;r_restart;r_restart crashing or showing random textures (Sajt, Randy) d darkplaces renderer: fix model lighting with r_shadow_realtime_world_lightmaps mode, it seems to be adding dlights to vertices? (Mitchell) d darkplaces renderer: fix r_shadow_glsl 1 mode on textureless maps (LordHavoc) d darkplaces renderer: fix rtdlights not rendering in q3bsp (Vermeulen) d darkplaces renderer: fix the bug causing models in an unlit map to be black when they should be fullbright (Sajt) d darkplaces renderer: fix the sometimes non-animating framegroups on sprites (Kinn) d darkplaces renderer: implement PXQ_GFX_LETTERBOX extension (RenegadeC) d darkplaces renderer: make gl_picmip affect only maps, models, and sprites by setting their TEXF_PICMIP flag (Zenex, Urre) d darkplaces renderer: make gl_texture_anisotropy take effect immediately like gl_texturemode rather than needing an r_restart (metlslime, zinx) d darkplaces renderer: make static entities work in realtime lighting mode, like func_illusionary for example, they're currently black (Urre) d darkplaces renderer: make sure r_novis works (Carni) d darkplaces renderer: make sure zym code is rendering at correct brightness, it's too dark in nexuiz (Vermeulen) d darkplaces renderer: r_shadow_glsl 1 mode has inverted normalmaps - r_shadow_glsl 0 is correct (LordHavoc) d darkplaces renderer: skybox textures are not being freed when changed! (LordHavoc) d darkplaces renderer: water shader not working with fog (Tomaz) d darkplaces renderer: zym model rtlight support (Vermeulen) d darkplaces server: "edict -1" and other invalid numbers cause an error, should just complain (Supajoe) d darkplaces server: add DP_HALFLIFE_SPRITE extension (Urre) d darkplaces server: add findflag and findchainflag builtins (Sajt) d darkplaces server: add sv_gameplayfix_blowupfallenzombies and sv_gameplayfix_findradiusdistancetobox (LordHavoc) d darkplaces server: add sv_progs cvar, defaulted to "progs.dat", can be set from console or by menu to choose a mod (Black) d darkplaces server: client colors are being reset to "15 15" each level in prydon gate and dpmod (FrikaC, LordHavoc) d darkplaces server: don't use popup error window in windows dedicated server crashes (FrikaC) d darkplaces server: figure out what is wrong with dedicated server console on win32 and fix it (and tell Willis) d darkplaces server: figure out what's making monsters act like notarget is on while underwater (romi) d darkplaces server: figure out why zombies are disappearing when not entirely submerged in some hipnotic maps (romi) d darkplaces server: fix SV_SoundIndex warnings in sv_protocolname DARKPLACES5 (Spike) d darkplaces server: fix the sys_ticrate bounds checking, it's constantly setting it to 0.1 when it is already 0.1, don't set it if the change is insignificant d darkplaces server: for some clients PROTOCOL_DARKPLACES5 stops updating after a short while after a reconnect... why? d darkplaces server: implement q3bsp playerclip support with SOLID_SLIDEBOX (Vermeulen) d darkplaces server: make a getattachmentvectors qc builtin (Supajoe, Urre) d darkplaces server: make findradius use areagrid scans to speed up searching (Urre, Sajt) d darkplaces server: make qc profile command post an error message instead of crashing when used during demo playback (Sajt) d darkplaces server: make server able to work without models, just for sake of completeness d darkplaces server: make sys_ticrate impose a maximum frame time so that it calls SV_Physics multiple times in one frame to avoid slowing down d darkplaces server: prevent player name changes faster than once every 5 seconds (sublim3) d darkplaces server: still says " disconnected" in dpmod, figure out why and fix it d darkplaces server: stop sound before loading a level to get rid of looping noise (Edward Holness) d darkplaces sound: add a sound unloader of some sort, to allow music and other one-level stuff to be unloaded d darkplaces sound: clear sound buffer at startup so it doesn't play static during startup on windows (FrikaC) d darkplaces sound: dsound broken, needs to be managed as part of video system (jeremy janzen) d darkplaces sound: make sound engine restart ambients after a restart (RenegadeC) d darkplaces sound: make sound loader check both sound/%s and %s, incase a sound (like music/something.wav in q3 maps) is not in the sound directory (Static_Fiend) d darkplaces sound: make sound precaching not allocate an sfx if the sound is not found, so it complains only once about missing sounds when you connect, rather than constantly, and also so using "play" commands for non-existent files won't eat up sfx slots (fuh) d darkplaces sound: non-cd music tracks should not be affected by sound volume setting (Urre) d darkplaces video: add vid_vsync cvar and also to options menu (metlslime) d darkplaces: Host_Name_f validate player names, stripping \r and \n d darkplaces: PF_traceline/PF_tracebox now work with world as the edict d darkplaces: Quake3 bsp support (Vermeulen, Mitchell, Sajt) d darkplaces: TEXF_CLAMP needs to use GL_CLAMP_TO_EDGE (if not supported just use REPEAT as a fallback, not aware of any cards that lack this) d darkplaces: Zerstorer: riot shotgun rotates even as a view model: need to ignore that model flag when a view model d darkplaces: adaptive patch subdivision levels on X and Y based on r_subdivisions cvar d darkplaces: add "showdate" cvar d darkplaces: add "showtime" cvar d darkplaces: add "skin" and "pflags" parsing to light entity loader in rtlights mode (Electro) d darkplaces: add -benchmark commandline option which plays a demo, appends the resulting min/max/avg fps to gamedir/benchmark.log with commandline so people know what settings were used, like +exec realtimelow.cfg, +exec realtimemed.cfg, etc (romi) d darkplaces: add 66.28.32.64 to master server list (Willis) d darkplaces: add DP_EF_NOSHADOW extension (Urre) d darkplaces: add DP_GFX_EXTERNALTEXTURES extension (Electro) d darkplaces: add DP_LITSUPPORT extension and document it d darkplaces: add DP_SND_OGGVORBIS extension which can be checked by mods to know they can intentionally load .ogg instead of .wav, since the engine prefers wav over ogg normally (CheapAlert) d darkplaces: add DP_SV_ROTATINGBMODEL extension to explain that MOVETYPE_PUSH/SOLID_BSP support rotation in darkplaces and a demonstration of how to use it without qc modifications (Uffe, Supajoe) d darkplaces: add GAME_NEXUIZ mode d darkplaces: add GL_EXT_stencil_two_side support to shadow rendering - note: this got a 77% speedup! (fuh) d darkplaces: add PF_copyentity error checking for copying to world (yummyluv) d darkplaces: add a "cmd" command to the client for sending arbitrary commands to the server, mainly for use with KRIMZON_SV_PARSECLIENTCOMMAND d darkplaces: add a "edictset" command to console to set a single field of an edict to the specified value d darkplaces: add a newline to map name printing d darkplaces: add a scr_screenshot_jpeg_quality cvar (Electro) d darkplaces: add airborn blood images to the particlefont which would look like a cloud of droplets (Vermeulen) d darkplaces: add an extension for EF_RED and EF_BLUE (FrikaC) d darkplaces: add an optimized special case to AngleVectors for roll == 0, thanks to fuh for the idea d darkplaces: add and document DP_HALFLIFE_MAP_CVAR extension (the cvar which has existed for a long time) d darkplaces: add and document DP_SND_DIRECTIONLESSATTNNONE extension d darkplaces: add and document DP_SND_STEREOWAV extension d darkplaces: add anisotropic filtering options (Zombie_13, zinx) d darkplaces: add bullet hole decals to the particlefont (Vermeulen) d darkplaces: add cl_decals to effects options menu d darkplaces: add cl_particles_quality cvar (1-10) which would scale count of particles and inversely scale alpha of particles (TheBeast) d darkplaces: add constant insertion capabilities to Image_CopyMux d darkplaces: add cubemap px/nx/py/ny/pz/nz loading in skybox loader d darkplaces: add cvar_string builtin (Paul Timofeyev) d darkplaces: add display of current cursor coordinates in realtime lighting mode (Stribbs) d darkplaces: add error messages to LHNET_OpenSocket_Connectionless or its callers (Zombie13) d darkplaces: add extension for tenebrae dlight entities d darkplaces: add file access functions and string handling (diGGer) d darkplaces: add fov to menu d darkplaces: add gl_lightmaps cvar to disable texturing except lightmaps for testing (Vic) d darkplaces: add gl_texture_anisotropy to menu (Static_Fiend) d darkplaces: add lightning beam settings to menu (romi) d darkplaces: add log cvar to set console logging target (default "", or default "qconsole.log" if -condebug is used) d darkplaces: add multiple skin support to md2/md3 (Vermeuln) d darkplaces: add ogg music playback using optional library after adding wav music playback (Joseph Caporale, Static_Fiend, Akuma) d darkplaces: add r_shadow_realtime_world_lightmaps cvar to control lightmap brightness (Mitchell) d darkplaces: add r_showtris cvar (Riot) d darkplaces: add scr_conbrightness cvar (0-1) to control brightness of conback (0 = black and does not load conback, resets back to 0 if conback fails to load) d darkplaces: add skin and pflags support to light entity loader d darkplaces: add some cl_explosions_ cvars to control settings - start alpha, end alpha, start size, end size, life time (Supajoe, Mercury) d darkplaces: add stats to slist menu displaying how many masters/servers have been queried and replied (tell yummyluv) d darkplaces: add support for multiple -game's (note: this needs an enhanced COM_CheckParm to find the multiple matches) (Static_Fiend) d darkplaces: add support for red/cyan and red/green and red/blue anaglyph stereo glasses d darkplaces: add sv_freenonclients cvar (Vermeulen) d darkplaces: add sv_gameplayfix_grenadebouncedownslopes cvar (default 1) d darkplaces: add sv_gameplayfix_noairborncorpse cvar (default 1) d darkplaces: add sv_gameplayfix_stepwhilejumping cvar (default 1), note that sv_jumpstep must also be on to enable this d darkplaces: add sv_gameplayfix_swiminbmodels cvar (default 1) d darkplaces: add tenebrae light entity properties, like cubemap and style and such d darkplaces: add vid_pixelaspect patch from Grisha Spivak's email d darkplaces: add view height to chase_active again (yummyluv) d darkplaces: add wav music playback (Joseph Caporale, Static_Fiend) d darkplaces: add wgl support for mouse buttons 4 and 5 (Intellimouse Explorer) from Q2 (source supplied in email from joseph caporale@sbcglobal.net) d darkplaces: added RENDER_LIGHT flag to entity_render_t to make rtlighting optional per entity d darkplaces: added silly scr_zoomwindow as an experiment, turned out mostly useless d darkplaces: cap packet size at 1k for non-local connections, regardless of their rate setting d darkplaces: change R_Shadow_VertexShading functions to use sqrt and VectorLength2 instead of two VectorLength calls (Vic) d darkplaces: change cl_fakelocalping_min and _max to only lag by half each way, as currently it results in 2x ping d darkplaces: change particle engine to not compact particles array, but keep track of highest used number, update it each frame (Tomaz) d darkplaces: check if nodrawtoclient works and if not, fix it (Uffe) d darkplaces: cleaned up rtlight handling, merging most code between world rtlights and dlights d darkplaces: collision: 'wall stuttering' collision bugs: getting stuck and nudged out constantly when sliding along certain walls d darkplaces: collision: client getting fraction out of bounds errors when in a map the client does not have d darkplaces: collision: q3bsp curve problems: comparing nudged impacts causes player to hit edges of triangles in a q3bsp curve closer than the surface d darkplaces: colors of player in demos seems to alter player config (this is clearly a more severe problem than just demos) (tkimmet@ezworks.net) d darkplaces: console scrolling should not reset when new messages appear d darkplaces: crashes if you type too long a command line in the console (Sajt) d darkplaces: debug server crash d darkplaces: dedicated server hosting prydon with multiple players exhibited severe networking bugs in tests, including failure to find acked network frames, and a segfault (Supajoe, Uffe, FrikaC, Harb) d darkplaces: dedicated server should error out if it has no sockets (yummyluv) d darkplaces: dedicated server should not bother allocating a loopback socket (yummyluv) d darkplaces: default a few cvars accordingly for GAME_TENEBRAE mode d darkplaces: default deathmatch 1 in multiplayer games like Nexuiz incase someone starts a game from console (Vermeulen) d darkplaces: default to 32bit color d darkplaces: default to sv_cullentities_pvs mode again... trace is too slow in q3bsp and unreliable by nature anyway d darkplaces: delay "connect" and "playdemo" and "timedemo" until after video init to cause quicker video startup (KrimZon) d darkplaces: disable mod_q3bsp_optimizedtraceline by default until it works d darkplaces: display "No servers found" instead of a cursor when there are none (yummyluv) d darkplaces: don't accept connect packets after first one (tell Willis) d darkplaces: don't complain if lightning bolt models are missing in client (Electro, Sajt) d darkplaces: don't crash if SOLID_BSP is used with modelindex 0 - TargetQuake does this... d darkplaces: embed a fallback conchars.tga so it can load in an empty directory with a visible console (right now it uses the checkerboard texture) d darkplaces: figure out and fix vis problems when noclipping out of world in q1bsp and q3bsp d darkplaces: figure out and fix win32 networking problems d darkplaces: figure out how monster models are disappearing in waistdeep water in e1m3 (scar3crow) d darkplaces: figure out random crashes on map changes (Uffe, QorpsE) d darkplaces: figure out what is breaking with rate limited (partial) entity updates that is losing entities (Urre, FrikaC, Harb) d darkplaces: figure out what is broken about the shadow volumes or stencil comparisons d darkplaces: figure out what is causing invalid entity numbers in TouchAreaGrid in world.c - suspicion: problem with reallocation of edicts? d darkplaces: figure out what is wrong with loading _glow/_luma textures on md3 models (not bsp textures) (kd23 Nexuiz) d darkplaces: figure out why -sndspeed 22050, 44100 and 16000 are choppy in windows? (cheapalert) d darkplaces: figure out why disconnections are showing up as " disconnected" d darkplaces: figure out why dlights are apparently disappearing in nexuiz when far away (Vermeulen) d darkplaces: figure out why fullbrights are black on models (romi) d darkplaces: figure out why quad is creating two coronas, one at player and one at 0 0 0 - answer: viewmodel dlight (Tomaz) d darkplaces: finish new udp networking code (yummyluv) d darkplaces: finish the partial update support in protocol.[ch] and reduce packet size to 1k to fix NAT routers (yummyluv, Vermeulen, Elric) d darkplaces: fix "game speed" menu option, it's too far left (Tomaz) d darkplaces: fix 'fall to death in wedge corner' glitch from quake (Zombie) d darkplaces: fix 2D attenuation texturing which is all black d darkplaces: fix PF_substring's mishandling of the end variable (Fuh) d darkplaces: fix Short format entity origins to fix shell casings sitting in floor/above floor (Tomaz) d darkplaces: fix black models bug with unlit maps (CheapAlert) d darkplaces: fix bounding box bugs with viewmodelforclient (diGGer) d darkplaces: fix broken key repeat on backspace key in console (Mercury, CheapAlert) d darkplaces: fix broken mouse button display in key binding menu, it shows ??? for mouse buttons (Mercury, Tomaz) d darkplaces: fix con_notify (should control number of lines) d darkplaces: fix cubemap upload scaling crashes (Urre) d darkplaces: fix cursor being flipped in Prydon (FrikaC) d darkplaces: fix curve collision bugs, catching on edges of triangles d darkplaces: fix entity glows to use Mod_FindNonSolidLocation... maybe all dlights should? (CTF has this problem with flags) d darkplaces: fix envmap command, it's saving black again, and is the wrong arrangement (Tomaz) d darkplaces: fix gl_texturemode change errors (Vic) d darkplaces: fix intermission failing to move view to intermission camera (romi, Zombie_13) d darkplaces: fix invisible md3 bug d darkplaces: fix invisible md3 models when missing textures (John Truex) d darkplaces: fix key based turning being affected by slowmo - it should not be d darkplaces: fix logging, setting log_file during the game doesn't open a log apparently (FrikaC) d darkplaces: fix md3 shadow volumes d darkplaces: fix network timeouts d darkplaces: fix non-polygonal lightning beam model pitch (it was backwards) (thanks Eksess for reporting this) d darkplaces: fix particle trails (I think trail start is identical to trail end) (Supajoe, Sajt) d darkplaces: fix q3bsp static shadow volumes (currently they are calculated as if novis) d darkplaces: fix r_drawentities view problem (stops updating r_refdef.vieworg?) (Vic) d darkplaces: fix r_editlights_edit origin not working (romi) d darkplaces: fix r_novis d darkplaces: fix r_shadow_portallight 1 (default) mode (Vermeulen) d darkplaces: fix server crashing from client timeouts (Moz) d darkplaces: fix server list not working until you set maxplayers above 1 (Rick) d darkplaces: fix server list only querying the master to reply (Rick) d darkplaces: fix skybox geometry (Sajt) d darkplaces: fix skybox orientation to match glquake/quake2/quake3, it needs to be rotated 90 degrees; +X should be rt (metlslime) d darkplaces: fix sounds not following entities (yummyluv, Sajt) d darkplaces: fix starting non-existent maps. (drops to console with an error like it should) d darkplaces: fix startup on multiplayer games so they don't start a game when executing startdemos unless -listen or -dedicated was used (yummyluv) d darkplaces: fix suffix table used by cubemap loader to load skyboxes in the correct arrangement, matching the sky (Tomaz) d darkplaces: fix the dedicated server timing, seems to be using host_maxfps instead of sys_ticrate d darkplaces: fix the fact singleplayer is using maxplayers 8 d darkplaces: fix the weird broken config parsing at startup d darkplaces: fix toggling decals in menu d darkplaces: fix up comments on USETEXMATRIX stuff a little in r_shadow.c (Vic) d darkplaces: fix video modes menu as you can select 2048x1536 and then go right to get 0x0 (Sajt) d darkplaces: fix vis decompression underrun/overrun warnings as the problem appears to be more visleafs than the data contains (Vic) d darkplaces: fix whatever is breaking in prydon gate town curig (Uffe) d darkplaces: fix win32 bug where shift key types a character (Black, Sajt) d darkplaces: fix wrapping textures on sprites/models (Elric) d darkplaces: fixed SV_TouchAreaGrid to not crash if SV_IncreaseEdicts is called during a touch function, by making a list of edicts to touch and then running through the list afterward (romi) d darkplaces: fov limit now 1-170, was 10-170 d darkplaces: frikbot scores don't update - discovered this is because of the fact they have no client (Todd) d darkplaces: generate tvectors the same as svectors in bumpvector calculations (Riot) d darkplaces: get rid of frags per hour rating in deathmatch scoreboard and mini scoreboard d darkplaces: get rid of stencil options and make 32bit color automatically use stencil d darkplaces: give each gamemode a default screenshot name pattern, and make -game override the name pattern to match the mod dir (Rick) d darkplaces: gl_flashblend 1 should disable dlighting of models (Tomaz) d darkplaces: have a look at CFQ and figure out why its b0rked (it assumed nq noclip movement) d darkplaces: heartbeat should print an error message if used with no server running (yummyluv) d darkplaces: identify weird lightmap texturing bug on TNT cards - goes away in r_textureunits 1 (NotoriousRay, Uffe) d darkplaces: implement cubemap support on rtlights (romi, Vermeulen, Mitchell) d darkplaces: improve framerate limiting to sleep until next frame, instead of just sleeping a little d darkplaces: improve tenebrae compatibility by handling EF_FULLDYNAMIC flag in tenebrae mode, also make all sprites render additive d darkplaces: integrate zinx's psycho.c gamma hack as an easteregg (zinx) d darkplaces: intermission: origin and angles are wrong: probably not getting them from entity correctly (resolved: rewrote view setup and fixed timerefresh and envmap command bugs in the process, and also fixed listener positioning during intermissions) d darkplaces: intermission: statusbar disappears (resolved: not fixed, people seem to kind of prefer it this way) d darkplaces: intermission: view model isn't disappearing (resolved: fixed) d darkplaces: keep track of min and max fps (based on single frame frametime) during timedemo and print these stats (romi) d darkplaces: limit maximum lerp time on animations to .1 seconds (Vermeulen) d darkplaces: loadgame broken (Linny Amore) d darkplaces: make 22khz ogg files not crash (CheapAlert) d darkplaces: make 48khz ogg files load (CheapAlert) d darkplaces: make Com_HexDumpToConsole not use color d darkplaces: make DP_EF_FULLBRIGHT extension (FrikaC) d darkplaces: make LHNET_OpenSocket_Connectionless call getsockname to find out the address/port of the socket d darkplaces: make LHNET_Read print out the names of read errors (yummyluv) d darkplaces: make MAX_PACKETFRAGMENT a property of each net connection, so memory loopbacks could use huge limits (Sajt) d darkplaces: make Mem_Free function clear memory only if developer is on d darkplaces: make S_Update take a matrix4x4_t * d darkplaces: make TE_EXPLOSION2 use a spherical spawn pattern rather than cube shape (VorteX) d darkplaces: make bounce check for fabs(dotproduct)<60 velocity, not dotproduct<60, so now an explosion above gibs will cause them to bounce up into the air d darkplaces: make client load .ent files d darkplaces: make console editing allow cursoring left/right on the line and insert and delete, etc (Vic) d darkplaces: make envmap also save px/nx/py/ny/pz/nz images, in addition to the ft/bk/lf/rt/up/dn skybox arrangement (Tomaz) d darkplaces: make fopen builtin check / as well as data/ when reading (writing would always go in data/) d darkplaces: make light_lev dlights from qc require PFLAGS_FULLDYNAMIC flag d darkplaces: make lightning work without bolt models persent (Vermeulen) d darkplaces: make missing skins show as white on models (Electro) d darkplaces: make model lerping optional d darkplaces: make most QC builtin give warnings instead of errors, so broken mods still run d darkplaces: make notify lines show based on cl.time, not realtime, so they last the proper length when using cl_avidemo (Urre) d darkplaces: make r_fullbrights affect model skins, not just map textures d darkplaces: make reliable message splitting use a different limit than unreliable message size, to fix NAT routers (yummyluv) d darkplaces: make rocket trail have an orange glow d darkplaces: make screenshots save to screenshots directory (Sajt) d darkplaces: make screenshots save to screenshots/fniggium%04i.tga in GAME_FNIGGIUM (Sajt) d darkplaces: make sprite lerping optional (yummyluv) d darkplaces: make sure 24bit sky textures work (Static_Fiend) d darkplaces: make sure EF_FULLBRIGHT works on bmodels (FrikaC) d darkplaces: make sure EF_FULLBRIGHT works on models (FrikaC) d darkplaces: make sure EF_FULLBRIGHT works on sprites (FrikaC) d darkplaces: make sure PR_SetString points NULL strings at pr_strings (which would be an offset of 0) (Fuh) d darkplaces: make sure r_drawportals works d darkplaces: make sure r_fullbright works d darkplaces: make sure that disappearing entities are removed on the client in quake demos d darkplaces: make sure that sound engine does not remove sounds when volume drops to 0 due to going out of range - now spawns sounds even if out of range (Sajt) d darkplaces: make sure that textureless models are white and not invisible, apparently creating a .bmp texture (not supported) made the models black, even more odd... (McKilled, QorpsE) d darkplaces: make the WriteEntitiesToClient code call TraceBox directly instead of SV_Move because checking all the entities is far too slow in helm18 (banshee21) d darkplaces: make the reply receive code drop packets from servers not in the list (Willis) d darkplaces: make the static light built messages be developer prints (Tomaz) d darkplaces: make the world lights check pvs bits instead of recursing a box which would tend to touch solids d darkplaces: make v_cshift affect view even if in a liquid, by adding another cshift slot for it d darkplaces: make water and sky never cast shadows d darkplaces: make water scrolling optional d darkplaces: may be reading md3 tag matrices wrong (Electro) d darkplaces: memory pool nesting, allowing pools of pools to be batch freed (Vicious) d darkplaces: merge pvs info for all brush model formats d darkplaces: moved R_ShadowVolumeLighting to r_shadow.c d darkplaces: net_slist and the server browser should show servers when they are queried, not just when they reply; which would replace the matching entry (yummyluv) d darkplaces: net_slist should print out "No network." if networking is not initialized (yummyluv) d darkplaces: new dpmaster release (Elric, Vic) d darkplaces: noclipping out the ceiling of q3dm17 crashes (Static_Fiend) d darkplaces: optimized ray-triangle collision code d darkplaces: oriented sprites are not responding to angles properly (yummyluv) d darkplaces: physics bug: fiends can leap through the player (thanks to Tomaz for massive assistance in tracking down this longstanding bug) d darkplaces: physics bug: rotating bmodels stop when the player blocks them instead of pushing the player d darkplaces: playerprethink being called before clientconnect? (Electro) d darkplaces: post new darkplaces build. (email FrikaC) d darkplaces: post new dpmaster build. d darkplaces: put new shell casings in dpmod (Tomaz) d darkplaces: q1bsp trace bug: 'wall hugging' stuttering, also stuttering movement when walking over steps or monsters and causes block on moving doors (Urre, romi, Static_Fiend) d darkplaces: q1bsp trace bug: bullets don't hit walls at extremely steep angles, especially at very high framerates... d darkplaces: q1bsp trace bug: movetogoal is broken - monsters are not going around corners, just running into walls (scar3crow) d darkplaces: q1bsp trace bug: scrags frequently fly through ceilings - this needs to be fixed d darkplaces: q1bsp: parse submodels before leafs, so that the pvs can be allocated smaller (only enough for the world model's visleafs count) (Vic) d darkplaces: r_skyscroll1 and r_skyscroll2 cvars (Sajt) d darkplaces: reduce r_lightningbeam_repeatdistance to 128, 1024 is way too long d darkplaces: release new hmap (fixes compilation of TF entities for one person, adds support for GTKRadiant Q1Pack by adding -wadpath option) d darkplaces: release new hqbsp with -wadpath support (also searchs in map's directory and map's parent directory) d darkplaces: remove dead master server from default masters list (yummyluv) d darkplaces: remove frags per hour rating from scoreboard because it depends on cl.scores[i]->entertime (which is never set) d darkplaces: rename R_Model_Brush_ functions to R_Q1BSP_ d darkplaces: rename cl_fakelocalping_* to cl_netlocalping_* and *_fakepacketloss_* to *_netpacketloss_* d darkplaces: rename r_picmip and r_max_size and such to glquake names d darkplaces: rename r_shadow_polygonoffset and r_shadow_polygonfactor to r_shadow_shadow_polygonoffset and r_shadow_shadow_polygonfactor (Urre) d darkplaces: rename r_shadow_shadows to r_shadow_dlightshadows and add r_shadow_worldshadows (mashakos) d darkplaces: replace key system with twilight key system, note that this breaks existing mouse4 and mouse5 binds, and adds in_bindmap capability d darkplaces: restarting server with two people on it, hits the name change timer and thus people rejoin with blank names (romi) d darkplaces: revert noclip movement to match nq for compatibility with mods that trap movement as input (MauveBib) d darkplaces: safety checked lightmap access in Mod_Q1BSP_RecursiveLightPoint as one map Sajt uses was crashing (Sajt) d darkplaces: segfault reading memory in windows when starting a new server from menu (yummyluv) d darkplaces: server is starting before the "port" cvar is set by commandline and scripts? (yummyluv) d darkplaces: shadow volume rendering should not unlock the arrays between renders (Mercury) d darkplaces: support water lightmaps for use with hmap2 water lighting d darkplaces: tags support on md3 (Electro needs this urgently) d darkplaces: te_explosion2 builtin needs to be fixed, it is missing the colorlength parameter, update pr_cmds.c and dpextensions.qc (VorteX) d darkplaces: tenebrae dlights have reversed pitch (like v_angle, not model angles), make DP match this d darkplaces: tweak the blood decals in the particlefont to make them look more like the q2e_blood.avi video (Vermeulen) d darkplaces: typing ip in join game menu should show 'trying' and 'no response' after a while, or 'no network' if networking is not initialized (yummyluv) d darkplaces: update darkplaces readme d darkplaces: upgrade network protocol to send precise angles for entities, and make EF_LOWPRECISION downgrade both origin and angles, note this does not cover svc_setangle (Urre, Wazat for Battlemech, FrikaC, mashakos, RenegadeC, Sajt) d darkplaces: upgrade punchangle protocol to 16bit angles for smoother motion (Urre) d darkplaces: worked around Intel precision bug with view blends (they were not covering one line of the screen, due to being so huge that it had precision problems, on ATI and NVIDIA) (Sajt) d dpmaster: add a commandline option to dpmaster that remaps a server ip to another ip, so LordHavoc can make his server show up on his dpmaster d dpmaster: rename 'interface' variable so it compiles in MSVC, interface is a compiler keyword (Vic) d dpmod: add a "monsterwander" cvar and default it off, this would enable the spawnwanderpath code (Zombie13) d dpmod: add back charge-up on plasma rifle alt-fire and increase the max charge to 50 cells d dpmod: add back nails in walls, even if only in singleplayer (Zenex) d dpmod: add back tarbaby gibs (scar3crow) d dpmod: add combo kill detection; rapid burst of kills (Sajt) d dpmod: add flame thrower enforcers back (scar3crow) d dpmod: add flame thrower weapon, and make its altfire drop a canister of fuel (10 fuel units?), which can be ignited to set off as a bomb about the size of a rocket blast, plus some fireballs raining down (scar3crow) d dpmod: add frags for killing monsters in dpmod (scar3crow) d dpmod: add killing spree reporting; how many kills since spawn when you die, as well as announcing when you hit certain numbers of kills (Sajt) d dpmod: add q3bsp teleport target entity d dpmod: add rotfish to spawnmonsters code (only spawn if they land in water) (Zombie) d dpmod: add support for info_player_deathmatch in singleplayer for q3 compatibility (Static_Fiend) d dpmod: add target_position entity for a touch of q3 compatibility on jumppads (Static_Fiend) d dpmod: apparently can't fire in start.bsp? (scar3crow) d dpmod: change weapons 8-10 to lightning, plasma, plasma wave (joe hill) d dpmod: fix backpacks (giving no ammo) d dpmod: fix the plasma wave doing excessive damage at low framerates d dpmod: impulse 154 should cycle to deathmatch 7 (Rick) d dpmod: make a skill 4 mode where monsters are nearly invisible (alpha 0.2?) except when attacking or in pain d dpmod: make enforcers drop more cells for plasma gun (Sajt) d dpmod: make grapple off-hand (joe hill) d dpmod: make grunts reload less often, like every 10 shotgun shells (scar3crow) d dpmod: make tarbabies easier to kill? (Sajt) d dpmod: make tarbabies explode larger (Sajt) d dpmod: make the in-wall spikeballs only appear in developer 1 mode (Tomaz) d dpmod: modify anglemod to be able to recover from extremely large angles numbers (Zombie13) d dpmod: post new dpmod build. d dpmod: revert back to id1 weapons d dpmod: set oldorigin when spawning to prevent being stuck at the spawn from causing an instant teleport back to where you died (Sajt) d dpmod: switch to new Tomaz weapon models d dpmod: up nail limit to 500 (scar3crow) d dpmod: use Tomaz's ammo box models (Tomaz) d dpmod: why can't I pick up nails when I have no nailguns? and other similar pickup problems with weapons d dpzoo.map: colored lighting d dpzoo.map: rain d dpzoo.map: skybox d dpzoo.map: snow d dpzoo.map: transparent glass bmodels (DP_ENT_ALPHA) d feature darkplaces client extensions: DP_EF_NOSELFSHADOW extension (ChrisP) d feature darkplaces client: add .loc file support and say macros d feature darkplaces client: add BX_WAL_SUPPORT to extensions and document it, the feature has been in for a long time, also update wiki.quakesrc.org accordingly d feature darkplaces client: add a dot crosshair texture (HellToupee) d feature darkplaces client: add a sv_fixedframeratesingleplayer cvar (default off), to allow fixed framerate singleplayer mods, mainly useful for physics (Urre) d feature darkplaces client: add qw protocol support (making darkplaces work as a qwcl client) (Amish, Fuh) d feature darkplaces client: add showbrand cvar which would show gfx/brand.tga in the left/right top/bottom corner (depending on value of scr_showbrand) all the time, this would be useful for screenshots (Spirit_of_85) d feature darkplaces client: cl_capture_video avi support would be nice, the Intel(r) 4:2:0 codec seems to be standard on Windows XP so this should be easy d feature darkplaces client: make tab completion able to complete map names when using a map or changelevel command (Zenex, Eksess) d feature darkplaces client: mod browser (and ability to switch mods), this depends on "gamedir" cvar todo item (mashakos, FrikaC) d feature darkplaces client: play sound/misc/talk2.wav instead of sound/misc/talk.wav for team chat messages, and indicate team chat messages with () around the playre name, which is compatible with other engines (Yellow No. 5) d feature darkplaces client: query qw masters for server browser d feature darkplaces client: v_deathtilt cvar (Sajt, MauveBib) d feature darkplaces console: "toggle" console command present in doom3: toggle , and toggle (Dresk) d feature darkplaces console: add a "maps" command which takes the list from "dir maps/*.bsp" and prints the actual names of all the levels according to their worldspawn.message keys (RPG, Zenex, Eksess) d feature darkplaces console: add condump command to output recent console history (note: wordwrap will remain, trailing spaces will be stripped though), and add it to the readme (Edward Holness) d feature darkplaces console: change commandline history to clear the commandline when cursoring below the most recent history, and not allow cursoring back more than the oldest history (up2nogood) d feature darkplaces console: expand parameters such as $cvar to use the value of the cvar, DP_CON_EXPANDCVAR (up2nogood) d feature darkplaces console: make aliases given parameters insert the parameters in place of $1, $2, $* macros in the alias string, add this as DP_CON_ALIASPARAMETERS (up2nogood) d feature darkplaces csqc: add "pl" support in getplayerkey function (Dresk) d feature darkplaces csqc: add builtins to clientside qc for rendering arbitrary polygon meshes d feature darkplaces csqc: add clientside quakec (KrimZon, FrikaC) d feature darkplaces cvars: sort cvars and commands by name so that when saved to config they are sorted (might also be able to remove sorting from cvar/command listing) d feature darkplaces editlights: add coronasize setting to rtlights (romi) d feature darkplaces extensions: document DP_QC_UNLIMITEDTEMPSTRINGS extension explaining the new tempstring system and the prvm_tempstringmemory cvar, add a note to DP_QC_MULTIPLETEMPSTRINGS that it is superceded by DP_QC_UNLIMITEDTEMPSTRINGS when present d feature darkplaces filesystem: gamedir command to switch between mods, should be able to take multiple parameters to load multiple mods ontop of eachother like the -game commandline option can (FrikaC) d feature darkplaces init: add -demo option like -benchmark except playdemo instead of timedemo d feature darkplaces init: add -demolooponly option which makes escape key quit, and disables all other keys (Speedy) d feature darkplaces loader: support dpm models (Vermeulen) d feature darkplaces mac osx: add mac osx builds to build script (inertia, mwh) d feature darkplaces menu: add gl_picmip setting to graphics options menu, and an r_restart button (LordHavoc) d feature darkplaces menu: add lan searching to the server browser and related code (Vermeulen) d feature darkplaces menu: add some basic graphics/effects options profiles so that people can choose profiles like "Classic", "Modern", "Excessive", "Realistic", or any other profiles that make sense, may also need to reorganize the graphics/effects options menus to be a bit less confusing (Tron) d feature darkplaces networking: add "packet serverip:port command" command to send out of band packets, and hexdump the replies (Spike) d feature darkplaces networking: download individual files on demand from the server (Baker, CanadianSniper, Zop, Dresk, Chris) d feature darkplaces particles: reimplement quake effects for a cl_particles_quake mode (Mr Fribbles, metlslime) d feature darkplaces physics: add a sv_ cvar to disable demonland.wav when monsters fall, this would allow getting rid of the GAME_NEXUIZ check in that code d feature darkplaces playerphysics: add sv_maxairspeed cvar and use it in sv_user.c, default 30 to match quake player physics (Vermeulen) d feature darkplaces protocol: add "sendcvar " command which executes on clients and forwards a "sentcvar " to the server, which the qc can catch (Urre) d feature darkplaces protocol: add EF_DOUBLESIDED for double sided entity rendering (disable cull face for this entity) (yummyluv) d feature darkplaces protocol: add PRYDON_CLIENTCURSOR extension - clientside mouse with highlighting of selected entities with the EF_SELECTABLE flag, and qc fields on the client entity on the server would indicate which entity the cursor is highlighting as well as where it is (Urre, Harb, FrikaC) d feature darkplaces protocol: add back colormod extension (FrikaC, Uffe, Gilgamesh, Wazat) d feature darkplaces protocol: add buttons 9-16 (yummyluv) d feature darkplaces protocol: allow sending of additional precaches during game, this needs to send a reliable message to all connected clients stating the new filename to load, and also to be sent to new connections (VorteX, Vermeulen) d feature darkplaces renderer: add HalfLife2 style water rendering (Mitchell, Stribbs, Jimmy Freestone) d feature darkplaces renderer: add a nearclip cvar (Tomaz) d feature darkplaces renderer: add q3bsp water rendering, both scrolling and watershader (Zombie) d feature darkplaces renderer: add r_shadow_visiblelighting cvar which draws redish orange polygons similar to visiblevolumes for measuring number of light passes per pixel (Harbish) d feature darkplaces renderer: v_hwgamma 2 should force use of hardware gamma, ignoring failure return values, this might make fancy gamma ramps work on windows if the driver can bypass windows limitations d feature darkplaces server: add DP_QC_WRITEUNTERMINATEDSTRING extension (shadowalker) d feature darkplaces server: add DP_SV_PRINT extension d feature darkplaces server: add filename/line number reporting to progs stack and opcode printouts (Spike) d feature darkplaces server: add sv_playerphysicsqc cvar to allow engine to ignore SV_PlayerPhysics function, this would also have to change the reported extensions (Gleeb) d feature darkplaces server: automatically choose a server port if the bind fails, just keep incrementing the port until it finds an available port (tell Spike) d feature darkplaces server: finish DP_QC_BOTCLIENT extension docs and implement it (MauveBib, Supajoe) d feature darkplaces server: make a DP_SV_CUSTOMIZEENTITYFORCLIENT extension which calls a .float customizeentityforclient() function for each client that may see the entity, the function returns TRUE if it should send, FALSE if it should not, and is fully capable of editing the entity's fields, this allows cloaked players to appear less transparent to their teammates, navigation markers to only show to their team, etc (Urre, Supa, Wazat, SavageX, Vermeulen, Spike) d feature darkplaces sound: add a snd_soundradius cvar, default 1000 (Urre) d feature darkplaces sound: add snd_speed and snd_channels cvars (hyenur) d feature darkplaces sound: make Host_Shutdown clear sound buffer to avoid looping while quitting (up2nogood) d feature darkplaces video: add widescreen mode support, with 3 lists of resolutions in the menu based on aspect ratio setting, using this list http://www.deathmask.net/misc/widescreen.txt and figure out how to bias the fov based on aspect (Willis) d feature darkplaces: showfps should show spf when below 1fps (Sajt) d feature dpmodel: merge in jalisk0's patches for halflife2 smd import: http://www.quakesrc.org/forums/viewtopic.php?t=4731 d feature hmap2: make water have lightmaps (unless -nowaterlightmaps is specified) d feature lhfire: get lhfire_gui build from Tomaz d feature modeltools: add a makesp2 tool to make a very simple .sp2 sprite given a base name and frame size, the format is IDS2{} (Morphed) d feature zmodel: add "rotate" command to rotate around yaw (Vermeulen) d hmap2 -qbsp: degenerate edge error that occurs in mrinsane's newmap.map file, tyrqbsp does not have this problem (mrinsane) d hmap2 -vis: fix CompressVis bitbytes to be correct (Transfusion) d hmap2: add -ambientlight option, with warning that it does not produce a .light file (Harb) d hmap2: add -minlight option, with warning that it does not produce a .lights file (Harb) d hmap2: add a -harshshade option which would not have the 90 degrees incidence = grey hack seen in light.exe (Urre) d hmap2: add tyrlite compatible "delay" settings, with the interpretation of no specified delay being dependent on a -tyrlite option, and add a new type which is a sun light; light cast in a direction, from sky polygons or the void, these light types would warn that they disable .lights files d hmap2: light not properly figuring out the origin of rotating objects - it should take the "origin" key (FrikaC) d hmap2: make LoadBrush reject incomplete brushes - they produce bogus polygon boundaries (Tomaz) d hmap2: release hmap2 (Vic, Supajoe, Urre) d hmap2: report locations of lights which can not be vis optimized (Urre, FrikaC) d hmap2: tweak the light point generation a bit more to try to solve the 'corner light' glitch (Urre) d hmap2: update .bat files to use hmap2 name and remove -noreuse from revis.bat (Vic) d hmap: add support for GTKRadiant stuff d lhfire: post lhfire build with example scripts. d litsupport: fix the one COM_HunkFile call that uses two parameters (glquake took one) and fix the few "//lit support begin" messages at the end of code blocks (metlslime) d lmp2pcx: post new lmp2pcx build. d optimization darkplaces renderer: cache collision trace results for more performance in r_shadow_bouncegrid d optimization darkplaces renderer: initialize more lighting state in R_Shadow_Stage_Light to reduce per-surface overhead (LordHavoc) d optimization darkplaces renderer: rename r_shadow_glsl_geforcefxlowprecision to r_shadow_glsl_usehalffloat and enable it by default if the extension is present, it's about a 20% speed gain on GF6 compared to 5% on GFFX (SavageX) d optimization darkplaces server: optimize pvs checking by caching pvs cluster indices corresponding to entity box (Sajt) d revelation: fix bodies, they're standing due to invalid frame mappings (romi) d revelation: fix lingering glow on lightning deaths (romi) d revelation: reduce damage from weapons (romi) d sv_user.qc: figure out why looking up/down slows movement and fix it (Vermeulen) d zmodel: fix scale and origin commands (Vermeulen) f LordHavoc: examine .mb (Maya Binary) file from Electro and learn its format (Electro) f bug darkplaces capturevideo: cl_capturevideo 1 with sound off is not locking the framerate of a server (Vermeulen) f bug darkplaces client: decals are not sticking to submodels f bug darkplaces client: it has been reported that sometimes level changes on quakeworld servers don't load a map, this may be related to downloading? (Baker) f bug darkplaces client: occasionally when level changes on remote server, Host_Error occurs (LordHavoc) f bug darkplaces client: occasionally when level changes on remote server, connection stops and console scrolls wildly without user intervention, and it does not print any kind of error to the terminal, vid_restart in this state causes a crash (LordHavoc) f bug darkplaces client: pain flash seems to be framerate dependent? (Urre) f bug darkplaces client: when going through a teleporter the cl_movement prediction still interpolates the move (div0) f bug darkplaces crash: q3dm2 and q3dm11 crash (Stribbs) f bug darkplaces loader: occasional crash due to memory corruption when doing "deathmatch 1;map start" during demo loop (Willis) f bug darkplaces model loader: a q1 mdl file with a _1.tga but no _0.tga crashes at load (daemon) f bug darkplaces physics: GAME_TAOV: Vigil's movement isn't working properly, the qc uses MOVETYPE_STEP and clears FL_ONGROUND every frame and moves using velocity, this is causing a landing sound every frame and causing the player to slide down minor slopes very quickly, this did not occur in Quake, and seems that it must be related to a velocity_z check or FL_ONGROUND check in the MOVETYPE_STEP physics code (RenegadeC, xaGe) f bug darkplaces physics: figure out why monsters keep making fall pain sound after they've landed in dpmod (Cruaich) f bug darkplaces renderer: alias layers should have a shadow volume pass so that nodraw textures don't cast a shadow f bug darkplaces renderer: fix disappearing viewmodel (and other models) when in an unvised q3bsp, or partially inside a wall in q3bsp f bug darkplaces renderer: modify r_showtris_polygonoffset to push back all filled geometry, not lines, because polygonoffset will not affect GL_LINES at all f bug darkplaces renderer: r_editlights 1 causes crashes on level change 40% of the time? (romi) f bug darkplaces renderer: rtlight "style" values are broken, e1m6 trap hall for example (romi) f bug darkplaces renderer: showfps values 2 and 3 are printing bogus numbers like -2 billion (Willis) f bug darkplaces renderer: the quake logo shadow is missing in e1m5 rtlights, too much vis optimization... (romi) f bug darkplaces server: items still falling through the floor in nexuiz, and they seem to fall through more often at smaller sys_ticrate values such as 0.02 rather than 0.05 (GreEn`mArine) f bug darkplaces server: losing runes on episode completion, completing episode 1 then 2 then 3 causes it to forget 1, then 4 causes it to forget 2 and 3, making it impossible to open the boss gate (James D) f bug darkplaces server: player entered the game is printed twice, test with +map start f bug darkplaces sound: remove looping sounds when their owner entity has been removed by network code, this would mean that Nexuiz could have rocket/electro noise again - thought about this a bit more and can't do this (Qantourisc) f bug darkplaces: client's slowmo detection (measuring packet times and comparing to game time changes) may be making the game unpleasant (Wazat) f change darkplaces client: hardcode sbar image sizes so they can be replaced with higher quality images f darkplaces client: add chase_pitch cvar to control pitch angle of chase camera, and chase_angle cvar to control yaw angle of chase camera, and add back chase_right cvar (Electro) f darkplaces client: figure out why dlights are flashing on/off in TEU, particularly test the flashlight (Electro) f darkplaces client: fix view blends slightly lingering as time goes on, they should go away completely (Cruaich) f darkplaces loading: crash when progs/k_spike.mdl isn't found? (CheapAlert) f darkplaces physics: can't move when stuck in a monster (Sajt) f darkplaces physics: walking backward toward the cage in e4m2, it's 'sticky' (MoALTz) f darkplaces protocol: PROTOCOL_DARKPLACES5 not sending skin? (Sajt) f darkplaces protocol: add DP_EF_HIGHPRECISION to send float origins instead of shorts (VorteX) f darkplaces protocol: add EF_PARTICLESPAWNER extension (FrikaC, [TACO]) f darkplaces server: Mem_Alloc crash when entities are spawning, sv_main line 1760 (VorteX) f darkplaces server: add an extension to indicate that MOVETYPE_WALK works on non-clients (tell FrikaC) f darkplaces server: add automatic binding to whatever address the machine's hostname resolves to (in addition to 0.0.0.0); see original quake code for examples (yummyluv) f darkplaces testing: figure out BoxOnPlaneSide crash that happens in dpmod dpdm2 deathmatch 7 occasionally f darkplaces testing: figure out a workaround for broken gcc optimizers on BoxOnPlaneSide? (Diablo-D3) f darkplaces video: detect 5x4 video aspect ratio modes (such as 1280x1024) and set a 0.9375 pixel aspect (which is (1280/1024)/(1280/960)) when these modes are active? f darkplaces: add DP_EF_PRECISEANGLES extension (sends short angles instead of byte), failed because network protocol was upgraded by default (Wazat for Battlemech, FrikaC, mashakos, RenegadeC, Sajt) f darkplaces: add _0.tga support (per texture) to bsp/md2/md3 loaders f darkplaces: add a loading screen (gfx/loadback.tga or the loading plaque if that's not found) before loading commences so that people have something to look at when the engine starts... (Sajt) f darkplaces: add a new TE_TELEPORTSHELL effect which would take an entity and create a fading plasma shell of its model at the moment of teleportation (tell fuh and Mercury about this) f darkplaces: add another TE_TELEPORT effect that spawns particles at a model's vertices (Urre) f darkplaces: add crude DML model loading with animation list (ask Riot for dml library) (Mitchell) f darkplaces: change particle() macro in cl_particles.c to have a do{}while(0) to eat the ; f darkplaces: client crashes on +button8? (Static_Fiend) f darkplaces: crashes on radeon in rare situations that seem to occur in dpmod dm 7 mode? (Option42) f darkplaces: crosshair_size 0 draws incorrectly (Sajt) f darkplaces: document how polygon collision works in the code (KrimZon) f darkplaces: examine proquake code to find nat fix and implement similar in darkplaces f darkplaces: figure out and fix network entity protocol bugs (sublim3) f darkplaces: figure out what crashes when this sequence is done: r_speeds 1;map anything, crash (Stribbs) f darkplaces: figure out why bmodels aren't receiving lightmap dlights f darkplaces: fix colormapping (Demonix) f darkplaces: fix connecting to proquake servers through routers (Demonix) f darkplaces: fix sound resampling to not assume sound ends with value 0, and add support for passing in start and end times, as doubles, so that it can handle arbitrary mixing alignments f darkplaces: hack PF_nextent to skip inaccessible client slots depending on maxplayers - but always report ones that are active at the time (FrikaC) f darkplaces: hitting capslock and tab at the same time segfaults f darkplaces: look at and integrate Vic's updated zone.[ch] (Vic) f darkplaces: make a flag for rtlights that makes them appear in normal mode (not just r_shadow_realtime_world mode) (Vermeulen) f darkplaces: model interpolation off crashes? (Sajt) f darkplaces: pointcontents crash when building harvester in gvb2? (yummyluv) f darkplaces: r_shadow_showtris messes up r_shadow_visiblevolumes color (jitspoe) f darkplaces: send bmodels even if alpha is 0 or EF_NODRAW is on f darkplaces: shadows are not working with model tag attachments (Electro) f darkplaces: should add quake3 shader support even though the language is utterly insane f dpmod: figure out why the dbsg isn't selectable in deathmatch 7 mode f dpmod: make tarbabies have a self.resist_explosive = 3; like zombies (Sajt) f feature darkplaces client: add back cl_particles_lighting cvar and add back the particle lighting (romi) f feature darkplaces renderer: add cubemap reflections like UT2003 somehow (perhaps entities would define the reflection maps for rooms, and a water entity would take care of the rest?) f feature darkplaces server: add an extension to check if a file exists outside the data directory, FRIK_FILE can do this but only inside data directory (Error) f feature dpmod: include .lit and .dlit files for all id1 maps - this idea was rejected due to download size f feature dpmod: include .vis files for all id1 maps - this idea rejected due to lack of .vis support and download size f hqbsp: CreateBrushFaces should use RadiusFromBounds for its rotation box code, but hmap is obsolete (Vic) f optimization darkplaces renderer: change water distortion textures from multiple 2D textures to one 3D texture for smoother animation (Tomaz) f optimization darkplaces visibility: R_Q3BSP_RecursiveWorldNode should take clipflags parameter and do not cull a node against a plane if the parent node is totally on one side of the plane (Vic) darkplaces/screen.h0000664000175000017500000000464413067716222013625 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // screen.h #ifndef SCREEN_H #define SCREEN_H void CL_Screen_Init (void); void CL_UpdateScreen (void); void SCR_CenterPrint(const char *str); void SCR_BeginLoadingPlaque (qboolean startup); // invoke refresh of loading plaque (nothing else seen) void SCR_UpdateLoadingScreen(qboolean clear, qboolean startup); void SCR_UpdateLoadingScreenIfShown(void); // pushes an item on the loading screen void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent); void SCR_PopLoadingScreen (qboolean redraw); void SCR_ClearLoadingScreen (qboolean redraw); extern float scr_con_current; // current height of displayed console extern int sb_lines; extern cvar_t scr_viewsize; extern cvar_t scr_fov; extern cvar_t showfps; extern cvar_t showtime; extern cvar_t showdate; extern cvar_t crosshair; extern cvar_t crosshair_size; extern cvar_t scr_conalpha; extern cvar_t scr_conalphafactor; extern cvar_t scr_conalpha2factor; extern cvar_t scr_conalpha3factor; extern cvar_t scr_conscroll_x; extern cvar_t scr_conscroll_y; extern cvar_t scr_conscroll2_x; extern cvar_t scr_conscroll2_y; extern cvar_t scr_conscroll3_x; extern cvar_t scr_conscroll3_y; extern cvar_t scr_conbrightness; extern cvar_t r_letterbox; extern cvar_t scr_refresh; extern cvar_t scr_stipple; extern cvar_t r_stereo_separation; extern cvar_t r_stereo_angle; qboolean R_Stereo_Active(void); extern int r_stereo_side; typedef struct scr_touchscreenarea_s { const char *pic; const char *text; float rect[4]; float textheight; float active; float activealpha; float inactivealpha; } scr_touchscreenarea_t; // FIXME: should resize dynamically? extern int scr_numtouchscreenareas; extern scr_touchscreenarea_t scr_touchscreenareas[128]; #endif darkplaces/csprogs.c0000664000175000017500000012204113067716216014014 0ustar kalevkalev#include "quakedef.h" #include "progsvm.h" #include "clprogdefs.h" #include "csprogs.h" #include "cl_collision.h" #include "snd_main.h" #include "clvm_cmds.h" #include "prvm_cmds.h" //============================================================================ // Client prog handling //[515]: omg !!! optimize it ! a lot of hacks here and there also :P #define CSQC_RETURNVAL prog->globals.fp[OFS_RETURN] #define CSQC_BEGIN #define CSQC_END void CL_VM_PreventInformationLeaks(void) { prvm_prog_t *prog = CLVM_prog; if(!cl.csqc_loaded) return; CSQC_BEGIN VM_ClearTraceGlobals(prog); PRVM_clientglobalfloat(trace_networkentity) = 0; CSQC_END } //[515]: these are required funcs static const char *cl_required_func[] = { "CSQC_Init", "CSQC_InputEvent", "CSQC_UpdateView", "CSQC_ConsoleCommand", }; static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*); #define CL_REQFIELDS (sizeof(cl_reqfields) / sizeof(prvm_required_field_t)) prvm_required_field_t cl_reqfields[] = { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) {ev_float, #x }, #define PRVM_DECLARE_clientfieldvector(x) {ev_vector, #x }, #define PRVM_DECLARE_clientfieldstring(x) {ev_string, #x }, #define PRVM_DECLARE_clientfieldedict(x) {ev_entity, #x }, #define PRVM_DECLARE_clientfieldfunction(x) {ev_function, #x }, #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; #define CL_REQGLOBALS (sizeof(cl_reqglobals) / sizeof(prvm_required_field_t)) prvm_required_field_t cl_reqglobals[] = { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) {ev_float, #x}, #define PRVM_DECLARE_clientglobalvector(x) {ev_vector, #x}, #define PRVM_DECLARE_clientglobalstring(x) {ev_string, #x}, #define PRVM_DECLARE_clientglobaledict(x) {ev_entity, #x}, #define PRVM_DECLARE_clientglobalfunction(x) {ev_function, #x}, #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin) { prvm_prog_t *prog = CLVM_prog; if(cl.csqc_loaded) { CSQC_BEGIN PRVM_clientglobalfloat(dmg_take) = dmg_take; PRVM_clientglobalfloat(dmg_save) = dmg_save; VectorCopy(dmg_origin, PRVM_clientglobalvector(dmg_origin)); CSQC_END } } void CSQC_UpdateNetworkTimes(double newtime, double oldtime) { prvm_prog_t *prog = CLVM_prog; if(!cl.csqc_loaded) return; CSQC_BEGIN PRVM_clientglobalfloat(servertime) = newtime; PRVM_clientglobalfloat(serverprevtime) = oldtime; PRVM_clientglobalfloat(serverdeltatime) = newtime - oldtime; CSQC_END } //[515]: set globals before calling R_UpdateView, WEIRD CRAP static void CSQC_SetGlobals (double frametime) { vec3_t pmove_org; prvm_prog_t *prog = CLVM_prog; CSQC_BEGIN PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobalfloat(cltime) = realtime; // Spike named it that way. PRVM_clientglobalfloat(frametime) = frametime; PRVM_clientglobalfloat(servercommandframe) = cls.servermovesequence; PRVM_clientglobalfloat(clientcommandframe) = cl.movecmd[0].sequence; VectorCopy(cl.viewangles, PRVM_clientglobalvector(input_angles)); // // FIXME: this actually belongs into getinputstate().. [12/17/2007 Black] PRVM_clientglobalfloat(input_buttons) = cl.movecmd[0].buttons; VectorSet(PRVM_clientglobalvector(input_movevalues), cl.movecmd[0].forwardmove, cl.movecmd[0].sidemove, cl.movecmd[0].upmove); VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin); VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles); // LordHavoc: Spike says not to do this, but without pmove_org the // CSQC is useless as it can't alter the view origin without // completely replacing it Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, pmove_org); VectorCopy(pmove_org, PRVM_clientglobalvector(pmove_org)); VectorCopy(cl.movement_velocity, PRVM_clientglobalvector(pmove_vel)); PRVM_clientglobalfloat(pmove_onground) = cl.onground; PRVM_clientglobalfloat(pmove_inwater) = cl.inwater; VectorCopy(cl.viewangles, PRVM_clientglobalvector(view_angles)); VectorCopy(cl.punchangle, PRVM_clientglobalvector(view_punchangle)); VectorCopy(cl.punchvector, PRVM_clientglobalvector(view_punchvector)); PRVM_clientglobalfloat(maxclients) = cl.maxclients; PRVM_clientglobalfloat(player_localentnum) = cl.viewentity; CSQC_R_RecalcView(); CSQC_END } void CSQC_Predraw (prvm_edict_t *ed) { prvm_prog_t *prog = CLVM_prog; int b; if(!PRVM_clientedictfunction(ed, predraw)) return; b = PRVM_clientglobaledict(self); PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, predraw), "CSQC_Predraw: NULL function\n"); PRVM_clientglobaledict(self) = b; } void CSQC_Think (prvm_edict_t *ed) { prvm_prog_t *prog = CLVM_prog; int b; if(PRVM_clientedictfunction(ed, think)) if(PRVM_clientedictfloat(ed, nextthink) && PRVM_clientedictfloat(ed, nextthink) <= PRVM_clientglobalfloat(time)) { PRVM_clientedictfloat(ed, nextthink) = 0; b = PRVM_clientglobaledict(self); PRVM_clientglobaledict(self) = PRVM_EDICT_TO_PROG(ed); prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, think), "CSQC_Think: NULL function\n"); PRVM_clientglobaledict(self) = b; } } extern cvar_t cl_noplayershadow; extern cvar_t r_equalize_entities_fullbright; qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum) { prvm_prog_t *prog = CLVM_prog; int renderflags; int c; float scale; entity_render_t *entrender; dp_model_t *model; model = CL_GetModelFromEdict(ed); if (!model) return false; if (edictnum) { if (r_refdef.scene.numentities >= r_refdef.scene.maxentities) return false; entrender = cl.csqcrenderentities + edictnum; r_refdef.scene.entities[r_refdef.scene.numentities++] = entrender; entrender->entitynumber = edictnum + MAX_EDICTS; //entrender->shadertime = 0; // shadertime was set by spawn() entrender->flags = 0; entrender->effects = 0; entrender->alpha = 1; entrender->scale = 1; VectorSet(entrender->colormod, 1, 1, 1); VectorSet(entrender->glowmod, 1, 1, 1); entrender->allowdecals = true; } else { entrender = CL_NewTempEntity(0); if (!entrender) return false; } entrender->userwavefunc_param[0] = PRVM_clientedictfloat(ed, userwavefunc_param0); entrender->userwavefunc_param[1] = PRVM_clientedictfloat(ed, userwavefunc_param1); entrender->userwavefunc_param[2] = PRVM_clientedictfloat(ed, userwavefunc_param2); entrender->userwavefunc_param[3] = PRVM_clientedictfloat(ed, userwavefunc_param3); entrender->model = model; entrender->skinnum = (int)PRVM_clientedictfloat(ed, skin); entrender->effects |= entrender->model->effects; renderflags = (int)PRVM_clientedictfloat(ed, renderflags); entrender->alpha = PRVM_clientedictfloat(ed, alpha); entrender->scale = scale = PRVM_clientedictfloat(ed, scale); VectorCopy(PRVM_clientedictvector(ed, colormod), entrender->colormod); VectorCopy(PRVM_clientedictvector(ed, glowmod), entrender->glowmod); if(PRVM_clientedictfloat(ed, effects)) entrender->effects |= (int)PRVM_clientedictfloat(ed, effects); if (!entrender->alpha) entrender->alpha = 1.0f; if (!entrender->scale) entrender->scale = scale = 1.0f; if (!VectorLength2(entrender->colormod)) VectorSet(entrender->colormod, 1, 1, 1); if (!VectorLength2(entrender->glowmod)) VectorSet(entrender->glowmod, 1, 1, 1); // LordHavoc: use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad) CL_GetTagMatrix(prog, &entrender->matrix, ed, 0); // set up the animation data VM_GenerateFrameGroupBlend(prog, ed->priv.server->framegroupblend, ed); VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model, cl.time); VM_UpdateEdictSkeleton(prog, ed, model, ed->priv.server->frameblend); if (PRVM_clientedictfloat(ed, shadertime)) // hack for csprogs.dat files that do not set shadertime, leaves the value at entity spawn time entrender->shadertime = PRVM_clientedictfloat(ed, shadertime); // transparent offset if (renderflags & RF_USETRANSPARENTOFFSET) entrender->transparent_offset = PRVM_clientglobalfloat(transparent_offset); // model light if (renderflags & RF_MODELLIGHT) { if (PRVM_clientedictvector(ed, modellight_ambient)) VectorCopy(PRVM_clientedictvector(ed, modellight_ambient), entrender->modellight_ambient); else VectorClear(entrender->modellight_ambient); if (PRVM_clientedictvector(ed, modellight_diffuse)) VectorCopy(PRVM_clientedictvector(ed, modellight_diffuse), entrender->modellight_diffuse); else VectorClear(entrender->modellight_diffuse); if (PRVM_clientedictvector(ed, modellight_dir)) VectorCopy(PRVM_clientedictvector(ed, modellight_dir), entrender->modellight_lightdir); else VectorClear(entrender->modellight_lightdir); entrender->flags |= RENDER_CUSTOMIZEDMODELLIGHT; } if(renderflags) { if(renderflags & RF_VIEWMODEL) entrender->flags |= RENDER_VIEWMODEL | RENDER_NODEPTHTEST; if(renderflags & RF_EXTERNALMODEL) entrender->flags |= RENDER_EXTERIORMODEL; if(renderflags & RF_WORLDOBJECT) entrender->flags |= RENDER_WORLDOBJECT; if(renderflags & RF_DEPTHHACK) entrender->flags |= RENDER_NODEPTHTEST; if(renderflags & RF_ADDITIVE) entrender->flags |= RENDER_ADDITIVE; if(renderflags & RF_DYNAMICMODELLIGHT) entrender->flags |= RENDER_DYNAMICMODELLIGHT; } c = (int)PRVM_clientedictfloat(ed, colormap); if (c <= 0) CL_SetEntityColormapColors(entrender, -1); else if (c <= cl.maxclients && cl.scores != NULL) CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors); else CL_SetEntityColormapColors(entrender, c); entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW); // either fullbright or lit if(!r_fullbright.integer) { if (!(entrender->effects & EF_FULLBRIGHT) && !(renderflags & RF_FULLBRIGHT)) entrender->flags |= RENDER_LIGHT; else if(r_equalize_entities_fullbright.integer) entrender->flags |= RENDER_LIGHT | RENDER_EQUALIZE; } // hide player shadow during intermission or nehahra movie if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (entrender->alpha >= 1) && !(renderflags & RF_NOSHADOW) && !(entrender->flags & RENDER_VIEWMODEL) && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer))) entrender->flags |= RENDER_SHADOW; if (entrender->flags & RENDER_VIEWMODEL) entrender->flags |= RENDER_NOSELFSHADOW; if (entrender->effects & EF_NOSELFSHADOW) entrender->flags |= RENDER_NOSELFSHADOW; if (entrender->effects & EF_NODEPTHTEST) entrender->flags |= RENDER_NODEPTHTEST; if (entrender->effects & EF_ADDITIVE) entrender->flags |= RENDER_ADDITIVE; if (entrender->effects & EF_DOUBLESIDED) entrender->flags |= RENDER_DOUBLESIDED; if (entrender->effects & EF_DYNAMICMODELLIGHT) entrender->flags |= RENDER_DYNAMICMODELLIGHT; // make the other useful stuff memcpy(entrender->framegroupblend, ed->priv.server->framegroupblend, sizeof(ed->priv.server->framegroupblend)); CL_UpdateRenderEntity(entrender); // override animation data with full control memcpy(entrender->frameblend, ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); if (ed->priv.server->skeleton.relativetransforms) entrender->skeleton = &ed->priv.server->skeleton; else entrender->skeleton = NULL; return true; } // 0 = keydown, key, character (EXT_CSQC) // 1 = keyup, key, character (EXT_CSQC) // 2 = mousemove relative, x, y (EXT_CSQC) // 3 = mousemove absolute, x, y (DP_CSQC) qboolean CL_VM_InputEvent (int eventtype, float x, float y) { prvm_prog_t *prog = CLVM_prog; qboolean r; if(!cl.csqc_loaded) return false; CSQC_BEGIN if (!PRVM_clientfunction(CSQC_InputEvent)) r = false; else { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; PRVM_G_FLOAT(OFS_PARM0) = eventtype; PRVM_G_FLOAT(OFS_PARM1) = x; // key or x PRVM_G_FLOAT(OFS_PARM2) = y; // ascii or y prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_InputEvent), "QC function CSQC_InputEvent is missing"); r = CSQC_RETURNVAL != 0; } CSQC_END return r; } extern r_refdef_view_t csqc_original_r_refdef_view; extern r_refdef_view_t csqc_main_r_refdef_view; qboolean CL_VM_UpdateView (double frametime) { prvm_prog_t *prog = CLVM_prog; vec3_t emptyvector; emptyvector[0] = 0; emptyvector[1] = 0; emptyvector[2] = 0; // vec3_t oldangles; if(!cl.csqc_loaded) return false; R_TimeReport("pre-UpdateView"); CSQC_BEGIN r_refdef.view.ismain = true; csqc_original_r_refdef_view = r_refdef.view; csqc_main_r_refdef_view = r_refdef.view; //VectorCopy(cl.viewangles, oldangles); PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; CSQC_SetGlobals(frametime); // clear renderable entity and light lists to prevent crashes if the // CSQC_UpdateView function does not call R_ClearScene as it should r_refdef.scene.numentities = 0; r_refdef.scene.numlights = 0; // pass in width and height as parameters (EXT_CSQC_1) PRVM_G_FLOAT(OFS_PARM0) = vid.width; PRVM_G_FLOAT(OFS_PARM1) = vid.height; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_UpdateView), "QC function CSQC_UpdateView is missing"); //VectorCopy(oldangles, cl.viewangles); // Dresk : Reset Dmg Globals Here CL_VM_UpdateDmgGlobals(0, 0, emptyvector); r_refdef.view = csqc_main_r_refdef_view; R_RenderView_UpdateViewVectors(); // we have to do this, as we undid the scene render doing this for us CSQC_END R_TimeReport("UpdateView"); return true; } qboolean CL_VM_ConsoleCommand (const char *cmd) { prvm_prog_t *prog = CLVM_prog; int restorevm_tempstringsbuf_cursize; qboolean r = false; if(!cl.csqc_loaded) return false; CSQC_BEGIN if (PRVM_clientfunction(CSQC_ConsoleCommand)) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, cmd); prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_ConsoleCommand), "QC function CSQC_ConsoleCommand is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; r = CSQC_RETURNVAL != 0; } CSQC_END return r; } qboolean CL_VM_Parse_TempEntity (void) { prvm_prog_t *prog = CLVM_prog; int t; qboolean r = false; if(!cl.csqc_loaded) return false; CSQC_BEGIN if(PRVM_clientfunction(CSQC_Parse_TempEntity)) { t = cl_message.readcount; PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_TempEntity), "QC function CSQC_Parse_TempEntity is missing"); r = CSQC_RETURNVAL != 0; if(!r) { cl_message.readcount = t; cl_message.badread = false; } } CSQC_END return r; } void CL_VM_Parse_StuffCmd (const char *msg) { prvm_prog_t *prog = CLVM_prog; int restorevm_tempstringsbuf_cursize; if(msg[0] == 'c') if(msg[1] == 's') if(msg[2] == 'q') if(msg[3] == 'c') { // if this is setting a csqc variable, deprotect csqc_progcrc // temporarily so that it can be set by the cvar command, // and then reprotect it afterwards int crcflags = csqc_progcrc.flags; int sizeflags = csqc_progcrc.flags; csqc_progcrc.flags &= ~CVAR_READONLY; csqc_progsize.flags &= ~CVAR_READONLY; Cmd_ExecuteString (msg, src_command, true); csqc_progcrc.flags = crcflags; csqc_progsize.flags = sizeflags; return; } if(cls.demoplayback) if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55)) { // special handling for map download commands // run these commands IMMEDIATELY, instead of waiting for a client frame // that way, there is no black screen when playing back demos // I know this is a really ugly hack, but I can't think of any better way // FIXME find the actual CAUSE of this, and make demo playback WAIT // until all maps are loaded, then remove this hack char buf[MAX_INPUTLINE]; const char *p, *q; size_t l; p = msg; for(;;) { q = strchr(p, '\n'); if(q) l = q - p; else l = strlen(p); if(l > sizeof(buf) - 1) l = sizeof(buf) - 1; strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline! Cmd_ExecuteString(buf, src_command, true); p += l; if(*p == '\n') ++p; // skip the newline and continue else break; // end of string or overflow } Cmd_ExecuteString("curl --clear_autodownload", src_command, true); // don't inhibit CSQC loading return; } if(!cl.csqc_loaded) { Cbuf_AddText(msg); return; } CSQC_BEGIN if(PRVM_clientfunction(CSQC_Parse_StuffCmd)) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_StuffCmd), "QC function CSQC_Parse_StuffCmd is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } else Cbuf_AddText(msg); CSQC_END } static void CL_VM_Parse_Print (const char *msg) { prvm_prog_t *prog = CLVM_prog; int restorevm_tempstringsbuf_cursize; PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_Print), "QC function CSQC_Parse_Print is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } void CSQC_AddPrintText (const char *msg) { prvm_prog_t *prog = CLVM_prog; size_t i; if(!cl.csqc_loaded) { Con_Print(msg); return; } CSQC_BEGIN if(PRVM_clientfunction(CSQC_Parse_Print)) { // FIXME: is this bugged? i = strlen(msg)-1; if(msg[i] != '\n' && msg[i] != '\r') { if(strlen(cl.csqc_printtextbuf)+i >= MAX_INPUTLINE) { CL_VM_Parse_Print(cl.csqc_printtextbuf); cl.csqc_printtextbuf[0] = 0; } else strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); return; } strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE); CL_VM_Parse_Print(cl.csqc_printtextbuf); cl.csqc_printtextbuf[0] = 0; } else Con_Print(msg); CSQC_END } void CL_VM_Parse_CenterPrint (const char *msg) { prvm_prog_t *prog = CLVM_prog; int restorevm_tempstringsbuf_cursize; if(!cl.csqc_loaded) { SCR_CenterPrint(msg); return; } CSQC_BEGIN if(PRVM_clientfunction(CSQC_Parse_CenterPrint)) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(prog, msg); prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Parse_CenterPrint), "QC function CSQC_Parse_CenterPrint is missing"); prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } else SCR_CenterPrint(msg); CSQC_END } void CL_VM_UpdateIntermissionState (int intermission) { prvm_prog_t *prog = CLVM_prog; if(cl.csqc_loaded) { CSQC_BEGIN PRVM_clientglobalfloat(intermission) = intermission; CSQC_END } } void CL_VM_UpdateShowingScoresState (int showingscores) { prvm_prog_t *prog = CLVM_prog; if(cl.csqc_loaded) { CSQC_BEGIN PRVM_clientglobalfloat(sb_showscores) = showingscores; CSQC_END } } qboolean CL_VM_Event_Sound(int sound_num, float fvolume, int channel, float attenuation, int ent, vec3_t pos, int flags, float speed) { prvm_prog_t *prog = CLVM_prog; qboolean r = false; if(cl.csqc_loaded) { CSQC_BEGIN if(PRVM_clientfunction(CSQC_Event_Sound)) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; PRVM_G_FLOAT(OFS_PARM0) = ent; PRVM_G_FLOAT(OFS_PARM1) = CHAN_ENGINE2USER(channel); PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(prog, cl.sound_name[sound_num] ); PRVM_G_FLOAT(OFS_PARM3) = fvolume; PRVM_G_FLOAT(OFS_PARM4) = attenuation; VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) ); PRVM_G_FLOAT(OFS_PARM6) = speed * 100.0f; PRVM_G_FLOAT(OFS_PARM7) = flags; // flags prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Event_Sound), "QC function CSQC_Event_Sound is missing"); r = CSQC_RETURNVAL != 0; } CSQC_END } return r; } static void CL_VM_UpdateCoopDeathmatchGlobals (int gametype) { prvm_prog_t *prog = CLVM_prog; // Avoid global names for clean(er) coding int localcoop; int localdeathmatch; if(cl.csqc_loaded) { if(gametype == GAME_COOP) { localcoop = 1; localdeathmatch = 0; } else if(gametype == GAME_DEATHMATCH) { localcoop = 0; localdeathmatch = 1; } else { // How did the ServerInfo send an unknown gametype? // Better just assign the globals as 0... localcoop = 0; localdeathmatch = 0; } CSQC_BEGIN PRVM_clientglobalfloat(coop) = localcoop; PRVM_clientglobalfloat(deathmatch) = localdeathmatch; CSQC_END } } #if 0 static float CL_VM_Event (float event) //[515]: needed ? I'd say "YES", but don't know for what :D { prvm_prog_t *prog = CLVM_prog; float r = 0; if(!cl.csqc_loaded) return 0; CSQC_BEGIN if(PRVM_clientfunction(CSQC_Event)) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[cl.playerentity]; PRVM_G_FLOAT(OFS_PARM0) = event; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Event), "QC function CSQC_Event is missing"); r = CSQC_RETURNVAL; } CSQC_END return r; } #endif void CSQC_ReadEntities (void) { prvm_prog_t *prog = CLVM_prog; unsigned short entnum, oldself, realentnum; if(!cl.csqc_loaded) { Host_Error ("CSQC_ReadEntities: CSQC is not loaded"); return; } CSQC_BEGIN PRVM_clientglobalfloat(time) = cl.time; oldself = PRVM_clientglobaledict(self); while(1) { entnum = MSG_ReadShort(&cl_message); if(!entnum || cl_message.badread) break; realentnum = entnum & 0x7FFF; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum]; if(entnum & 0x8000) { if(PRVM_clientglobaledict(self)) { prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Remove), "QC function CSQC_Ent_Remove is missing"); cl.csqc_server2csqcentitynumber[realentnum] = 0; } else { // LordHavoc: removing an entity that is already gone on // the csqc side is possible for legitimate reasons (such // as a repeat of the remove message), so no warning is // needed //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ? } } else { if(!PRVM_clientglobaledict(self)) { if(!PRVM_clientfunction(CSQC_Ent_Spawn)) { prvm_edict_t *ed; ed = PRVM_ED_Alloc(prog); PRVM_clientedictfloat(ed, entnum) = realentnum; PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed); } else { // entity( float entnum ) CSQC_Ent_Spawn; // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas] PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum; // make sure no one gets wrong ideas PRVM_clientglobaledict(self) = 0; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Spawn), "QC function CSQC_Ent_Spawn is missing"); PRVM_clientglobaledict(self) = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) ); } PRVM_G_FLOAT(OFS_PARM0) = 1; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); } else { PRVM_G_FLOAT(OFS_PARM0) = 0; prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Ent_Update), "QC function CSQC_Ent_Update is missing"); } } } PRVM_clientglobaledict(self) = oldself; CSQC_END } static void CLVM_begin_increase_edicts(prvm_prog_t *prog) { // links don't survive the transition, so unlink everything World_UnlinkAll(&cl.world); } static void CLVM_end_increase_edicts(prvm_prog_t *prog) { int i; prvm_edict_t *ent; // link every entity except world for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) if (!ent->priv.server->free) CL_LinkEdict(ent); } static void CLVM_init_edict(prvm_prog_t *prog, prvm_edict_t *e) { int edictnum = PRVM_NUM_FOR_EDICT(e); entity_render_t *entrender; CL_ExpandCSQCRenderEntities(edictnum); entrender = cl.csqcrenderentities + edictnum; e->priv.server->move = false; // don't move on first frame memset(entrender, 0, sizeof(*entrender)); entrender->shadertime = cl.time; } static void CLVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) { entity_render_t *entrender = cl.csqcrenderentities + PRVM_NUM_FOR_EDICT(ed); R_DecalSystem_Reset(&entrender->decalsystem); memset(entrender, 0, sizeof(*entrender)); World_UnlinkEdict(ed); memset(ed->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); VM_RemoveEdictSkeleton(prog, ed); World_Physics_RemoveFromEntity(&cl.world, ed); World_Physics_RemoveJointFromEntity(&cl.world, ed); } static void CLVM_count_edicts(prvm_prog_t *prog) { int i; prvm_edict_t *ent; int active = 0, models = 0, solid = 0; for (i=0 ; inum_edicts ; i++) { ent = PRVM_EDICT_NUM(i); if (ent->priv.server->free) continue; active++; if (PRVM_clientedictfloat(ent, solid)) solid++; if (PRVM_clientedictstring(ent, model)) models++; } Con_Printf("num_edicts:%3i\n", prog->num_edicts); Con_Printf("active :%3i\n", active); Con_Printf("view :%3i\n", models); Con_Printf("touch :%3i\n", solid); } static qboolean CLVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) { return true; } // returns true if the packet is valid, false if end of file is reached // used for dumping the CSQC download into demo files qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol) { int packetsize = buf->maxsize - 7; // byte short long int npackets = ((int)len + packetsize - 1) / (packetsize); char vabuf[1024]; if(protocol == PROTOCOL_QUAKEWORLD) return false; // CSQC can't run in QW anyway SZ_Clear(buf); if(cnt == 0) { MSG_WriteByte(buf, svc_stufftext); MSG_WriteString(buf, va(vabuf, sizeof(vabuf), "\ncl_downloadbegin %lu %s\n", (unsigned long)len, filename)); return true; } else if(cnt >= 1 && cnt <= npackets) { unsigned long thispacketoffset = (cnt - 1) * packetsize; int thispacketsize = (int)len - thispacketoffset; if(thispacketsize > packetsize) thispacketsize = packetsize; MSG_WriteByte(buf, svc_downloaddata); MSG_WriteLong(buf, thispacketoffset); MSG_WriteShort(buf, thispacketsize); SZ_Write(buf, data + thispacketoffset, thispacketsize); return true; } else if(cnt == npackets + 1) { MSG_WriteByte(buf, svc_stufftext); MSG_WriteString(buf, va(vabuf, sizeof(vabuf), "\ncl_downloadfinished %lu %d\n", (unsigned long)len, crc)); return true; } return false; } extern cvar_t csqc_usedemoprogs; void CL_VM_Init (void) { prvm_prog_t *prog = CLVM_prog; const char* csprogsfn = NULL; unsigned char *csprogsdata = NULL; fs_offset_t csprogsdatasize = 0; int csprogsdatacrc, requiredcrc; int requiredsize; char vabuf[1024]; // reset csqc_progcrc after reading it, so that changing servers doesn't // expect csqc on the next server requiredcrc = csqc_progcrc.integer; requiredsize = csqc_progsize.integer; Cvar_SetValueQuick(&csqc_progcrc, -1); Cvar_SetValueQuick(&csqc_progsize, -1); // if the server is not requesting a csprogs, then we're done here if (requiredcrc < 0) return; // see if the requested csprogs.dat file matches the requested crc if (!cls.demoplayback || csqc_usedemoprogs.integer) { csprogsfn = va(vabuf, sizeof(vabuf), "dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc); if(cls.caughtcsprogsdata && cls.caughtcsprogsdatasize == requiredsize && CRC_Block(cls.caughtcsprogsdata, (size_t)cls.caughtcsprogsdatasize) == requiredcrc) { Con_DPrintf("Using buffered \"%s\"\n", csprogsfn); csprogsdata = cls.caughtcsprogsdata; csprogsdatasize = cls.caughtcsprogsdatasize; cls.caughtcsprogsdata = NULL; cls.caughtcsprogsdatasize = 0; } else { Con_DPrintf("Not using buffered \"%s\" (buffered: %p, %d)\n", csprogsfn, cls.caughtcsprogsdata, (int) cls.caughtcsprogsdatasize); csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); } } if (!csprogsdata) { csprogsfn = csqc_progname.string; csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize); } if (csprogsdata) { csprogsdatacrc = CRC_Block(csprogsdata, (size_t)csprogsdatasize); if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize) { if (cls.demoplayback) { Con_Printf("^1Warning: Your %s is not the same version as the demo was recorded with (CRC/size are %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); // Mem_Free(csprogsdata); // return; // We WANT to continue here, and play the demo with different csprogs! // After all, this is just a warning. Sure things may go wrong from here. } else { Mem_Free(csprogsdata); Con_Printf("^1Your %s is not the same version as the server (CRC is %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize); CL_Disconnect(); return; } } } else { if (requiredcrc >= 0) { if (cls.demoplayback) Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); else Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string); CL_Disconnect(); } return; } PRVM_Prog_Init(prog); // allocate the mempools prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL); prog->edictprivate_size = 0; // no private struct used prog->name = "client"; prog->num_edicts = 1; prog->max_edicts = 512; prog->limit_edicts = CL_MAX_EDICTS; prog->reserved_edicts = 0; prog->edictprivate_size = sizeof(edict_engineprivate_t); // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black] prog->extensionstring = vm_sv_extensions; prog->builtins = vm_cl_builtins; prog->numbuiltins = vm_cl_numbuiltins; // all callbacks must be defined (pointers are not checked before calling) prog->begin_increase_edicts = CLVM_begin_increase_edicts; prog->end_increase_edicts = CLVM_end_increase_edicts; prog->init_edict = CLVM_init_edict; prog->free_edict = CLVM_free_edict; prog->count_edicts = CLVM_count_edicts; prog->load_edict = CLVM_load_edict; prog->init_cmd = CLVM_init_cmd; prog->reset_cmd = CLVM_reset_cmd; prog->error_cmd = Host_Error; prog->ExecuteProgram = CLVM_ExecuteProgram; PRVM_Prog_Load(prog, csprogsfn, csprogsdata, csprogsdatasize, cl_numrequiredfunc, cl_required_func, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals); if (!prog->loaded) { Host_Error("CSQC %s ^2failed to load\n", csprogsfn); if(!sv.active) CL_Disconnect(); Mem_Free(csprogsdata); return; } Con_DPrintf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize); if(cls.demorecording) { if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc) { int i; static char buf[NET_MAXMESSAGE]; sizebuf_t sb; unsigned char *demobuf; fs_offset_t demofilesize; sb.data = (unsigned char *) buf; sb.maxsize = sizeof(buf); i = 0; CL_CutDemo(&demobuf, &demofilesize); while(MakeDownloadPacket(csqc_progname.string, csprogsdata, (size_t)csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol)) CL_WriteDemoMessage(&sb); CL_PasteDemo(&demobuf, &demofilesize); cls.demo_lastcsprogssize = csprogsdatasize; cls.demo_lastcsprogscrc = csprogsdatacrc; } } Mem_Free(csprogsdata); // check if OP_STATE animation is possible in this dat file if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0) prog->flag |= PRVM_OP_STATE; // set time PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = 0; PRVM_clientglobalstring(mapname) = PRVM_SetEngineString(prog, cl.worldname); PRVM_clientglobalfloat(player_localnum) = cl.realplayerentity - 1; PRVM_clientglobalfloat(player_localentnum) = cl.viewentity; // set map description (use world entity 0) PRVM_clientedictstring(prog->edicts, message) = PRVM_SetEngineString(prog, cl.worldmessage); VectorCopy(cl.world.mins, PRVM_clientedictvector(prog->edicts, mins)); VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, maxs)); VectorCopy(cl.world.mins, PRVM_clientedictvector(prog->edicts, absmin)); VectorCopy(cl.world.maxs, PRVM_clientedictvector(prog->edicts, absmax)); // call the prog init prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Init), "QC function CSQC_Init is missing"); // Once CSQC_Init was called, we consider csqc code fully initialized. prog->inittime = realtime; cl.csqc_loaded = true; cl.csqc_vidvars.drawcrosshair = false; cl.csqc_vidvars.drawenginesbar = false; // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo) CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype); } void CL_VM_ShutDown (void) { prvm_prog_t *prog = CLVM_prog; Cmd_ClearCsqcFuncs(); //Cvar_SetValueQuick(&csqc_progcrc, -1); //Cvar_SetValueQuick(&csqc_progsize, -1); if(!cl.csqc_loaded) return; CSQC_BEGIN if (prog->loaded) { PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = 0; if (PRVM_clientfunction(CSQC_Shutdown)) prog->ExecuteProgram(prog, PRVM_clientfunction(CSQC_Shutdown), "QC function CSQC_Shutdown is missing"); } PRVM_Prog_Reset(prog); CSQC_END Con_DPrint("CSQC ^1unloaded\n"); cl.csqc_loaded = false; } qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out) { prvm_prog_t *prog = CLVM_prog; prvm_edict_t *ed; dp_model_t *mod; matrix4x4_t matrix; qboolean r = 0; CSQC_BEGIN; // FIXME consider attachments here! ed = PRVM_EDICT_NUM(entnum - MAX_EDICTS); if(!ed->priv.required->free) { mod = CL_GetModelFromEdict(ed); VectorCopy(PRVM_clientedictvector(ed, origin), out); if(CL_GetTagMatrix(prog, &matrix, ed, 0) == 0) Matrix4x4_OriginFromMatrix(&matrix, out); if (mod && mod->soundfromcenter) VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out); r = 1; } CSQC_END; return r; } qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin) { prvm_prog_t *prog = CLVM_prog; qboolean ret = false; prvm_edict_t *ed; vec3_t forward, left, up, origin, ang; matrix4x4_t mat, matq; CSQC_BEGIN ed = PRVM_EDICT_NUM(entnum); // camera: // camera_transform if(PRVM_clientedictfunction(ed, camera_transform)) { ret = true; if(viewmatrix && clipplane && visorigin) { Matrix4x4_ToVectors(viewmatrix, forward, left, up, origin); AnglesFromVectors(ang, forward, up, false); PRVM_clientglobalfloat(time) = cl.time; PRVM_clientglobaledict(self) = entnum; VectorCopy(origin, PRVM_G_VECTOR(OFS_PARM0)); VectorCopy(ang, PRVM_G_VECTOR(OFS_PARM1)); VectorCopy(forward, PRVM_clientglobalvector(v_forward)); VectorScale(left, -1, PRVM_clientglobalvector(v_right)); VectorCopy(up, PRVM_clientglobalvector(v_up)); VectorCopy(origin, PRVM_clientglobalvector(trace_endpos)); prog->ExecuteProgram(prog, PRVM_clientedictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); VectorCopy(PRVM_G_VECTOR(OFS_RETURN), origin); VectorCopy(PRVM_clientglobalvector(v_forward), forward); VectorScale(PRVM_clientglobalvector(v_right), -1, left); VectorCopy(PRVM_clientglobalvector(v_up), up); VectorCopy(PRVM_clientglobalvector(trace_endpos), visorigin); Matrix4x4_Invert_Full(&mat, viewmatrix); Matrix4x4_FromVectors(viewmatrix, forward, left, up, origin); Matrix4x4_Concat(&matq, viewmatrix, &mat); Matrix4x4_TransformPositivePlane(&matq, clipplane->normal[0], clipplane->normal[1], clipplane->normal[2], clipplane->dist, clipplane->normal_and_dist); } } CSQC_END return ret; } int CL_VM_GetViewEntity(void) { if(cl.csqc_server2csqcentitynumber[cl.viewentity]) return cl.csqc_server2csqcentitynumber[cl.viewentity] + MAX_EDICTS; return cl.viewentity; } darkplaces/snd_oss.c0000664000175000017500000002007313067716222014003 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // OSS module, used by Linux and FreeBSD #include "quakedef.h" #include #include #include #include #include #include "snd_main.h" #define NB_FRAGMENTS 4 static int audio_fd = -1; static int old_osstime = 0; static unsigned int osssoundtime; /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { int flags, ioctl_param, prev_value; unsigned int fragmentsize; Con_DPrint("SndSys_Init: using the OSS module\n"); // Check the requested sound format if (requested->width < 1 || requested->width > 2) { Con_Printf("SndSys_Init: invalid sound width (%hu)\n", requested->width); if (suggested != NULL) { memcpy(suggested, requested, sizeof(*suggested)); if (requested->width < 1) suggested->width = 1; else suggested->width = 2; } return false; } // Open /dev/dsp audio_fd = open("/dev/dsp", O_WRONLY); if (audio_fd < 0) { perror("/dev/dsp"); Con_Print("SndSys_Init: could not open /dev/dsp\n"); return false; } // Use non-blocking IOs if possible flags = fcntl(audio_fd, F_GETFL); if (flags != -1) { if (fcntl(audio_fd, F_SETFL, flags | O_NONBLOCK) == -1) Con_Print("SndSys_Init : fcntl(F_SETFL, O_NONBLOCK) failed!\n"); } else Con_Print("SndSys_Init: fcntl(F_GETFL) failed!\n"); // Set the fragment size (up to "NB_FRAGMENTS" fragments of "fragmentsize" bytes) fragmentsize = requested->speed * requested->channels * requested->width / 10; fragmentsize = (unsigned int)ceilf((float)fragmentsize / (float)NB_FRAGMENTS); fragmentsize = CeilPowerOf2(fragmentsize); ioctl_param = (NB_FRAGMENTS << 16) | log2i(fragmentsize); if (ioctl(audio_fd, SNDCTL_DSP_SETFRAGMENT, &ioctl_param) == -1) { Con_Print ("SndSys_Init: could not set the fragment size\n"); SndSys_Shutdown (); return false; } Con_Printf ("SndSys_Init: using %u fragments of %u bytes\n", ioctl_param >> 16, 1 << (ioctl_param & 0xFFFF)); // Set the sound width if (requested->width == 1) ioctl_param = AFMT_U8; else ioctl_param = AFMT_S16_NE; prev_value = ioctl_param; if (ioctl(audio_fd, SNDCTL_DSP_SETFMT, &ioctl_param) == -1 || ioctl_param != prev_value) { if (ioctl_param != prev_value && suggested != NULL) { memcpy(suggested, requested, sizeof(*suggested)); if (ioctl_param == AFMT_S16_NE) suggested->width = 2; else suggested->width = 1; } Con_Printf("SndSys_Init: could not set the sound width to %hu\n", requested->width); SndSys_Shutdown(); return false; } // Set the sound channels ioctl_param = requested->channels; if (ioctl(audio_fd, SNDCTL_DSP_CHANNELS, &ioctl_param) == -1 || ioctl_param != requested->channels) { if (ioctl_param != requested->channels && suggested != NULL) { memcpy(suggested, requested, sizeof(*suggested)); suggested->channels = ioctl_param; } Con_Printf("SndSys_Init: could not set the number of channels to %hu\n", requested->channels); SndSys_Shutdown(); return false; } // Set the sound speed ioctl_param = requested->speed; if (ioctl(audio_fd, SNDCTL_DSP_SPEED, &ioctl_param) == -1 || (unsigned int)ioctl_param != requested->speed) { if ((unsigned int)ioctl_param != requested->speed && suggested != NULL) { memcpy(suggested, requested, sizeof(*suggested)); suggested->speed = ioctl_param; } Con_Printf("SndSys_Init: could not set the sound speed to %u\n", requested->speed); SndSys_Shutdown(); return false; } // TOCHECK: I'm not sure which channel layout OSS uses for 5.1 and 7.1 if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_ALSA); old_osstime = 0; osssoundtime = 0; snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL); return true; } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown (void) { // Stop the sound and close the device if (audio_fd >= 0) { ioctl(audio_fd, SNDCTL_DSP_RESET, NULL); close(audio_fd); audio_fd = -1; } if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } } /* ==================== SndSys_Write ==================== */ static int SndSys_Write (const unsigned char* buffer, unsigned int nb_bytes) { int written; unsigned int factor; written = write (audio_fd, buffer, nb_bytes); if (written < 0) { if (errno != EAGAIN) Con_Printf ("SndSys_Write: audio write returned %d! (errno= %d)\n", written, errno); return written; } factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; if (written % factor != 0) Sys_Error ("SndSys_Write: nb of bytes written (%d) isn't aligned to a frame sample!\n", written); snd_renderbuffer->startframe += written / factor; if ((unsigned int)written < nb_bytes) { Con_DPrintf("SndSys_Submit: audio can't keep up! (%u < %u)\n", written, nb_bytes); } return written; } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { unsigned int startoffset, factor, limit, nbframes; int written; if (audio_fd < 0 || snd_renderbuffer->startframe == snd_renderbuffer->endframe) return; startoffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; factor = snd_renderbuffer->format.width * snd_renderbuffer->format.channels; limit = snd_renderbuffer->maxframes - startoffset; nbframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; if (nbframes > limit) { written = SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], limit * factor); if (written < 0 || (unsigned int)written < limit * factor) return; nbframes -= limit; startoffset = 0; } SndSys_Write (&snd_renderbuffer->ring[startoffset * factor], nbframes * factor); } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { struct count_info count; int new_osstime; unsigned int timediff; if (ioctl (audio_fd, SNDCTL_DSP_GETOPTR, &count) == -1) { Con_Print ("SndSys_GetSoundTimeDiff: can't ioctl (SNDCTL_DSP_GETOPTR)\n"); return 0; } new_osstime = count.bytes / (snd_renderbuffer->format.width * snd_renderbuffer->format.channels); if (new_osstime >= old_osstime) timediff = new_osstime - old_osstime; else { Con_Print ("SndSys_GetSoundTime: osstime wrapped\n"); timediff = 0; } old_osstime = new_osstime; osssoundtime += timediff; return osssoundtime; } /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { // Nothing to do return true; } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { // Nothing to do } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/DPiOS.xcodeproj/0000775000175000017500000000000013067716406015103 5ustar kalevkalevdarkplaces/DPiOS.xcodeproj/project.pbxproj0000775000175000017500000021270413067716216020167 0ustar kalevkalev// !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D30AB110D05D00D00671497 /* Foundation.framework */; }; 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */; }; 28FD15000DC6FC520079059D /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD14FF0DC6FC520079059D /* OpenGLES.framework */; }; 28FD15080DC6FC5B0079059D /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28FD15070DC6FC5B0079059D /* QuartzCore.framework */; }; 74063A3E1751ADDB0015D12C /* mod_skeletal_animatevertices_sse.c in Sources */ = {isa = PBXBuildFile; fileRef = 74063A3C1751ADDA0015D12C /* mod_skeletal_animatevertices_sse.c */; }; 74063A401751B0250015D12C /* cd_null.c in Sources */ = {isa = PBXBuildFile; fileRef = 74063A3F1751B0250015D12C /* cd_null.c */; }; 74063A421751B0AF0015D12C /* clvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 74063A411751B0AF0015D12C /* clvm_cmds.c */; }; 74063A491751B9B60015D12C /* libSDL2-ios-armv7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74063A471751B9B60015D12C /* libSDL2-ios-armv7.a */; }; 74063A4A1751B9B60015D12C /* libSDL2-ios-simulator.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 74063A481751B9B60015D12C /* libSDL2-ios-simulator.a */; }; 74063A511751C4560015D12C /* gamedata in Resources */ = {isa = PBXBuildFile; fileRef = 74063A501751C4560015D12C /* gamedata */; }; 7463B77812F9CE6B00983F6A /* bih.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C012F9CE6B00983F6A /* bih.c */; }; 7463B77912F9CE6B00983F6A /* cap_avi.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C312F9CE6B00983F6A /* cap_avi.c */; }; 7463B77A12F9CE6B00983F6A /* cap_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C512F9CE6B00983F6A /* cap_ogg.c */; }; 7463B77C12F9CE6B00983F6A /* cd_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6C812F9CE6B00983F6A /* cd_shared.c */; }; 7463B77D12F9CE6B00983F6A /* cl_collision.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CA12F9CE6B00983F6A /* cl_collision.c */; }; 7463B77E12F9CE6B00983F6A /* cl_demo.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CC12F9CE6B00983F6A /* cl_demo.c */; }; 7463B77F12F9CE6B00983F6A /* cl_dyntexture.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */; }; 7463B78112F9CE6B00983F6A /* cl_input.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D112F9CE6B00983F6A /* cl_input.c */; }; 7463B78212F9CE6B00983F6A /* cl_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D212F9CE6B00983F6A /* cl_main.c */; }; 7463B78312F9CE6B00983F6A /* cl_parse.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D312F9CE6B00983F6A /* cl_parse.c */; }; 7463B78412F9CE6B00983F6A /* cl_particles.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D412F9CE6B00983F6A /* cl_particles.c */; }; 7463B78512F9CE6B00983F6A /* cl_screen.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D512F9CE6B00983F6A /* cl_screen.c */; }; 7463B78612F9CE6B00983F6A /* cl_video.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6D712F9CE6B00983F6A /* cl_video.c */; }; 7463B78712F9CE6B00983F6A /* cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6DC12F9CE6B00983F6A /* cmd.c */; }; 7463B78812F9CE6B00983F6A /* collision.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6DE12F9CE6B00983F6A /* collision.c */; }; 7463B78912F9CE6B00983F6A /* common.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E012F9CE6B00983F6A /* common.c */; }; 7463B78A12F9CE6B00983F6A /* console.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E212F9CE6B00983F6A /* console.c */; }; 7463B78B12F9CE6B00983F6A /* crypto.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E412F9CE6B00983F6A /* crypto.c */; }; 7463B78C12F9CE6B00983F6A /* csprogs.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E612F9CE6B00983F6A /* csprogs.c */; }; 7463B78D12F9CE6B00983F6A /* curves.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6E812F9CE6B00983F6A /* curves.c */; }; 7463B78E12F9CE6B00983F6A /* cvar.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EA12F9CE6B00983F6A /* cvar.c */; }; 7463B78F12F9CE6B00983F6A /* dpsoftrast.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */; }; 7463B79012F9CE6B00983F6A /* dpvsimpledecode.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */; }; 7463B79112F9CE6B00983F6A /* filematch.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F112F9CE6B00983F6A /* filematch.c */; }; 7463B79212F9CE6B00983F6A /* fractalnoise.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F212F9CE6B00983F6A /* fractalnoise.c */; }; 7463B79312F9CE6B00983F6A /* fs.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F312F9CE6B00983F6A /* fs.c */; }; 7463B79412F9CE6B00983F6A /* ft2.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F712F9CE6B00983F6A /* ft2.c */; }; 7463B79512F9CE6B00983F6A /* gl_backend.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6F912F9CE6B00983F6A /* gl_backend.c */; }; 7463B79612F9CE6B00983F6A /* gl_draw.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FB12F9CE6B00983F6A /* gl_draw.c */; }; 7463B79712F9CE6B00983F6A /* gl_rmain.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */; }; 7463B79812F9CE6B00983F6A /* gl_rsurf.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */; }; 7463B79912F9CE6B00983F6A /* gl_textures.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B6FE12F9CE6B00983F6A /* gl_textures.c */; }; 7463B79A12F9CE6B00983F6A /* hmac.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70012F9CE6B00983F6A /* hmac.c */; }; 7463B79B12F9CE6B00983F6A /* host_cmd.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70212F9CE6B00983F6A /* host_cmd.c */; }; 7463B79C12F9CE6B00983F6A /* host.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70312F9CE6B00983F6A /* host.c */; }; 7463B79D12F9CE6B00983F6A /* image_png.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70412F9CE6B00983F6A /* image_png.c */; }; 7463B79E12F9CE6B00983F6A /* image.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70612F9CE6B00983F6A /* image.c */; }; 7463B79F12F9CE6B00983F6A /* jpeg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70A12F9CE6B00983F6A /* jpeg.c */; }; 7463B7A012F9CE6B00983F6A /* keys.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70C12F9CE6B00983F6A /* keys.c */; }; 7463B7A112F9CE6B00983F6A /* lhnet.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B70F12F9CE6B00983F6A /* lhnet.c */; }; 7463B7A212F9CE6B00983F6A /* libcurl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71112F9CE6B00983F6A /* libcurl.c */; }; 7463B7A312F9CE6B00983F6A /* mathlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71312F9CE6B00983F6A /* mathlib.c */; }; 7463B7A412F9CE6B00983F6A /* matrixlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71512F9CE6B00983F6A /* matrixlib.c */; }; 7463B7A512F9CE6B00983F6A /* mdfour.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71712F9CE6B00983F6A /* mdfour.c */; }; 7463B7A612F9CE6B00983F6A /* menu.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71912F9CE6B00983F6A /* menu.c */; }; 7463B7A712F9CE6B00983F6A /* meshqueue.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71B12F9CE6B00983F6A /* meshqueue.c */; }; 7463B7A812F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */; }; 7463B7A912F9CE6B00983F6A /* model_alias.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B71F12F9CE6B00983F6A /* model_alias.c */; }; 7463B7AA12F9CE6B00983F6A /* model_brush.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72112F9CE6B00983F6A /* model_brush.c */; }; 7463B7AB12F9CE6B00983F6A /* model_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72612F9CE6B00983F6A /* model_shared.c */; }; 7463B7AC12F9CE6B00983F6A /* model_sprite.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72812F9CE6B00983F6A /* model_sprite.c */; }; 7463B7AD12F9CE6B00983F6A /* mvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */; }; 7463B7AE12F9CE6B00983F6A /* netconn.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B72E12F9CE6B00983F6A /* netconn.c */; }; 7463B7AF12F9CE6B00983F6A /* palette.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73012F9CE6B00983F6A /* palette.c */; }; 7463B7B012F9CE6B00983F6A /* polygon.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73212F9CE6B00983F6A /* polygon.c */; }; 7463B7B112F9CE6B00983F6A /* portals.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73412F9CE6B00983F6A /* portals.c */; }; 7463B7B212F9CE6B00983F6A /* protocol.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73A12F9CE6B00983F6A /* protocol.c */; }; 7463B7B312F9CE6B00983F6A /* prvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */; }; 7463B7B412F9CE6B00983F6A /* prvm_edict.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73E12F9CE6B00983F6A /* prvm_edict.c */; }; 7463B7B512F9CE6B00983F6A /* prvm_exec.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B73F12F9CE6B00983F6A /* prvm_exec.c */; }; 7463B7B612F9CE6B00983F6A /* r_explosion.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74312F9CE6B00983F6A /* r_explosion.c */; }; 7463B7B712F9CE6B00983F6A /* r_lightning.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74412F9CE6B00983F6A /* r_lightning.c */; }; 7463B7B812F9CE6B00983F6A /* r_modules.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74512F9CE6B00983F6A /* r_modules.c */; }; 7463B7B912F9CE6B00983F6A /* r_shadow.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74712F9CE6B00983F6A /* r_shadow.c */; }; 7463B7BA12F9CE6B00983F6A /* r_sky.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74912F9CE6B00983F6A /* r_sky.c */; }; 7463B7BB12F9CE6B00983F6A /* r_sprites.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74A12F9CE6B00983F6A /* r_sprites.c */; }; 7463B7BC12F9CE6B00983F6A /* sbar.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B74E12F9CE6B00983F6A /* sbar.c */; }; 7463B7BD12F9CE6B00983F6A /* snd_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75212F9CE6B00983F6A /* snd_main.c */; }; 7463B7BE12F9CE6B00983F6A /* snd_mem.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75412F9CE6B00983F6A /* snd_mem.c */; }; 7463B7BF12F9CE6B00983F6A /* snd_mix.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75512F9CE6B00983F6A /* snd_mix.c */; }; 7463B7C112F9CE6B00983F6A /* snd_ogg.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75812F9CE6B00983F6A /* snd_ogg.c */; }; 7463B7C212F9CE6B00983F6A /* snd_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75A12F9CE6B00983F6A /* snd_sdl.c */; }; 7463B7C312F9CE6B00983F6A /* snd_wav.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75B12F9CE6B00983F6A /* snd_wav.c */; }; 7463B7C412F9CE6B00983F6A /* sv_demo.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B75F12F9CE6B00983F6A /* sv_demo.c */; }; 7463B7C512F9CE6B00983F6A /* sv_main.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76112F9CE6B00983F6A /* sv_main.c */; }; 7463B7C612F9CE6B00983F6A /* sv_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76212F9CE6B00983F6A /* sv_move.c */; }; 7463B7C712F9CE6B00983F6A /* sv_phys.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76312F9CE6B00983F6A /* sv_phys.c */; }; 7463B7C812F9CE6B00983F6A /* sv_user.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76412F9CE6B00983F6A /* sv_user.c */; }; 7463B7C912F9CE6B00983F6A /* svbsp.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76512F9CE6B00983F6A /* svbsp.c */; }; 7463B7CA12F9CE6B00983F6A /* svvm_cmds.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76712F9CE6B00983F6A /* svvm_cmds.c */; }; 7463B7CB12F9CE6B00983F6A /* sys_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76812F9CE6B00983F6A /* sys_sdl.c */; }; 7463B7CC12F9CE6B00983F6A /* sys_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76912F9CE6B00983F6A /* sys_shared.c */; }; 7463B7CD12F9CE6B00983F6A /* utf8lib.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76C12F9CE6B00983F6A /* utf8lib.c */; }; 7463B7CE12F9CE6B00983F6A /* vid_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76E12F9CE6B00983F6A /* vid_sdl.c */; }; 7463B7CF12F9CE6B00983F6A /* vid_shared.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B76F12F9CE6B00983F6A /* vid_shared.c */; }; 7463B7D012F9CE6B00983F6A /* view.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77112F9CE6B00983F6A /* view.c */; }; 7463B7D112F9CE6B00983F6A /* wad.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77212F9CE6B00983F6A /* wad.c */; }; 7463B7D212F9CE6B00983F6A /* world.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77412F9CE6B00983F6A /* world.c */; }; 7463B7D312F9CE6B00983F6A /* zone.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B77612F9CE6B00983F6A /* zone.c */; }; 7463B7D912F9CF8F00983F6A /* darkplaces64x64.png in Resources */ = {isa = PBXBuildFile; fileRef = 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */; }; 7463B7EF12F9D17D00983F6A /* builddate.c in Sources */ = {isa = PBXBuildFile; fileRef = 7463B7ED12F9D17D00983F6A /* builddate.c */; }; 7463B7F012F9D17D00983F6A /* (null) in Sources */ = {isa = PBXBuildFile; }; 7487D481130102AA00AEE909 /* thread_sdl.c in Sources */ = {isa = PBXBuildFile; fileRef = 7487D47F130102AA00AEE909 /* thread_sdl.c */; }; FD779EDE0E26BA1200F39101 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD779EDD0E26BA1200F39101 /* CoreAudio.framework */; }; FD77A0850E26BDB800F39101 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FD77A0840E26BDB800F39101 /* AudioToolbox.framework */; }; FDB8BFC60E5A0F6A00980157 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 1D30AB110D05D00D00671497 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 1D6058910D05DD3D006BFB54 /* DPiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DPiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 28FD14FF0DC6FC520079059D /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; 28FD15070DC6FC5B0079059D /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 74063A3C1751ADDA0015D12C /* mod_skeletal_animatevertices_sse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mod_skeletal_animatevertices_sse.c; sourceTree = ""; }; 74063A3D1751ADDA0015D12C /* mod_skeletal_animatevertices_sse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mod_skeletal_animatevertices_sse.h; sourceTree = ""; }; 74063A3F1751B0250015D12C /* cd_null.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cd_null.c; sourceTree = ""; }; 74063A411751B0AF0015D12C /* clvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = clvm_cmds.c; sourceTree = ""; }; 74063A471751B9B60015D12C /* libSDL2-ios-armv7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libSDL2-ios-armv7.a"; path = "lib/libSDL2-ios-armv7.a"; sourceTree = ""; }; 74063A481751B9B60015D12C /* libSDL2-ios-simulator.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libSDL2-ios-simulator.a"; path = "lib/libSDL2-ios-simulator.a"; sourceTree = ""; }; 74063A501751C4560015D12C /* gamedata */ = {isa = PBXFileReference; lastKnownFileType = folder; path = gamedata; sourceTree = ""; }; 7463B6C012F9CE6B00983F6A /* bih.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bih.c; sourceTree = ""; }; 7463B6C112F9CE6B00983F6A /* bih.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bih.h; sourceTree = ""; }; 7463B6C212F9CE6B00983F6A /* bspfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bspfile.h; sourceTree = ""; }; 7463B6C312F9CE6B00983F6A /* cap_avi.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cap_avi.c; sourceTree = ""; }; 7463B6C412F9CE6B00983F6A /* cap_avi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cap_avi.h; sourceTree = ""; }; 7463B6C512F9CE6B00983F6A /* cap_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cap_ogg.c; sourceTree = ""; }; 7463B6C612F9CE6B00983F6A /* cap_ogg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cap_ogg.h; sourceTree = ""; }; 7463B6C812F9CE6B00983F6A /* cd_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cd_shared.c; sourceTree = ""; }; 7463B6C912F9CE6B00983F6A /* cdaudio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cdaudio.h; sourceTree = ""; }; 7463B6CA12F9CE6B00983F6A /* cl_collision.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_collision.c; sourceTree = ""; }; 7463B6CB12F9CE6B00983F6A /* cl_collision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_collision.h; sourceTree = ""; }; 7463B6CC12F9CE6B00983F6A /* cl_demo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_demo.c; sourceTree = ""; }; 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_dyntexture.c; sourceTree = ""; }; 7463B6CE12F9CE6B00983F6A /* cl_dyntexture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_dyntexture.h; sourceTree = ""; }; 7463B6D112F9CE6B00983F6A /* cl_input.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_input.c; sourceTree = ""; }; 7463B6D212F9CE6B00983F6A /* cl_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_main.c; sourceTree = ""; }; 7463B6D312F9CE6B00983F6A /* cl_parse.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_parse.c; sourceTree = ""; }; 7463B6D412F9CE6B00983F6A /* cl_particles.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_particles.c; sourceTree = ""; }; 7463B6D512F9CE6B00983F6A /* cl_screen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_screen.c; sourceTree = ""; }; 7463B6D612F9CE6B00983F6A /* cl_screen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_screen.h; sourceTree = ""; }; 7463B6D712F9CE6B00983F6A /* cl_video.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cl_video.c; sourceTree = ""; }; 7463B6D812F9CE6B00983F6A /* cl_video.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cl_video.h; sourceTree = ""; }; 7463B6D912F9CE6B00983F6A /* client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = client.h; sourceTree = ""; }; 7463B6DA12F9CE6B00983F6A /* clprogdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clprogdefs.h; sourceTree = ""; }; 7463B6DB12F9CE6B00983F6A /* clvm_cmds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = clvm_cmds.h; sourceTree = ""; }; 7463B6DC12F9CE6B00983F6A /* cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cmd.c; sourceTree = ""; }; 7463B6DD12F9CE6B00983F6A /* cmd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cmd.h; sourceTree = ""; }; 7463B6DE12F9CE6B00983F6A /* collision.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = collision.c; sourceTree = ""; }; 7463B6DF12F9CE6B00983F6A /* collision.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = collision.h; sourceTree = ""; }; 7463B6E012F9CE6B00983F6A /* common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = common.c; sourceTree = ""; }; 7463B6E112F9CE6B00983F6A /* common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; }; 7463B6E212F9CE6B00983F6A /* console.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = console.c; sourceTree = ""; }; 7463B6E312F9CE6B00983F6A /* console.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = console.h; sourceTree = ""; }; 7463B6E412F9CE6B00983F6A /* crypto.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = crypto.c; sourceTree = ""; }; 7463B6E512F9CE6B00983F6A /* crypto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = crypto.h; sourceTree = ""; }; 7463B6E612F9CE6B00983F6A /* csprogs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = csprogs.c; sourceTree = ""; }; 7463B6E712F9CE6B00983F6A /* csprogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = csprogs.h; sourceTree = ""; }; 7463B6E812F9CE6B00983F6A /* curves.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = curves.c; sourceTree = ""; }; 7463B6E912F9CE6B00983F6A /* curves.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = curves.h; sourceTree = ""; }; 7463B6EA12F9CE6B00983F6A /* cvar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = cvar.c; sourceTree = ""; }; 7463B6EB12F9CE6B00983F6A /* cvar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cvar.h; sourceTree = ""; }; 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dpsoftrast.c; sourceTree = ""; }; 7463B6ED12F9CE6B00983F6A /* dpsoftrast.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dpsoftrast.h; sourceTree = ""; }; 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = dpvsimpledecode.c; sourceTree = ""; }; 7463B6EF12F9CE6B00983F6A /* dpvsimpledecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dpvsimpledecode.h; sourceTree = ""; }; 7463B6F012F9CE6B00983F6A /* draw.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = draw.h; sourceTree = ""; }; 7463B6F112F9CE6B00983F6A /* filematch.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = filematch.c; sourceTree = ""; }; 7463B6F212F9CE6B00983F6A /* fractalnoise.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fractalnoise.c; sourceTree = ""; }; 7463B6F312F9CE6B00983F6A /* fs.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fs.c; sourceTree = ""; }; 7463B6F412F9CE6B00983F6A /* fs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fs.h; sourceTree = ""; }; 7463B6F512F9CE6B00983F6A /* ft2_defs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2_defs.h; sourceTree = ""; }; 7463B6F612F9CE6B00983F6A /* ft2_fontdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2_fontdefs.h; sourceTree = ""; }; 7463B6F712F9CE6B00983F6A /* ft2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ft2.c; sourceTree = ""; }; 7463B6F812F9CE6B00983F6A /* ft2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ft2.h; sourceTree = ""; }; 7463B6F912F9CE6B00983F6A /* gl_backend.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_backend.c; sourceTree = ""; }; 7463B6FA12F9CE6B00983F6A /* gl_backend.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gl_backend.h; sourceTree = ""; }; 7463B6FB12F9CE6B00983F6A /* gl_draw.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_draw.c; sourceTree = ""; }; 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_rmain.c; sourceTree = ""; }; 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_rsurf.c; sourceTree = ""; }; 7463B6FE12F9CE6B00983F6A /* gl_textures.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gl_textures.c; sourceTree = ""; }; 7463B6FF12F9CE6B00983F6A /* glquake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = glquake.h; sourceTree = ""; }; 7463B70012F9CE6B00983F6A /* hmac.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = hmac.c; sourceTree = ""; }; 7463B70112F9CE6B00983F6A /* hmac.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = hmac.h; sourceTree = ""; }; 7463B70212F9CE6B00983F6A /* host_cmd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = host_cmd.c; sourceTree = ""; }; 7463B70312F9CE6B00983F6A /* host.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = host.c; sourceTree = ""; }; 7463B70412F9CE6B00983F6A /* image_png.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = image_png.c; sourceTree = ""; }; 7463B70512F9CE6B00983F6A /* image_png.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image_png.h; sourceTree = ""; }; 7463B70612F9CE6B00983F6A /* image.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = image.c; sourceTree = ""; }; 7463B70712F9CE6B00983F6A /* image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = image.h; sourceTree = ""; }; 7463B70812F9CE6B00983F6A /* input.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = input.h; sourceTree = ""; }; 7463B70912F9CE6B00983F6A /* intoverflow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = intoverflow.h; sourceTree = ""; }; 7463B70A12F9CE6B00983F6A /* jpeg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = jpeg.c; sourceTree = ""; }; 7463B70B12F9CE6B00983F6A /* jpeg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jpeg.h; sourceTree = ""; }; 7463B70C12F9CE6B00983F6A /* keys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = keys.c; sourceTree = ""; }; 7463B70D12F9CE6B00983F6A /* keys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = keys.h; sourceTree = ""; }; 7463B70E12F9CE6B00983F6A /* lhfont.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lhfont.h; sourceTree = ""; }; 7463B70F12F9CE6B00983F6A /* lhnet.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lhnet.c; sourceTree = ""; }; 7463B71012F9CE6B00983F6A /* lhnet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lhnet.h; sourceTree = ""; }; 7463B71112F9CE6B00983F6A /* libcurl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = libcurl.c; sourceTree = ""; }; 7463B71212F9CE6B00983F6A /* libcurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libcurl.h; sourceTree = ""; }; 7463B71312F9CE6B00983F6A /* mathlib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mathlib.c; sourceTree = ""; }; 7463B71412F9CE6B00983F6A /* mathlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mathlib.h; sourceTree = ""; }; 7463B71512F9CE6B00983F6A /* matrixlib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = matrixlib.c; sourceTree = ""; }; 7463B71612F9CE6B00983F6A /* matrixlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = matrixlib.h; sourceTree = ""; }; 7463B71712F9CE6B00983F6A /* mdfour.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mdfour.c; sourceTree = ""; }; 7463B71812F9CE6B00983F6A /* mdfour.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mdfour.h; sourceTree = ""; }; 7463B71912F9CE6B00983F6A /* menu.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = menu.c; sourceTree = ""; }; 7463B71A12F9CE6B00983F6A /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; }; 7463B71B12F9CE6B00983F6A /* meshqueue.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = meshqueue.c; sourceTree = ""; }; 7463B71C12F9CE6B00983F6A /* meshqueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = meshqueue.h; sourceTree = ""; }; 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mod_skeletal_animatevertices_generic.c; sourceTree = ""; }; 7463B71E12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mod_skeletal_animatevertices_generic.h; sourceTree = ""; }; 7463B71F12F9CE6B00983F6A /* model_alias.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_alias.c; sourceTree = ""; }; 7463B72012F9CE6B00983F6A /* model_alias.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_alias.h; sourceTree = ""; }; 7463B72112F9CE6B00983F6A /* model_brush.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_brush.c; sourceTree = ""; }; 7463B72212F9CE6B00983F6A /* model_brush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_brush.h; sourceTree = ""; }; 7463B72312F9CE6B00983F6A /* model_dpmodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_dpmodel.h; sourceTree = ""; }; 7463B72412F9CE6B00983F6A /* model_iqm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_iqm.h; sourceTree = ""; }; 7463B72512F9CE6B00983F6A /* model_psk.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_psk.h; sourceTree = ""; }; 7463B72612F9CE6B00983F6A /* model_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_shared.c; sourceTree = ""; }; 7463B72712F9CE6B00983F6A /* model_shared.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_shared.h; sourceTree = ""; }; 7463B72812F9CE6B00983F6A /* model_sprite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = model_sprite.c; sourceTree = ""; }; 7463B72912F9CE6B00983F6A /* model_sprite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_sprite.h; sourceTree = ""; }; 7463B72A12F9CE6B00983F6A /* model_zymotic.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model_zymotic.h; sourceTree = ""; }; 7463B72B12F9CE6B00983F6A /* modelgen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = modelgen.h; sourceTree = ""; }; 7463B72C12F9CE6B00983F6A /* mprogdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mprogdefs.h; sourceTree = ""; }; 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mvm_cmds.c; sourceTree = ""; }; 7463B72E12F9CE6B00983F6A /* netconn.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = netconn.c; sourceTree = ""; }; 7463B72F12F9CE6B00983F6A /* netconn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = netconn.h; sourceTree = ""; }; 7463B73012F9CE6B00983F6A /* palette.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = palette.c; sourceTree = ""; }; 7463B73112F9CE6B00983F6A /* palette.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = palette.h; sourceTree = ""; }; 7463B73212F9CE6B00983F6A /* polygon.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = polygon.c; sourceTree = ""; }; 7463B73312F9CE6B00983F6A /* polygon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = polygon.h; sourceTree = ""; }; 7463B73412F9CE6B00983F6A /* portals.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = portals.c; sourceTree = ""; }; 7463B73512F9CE6B00983F6A /* portals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = portals.h; sourceTree = ""; }; 7463B73612F9CE6B00983F6A /* pr_comp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pr_comp.h; sourceTree = ""; }; 7463B73712F9CE6B00983F6A /* progdefs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progdefs.h; sourceTree = ""; }; 7463B73812F9CE6B00983F6A /* progs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progs.h; sourceTree = ""; }; 7463B73912F9CE6B00983F6A /* progsvm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = progsvm.h; sourceTree = ""; }; 7463B73A12F9CE6B00983F6A /* protocol.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = protocol.c; sourceTree = ""; }; 7463B73B12F9CE6B00983F6A /* protocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = protocol.h; sourceTree = ""; }; 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_cmds.c; sourceTree = ""; }; 7463B73D12F9CE6B00983F6A /* prvm_cmds.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prvm_cmds.h; sourceTree = ""; }; 7463B73E12F9CE6B00983F6A /* prvm_edict.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_edict.c; sourceTree = ""; }; 7463B73F12F9CE6B00983F6A /* prvm_exec.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prvm_exec.c; sourceTree = ""; }; 7463B74012F9CE6B00983F6A /* prvm_execprogram.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prvm_execprogram.h; sourceTree = ""; }; 7463B74112F9CE6B00983F6A /* qtypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = qtypes.h; sourceTree = ""; }; 7463B74212F9CE6B00983F6A /* quakedef.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = quakedef.h; sourceTree = ""; }; 7463B74312F9CE6B00983F6A /* r_explosion.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_explosion.c; sourceTree = ""; }; 7463B74412F9CE6B00983F6A /* r_lightning.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_lightning.c; sourceTree = ""; }; 7463B74512F9CE6B00983F6A /* r_modules.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_modules.c; sourceTree = ""; }; 7463B74612F9CE6B00983F6A /* r_modules.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_modules.h; sourceTree = ""; }; 7463B74712F9CE6B00983F6A /* r_shadow.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_shadow.c; sourceTree = ""; }; 7463B74812F9CE6B00983F6A /* r_shadow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_shadow.h; sourceTree = ""; }; 7463B74912F9CE6B00983F6A /* r_sky.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_sky.c; sourceTree = ""; }; 7463B74A12F9CE6B00983F6A /* r_sprites.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = r_sprites.c; sourceTree = ""; }; 7463B74B12F9CE6B00983F6A /* r_textures.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r_textures.h; sourceTree = ""; }; 7463B74C12F9CE6B00983F6A /* render.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = render.h; sourceTree = ""; }; 7463B74E12F9CE6B00983F6A /* sbar.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sbar.c; sourceTree = ""; }; 7463B74F12F9CE6B00983F6A /* sbar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sbar.h; sourceTree = ""; }; 7463B75012F9CE6B00983F6A /* screen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = screen.h; sourceTree = ""; }; 7463B75112F9CE6B00983F6A /* server.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = server.h; sourceTree = ""; }; 7463B75212F9CE6B00983F6A /* snd_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_main.c; sourceTree = ""; }; 7463B75312F9CE6B00983F6A /* snd_main.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_main.h; sourceTree = ""; }; 7463B75412F9CE6B00983F6A /* snd_mem.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_mem.c; sourceTree = ""; }; 7463B75512F9CE6B00983F6A /* snd_mix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_mix.c; sourceTree = ""; }; 7463B75812F9CE6B00983F6A /* snd_ogg.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_ogg.c; sourceTree = ""; }; 7463B75912F9CE6B00983F6A /* snd_ogg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_ogg.h; sourceTree = ""; }; 7463B75A12F9CE6B00983F6A /* snd_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_sdl.c; sourceTree = ""; }; 7463B75B12F9CE6B00983F6A /* snd_wav.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = snd_wav.c; sourceTree = ""; }; 7463B75C12F9CE6B00983F6A /* snd_wav.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = snd_wav.h; sourceTree = ""; }; 7463B75D12F9CE6B00983F6A /* sound.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sound.h; sourceTree = ""; }; 7463B75E12F9CE6B00983F6A /* spritegn.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = spritegn.h; sourceTree = ""; }; 7463B75F12F9CE6B00983F6A /* sv_demo.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_demo.c; sourceTree = ""; }; 7463B76012F9CE6B00983F6A /* sv_demo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sv_demo.h; sourceTree = ""; }; 7463B76112F9CE6B00983F6A /* sv_main.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_main.c; sourceTree = ""; }; 7463B76212F9CE6B00983F6A /* sv_move.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_move.c; sourceTree = ""; }; 7463B76312F9CE6B00983F6A /* sv_phys.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_phys.c; sourceTree = ""; }; 7463B76412F9CE6B00983F6A /* sv_user.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sv_user.c; sourceTree = ""; }; 7463B76512F9CE6B00983F6A /* svbsp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = svbsp.c; sourceTree = ""; }; 7463B76612F9CE6B00983F6A /* svbsp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = svbsp.h; sourceTree = ""; }; 7463B76712F9CE6B00983F6A /* svvm_cmds.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = svvm_cmds.c; sourceTree = ""; }; 7463B76812F9CE6B00983F6A /* sys_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sys_sdl.c; sourceTree = ""; }; 7463B76912F9CE6B00983F6A /* sys_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sys_shared.c; sourceTree = ""; }; 7463B76A12F9CE6B00983F6A /* sys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sys.h; sourceTree = ""; }; 7463B76C12F9CE6B00983F6A /* utf8lib.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = utf8lib.c; sourceTree = ""; }; 7463B76D12F9CE6B00983F6A /* utf8lib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = utf8lib.h; sourceTree = ""; }; 7463B76E12F9CE6B00983F6A /* vid_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vid_sdl.c; sourceTree = ""; }; 7463B76F12F9CE6B00983F6A /* vid_shared.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = vid_shared.c; sourceTree = ""; }; 7463B77012F9CE6B00983F6A /* vid.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vid.h; sourceTree = ""; }; 7463B77112F9CE6B00983F6A /* view.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = view.c; sourceTree = ""; }; 7463B77212F9CE6B00983F6A /* wad.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = wad.c; sourceTree = ""; }; 7463B77312F9CE6B00983F6A /* wad.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wad.h; sourceTree = ""; }; 7463B77412F9CE6B00983F6A /* world.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = world.c; sourceTree = ""; }; 7463B77512F9CE6B00983F6A /* world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = world.h; sourceTree = ""; }; 7463B77612F9CE6B00983F6A /* zone.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zone.c; sourceTree = ""; }; 7463B77712F9CE6B00983F6A /* zone.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zone.h; sourceTree = ""; }; 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = darkplaces64x64.png; sourceTree = ""; }; 7463B7ED12F9D17D00983F6A /* builddate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = builddate.c; sourceTree = ""; }; 7487D47F130102AA00AEE909 /* thread_sdl.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = thread_sdl.c; sourceTree = ""; }; 7487D480130102AA00AEE909 /* thread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = thread.h; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; FD779EDD0E26BA1200F39101 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; FD77A0840E26BDB800F39101 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 1D60588F0D05DD3D006BFB54 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1D60589F0D05DD5A006BFB54 /* Foundation.framework in Frameworks */, 1DF5F4E00D08C38300B7A737 /* UIKit.framework in Frameworks */, 28FD15000DC6FC520079059D /* OpenGLES.framework in Frameworks */, 28FD15080DC6FC5B0079059D /* QuartzCore.framework in Frameworks */, FD779EDE0E26BA1200F39101 /* CoreAudio.framework in Frameworks */, FD77A0850E26BDB800F39101 /* AudioToolbox.framework in Frameworks */, FDB8BFC60E5A0F6A00980157 /* CoreGraphics.framework in Frameworks */, 74063A491751B9B60015D12C /* libSDL2-ios-armv7.a in Frameworks */, 74063A4A1751B9B60015D12C /* libSDL2-ios-simulator.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 19C28FACFE9D520D11CA2CBB /* Products */ = { isa = PBXGroup; children = ( 1D6058910D05DD3D006BFB54 /* DPiOS.app */, ); name = Products; sourceTree = ""; }; 29B97314FDCFA39411CA2CEA /* CustomTemplate */ = { isa = PBXGroup; children = ( 29B97315FDCFA39411CA2CEA /* Sources */, 29B97317FDCFA39411CA2CEA /* Resources */, FD779EC50E26B99E00F39101 /* Libraries */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 19C28FACFE9D520D11CA2CBB /* Products */, ); name = CustomTemplate; sourceTree = ""; }; 29B97315FDCFA39411CA2CEA /* Sources */ = { isa = PBXGroup; children = ( 74063A411751B0AF0015D12C /* clvm_cmds.c */, 74063A3F1751B0250015D12C /* cd_null.c */, 7463B6C012F9CE6B00983F6A /* bih.c */, 7463B6C112F9CE6B00983F6A /* bih.h */, 7463B6C212F9CE6B00983F6A /* bspfile.h */, 7463B7ED12F9D17D00983F6A /* builddate.c */, 7463B6C312F9CE6B00983F6A /* cap_avi.c */, 7463B6C412F9CE6B00983F6A /* cap_avi.h */, 7463B6C512F9CE6B00983F6A /* cap_ogg.c */, 7463B6C612F9CE6B00983F6A /* cap_ogg.h */, 7463B6C812F9CE6B00983F6A /* cd_shared.c */, 7463B6C912F9CE6B00983F6A /* cdaudio.h */, 7463B6CA12F9CE6B00983F6A /* cl_collision.c */, 7463B6CB12F9CE6B00983F6A /* cl_collision.h */, 7463B6CC12F9CE6B00983F6A /* cl_demo.c */, 7463B6CD12F9CE6B00983F6A /* cl_dyntexture.c */, 7463B6CE12F9CE6B00983F6A /* cl_dyntexture.h */, 7463B6D112F9CE6B00983F6A /* cl_input.c */, 7463B6D212F9CE6B00983F6A /* cl_main.c */, 7463B6D312F9CE6B00983F6A /* cl_parse.c */, 7463B6D412F9CE6B00983F6A /* cl_particles.c */, 7463B6D512F9CE6B00983F6A /* cl_screen.c */, 7463B6D612F9CE6B00983F6A /* cl_screen.h */, 7463B6D712F9CE6B00983F6A /* cl_video.c */, 7463B6D812F9CE6B00983F6A /* cl_video.h */, 7463B6D912F9CE6B00983F6A /* client.h */, 7463B6DA12F9CE6B00983F6A /* clprogdefs.h */, 7463B6DB12F9CE6B00983F6A /* clvm_cmds.h */, 7463B6DC12F9CE6B00983F6A /* cmd.c */, 7463B6DD12F9CE6B00983F6A /* cmd.h */, 7463B6DE12F9CE6B00983F6A /* collision.c */, 7463B6DF12F9CE6B00983F6A /* collision.h */, 7463B6E012F9CE6B00983F6A /* common.c */, 7463B6E112F9CE6B00983F6A /* common.h */, 7463B6E212F9CE6B00983F6A /* console.c */, 7463B6E312F9CE6B00983F6A /* console.h */, 7463B6E412F9CE6B00983F6A /* crypto.c */, 7463B6E512F9CE6B00983F6A /* crypto.h */, 7463B6E612F9CE6B00983F6A /* csprogs.c */, 7463B6E712F9CE6B00983F6A /* csprogs.h */, 7463B6E812F9CE6B00983F6A /* curves.c */, 7463B6E912F9CE6B00983F6A /* curves.h */, 7463B6EA12F9CE6B00983F6A /* cvar.c */, 7463B6EB12F9CE6B00983F6A /* cvar.h */, 7463B6EC12F9CE6B00983F6A /* dpsoftrast.c */, 7463B6ED12F9CE6B00983F6A /* dpsoftrast.h */, 7463B6EE12F9CE6B00983F6A /* dpvsimpledecode.c */, 7463B6EF12F9CE6B00983F6A /* dpvsimpledecode.h */, 7463B6F012F9CE6B00983F6A /* draw.h */, 7463B6F112F9CE6B00983F6A /* filematch.c */, 7463B6F212F9CE6B00983F6A /* fractalnoise.c */, 7463B6F312F9CE6B00983F6A /* fs.c */, 7463B6F412F9CE6B00983F6A /* fs.h */, 7463B6F712F9CE6B00983F6A /* ft2.c */, 7463B6F812F9CE6B00983F6A /* ft2.h */, 7463B6F512F9CE6B00983F6A /* ft2_defs.h */, 7463B6F612F9CE6B00983F6A /* ft2_fontdefs.h */, 7463B6F912F9CE6B00983F6A /* gl_backend.c */, 7463B6FA12F9CE6B00983F6A /* gl_backend.h */, 7463B6FB12F9CE6B00983F6A /* gl_draw.c */, 7463B6FC12F9CE6B00983F6A /* gl_rmain.c */, 7463B6FD12F9CE6B00983F6A /* gl_rsurf.c */, 7463B6FE12F9CE6B00983F6A /* gl_textures.c */, 7463B6FF12F9CE6B00983F6A /* glquake.h */, 7463B70012F9CE6B00983F6A /* hmac.c */, 7463B70112F9CE6B00983F6A /* hmac.h */, 7463B70312F9CE6B00983F6A /* host.c */, 7463B70212F9CE6B00983F6A /* host_cmd.c */, 7463B70612F9CE6B00983F6A /* image.c */, 7463B70712F9CE6B00983F6A /* image.h */, 7463B70412F9CE6B00983F6A /* image_png.c */, 7463B70512F9CE6B00983F6A /* image_png.h */, 7463B70812F9CE6B00983F6A /* input.h */, 7463B70912F9CE6B00983F6A /* intoverflow.h */, 7463B70A12F9CE6B00983F6A /* jpeg.c */, 7463B70B12F9CE6B00983F6A /* jpeg.h */, 7463B70C12F9CE6B00983F6A /* keys.c */, 7463B70D12F9CE6B00983F6A /* keys.h */, 7463B70E12F9CE6B00983F6A /* lhfont.h */, 7463B70F12F9CE6B00983F6A /* lhnet.c */, 7463B71012F9CE6B00983F6A /* lhnet.h */, 7463B71112F9CE6B00983F6A /* libcurl.c */, 7463B71212F9CE6B00983F6A /* libcurl.h */, 7463B71312F9CE6B00983F6A /* mathlib.c */, 7463B71412F9CE6B00983F6A /* mathlib.h */, 7463B71512F9CE6B00983F6A /* matrixlib.c */, 7463B71612F9CE6B00983F6A /* matrixlib.h */, 7463B71712F9CE6B00983F6A /* mdfour.c */, 7463B71812F9CE6B00983F6A /* mdfour.h */, 7463B71912F9CE6B00983F6A /* menu.c */, 7463B71A12F9CE6B00983F6A /* menu.h */, 7463B71B12F9CE6B00983F6A /* meshqueue.c */, 7463B71C12F9CE6B00983F6A /* meshqueue.h */, 7463B71D12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c */, 7463B71E12F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.h */, 74063A3C1751ADDA0015D12C /* mod_skeletal_animatevertices_sse.c */, 74063A3D1751ADDA0015D12C /* mod_skeletal_animatevertices_sse.h */, 7463B71F12F9CE6B00983F6A /* model_alias.c */, 7463B72012F9CE6B00983F6A /* model_alias.h */, 7463B72112F9CE6B00983F6A /* model_brush.c */, 7463B72212F9CE6B00983F6A /* model_brush.h */, 7463B72312F9CE6B00983F6A /* model_dpmodel.h */, 7463B72412F9CE6B00983F6A /* model_iqm.h */, 7463B72512F9CE6B00983F6A /* model_psk.h */, 7463B72612F9CE6B00983F6A /* model_shared.c */, 7463B72712F9CE6B00983F6A /* model_shared.h */, 7463B72812F9CE6B00983F6A /* model_sprite.c */, 7463B72912F9CE6B00983F6A /* model_sprite.h */, 7463B72A12F9CE6B00983F6A /* model_zymotic.h */, 7463B72B12F9CE6B00983F6A /* modelgen.h */, 7463B72C12F9CE6B00983F6A /* mprogdefs.h */, 7463B72D12F9CE6B00983F6A /* mvm_cmds.c */, 7463B72E12F9CE6B00983F6A /* netconn.c */, 7463B72F12F9CE6B00983F6A /* netconn.h */, 7463B73012F9CE6B00983F6A /* palette.c */, 7463B73112F9CE6B00983F6A /* palette.h */, 7463B73212F9CE6B00983F6A /* polygon.c */, 7463B73312F9CE6B00983F6A /* polygon.h */, 7463B73412F9CE6B00983F6A /* portals.c */, 7463B73512F9CE6B00983F6A /* portals.h */, 7463B73612F9CE6B00983F6A /* pr_comp.h */, 7463B73712F9CE6B00983F6A /* progdefs.h */, 7463B73812F9CE6B00983F6A /* progs.h */, 7463B73912F9CE6B00983F6A /* progsvm.h */, 7463B73A12F9CE6B00983F6A /* protocol.c */, 7463B73B12F9CE6B00983F6A /* protocol.h */, 7463B73C12F9CE6B00983F6A /* prvm_cmds.c */, 7463B73D12F9CE6B00983F6A /* prvm_cmds.h */, 7463B73E12F9CE6B00983F6A /* prvm_edict.c */, 7463B73F12F9CE6B00983F6A /* prvm_exec.c */, 7463B74012F9CE6B00983F6A /* prvm_execprogram.h */, 7463B74112F9CE6B00983F6A /* qtypes.h */, 7463B74212F9CE6B00983F6A /* quakedef.h */, 7463B74312F9CE6B00983F6A /* r_explosion.c */, 7463B74412F9CE6B00983F6A /* r_lightning.c */, 7463B74512F9CE6B00983F6A /* r_modules.c */, 7463B74612F9CE6B00983F6A /* r_modules.h */, 7463B74712F9CE6B00983F6A /* r_shadow.c */, 7463B74812F9CE6B00983F6A /* r_shadow.h */, 7463B74912F9CE6B00983F6A /* r_sky.c */, 7463B74A12F9CE6B00983F6A /* r_sprites.c */, 7463B74B12F9CE6B00983F6A /* r_textures.h */, 7463B74C12F9CE6B00983F6A /* render.h */, 7463B74E12F9CE6B00983F6A /* sbar.c */, 7463B74F12F9CE6B00983F6A /* sbar.h */, 7463B75012F9CE6B00983F6A /* screen.h */, 7463B75112F9CE6B00983F6A /* server.h */, 7463B75212F9CE6B00983F6A /* snd_main.c */, 7463B75312F9CE6B00983F6A /* snd_main.h */, 7463B75412F9CE6B00983F6A /* snd_mem.c */, 7463B75512F9CE6B00983F6A /* snd_mix.c */, 7463B75812F9CE6B00983F6A /* snd_ogg.c */, 7463B75912F9CE6B00983F6A /* snd_ogg.h */, 7463B75A12F9CE6B00983F6A /* snd_sdl.c */, 7463B75B12F9CE6B00983F6A /* snd_wav.c */, 7463B75C12F9CE6B00983F6A /* snd_wav.h */, 7463B75D12F9CE6B00983F6A /* sound.h */, 7463B75E12F9CE6B00983F6A /* spritegn.h */, 7463B75F12F9CE6B00983F6A /* sv_demo.c */, 7463B76012F9CE6B00983F6A /* sv_demo.h */, 7463B76112F9CE6B00983F6A /* sv_main.c */, 7463B76212F9CE6B00983F6A /* sv_move.c */, 7463B76312F9CE6B00983F6A /* sv_phys.c */, 7463B76412F9CE6B00983F6A /* sv_user.c */, 7463B76512F9CE6B00983F6A /* svbsp.c */, 7463B76612F9CE6B00983F6A /* svbsp.h */, 7463B76712F9CE6B00983F6A /* svvm_cmds.c */, 7463B76A12F9CE6B00983F6A /* sys.h */, 7463B76812F9CE6B00983F6A /* sys_sdl.c */, 7463B76912F9CE6B00983F6A /* sys_shared.c */, 7487D480130102AA00AEE909 /* thread.h */, 7487D47F130102AA00AEE909 /* thread_sdl.c */, 7463B76C12F9CE6B00983F6A /* utf8lib.c */, 7463B76D12F9CE6B00983F6A /* utf8lib.h */, 7463B77012F9CE6B00983F6A /* vid.h */, 7463B76E12F9CE6B00983F6A /* vid_sdl.c */, 7463B76F12F9CE6B00983F6A /* vid_shared.c */, 7463B77112F9CE6B00983F6A /* view.c */, 7463B77212F9CE6B00983F6A /* wad.c */, 7463B77312F9CE6B00983F6A /* wad.h */, 7463B77412F9CE6B00983F6A /* world.c */, 7463B77512F9CE6B00983F6A /* world.h */, 7463B77612F9CE6B00983F6A /* zone.c */, 7463B77712F9CE6B00983F6A /* zone.h */, ); name = Sources; sourceTree = ""; }; 29B97317FDCFA39411CA2CEA /* Resources */ = { isa = PBXGroup; children = ( 74063A501751C4560015D12C /* gamedata */, 7463B7D812F9CF8F00983F6A /* darkplaces64x64.png */, 8D1107310486CEB800E47090 /* Info.plist */, ); name = Resources; sourceTree = ""; }; 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( FDB8BFC50E5A0F6A00980157 /* CoreGraphics.framework */, FD77A0840E26BDB800F39101 /* AudioToolbox.framework */, FD779EDD0E26BA1200F39101 /* CoreAudio.framework */, 28FD15070DC6FC5B0079059D /* QuartzCore.framework */, 28FD14FF0DC6FC520079059D /* OpenGLES.framework */, 1DF5F4DF0D08C38300B7A737 /* UIKit.framework */, 1D30AB110D05D00D00671497 /* Foundation.framework */, ); name = Frameworks; sourceTree = ""; }; FD779EC50E26B99E00F39101 /* Libraries */ = { isa = PBXGroup; children = ( 74063A471751B9B60015D12C /* libSDL2-ios-armv7.a */, 74063A481751B9B60015D12C /* libSDL2-ios-simulator.a */, ); name = Libraries; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 1D6058900D05DD3D006BFB54 /* DPiOS */ = { isa = PBXNativeTarget; buildConfigurationList = 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "DPiOS" */; buildPhases = ( 1D60588D0D05DD3D006BFB54 /* Resources */, 1D60588E0D05DD3D006BFB54 /* Sources */, 1D60588F0D05DD3D006BFB54 /* Frameworks */, ); buildRules = ( ); dependencies = ( ); name = DPiOS; productName = DPiOS; productReference = 1D6058910D05DD3D006BFB54 /* DPiOS.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0420; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DPiOS" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 1; knownRegions = ( English, Japanese, French, German, ); mainGroup = 29B97314FDCFA39411CA2CEA /* CustomTemplate */; projectDirPath = ""; projectRoot = ""; targets = ( 1D6058900D05DD3D006BFB54 /* DPiOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 1D60588D0D05DD3D006BFB54 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 7463B7D912F9CF8F00983F6A /* darkplaces64x64.png in Resources */, 74063A511751C4560015D12C /* gamedata in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 1D60588E0D05DD3D006BFB54 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7463B77812F9CE6B00983F6A /* bih.c in Sources */, 7463B77912F9CE6B00983F6A /* cap_avi.c in Sources */, 7463B77A12F9CE6B00983F6A /* cap_ogg.c in Sources */, 7463B77C12F9CE6B00983F6A /* cd_shared.c in Sources */, 7463B77D12F9CE6B00983F6A /* cl_collision.c in Sources */, 7463B77E12F9CE6B00983F6A /* cl_demo.c in Sources */, 7463B77F12F9CE6B00983F6A /* cl_dyntexture.c in Sources */, 7463B78112F9CE6B00983F6A /* cl_input.c in Sources */, 7463B78212F9CE6B00983F6A /* cl_main.c in Sources */, 7463B78312F9CE6B00983F6A /* cl_parse.c in Sources */, 7463B78412F9CE6B00983F6A /* cl_particles.c in Sources */, 7463B78512F9CE6B00983F6A /* cl_screen.c in Sources */, 7463B78612F9CE6B00983F6A /* cl_video.c in Sources */, 7463B78712F9CE6B00983F6A /* cmd.c in Sources */, 7463B78812F9CE6B00983F6A /* collision.c in Sources */, 7463B78912F9CE6B00983F6A /* common.c in Sources */, 7463B78A12F9CE6B00983F6A /* console.c in Sources */, 7463B78B12F9CE6B00983F6A /* crypto.c in Sources */, 7463B78C12F9CE6B00983F6A /* csprogs.c in Sources */, 7463B78D12F9CE6B00983F6A /* curves.c in Sources */, 7463B78E12F9CE6B00983F6A /* cvar.c in Sources */, 7463B78F12F9CE6B00983F6A /* dpsoftrast.c in Sources */, 7463B79012F9CE6B00983F6A /* dpvsimpledecode.c in Sources */, 7463B79112F9CE6B00983F6A /* filematch.c in Sources */, 7463B79212F9CE6B00983F6A /* fractalnoise.c in Sources */, 7463B79312F9CE6B00983F6A /* fs.c in Sources */, 7463B79412F9CE6B00983F6A /* ft2.c in Sources */, 7463B79512F9CE6B00983F6A /* gl_backend.c in Sources */, 7463B79612F9CE6B00983F6A /* gl_draw.c in Sources */, 7463B79712F9CE6B00983F6A /* gl_rmain.c in Sources */, 7463B79812F9CE6B00983F6A /* gl_rsurf.c in Sources */, 7463B79912F9CE6B00983F6A /* gl_textures.c in Sources */, 7463B79A12F9CE6B00983F6A /* hmac.c in Sources */, 7463B79B12F9CE6B00983F6A /* host_cmd.c in Sources */, 7463B79C12F9CE6B00983F6A /* host.c in Sources */, 7463B79D12F9CE6B00983F6A /* image_png.c in Sources */, 7463B79E12F9CE6B00983F6A /* image.c in Sources */, 7463B79F12F9CE6B00983F6A /* jpeg.c in Sources */, 7463B7A012F9CE6B00983F6A /* keys.c in Sources */, 7463B7A112F9CE6B00983F6A /* lhnet.c in Sources */, 7463B7A212F9CE6B00983F6A /* libcurl.c in Sources */, 7463B7A312F9CE6B00983F6A /* mathlib.c in Sources */, 7463B7A412F9CE6B00983F6A /* matrixlib.c in Sources */, 7463B7A512F9CE6B00983F6A /* mdfour.c in Sources */, 7463B7A612F9CE6B00983F6A /* menu.c in Sources */, 7463B7A712F9CE6B00983F6A /* meshqueue.c in Sources */, 7463B7A812F9CE6B00983F6A /* mod_skeletal_animatevertices_generic.c in Sources */, 7463B7A912F9CE6B00983F6A /* model_alias.c in Sources */, 7463B7AA12F9CE6B00983F6A /* model_brush.c in Sources */, 7463B7AB12F9CE6B00983F6A /* model_shared.c in Sources */, 7463B7AC12F9CE6B00983F6A /* model_sprite.c in Sources */, 7463B7AD12F9CE6B00983F6A /* mvm_cmds.c in Sources */, 7463B7AE12F9CE6B00983F6A /* netconn.c in Sources */, 7463B7AF12F9CE6B00983F6A /* palette.c in Sources */, 7463B7B012F9CE6B00983F6A /* polygon.c in Sources */, 7463B7B112F9CE6B00983F6A /* portals.c in Sources */, 7463B7B212F9CE6B00983F6A /* protocol.c in Sources */, 7463B7B312F9CE6B00983F6A /* prvm_cmds.c in Sources */, 7463B7B412F9CE6B00983F6A /* prvm_edict.c in Sources */, 7463B7B512F9CE6B00983F6A /* prvm_exec.c in Sources */, 7463B7B612F9CE6B00983F6A /* r_explosion.c in Sources */, 7463B7B712F9CE6B00983F6A /* r_lightning.c in Sources */, 7463B7B812F9CE6B00983F6A /* r_modules.c in Sources */, 7463B7B912F9CE6B00983F6A /* r_shadow.c in Sources */, 7463B7BA12F9CE6B00983F6A /* r_sky.c in Sources */, 7463B7BB12F9CE6B00983F6A /* r_sprites.c in Sources */, 7463B7BC12F9CE6B00983F6A /* sbar.c in Sources */, 7463B7BD12F9CE6B00983F6A /* snd_main.c in Sources */, 7463B7BE12F9CE6B00983F6A /* snd_mem.c in Sources */, 7463B7BF12F9CE6B00983F6A /* snd_mix.c in Sources */, 7463B7C112F9CE6B00983F6A /* snd_ogg.c in Sources */, 7463B7C212F9CE6B00983F6A /* snd_sdl.c in Sources */, 7463B7C312F9CE6B00983F6A /* snd_wav.c in Sources */, 7463B7C412F9CE6B00983F6A /* sv_demo.c in Sources */, 7463B7C512F9CE6B00983F6A /* sv_main.c in Sources */, 7463B7C612F9CE6B00983F6A /* sv_move.c in Sources */, 7463B7C712F9CE6B00983F6A /* sv_phys.c in Sources */, 7463B7C812F9CE6B00983F6A /* sv_user.c in Sources */, 7463B7C912F9CE6B00983F6A /* svbsp.c in Sources */, 7463B7CA12F9CE6B00983F6A /* svvm_cmds.c in Sources */, 7463B7CB12F9CE6B00983F6A /* sys_sdl.c in Sources */, 7463B7CC12F9CE6B00983F6A /* sys_shared.c in Sources */, 7463B7CD12F9CE6B00983F6A /* utf8lib.c in Sources */, 7463B7CE12F9CE6B00983F6A /* vid_sdl.c in Sources */, 7463B7CF12F9CE6B00983F6A /* vid_shared.c in Sources */, 7463B7D012F9CE6B00983F6A /* view.c in Sources */, 7463B7D112F9CE6B00983F6A /* wad.c in Sources */, 7463B7D212F9CE6B00983F6A /* world.c in Sources */, 7463B7D312F9CE6B00983F6A /* zone.c in Sources */, 7463B7EF12F9D17D00983F6A /* builddate.c in Sources */, 7463B7F012F9D17D00983F6A /* (null) in Sources */, 7487D481130102AA00AEE909 /* thread_sdl.c in Sources */, 74063A3E1751ADDB0015D12C /* mod_skeletal_animatevertices_sse.c in Sources */, 74063A401751B0250015D12C /* cd_null.c in Sources */, 74063A421751B0AF0015D12C /* clvm_cmds.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ 1D6058940D05DD3E006BFB54 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; COPY_PHASE_STRIP = NO; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( __IPHONEOS__, USE_GLES2, "FORCEGAME=\\\"steelstorm\\\"", "DP_FS_BASEDIR=\\\"\\\"", "DP_GAME_STEELSTORM=1", ); INFOPLIST_FILE = Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/lib\"", ); PRODUCT_NAME = DPiOS; TARGETED_DEVICE_FAMILY = 2; USER_HEADER_SEARCH_PATHS = ""; }; name = Debug; }; 1D6058950D05DD3E006BFB54 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = YES; COPY_PHASE_STRIP = YES; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = ""; GCC_PREPROCESSOR_DEFINITIONS = ( __IPHONEOS__, USE_GLES2, "FORCEGAME=\\\"steelstorm\\\"", "DP_FS_BASEDIR=\\\"\\\"", "DP_GAME_STEELSTORM=1", ); INFOPLIST_FILE = Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 5.0; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "\"$(SRCROOT)/lib\"", ); PRODUCT_NAME = DPiOS; TARGETED_DEVICE_FAMILY = 2; }; name = Release; }; C01FCF4F08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/lib"; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ( "-ObjC", "-lz", ); PRODUCT_NAME = Darkplaces; SDKROOT = iphoneos; VALID_ARCHS = armv7; }; name = Debug; }; C01FCF5008A954540054247B /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; GCC_C_LANGUAGE_STANDARD = "compiler-default"; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; HEADER_SEARCH_PATHS = "$(SRCROOT)/include"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/lib"; OTHER_CFLAGS = ""; OTHER_LDFLAGS = ( "-ObjC", "-lz", ); PRODUCT_NAME = Darkplaces; SDKROOT = iphoneos; VALID_ARCHS = armv7; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 1D6058960D05DD3E006BFB54 /* Build configuration list for PBXNativeTarget "DPiOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 1D6058940D05DD3E006BFB54 /* Debug */, 1D6058950D05DD3E006BFB54 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; C01FCF4E08A954540054247B /* Build configuration list for PBXProject "DPiOS" */ = { isa = XCConfigurationList; buildConfigurations = ( C01FCF4F08A954540054247B /* Debug */, C01FCF5008A954540054247B /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } darkplaces/DPiOS.xcodeproj/project.xcworkspace/0000775000175000017500000000000013067716406021101 5ustar kalevkalevdarkplaces/DPiOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata0000664000175000017500000000022613067716216026042 0ustar kalevkalev darkplaces/darkplaces24x24.png0000664000175000017500000000273613067716220015516 0ustar kalevkalev‰PNG  IHDRàw=ø¥IDATxœ•_L[×Ç?¶9@lphR'%†Ò¸ªÆÒú'­ i“© “Z¥jûÐÇ)S§iÓ¤IS^÷°—>ŒuÒª&J4ec­”²%d4)I˜%Ø؆k ÆÁ_›ë»K¨šéèÚº÷÷ýþÎ÷÷ý”Ò"„¨BHÀÕîÝÓÛóòk¾oîÜ.F"‘ ,ƒÁôØÔƒÅ纻£#W¯½ÔwhïÉí/8&''W"P6mß·÷÷÷×½ûÚsïÿ=þ»=mmoGg|v»³áîÂRAÓ´40„€ ¯iZ¡å oñO_\þñå/>ëøvÒojš–Ò€nÝ^uæÌ{©á ×¢ÿþùÁ7zç&xÓž§žþ¤ÓiSIj£lxÌܸqãvOï±Ë¿üÉË5^¯·èž\Vnس6ôÇ¿5úŽì¼)›CCÃÿjò9ÃØŸ0•¶›@ImØP' æ þ>K¬Ÿ={Vêº~84VN —nÿæó±±1þ211ÑÄ€ew½#vùÒ%‹Åb))CŘŠH’###á*Wýbkkk5дOZ¥”–¦†ÞöêÔ‰ß_¸žݦŽ¾ØÛÛ›^‹/¶4gBf“ÛU½ÿ~°(™ª(ÛÜ¢HRVû¶‚’O*’Fˆ¹hì•Ÿžî–¿ýúëu%Ã# bFÔÕòÌgc««ÇÖã‰f§Ó)úûúZë.êÜuÉ¥¥¥ñ‹/FUAKÚt¬^ÉVj‡PÙ´=ÊÉ‹•lVØ•+W”¨x|>_ÍØ?.õ¾ßR{:ØùæÅDbEcÀÜÀÀ€w×à`Ó©Ãy)¥Y!ª1ímíĪE¬Êõ×Y¨¿sçÎöŽ¶Ž¿¾~õ)_÷+m~¿?£úb}þæÈÏÆ€º¢mÈ©¸´À]+Ÿüê¦ÓV²«–QNÉüÀƒ¦††<ŽújÃ0¶;Æ?8sfø?ÓÇ:NžŒßòiHØ„6Ossiv)ùÖ©7z¶ik¹d2©)и’HW[48jßÚ¼c`Öãñ¹«}Ú­k¯ß˜|0ssâ©T*L“À¬ØŒÌk+¯žxóó®ì=Û6½I9 pªUì¹O,×é;aÑ3W<ø°=k·Ùó^ßõÁ -D"‘Yட–…’cíüWW¾”¯÷t¶—ü?ò¼sª+žŸ¹uëÖ‚*VÈ–ÎöâÙ£G±ÕÔä–— kË÷Â9瀛ê»0T­ ]×M)eÿùË«Ÿ=Ü•õøG^òÔ=ñaçG=LJLÓô—J¥…¡¡!»c‡;«¬ °®§ࢪYB™D·!*­ŸŸÓ¢‰û‰\¼ÊÝhlfÓÛk­Öªd¾X,¦‚Á ¥½¥éз££m÷g‚Ét:n+YŠäÑ–ºm ]×KRÊu`A¹gejjêàVÙš”GeÚ¨®óg×VSš¦¥€%3¯ ±¡â ÀÔuÝÜ:Ñ ÊYTÏ9 ŽrÛç•e‹¹‚q×ãa•éâ9r*k³2Í`ËD3 !DåÆÌ+}W)ß–«ê!”öµîuNM?L©$"[²7¶‚C¹‹¿·Ôð¶P¾¸*¿+[×õ}RʧiЀŒ®ë›ßÅú/§˜lN¡–IEND®B`‚darkplaces/darkplaces-sdl2-vs2013.vcxproj0000664000175000017500000004464113067716220017520 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2} darkplacessdl2 Win32Proj darkplaces-sdl2-vs2013 Application v120 MultiByte true Application v120 MultiByte Application v120 MultiByte true Application v120 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/cl_collision.c0000664000175000017500000010751013067716216015011 0ustar kalevkalev #include "quakedef.h" #include "cl_collision.h" float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent) { float maxfrac; int n; entity_render_t *ent; vec_t tracemins[3], tracemaxs[3]; trace_t trace; vec_t tempnormal[3], starttransformed[3], endtransformed[3]; memset (&trace, 0 , sizeof(trace_t)); trace.fraction = 1; VectorCopy (end, trace.endpos); if (hitent) *hitent = 0; if (cl.worldmodel && cl.worldmodel->TraceLine) cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, start, end, SUPERCONTENTS_SOLID, 0); if (normal) VectorCopy(trace.plane.normal, normal); maxfrac = trace.fraction; tracemins[0] = min(start[0], end[0]); tracemaxs[0] = max(start[0], end[0]); tracemins[1] = min(start[1], end[1]); tracemaxs[1] = max(start[1], end[1]); tracemins[2] = min(start[2], end[2]); tracemaxs[2] = max(start[2], end[2]); // look for embedded bmodels for (n = 0;n < cl.num_entities;n++) { if (!cl.entities_active[n]) continue; ent = &cl.entities[n].render; if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs)) continue; if (!ent->model || !ent->model->TraceLine) continue; if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer) continue; // if transparent and not selectable, skip entity if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST)))) continue; if (ent == ignoreent) continue; Matrix4x4_Transform(&ent->inversematrix, start, starttransformed); Matrix4x4_Transform(&ent->inversematrix, end, endtransformed); Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, 0, SUPERCONTENTS_SOLID, 0, NULL); if (maxfrac < trace.fraction) continue; ent->model->TraceLine(ent->model, ent->frameblend, ent->skeleton, &trace, starttransformed, endtransformed, SUPERCONTENTS_SOLID, 0); if (maxfrac > trace.fraction) { if (hitent) *hitent = n; maxfrac = trace.fraction; if (normal) { VectorCopy(trace.plane.normal, tempnormal); Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal); } } } maxfrac = bound(0, maxfrac, 1); //maxrealfrac = bound(0, maxrealfrac, 1); //if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__); if (impact) VectorLerp(start, maxfrac, end, impact); return maxfrac; } void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius) { // FIXME: check multiple brush models if (cl.worldmodel && cl.worldmodel->brush.FindNonSolidLocation) cl.worldmodel->brush.FindNonSolidLocation(cl.worldmodel, in, out, radius); } dp_model_t *CL_GetModelByIndex(int modelindex) { if(!modelindex) return NULL; if (modelindex < 0) { modelindex = -(modelindex+1); if (modelindex < MAX_MODELS) return cl.csqc_model_precache[modelindex]; } else { if(modelindex < MAX_MODELS) return cl.model_precache[modelindex]; } return NULL; } dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed) { prvm_prog_t *prog = CLVM_prog; if (!ed || ed->priv.server->free) return NULL; return CL_GetModelByIndex((int)PRVM_clientedictfloat(ed, modelindex)); } void CL_LinkEdict(prvm_edict_t *ent) { prvm_prog_t *prog = CLVM_prog; vec3_t mins, maxs; if (ent == prog->edicts) return; // don't add the world if (ent->priv.server->free) return; // set the abs box if (PRVM_clientedictfloat(ent, solid) == SOLID_BSP) { dp_model_t *model = CL_GetModelByIndex( (int)PRVM_clientedictfloat(ent, modelindex) ); if (model == NULL) { Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent)); model = CL_GetModelByIndex( 0 ); } if( model != NULL ) { if (!model->TraceBox) Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent)); if (PRVM_clientedictvector(ent, angles)[0] || PRVM_clientedictvector(ent, angles)[2] || PRVM_clientedictvector(ent, avelocity)[0] || PRVM_clientedictvector(ent, avelocity)[2]) { VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmins, mins); VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmaxs, maxs); } else if (PRVM_clientedictvector(ent, angles)[1] || PRVM_clientedictvector(ent, avelocity)[1]) { VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmins, mins); VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmaxs, maxs); } else { VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmins, mins); VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmaxs, maxs); } } else { // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); } } else { VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins); VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs); } VectorCopy(mins, PRVM_clientedictvector(ent, absmin)); VectorCopy(maxs, PRVM_clientedictvector(ent, absmax)); World_LinkEdict(&cl.world, ent, mins, maxs); } int CL_GenericHitSuperContentsMask(const prvm_edict_t *passedict) { prvm_prog_t *prog = CLVM_prog; if (passedict) { int dphitcontentsmask = (int)PRVM_clientedictfloat(passedict, dphitcontentsmask); if (dphitcontentsmask) return dphitcontentsmask; else if (PRVM_clientedictfloat(passedict, solid) == SOLID_SLIDEBOX) { if ((int)PRVM_clientedictfloat(passedict, flags) & FL_MONSTER) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP; else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP; } else if (PRVM_clientedictfloat(passedict, solid) == SOLID_CORPSE) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; else if (PRVM_clientedictfloat(passedict, solid) == SOLID_TRIGGER) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; } else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; } /* ================== CL_Move ================== */ trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) { prvm_prog_t *prog = CLVM_prog; int i, bodysupercontents; int passedictprog; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may need conversion vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (hitnetworkentity) *hitnetworkentity = 0; VectorCopy(start, clipstart); VectorClear(clipmins2); VectorClear(clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f)", clipstart[0], clipstart[1], clipstart[2]); #endif // clip to world Collision_ClipPointToWorld(&cliptrace, cl.worldmodel, clipstart, hitsupercontentsmask, skipsupercontentsmask); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog ? prog->edicts : NULL; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = clipstart[i] - 1; clipboxmaxs[i] = clipstart[i] + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) // this checks prog because this function is often called without a CSQC // VM context if (prog == NULL || passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; // collide against network entities if (hitnetworkbrushmodels) { for (i = 0;i < cl.num_brushmodel_entities;i++) { entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) continue; Collision_ClipPointToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, hitsupercontentsmask, skipsupercontentsmask); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = cl.brushmodel_entities[i]; Collision_CombineTraces(&cliptrace, &trace, NULL, true); } } // collide against player entities if (hitnetworkplayers) { vec3_t origin, entmins, entmaxs; matrix4x4_t entmatrix, entinversematrix; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit network players, if we are a nonsolid player if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) goto skipnetworkplayers; } for (i = 1;i <= cl.maxclients;i++) { entity_render_t *ent = &cl.entities[i].render; // don't hit ourselves if (i == cl.playerentity) continue; // don't hit players that don't exist if (!cl.entities_active[i]) continue; if (!cl.scores[i-1].name[0]) continue; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit spectators or nonsolid players if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) continue; } Matrix4x4_OriginFromMatrix(&ent->matrix, origin); VectorAdd(origin, cl.playerstandmins, entmins); VectorAdd(origin, cl.playerstandmaxs, entmaxs); if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) continue; Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); Collision_ClipPointToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, hitsupercontentsmask, skipsupercontentsmask); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = i; Collision_CombineTraces(&cliptrace, &trace, NULL, false); } skipnetworkplayers: ; } // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below // note: if prog is NULL then there won't be any linked entities numtouchedicts = 0; if (hitcsqcentities && prog != NULL) { numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_clientedictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) model = CL_GetModelFromEdict(touch); if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VectorCopy(PRVM_clientedictvector(touch, mins), touchmins); VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs); if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipstart, hitsupercontentsmask, skipsupercontentsmask, 0.0f); else Collision_ClipPointToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, hitsupercontentsmask, skipsupercontentsmask); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = 0; Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } /* ================== CL_TraceLine ================== */ trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces) { prvm_prog_t *prog = CLVM_prog; int i, bodysupercontents; int passedictprog; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may need conversion vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (VectorCompare(start, end)) return CL_TracePoint(start, type, passedict, hitsupercontentsmask, skipsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); if (hitnetworkentity) *hitnetworkentity = 0; VectorCopy(start, clipstart); VectorCopy(end, clipend); VectorClear(clipmins2); VectorClear(clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_ClipLineToWorld(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask, extend, hitsurfaces); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog ? prog->edicts : NULL; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) // this checks prog because this function is often called without a CSQC // VM context if (prog == NULL || passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; // collide against network entities if (hitnetworkbrushmodels) { for (i = 0;i < cl.num_brushmodel_entities;i++) { entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) continue; Collision_ClipLineToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, end, hitsupercontentsmask, skipsupercontentsmask, extend, hitsurfaces); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = cl.brushmodel_entities[i]; Collision_CombineTraces(&cliptrace, &trace, NULL, true); } } // collide against player entities if (hitnetworkplayers) { vec3_t origin, entmins, entmaxs; matrix4x4_t entmatrix, entinversematrix; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit network players, if we are a nonsolid player if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) goto skipnetworkplayers; } for (i = 1;i <= cl.maxclients;i++) { entity_render_t *ent = &cl.entities[i].render; // don't hit ourselves if (i == cl.playerentity) continue; // don't hit players that don't exist if (!cl.entities_active[i]) continue; if (!cl.scores[i-1].name[0]) continue; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit spectators or nonsolid players if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) continue; } Matrix4x4_OriginFromMatrix(&ent->matrix, origin); VectorAdd(origin, cl.playerstandmins, entmins); VectorAdd(origin, cl.playerstandmaxs, entmaxs); if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) continue; Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); Collision_ClipLineToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, end, hitsupercontentsmask, skipsupercontentsmask, extend, hitsurfaces); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = i; Collision_CombineTraces(&cliptrace, &trace, NULL, false); } skipnetworkplayers: ; } // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below // note: if prog is NULL then there won't be any linked entities numtouchedicts = 0; if (hitcsqcentities && prog != NULL) { numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_clientedictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) model = CL_GetModelFromEdict(touch); if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VectorCopy(PRVM_clientedictvector(touch, mins), touchmins); VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs); if (type == MOVE_MISSILE && (int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); else Collision_ClipLineToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask, extend, hitsurfaces); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = 0; Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } /* ================== CL_Move ================== */ trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities) { prvm_prog_t *prog = CLVM_prog; vec3_t hullmins, hullmaxs; int i, bodysupercontents; int passedictprog; qboolean pointtrace; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may need conversion vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size of the moving object vec3_t clipmins, clipmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (VectorCompare(mins, maxs)) { vec3_t shiftstart, shiftend; VectorAdd(start, mins, shiftstart); VectorAdd(end, mins, shiftend); if (VectorCompare(start, end)) trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, skipsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities); else trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, skipsupercontentsmask, extend, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false); VectorSubtract(trace.endpos, mins, trace.endpos); return trace; } if (hitnetworkentity) *hitnetworkentity = 0; VectorCopy(start, clipstart); VectorCopy(end, clipend); VectorCopy(mins, clipmins); VectorCopy(maxs, clipmaxs); VectorCopy(mins, clipmins2); VectorCopy(maxs, clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_ClipToWorld(&cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog ? prog->edicts : NULL; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp if (cl.worldmodel && cl.worldmodel->brush.RoundUpToHullSize) cl.worldmodel->brush.RoundUpToHullSize(cl.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs); else { VectorCopy(clipmins, hullmins); VectorCopy(clipmaxs, hullmaxs); } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) // this checks prog because this function is often called without a CSQC // VM context if (prog == NULL || passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0; // figure out whether this is a point trace for comparisons pointtrace = VectorCompare(clipmins, clipmaxs); // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL; // collide against network entities if (hitnetworkbrushmodels) { for (i = 0;i < cl.num_brushmodel_entities;i++) { entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) continue; Collision_ClipToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, mins, maxs, end, hitsupercontentsmask, skipsupercontentsmask, extend); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = cl.brushmodel_entities[i]; Collision_CombineTraces(&cliptrace, &trace, NULL, true); } } // collide against player entities if (hitnetworkplayers) { vec3_t origin, entmins, entmaxs; matrix4x4_t entmatrix, entinversematrix; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit network players, if we are a nonsolid player if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616) goto skipnetworkplayers; } for (i = 1;i <= cl.maxclients;i++) { entity_render_t *ent = &cl.entities[i].render; // don't hit ourselves if (i == cl.playerentity) continue; // don't hit players that don't exist if (!cl.entities_active[i]) continue; if (!cl.scores[i-1].name[0]) continue; if(IS_OLDNEXUIZ_DERIVED(gamemode)) { // don't hit spectators or nonsolid players if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616) continue; } Matrix4x4_OriginFromMatrix(&ent->matrix, origin); VectorAdd(origin, cl.playerstandmins, entmins); VectorAdd(origin, cl.playerstandmaxs, entmaxs); if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs)) continue; Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]); Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]); Collision_ClipToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, mins, maxs, end, hitsupercontentsmask, skipsupercontentsmask, extend); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = i; Collision_CombineTraces(&cliptrace, &trace, NULL, false); } skipnetworkplayers: ; } // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below // note: if prog is NULL then there won't be any linked entities numtouchedicts = 0; if (hitcsqcentities && prog != NULL) { numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_clientedictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (pointtrace && VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) model = CL_GetModelFromEdict(touch); if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VectorCopy(PRVM_clientedictvector(touch, mins), touchmins); VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs); if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); else Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); if (cliptrace.fraction > trace.fraction && hitnetworkentity) *hitnetworkentity = 0; Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } /* ================== CL_Cache_TraceLine ================== */ trace_t CL_Cache_TraceLineSurfaces(const vec3_t start, const vec3_t end, int type, int hitsupercontentsmask, int skipsupercontentsmask) { prvm_prog_t *prog = CLVM_prog; int i; prvm_edict_t *touch; trace_t trace; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; VectorCopy(start, clipstart); VectorCopy(end, clipend); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_Cache_ClipLineToWorldSurfaces(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog ? prog->edicts : NULL; if (type == MOVE_WORLDONLY) goto finished; // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + 1; } // if the passedict is world, make it NULL (to avoid two checks each time) // this checks prog because this function is often called without a CSQC // VM context // collide against network entities for (i = 0;i < cl.num_brushmodel_entities;i++) { entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render; if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs)) continue; Collision_Cache_ClipLineToGenericEntitySurfaces(&trace, ent->model, &ent->matrix, &ent->inversematrix, start, end, hitsupercontentsmask, skipsupercontentsmask); Collision_CombineTraces(&cliptrace, &trace, NULL, true); } // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below // note: if prog is NULL then there won't be any linked entities numtouchedicts = 0; if (prog != NULL) { numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; // might interact, so do an exact clip // only hit entity models, not collision shapes model = CL_GetModelFromEdict(touch); if (!model) continue; // animated models are too slow to collide against and can't be cached if (touch->priv.server->frameblend || touch->priv.server->skeleton.relativetransforms) continue; if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP) continue; Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1); Matrix4x4_Invert_Simple(&imatrix, &matrix); Collision_Cache_ClipLineToGenericEntitySurfaces(&trace, model, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask); Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } darkplaces/ft2.h0000664000175000017500000000535413067716220013036 0ustar kalevkalev/* Header for FreeType 2 and UTF-8 encoding support for * DarkPlaces */ #ifndef DP_FREETYPE2_H__ #define DP_FREETYPE2_H__ //#include #include "utf8lib.h" /* * From http://www.unicode.org/Public/UNIDATA/Blocks.txt * * E000..F8FF; Private Use Area * F0000..FFFFF; Supplementary Private Use Area-A * * We use: * Range E000 - E0FF * Contains the non-FreeType2 version of characters. */ typedef struct ft2_font_map_s ft2_font_map_t; typedef struct ft2_attachment_s ft2_attachment_t; #ifdef WIN64 #define ft2_oldstyle_map ((ft2_font_map_t*)-1LL) #else #define ft2_oldstyle_map ((ft2_font_map_t*)-1) #endif typedef float ft2_kernvec[2]; typedef struct ft2_kerning_s { ft2_kernvec kerning[256][256]; /* kerning[left char][right char] */ } ft2_kerning_t; typedef struct ft2_font_s { char name[64]; qboolean has_kerning; // last requested size loaded using Font_SetSize float currentw; float currenth; float ascend; float descend; qboolean image_font; // only fallbacks are freetype fonts // TODO: clean this up and do not expose everything. const unsigned char *data; // FT2 needs it to stay //fs_offset_t datasize; void *face; // an unordered array of ordered linked lists of glyph maps for a specific size ft2_font_map_t *font_maps[MAX_FONT_SIZES]; int num_sizes; // attachments size_t attachmentcount; ft2_attachment_t *attachments; ft2_settings_t *settings; // fallback mechanism struct ft2_font_s *next; } ft2_font_t; void Font_CloseLibrary(void); void Font_Init(void); qboolean Font_OpenLibrary(void); ft2_font_t* Font_Alloc(void); void Font_UnloadFont(ft2_font_t *font); // IndexForSize suggests to change the width and height if a font size is in a reasonable range // for example, you render at a size of 12.4, and a font of size 12 has been loaded // in such a case, *outw and *outh are set to 12, which is often a good alternative size int Font_IndexForSize(ft2_font_t *font, float size, float *outw, float *outh); ft2_font_map_t *Font_MapForIndex(ft2_font_t *font, int index); qboolean Font_LoadFont(const char *name, dp_font_t *dpfnt); qboolean Font_GetKerningForSize(ft2_font_t *font, float w, float h, Uchar left, Uchar right, float *outx, float *outy); qboolean Font_GetKerningForMap(ft2_font_t *font, int map_index, float w, float h, Uchar left, Uchar right, float *outx, float *outy); float Font_VirtualToRealSize(float sz); float Font_SnapTo(float val, float snapwidth); // since this is used on a font_map_t, let's name it FontMap_* ft2_font_map_t *FontMap_FindForChar(ft2_font_map_t *start, Uchar ch); #endif // DP_FREETYPE2_H__ darkplaces/libcurl.c0000664000175000017500000013576313067716220014002 0ustar kalevkalev#include "quakedef.h" #include "fs.h" #include "libcurl.h" #include "thread.h" #include "image.h" #include "jpeg.h" #include "image_png.h" static cvar_t cl_curl_maxdownloads = {CVAR_SAVE, "cl_curl_maxdownloads","1", "maximum number of concurrent HTTP/FTP downloads"}; static cvar_t cl_curl_maxspeed = {CVAR_SAVE, "cl_curl_maxspeed","300", "maximum download speed (KiB/s)"}; static cvar_t sv_curl_defaulturl = {CVAR_SAVE, "sv_curl_defaulturl","", "default autodownload source URL"}; static cvar_t sv_curl_serverpackages = {CVAR_SAVE, "sv_curl_serverpackages","", "list of required files for the clients, separated by spaces"}; static cvar_t sv_curl_maxspeed = {CVAR_SAVE, "sv_curl_maxspeed","0", "maximum download speed for clients downloading from sv_curl_defaulturl (KiB/s)"}; static cvar_t cl_curl_enabled = {CVAR_SAVE, "cl_curl_enabled","1", "whether client's download support is enabled"}; static cvar_t cl_curl_useragent = {0, "cl_curl_useragent","1", "send the User-Agent string (note: turning this off may break stuff)"}; static cvar_t cl_curl_useragent_append = {0, "cl_curl_useragent_append","", "a string to append to the User-Agent string (useful for name and version number of your mod)"}; /* ================================================================= Minimal set of definitions from libcurl WARNING: for a matter of simplicity, several pointer types are casted to "void*", and most enumerated values are not included ================================================================= */ typedef struct CURL_s CURL; typedef struct CURLM_s CURLM; typedef struct curl_slist curl_slist; typedef enum { CURLE_OK = 0 } CURLcode; typedef enum { CURLM_CALL_MULTI_PERFORM=-1, /* please call curl_multi_perform() soon */ CURLM_OK = 0 } CURLMcode; #define CURL_GLOBAL_NOTHING 0 #define CURL_GLOBAL_SSL 1 #define CURL_GLOBAL_WIN32 2 #define CURLOPTTYPE_LONG 0 #define CURLOPTTYPE_OBJECTPOINT 10000 #define CURLOPTTYPE_FUNCTIONPOINT 20000 #define CURLOPTTYPE_OFF_T 30000 #define CINIT(name,type,number) CURLOPT_ ## name = CURLOPTTYPE_ ## type + number typedef enum { CINIT(WRITEDATA, OBJECTPOINT, 1), CINIT(URL, OBJECTPOINT, 2), CINIT(ERRORBUFFER, OBJECTPOINT, 10), CINIT(WRITEFUNCTION, FUNCTIONPOINT, 11), CINIT(POSTFIELDS, OBJECTPOINT, 15), CINIT(REFERER, OBJECTPOINT, 16), CINIT(USERAGENT, OBJECTPOINT, 18), CINIT(LOW_SPEED_LIMIT, LONG , 19), CINIT(LOW_SPEED_TIME, LONG, 20), CINIT(RESUME_FROM, LONG, 21), CINIT(HTTPHEADER, OBJECTPOINT, 23), CINIT(POST, LONG, 47), /* HTTP POST method */ CINIT(FOLLOWLOCATION, LONG, 52), /* use Location: Luke! */ CINIT(POSTFIELDSIZE, LONG, 60), CINIT(PRIVATE, OBJECTPOINT, 103), CINIT(PROTOCOLS, LONG, 181), CINIT(REDIR_PROTOCOLS, LONG, 182) } CURLoption; #define CURLPROTO_HTTP (1<<0) #define CURLPROTO_HTTPS (1<<1) #define CURLPROTO_FTP (1<<2) typedef enum { CURLINFO_TEXT = 0, CURLINFO_HEADER_IN, /* 1 */ CURLINFO_HEADER_OUT, /* 2 */ CURLINFO_DATA_IN, /* 3 */ CURLINFO_DATA_OUT, /* 4 */ CURLINFO_SSL_DATA_IN, /* 5 */ CURLINFO_SSL_DATA_OUT, /* 6 */ CURLINFO_END } curl_infotype; #define CURLINFO_STRING 0x100000 #define CURLINFO_LONG 0x200000 #define CURLINFO_DOUBLE 0x300000 #define CURLINFO_SLIST 0x400000 #define CURLINFO_MASK 0x0fffff #define CURLINFO_TYPEMASK 0xf00000 typedef enum { CURLINFO_NONE, /* first, never use this */ CURLINFO_EFFECTIVE_URL = CURLINFO_STRING + 1, CURLINFO_RESPONSE_CODE = CURLINFO_LONG + 2, CURLINFO_TOTAL_TIME = CURLINFO_DOUBLE + 3, CURLINFO_NAMELOOKUP_TIME = CURLINFO_DOUBLE + 4, CURLINFO_CONNECT_TIME = CURLINFO_DOUBLE + 5, CURLINFO_PRETRANSFER_TIME = CURLINFO_DOUBLE + 6, CURLINFO_SIZE_UPLOAD = CURLINFO_DOUBLE + 7, CURLINFO_SIZE_DOWNLOAD = CURLINFO_DOUBLE + 8, CURLINFO_SPEED_DOWNLOAD = CURLINFO_DOUBLE + 9, CURLINFO_SPEED_UPLOAD = CURLINFO_DOUBLE + 10, CURLINFO_HEADER_SIZE = CURLINFO_LONG + 11, CURLINFO_REQUEST_SIZE = CURLINFO_LONG + 12, CURLINFO_SSL_VERIFYRESULT = CURLINFO_LONG + 13, CURLINFO_FILETIME = CURLINFO_LONG + 14, CURLINFO_CONTENT_LENGTH_DOWNLOAD = CURLINFO_DOUBLE + 15, CURLINFO_CONTENT_LENGTH_UPLOAD = CURLINFO_DOUBLE + 16, CURLINFO_STARTTRANSFER_TIME = CURLINFO_DOUBLE + 17, CURLINFO_CONTENT_TYPE = CURLINFO_STRING + 18, CURLINFO_REDIRECT_TIME = CURLINFO_DOUBLE + 19, CURLINFO_REDIRECT_COUNT = CURLINFO_LONG + 20, CURLINFO_PRIVATE = CURLINFO_STRING + 21, CURLINFO_HTTP_CONNECTCODE = CURLINFO_LONG + 22, CURLINFO_HTTPAUTH_AVAIL = CURLINFO_LONG + 23, CURLINFO_PROXYAUTH_AVAIL = CURLINFO_LONG + 24, CURLINFO_OS_ERRNO = CURLINFO_LONG + 25, CURLINFO_NUM_CONNECTS = CURLINFO_LONG + 26, CURLINFO_SSL_ENGINES = CURLINFO_SLIST + 27 } CURLINFO; typedef enum { CURLMSG_NONE, /* first, not used */ CURLMSG_DONE, /* This easy handle has completed. 'result' contains the CURLcode of the transfer */ CURLMSG_LAST } CURLMSG; typedef struct { CURLMSG msg; /* what this message means */ CURL *easy_handle; /* the handle it concerns */ union { void *whatever; /* message-specific data */ CURLcode result; /* return code for transfer */ } data; } CURLMsg; static void (*qcurl_global_init) (long flags); static void (*qcurl_global_cleanup) (void); static CURL * (*qcurl_easy_init) (void); static void (*qcurl_easy_cleanup) (CURL *handle); static CURLcode (*qcurl_easy_setopt) (CURL *handle, CURLoption option, ...); static CURLcode (*qcurl_easy_getinfo) (CURL *handle, CURLINFO info, ...); static const char * (*qcurl_easy_strerror) (CURLcode); static CURLM * (*qcurl_multi_init) (void); static CURLMcode (*qcurl_multi_perform) (CURLM *multi_handle, int *running_handles); static CURLMcode (*qcurl_multi_add_handle) (CURLM *multi_handle, CURL *easy_handle); static CURLMcode (*qcurl_multi_remove_handle) (CURLM *multi_handle, CURL *easy_handle); static CURLMsg * (*qcurl_multi_info_read) (CURLM *multi_handle, int *msgs_in_queue); static void (*qcurl_multi_cleanup) (CURLM *); static const char * (*qcurl_multi_strerror) (CURLcode); static curl_slist * (*qcurl_slist_append) (curl_slist *list, const char *string); static void (*qcurl_slist_free_all) (curl_slist *list); static dllfunction_t curlfuncs[] = { {"curl_global_init", (void **) &qcurl_global_init}, {"curl_global_cleanup", (void **) &qcurl_global_cleanup}, {"curl_easy_init", (void **) &qcurl_easy_init}, {"curl_easy_cleanup", (void **) &qcurl_easy_cleanup}, {"curl_easy_setopt", (void **) &qcurl_easy_setopt}, {"curl_easy_strerror", (void **) &qcurl_easy_strerror}, {"curl_easy_getinfo", (void **) &qcurl_easy_getinfo}, {"curl_multi_init", (void **) &qcurl_multi_init}, {"curl_multi_perform", (void **) &qcurl_multi_perform}, {"curl_multi_add_handle", (void **) &qcurl_multi_add_handle}, {"curl_multi_remove_handle",(void **) &qcurl_multi_remove_handle}, {"curl_multi_info_read", (void **) &qcurl_multi_info_read}, {"curl_multi_cleanup", (void **) &qcurl_multi_cleanup}, {"curl_multi_strerror", (void **) &qcurl_multi_strerror}, {"curl_slist_append", (void **) &qcurl_slist_append}, {"curl_slist_free_all", (void **) &qcurl_slist_free_all}, {NULL, NULL} }; // Handle for CURL DLL static dllhandle_t curl_dll = NULL; // will be checked at many places to find out if qcurl calls are allowed #define LOADTYPE_NONE 0 #define LOADTYPE_PAK 1 #define LOADTYPE_CACHEPIC 2 #define LOADTYPE_SKINFRAME 3 void *curl_mutex = NULL; typedef struct downloadinfo_s { char filename[MAX_OSPATH]; char url[1024]; char referer[256]; qfile_t *stream; fs_offset_t startpos; CURL *curle; qboolean started; int loadtype; size_t bytes_received; // for buffer double bytes_received_curl; // for throttling double bytes_sent_curl; // for throttling struct downloadinfo_s *next, *prev; qboolean forthismap; double maxspeed; curl_slist *slist; // http headers unsigned char *buffer; size_t buffersize; curl_callback_t callback; void *callback_data; const unsigned char *postbuf; size_t postbufsize; const char *post_content_type; const char *extraheaders; } downloadinfo; static downloadinfo *downloads = NULL; static int numdownloads = 0; static qboolean noclear = FALSE; static int numdownloads_fail = 0; static int numdownloads_success = 0; static int numdownloads_added = 0; static char command_when_done[256] = ""; static char command_when_error[256] = ""; /* ==================== Curl_CommandWhenDone Sets the command which is to be executed when the last download completes AND all downloads since last server connect ended with a successful status. Setting the command to NULL clears it. ==================== */ static void Curl_CommandWhenDone(const char *cmd) { if(!curl_dll) return; if(cmd) strlcpy(command_when_done, cmd, sizeof(command_when_done)); else *command_when_done = 0; } /* FIXME Do not use yet. Not complete. Problem: what counts as an error? */ static void Curl_CommandWhenError(const char *cmd) { if(!curl_dll) return; if(cmd) strlcpy(command_when_error, cmd, sizeof(command_when_error)); else *command_when_error = 0; } /* ==================== Curl_Clear_forthismap Clears the "will disconnect on failure" flags. ==================== */ void Curl_Clear_forthismap(void) { downloadinfo *di; if(noclear) return; if (curl_mutex) Thread_LockMutex(curl_mutex); for(di = downloads; di; di = di->next) di->forthismap = false; Curl_CommandWhenError(NULL); Curl_CommandWhenDone(NULL); numdownloads_fail = 0; numdownloads_success = 0; numdownloads_added = 0; if (curl_mutex) Thread_UnlockMutex(curl_mutex); } /* ==================== Curl_Have_forthismap Returns true if a download needed for the current game is running. ==================== */ qboolean Curl_Have_forthismap(void) { return numdownloads_added != 0; } void Curl_Register_predownload(void) { if (curl_mutex) Thread_LockMutex(curl_mutex); Curl_CommandWhenDone("cl_begindownloads"); Curl_CommandWhenError("cl_begindownloads"); if (curl_mutex) Thread_UnlockMutex(curl_mutex); } /* ==================== Curl_CheckCommandWhenDone Checks if a "done command" is to be executed. All downloads finished, at least one success since connect, no single failure -> execute the command. */ static void Curl_CheckCommandWhenDone(void) { if(!curl_dll) return; if(numdownloads_added && ((numdownloads_success + numdownloads_fail) == numdownloads_added)) { if(numdownloads_fail == 0) { Con_DPrintf("cURL downloads occurred, executing %s\n", command_when_done); Cbuf_AddText("\n"); Cbuf_AddText(command_when_done); Cbuf_AddText("\n"); } else { Con_DPrintf("cURL downloads FAILED, executing %s\n", command_when_error); Cbuf_AddText("\n"); Cbuf_AddText(command_when_error); Cbuf_AddText("\n"); } Curl_Clear_forthismap(); } } /* ==================== CURL_CloseLibrary Load the cURL DLL ==================== */ static qboolean CURL_OpenLibrary (void) { const char* dllnames [] = { #if defined(WIN32) "libcurl-4.dll", "libcurl-3.dll", #elif defined(MACOSX) "libcurl.4.dylib", // Mac OS X Notyetreleased "libcurl.3.dylib", // Mac OS X Tiger "libcurl.2.dylib", // Mac OS X Panther #else "libcurl.so.4", "libcurl.so.3", "libcurl.so", // FreeBSD #endif NULL }; // Already loaded? if (curl_dll) return true; // Load the DLL return Sys_LoadLibrary (dllnames, &curl_dll, curlfuncs); } /* ==================== CURL_CloseLibrary Unload the cURL DLL ==================== */ static void CURL_CloseLibrary (void) { Sys_UnloadLibrary (&curl_dll); } static CURLM *curlm = NULL; static double bytes_received = 0; // used for bandwidth throttling static double bytes_sent = 0; // used for bandwidth throttling static double curltime = 0; /* ==================== CURL_fwrite fwrite-compatible function that writes the data to a file. libcurl can call this. ==================== */ static size_t CURL_fwrite(void *data, size_t size, size_t nmemb, void *vdi) { fs_offset_t ret = -1; size_t bytes = size * nmemb; downloadinfo *di = (downloadinfo *) vdi; if(di->buffer) { if(di->bytes_received + bytes <= di->buffersize) { memcpy(di->buffer + di->bytes_received, data, bytes); ret = bytes; } // otherwise: buffer overrun, ret stays -1 } if(di->stream) { ret = FS_Write(di->stream, data, bytes); } di->bytes_received += bytes; return ret; // Why not ret / nmemb? // Because CURLOPT_WRITEFUNCTION docs say to return the number of bytes. // Yes, this is incompatible to fwrite(2). } typedef enum { CURL_DOWNLOAD_SUCCESS = 0, CURL_DOWNLOAD_FAILED, CURL_DOWNLOAD_ABORTED, CURL_DOWNLOAD_SERVERERROR } CurlStatus; static void curl_default_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) { downloadinfo *di = (downloadinfo *) cbdata; switch(status) { case CURLCBSTATUS_OK: Con_DPrintf("Download of %s: OK\n", di->filename); break; case CURLCBSTATUS_FAILED: Con_DPrintf("Download of %s: FAILED\n", di->filename); break; case CURLCBSTATUS_ABORTED: Con_DPrintf("Download of %s: ABORTED\n", di->filename); break; case CURLCBSTATUS_SERVERERROR: Con_DPrintf("Download of %s: (unknown server error)\n", di->filename); break; case CURLCBSTATUS_UNKNOWN: Con_DPrintf("Download of %s: (unknown client error)\n", di->filename); break; default: Con_DPrintf("Download of %s: %d\n", di->filename, status); break; } } static void curl_quiet_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) { curl_default_callback(status, length_received, buffer, cbdata); } static unsigned char *decode_image(downloadinfo *di, const char *content_type) { unsigned char *pixels = NULL; fs_offset_t filesize = 0; unsigned char *data = FS_LoadFile(di->filename, tempmempool, true, &filesize); if(data) { int mip = 0; if(!strcmp(content_type, "image/jpeg")) pixels = JPEG_LoadImage_BGRA(data, filesize, &mip); else if(!strcmp(content_type, "image/png")) pixels = PNG_LoadImage_BGRA(data, filesize, &mip); else if(filesize >= 7 && !strncmp((char *) data, "\xFF\xD8", 7)) pixels = JPEG_LoadImage_BGRA(data, filesize, &mip); else if(filesize >= 7 && !strncmp((char *) data, "\x89PNG\x0D\x0A\x1A\x0A", 7)) pixels = PNG_LoadImage_BGRA(data, filesize, &mip); else Con_Printf("Did not detect content type: %s\n", content_type); Mem_Free(data); } // do we call Image_MakeLinearColorsFromsRGB or not? return pixels; } /* ==================== Curl_EndDownload stops a download. It receives a status (CURL_DOWNLOAD_SUCCESS, CURL_DOWNLOAD_FAILED or CURL_DOWNLOAD_ABORTED) and in the second case the error code from libcurl, or 0, if another error has occurred. ==================== */ static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata); static void Curl_EndDownload(downloadinfo *di, CurlStatus status, CURLcode error, const char *content_type_) { char content_type[64]; qboolean ok = false; if(!curl_dll) return; switch(status) { case CURL_DOWNLOAD_SUCCESS: ok = true; di->callback(CURLCBSTATUS_OK, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_FAILED: di->callback(CURLCBSTATUS_FAILED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_ABORTED: di->callback(CURLCBSTATUS_ABORTED, di->bytes_received, di->buffer, di->callback_data); break; case CURL_DOWNLOAD_SERVERERROR: // reopen to enforce it to have zero bytes again if(di->stream) { FS_Close(di->stream); di->stream = FS_OpenRealFile(di->filename, "wb", false); } if(di->callback) di->callback(error ? (int) error : CURLCBSTATUS_SERVERERROR, di->bytes_received, di->buffer, di->callback_data); break; default: if(di->callback) di->callback(CURLCBSTATUS_UNKNOWN, di->bytes_received, di->buffer, di->callback_data); break; } if(content_type_) strlcpy(content_type, content_type_, sizeof(content_type)); else *content_type = 0; if(di->curle) { qcurl_multi_remove_handle(curlm, di->curle); qcurl_easy_cleanup(di->curle); if(di->slist) qcurl_slist_free_all(di->slist); } if(!di->callback && ok && !di->bytes_received) { Con_Printf("ERROR: empty file\n"); ok = false; } if(di->stream) FS_Close(di->stream); #define CLEAR_AND_RETRY() \ do \ { \ di->stream = FS_OpenRealFile(di->filename, "wb", false); \ FS_Close(di->stream); \ if(di->startpos && !di->callback) \ { \ Curl_Begin(di->url, di->extraheaders, di->maxspeed, di->filename, di->loadtype, di->forthismap, di->post_content_type, di->postbuf, di->postbufsize, NULL, 0, NULL, NULL); \ di->forthismap = false; \ } \ } \ while(0) if(ok && di->loadtype == LOADTYPE_PAK) { ok = FS_AddPack(di->filename, NULL, true); if(!ok) CLEAR_AND_RETRY(); } else if(ok && di->loadtype == LOADTYPE_CACHEPIC) { const char *p; unsigned char *pixels = NULL; p = di->filename; #ifdef WE_ARE_EVIL if(!strncmp(p, "dlcache/", 8)) p += 8; #endif pixels = decode_image(di, content_type); if(pixels) Draw_NewPic(p, image_width, image_height, true, pixels); else CLEAR_AND_RETRY(); } else if(ok && di->loadtype == LOADTYPE_SKINFRAME) { const char *p; unsigned char *pixels = NULL; p = di->filename; #ifdef WE_ARE_EVIL if(!strncmp(p, "dlcache/", 8)) p += 8; #endif pixels = decode_image(di, content_type); if(pixels) R_SkinFrame_LoadInternalBGRA(p, TEXF_FORCE_RELOAD | TEXF_MIPMAP | TEXF_ALPHA, pixels, image_width, image_height, false); // TODO what sRGB argument to put here? else CLEAR_AND_RETRY(); } if(di->prev) di->prev->next = di->next; else downloads = di->next; if(di->next) di->next->prev = di->prev; --numdownloads; if(di->forthismap) { if(ok) ++numdownloads_success; else ++numdownloads_fail; } Z_Free(di); } /* ==================== CleanURL Returns a "cleaned up" URL for display (to strip login data) ==================== */ static const char *CleanURL(const char *url, char *urlbuf, size_t urlbuflength) { const char *p, *q, *r; // if URL is of form anything://foo-without-slash@rest, replace by anything://rest p = strstr(url, "://"); if(p) { q = strchr(p + 3, '@'); if(q) { r = strchr(p + 3, '/'); if(!r || q < r) { dpsnprintf(urlbuf, urlbuflength, "%.*s%s", (int)(p - url + 3), url, q + 1); return urlbuf; } } } return url; } /* ==================== CheckPendingDownloads checks if there are free download slots to start new downloads in. To not start too many downloads at once, only one download is added at a time, up to a maximum number of cl_curl_maxdownloads are running. ==================== */ static void CheckPendingDownloads(void) { const char *h; char urlbuf[1024]; char vabuf[1024]; if(!curl_dll) return; if(numdownloads < cl_curl_maxdownloads.integer) { downloadinfo *di; for(di = downloads; di; di = di->next) { if(!di->started) { if(!di->buffer) { Con_Printf("Downloading %s -> %s", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename); di->stream = FS_OpenRealFile(di->filename, "ab", false); if(!di->stream) { Con_Printf("\nFAILED: Could not open output file %s\n", di->filename); Curl_EndDownload(di, CURL_DOWNLOAD_FAILED, CURLE_OK, NULL); return; } FS_Seek(di->stream, 0, SEEK_END); di->startpos = FS_Tell(di->stream); if(di->startpos > 0) Con_Printf(", resuming from position %ld", (long) di->startpos); Con_Print("...\n"); } else { Con_DPrintf("Downloading %s -> memory\n", CleanURL(di->url, urlbuf, sizeof(urlbuf))); di->startpos = 0; } di->curle = qcurl_easy_init(); di->slist = NULL; qcurl_easy_setopt(di->curle, CURLOPT_URL, di->url); if(cl_curl_useragent.integer) { const char *ua #ifdef HTTP_USER_AGENT = HTTP_USER_AGENT; #else = engineversion; #endif if(!ua) ua = ""; if(*cl_curl_useragent_append.string) ua = va(vabuf, sizeof(vabuf), "%s%s%s", ua, (ua[0] && ua[strlen(ua)-1] != ' ') ? " " : "", cl_curl_useragent_append.string); qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ua); } else qcurl_easy_setopt(di->curle, CURLOPT_USERAGENT, ""); qcurl_easy_setopt(di->curle, CURLOPT_REFERER, di->referer); qcurl_easy_setopt(di->curle, CURLOPT_RESUME_FROM, (long) di->startpos); qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 1); qcurl_easy_setopt(di->curle, CURLOPT_WRITEFUNCTION, CURL_fwrite); qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_LIMIT, (long) 256); qcurl_easy_setopt(di->curle, CURLOPT_LOW_SPEED_TIME, (long) 45); qcurl_easy_setopt(di->curle, CURLOPT_WRITEDATA, (void *) di); qcurl_easy_setopt(di->curle, CURLOPT_PRIVATE, (void *) di); qcurl_easy_setopt(di->curle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP); if(qcurl_easy_setopt(di->curle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP) != CURLE_OK) { Con_Printf("^1WARNING:^7 for security reasons, please upgrade to libcurl 7.19.4 or above. In a later version of DarkPlaces, HTTP redirect support will be disabled for this libcurl version.\n"); //qcurl_easy_setopt(di->curle, CURLOPT_FOLLOWLOCATION, 0); } if(di->post_content_type) { qcurl_easy_setopt(di->curle, CURLOPT_POST, 1); qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDS, di->postbuf); qcurl_easy_setopt(di->curle, CURLOPT_POSTFIELDSIZE, di->postbufsize); di->slist = qcurl_slist_append(di->slist, va(vabuf, sizeof(vabuf), "Content-Type: %s", di->post_content_type)); } // parse extra headers into slist // \n separated list! h = di->extraheaders; while(h) { const char *hh = strchr(h, '\n'); if(hh) { char *buf = (char *) Mem_Alloc(tempmempool, hh - h + 1); memcpy(buf, h, hh - h); buf[hh - h] = 0; di->slist = qcurl_slist_append(di->slist, buf); h = hh + 1; } else { di->slist = qcurl_slist_append(di->slist, h); h = NULL; } } qcurl_easy_setopt(di->curle, CURLOPT_HTTPHEADER, di->slist); qcurl_multi_add_handle(curlm, di->curle); di->started = true; ++numdownloads; if(numdownloads >= cl_curl_maxdownloads.integer) break; } } } } /* ==================== Curl_Init this function MUST be called before using anything else in this file. On Win32, this must be called AFTER WSAStartup has been done! ==================== */ void Curl_Init(void) { CURL_OpenLibrary(); if(!curl_dll) return; if (Thread_HasThreads()) curl_mutex = Thread_CreateMutex(); qcurl_global_init(CURL_GLOBAL_NOTHING); curlm = qcurl_multi_init(); } /* ==================== Curl_Shutdown Surprise... closes all the stuff. Please do this BEFORE shutting down LHNET. ==================== */ void Curl_ClearRequirements(void); void Curl_Shutdown(void) { if(!curl_dll) return; Curl_ClearRequirements(); Curl_CancelAll(); if (curl_mutex) Thread_DestroyMutex(curl_mutex); CURL_CloseLibrary(); curl_dll = NULL; } /* ==================== Curl_Find Finds the internal information block for a download given by file name. ==================== */ static downloadinfo *Curl_Find(const char *filename) { downloadinfo *di; if(!curl_dll) return NULL; for(di = downloads; di; di = di->next) if(!strcasecmp(di->filename, filename)) return di; return NULL; } void Curl_Cancel_ToMemory(curl_callback_t callback, void *cbdata) { downloadinfo *di; if(!curl_dll) return; for(di = downloads; di; ) { if(di->callback == callback && di->callback_data == cbdata) { di->callback = curl_quiet_callback; // do NOT call the callback Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); di = downloads; } else di = di->next; } } /* ==================== Curl_Begin Starts a download of a given URL to the file name portion of this URL (or name if given) in the "dlcache/" folder. ==================== */ static qboolean Curl_Begin(const char *URL, const char *extraheaders, double maxspeed, const char *name, int loadtype, qboolean forthismap, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) { if(buf) if(loadtype != LOADTYPE_NONE) Host_Error("Curl_Begin: loadtype and buffer are both set"); if(!curl_dll || !cl_curl_enabled.integer) { return false; } else { char fn[MAX_OSPATH]; char urlbuf[1024]; const char *p, *q; size_t length; downloadinfo *di; // if URL is protocol:///* or protocol://:port/*, insert the IP of the current server p = strchr(URL, ':'); if(p) { if(!strncmp(p, ":///", 4) || !strncmp(p, "://:", 4)) { char addressstring[128]; *addressstring = 0; InfoString_GetValue(cls.userinfo, "*ip", addressstring, sizeof(addressstring)); q = strchr(addressstring, ':'); if(!q) q = addressstring + strlen(addressstring); if(*addressstring) { dpsnprintf(urlbuf, sizeof(urlbuf), "%.*s://%.*s%s", (int) (p - URL), URL, (int) (q - addressstring), addressstring, URL + (p - URL) + 3); URL = urlbuf; } } } // Note: This extraction of the file name portion is NOT entirely correct. // // It does the following: // // http://host/some/script.cgi/SomeFile.pk3?uid=ABCDE -> SomeFile.pk3 // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 -> SomeFile.pk3 // http://host/some/script.php?uid=ABCDE&file=SomeFile.pk3 -> script.php // // However, I'd like to keep this "buggy" behavior so that PHP script // authors can write download scripts without having to enable // AcceptPathInfo on Apache. They just have to ensure that their script // can be called with such a "fake" path name like // http://host/some/script.php?uid=ABCDE&file=/SomeFile.pk3 // // By the way, such PHP scripts should either send the file or a // "Location:" redirect; PHP code example: // // header("Location: http://www.example.com/"); // // By the way, this will set User-Agent to something like // "Nexuiz build 22:27:55 Mar 17 2006" (engineversion) and Referer to // dp://serverhost:serverport/ so you can filter on this; an example // httpd log file line might be: // // 141.2.16.3 - - [17/Mar/2006:22:32:43 +0100] "GET /maps/tznex07.pk3 HTTP/1.1" 200 1077455 "dp://141.2.16.7:26000/" "Nexuiz Linux 22:07:43 Mar 17 2006" if (curl_mutex) Thread_LockMutex(curl_mutex); if(buf) { if(!name) name = CleanURL(URL, urlbuf, sizeof(urlbuf)); } else { if(!name) { name = CleanURL(URL, urlbuf, sizeof(urlbuf)); p = strrchr(name, '/'); p = p ? (p+1) : name; q = strchr(p, '?'); length = q ? (size_t)(q - p) : strlen(p); dpsnprintf(fn, sizeof(fn), "dlcache/%.*s", (int)length, p); } else { dpsnprintf(fn, sizeof(fn), "dlcache/%s", name); } name = fn; // make it point back // already downloading the file? { downloadinfo *existingdownloadinfo = Curl_Find(fn); if(existingdownloadinfo) { Con_Printf("Can't download %s, already getting it from %s!\n", fn, CleanURL(existingdownloadinfo->url, urlbuf, sizeof(urlbuf))); // however, if it was not for this map yet... if(forthismap && !existingdownloadinfo->forthismap) { existingdownloadinfo->forthismap = true; // this "fakes" a download attempt so the client will wait for // the download to finish and then reconnect ++numdownloads_added; } if (curl_mutex) Thread_UnlockMutex(curl_mutex); return false; } } if(FS_FileExists(fn)) { if(loadtype == LOADTYPE_PAK) { qboolean already_loaded; if(FS_AddPack(fn, &already_loaded, true)) { Con_DPrintf("%s already exists, not downloading!\n", fn); if(already_loaded) Con_DPrintf("(pak was already loaded)\n"); else { if(forthismap) { ++numdownloads_added; ++numdownloads_success; } } if (curl_mutex) Thread_UnlockMutex(curl_mutex); return false; } else { qfile_t *f = FS_OpenRealFile(fn, "rb", false); if(f) { char b[4] = {0}; FS_Read(f, b, sizeof(b)); // no "-1", I will use memcmp if(memcmp(b, "PK\x03\x04", 4) && memcmp(b, "PACK", 4)) { Con_DPrintf("Detected non-PAK %s, clearing and NOT resuming.\n", fn); FS_Close(f); f = FS_OpenRealFile(fn, "wb", false); if(f) FS_Close(f); } else { // OK FS_Close(f); } } } } else { // never resume these qfile_t *f = FS_OpenRealFile(fn, "wb", false); if(f) FS_Close(f); } } } // if we get here, we actually want to download... so first verify the // URL scheme (so one can't read local files using file://) if(strncmp(URL, "http://", 7) && strncmp(URL, "ftp://", 6) && strncmp(URL, "https://", 8)) { Con_Printf("Curl_Begin(\"%s\"): nasty URL scheme rejected\n", URL); if (curl_mutex) Thread_UnlockMutex(curl_mutex); return false; } if(forthismap) ++numdownloads_added; di = (downloadinfo *) Z_Malloc(sizeof(*di)); strlcpy(di->filename, name, sizeof(di->filename)); strlcpy(di->url, URL, sizeof(di->url)); dpsnprintf(di->referer, sizeof(di->referer), "dp://%s/", cls.netcon ? cls.netcon->address : "notconnected.invalid"); di->forthismap = forthismap; di->stream = NULL; di->startpos = 0; di->curle = NULL; di->started = false; di->loadtype = loadtype; di->maxspeed = maxspeed; di->bytes_received = 0; di->bytes_received_curl = 0; di->bytes_sent_curl = 0; di->extraheaders = extraheaders; di->next = downloads; di->prev = NULL; if(di->next) di->next->prev = di; di->buffer = buf; di->buffersize = bufsize; if(callback == NULL) { di->callback = curl_default_callback; di->callback_data = di; } else { di->callback = callback; di->callback_data = cbdata; } if(post_content_type) { di->post_content_type = post_content_type; di->postbuf = postbuf; di->postbufsize = postbufsize; } else { di->post_content_type = NULL; di->postbuf = NULL; di->postbufsize = 0; } downloads = di; if (curl_mutex) Thread_UnlockMutex(curl_mutex); return true; } } qboolean Curl_Begin_ToFile(const char *URL, double maxspeed, const char *name, int loadtype, qboolean forthismap) { return Curl_Begin(URL, NULL, maxspeed, name, loadtype, forthismap, NULL, NULL, 0, NULL, 0, NULL, NULL); } qboolean Curl_Begin_ToMemory(const char *URL, double maxspeed, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) { return Curl_Begin(URL, NULL, maxspeed, NULL, false, false, NULL, NULL, 0, buf, bufsize, callback, cbdata); } qboolean Curl_Begin_ToMemory_POST(const char *URL, const char *extraheaders, double maxspeed, const char *post_content_type, const unsigned char *postbuf, size_t postbufsize, unsigned char *buf, size_t bufsize, curl_callback_t callback, void *cbdata) { return Curl_Begin(URL, extraheaders, maxspeed, NULL, false, false, post_content_type, postbuf, postbufsize, buf, bufsize, callback, cbdata); } /* ==================== Curl_Run call this regularily as this will always download as much as possible without blocking. ==================== */ void Curl_Run(void) { double maxspeed; downloadinfo *di; noclear = FALSE; if(!cl_curl_enabled.integer) return; if(!curl_dll) return; if (curl_mutex) Thread_LockMutex(curl_mutex); Curl_CheckCommandWhenDone(); if(!downloads) { if (curl_mutex) Thread_UnlockMutex(curl_mutex); return; } if(realtime < curltime) // throttle { if (curl_mutex) Thread_UnlockMutex(curl_mutex); return; } { int remaining; CURLMcode mc; do { mc = qcurl_multi_perform(curlm, &remaining); } while(mc == CURLM_CALL_MULTI_PERFORM); for(di = downloads; di; di = di->next) { double b = 0; if(di->curle) { qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_UPLOAD, &b); bytes_sent += (b - di->bytes_sent_curl); di->bytes_sent_curl = b; qcurl_easy_getinfo(di->curle, CURLINFO_SIZE_DOWNLOAD, &b); bytes_sent += (b - di->bytes_received_curl); di->bytes_received_curl = b; } } for(;;) { CURLMsg *msg = qcurl_multi_info_read(curlm, &remaining); if(!msg) break; if(msg->msg == CURLMSG_DONE) { const char *ct = NULL; CurlStatus failed = CURL_DOWNLOAD_SUCCESS; CURLcode result; qcurl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &di); result = msg->data.result; if(result) { failed = CURL_DOWNLOAD_FAILED; } else { long code; qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &code); switch(code / 100) { case 4: // e.g. 404? case 5: // e.g. 500? failed = CURL_DOWNLOAD_SERVERERROR; result = (CURLcode) code; break; } qcurl_easy_getinfo(msg->easy_handle, CURLINFO_CONTENT_TYPE, &ct); } Curl_EndDownload(di, failed, result, ct); } } } CheckPendingDownloads(); // when will we curl the next time? // we will wait a bit to ensure our download rate is kept. // we now know that realtime >= curltime... so set up a new curltime // use the slowest allowing download to derive the maxspeed... this CAN // be done better, but maybe later maxspeed = cl_curl_maxspeed.value; for(di = downloads; di; di = di->next) if(di->maxspeed > 0) if(di->maxspeed < maxspeed || maxspeed <= 0) maxspeed = di->maxspeed; if(maxspeed > 0) { double bytes = bytes_sent + bytes_received; // maybe smoothen a bit? curltime = realtime + bytes / (maxspeed * 1024.0); bytes_sent = 0; bytes_received = 0; } else curltime = realtime; if (curl_mutex) Thread_UnlockMutex(curl_mutex); } /* ==================== Curl_CancelAll Stops ALL downloads. ==================== */ void Curl_CancelAll(void) { if(!curl_dll) return; if (curl_mutex) Thread_LockMutex(curl_mutex); while(downloads) { Curl_EndDownload(downloads, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); // INVARIANT: downloads will point to the next download after that! } if (curl_mutex) Thread_UnlockMutex(curl_mutex); } /* ==================== Curl_Running returns true iff there is a download running. ==================== */ qboolean Curl_Running(void) { if(!curl_dll) return false; return downloads != NULL; } /* ==================== Curl_GetDownloadAmount returns a value from 0.0 to 1.0 which represents the downloaded amount of data for the given download. ==================== */ static double Curl_GetDownloadAmount(downloadinfo *di) { if(!curl_dll) return -2; if(di->curle) { double length; qcurl_easy_getinfo(di->curle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); if(length > 0) return (di->startpos + di->bytes_received) / (di->startpos + length); else return 0; } else return -1; } /* ==================== Curl_GetDownloadSpeed returns the speed of the given download in bytes per second ==================== */ static double Curl_GetDownloadSpeed(downloadinfo *di) { if(!curl_dll) return -2; if(di->curle) { double speed; qcurl_easy_getinfo(di->curle, CURLINFO_SPEED_DOWNLOAD, &speed); return speed; } else return -1; } /* ==================== Curl_Info_f prints the download list ==================== */ // TODO rewrite using Curl_GetDownloadInfo? static void Curl_Info_f(void) { downloadinfo *di; char urlbuf[1024]; if(!curl_dll) return; if(Curl_Running()) { if (curl_mutex) Thread_LockMutex(curl_mutex); Con_Print("Currently running downloads:\n"); for(di = downloads; di; di = di->next) { double speed, percent; Con_Printf(" %s -> %s ", CleanURL(di->url, urlbuf, sizeof(urlbuf)), di->filename); percent = 100.0 * Curl_GetDownloadAmount(di); speed = Curl_GetDownloadSpeed(di); if(percent >= 0) Con_Printf("(%.1f%% @ %.1f KiB/s)\n", percent, speed / 1024.0); else Con_Print("(queued)\n"); } if (curl_mutex) Thread_UnlockMutex(curl_mutex); } else { Con_Print("No downloads running.\n"); } } /* ==================== Curl_Curl_f implements the "curl" console command curl --info curl --cancel curl --cancel filename curl url For internal use: curl [--pak] [--forthismap] [--for filename filename...] url --pak: after downloading, load the package into the virtual file system --for filename...: only download of at least one of the named files is missing --forthismap: don't reconnect on failure curl --clear_autodownload clears the download success/failure counters curl --finish_autodownload if at least one download has been started, disconnect and drop to the menu once the last download completes successfully, reconnect to the current server ==================== */ static void Curl_Curl_f(void) { double maxspeed = 0; int i; int end; int loadtype = LOADTYPE_NONE; qboolean forthismap = false; const char *url; const char *name = 0; if(!curl_dll) { Con_Print("libcurl DLL not found, this command is inactive.\n"); return; } if(!cl_curl_enabled.integer) { Con_Print("curl support not enabled. Set cl_curl_enabled to 1 to enable.\n"); return; } if(Cmd_Argc() < 2) { Con_Print("usage:\ncurl --info, curl --cancel [filename], curl url\n"); return; } url = Cmd_Argv(Cmd_Argc() - 1); end = Cmd_Argc(); for(i = 1; i != end; ++i) { const char *a = Cmd_Argv(i); if(!strcmp(a, "--info")) { Curl_Info_f(); return; } else if(!strcmp(a, "--cancel")) { if(i == end - 1) // last argument Curl_CancelAll(); else { downloadinfo *di = Curl_Find(url); if(di) Curl_EndDownload(di, CURL_DOWNLOAD_ABORTED, CURLE_OK, NULL); else Con_Print("download not found\n"); } return; } else if(!strcmp(a, "--pak")) { loadtype = LOADTYPE_PAK; } else if(!strcmp(a, "--cachepic")) { loadtype = LOADTYPE_CACHEPIC; } else if(!strcmp(a, "--skinframe")) { loadtype = LOADTYPE_SKINFRAME; } else if(!strcmp(a, "--for")) // must be last option { for(i = i + 1; i != end - 1; ++i) { if(!FS_FileExists(Cmd_Argv(i))) goto needthefile; // why can't I have a "double break"? } // if we get here, we have all the files... return; } else if(!strcmp(a, "--forthismap")) { forthismap = true; } else if(!strcmp(a, "--as")) { if(i < end - 1) { ++i; name = Cmd_Argv(i); } } else if(!strcmp(a, "--clear_autodownload")) { // mark all running downloads as "not for this map", so if they // fail, it does not matter Curl_Clear_forthismap(); return; } else if(!strcmp(a, "--finish_autodownload")) { if(numdownloads_added) { char donecommand[256]; if(cls.netcon) { if(cl.loadbegun) // curling won't inhibit loading the map any more when at this stage, so bail out and force a reconnect { dpsnprintf(donecommand, sizeof(donecommand), "connect %s", cls.netcon->address); Curl_CommandWhenDone(donecommand); noclear = TRUE; CL_Disconnect(); noclear = FALSE; Curl_CheckCommandWhenDone(); } else Curl_Register_predownload(); } } return; } else if(!strncmp(a, "--maxspeed=", 11)) { maxspeed = atof(a + 11); } else if(*a == '-') { Con_Printf("curl: invalid option %s\n", a); // but we ignore the option } } needthefile: Curl_Begin_ToFile(url, maxspeed, name, loadtype, forthismap); } /* static void curl_curlcat_callback(int code, size_t length_received, unsigned char *buffer, void *cbdata) { Con_Printf("Received %d bytes (status %d):\n%.*s\n", (int) length_received, code, (int) length_received, buffer); Z_Free(buffer); } void Curl_CurlCat_f(void) { unsigned char *buf; const char *url = Cmd_Argv(1); buf = Z_Malloc(16384); Curl_Begin_ToMemory(url, buf, 16384, curl_curlcat_callback, NULL); } */ /* ==================== Curl_Init_Commands loads the commands and cvars this library uses ==================== */ void Curl_Init_Commands(void) { Cvar_RegisterVariable (&cl_curl_enabled); Cvar_RegisterVariable (&cl_curl_maxdownloads); Cvar_RegisterVariable (&cl_curl_maxspeed); Cvar_RegisterVariable (&sv_curl_defaulturl); Cvar_RegisterVariable (&sv_curl_serverpackages); Cvar_RegisterVariable (&sv_curl_maxspeed); Cvar_RegisterVariable (&cl_curl_useragent); Cvar_RegisterVariable (&cl_curl_useragent_append); Cmd_AddCommand ("curl", Curl_Curl_f, "download data from an URL and add to search path"); //Cmd_AddCommand ("curlcat", Curl_CurlCat_f, "display data from an URL (debugging command)"); } /* ==================== Curl_GetDownloadInfo returns an array of Curl_downloadinfo_t structs for usage by GUIs. The number of elements in the array is returned in int *nDownloads. const char **additional_info may be set to a string of additional user information, or to NULL if no such display shall occur. The returned array must be freed later using Z_Free. ==================== */ Curl_downloadinfo_t *Curl_GetDownloadInfo(int *nDownloads, const char **additional_info, char *addinfo, size_t addinfolength) { int i; downloadinfo *di; Curl_downloadinfo_t *downinfo; if(!curl_dll) { *nDownloads = 0; if(additional_info) *additional_info = NULL; return NULL; } if (curl_mutex) Thread_LockMutex(curl_mutex); i = 0; for(di = downloads; di; di = di->next) ++i; downinfo = (Curl_downloadinfo_t *) Z_Malloc(sizeof(*downinfo) * i); i = 0; for(di = downloads; di; di = di->next) { // do not show infobars for background downloads if(developer.integer <= 0) if(di->buffer) continue; strlcpy(downinfo[i].filename, di->filename, sizeof(downinfo[i].filename)); if(di->curle) { downinfo[i].progress = Curl_GetDownloadAmount(di); downinfo[i].speed = Curl_GetDownloadSpeed(di); downinfo[i].queued = false; } else { downinfo[i].queued = true; } ++i; } if(additional_info) { // TODO: can I clear command_when_done as soon as the first download fails? if(*command_when_done && !numdownloads_fail && numdownloads_added) { if(!strncmp(command_when_done, "connect ", 8)) dpsnprintf(addinfo, addinfolength, "(will join %s when done)", command_when_done + 8); else if(!strcmp(command_when_done, "cl_begindownloads")) dpsnprintf(addinfo, addinfolength, "(will enter the game when done)"); else dpsnprintf(addinfo, addinfolength, "(will do '%s' when done)", command_when_done); *additional_info = addinfo; } else *additional_info = NULL; } *nDownloads = i; if (curl_mutex) Thread_UnlockMutex(curl_mutex); return downinfo; } /* ==================== Curl_FindPackURL finds the URL where to find a given package. For this, it reads a file "curl_urls.txt" of the following format: data*.pk3 - revdm*.pk3 http://revdm/downloads/are/here/ * http://any/other/stuff/is/here/ The URLs should end in /. If not, downloads will still work, but the cached files can't be just put into the data directory with the same download configuration (you might want to do this if you want to tag downloaded files from your server, but you should not). "-" means "don't download". If no single pattern matched, the cvar sv_curl_defaulturl is used as download location instead. Note: pak1.pak and data*.pk3 are excluded from autodownload at another point in this file for obvious reasons. ==================== */ static const char *Curl_FindPackURL(const char *filename) { static char foundurl[1024]; // invoked only by server fs_offset_t filesize; char *buf = (char *) FS_LoadFile("curl_urls.txt", tempmempool, true, &filesize); if(buf && filesize) { // read lines of format "pattern url" char *p = buf; char *pattern = NULL, *patternend = NULL, *url = NULL, *urlend = NULL; qboolean eof = false; pattern = p; while(!eof) { switch(*p) { case 0: eof = true; // fallthrough case '\n': case '\r': if(pattern && url && patternend) { if(!urlend) urlend = p; *patternend = 0; *urlend = 0; if(matchpattern(filename, pattern, true)) { strlcpy(foundurl, url, sizeof(foundurl)); Z_Free(buf); return foundurl; } } pattern = NULL; patternend = NULL; url = NULL; urlend = NULL; break; case ' ': case '\t': if(pattern && !patternend) patternend = p; else if(url && !urlend) urlend = p; break; default: if(!pattern) pattern = p; else if(pattern && patternend && !url) url = p; break; } ++p; } } if(buf) Z_Free(buf); return sv_curl_defaulturl.string; } typedef struct requirement_s { struct requirement_s *next; char filename[MAX_OSPATH]; } requirement; static requirement *requirements = NULL; /* ==================== Curl_RequireFile Adds the given file to the list of requirements. ==================== */ void Curl_RequireFile(const char *filename) { requirement *req = (requirement *) Z_Malloc(sizeof(*requirements)); req->next = requirements; strlcpy(req->filename, filename, sizeof(req->filename)); requirements = req; } /* ==================== Curl_ClearRequirements Clears the list of required files for playing on the current map. This should be called at every map change. ==================== */ void Curl_ClearRequirements(void) { while(requirements) { requirement *req = requirements; requirements = requirements->next; Z_Free(req); } } /* ==================== Curl_SendRequirements Makes the current host_clients download all files he needs. This is done by sending him the following console commands: curl --clear_autodownload curl --pak --for maps/pushmoddm1.bsp --forthismap http://where/this/darn/map/is/pushmoddm1.pk3 curl --finish_autodownload ==================== */ static qboolean Curl_SendRequirement(const char *filename, qboolean foundone, char *sendbuffer, size_t sendbuffer_len) { const char *p; const char *thispack = FS_WhichPack(filename); const char *packurl; if(!thispack || !*thispack) return false; p = strrchr(thispack, '/'); if(p) thispack = p + 1; packurl = Curl_FindPackURL(thispack); if(packurl && *packurl && strcmp(packurl, "-")) { if(!foundone) strlcat(sendbuffer, "curl --clear_autodownload\n", sendbuffer_len); strlcat(sendbuffer, "curl --pak --forthismap --as ", sendbuffer_len); strlcat(sendbuffer, thispack, sendbuffer_len); if(sv_curl_maxspeed.value > 0) dpsnprintf(sendbuffer + strlen(sendbuffer), sendbuffer_len - strlen(sendbuffer), " --maxspeed=%.1f", sv_curl_maxspeed.value); strlcat(sendbuffer, " --for ", sendbuffer_len); strlcat(sendbuffer, filename, sendbuffer_len); strlcat(sendbuffer, " ", sendbuffer_len); strlcat(sendbuffer, packurl, sendbuffer_len); strlcat(sendbuffer, thispack, sendbuffer_len); strlcat(sendbuffer, "\n", sendbuffer_len); return true; } return false; } void Curl_SendRequirements(void) { // for each requirement, find the pack name char sendbuffer[4096] = ""; requirement *req; qboolean foundone = false; const char *p; for(req = requirements; req; req = req->next) foundone = Curl_SendRequirement(req->filename, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; p = sv_curl_serverpackages.string; while(COM_ParseToken_Simple(&p, false, false, true)) foundone = Curl_SendRequirement(com_token, foundone, sendbuffer, sizeof(sendbuffer)) || foundone; if(foundone) strlcat(sendbuffer, "curl --finish_autodownload\n", sizeof(sendbuffer)); if(strlen(sendbuffer) + 1 < sizeof(sendbuffer)) Host_ClientCommands("%s", sendbuffer); else Con_Printf("Could not initiate autodownload due to URL buffer overflow\n"); } darkplaces/darkplaces-wgl-vs2012.vcxproj0000664000175000017500000004331413067716220017440 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28} darkplaceswgl Win32Proj darkplaces-wgl-vs2012 Application v110 MultiByte true Application v110 MultiByte Application v110 MultiByte true Application v110 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 darkplaces/keysym2ucs.c0000664000175000017500000020266013067716220014453 0ustar kalevkalev/* $XFree86$ * This module converts keysym values into the corresponding ISO 10646 * (UCS, Unicode) values. * * The array keysymtab[] contains pairs of X11 keysym values for graphical * characters and the corresponding Unicode value. The function * keysym2ucs() maps a keysym onto a Unicode value using a binary search, * therefore keysymtab[] must remain SORTED by keysym value. * * The keysym -> UTF-8 conversion will hopefully one day be provided * by Xlib via XmbLookupString() and should ideally not have to be * done in X applications. But we are not there yet. * * We allow to represent any UCS character in the range U-00000000 to * U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff. * This admittedly does not cover the entire 31-bit space of UCS, but * it does cover all of the characters up to U-10FFFF, which can be * represented by UTF-16, and more, and it is very unlikely that higher * UCS codes will ever be assigned by ISO. So to get Unicode character * U+ABCD you can directly use keysym 0x0100abcd. * * NOTE: The comments in the table below contain the actual character * encoded in UTF-8, so for viewing and editing best use an editor in * UTF-8 mode. * * Author: Markus G. Kuhn , * University of Cambridge, April 2001 * * Special thanks to Richard Verhoeven for preparing * an initial draft of the mapping table. * * This software is in the public domain. Share and enjoy! * * AUTOMATICALLY GENERATED FILE, DO NOT EDIT !!! (unicode/convmap.pl) */ #include struct codepair { unsigned short keysym; unsigned short ucs; } keysymtab[] = { { 0x01a1, 0x0104 }, /* Aogonek Ä„ LATIN CAPITAL LETTER A WITH OGONEK */ { 0x01a2, 0x02d8 }, /* breve ˘ BREVE */ { 0x01a3, 0x0141 }, /* Lstroke Å LATIN CAPITAL LETTER L WITH STROKE */ { 0x01a5, 0x013d }, /* Lcaron Ľ LATIN CAPITAL LETTER L WITH CARON */ { 0x01a6, 0x015a }, /* Sacute Åš LATIN CAPITAL LETTER S WITH ACUTE */ { 0x01a9, 0x0160 }, /* Scaron Å  LATIN CAPITAL LETTER S WITH CARON */ { 0x01aa, 0x015e }, /* Scedilla Åž LATIN CAPITAL LETTER S WITH CEDILLA */ { 0x01ab, 0x0164 }, /* Tcaron Ť LATIN CAPITAL LETTER T WITH CARON */ { 0x01ac, 0x0179 }, /* Zacute Ź LATIN CAPITAL LETTER Z WITH ACUTE */ { 0x01ae, 0x017d }, /* Zcaron Ž LATIN CAPITAL LETTER Z WITH CARON */ { 0x01af, 0x017b }, /* Zabovedot Å» LATIN CAPITAL LETTER Z WITH DOT ABOVE */ { 0x01b1, 0x0105 }, /* aogonek Ä… LATIN SMALL LETTER A WITH OGONEK */ { 0x01b2, 0x02db }, /* ogonek Ë› OGONEK */ { 0x01b3, 0x0142 }, /* lstroke Å‚ LATIN SMALL LETTER L WITH STROKE */ { 0x01b5, 0x013e }, /* lcaron ľ LATIN SMALL LETTER L WITH CARON */ { 0x01b6, 0x015b }, /* sacute Å› LATIN SMALL LETTER S WITH ACUTE */ { 0x01b7, 0x02c7 }, /* caron ˇ CARON */ { 0x01b9, 0x0161 }, /* scaron Å¡ LATIN SMALL LETTER S WITH CARON */ { 0x01ba, 0x015f }, /* scedilla ÅŸ LATIN SMALL LETTER S WITH CEDILLA */ { 0x01bb, 0x0165 }, /* tcaron Å¥ LATIN SMALL LETTER T WITH CARON */ { 0x01bc, 0x017a }, /* zacute ź LATIN SMALL LETTER Z WITH ACUTE */ { 0x01bd, 0x02dd }, /* doubleacute Ë DOUBLE ACUTE ACCENT */ { 0x01be, 0x017e }, /* zcaron ž LATIN SMALL LETTER Z WITH CARON */ { 0x01bf, 0x017c }, /* zabovedot ż LATIN SMALL LETTER Z WITH DOT ABOVE */ { 0x01c0, 0x0154 }, /* Racute Å” LATIN CAPITAL LETTER R WITH ACUTE */ { 0x01c3, 0x0102 }, /* Abreve Ä‚ LATIN CAPITAL LETTER A WITH BREVE */ { 0x01c5, 0x0139 }, /* Lacute Ĺ LATIN CAPITAL LETTER L WITH ACUTE */ { 0x01c6, 0x0106 }, /* Cacute Ć LATIN CAPITAL LETTER C WITH ACUTE */ { 0x01c8, 0x010c }, /* Ccaron ÄŒ LATIN CAPITAL LETTER C WITH CARON */ { 0x01ca, 0x0118 }, /* Eogonek Ę LATIN CAPITAL LETTER E WITH OGONEK */ { 0x01cc, 0x011a }, /* Ecaron Äš LATIN CAPITAL LETTER E WITH CARON */ { 0x01cf, 0x010e }, /* Dcaron ÄŽ LATIN CAPITAL LETTER D WITH CARON */ { 0x01d0, 0x0110 }, /* Dstroke Ä LATIN CAPITAL LETTER D WITH STROKE */ { 0x01d1, 0x0143 }, /* Nacute Ń LATIN CAPITAL LETTER N WITH ACUTE */ { 0x01d2, 0x0147 }, /* Ncaron Ň LATIN CAPITAL LETTER N WITH CARON */ { 0x01d5, 0x0150 }, /* Odoubleacute Å LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ { 0x01d8, 0x0158 }, /* Rcaron Ř LATIN CAPITAL LETTER R WITH CARON */ { 0x01d9, 0x016e }, /* Uring Å® LATIN CAPITAL LETTER U WITH RING ABOVE */ { 0x01db, 0x0170 }, /* Udoubleacute Å° LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ { 0x01de, 0x0162 }, /* Tcedilla Å¢ LATIN CAPITAL LETTER T WITH CEDILLA */ { 0x01e0, 0x0155 }, /* racute Å• LATIN SMALL LETTER R WITH ACUTE */ { 0x01e3, 0x0103 }, /* abreve ă LATIN SMALL LETTER A WITH BREVE */ { 0x01e5, 0x013a }, /* lacute ĺ LATIN SMALL LETTER L WITH ACUTE */ { 0x01e6, 0x0107 }, /* cacute ć LATIN SMALL LETTER C WITH ACUTE */ { 0x01e8, 0x010d }, /* ccaron Ä LATIN SMALL LETTER C WITH CARON */ { 0x01ea, 0x0119 }, /* eogonek Ä™ LATIN SMALL LETTER E WITH OGONEK */ { 0x01ec, 0x011b }, /* ecaron Ä› LATIN SMALL LETTER E WITH CARON */ { 0x01ef, 0x010f }, /* dcaron Ä LATIN SMALL LETTER D WITH CARON */ { 0x01f0, 0x0111 }, /* dstroke Ä‘ LATIN SMALL LETTER D WITH STROKE */ { 0x01f1, 0x0144 }, /* nacute Å„ LATIN SMALL LETTER N WITH ACUTE */ { 0x01f2, 0x0148 }, /* ncaron ň LATIN SMALL LETTER N WITH CARON */ { 0x01f5, 0x0151 }, /* odoubleacute Å‘ LATIN SMALL LETTER O WITH DOUBLE ACUTE */ { 0x01f8, 0x0159 }, /* rcaron Å™ LATIN SMALL LETTER R WITH CARON */ { 0x01f9, 0x016f }, /* uring ů LATIN SMALL LETTER U WITH RING ABOVE */ { 0x01fb, 0x0171 }, /* udoubleacute ű LATIN SMALL LETTER U WITH DOUBLE ACUTE */ { 0x01fe, 0x0163 }, /* tcedilla Å£ LATIN SMALL LETTER T WITH CEDILLA */ { 0x01ff, 0x02d9 }, /* abovedot Ë™ DOT ABOVE */ { 0x02a1, 0x0126 }, /* Hstroke Ħ LATIN CAPITAL LETTER H WITH STROKE */ { 0x02a6, 0x0124 }, /* Hcircumflex Ĥ LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ { 0x02a9, 0x0130 }, /* Iabovedot Ä° LATIN CAPITAL LETTER I WITH DOT ABOVE */ { 0x02ab, 0x011e }, /* Gbreve Äž LATIN CAPITAL LETTER G WITH BREVE */ { 0x02ac, 0x0134 }, /* Jcircumflex Ä´ LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ { 0x02b1, 0x0127 }, /* hstroke ħ LATIN SMALL LETTER H WITH STROKE */ { 0x02b6, 0x0125 }, /* hcircumflex Ä¥ LATIN SMALL LETTER H WITH CIRCUMFLEX */ { 0x02b9, 0x0131 }, /* idotless ı LATIN SMALL LETTER DOTLESS I */ { 0x02bb, 0x011f }, /* gbreve ÄŸ LATIN SMALL LETTER G WITH BREVE */ { 0x02bc, 0x0135 }, /* jcircumflex ĵ LATIN SMALL LETTER J WITH CIRCUMFLEX */ { 0x02c5, 0x010a }, /* Cabovedot ÄŠ LATIN CAPITAL LETTER C WITH DOT ABOVE */ { 0x02c6, 0x0108 }, /* Ccircumflex Ĉ LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ { 0x02d5, 0x0120 }, /* Gabovedot Ä  LATIN CAPITAL LETTER G WITH DOT ABOVE */ { 0x02d8, 0x011c }, /* Gcircumflex Äœ LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ { 0x02dd, 0x016c }, /* Ubreve Ŭ LATIN CAPITAL LETTER U WITH BREVE */ { 0x02de, 0x015c }, /* Scircumflex Åœ LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ { 0x02e5, 0x010b }, /* cabovedot Ä‹ LATIN SMALL LETTER C WITH DOT ABOVE */ { 0x02e6, 0x0109 }, /* ccircumflex ĉ LATIN SMALL LETTER C WITH CIRCUMFLEX */ { 0x02f5, 0x0121 }, /* gabovedot Ä¡ LATIN SMALL LETTER G WITH DOT ABOVE */ { 0x02f8, 0x011d }, /* gcircumflex Ä LATIN SMALL LETTER G WITH CIRCUMFLEX */ { 0x02fd, 0x016d }, /* ubreve Å­ LATIN SMALL LETTER U WITH BREVE */ { 0x02fe, 0x015d }, /* scircumflex Å LATIN SMALL LETTER S WITH CIRCUMFLEX */ { 0x03a2, 0x0138 }, /* kra ĸ LATIN SMALL LETTER KRA */ { 0x03a3, 0x0156 }, /* Rcedilla Å– LATIN CAPITAL LETTER R WITH CEDILLA */ { 0x03a5, 0x0128 }, /* Itilde Ĩ LATIN CAPITAL LETTER I WITH TILDE */ { 0x03a6, 0x013b }, /* Lcedilla Ä» LATIN CAPITAL LETTER L WITH CEDILLA */ { 0x03aa, 0x0112 }, /* Emacron Ä’ LATIN CAPITAL LETTER E WITH MACRON */ { 0x03ab, 0x0122 }, /* Gcedilla Ä¢ LATIN CAPITAL LETTER G WITH CEDILLA */ { 0x03ac, 0x0166 }, /* Tslash Ŧ LATIN CAPITAL LETTER T WITH STROKE */ { 0x03b3, 0x0157 }, /* rcedilla Å— LATIN SMALL LETTER R WITH CEDILLA */ { 0x03b5, 0x0129 }, /* itilde Ä© LATIN SMALL LETTER I WITH TILDE */ { 0x03b6, 0x013c }, /* lcedilla ļ LATIN SMALL LETTER L WITH CEDILLA */ { 0x03ba, 0x0113 }, /* emacron Ä“ LATIN SMALL LETTER E WITH MACRON */ { 0x03bb, 0x0123 }, /* gcedilla Ä£ LATIN SMALL LETTER G WITH CEDILLA */ { 0x03bc, 0x0167 }, /* tslash ŧ LATIN SMALL LETTER T WITH STROKE */ { 0x03bd, 0x014a }, /* ENG ÅŠ LATIN CAPITAL LETTER ENG */ { 0x03bf, 0x014b }, /* eng Å‹ LATIN SMALL LETTER ENG */ { 0x03c0, 0x0100 }, /* Amacron Ä€ LATIN CAPITAL LETTER A WITH MACRON */ { 0x03c7, 0x012e }, /* Iogonek Ä® LATIN CAPITAL LETTER I WITH OGONEK */ { 0x03cc, 0x0116 }, /* Eabovedot Ä– LATIN CAPITAL LETTER E WITH DOT ABOVE */ { 0x03cf, 0x012a }, /* Imacron Ī LATIN CAPITAL LETTER I WITH MACRON */ { 0x03d1, 0x0145 }, /* Ncedilla Å… LATIN CAPITAL LETTER N WITH CEDILLA */ { 0x03d2, 0x014c }, /* Omacron ÅŒ LATIN CAPITAL LETTER O WITH MACRON */ { 0x03d3, 0x0136 }, /* Kcedilla Ķ LATIN CAPITAL LETTER K WITH CEDILLA */ { 0x03d9, 0x0172 }, /* Uogonek Ų LATIN CAPITAL LETTER U WITH OGONEK */ { 0x03dd, 0x0168 }, /* Utilde Ũ LATIN CAPITAL LETTER U WITH TILDE */ { 0x03de, 0x016a }, /* Umacron Ū LATIN CAPITAL LETTER U WITH MACRON */ { 0x03e0, 0x0101 }, /* amacron Ä LATIN SMALL LETTER A WITH MACRON */ { 0x03e7, 0x012f }, /* iogonek į LATIN SMALL LETTER I WITH OGONEK */ { 0x03ec, 0x0117 }, /* eabovedot Ä— LATIN SMALL LETTER E WITH DOT ABOVE */ { 0x03ef, 0x012b }, /* imacron Ä« LATIN SMALL LETTER I WITH MACRON */ { 0x03f1, 0x0146 }, /* ncedilla ņ LATIN SMALL LETTER N WITH CEDILLA */ { 0x03f2, 0x014d }, /* omacron Å LATIN SMALL LETTER O WITH MACRON */ { 0x03f3, 0x0137 }, /* kcedilla Ä· LATIN SMALL LETTER K WITH CEDILLA */ { 0x03f9, 0x0173 }, /* uogonek ų LATIN SMALL LETTER U WITH OGONEK */ { 0x03fd, 0x0169 }, /* utilde Å© LATIN SMALL LETTER U WITH TILDE */ { 0x03fe, 0x016b }, /* umacron Å« LATIN SMALL LETTER U WITH MACRON */ { 0x047e, 0x203e }, /* overline ‾ OVERLINE */ { 0x04a1, 0x3002 }, /* kana_fullstop 。 IDEOGRAPHIC FULL STOP */ { 0x04a2, 0x300c }, /* kana_openingbracket 「 LEFT CORNER BRACKET */ { 0x04a3, 0x300d }, /* kana_closingbracket 〠RIGHT CORNER BRACKET */ { 0x04a4, 0x3001 }, /* kana_comma 〠IDEOGRAPHIC COMMA */ { 0x04a5, 0x30fb }, /* kana_conjunctive ・ KATAKANA MIDDLE DOT */ { 0x04a6, 0x30f2 }, /* kana_WO ヲ KATAKANA LETTER WO */ { 0x04a7, 0x30a1 }, /* kana_a ã‚¡ KATAKANA LETTER SMALL A */ { 0x04a8, 0x30a3 }, /* kana_i ã‚£ KATAKANA LETTER SMALL I */ { 0x04a9, 0x30a5 }, /* kana_u ã‚¥ KATAKANA LETTER SMALL U */ { 0x04aa, 0x30a7 }, /* kana_e ェ KATAKANA LETTER SMALL E */ { 0x04ab, 0x30a9 }, /* kana_o ã‚© KATAKANA LETTER SMALL O */ { 0x04ac, 0x30e3 }, /* kana_ya ャ KATAKANA LETTER SMALL YA */ { 0x04ad, 0x30e5 }, /* kana_yu ュ KATAKANA LETTER SMALL YU */ { 0x04ae, 0x30e7 }, /* kana_yo ョ KATAKANA LETTER SMALL YO */ { 0x04af, 0x30c3 }, /* kana_tsu ッ KATAKANA LETTER SMALL TU */ { 0x04b0, 0x30fc }, /* prolongedsound ー KATAKANA-HIRAGANA PROLONGED SOUND MARK */ { 0x04b1, 0x30a2 }, /* kana_A ã‚¢ KATAKANA LETTER A */ { 0x04b2, 0x30a4 }, /* kana_I イ KATAKANA LETTER I */ { 0x04b3, 0x30a6 }, /* kana_U ウ KATAKANA LETTER U */ { 0x04b4, 0x30a8 }, /* kana_E エ KATAKANA LETTER E */ { 0x04b5, 0x30aa }, /* kana_O オ KATAKANA LETTER O */ { 0x04b6, 0x30ab }, /* kana_KA ã‚« KATAKANA LETTER KA */ { 0x04b7, 0x30ad }, /* kana_KI ã‚­ KATAKANA LETTER KI */ { 0x04b8, 0x30af }, /* kana_KU ク KATAKANA LETTER KU */ { 0x04b9, 0x30b1 }, /* kana_KE ケ KATAKANA LETTER KE */ { 0x04ba, 0x30b3 }, /* kana_KO コ KATAKANA LETTER KO */ { 0x04bb, 0x30b5 }, /* kana_SA サ KATAKANA LETTER SA */ { 0x04bc, 0x30b7 }, /* kana_SHI ã‚· KATAKANA LETTER SI */ { 0x04bd, 0x30b9 }, /* kana_SU ス KATAKANA LETTER SU */ { 0x04be, 0x30bb }, /* kana_SE ã‚» KATAKANA LETTER SE */ { 0x04bf, 0x30bd }, /* kana_SO ソ KATAKANA LETTER SO */ { 0x04c0, 0x30bf }, /* kana_TA ã‚¿ KATAKANA LETTER TA */ { 0x04c1, 0x30c1 }, /* kana_CHI ムKATAKANA LETTER TI */ { 0x04c2, 0x30c4 }, /* kana_TSU ツ KATAKANA LETTER TU */ { 0x04c3, 0x30c6 }, /* kana_TE テ KATAKANA LETTER TE */ { 0x04c4, 0x30c8 }, /* kana_TO ト KATAKANA LETTER TO */ { 0x04c5, 0x30ca }, /* kana_NA ナ KATAKANA LETTER NA */ { 0x04c6, 0x30cb }, /* kana_NI ニ KATAKANA LETTER NI */ { 0x04c7, 0x30cc }, /* kana_NU ヌ KATAKANA LETTER NU */ { 0x04c8, 0x30cd }, /* kana_NE ムKATAKANA LETTER NE */ { 0x04c9, 0x30ce }, /* kana_NO ノ KATAKANA LETTER NO */ { 0x04ca, 0x30cf }, /* kana_HA ムKATAKANA LETTER HA */ { 0x04cb, 0x30d2 }, /* kana_HI ヒ KATAKANA LETTER HI */ { 0x04cc, 0x30d5 }, /* kana_FU フ KATAKANA LETTER HU */ { 0x04cd, 0x30d8 }, /* kana_HE ヘ KATAKANA LETTER HE */ { 0x04ce, 0x30db }, /* kana_HO ホ KATAKANA LETTER HO */ { 0x04cf, 0x30de }, /* kana_MA マ KATAKANA LETTER MA */ { 0x04d0, 0x30df }, /* kana_MI ミ KATAKANA LETTER MI */ { 0x04d1, 0x30e0 }, /* kana_MU ム KATAKANA LETTER MU */ { 0x04d2, 0x30e1 }, /* kana_ME メ KATAKANA LETTER ME */ { 0x04d3, 0x30e2 }, /* kana_MO モ KATAKANA LETTER MO */ { 0x04d4, 0x30e4 }, /* kana_YA ヤ KATAKANA LETTER YA */ { 0x04d5, 0x30e6 }, /* kana_YU ユ KATAKANA LETTER YU */ { 0x04d6, 0x30e8 }, /* kana_YO ヨ KATAKANA LETTER YO */ { 0x04d7, 0x30e9 }, /* kana_RA ラ KATAKANA LETTER RA */ { 0x04d8, 0x30ea }, /* kana_RI リ KATAKANA LETTER RI */ { 0x04d9, 0x30eb }, /* kana_RU ル KATAKANA LETTER RU */ { 0x04da, 0x30ec }, /* kana_RE レ KATAKANA LETTER RE */ { 0x04db, 0x30ed }, /* kana_RO ロ KATAKANA LETTER RO */ { 0x04dc, 0x30ef }, /* kana_WA ワ KATAKANA LETTER WA */ { 0x04dd, 0x30f3 }, /* kana_N ン KATAKANA LETTER N */ { 0x04de, 0x309b }, /* voicedsound ã‚› KATAKANA-HIRAGANA VOICED SOUND MARK */ { 0x04df, 0x309c }, /* semivoicedsound ã‚œ KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ { 0x05ac, 0x060c }, /* Arabic_comma ØŒ ARABIC COMMA */ { 0x05bb, 0x061b }, /* Arabic_semicolon Ø› ARABIC SEMICOLON */ { 0x05bf, 0x061f }, /* Arabic_question_mark ØŸ ARABIC QUESTION MARK */ { 0x05c1, 0x0621 }, /* Arabic_hamza Ø¡ ARABIC LETTER HAMZA */ { 0x05c2, 0x0622 }, /* Arabic_maddaonalef Ø¢ ARABIC LETTER ALEF WITH MADDA ABOVE */ { 0x05c3, 0x0623 }, /* Arabic_hamzaonalef Ø£ ARABIC LETTER ALEF WITH HAMZA ABOVE */ { 0x05c4, 0x0624 }, /* Arabic_hamzaonwaw ؤ ARABIC LETTER WAW WITH HAMZA ABOVE */ { 0x05c5, 0x0625 }, /* Arabic_hamzaunderalef Ø¥ ARABIC LETTER ALEF WITH HAMZA BELOW */ { 0x05c6, 0x0626 }, /* Arabic_hamzaonyeh ئ ARABIC LETTER YEH WITH HAMZA ABOVE */ { 0x05c7, 0x0627 }, /* Arabic_alef ا ARABIC LETTER ALEF */ { 0x05c8, 0x0628 }, /* Arabic_beh ب ARABIC LETTER BEH */ { 0x05c9, 0x0629 }, /* Arabic_tehmarbuta Ø© ARABIC LETTER TEH MARBUTA */ { 0x05ca, 0x062a }, /* Arabic_teh ت ARABIC LETTER TEH */ { 0x05cb, 0x062b }, /* Arabic_theh Ø« ARABIC LETTER THEH */ { 0x05cc, 0x062c }, /* Arabic_jeem ج ARABIC LETTER JEEM */ { 0x05cd, 0x062d }, /* Arabic_hah Ø­ ARABIC LETTER HAH */ { 0x05ce, 0x062e }, /* Arabic_khah Ø® ARABIC LETTER KHAH */ { 0x05cf, 0x062f }, /* Arabic_dal د ARABIC LETTER DAL */ { 0x05d0, 0x0630 }, /* Arabic_thal Ø° ARABIC LETTER THAL */ { 0x05d1, 0x0631 }, /* Arabic_ra ر ARABIC LETTER REH */ { 0x05d2, 0x0632 }, /* Arabic_zain ز ARABIC LETTER ZAIN */ { 0x05d3, 0x0633 }, /* Arabic_seen س ARABIC LETTER SEEN */ { 0x05d4, 0x0634 }, /* Arabic_sheen Ø´ ARABIC LETTER SHEEN */ { 0x05d5, 0x0635 }, /* Arabic_sad ص ARABIC LETTER SAD */ { 0x05d6, 0x0636 }, /* Arabic_dad ض ARABIC LETTER DAD */ { 0x05d7, 0x0637 }, /* Arabic_tah Ø· ARABIC LETTER TAH */ { 0x05d8, 0x0638 }, /* Arabic_zah ظ ARABIC LETTER ZAH */ { 0x05d9, 0x0639 }, /* Arabic_ain ع ARABIC LETTER AIN */ { 0x05da, 0x063a }, /* Arabic_ghain غ ARABIC LETTER GHAIN */ { 0x05e0, 0x0640 }, /* Arabic_tatweel Ù€ ARABIC TATWEEL */ { 0x05e1, 0x0641 }, /* Arabic_feh Ù ARABIC LETTER FEH */ { 0x05e2, 0x0642 }, /* Arabic_qaf Ù‚ ARABIC LETTER QAF */ { 0x05e3, 0x0643 }, /* Arabic_kaf Ùƒ ARABIC LETTER KAF */ { 0x05e4, 0x0644 }, /* Arabic_lam Ù„ ARABIC LETTER LAM */ { 0x05e5, 0x0645 }, /* Arabic_meem Ù… ARABIC LETTER MEEM */ { 0x05e6, 0x0646 }, /* Arabic_noon Ù† ARABIC LETTER NOON */ { 0x05e7, 0x0647 }, /* Arabic_ha Ù‡ ARABIC LETTER HEH */ { 0x05e8, 0x0648 }, /* Arabic_waw Ùˆ ARABIC LETTER WAW */ { 0x05e9, 0x0649 }, /* Arabic_alefmaksura Ù‰ ARABIC LETTER ALEF MAKSURA */ { 0x05ea, 0x064a }, /* Arabic_yeh ÙŠ ARABIC LETTER YEH */ { 0x05eb, 0x064b }, /* Arabic_fathatan Ù‹ ARABIC FATHATAN */ { 0x05ec, 0x064c }, /* Arabic_dammatan ÙŒ ARABIC DAMMATAN */ { 0x05ed, 0x064d }, /* Arabic_kasratan Ù ARABIC KASRATAN */ { 0x05ee, 0x064e }, /* Arabic_fatha ÙŽ ARABIC FATHA */ { 0x05ef, 0x064f }, /* Arabic_damma Ù ARABIC DAMMA */ { 0x05f0, 0x0650 }, /* Arabic_kasra Ù ARABIC KASRA */ { 0x05f1, 0x0651 }, /* Arabic_shadda Ù‘ ARABIC SHADDA */ { 0x05f2, 0x0652 }, /* Arabic_sukun Ù’ ARABIC SUKUN */ { 0x06a1, 0x0452 }, /* Serbian_dje Ñ’ CYRILLIC SMALL LETTER DJE */ { 0x06a2, 0x0453 }, /* Macedonia_gje Ñ“ CYRILLIC SMALL LETTER GJE */ { 0x06a3, 0x0451 }, /* Cyrillic_io Ñ‘ CYRILLIC SMALL LETTER IO */ { 0x06a4, 0x0454 }, /* Ukrainian_ie Ñ” CYRILLIC SMALL LETTER UKRAINIAN IE */ { 0x06a5, 0x0455 }, /* Macedonia_dse Ñ• CYRILLIC SMALL LETTER DZE */ { 0x06a6, 0x0456 }, /* Ukrainian_i Ñ– CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ { 0x06a7, 0x0457 }, /* Ukrainian_yi Ñ— CYRILLIC SMALL LETTER YI */ { 0x06a8, 0x0458 }, /* Cyrillic_je ј CYRILLIC SMALL LETTER JE */ { 0x06a9, 0x0459 }, /* Cyrillic_lje Ñ™ CYRILLIC SMALL LETTER LJE */ { 0x06aa, 0x045a }, /* Cyrillic_nje Ñš CYRILLIC SMALL LETTER NJE */ { 0x06ab, 0x045b }, /* Serbian_tshe Ñ› CYRILLIC SMALL LETTER TSHE */ { 0x06ac, 0x045c }, /* Macedonia_kje Ñœ CYRILLIC SMALL LETTER KJE */ { 0x06ae, 0x045e }, /* Byelorussian_shortu Ñž CYRILLIC SMALL LETTER SHORT U */ { 0x06af, 0x045f }, /* Cyrillic_dzhe ÑŸ CYRILLIC SMALL LETTER DZHE */ { 0x06b0, 0x2116 }, /* numerosign â„– NUMERO SIGN */ { 0x06b1, 0x0402 }, /* Serbian_DJE Ђ CYRILLIC CAPITAL LETTER DJE */ { 0x06b2, 0x0403 }, /* Macedonia_GJE Ѓ CYRILLIC CAPITAL LETTER GJE */ { 0x06b3, 0x0401 }, /* Cyrillic_IO Ð CYRILLIC CAPITAL LETTER IO */ { 0x06b4, 0x0404 }, /* Ukrainian_IE Є CYRILLIC CAPITAL LETTER UKRAINIAN IE */ { 0x06b5, 0x0405 }, /* Macedonia_DSE Ð… CYRILLIC CAPITAL LETTER DZE */ { 0x06b6, 0x0406 }, /* Ukrainian_I І CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ { 0x06b7, 0x0407 }, /* Ukrainian_YI Ї CYRILLIC CAPITAL LETTER YI */ { 0x06b8, 0x0408 }, /* Cyrillic_JE Ј CYRILLIC CAPITAL LETTER JE */ { 0x06b9, 0x0409 }, /* Cyrillic_LJE Љ CYRILLIC CAPITAL LETTER LJE */ { 0x06ba, 0x040a }, /* Cyrillic_NJE Њ CYRILLIC CAPITAL LETTER NJE */ { 0x06bb, 0x040b }, /* Serbian_TSHE Ћ CYRILLIC CAPITAL LETTER TSHE */ { 0x06bc, 0x040c }, /* Macedonia_KJE ÐŒ CYRILLIC CAPITAL LETTER KJE */ { 0x06be, 0x040e }, /* Byelorussian_SHORTU ÐŽ CYRILLIC CAPITAL LETTER SHORT U */ { 0x06bf, 0x040f }, /* Cyrillic_DZHE Ð CYRILLIC CAPITAL LETTER DZHE */ { 0x06c0, 0x044e }, /* Cyrillic_yu ÑŽ CYRILLIC SMALL LETTER YU */ { 0x06c1, 0x0430 }, /* Cyrillic_a а CYRILLIC SMALL LETTER A */ { 0x06c2, 0x0431 }, /* Cyrillic_be б CYRILLIC SMALL LETTER BE */ { 0x06c3, 0x0446 }, /* Cyrillic_tse ц CYRILLIC SMALL LETTER TSE */ { 0x06c4, 0x0434 }, /* Cyrillic_de д CYRILLIC SMALL LETTER DE */ { 0x06c5, 0x0435 }, /* Cyrillic_ie е CYRILLIC SMALL LETTER IE */ { 0x06c6, 0x0444 }, /* Cyrillic_ef Ñ„ CYRILLIC SMALL LETTER EF */ { 0x06c7, 0x0433 }, /* Cyrillic_ghe г CYRILLIC SMALL LETTER GHE */ { 0x06c8, 0x0445 }, /* Cyrillic_ha Ñ… CYRILLIC SMALL LETTER HA */ { 0x06c9, 0x0438 }, /* Cyrillic_i и CYRILLIC SMALL LETTER I */ { 0x06ca, 0x0439 }, /* Cyrillic_shorti й CYRILLIC SMALL LETTER SHORT I */ { 0x06cb, 0x043a }, /* Cyrillic_ka к CYRILLIC SMALL LETTER KA */ { 0x06cc, 0x043b }, /* Cyrillic_el л CYRILLIC SMALL LETTER EL */ { 0x06cd, 0x043c }, /* Cyrillic_em м CYRILLIC SMALL LETTER EM */ { 0x06ce, 0x043d }, /* Cyrillic_en н CYRILLIC SMALL LETTER EN */ { 0x06cf, 0x043e }, /* Cyrillic_o о CYRILLIC SMALL LETTER O */ { 0x06d0, 0x043f }, /* Cyrillic_pe п CYRILLIC SMALL LETTER PE */ { 0x06d1, 0x044f }, /* Cyrillic_ya Ñ CYRILLIC SMALL LETTER YA */ { 0x06d2, 0x0440 }, /* Cyrillic_er Ñ€ CYRILLIC SMALL LETTER ER */ { 0x06d3, 0x0441 }, /* Cyrillic_es Ñ CYRILLIC SMALL LETTER ES */ { 0x06d4, 0x0442 }, /* Cyrillic_te Ñ‚ CYRILLIC SMALL LETTER TE */ { 0x06d5, 0x0443 }, /* Cyrillic_u у CYRILLIC SMALL LETTER U */ { 0x06d6, 0x0436 }, /* Cyrillic_zhe ж CYRILLIC SMALL LETTER ZHE */ { 0x06d7, 0x0432 }, /* Cyrillic_ve в CYRILLIC SMALL LETTER VE */ { 0x06d8, 0x044c }, /* Cyrillic_softsign ÑŒ CYRILLIC SMALL LETTER SOFT SIGN */ { 0x06d9, 0x044b }, /* Cyrillic_yeru Ñ‹ CYRILLIC SMALL LETTER YERU */ { 0x06da, 0x0437 }, /* Cyrillic_ze з CYRILLIC SMALL LETTER ZE */ { 0x06db, 0x0448 }, /* Cyrillic_sha ш CYRILLIC SMALL LETTER SHA */ { 0x06dc, 0x044d }, /* Cyrillic_e Ñ CYRILLIC SMALL LETTER E */ { 0x06dd, 0x0449 }, /* Cyrillic_shcha щ CYRILLIC SMALL LETTER SHCHA */ { 0x06de, 0x0447 }, /* Cyrillic_che ч CYRILLIC SMALL LETTER CHE */ { 0x06df, 0x044a }, /* Cyrillic_hardsign ÑŠ CYRILLIC SMALL LETTER HARD SIGN */ { 0x06e0, 0x042e }, /* Cyrillic_YU Ю CYRILLIC CAPITAL LETTER YU */ { 0x06e1, 0x0410 }, /* Cyrillic_A Ð CYRILLIC CAPITAL LETTER A */ { 0x06e2, 0x0411 }, /* Cyrillic_BE Б CYRILLIC CAPITAL LETTER BE */ { 0x06e3, 0x0426 }, /* Cyrillic_TSE Ц CYRILLIC CAPITAL LETTER TSE */ { 0x06e4, 0x0414 }, /* Cyrillic_DE Д CYRILLIC CAPITAL LETTER DE */ { 0x06e5, 0x0415 }, /* Cyrillic_IE Е CYRILLIC CAPITAL LETTER IE */ { 0x06e6, 0x0424 }, /* Cyrillic_EF Ф CYRILLIC CAPITAL LETTER EF */ { 0x06e7, 0x0413 }, /* Cyrillic_GHE Г CYRILLIC CAPITAL LETTER GHE */ { 0x06e8, 0x0425 }, /* Cyrillic_HA Ð¥ CYRILLIC CAPITAL LETTER HA */ { 0x06e9, 0x0418 }, /* Cyrillic_I И CYRILLIC CAPITAL LETTER I */ { 0x06ea, 0x0419 }, /* Cyrillic_SHORTI Й CYRILLIC CAPITAL LETTER SHORT I */ { 0x06eb, 0x041a }, /* Cyrillic_KA К CYRILLIC CAPITAL LETTER KA */ { 0x06ec, 0x041b }, /* Cyrillic_EL Л CYRILLIC CAPITAL LETTER EL */ { 0x06ed, 0x041c }, /* Cyrillic_EM Ðœ CYRILLIC CAPITAL LETTER EM */ { 0x06ee, 0x041d }, /* Cyrillic_EN Ð CYRILLIC CAPITAL LETTER EN */ { 0x06ef, 0x041e }, /* Cyrillic_O О CYRILLIC CAPITAL LETTER O */ { 0x06f0, 0x041f }, /* Cyrillic_PE П CYRILLIC CAPITAL LETTER PE */ { 0x06f1, 0x042f }, /* Cyrillic_YA Я CYRILLIC CAPITAL LETTER YA */ { 0x06f2, 0x0420 }, /* Cyrillic_ER Р CYRILLIC CAPITAL LETTER ER */ { 0x06f3, 0x0421 }, /* Cyrillic_ES С CYRILLIC CAPITAL LETTER ES */ { 0x06f4, 0x0422 }, /* Cyrillic_TE Т CYRILLIC CAPITAL LETTER TE */ { 0x06f5, 0x0423 }, /* Cyrillic_U У CYRILLIC CAPITAL LETTER U */ { 0x06f6, 0x0416 }, /* Cyrillic_ZHE Ж CYRILLIC CAPITAL LETTER ZHE */ { 0x06f7, 0x0412 }, /* Cyrillic_VE Ð’ CYRILLIC CAPITAL LETTER VE */ { 0x06f8, 0x042c }, /* Cyrillic_SOFTSIGN Ь CYRILLIC CAPITAL LETTER SOFT SIGN */ { 0x06f9, 0x042b }, /* Cyrillic_YERU Ы CYRILLIC CAPITAL LETTER YERU */ { 0x06fa, 0x0417 }, /* Cyrillic_ZE З CYRILLIC CAPITAL LETTER ZE */ { 0x06fb, 0x0428 }, /* Cyrillic_SHA Ш CYRILLIC CAPITAL LETTER SHA */ { 0x06fc, 0x042d }, /* Cyrillic_E Э CYRILLIC CAPITAL LETTER E */ { 0x06fd, 0x0429 }, /* Cyrillic_SHCHA Щ CYRILLIC CAPITAL LETTER SHCHA */ { 0x06fe, 0x0427 }, /* Cyrillic_CHE Ч CYRILLIC CAPITAL LETTER CHE */ { 0x06ff, 0x042a }, /* Cyrillic_HARDSIGN Ъ CYRILLIC CAPITAL LETTER HARD SIGN */ { 0x07a1, 0x0386 }, /* Greek_ALPHAaccent Ά GREEK CAPITAL LETTER ALPHA WITH TONOS */ { 0x07a2, 0x0388 }, /* Greek_EPSILONaccent Έ GREEK CAPITAL LETTER EPSILON WITH TONOS */ { 0x07a3, 0x0389 }, /* Greek_ETAaccent Ή GREEK CAPITAL LETTER ETA WITH TONOS */ { 0x07a4, 0x038a }, /* Greek_IOTAaccent Ί GREEK CAPITAL LETTER IOTA WITH TONOS */ { 0x07a5, 0x03aa }, /* Greek_IOTAdiaeresis Ϊ GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ { 0x07a7, 0x038c }, /* Greek_OMICRONaccent ÎŒ GREEK CAPITAL LETTER OMICRON WITH TONOS */ { 0x07a8, 0x038e }, /* Greek_UPSILONaccent ÎŽ GREEK CAPITAL LETTER UPSILON WITH TONOS */ { 0x07a9, 0x03ab }, /* Greek_UPSILONdieresis Ϋ GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ { 0x07ab, 0x038f }, /* Greek_OMEGAaccent Î GREEK CAPITAL LETTER OMEGA WITH TONOS */ { 0x07ae, 0x0385 }, /* Greek_accentdieresis Î… GREEK DIALYTIKA TONOS */ { 0x07af, 0x2015 }, /* Greek_horizbar ― HORIZONTAL BAR */ { 0x07b1, 0x03ac }, /* Greek_alphaaccent ά GREEK SMALL LETTER ALPHA WITH TONOS */ { 0x07b2, 0x03ad }, /* Greek_epsilonaccent έ GREEK SMALL LETTER EPSILON WITH TONOS */ { 0x07b3, 0x03ae }, /* Greek_etaaccent ή GREEK SMALL LETTER ETA WITH TONOS */ { 0x07b4, 0x03af }, /* Greek_iotaaccent ί GREEK SMALL LETTER IOTA WITH TONOS */ { 0x07b5, 0x03ca }, /* Greek_iotadieresis ÏŠ GREEK SMALL LETTER IOTA WITH DIALYTIKA */ { 0x07b6, 0x0390 }, /* Greek_iotaaccentdieresis Î GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ { 0x07b7, 0x03cc }, /* Greek_omicronaccent ÏŒ GREEK SMALL LETTER OMICRON WITH TONOS */ { 0x07b8, 0x03cd }, /* Greek_upsilonaccent Ï GREEK SMALL LETTER UPSILON WITH TONOS */ { 0x07b9, 0x03cb }, /* Greek_upsilondieresis Ï‹ GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ { 0x07ba, 0x03b0 }, /* Greek_upsilonaccentdieresis ΰ GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ { 0x07bb, 0x03ce }, /* Greek_omegaaccent ÏŽ GREEK SMALL LETTER OMEGA WITH TONOS */ { 0x07c1, 0x0391 }, /* Greek_ALPHA Α GREEK CAPITAL LETTER ALPHA */ { 0x07c2, 0x0392 }, /* Greek_BETA Î’ GREEK CAPITAL LETTER BETA */ { 0x07c3, 0x0393 }, /* Greek_GAMMA Γ GREEK CAPITAL LETTER GAMMA */ { 0x07c4, 0x0394 }, /* Greek_DELTA Δ GREEK CAPITAL LETTER DELTA */ { 0x07c5, 0x0395 }, /* Greek_EPSILON Ε GREEK CAPITAL LETTER EPSILON */ { 0x07c6, 0x0396 }, /* Greek_ZETA Ζ GREEK CAPITAL LETTER ZETA */ { 0x07c7, 0x0397 }, /* Greek_ETA Η GREEK CAPITAL LETTER ETA */ { 0x07c8, 0x0398 }, /* Greek_THETA Θ GREEK CAPITAL LETTER THETA */ { 0x07c9, 0x0399 }, /* Greek_IOTA Ι GREEK CAPITAL LETTER IOTA */ { 0x07ca, 0x039a }, /* Greek_KAPPA Κ GREEK CAPITAL LETTER KAPPA */ { 0x07cb, 0x039b }, /* Greek_LAMBDA Λ GREEK CAPITAL LETTER LAMDA */ { 0x07cc, 0x039c }, /* Greek_MU Îœ GREEK CAPITAL LETTER MU */ { 0x07cd, 0x039d }, /* Greek_NU Î GREEK CAPITAL LETTER NU */ { 0x07ce, 0x039e }, /* Greek_XI Ξ GREEK CAPITAL LETTER XI */ { 0x07cf, 0x039f }, /* Greek_OMICRON Ο GREEK CAPITAL LETTER OMICRON */ { 0x07d0, 0x03a0 }, /* Greek_PI Π GREEK CAPITAL LETTER PI */ { 0x07d1, 0x03a1 }, /* Greek_RHO Ρ GREEK CAPITAL LETTER RHO */ { 0x07d2, 0x03a3 }, /* Greek_SIGMA Σ GREEK CAPITAL LETTER SIGMA */ { 0x07d4, 0x03a4 }, /* Greek_TAU Τ GREEK CAPITAL LETTER TAU */ { 0x07d5, 0x03a5 }, /* Greek_UPSILON Î¥ GREEK CAPITAL LETTER UPSILON */ { 0x07d6, 0x03a6 }, /* Greek_PHI Φ GREEK CAPITAL LETTER PHI */ { 0x07d7, 0x03a7 }, /* Greek_CHI Χ GREEK CAPITAL LETTER CHI */ { 0x07d8, 0x03a8 }, /* Greek_PSI Ψ GREEK CAPITAL LETTER PSI */ { 0x07d9, 0x03a9 }, /* Greek_OMEGA Ω GREEK CAPITAL LETTER OMEGA */ { 0x07e1, 0x03b1 }, /* Greek_alpha α GREEK SMALL LETTER ALPHA */ { 0x07e2, 0x03b2 }, /* Greek_beta β GREEK SMALL LETTER BETA */ { 0x07e3, 0x03b3 }, /* Greek_gamma γ GREEK SMALL LETTER GAMMA */ { 0x07e4, 0x03b4 }, /* Greek_delta δ GREEK SMALL LETTER DELTA */ { 0x07e5, 0x03b5 }, /* Greek_epsilon ε GREEK SMALL LETTER EPSILON */ { 0x07e6, 0x03b6 }, /* Greek_zeta ζ GREEK SMALL LETTER ZETA */ { 0x07e7, 0x03b7 }, /* Greek_eta η GREEK SMALL LETTER ETA */ { 0x07e8, 0x03b8 }, /* Greek_theta θ GREEK SMALL LETTER THETA */ { 0x07e9, 0x03b9 }, /* Greek_iota ι GREEK SMALL LETTER IOTA */ { 0x07ea, 0x03ba }, /* Greek_kappa κ GREEK SMALL LETTER KAPPA */ { 0x07eb, 0x03bb }, /* Greek_lambda λ GREEK SMALL LETTER LAMDA */ { 0x07ec, 0x03bc }, /* Greek_mu μ GREEK SMALL LETTER MU */ { 0x07ed, 0x03bd }, /* Greek_nu ν GREEK SMALL LETTER NU */ { 0x07ee, 0x03be }, /* Greek_xi ξ GREEK SMALL LETTER XI */ { 0x07ef, 0x03bf }, /* Greek_omicron ο GREEK SMALL LETTER OMICRON */ { 0x07f0, 0x03c0 }, /* Greek_pi Ï€ GREEK SMALL LETTER PI */ { 0x07f1, 0x03c1 }, /* Greek_rho Ï GREEK SMALL LETTER RHO */ { 0x07f2, 0x03c3 }, /* Greek_sigma σ GREEK SMALL LETTER SIGMA */ { 0x07f3, 0x03c2 }, /* Greek_finalsmallsigma Ï‚ GREEK SMALL LETTER FINAL SIGMA */ { 0x07f4, 0x03c4 }, /* Greek_tau Ï„ GREEK SMALL LETTER TAU */ { 0x07f5, 0x03c5 }, /* Greek_upsilon Ï… GREEK SMALL LETTER UPSILON */ { 0x07f6, 0x03c6 }, /* Greek_phi φ GREEK SMALL LETTER PHI */ { 0x07f7, 0x03c7 }, /* Greek_chi χ GREEK SMALL LETTER CHI */ { 0x07f8, 0x03c8 }, /* Greek_psi ψ GREEK SMALL LETTER PSI */ { 0x07f9, 0x03c9 }, /* Greek_omega ω GREEK SMALL LETTER OMEGA */ { 0x08a1, 0x23b7 }, /* leftradical ⎷ ??? */ { 0x08a2, 0x250c }, /* topleftradical ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ { 0x08a3, 0x2500 }, /* horizconnector ─ BOX DRAWINGS LIGHT HORIZONTAL */ { 0x08a4, 0x2320 }, /* topintegral ⌠ TOP HALF INTEGRAL */ { 0x08a5, 0x2321 }, /* botintegral ⌡ BOTTOM HALF INTEGRAL */ { 0x08a6, 0x2502 }, /* vertconnector │ BOX DRAWINGS LIGHT VERTICAL */ { 0x08a7, 0x23a1 }, /* topleftsqbracket ⎡ ??? */ { 0x08a8, 0x23a3 }, /* botleftsqbracket ⎣ ??? */ { 0x08a9, 0x23a4 }, /* toprightsqbracket ⎤ ??? */ { 0x08aa, 0x23a6 }, /* botrightsqbracket ⎦ ??? */ { 0x08ab, 0x239b }, /* topleftparens ⎛ ??? */ { 0x08ac, 0x239d }, /* botleftparens ⎠??? */ { 0x08ad, 0x239e }, /* toprightparens ⎞ ??? */ { 0x08ae, 0x23a0 }, /* botrightparens ⎠ ??? */ { 0x08af, 0x23a8 }, /* leftmiddlecurlybrace ⎨ ??? */ { 0x08b0, 0x23ac }, /* rightmiddlecurlybrace ⎬ ??? */ /* 0x08b1 topleftsummation ? ??? */ /* 0x08b2 botleftsummation ? ??? */ /* 0x08b3 topvertsummationconnector ? ??? */ /* 0x08b4 botvertsummationconnector ? ??? */ /* 0x08b5 toprightsummation ? ??? */ /* 0x08b6 botrightsummation ? ??? */ /* 0x08b7 rightmiddlesummation ? ??? */ { 0x08bc, 0x2264 }, /* lessthanequal ≤ LESS-THAN OR EQUAL TO */ { 0x08bd, 0x2260 }, /* notequal ≠ NOT EQUAL TO */ { 0x08be, 0x2265 }, /* greaterthanequal ≥ GREATER-THAN OR EQUAL TO */ { 0x08bf, 0x222b }, /* integral ∫ INTEGRAL */ { 0x08c0, 0x2234 }, /* therefore ∴ THEREFORE */ { 0x08c1, 0x221d }, /* variation ∠PROPORTIONAL TO */ { 0x08c2, 0x221e }, /* infinity ∞ INFINITY */ { 0x08c5, 0x2207 }, /* nabla ∇ NABLA */ { 0x08c8, 0x223c }, /* approximate ∼ TILDE OPERATOR */ { 0x08c9, 0x2243 }, /* similarequal ≃ ASYMPTOTICALLY EQUAL TO */ { 0x08cd, 0x21d4 }, /* ifonlyif ⇔ LEFT RIGHT DOUBLE ARROW */ { 0x08ce, 0x21d2 }, /* implies ⇒ RIGHTWARDS DOUBLE ARROW */ { 0x08cf, 0x2261 }, /* identical ≡ IDENTICAL TO */ { 0x08d6, 0x221a }, /* radical √ SQUARE ROOT */ { 0x08da, 0x2282 }, /* includedin ⊂ SUBSET OF */ { 0x08db, 0x2283 }, /* includes ⊃ SUPERSET OF */ { 0x08dc, 0x2229 }, /* intersection ∩ INTERSECTION */ { 0x08dd, 0x222a }, /* union ∪ UNION */ { 0x08de, 0x2227 }, /* logicaland ∧ LOGICAL AND */ { 0x08df, 0x2228 }, /* logicalor ∨ LOGICAL OR */ { 0x08ef, 0x2202 }, /* partialderivative ∂ PARTIAL DIFFERENTIAL */ { 0x08f6, 0x0192 }, /* function Æ’ LATIN SMALL LETTER F WITH HOOK */ { 0x08fb, 0x2190 }, /* leftarrow ↠LEFTWARDS ARROW */ { 0x08fc, 0x2191 }, /* uparrow ↑ UPWARDS ARROW */ { 0x08fd, 0x2192 }, /* rightarrow → RIGHTWARDS ARROW */ { 0x08fe, 0x2193 }, /* downarrow ↓ DOWNWARDS ARROW */ /* 0x09df blank ? ??? */ { 0x09e0, 0x25c6 }, /* soliddiamond â—† BLACK DIAMOND */ { 0x09e1, 0x2592 }, /* checkerboard â–’ MEDIUM SHADE */ { 0x09e2, 0x2409 }, /* ht ≠SYMBOL FOR HORIZONTAL TABULATION */ { 0x09e3, 0x240c }, /* ff ⌠SYMBOL FOR FORM FEED */ { 0x09e4, 0x240d }, /* cr â SYMBOL FOR CARRIAGE RETURN */ { 0x09e5, 0x240a }, /* lf ⊠SYMBOL FOR LINE FEED */ { 0x09e8, 0x2424 }, /* nl ⤠SYMBOL FOR NEWLINE */ { 0x09e9, 0x240b }, /* vt â‹ SYMBOL FOR VERTICAL TABULATION */ { 0x09ea, 0x2518 }, /* lowrightcorner ┘ BOX DRAWINGS LIGHT UP AND LEFT */ { 0x09eb, 0x2510 }, /* uprightcorner â” BOX DRAWINGS LIGHT DOWN AND LEFT */ { 0x09ec, 0x250c }, /* upleftcorner ┌ BOX DRAWINGS LIGHT DOWN AND RIGHT */ { 0x09ed, 0x2514 }, /* lowleftcorner â”” BOX DRAWINGS LIGHT UP AND RIGHT */ { 0x09ee, 0x253c }, /* crossinglines ┼ BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ { 0x09ef, 0x23ba }, /* horizlinescan1 ⎺ HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ { 0x09f0, 0x23bb }, /* horizlinescan3 ⎻ HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ { 0x09f1, 0x2500 }, /* horizlinescan5 ─ BOX DRAWINGS LIGHT HORIZONTAL */ { 0x09f2, 0x23bc }, /* horizlinescan7 ⎼ HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ { 0x09f3, 0x23bd }, /* horizlinescan9 ⎽ HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ { 0x09f4, 0x251c }, /* leftt ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ { 0x09f5, 0x2524 }, /* rightt ┤ BOX DRAWINGS LIGHT VERTICAL AND LEFT */ { 0x09f6, 0x2534 }, /* bott â”´ BOX DRAWINGS LIGHT UP AND HORIZONTAL */ { 0x09f7, 0x252c }, /* topt ┬ BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ { 0x09f8, 0x2502 }, /* vertbar │ BOX DRAWINGS LIGHT VERTICAL */ { 0x0aa1, 0x2003 }, /* emspace   EM SPACE */ { 0x0aa2, 0x2002 }, /* enspace   EN SPACE */ { 0x0aa3, 0x2004 }, /* em3space   THREE-PER-EM SPACE */ { 0x0aa4, 0x2005 }, /* em4space   FOUR-PER-EM SPACE */ { 0x0aa5, 0x2007 }, /* digitspace   FIGURE SPACE */ { 0x0aa6, 0x2008 }, /* punctspace   PUNCTUATION SPACE */ { 0x0aa7, 0x2009 }, /* thinspace   THIN SPACE */ { 0x0aa8, 0x200a }, /* hairspace   HAIR SPACE */ { 0x0aa9, 0x2014 }, /* emdash — EM DASH */ { 0x0aaa, 0x2013 }, /* endash – EN DASH */ /* 0x0aac signifblank ? ??? */ { 0x0aae, 0x2026 }, /* ellipsis … HORIZONTAL ELLIPSIS */ { 0x0aaf, 0x2025 }, /* doubbaselinedot ‥ TWO DOT LEADER */ { 0x0ab0, 0x2153 }, /* onethird â…“ VULGAR FRACTION ONE THIRD */ { 0x0ab1, 0x2154 }, /* twothirds â…” VULGAR FRACTION TWO THIRDS */ { 0x0ab2, 0x2155 }, /* onefifth â…• VULGAR FRACTION ONE FIFTH */ { 0x0ab3, 0x2156 }, /* twofifths â…– VULGAR FRACTION TWO FIFTHS */ { 0x0ab4, 0x2157 }, /* threefifths â…— VULGAR FRACTION THREE FIFTHS */ { 0x0ab5, 0x2158 }, /* fourfifths â…˜ VULGAR FRACTION FOUR FIFTHS */ { 0x0ab6, 0x2159 }, /* onesixth â…™ VULGAR FRACTION ONE SIXTH */ { 0x0ab7, 0x215a }, /* fivesixths â…š VULGAR FRACTION FIVE SIXTHS */ { 0x0ab8, 0x2105 }, /* careof â„… CARE OF */ { 0x0abb, 0x2012 }, /* figdash ‒ FIGURE DASH */ { 0x0abc, 0x2329 }, /* leftanglebracket 〈 LEFT-POINTING ANGLE BRACKET */ /* 0x0abd decimalpoint ? ??? */ { 0x0abe, 0x232a }, /* rightanglebracket 〉 RIGHT-POINTING ANGLE BRACKET */ /* 0x0abf marker ? ??? */ { 0x0ac3, 0x215b }, /* oneeighth â…› VULGAR FRACTION ONE EIGHTH */ { 0x0ac4, 0x215c }, /* threeeighths â…œ VULGAR FRACTION THREE EIGHTHS */ { 0x0ac5, 0x215d }, /* fiveeighths â… VULGAR FRACTION FIVE EIGHTHS */ { 0x0ac6, 0x215e }, /* seveneighths â…ž VULGAR FRACTION SEVEN EIGHTHS */ { 0x0ac9, 0x2122 }, /* trademark â„¢ TRADE MARK SIGN */ { 0x0aca, 0x2613 }, /* signaturemark ☓ SALTIRE */ /* 0x0acb trademarkincircle ? ??? */ { 0x0acc, 0x25c1 }, /* leftopentriangle â— WHITE LEFT-POINTING TRIANGLE */ { 0x0acd, 0x25b7 }, /* rightopentriangle â–· WHITE RIGHT-POINTING TRIANGLE */ { 0x0ace, 0x25cb }, /* emopencircle â—‹ WHITE CIRCLE */ { 0x0acf, 0x25af }, /* emopenrectangle â–¯ WHITE VERTICAL RECTANGLE */ { 0x0ad0, 0x2018 }, /* leftsinglequotemark ‘ LEFT SINGLE QUOTATION MARK */ { 0x0ad1, 0x2019 }, /* rightsinglequotemark ’ RIGHT SINGLE QUOTATION MARK */ { 0x0ad2, 0x201c }, /* leftdoublequotemark “ LEFT DOUBLE QUOTATION MARK */ { 0x0ad3, 0x201d }, /* rightdoublequotemark †RIGHT DOUBLE QUOTATION MARK */ { 0x0ad4, 0x211e }, /* prescription â„ž PRESCRIPTION TAKE */ { 0x0ad6, 0x2032 }, /* minutes ′ PRIME */ { 0x0ad7, 0x2033 }, /* seconds ″ DOUBLE PRIME */ { 0x0ad9, 0x271d }, /* latincross ✠LATIN CROSS */ /* 0x0ada hexagram ? ??? */ { 0x0adb, 0x25ac }, /* filledrectbullet â–¬ BLACK RECTANGLE */ { 0x0adc, 0x25c0 }, /* filledlefttribullet â—€ BLACK LEFT-POINTING TRIANGLE */ { 0x0add, 0x25b6 }, /* filledrighttribullet â–¶ BLACK RIGHT-POINTING TRIANGLE */ { 0x0ade, 0x25cf }, /* emfilledcircle â— BLACK CIRCLE */ { 0x0adf, 0x25ae }, /* emfilledrect â–® BLACK VERTICAL RECTANGLE */ { 0x0ae0, 0x25e6 }, /* enopencircbullet â—¦ WHITE BULLET */ { 0x0ae1, 0x25ab }, /* enopensquarebullet â–« WHITE SMALL SQUARE */ { 0x0ae2, 0x25ad }, /* openrectbullet â–­ WHITE RECTANGLE */ { 0x0ae3, 0x25b3 }, /* opentribulletup â–³ WHITE UP-POINTING TRIANGLE */ { 0x0ae4, 0x25bd }, /* opentribulletdown â–½ WHITE DOWN-POINTING TRIANGLE */ { 0x0ae5, 0x2606 }, /* openstar ☆ WHITE STAR */ { 0x0ae6, 0x2022 }, /* enfilledcircbullet • BULLET */ { 0x0ae7, 0x25aa }, /* enfilledsqbullet â–ª BLACK SMALL SQUARE */ { 0x0ae8, 0x25b2 }, /* filledtribulletup â–² BLACK UP-POINTING TRIANGLE */ { 0x0ae9, 0x25bc }, /* filledtribulletdown â–¼ BLACK DOWN-POINTING TRIANGLE */ { 0x0aea, 0x261c }, /* leftpointer ☜ WHITE LEFT POINTING INDEX */ { 0x0aeb, 0x261e }, /* rightpointer ☞ WHITE RIGHT POINTING INDEX */ { 0x0aec, 0x2663 }, /* club ♣ BLACK CLUB SUIT */ { 0x0aed, 0x2666 }, /* diamond ♦ BLACK DIAMOND SUIT */ { 0x0aee, 0x2665 }, /* heart ♥ BLACK HEART SUIT */ { 0x0af0, 0x2720 }, /* maltesecross ✠ MALTESE CROSS */ { 0x0af1, 0x2020 }, /* dagger † DAGGER */ { 0x0af2, 0x2021 }, /* doubledagger ‡ DOUBLE DAGGER */ { 0x0af3, 0x2713 }, /* checkmark ✓ CHECK MARK */ { 0x0af4, 0x2717 }, /* ballotcross ✗ BALLOT X */ { 0x0af5, 0x266f }, /* musicalsharp ♯ MUSIC SHARP SIGN */ { 0x0af6, 0x266d }, /* musicalflat â™­ MUSIC FLAT SIGN */ { 0x0af7, 0x2642 }, /* malesymbol ♂ MALE SIGN */ { 0x0af8, 0x2640 }, /* femalesymbol ♀ FEMALE SIGN */ { 0x0af9, 0x260e }, /* telephone ☎ BLACK TELEPHONE */ { 0x0afa, 0x2315 }, /* telephonerecorder ⌕ TELEPHONE RECORDER */ { 0x0afb, 0x2117 }, /* phonographcopyright â„— SOUND RECORDING COPYRIGHT */ { 0x0afc, 0x2038 }, /* caret ‸ CARET */ { 0x0afd, 0x201a }, /* singlelowquotemark ‚ SINGLE LOW-9 QUOTATION MARK */ { 0x0afe, 0x201e }, /* doublelowquotemark „ DOUBLE LOW-9 QUOTATION MARK */ /* 0x0aff cursor ? ??? */ { 0x0ba3, 0x003c }, /* leftcaret < LESS-THAN SIGN */ { 0x0ba6, 0x003e }, /* rightcaret > GREATER-THAN SIGN */ { 0x0ba8, 0x2228 }, /* downcaret ∨ LOGICAL OR */ { 0x0ba9, 0x2227 }, /* upcaret ∧ LOGICAL AND */ { 0x0bc0, 0x00af }, /* overbar ¯ MACRON */ { 0x0bc2, 0x22a5 }, /* downtack ⊥ UP TACK */ { 0x0bc3, 0x2229 }, /* upshoe ∩ INTERSECTION */ { 0x0bc4, 0x230a }, /* downstile ⌊ LEFT FLOOR */ { 0x0bc6, 0x005f }, /* underbar _ LOW LINE */ { 0x0bca, 0x2218 }, /* jot ∘ RING OPERATOR */ { 0x0bcc, 0x2395 }, /* quad ⎕ APL FUNCTIONAL SYMBOL QUAD */ { 0x0bce, 0x22a4 }, /* uptack ⊤ DOWN TACK */ { 0x0bcf, 0x25cb }, /* circle â—‹ WHITE CIRCLE */ { 0x0bd3, 0x2308 }, /* upstile ⌈ LEFT CEILING */ { 0x0bd6, 0x222a }, /* downshoe ∪ UNION */ { 0x0bd8, 0x2283 }, /* rightshoe ⊃ SUPERSET OF */ { 0x0bda, 0x2282 }, /* leftshoe ⊂ SUBSET OF */ { 0x0bdc, 0x22a2 }, /* lefttack ⊢ RIGHT TACK */ { 0x0bfc, 0x22a3 }, /* righttack ⊣ LEFT TACK */ { 0x0cdf, 0x2017 }, /* hebrew_doublelowline ‗ DOUBLE LOW LINE */ { 0x0ce0, 0x05d0 }, /* hebrew_aleph × HEBREW LETTER ALEF */ { 0x0ce1, 0x05d1 }, /* hebrew_bet ב HEBREW LETTER BET */ { 0x0ce2, 0x05d2 }, /* hebrew_gimel ×’ HEBREW LETTER GIMEL */ { 0x0ce3, 0x05d3 }, /* hebrew_dalet ד HEBREW LETTER DALET */ { 0x0ce4, 0x05d4 }, /* hebrew_he ×” HEBREW LETTER HE */ { 0x0ce5, 0x05d5 }, /* hebrew_waw ו HEBREW LETTER VAV */ { 0x0ce6, 0x05d6 }, /* hebrew_zain ×– HEBREW LETTER ZAYIN */ { 0x0ce7, 0x05d7 }, /* hebrew_chet ×— HEBREW LETTER HET */ { 0x0ce8, 0x05d8 }, /* hebrew_tet ט HEBREW LETTER TET */ { 0x0ce9, 0x05d9 }, /* hebrew_yod ×™ HEBREW LETTER YOD */ { 0x0cea, 0x05da }, /* hebrew_finalkaph ך HEBREW LETTER FINAL KAF */ { 0x0ceb, 0x05db }, /* hebrew_kaph ×› HEBREW LETTER KAF */ { 0x0cec, 0x05dc }, /* hebrew_lamed ל HEBREW LETTER LAMED */ { 0x0ced, 0x05dd }, /* hebrew_finalmem × HEBREW LETTER FINAL MEM */ { 0x0cee, 0x05de }, /* hebrew_mem מ HEBREW LETTER MEM */ { 0x0cef, 0x05df }, /* hebrew_finalnun ן HEBREW LETTER FINAL NUN */ { 0x0cf0, 0x05e0 }, /* hebrew_nun ×  HEBREW LETTER NUN */ { 0x0cf1, 0x05e1 }, /* hebrew_samech ס HEBREW LETTER SAMEKH */ { 0x0cf2, 0x05e2 }, /* hebrew_ayin ×¢ HEBREW LETTER AYIN */ { 0x0cf3, 0x05e3 }, /* hebrew_finalpe ×£ HEBREW LETTER FINAL PE */ { 0x0cf4, 0x05e4 }, /* hebrew_pe פ HEBREW LETTER PE */ { 0x0cf5, 0x05e5 }, /* hebrew_finalzade ×¥ HEBREW LETTER FINAL TSADI */ { 0x0cf6, 0x05e6 }, /* hebrew_zade צ HEBREW LETTER TSADI */ { 0x0cf7, 0x05e7 }, /* hebrew_qoph ק HEBREW LETTER QOF */ { 0x0cf8, 0x05e8 }, /* hebrew_resh ר HEBREW LETTER RESH */ { 0x0cf9, 0x05e9 }, /* hebrew_shin ש HEBREW LETTER SHIN */ { 0x0cfa, 0x05ea }, /* hebrew_taw ת HEBREW LETTER TAV */ { 0x0da1, 0x0e01 }, /* Thai_kokai ภTHAI CHARACTER KO KAI */ { 0x0da2, 0x0e02 }, /* Thai_khokhai ข THAI CHARACTER KHO KHAI */ { 0x0da3, 0x0e03 }, /* Thai_khokhuat ฃ THAI CHARACTER KHO KHUAT */ { 0x0da4, 0x0e04 }, /* Thai_khokhwai ค THAI CHARACTER KHO KHWAI */ { 0x0da5, 0x0e05 }, /* Thai_khokhon ฅ THAI CHARACTER KHO KHON */ { 0x0da6, 0x0e06 }, /* Thai_khorakhang ฆ THAI CHARACTER KHO RAKHANG */ { 0x0da7, 0x0e07 }, /* Thai_ngongu ง THAI CHARACTER NGO NGU */ { 0x0da8, 0x0e08 }, /* Thai_chochan จ THAI CHARACTER CHO CHAN */ { 0x0da9, 0x0e09 }, /* Thai_choching ฉ THAI CHARACTER CHO CHING */ { 0x0daa, 0x0e0a }, /* Thai_chochang ช THAI CHARACTER CHO CHANG */ { 0x0dab, 0x0e0b }, /* Thai_soso ซ THAI CHARACTER SO SO */ { 0x0dac, 0x0e0c }, /* Thai_chochoe ฌ THAI CHARACTER CHO CHOE */ { 0x0dad, 0x0e0d }, /* Thai_yoying ภTHAI CHARACTER YO YING */ { 0x0dae, 0x0e0e }, /* Thai_dochada ฎ THAI CHARACTER DO CHADA */ { 0x0daf, 0x0e0f }, /* Thai_topatak ภTHAI CHARACTER TO PATAK */ { 0x0db0, 0x0e10 }, /* Thai_thothan ภTHAI CHARACTER THO THAN */ { 0x0db1, 0x0e11 }, /* Thai_thonangmontho ฑ THAI CHARACTER THO NANGMONTHO */ { 0x0db2, 0x0e12 }, /* Thai_thophuthao ฒ THAI CHARACTER THO PHUTHAO */ { 0x0db3, 0x0e13 }, /* Thai_nonen ณ THAI CHARACTER NO NEN */ { 0x0db4, 0x0e14 }, /* Thai_dodek ด THAI CHARACTER DO DEK */ { 0x0db5, 0x0e15 }, /* Thai_totao ต THAI CHARACTER TO TAO */ { 0x0db6, 0x0e16 }, /* Thai_thothung ถ THAI CHARACTER THO THUNG */ { 0x0db7, 0x0e17 }, /* Thai_thothahan ท THAI CHARACTER THO THAHAN */ { 0x0db8, 0x0e18 }, /* Thai_thothong ธ THAI CHARACTER THO THONG */ { 0x0db9, 0x0e19 }, /* Thai_nonu น THAI CHARACTER NO NU */ { 0x0dba, 0x0e1a }, /* Thai_bobaimai บ THAI CHARACTER BO BAIMAI */ { 0x0dbb, 0x0e1b }, /* Thai_popla ป THAI CHARACTER PO PLA */ { 0x0dbc, 0x0e1c }, /* Thai_phophung ผ THAI CHARACTER PHO PHUNG */ { 0x0dbd, 0x0e1d }, /* Thai_fofa ภTHAI CHARACTER FO FA */ { 0x0dbe, 0x0e1e }, /* Thai_phophan พ THAI CHARACTER PHO PHAN */ { 0x0dbf, 0x0e1f }, /* Thai_fofan ฟ THAI CHARACTER FO FAN */ { 0x0dc0, 0x0e20 }, /* Thai_phosamphao ภ THAI CHARACTER PHO SAMPHAO */ { 0x0dc1, 0x0e21 }, /* Thai_moma ม THAI CHARACTER MO MA */ { 0x0dc2, 0x0e22 }, /* Thai_yoyak ย THAI CHARACTER YO YAK */ { 0x0dc3, 0x0e23 }, /* Thai_rorua ร THAI CHARACTER RO RUA */ { 0x0dc4, 0x0e24 }, /* Thai_ru ฤ THAI CHARACTER RU */ { 0x0dc5, 0x0e25 }, /* Thai_loling ล THAI CHARACTER LO LING */ { 0x0dc6, 0x0e26 }, /* Thai_lu ฦ THAI CHARACTER LU */ { 0x0dc7, 0x0e27 }, /* Thai_wowaen ว THAI CHARACTER WO WAEN */ { 0x0dc8, 0x0e28 }, /* Thai_sosala ศ THAI CHARACTER SO SALA */ { 0x0dc9, 0x0e29 }, /* Thai_sorusi ษ THAI CHARACTER SO RUSI */ { 0x0dca, 0x0e2a }, /* Thai_sosua ส THAI CHARACTER SO SUA */ { 0x0dcb, 0x0e2b }, /* Thai_hohip ห THAI CHARACTER HO HIP */ { 0x0dcc, 0x0e2c }, /* Thai_lochula ฬ THAI CHARACTER LO CHULA */ { 0x0dcd, 0x0e2d }, /* Thai_oang อ THAI CHARACTER O ANG */ { 0x0dce, 0x0e2e }, /* Thai_honokhuk ฮ THAI CHARACTER HO NOKHUK */ { 0x0dcf, 0x0e2f }, /* Thai_paiyannoi ฯ THAI CHARACTER PAIYANNOI */ { 0x0dd0, 0x0e30 }, /* Thai_saraa ะ THAI CHARACTER SARA A */ { 0x0dd1, 0x0e31 }, /* Thai_maihanakat ั THAI CHARACTER MAI HAN-AKAT */ { 0x0dd2, 0x0e32 }, /* Thai_saraaa า THAI CHARACTER SARA AA */ { 0x0dd3, 0x0e33 }, /* Thai_saraam ำ THAI CHARACTER SARA AM */ { 0x0dd4, 0x0e34 }, /* Thai_sarai ิ THAI CHARACTER SARA I */ { 0x0dd5, 0x0e35 }, /* Thai_saraii ี THAI CHARACTER SARA II */ { 0x0dd6, 0x0e36 }, /* Thai_saraue ึ THAI CHARACTER SARA UE */ { 0x0dd7, 0x0e37 }, /* Thai_sarauee ื THAI CHARACTER SARA UEE */ { 0x0dd8, 0x0e38 }, /* Thai_sarau ุ THAI CHARACTER SARA U */ { 0x0dd9, 0x0e39 }, /* Thai_sarauu ู THAI CHARACTER SARA UU */ { 0x0dda, 0x0e3a }, /* Thai_phinthu ฺ THAI CHARACTER PHINTHU */ /* 0x0dde Thai_maihanakat_maitho ? ??? */ { 0x0ddf, 0x0e3f }, /* Thai_baht ฿ THAI CURRENCY SYMBOL BAHT */ { 0x0de0, 0x0e40 }, /* Thai_sarae เ THAI CHARACTER SARA E */ { 0x0de1, 0x0e41 }, /* Thai_saraae ๠THAI CHARACTER SARA AE */ { 0x0de2, 0x0e42 }, /* Thai_sarao โ THAI CHARACTER SARA O */ { 0x0de3, 0x0e43 }, /* Thai_saraaimaimuan ใ THAI CHARACTER SARA AI MAIMUAN */ { 0x0de4, 0x0e44 }, /* Thai_saraaimaimalai ไ THAI CHARACTER SARA AI MAIMALAI */ { 0x0de5, 0x0e45 }, /* Thai_lakkhangyao ๅ THAI CHARACTER LAKKHANGYAO */ { 0x0de6, 0x0e46 }, /* Thai_maiyamok ๆ THAI CHARACTER MAIYAMOK */ { 0x0de7, 0x0e47 }, /* Thai_maitaikhu ็ THAI CHARACTER MAITAIKHU */ { 0x0de8, 0x0e48 }, /* Thai_maiek ่ THAI CHARACTER MAI EK */ { 0x0de9, 0x0e49 }, /* Thai_maitho ้ THAI CHARACTER MAI THO */ { 0x0dea, 0x0e4a }, /* Thai_maitri ๊ THAI CHARACTER MAI TRI */ { 0x0deb, 0x0e4b }, /* Thai_maichattawa ๋ THAI CHARACTER MAI CHATTAWA */ { 0x0dec, 0x0e4c }, /* Thai_thanthakhat ์ THAI CHARACTER THANTHAKHAT */ { 0x0ded, 0x0e4d }, /* Thai_nikhahit ๠THAI CHARACTER NIKHAHIT */ { 0x0df0, 0x0e50 }, /* Thai_leksun ๠THAI DIGIT ZERO */ { 0x0df1, 0x0e51 }, /* Thai_leknung ๑ THAI DIGIT ONE */ { 0x0df2, 0x0e52 }, /* Thai_leksong ๒ THAI DIGIT TWO */ { 0x0df3, 0x0e53 }, /* Thai_leksam ๓ THAI DIGIT THREE */ { 0x0df4, 0x0e54 }, /* Thai_leksi ๔ THAI DIGIT FOUR */ { 0x0df5, 0x0e55 }, /* Thai_lekha ๕ THAI DIGIT FIVE */ { 0x0df6, 0x0e56 }, /* Thai_lekhok ๖ THAI DIGIT SIX */ { 0x0df7, 0x0e57 }, /* Thai_lekchet ๗ THAI DIGIT SEVEN */ { 0x0df8, 0x0e58 }, /* Thai_lekpaet ๘ THAI DIGIT EIGHT */ { 0x0df9, 0x0e59 }, /* Thai_lekkao ๙ THAI DIGIT NINE */ { 0x0ea1, 0x3131 }, /* Hangul_Kiyeog ㄱ HANGUL LETTER KIYEOK */ { 0x0ea2, 0x3132 }, /* Hangul_SsangKiyeog ㄲ HANGUL LETTER SSANGKIYEOK */ { 0x0ea3, 0x3133 }, /* Hangul_KiyeogSios ㄳ HANGUL LETTER KIYEOK-SIOS */ { 0x0ea4, 0x3134 }, /* Hangul_Nieun ã„´ HANGUL LETTER NIEUN */ { 0x0ea5, 0x3135 }, /* Hangul_NieunJieuj ㄵ HANGUL LETTER NIEUN-CIEUC */ { 0x0ea6, 0x3136 }, /* Hangul_NieunHieuh ㄶ HANGUL LETTER NIEUN-HIEUH */ { 0x0ea7, 0x3137 }, /* Hangul_Dikeud ã„· HANGUL LETTER TIKEUT */ { 0x0ea8, 0x3138 }, /* Hangul_SsangDikeud ㄸ HANGUL LETTER SSANGTIKEUT */ { 0x0ea9, 0x3139 }, /* Hangul_Rieul ㄹ HANGUL LETTER RIEUL */ { 0x0eaa, 0x313a }, /* Hangul_RieulKiyeog ㄺ HANGUL LETTER RIEUL-KIYEOK */ { 0x0eab, 0x313b }, /* Hangul_RieulMieum ã„» HANGUL LETTER RIEUL-MIEUM */ { 0x0eac, 0x313c }, /* Hangul_RieulPieub ㄼ HANGUL LETTER RIEUL-PIEUP */ { 0x0ead, 0x313d }, /* Hangul_RieulSios ㄽ HANGUL LETTER RIEUL-SIOS */ { 0x0eae, 0x313e }, /* Hangul_RieulTieut ㄾ HANGUL LETTER RIEUL-THIEUTH */ { 0x0eaf, 0x313f }, /* Hangul_RieulPhieuf ã„¿ HANGUL LETTER RIEUL-PHIEUPH */ { 0x0eb0, 0x3140 }, /* Hangul_RieulHieuh ã…€ HANGUL LETTER RIEUL-HIEUH */ { 0x0eb1, 0x3141 }, /* Hangul_Mieum ã… HANGUL LETTER MIEUM */ { 0x0eb2, 0x3142 }, /* Hangul_Pieub ã…‚ HANGUL LETTER PIEUP */ { 0x0eb3, 0x3143 }, /* Hangul_SsangPieub ã…ƒ HANGUL LETTER SSANGPIEUP */ { 0x0eb4, 0x3144 }, /* Hangul_PieubSios ã…„ HANGUL LETTER PIEUP-SIOS */ { 0x0eb5, 0x3145 }, /* Hangul_Sios ã…… HANGUL LETTER SIOS */ { 0x0eb6, 0x3146 }, /* Hangul_SsangSios ã…† HANGUL LETTER SSANGSIOS */ { 0x0eb7, 0x3147 }, /* Hangul_Ieung ã…‡ HANGUL LETTER IEUNG */ { 0x0eb8, 0x3148 }, /* Hangul_Jieuj ã…ˆ HANGUL LETTER CIEUC */ { 0x0eb9, 0x3149 }, /* Hangul_SsangJieuj ã…‰ HANGUL LETTER SSANGCIEUC */ { 0x0eba, 0x314a }, /* Hangul_Cieuc ã…Š HANGUL LETTER CHIEUCH */ { 0x0ebb, 0x314b }, /* Hangul_Khieuq ã…‹ HANGUL LETTER KHIEUKH */ { 0x0ebc, 0x314c }, /* Hangul_Tieut ã…Œ HANGUL LETTER THIEUTH */ { 0x0ebd, 0x314d }, /* Hangul_Phieuf ã… HANGUL LETTER PHIEUPH */ { 0x0ebe, 0x314e }, /* Hangul_Hieuh ã…Ž HANGUL LETTER HIEUH */ { 0x0ebf, 0x314f }, /* Hangul_A ã… HANGUL LETTER A */ { 0x0ec0, 0x3150 }, /* Hangul_AE ã… HANGUL LETTER AE */ { 0x0ec1, 0x3151 }, /* Hangul_YA ã…‘ HANGUL LETTER YA */ { 0x0ec2, 0x3152 }, /* Hangul_YAE ã…’ HANGUL LETTER YAE */ { 0x0ec3, 0x3153 }, /* Hangul_EO ã…“ HANGUL LETTER EO */ { 0x0ec4, 0x3154 }, /* Hangul_E ã…” HANGUL LETTER E */ { 0x0ec5, 0x3155 }, /* Hangul_YEO ã…• HANGUL LETTER YEO */ { 0x0ec6, 0x3156 }, /* Hangul_YE ã…– HANGUL LETTER YE */ { 0x0ec7, 0x3157 }, /* Hangul_O ã…— HANGUL LETTER O */ { 0x0ec8, 0x3158 }, /* Hangul_WA ã…˜ HANGUL LETTER WA */ { 0x0ec9, 0x3159 }, /* Hangul_WAE ã…™ HANGUL LETTER WAE */ { 0x0eca, 0x315a }, /* Hangul_OE ã…š HANGUL LETTER OE */ { 0x0ecb, 0x315b }, /* Hangul_YO ã…› HANGUL LETTER YO */ { 0x0ecc, 0x315c }, /* Hangul_U ã…œ HANGUL LETTER U */ { 0x0ecd, 0x315d }, /* Hangul_WEO ã… HANGUL LETTER WEO */ { 0x0ece, 0x315e }, /* Hangul_WE ã…ž HANGUL LETTER WE */ { 0x0ecf, 0x315f }, /* Hangul_WI ã…Ÿ HANGUL LETTER WI */ { 0x0ed0, 0x3160 }, /* Hangul_YU ã…  HANGUL LETTER YU */ { 0x0ed1, 0x3161 }, /* Hangul_EU ã…¡ HANGUL LETTER EU */ { 0x0ed2, 0x3162 }, /* Hangul_YI ã…¢ HANGUL LETTER YI */ { 0x0ed3, 0x3163 }, /* Hangul_I ã…£ HANGUL LETTER I */ { 0x0ed4, 0x11a8 }, /* Hangul_J_Kiyeog ᆨ HANGUL JONGSEONG KIYEOK */ { 0x0ed5, 0x11a9 }, /* Hangul_J_SsangKiyeog ᆩ HANGUL JONGSEONG SSANGKIYEOK */ { 0x0ed6, 0x11aa }, /* Hangul_J_KiyeogSios ᆪ HANGUL JONGSEONG KIYEOK-SIOS */ { 0x0ed7, 0x11ab }, /* Hangul_J_Nieun ᆫ HANGUL JONGSEONG NIEUN */ { 0x0ed8, 0x11ac }, /* Hangul_J_NieunJieuj ᆬ HANGUL JONGSEONG NIEUN-CIEUC */ { 0x0ed9, 0x11ad }, /* Hangul_J_NieunHieuh ᆭ HANGUL JONGSEONG NIEUN-HIEUH */ { 0x0eda, 0x11ae }, /* Hangul_J_Dikeud ᆮ HANGUL JONGSEONG TIKEUT */ { 0x0edb, 0x11af }, /* Hangul_J_Rieul ᆯ HANGUL JONGSEONG RIEUL */ { 0x0edc, 0x11b0 }, /* Hangul_J_RieulKiyeog ᆰ HANGUL JONGSEONG RIEUL-KIYEOK */ { 0x0edd, 0x11b1 }, /* Hangul_J_RieulMieum ᆱ HANGUL JONGSEONG RIEUL-MIEUM */ { 0x0ede, 0x11b2 }, /* Hangul_J_RieulPieub ᆲ HANGUL JONGSEONG RIEUL-PIEUP */ { 0x0edf, 0x11b3 }, /* Hangul_J_RieulSios ᆳ HANGUL JONGSEONG RIEUL-SIOS */ { 0x0ee0, 0x11b4 }, /* Hangul_J_RieulTieut ᆴ HANGUL JONGSEONG RIEUL-THIEUTH */ { 0x0ee1, 0x11b5 }, /* Hangul_J_RieulPhieuf ᆵ HANGUL JONGSEONG RIEUL-PHIEUPH */ { 0x0ee2, 0x11b6 }, /* Hangul_J_RieulHieuh ᆶ HANGUL JONGSEONG RIEUL-HIEUH */ { 0x0ee3, 0x11b7 }, /* Hangul_J_Mieum ᆷ HANGUL JONGSEONG MIEUM */ { 0x0ee4, 0x11b8 }, /* Hangul_J_Pieub ᆸ HANGUL JONGSEONG PIEUP */ { 0x0ee5, 0x11b9 }, /* Hangul_J_PieubSios ᆹ HANGUL JONGSEONG PIEUP-SIOS */ { 0x0ee6, 0x11ba }, /* Hangul_J_Sios ᆺ HANGUL JONGSEONG SIOS */ { 0x0ee7, 0x11bb }, /* Hangul_J_SsangSios ᆻ HANGUL JONGSEONG SSANGSIOS */ { 0x0ee8, 0x11bc }, /* Hangul_J_Ieung ᆼ HANGUL JONGSEONG IEUNG */ { 0x0ee9, 0x11bd }, /* Hangul_J_Jieuj ᆽ HANGUL JONGSEONG CIEUC */ { 0x0eea, 0x11be }, /* Hangul_J_Cieuc ᆾ HANGUL JONGSEONG CHIEUCH */ { 0x0eeb, 0x11bf }, /* Hangul_J_Khieuq ᆿ HANGUL JONGSEONG KHIEUKH */ { 0x0eec, 0x11c0 }, /* Hangul_J_Tieut ᇀ HANGUL JONGSEONG THIEUTH */ { 0x0eed, 0x11c1 }, /* Hangul_J_Phieuf ᇠHANGUL JONGSEONG PHIEUPH */ { 0x0eee, 0x11c2 }, /* Hangul_J_Hieuh ᇂ HANGUL JONGSEONG HIEUH */ { 0x0eef, 0x316d }, /* Hangul_RieulYeorinHieuh ã…­ HANGUL LETTER RIEUL-YEORINHIEUH */ { 0x0ef0, 0x3171 }, /* Hangul_SunkyeongeumMieum ã…± HANGUL LETTER KAPYEOUNMIEUM */ { 0x0ef1, 0x3178 }, /* Hangul_SunkyeongeumPieub ã…¸ HANGUL LETTER KAPYEOUNPIEUP */ { 0x0ef2, 0x317f }, /* Hangul_PanSios ã…¿ HANGUL LETTER PANSIOS */ { 0x0ef3, 0x3181 }, /* Hangul_KkogjiDalrinIeung ㆠHANGUL LETTER YESIEUNG */ { 0x0ef4, 0x3184 }, /* Hangul_SunkyeongeumPhieuf ㆄ HANGUL LETTER KAPYEOUNPHIEUPH */ { 0x0ef5, 0x3186 }, /* Hangul_YeorinHieuh ㆆ HANGUL LETTER YEORINHIEUH */ { 0x0ef6, 0x318d }, /* Hangul_AraeA ㆠHANGUL LETTER ARAEA */ { 0x0ef7, 0x318e }, /* Hangul_AraeAE ㆎ HANGUL LETTER ARAEAE */ { 0x0ef8, 0x11eb }, /* Hangul_J_PanSios ᇫ HANGUL JONGSEONG PANSIOS */ { 0x0ef9, 0x11f0 }, /* Hangul_J_KkogjiDalrinIeung ᇰ HANGUL JONGSEONG YESIEUNG */ { 0x0efa, 0x11f9 }, /* Hangul_J_YeorinHieuh ᇹ HANGUL JONGSEONG YEORINHIEUH */ { 0x0eff, 0x20a9 }, /* Korean_Won â‚© WON SIGN */ { 0x13a4, 0x20ac }, /* Euro € EURO SIGN */ { 0x13bc, 0x0152 }, /* OE Å’ LATIN CAPITAL LIGATURE OE */ { 0x13bd, 0x0153 }, /* oe Å“ LATIN SMALL LIGATURE OE */ { 0x13be, 0x0178 }, /* Ydiaeresis Ÿ LATIN CAPITAL LETTER Y WITH DIAERESIS */ { 0x20ac, 0x20ac }, /* EuroSign € EURO SIGN */ }; extern long keysym2ucs(KeySym keysym); // LordHavoc: suppress warning just in this case, it's not worth having a header file for this... long keysym2ucs(KeySym keysym) { int min = 0; int max = sizeof(keysymtab) / sizeof(struct codepair) - 1; int mid; /* first check for Latin-1 characters (1:1 mapping) */ if ((keysym >= 0x0020 && keysym <= 0x007e) || (keysym >= 0x00a0 && keysym <= 0x00ff)) return keysym; /* also check for directly encoded 24-bit UCS characters */ if ((keysym & 0xff000000) == 0x01000000) return keysym & 0x00ffffff; /* binary search in table */ while (max >= min) { mid = (min + max) / 2; if (keysymtab[mid].keysym < keysym) min = mid + 1; else if (keysymtab[mid].keysym > keysym) max = mid - 1; else { /* found it */ return keysymtab[mid].ucs; } } /* no matching Unicode value found */ return -1; } darkplaces/sbar.c0000664000175000017500000023611413067716222013267 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sbar.c -- status bar code #include "quakedef.h" #include #include "cl_collision.h" #include "csprogs.h" cachepic_t *sb_disc; #define STAT_MINUS 10 // num frame for '-' stats digit cachepic_t *sb_nums[2][11]; cachepic_t *sb_colon, *sb_slash; cachepic_t *sb_ibar; cachepic_t *sb_sbar; cachepic_t *sb_scorebar; // AK only used by NEX cachepic_t *sb_sbar_minimal; cachepic_t *sb_sbar_overlay; // AK changed the bound to 9 cachepic_t *sb_weapons[7][9]; // 0 is active, 1 is owned, 2-5 are flashes cachepic_t *sb_ammo[4]; cachepic_t *sb_sigil[4]; cachepic_t *sb_armor[3]; cachepic_t *sb_items[32]; // 0-4 are based on health (in 20 increments) // 0 is static, 1 is temporary animation cachepic_t *sb_faces[5][2]; cachepic_t *sb_health; // GAME_NEXUIZ cachepic_t *sb_face_invis; cachepic_t *sb_face_quad; cachepic_t *sb_face_invuln; cachepic_t *sb_face_invis_invuln; qboolean sb_showscores; int sb_lines; // scan lines to draw cachepic_t *rsb_invbar[2]; cachepic_t *rsb_weapons[5]; cachepic_t *rsb_items[2]; cachepic_t *rsb_ammo[3]; cachepic_t *rsb_teambord; // PGM 01/19/97 - team color border //MED 01/04/97 added two more weapons + 3 alternates for grenade launcher cachepic_t *hsb_weapons[7][5]; // 0 is active, 1 is owned, 2-5 are flashes //MED 01/04/97 added array to simplify weapon parsing int hipweapons[4] = {HIT_LASER_CANNON_BIT,HIT_MJOLNIR_BIT,4,HIT_PROXIMITY_GUN_BIT}; //MED 01/04/97 added hipnotic items array cachepic_t *hsb_items[2]; cachepic_t *zymsb_crosshair_center; cachepic_t *zymsb_crosshair_line; cachepic_t *zymsb_crosshair_health; cachepic_t *zymsb_crosshair_ammo; cachepic_t *zymsb_crosshair_clip; cachepic_t *zymsb_crosshair_background; cachepic_t *zymsb_crosshair_left1; cachepic_t *zymsb_crosshair_left2; cachepic_t *zymsb_crosshair_right; cachepic_t *sb_ranking; cachepic_t *sb_complete; cachepic_t *sb_inter; cachepic_t *sb_finale; cvar_t showfps = {CVAR_SAVE, "showfps", "0", "shows your rendered fps (frames per second)"}; cvar_t showsound = {CVAR_SAVE, "showsound", "0", "shows number of active sound sources, sound latency, and other statistics"}; cvar_t showblur = {CVAR_SAVE, "showblur", "0", "shows the current alpha level of motionblur"}; cvar_t showspeed = {CVAR_SAVE, "showspeed", "0", "shows your current speed (qu per second); number selects unit: 1 = qu/s, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots"}; cvar_t showtopspeed = {CVAR_SAVE, "showtopspeed", "0", "shows your top speed (kept on screen for max 3 seconds); value -1 takes over the unit from showspeed, otherwise it's an unit number just like in showspeed"}; cvar_t showtime = {CVAR_SAVE, "showtime", "0", "shows current time of day (useful on screenshots)"}; cvar_t showtime_format = {CVAR_SAVE, "showtime_format", "%H:%M:%S", "format string for time of day"}; cvar_t showdate = {CVAR_SAVE, "showdate", "0", "shows current date (useful on screenshots)"}; cvar_t showdate_format = {CVAR_SAVE, "showdate_format", "%Y-%m-%d", "format string for date"}; cvar_t showtex = {0, "showtex", "0", "shows the name of the texture on the crosshair (for map debugging)"}; cvar_t sbar_alpha_bg = {CVAR_SAVE, "sbar_alpha_bg", "0.4", "opacity value of the statusbar background image"}; cvar_t sbar_alpha_fg = {CVAR_SAVE, "sbar_alpha_fg", "1", "opacity value of the statusbar weapon/item icons and numbers"}; cvar_t sbar_hudselector = {CVAR_SAVE, "sbar_hudselector", "0", "selects which of the builtin hud layouts to use (meaning is somewhat dependent on gamemode, so nexuiz has a very different set of hud layouts than quake for example)"}; cvar_t sbar_scorerank = {CVAR_SAVE, "sbar_scorerank", "1", "shows an overlay for your score (or team score) and rank in the scoreboard"}; cvar_t sbar_gametime = {CVAR_SAVE, "sbar_gametime", "1", "shows an overlay for the time left in the current match/level (or current game time if there is no timelimit set)"}; cvar_t sbar_miniscoreboard_size = {CVAR_SAVE, "sbar_miniscoreboard_size", "-1", "sets the size of the mini deathmatch overlay in items, or disables it when set to 0, or sets it to a sane default when set to -1"}; cvar_t sbar_flagstatus_right = {CVAR_SAVE, "sbar_flagstatus_right", "0", "moves Nexuiz flag status icons to the right"}; cvar_t sbar_flagstatus_pos = {CVAR_SAVE, "sbar_flagstatus_pos", "115", "pixel position of the Nexuiz flag status icons, from the bottom"}; cvar_t sbar_info_pos = {CVAR_SAVE, "sbar_info_pos", "0", "pixel position of the info strings (such as showfps), from the bottom"}; cvar_t cl_deathscoreboard = {0, "cl_deathscoreboard", "1", "shows scoreboard (+showscores) while dead"}; cvar_t crosshair_color_red = {CVAR_SAVE, "crosshair_color_red", "1", "customizable crosshair color"}; cvar_t crosshair_color_green = {CVAR_SAVE, "crosshair_color_green", "0", "customizable crosshair color"}; cvar_t crosshair_color_blue = {CVAR_SAVE, "crosshair_color_blue", "0", "customizable crosshair color"}; cvar_t crosshair_color_alpha = {CVAR_SAVE, "crosshair_color_alpha", "1", "how opaque the crosshair should be"}; cvar_t crosshair_size = {CVAR_SAVE, "crosshair_size", "1", "adjusts size of the crosshair on the screen"}; static void Sbar_MiniDeathmatchOverlay (int x, int y); static void Sbar_DeathmatchOverlay (void); static void Sbar_IntermissionOverlay (void); static void Sbar_FinaleOverlay (void); /* =============== Sbar_ShowScores Tab key down =============== */ static void Sbar_ShowScores (void) { if (sb_showscores) return; sb_showscores = true; CL_VM_UpdateShowingScoresState(sb_showscores); } /* =============== Sbar_DontShowScores Tab key up =============== */ static void Sbar_DontShowScores (void) { sb_showscores = false; CL_VM_UpdateShowingScoresState(sb_showscores); } static void sbar_start(void) { char vabuf[1024]; int i; if (gamemode == GAME_DELUXEQUAKE || gamemode == GAME_BLOODOMNICIDE) { } else if (IS_OLDNEXUIZ_DERIVED(gamemode)) { for (i = 0;i < 10;i++) sb_nums[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/num_%i",i), CACHEPICFLAG_QUIET); sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_bullets", CACHEPICFLAG_QUIET); sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor", CACHEPICFLAG_QUIET); sb_armor[1] = NULL; sb_armor[2] = NULL; sb_health = Draw_CachePic_Flags ("gfx/sb_health", CACHEPICFLAG_QUIET); sb_items[2] = Draw_CachePic_Flags ("gfx/sb_slowmo", CACHEPICFLAG_QUIET); sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invinc", CACHEPICFLAG_QUIET); sb_items[4] = Draw_CachePic_Flags ("gfx/sb_energy", CACHEPICFLAG_QUIET); sb_items[5] = Draw_CachePic_Flags ("gfx/sb_str", CACHEPICFLAG_QUIET); sb_items[11] = Draw_CachePic_Flags ("gfx/sb_flag_red_taken", CACHEPICFLAG_QUIET); sb_items[12] = Draw_CachePic_Flags ("gfx/sb_flag_red_lost", CACHEPICFLAG_QUIET); sb_items[13] = Draw_CachePic_Flags ("gfx/sb_flag_red_carrying", CACHEPICFLAG_QUIET); sb_items[14] = Draw_CachePic_Flags ("gfx/sb_key_carrying", CACHEPICFLAG_QUIET); sb_items[15] = Draw_CachePic_Flags ("gfx/sb_flag_blue_taken", CACHEPICFLAG_QUIET); sb_items[16] = Draw_CachePic_Flags ("gfx/sb_flag_blue_lost", CACHEPICFLAG_QUIET); sb_items[17] = Draw_CachePic_Flags ("gfx/sb_flag_blue_carrying", CACHEPICFLAG_QUIET); sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); sb_sbar_minimal = Draw_CachePic_Flags ("gfx/sbar_minimal", CACHEPICFLAG_QUIET); sb_sbar_overlay = Draw_CachePic_Flags ("gfx/sbar_overlay", CACHEPICFLAG_QUIET); for(i = 0; i < 9;i++) sb_weapons[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inv_weapon%i",i), CACHEPICFLAG_QUIET); } else if (gamemode == GAME_ZYMOTIC) { zymsb_crosshair_center = Draw_CachePic_Flags ("gfx/hud/crosshair_center", CACHEPICFLAG_QUIET); zymsb_crosshair_line = Draw_CachePic_Flags ("gfx/hud/crosshair_line", CACHEPICFLAG_QUIET); zymsb_crosshair_health = Draw_CachePic_Flags ("gfx/hud/crosshair_health", CACHEPICFLAG_QUIET); zymsb_crosshair_clip = Draw_CachePic_Flags ("gfx/hud/crosshair_clip", CACHEPICFLAG_QUIET); zymsb_crosshair_ammo = Draw_CachePic_Flags ("gfx/hud/crosshair_ammo", CACHEPICFLAG_QUIET); zymsb_crosshair_background = Draw_CachePic_Flags ("gfx/hud/crosshair_background", CACHEPICFLAG_QUIET); zymsb_crosshair_left1 = Draw_CachePic_Flags ("gfx/hud/crosshair_left1", CACHEPICFLAG_QUIET); zymsb_crosshair_left2 = Draw_CachePic_Flags ("gfx/hud/crosshair_left2", CACHEPICFLAG_QUIET); zymsb_crosshair_right = Draw_CachePic_Flags ("gfx/hud/crosshair_right", CACHEPICFLAG_QUIET); } else { sb_disc = Draw_CachePic_Flags ("gfx/disc", CACHEPICFLAG_QUIET); for (i = 0;i < 10;i++) { sb_nums[0][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/num_%i",i), CACHEPICFLAG_QUIET); sb_nums[1][i] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/anum_%i",i), CACHEPICFLAG_QUIET); } sb_nums[0][10] = Draw_CachePic_Flags ("gfx/num_minus", CACHEPICFLAG_QUIET); sb_nums[1][10] = Draw_CachePic_Flags ("gfx/anum_minus", CACHEPICFLAG_QUIET); sb_colon = Draw_CachePic_Flags ("gfx/num_colon", CACHEPICFLAG_QUIET); sb_slash = Draw_CachePic_Flags ("gfx/num_slash", CACHEPICFLAG_QUIET); sb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_shotgun", CACHEPICFLAG_QUIET); sb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_sshotgun", CACHEPICFLAG_QUIET); sb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_nailgun", CACHEPICFLAG_QUIET); sb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_snailgun", CACHEPICFLAG_QUIET); sb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_rlaunch", CACHEPICFLAG_QUIET); sb_weapons[0][5] = Draw_CachePic_Flags ("gfx/inv_srlaunch", CACHEPICFLAG_QUIET); sb_weapons[0][6] = Draw_CachePic_Flags ("gfx/inv_lightng", CACHEPICFLAG_QUIET); sb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_shotgun", CACHEPICFLAG_QUIET); sb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_sshotgun", CACHEPICFLAG_QUIET); sb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_nailgun", CACHEPICFLAG_QUIET); sb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_snailgun", CACHEPICFLAG_QUIET); sb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_rlaunch", CACHEPICFLAG_QUIET); sb_weapons[1][5] = Draw_CachePic_Flags ("gfx/inv2_srlaunch", CACHEPICFLAG_QUIET); sb_weapons[1][6] = Draw_CachePic_Flags ("gfx/inv2_lightng", CACHEPICFLAG_QUIET); for (i = 0;i < 5;i++) { sb_weapons[2+i][0] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_shotgun",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][1] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_sshotgun",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][2] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_nailgun",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][3] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_snailgun",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][4] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_rlaunch",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][5] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_srlaunch",i+1), CACHEPICFLAG_QUIET); sb_weapons[2+i][6] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_lightng",i+1), CACHEPICFLAG_QUIET); } sb_ammo[0] = Draw_CachePic_Flags ("gfx/sb_shells", CACHEPICFLAG_QUIET); sb_ammo[1] = Draw_CachePic_Flags ("gfx/sb_nails", CACHEPICFLAG_QUIET); sb_ammo[2] = Draw_CachePic_Flags ("gfx/sb_rocket", CACHEPICFLAG_QUIET); sb_ammo[3] = Draw_CachePic_Flags ("gfx/sb_cells", CACHEPICFLAG_QUIET); sb_armor[0] = Draw_CachePic_Flags ("gfx/sb_armor1", CACHEPICFLAG_QUIET); sb_armor[1] = Draw_CachePic_Flags ("gfx/sb_armor2", CACHEPICFLAG_QUIET); sb_armor[2] = Draw_CachePic_Flags ("gfx/sb_armor3", CACHEPICFLAG_QUIET); sb_items[0] = Draw_CachePic_Flags ("gfx/sb_key1", CACHEPICFLAG_QUIET); sb_items[1] = Draw_CachePic_Flags ("gfx/sb_key2", CACHEPICFLAG_QUIET); sb_items[2] = Draw_CachePic_Flags ("gfx/sb_invis", CACHEPICFLAG_QUIET); sb_items[3] = Draw_CachePic_Flags ("gfx/sb_invuln", CACHEPICFLAG_QUIET); sb_items[4] = Draw_CachePic_Flags ("gfx/sb_suit", CACHEPICFLAG_QUIET); sb_items[5] = Draw_CachePic_Flags ("gfx/sb_quad", CACHEPICFLAG_QUIET); sb_sigil[0] = Draw_CachePic_Flags ("gfx/sb_sigil1", CACHEPICFLAG_QUIET); sb_sigil[1] = Draw_CachePic_Flags ("gfx/sb_sigil2", CACHEPICFLAG_QUIET); sb_sigil[2] = Draw_CachePic_Flags ("gfx/sb_sigil3", CACHEPICFLAG_QUIET); sb_sigil[3] = Draw_CachePic_Flags ("gfx/sb_sigil4", CACHEPICFLAG_QUIET); sb_faces[4][0] = Draw_CachePic_Flags ("gfx/face1", CACHEPICFLAG_QUIET); sb_faces[4][1] = Draw_CachePic_Flags ("gfx/face_p1", CACHEPICFLAG_QUIET); sb_faces[3][0] = Draw_CachePic_Flags ("gfx/face2", CACHEPICFLAG_QUIET); sb_faces[3][1] = Draw_CachePic_Flags ("gfx/face_p2", CACHEPICFLAG_QUIET); sb_faces[2][0] = Draw_CachePic_Flags ("gfx/face3", CACHEPICFLAG_QUIET); sb_faces[2][1] = Draw_CachePic_Flags ("gfx/face_p3", CACHEPICFLAG_QUIET); sb_faces[1][0] = Draw_CachePic_Flags ("gfx/face4", CACHEPICFLAG_QUIET); sb_faces[1][1] = Draw_CachePic_Flags ("gfx/face_p4", CACHEPICFLAG_QUIET); sb_faces[0][0] = Draw_CachePic_Flags ("gfx/face5", CACHEPICFLAG_QUIET); sb_faces[0][1] = Draw_CachePic_Flags ("gfx/face_p5", CACHEPICFLAG_QUIET); sb_face_invis = Draw_CachePic_Flags ("gfx/face_invis", CACHEPICFLAG_QUIET); sb_face_invuln = Draw_CachePic_Flags ("gfx/face_invul2", CACHEPICFLAG_QUIET); sb_face_invis_invuln = Draw_CachePic_Flags ("gfx/face_inv2", CACHEPICFLAG_QUIET); sb_face_quad = Draw_CachePic_Flags ("gfx/face_quad", CACHEPICFLAG_QUIET); sb_sbar = Draw_CachePic_Flags ("gfx/sbar", CACHEPICFLAG_QUIET); sb_ibar = Draw_CachePic_Flags ("gfx/ibar", CACHEPICFLAG_QUIET); sb_scorebar = Draw_CachePic_Flags ("gfx/scorebar", CACHEPICFLAG_QUIET); //MED 01/04/97 added new hipnotic weapons if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) { hsb_weapons[0][0] = Draw_CachePic_Flags ("gfx/inv_laser", CACHEPICFLAG_QUIET); hsb_weapons[0][1] = Draw_CachePic_Flags ("gfx/inv_mjolnir", CACHEPICFLAG_QUIET); hsb_weapons[0][2] = Draw_CachePic_Flags ("gfx/inv_gren_prox", CACHEPICFLAG_QUIET); hsb_weapons[0][3] = Draw_CachePic_Flags ("gfx/inv_prox_gren", CACHEPICFLAG_QUIET); hsb_weapons[0][4] = Draw_CachePic_Flags ("gfx/inv_prox", CACHEPICFLAG_QUIET); hsb_weapons[1][0] = Draw_CachePic_Flags ("gfx/inv2_laser", CACHEPICFLAG_QUIET); hsb_weapons[1][1] = Draw_CachePic_Flags ("gfx/inv2_mjolnir", CACHEPICFLAG_QUIET); hsb_weapons[1][2] = Draw_CachePic_Flags ("gfx/inv2_gren_prox", CACHEPICFLAG_QUIET); hsb_weapons[1][3] = Draw_CachePic_Flags ("gfx/inv2_prox_gren", CACHEPICFLAG_QUIET); hsb_weapons[1][4] = Draw_CachePic_Flags ("gfx/inv2_prox", CACHEPICFLAG_QUIET); for (i = 0;i < 5;i++) { hsb_weapons[2+i][0] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_laser",i+1), CACHEPICFLAG_QUIET); hsb_weapons[2+i][1] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_mjolnir",i+1), CACHEPICFLAG_QUIET); hsb_weapons[2+i][2] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_gren_prox",i+1), CACHEPICFLAG_QUIET); hsb_weapons[2+i][3] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_prox_gren",i+1), CACHEPICFLAG_QUIET); hsb_weapons[2+i][4] = Draw_CachePic_Flags (va(vabuf, sizeof(vabuf), "gfx/inva%i_prox",i+1), CACHEPICFLAG_QUIET); } hsb_items[0] = Draw_CachePic_Flags ("gfx/sb_wsuit", CACHEPICFLAG_QUIET); hsb_items[1] = Draw_CachePic_Flags ("gfx/sb_eshld", CACHEPICFLAG_QUIET); } else if (gamemode == GAME_ROGUE) { rsb_invbar[0] = Draw_CachePic_Flags ("gfx/r_invbar1", CACHEPICFLAG_QUIET); rsb_invbar[1] = Draw_CachePic_Flags ("gfx/r_invbar2", CACHEPICFLAG_QUIET); rsb_weapons[0] = Draw_CachePic_Flags ("gfx/r_lava", CACHEPICFLAG_QUIET); rsb_weapons[1] = Draw_CachePic_Flags ("gfx/r_superlava", CACHEPICFLAG_QUIET); rsb_weapons[2] = Draw_CachePic_Flags ("gfx/r_gren", CACHEPICFLAG_QUIET); rsb_weapons[3] = Draw_CachePic_Flags ("gfx/r_multirock", CACHEPICFLAG_QUIET); rsb_weapons[4] = Draw_CachePic_Flags ("gfx/r_plasma", CACHEPICFLAG_QUIET); rsb_items[0] = Draw_CachePic_Flags ("gfx/r_shield1", CACHEPICFLAG_QUIET); rsb_items[1] = Draw_CachePic_Flags ("gfx/r_agrav1", CACHEPICFLAG_QUIET); // PGM 01/19/97 - team color border rsb_teambord = Draw_CachePic_Flags ("gfx/r_teambord", CACHEPICFLAG_QUIET); // PGM 01/19/97 - team color border rsb_ammo[0] = Draw_CachePic_Flags ("gfx/r_ammolava", CACHEPICFLAG_QUIET); rsb_ammo[1] = Draw_CachePic_Flags ("gfx/r_ammomulti", CACHEPICFLAG_QUIET); rsb_ammo[2] = Draw_CachePic_Flags ("gfx/r_ammoplasma", CACHEPICFLAG_QUIET); } } sb_ranking = Draw_CachePic_Flags ("gfx/ranking", CACHEPICFLAG_QUIET); sb_complete = Draw_CachePic_Flags ("gfx/complete", CACHEPICFLAG_QUIET); sb_inter = Draw_CachePic_Flags ("gfx/inter", CACHEPICFLAG_QUIET); sb_finale = Draw_CachePic_Flags ("gfx/finale", CACHEPICFLAG_QUIET); } static void sbar_shutdown(void) { } static void sbar_newmap(void) { } void Sbar_Init (void) { Cmd_AddCommand("+showscores", Sbar_ShowScores, "show scoreboard"); Cmd_AddCommand("-showscores", Sbar_DontShowScores, "hide scoreboard"); Cvar_RegisterVariable(&showfps); Cvar_RegisterVariable(&showsound); Cvar_RegisterVariable(&showblur); Cvar_RegisterVariable(&showspeed); Cvar_RegisterVariable(&showtopspeed); Cvar_RegisterVariable(&showtime); Cvar_RegisterVariable(&showtime_format); Cvar_RegisterVariable(&showdate); Cvar_RegisterVariable(&showdate_format); Cvar_RegisterVariable(&showtex); Cvar_RegisterVariable(&sbar_alpha_bg); Cvar_RegisterVariable(&sbar_alpha_fg); Cvar_RegisterVariable(&sbar_hudselector); Cvar_RegisterVariable(&sbar_scorerank); Cvar_RegisterVariable(&sbar_gametime); Cvar_RegisterVariable(&sbar_miniscoreboard_size); Cvar_RegisterVariable(&sbar_info_pos); Cvar_RegisterVariable(&cl_deathscoreboard); Cvar_RegisterVariable(&crosshair_color_red); Cvar_RegisterVariable(&crosshair_color_green); Cvar_RegisterVariable(&crosshair_color_blue); Cvar_RegisterVariable(&crosshair_color_alpha); Cvar_RegisterVariable(&crosshair_size); Cvar_RegisterVariable(&sbar_flagstatus_right); // (GAME_NEXUZI ONLY) Cvar_RegisterVariable(&sbar_flagstatus_pos); // (GAME_NEXUIZ ONLY) R_RegisterModule("sbar", sbar_start, sbar_shutdown, sbar_newmap, NULL, NULL); } //============================================================================= // drawing routines are relative to the status bar location int sbar_x, sbar_y; /* ============= Sbar_DrawPic ============= */ static void Sbar_DrawStretchPic (int x, int y, cachepic_t *pic, float alpha, float overridewidth, float overrideheight) { DrawQ_Pic (sbar_x + x, sbar_y + y, pic, overridewidth, overrideheight, 1, 1, 1, alpha, 0); } static void Sbar_DrawPic (int x, int y, cachepic_t *pic) { DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, sbar_alpha_fg.value, 0); } static void Sbar_DrawAlphaPic (int x, int y, cachepic_t *pic, float alpha) { DrawQ_Pic (sbar_x + x, sbar_y + y, pic, 0, 0, 1, 1, 1, alpha, 0); } /* ================ Sbar_DrawCharacter Draws one solid graphics character ================ */ static void Sbar_DrawCharacter (int x, int y, int num) { char vabuf[1024]; DrawQ_String (sbar_x + x + 4 , sbar_y + y, va(vabuf, sizeof(vabuf), "%c", num), 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, true, FONT_SBAR); } /* ================ Sbar_DrawString ================ */ static void Sbar_DrawString (int x, int y, char *str) { DrawQ_String (sbar_x + x, sbar_y + y, str, 0, 8, 8, 1, 1, 1, sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR); } /* ============= Sbar_DrawNum ============= */ static void Sbar_DrawNum (int x, int y, int num, int digits, int color) { char str[32], *ptr; int l, frame; l = dpsnprintf(str, sizeof(str), "%i", num); ptr = str; if (l > digits) ptr += (l-digits); if (l < digits) x += (digits-l)*24; while (*ptr) { if (*ptr == '-') frame = STAT_MINUS; else frame = *ptr -'0'; Sbar_DrawPic (x, y, sb_nums[color][frame]); x += 24; ptr++; } } /* ============= Sbar_DrawXNum ============= */ static void Sbar_DrawXNum (int x, int y, int num, int digits, int lettersize, float r, float g, float b, float a, int flags) { char str[32], *ptr; int l, frame; if (digits < 0) { digits = -digits; l = dpsnprintf(str, sizeof(str), "%0*i", digits, num); } else l = dpsnprintf(str, sizeof(str), "%i", num); ptr = str; if (l > digits) ptr += (l-digits); if (l < digits) x += (digits-l) * lettersize; while (*ptr) { if (*ptr == '-') frame = STAT_MINUS; else frame = *ptr -'0'; DrawQ_Pic (sbar_x + x, sbar_y + y, sb_nums[0][frame],lettersize,lettersize,r,g,b,a * sbar_alpha_fg.value,flags); x += lettersize; ptr++; } } //============================================================================= static int Sbar_IsTeammatch(void) { // currently only nexuiz uses the team score board return (IS_OLDNEXUIZ_DERIVED(gamemode) && (teamplay.integer > 0)); } /* =============== Sbar_SortFrags =============== */ static int fragsort[MAX_SCOREBOARD]; static int scoreboardlines; int Sbar_GetSortedPlayerIndex (int index) { return index >= 0 && index < scoreboardlines ? fragsort[index] : -1; } static scoreboard_t teams[MAX_SCOREBOARD]; static int teamsort[MAX_SCOREBOARD]; static int teamlines; void Sbar_SortFrags (void) { int i, j, k, color; // sort by frags scoreboardlines = 0; for (i=0 ; i max) str[max] = 0; // print the filename and message Sbar_DrawString(8, 12, str); // print the time Sbar_DrawString(8 + max*8, 12, timestr); #else char str[80]; int minutes, seconds, tens, units; int l; if (IS_OLDNEXUIZ_DERIVED(gamemode)) { dpsnprintf (str, sizeof(str), "Monsters:%3i /%3i", cl.stats[STAT_MONSTERS], cl.stats[STAT_TOTALMONSTERS]); Sbar_DrawString (8, 4, str); dpsnprintf (str, sizeof(str), "Secrets :%3i /%3i", cl.stats[STAT_SECRETS], cl.stats[STAT_TOTALSECRETS]); Sbar_DrawString (8, 12, str); } // time minutes = (int)(cl.time / 60); seconds = (int)(cl.time - 60*minutes); tens = seconds / 10; units = seconds - 10*tens; dpsnprintf (str, sizeof(str), "Time :%3i:%i%i", minutes, tens, units); Sbar_DrawString (184, 4, str); // draw level name if (IS_OLDNEXUIZ_DERIVED(gamemode)) { l = (int) strlen (cl.worldname); Sbar_DrawString (232 - l*4, 12, cl.worldname); } else { l = (int) strlen (cl.worldmessage); Sbar_DrawString (232 - l*4, 12, cl.worldmessage); } #endif } /* =============== Sbar_DrawScoreboard =============== */ static void Sbar_DrawScoreboard (void) { Sbar_SoloScoreboard (); // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode //if (cl.gametype == GAME_DEATHMATCH) if (!cl.islocalgame) Sbar_DeathmatchOverlay (); } //============================================================================= // AK to make DrawInventory smaller static void Sbar_DrawWeapon(int nr, float fade, int active) { char vabuf[1024]; if (sbar_hudselector.integer == 1) { // width = 300, height = 100 const int w_width = 32, w_height = 12, w_space = 2, font_size = 8; DrawQ_Pic((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr, vid_conheight.integer - w_height, sb_weapons[0][nr], w_width, w_height, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 0.6, (active ? 1 : 0.6) * fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); // FIXME ?? DrawQ_String((vid_conwidth.integer - w_width * 9) * 0.5 + w_width * nr + w_space, vid_conheight.integer - w_height + w_space, va(vabuf, sizeof(vabuf), "%i",nr+1), 0, font_size, font_size, 1, 1, 0, sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); } else { // width = 300, height = 100 const int w_width = 300, w_height = 100, w_space = 10; const float w_scale = 0.4; DrawQ_Pic(vid_conwidth.integer - (w_width + w_space) * w_scale, (w_height + w_space) * w_scale * nr + w_space, sb_weapons[0][nr], w_width * w_scale, w_height * w_scale, (active) ? 1 : 0.6, active ? 1 : 0.6, active ? 1 : 1, fade * sbar_alpha_fg.value, DRAWFLAG_NORMAL); //DrawQ_String(vid_conwidth.integer - (w_space + font_size ), (w_height + w_space) * w_scale * nr + w_space, va(vabuf, sizeof(vabuf), "%i",nr+1), 0, font_size, font_size, 1, 0, 0, fade, 0, NULL, true, FONT_DEFAULT); } } /* =============== Sbar_DrawInventory =============== */ static void Sbar_DrawInventory (void) { int i; char num[6]; float time; int flashon; if (gamemode == GAME_ROGUE) { if ( cl.stats[STAT_ACTIVEWEAPON] >= RIT_LAVA_NAILGUN ) Sbar_DrawAlphaPic (0, -24, rsb_invbar[0], sbar_alpha_bg.value); else Sbar_DrawAlphaPic (0, -24, rsb_invbar[1], sbar_alpha_bg.value); } else Sbar_DrawAlphaPic (0, -24, sb_ibar, sbar_alpha_bg.value); // weapons for (i=0 ; i<7 ; i++) { if (cl.stats[STAT_ITEMS] & (IT_SHOTGUN<= 10) { if ( cl.stats[STAT_ACTIVEWEAPON] == (IT_SHOTGUN<= 10) { if ( cl.stats[STAT_ACTIVEWEAPON] == (1<= RIT_LAVA_NAILGUN ) for (i=0;i<5;i++) if (cl.stats[STAT_ACTIVEWEAPON] == (RIT_LAVA_NAILGUN << i)) Sbar_DrawPic ((i+2)*24, -16, rsb_weapons[i]); } // ammo counts for (i=0 ; i<4 ; i++) { dpsnprintf (num, sizeof(num), "%4i",cl.stats[STAT_SHELLS+i] ); if (num[0] != ' ') Sbar_DrawCharacter ( (6*i+0)*8 - 2, -24, 18 + num[0] - '0'); if (num[1] != ' ') Sbar_DrawCharacter ( (6*i+1)*8 - 2, -24, 18 + num[1] - '0'); if (num[2] != ' ') Sbar_DrawCharacter ( (6*i+2)*8 - 2, -24, 18 + num[2] - '0'); if (num[3] != ' ') Sbar_DrawCharacter ( (6*i+3)*8 - 2, -24, 18 + num[3] - '0'); } // items for (i=0 ; i<6 ; i++) if (cl.stats[STAT_ITEMS] & (1<<(17+i))) { //MED 01/04/97 changed keys if (!(gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) || (i>1)) Sbar_DrawPic (192 + i*16, -16, sb_items[i]); } //MED 01/04/97 added hipnotic items // hipnotic items if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) { for (i=0 ; i<2 ; i++) if (cl.stats[STAT_ITEMS] & (1<<(24+i))) Sbar_DrawPic (288 + i*16, -16, hsb_items[i]); } if (gamemode == GAME_ROGUE) { // new rogue items for (i=0 ; i<2 ; i++) if (cl.stats[STAT_ITEMS] & (1<<(29+i))) Sbar_DrawPic (288 + i*16, -16, rsb_items[i]); } else { // sigils for (i=0 ; i<4 ; i++) if (cl.stats[STAT_ITEMS] & (1<<(28+i))) Sbar_DrawPic (320-32 + i*8, -16, sb_sigil[i]); } } //============================================================================= /* =============== Sbar_DrawFrags =============== */ static void Sbar_DrawFrags (void) { int i, k, l, x, f; char num[12]; scoreboard_t *s; unsigned char *c; Sbar_SortFrags (); // draw the text l = min(scoreboardlines, 4); x = 23 * 8; for (i = 0;i < l;i++) { k = fragsort[i]; s = &cl.scores[k]; // draw background c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; DrawQ_Fill (sbar_x + x + 10, sbar_y - 23, 28, 4, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; DrawQ_Fill (sbar_x + x + 10, sbar_y + 4 - 23, 28, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // draw number f = s->frags; dpsnprintf (num, sizeof(num), "%3i",f); if (k == cl.viewentity - 1) { Sbar_DrawCharacter ( x + 2, -24, 16); Sbar_DrawCharacter ( x + 32 - 4, -24, 17); } Sbar_DrawCharacter (x + 8, -24, num[0]); Sbar_DrawCharacter (x + 16, -24, num[1]); Sbar_DrawCharacter (x + 24, -24, num[2]); x += 32; } } //============================================================================= /* =============== Sbar_DrawFace =============== */ static void Sbar_DrawFace (void) { int f; // PGM 01/19/97 - team color drawing // PGM 03/02/97 - fixed so color swatch only appears in CTF modes if (gamemode == GAME_ROGUE && !cl.islocalgame && (teamplay.integer > 3) && (teamplay.integer < 7)) { char num[12]; scoreboard_t *s; unsigned char *c; s = &cl.scores[cl.viewentity - 1]; // draw background Sbar_DrawPic (112, 0, rsb_teambord); c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+3, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; DrawQ_Fill (sbar_x + 113, vid_conheight.integer-SBAR_HEIGHT+12, 22, 9, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // draw number f = s->frags; dpsnprintf (num, sizeof(num), "%3i",f); if ((s->colors & 0xf0)==0) { if (num[0] != ' ') Sbar_DrawCharacter(109, 3, 18 + num[0] - '0'); if (num[1] != ' ') Sbar_DrawCharacter(116, 3, 18 + num[1] - '0'); if (num[2] != ' ') Sbar_DrawCharacter(123, 3, 18 + num[2] - '0'); } else { Sbar_DrawCharacter ( 109, 3, num[0]); Sbar_DrawCharacter ( 116, 3, num[1]); Sbar_DrawCharacter ( 123, 3, num[2]); } return; } // PGM 01/19/97 - team color drawing if ( (cl.stats[STAT_ITEMS] & (IT_INVISIBILITY | IT_INVULNERABILITY) ) == (IT_INVISIBILITY | IT_INVULNERABILITY) ) Sbar_DrawPic (112, 0, sb_face_invis_invuln); else if (cl.stats[STAT_ITEMS] & IT_QUAD) Sbar_DrawPic (112, 0, sb_face_quad ); else if (cl.stats[STAT_ITEMS] & IT_INVISIBILITY) Sbar_DrawPic (112, 0, sb_face_invis ); else if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) Sbar_DrawPic (112, 0, sb_face_invuln); else { f = cl.stats[STAT_HEALTH] / 20; f = bound(0, f, 4); Sbar_DrawPic (112, 0, sb_faces[f][cl.time <= cl.faceanimtime]); } } double topspeed = 0; double topspeedxy = 0; time_t current_time = 3; time_t top_time = 0; time_t topxy_time = 0; static void get_showspeed_unit(int unitnumber, double *conversion_factor, const char **unit) { if(unitnumber < 0) unitnumber = showspeed.integer; switch(unitnumber) { default: case 1: if(IS_NEXUIZ_DERIVED(gamemode)) *unit = "in/s"; else *unit = "qu/s"; *conversion_factor = 1.0; break; case 2: *unit = "m/s"; *conversion_factor = 0.0254; if(!IS_NEXUIZ_DERIVED(gamemode)) *conversion_factor *= 1.5; // 1qu=1.5in is for non-Nexuiz/Xonotic only - Nexuiz/Xonotic players are overly large, but 1qu=1in fixes that break; case 3: *unit = "km/h"; *conversion_factor = 0.0254 * 3.6; if(!IS_NEXUIZ_DERIVED(gamemode)) *conversion_factor *= 1.5; break; case 4: *unit = "mph"; *conversion_factor = 0.0254 * 3.6 * 0.6213711922; if(!IS_NEXUIZ_DERIVED(gamemode)) *conversion_factor *= 1.5; break; case 5: *unit = "knots"; *conversion_factor = 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h if(!IS_NEXUIZ_DERIVED(gamemode)) *conversion_factor *= 1.5; break; } } static double showfps_nexttime = 0, showfps_lasttime = -1; static double showfps_framerate = 0; static int showfps_framecount = 0; void Sbar_ShowFPS_Update(void) { double interval = 1; double newtime; newtime = realtime; if (newtime >= showfps_nexttime) { showfps_framerate = showfps_framecount / (newtime - showfps_lasttime); if (showfps_nexttime < newtime - interval * 1.5) showfps_nexttime = newtime; showfps_lasttime = newtime; showfps_nexttime += interval; showfps_framecount = 0; } showfps_framecount++; } void Sbar_ShowFPS(void) { float fps_x, fps_y, fps_scalex, fps_scaley, fps_strings = 0; char soundstring[32]; char fpsstring[32]; char timestring[32]; char datestring[32]; char timedemostring1[32]; char timedemostring2[32]; char speedstring[32]; char blurstring[32]; char topspeedstring[48]; char texstring[MAX_QPATH]; qboolean red = false; soundstring[0] = 0; fpsstring[0] = 0; timedemostring1[0] = 0; timedemostring2[0] = 0; timestring[0] = 0; datestring[0] = 0; speedstring[0] = 0; blurstring[0] = 0; texstring[0] = 0; topspeedstring[0] = 0; if (showfps.integer) { red = (showfps_framerate < 1.0f); if(showfps.integer == 2) dpsnprintf(fpsstring, sizeof(fpsstring), "%7.3f mspf", (1000.0 / showfps_framerate)); else if (red) dpsnprintf(fpsstring, sizeof(fpsstring), "%4i spf", (int)(1.0 / showfps_framerate + 0.5)); else dpsnprintf(fpsstring, sizeof(fpsstring), "%4i fps", (int)(showfps_framerate + 0.5)); fps_strings++; if (cls.timedemo) { dpsnprintf(timedemostring1, sizeof(timedemostring1), "frame%4i %f", cls.td_frames, realtime - cls.td_starttime); dpsnprintf(timedemostring2, sizeof(timedemostring2), "%i seconds %3.0f/%3.0f/%3.0f fps", cls.td_onesecondavgcount, cls.td_onesecondminfps, cls.td_onesecondavgfps / max(1, cls.td_onesecondavgcount), cls.td_onesecondmaxfps); fps_strings++; fps_strings++; } } if (showtime.integer) { strlcpy(timestring, Sys_TimeString(showtime_format.string), sizeof(timestring)); fps_strings++; } if (showdate.integer) { strlcpy(datestring, Sys_TimeString(showdate_format.string), sizeof(datestring)); fps_strings++; } if (showblur.integer) { dpsnprintf(blurstring, sizeof(blurstring), "%3i%% blur", (int)(cl.motionbluralpha * 100)); fps_strings++; } if (showsound.integer) { dpsnprintf(soundstring, sizeof(soundstring), "%4i/4%i at %3ims", cls.soundstats.mixedsounds, cls.soundstats.totalsounds, cls.soundstats.latency_milliseconds); fps_strings++; } if (showspeed.integer || showtopspeed.integer) { double speed, speedxy, f; const char *unit; speed = VectorLength(cl.movement_velocity); speedxy = sqrt(cl.movement_velocity[0] * cl.movement_velocity[0] + cl.movement_velocity[1] * cl.movement_velocity[1]); if (showspeed.integer) { get_showspeed_unit(showspeed.integer, &f, &unit); dpsnprintf(speedstring, sizeof(speedstring), "%.0f (%.0f) %s", f*speed, f*speedxy, unit); fps_strings++; } if (showtopspeed.integer) { qboolean topspeed_latched = false, topspeedxy_latched = false; get_showspeed_unit(showtopspeed.integer, &f, &unit); if (speed >= topspeed || current_time - top_time > 3) { topspeed = speed; time(&top_time); } else topspeed_latched = true; if (speedxy >= topspeedxy || current_time - topxy_time > 3) { topspeedxy = speedxy; time(&topxy_time); } else topspeedxy_latched = true; dpsnprintf(topspeedstring, sizeof(topspeedstring), "%s%.0f%s (%s%.0f%s) %s", topspeed_latched ? "^1" : "^xf88", f*topspeed, "^xf88", topspeedxy_latched ? "^1" : "^xf88", f*topspeedxy, "^xf88", unit); time(¤t_time); fps_strings++; } } if (showtex.integer) { vec3_t org; vec3_t dest; vec3_t temp; trace_t trace; Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, org); VectorSet(temp, 65536, 0, 0); Matrix4x4_Transform(&r_refdef.view.matrix, temp, dest); trace.hittexture = NULL; // to make sure // TODO change this trace to be stopped by anything "visible" (i.e. with a drawsurface), but not stuff like weapclip // probably needs adding a new SUPERCONTENTS type trace = CL_TraceLine(org, dest, MOVE_NORMAL, NULL, SUPERCONTENTS_SOLID, 0, collision_extendmovelength.value, true, false, NULL, true, true); if(trace.hittexture) strlcpy(texstring, trace.hittexture->name, sizeof(texstring)); else strlcpy(texstring, "(no texture hit)", sizeof(texstring)); fps_strings++; } if (fps_strings) { fps_scalex = 12; fps_scaley = 12; //fps_y = vid_conheight.integer - sb_lines; // yes this may draw over the sbar //fps_y = bound(0, fps_y, vid_conheight.integer - fps_strings*fps_scaley); fps_y = vid_conheight.integer - sbar_info_pos.integer - fps_strings*fps_scaley; if (soundstring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(soundstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, soundstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (fpsstring[0]) { r_draw2d_force = true; fps_x = vid_conwidth.integer - DrawQ_TextWidth(fpsstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); if (red) DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); else DrawQ_String(fps_x, fps_y, fpsstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; r_draw2d_force = false; } if (timedemostring1[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(timedemostring1, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, timedemostring1, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (timedemostring2[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(timedemostring2, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, timedemostring2, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (timestring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(timestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, timestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (datestring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(datestring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, datestring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (speedstring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(speedstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, speedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (topspeedstring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(topspeedstring, 0, fps_scalex, fps_scaley, false, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, topspeedstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); fps_y += fps_scaley; } if (blurstring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(blurstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, blurstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } if (texstring[0]) { fps_x = vid_conwidth.integer - DrawQ_TextWidth(texstring, 0, fps_scalex, fps_scaley, true, FONT_INFOBAR); DrawQ_Fill(fps_x, fps_y, vid_conwidth.integer - fps_x, fps_scaley, 0, 0, 0, 0.5, 0); DrawQ_String(fps_x, fps_y, texstring, 0, fps_scalex, fps_scaley, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); fps_y += fps_scaley; } } } static void Sbar_DrawGauge(float x, float y, cachepic_t *pic, float width, float height, float rangey, float rangeheight, float c1, float c2, float c1r, float c1g, float c1b, float c1a, float c2r, float c2g, float c2b, float c2a, float c3r, float c3g, float c3b, float c3a, int drawflags) { float r[5]; c2 = bound(0, c2, 1); c1 = bound(0, c1, 1 - c2); r[0] = 0; r[1] = rangey + rangeheight * (c2 + c1); r[2] = rangey + rangeheight * (c2); r[3] = rangey; r[4] = height; if (r[1] > r[0]) DrawQ_SuperPic(x, y + r[0], pic, width, (r[1] - r[0]), 0,(r[0] / height), c3r,c3g,c3b,c3a, 1,(r[0] / height), c3r,c3g,c3b,c3a, 0,(r[1] / height), c3r,c3g,c3b,c3a, 1,(r[1] / height), c3r,c3g,c3b,c3a, drawflags); if (r[2] > r[1]) DrawQ_SuperPic(x, y + r[1], pic, width, (r[2] - r[1]), 0,(r[1] / height), c1r,c1g,c1b,c1a, 1,(r[1] / height), c1r,c1g,c1b,c1a, 0,(r[2] / height), c1r,c1g,c1b,c1a, 1,(r[2] / height), c1r,c1g,c1b,c1a, drawflags); if (r[3] > r[2]) DrawQ_SuperPic(x, y + r[2], pic, width, (r[3] - r[2]), 0,(r[2] / height), c2r,c2g,c2b,c2a, 1,(r[2] / height), c2r,c2g,c2b,c2a, 0,(r[3] / height), c2r,c2g,c2b,c2a, 1,(r[3] / height), c2r,c2g,c2b,c2a, drawflags); if (r[4] > r[3]) DrawQ_SuperPic(x, y + r[3], pic, width, (r[4] - r[3]), 0,(r[3] / height), c3r,c3g,c3b,c3a, 1,(r[3] / height), c3r,c3g,c3b,c3a, 0,(r[4] / height), c3r,c3g,c3b,c3a, 1,(r[4] / height), c3r,c3g,c3b,c3a, drawflags); } /* =============== Sbar_Draw =============== */ extern float v_dmg_time, v_dmg_roll, v_dmg_pitch; extern cvar_t v_kicktime; void Sbar_Score (int margin); void Sbar_Draw (void) { cachepic_t *pic; char vabuf[1024]; if(cl.csqc_vidvars.drawenginesbar) //[515]: csqc drawsbar { if (sb_showscores) Sbar_DrawScoreboard (); else if (cl.intermission == 1) { if(IS_OLDNEXUIZ_DERIVED(gamemode)) // display full scoreboard (that is, show scores + map name) { Sbar_DrawScoreboard(); return; } Sbar_IntermissionOverlay(); } else if (cl.intermission == 2) Sbar_FinaleOverlay(); else if (gamemode == GAME_DELUXEQUAKE) { } else if (IS_OLDNEXUIZ_DERIVED(gamemode)) { if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) { sbar_x = (vid_conwidth.integer - 640)/2; sbar_y = vid_conheight.integer - 47; Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); Sbar_DrawScoreboard (); } else if (sb_lines && sbar_hudselector.integer == 1) { int i; float fade; int redflag, blueflag; float x; sbar_x = (vid_conwidth.integer - 320)/2; sbar_y = vid_conheight.integer - 24 - 16; // calculate intensity to draw weapons bar at fade = 3.2 - 2 * (cl.time - cl.weapontime); fade = bound(0.7, fade, 1); for (i = 0; i < 8;i++) if (cl.stats[STAT_ITEMS] & (1 << i)) Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); if((cl.stats[STAT_ITEMS] & (1<<12))) Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); // flag icons redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; if (redflag == 3 && blueflag == 3) { // The Impossible Combination[tm] // Can only happen in Key Hunt mode... Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[14]); } else { if (redflag) Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 64)), sb_items[redflag+10]); if (blueflag) Sbar_DrawPic ((int) x, (int) ((vid_conheight.integer - sbar_y) - (sbar_flagstatus_pos.value + 128)), sb_items[blueflag+14]); } // armor if (cl.stats[STAT_ARMOR] > 0) { Sbar_DrawStretchPic (72, 0, sb_armor[0], sbar_alpha_fg.value, 24, 24); if(cl.stats[STAT_ARMOR] > 200) Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0,1,0,1,0); else if(cl.stats[STAT_ARMOR] > 100) Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.2,1,0.2,1,0); else if(cl.stats[STAT_ARMOR] > 50) Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.6,0.7,0.8,1,0); else if(cl.stats[STAT_ARMOR] > 25) Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,1,1,0.2,1,0); else Sbar_DrawXNum(0,0,cl.stats[STAT_ARMOR],3,24,0.7,0,0,1,0); } // health if (cl.stats[STAT_HEALTH] != 0) { Sbar_DrawStretchPic (184, 0, sb_health, sbar_alpha_fg.value, 24, 24); if(cl.stats[STAT_HEALTH] > 200) Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0,1,0,1,0); else if(cl.stats[STAT_HEALTH] > 100) Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.2,1,0.2,1,0); else if(cl.stats[STAT_HEALTH] > 50) Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); else if(cl.stats[STAT_HEALTH] > 25) Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,1,1,0.2,1,0); else Sbar_DrawXNum(112,0,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); } // ammo if ((cl.stats[STAT_ITEMS] & (NEX_IT_SHELLS | NEX_IT_BULLETS | NEX_IT_ROCKETS | NEX_IT_CELLS)) || cl.stats[STAT_AMMO] != 0) { if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) Sbar_DrawStretchPic (296, 0, sb_ammo[0], sbar_alpha_fg.value, 24, 24); else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) Sbar_DrawStretchPic (296, 0, sb_ammo[1], sbar_alpha_fg.value, 24, 24); else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) Sbar_DrawStretchPic (296, 0, sb_ammo[2], sbar_alpha_fg.value, 24, 24); else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) Sbar_DrawStretchPic (296, 0, sb_ammo[3], sbar_alpha_fg.value, 24, 24); if(cl.stats[STAT_AMMO] > 10) Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.6,0.7,0.8,1,0); else Sbar_DrawXNum(224, 0, cl.stats[STAT_AMMO], 3, 24, 0.7,0,0,1,0); } if (sbar_x + 320 + 160 <= vid_conwidth.integer) Sbar_MiniDeathmatchOverlay (sbar_x + 320, sbar_y); if (sbar_x > 0) Sbar_Score(16); // The margin can be at most 8 to support 640x480 console size: // 320 + 2 * (144 + 16) = 640 } else if (sb_lines) { int i; float fade; int redflag, blueflag; float x; sbar_x = (vid_conwidth.integer - 640)/2; sbar_y = vid_conheight.integer - 47; // calculate intensity to draw weapons bar at fade = 3 - 2 * (cl.time - cl.weapontime); if (fade > 0) { fade = min(fade, 1); for (i = 0; i < 8;i++) if (cl.stats[STAT_ITEMS] & (1 << i)) Sbar_DrawWeapon(i + 1, fade, (i + 2 == cl.stats[STAT_ACTIVEWEAPON])); if((cl.stats[STAT_ITEMS] & (1<<12))) Sbar_DrawWeapon(0, fade, (cl.stats[STAT_ACTIVEWEAPON] == 1)); } //if (!cl.islocalgame) // Sbar_DrawFrags (); if (sb_lines > 24) Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_fg.value); else Sbar_DrawAlphaPic (0, 0, sb_sbar_minimal, sbar_alpha_fg.value); // flag icons redflag = ((cl.stats[STAT_ITEMS]>>15) & 3); blueflag = ((cl.stats[STAT_ITEMS]>>17) & 3); x = sbar_flagstatus_right.integer ? vid_conwidth.integer - 10 - sbar_x - 64 : 10 - sbar_x; if (redflag == 3 && blueflag == 3) { // The Impossible Combination[tm] // Can only happen in Key Hunt mode... Sbar_DrawPic ((int) x, -179, sb_items[14]); } else { if (redflag) Sbar_DrawPic ((int) x, -117, sb_items[redflag+10]); if (blueflag) Sbar_DrawPic ((int) x, -177, sb_items[blueflag+14]); } // armor Sbar_DrawXNum ((340-3*24), 12, cl.stats[STAT_ARMOR], 3, 24, 0.6,0.7,0.8,1,0); // health if(cl.stats[STAT_HEALTH] > 100) Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,1,1,1,1,0); else if(cl.stats[STAT_HEALTH] <= 25 && cl.time - (int)cl.time > 0.5) Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.7,0,0,1,0); else Sbar_DrawXNum((154-3*24),12,cl.stats[STAT_HEALTH],3,24,0.6,0.7,0.8,1,0); // AK dont draw ammo for the laser if(cl.stats[STAT_ACTIVEWEAPON] != 12) { if (cl.stats[STAT_ITEMS] & NEX_IT_SHELLS) Sbar_DrawPic (519, 0, sb_ammo[0]); else if (cl.stats[STAT_ITEMS] & NEX_IT_BULLETS) Sbar_DrawPic (519, 0, sb_ammo[1]); else if (cl.stats[STAT_ITEMS] & NEX_IT_ROCKETS) Sbar_DrawPic (519, 0, sb_ammo[2]); else if (cl.stats[STAT_ITEMS] & NEX_IT_CELLS) Sbar_DrawPic (519, 0, sb_ammo[3]); if(cl.stats[STAT_AMMO] <= 10) Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.7, 0,0,1,0); else Sbar_DrawXNum ((519-3*24), 12, cl.stats[STAT_AMMO], 3, 24, 0.6, 0.7,0.8,1,0); } if (sb_lines > 24) DrawQ_Pic(sbar_x,sbar_y,sb_sbar_overlay,0,0,1,1,1,1,DRAWFLAG_MODULATE); if (sbar_x + 600 + 160 <= vid_conwidth.integer) Sbar_MiniDeathmatchOverlay (sbar_x + 600, sbar_y); if (sbar_x > 0) Sbar_Score(-16); // Because: // Mini scoreboard uses 12*4 per other team, that is, 144 // pixels when there are four teams... // Nexuiz by default sets vid_conwidth to 800... makes // sbar_x == 80... // so we need to shift it by 64 pixels to the right to fit // BUT: then it overlaps with the image that gets drawn // for viewsize 100! Therefore, just account for 3 teams, // that is, 96 pixels mini scoreboard size, needing 16 pixels // to the right! } } else if (gamemode == GAME_ZYMOTIC) { #if 1 float scale = 64.0f / 256.0f; float kickoffset[3]; VectorClear(kickoffset); if (v_dmg_time > 0) { kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; } sbar_x = (int)((vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]); sbar_y = (int)((vid_conheight.integer - 256 * scale)/2 + kickoffset[1]); // left1 16, 48 : 126 -66 // left2 16, 128 : 196 -66 // right 176, 48 : 196 -136 Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 48 * scale, zymsb_crosshair_left1, 64*scale, 80*scale, 78*scale, -66*scale, cl.stats[STAT_AMMO] * (1.0 / 200.0), cl.stats[STAT_SHELLS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); Sbar_DrawGauge(sbar_x + 16 * scale, sbar_y + 128 * scale, zymsb_crosshair_left2, 64*scale, 80*scale, 68*scale, -66*scale, cl.stats[STAT_NAILS] * (1.0 / 200.0), cl.stats[STAT_ROCKETS] * (1.0 / 200.0), 0.8f,0.8f,0.0f,1.0f, 0.8f,0.5f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); Sbar_DrawGauge(sbar_x + 176 * scale, sbar_y + 48 * scale, zymsb_crosshair_right, 64*scale, 160*scale, 148*scale, -136*scale, cl.stats[STAT_ARMOR] * (1.0 / 300.0), cl.stats[STAT_HEALTH] * (1.0 / 300.0), 0.0f,0.5f,1.0f,1.0f, 1.0f,0.0f,0.0f,1.0f, 0.3f,0.3f,0.3f,1.0f, DRAWFLAG_NORMAL); DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); #else float scale = 128.0f / 256.0f; float healthstart, healthheight, healthstarttc, healthendtc; float shieldstart, shieldheight, shieldstarttc, shieldendtc; float ammostart, ammoheight, ammostarttc, ammoendtc; float clipstart, clipheight, clipstarttc, clipendtc; float kickoffset[3], offset; VectorClear(kickoffset); if (v_dmg_time > 0) { kickoffset[0] = (v_dmg_time/v_kicktime.value*v_dmg_roll) * 10 * scale; kickoffset[1] = (v_dmg_time/v_kicktime.value*v_dmg_pitch) * 10 * scale; } sbar_x = (vid_conwidth.integer - 256 * scale)/2 + kickoffset[0]; sbar_y = (vid_conheight.integer - 256 * scale)/2 + kickoffset[1]; offset = 0; // TODO: offset should be controlled by recoil (question: how to detect firing?) DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + ( 88 - offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 0,0, 1,1,1,1, 1,0, 1,1,1,1, 0,1, 1,1,1,1, 1,1, 1,1,1,1, 0); DrawQ_SuperPic(sbar_x + (132 + offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 0,1, 1,1,1,1, 0,0, 1,1,1,1, 1,1, 1,1,1,1, 1,0, 1,1,1,1, 0); DrawQ_SuperPic(sbar_x + 120 * scale, sbar_y + (132 + offset) * scale, zymsb_crosshair_line, 16 * scale, 36 * scale, 1,1, 1,1,1,1, 0,1, 1,1,1,1, 1,0, 1,1,1,1, 0,0, 1,1,1,1, 0); DrawQ_SuperPic(sbar_x + ( 88 - offset) * scale, sbar_y + 120 * scale, zymsb_crosshair_line, 36 * scale, 16 * scale, 1,0, 1,1,1,1, 1,1, 1,1,1,1, 0,0, 1,1,1,1, 0,1, 1,1,1,1, 0); healthheight = cl.stats[STAT_HEALTH] * (152.0f / 300.0f); shieldheight = cl.stats[STAT_ARMOR] * (152.0f / 300.0f); healthstart = 204 - healthheight; shieldstart = healthstart - shieldheight; healthstarttc = healthstart * (1.0f / 256.0f); healthendtc = (healthstart + healthheight) * (1.0f / 256.0f); shieldstarttc = shieldstart * (1.0f / 256.0f); shieldendtc = (shieldstart + shieldheight) * (1.0f / 256.0f); ammoheight = cl.stats[STAT_SHELLS] * (62.0f / 200.0f); ammostart = 114 - ammoheight; ammostarttc = ammostart * (1.0f / 256.0f); ammoendtc = (ammostart + ammoheight) * (1.0f / 256.0f); clipheight = cl.stats[STAT_AMMO] * (122.0f / 200.0f); clipstart = 190 - clipheight; clipstarttc = clipstart * (1.0f / 256.0f); clipendtc = (clipstart + clipheight) * (1.0f / 256.0f); if (healthheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + healthstart * scale, zymsb_crosshair_health, 256 * scale, healthheight * scale, 0,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 1,healthstarttc, 1.0f,0.0f,0.0f,1.0f, 0,healthendtc, 1.0f,0.0f,0.0f,1.0f, 1,healthendtc, 1.0f,0.0f,0.0f,1.0f, DRAWFLAG_NORMAL); if (shieldheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + shieldstart * scale, zymsb_crosshair_health, 256 * scale, shieldheight * scale, 0,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 1,shieldstarttc, 0.0f,0.5f,1.0f,1.0f, 0,shieldendtc, 0.0f,0.5f,1.0f,1.0f, 1,shieldendtc, 0.0f,0.5f,1.0f,1.0f, DRAWFLAG_NORMAL); if (ammoheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + ammostart * scale, zymsb_crosshair_ammo, 256 * scale, ammoheight * scale, 0,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 1,ammostarttc, 0.8f,0.8f,0.0f,1.0f, 0,ammoendtc, 0.8f,0.8f,0.0f,1.0f, 1,ammoendtc, 0.8f,0.8f,0.0f,1.0f, DRAWFLAG_NORMAL); if (clipheight > 0) DrawQ_SuperPic(sbar_x + 0 * scale, sbar_y + clipstart * scale, zymsb_crosshair_clip, 256 * scale, clipheight * scale, 0,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 1,clipstarttc, 1.0f,1.0f,0.0f,1.0f, 0,clipendtc, 1.0f,1.0f,0.0f,1.0f, 1,clipendtc, 1.0f,1.0f,0.0f,1.0f, DRAWFLAG_NORMAL); DrawQ_Pic(sbar_x + 0 * scale, sbar_y + 0 * scale, zymsb_crosshair_background, 256 * scale, 256 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); DrawQ_Pic(sbar_x + 120 * scale, sbar_y + 120 * scale, zymsb_crosshair_center, 16 * scale, 16 * scale, 1, 1, 1, 1, DRAWFLAG_NORMAL); #endif } else // Quake and others { sbar_x = (vid_conwidth.integer - 320)/2; sbar_y = vid_conheight.integer - SBAR_HEIGHT; // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode //if (cl.gametype == GAME_DEATHMATCH && gamemode != GAME_TRANSFUSION) if (sb_lines > 24) { if (gamemode != GAME_GOODVSBAD2) Sbar_DrawInventory (); if (!cl.islocalgame && gamemode != GAME_TRANSFUSION) Sbar_DrawFrags (); } if (sb_showscores || (cl.stats[STAT_HEALTH] <= 0 && cl_deathscoreboard.integer)) { if (gamemode != GAME_GOODVSBAD2) Sbar_DrawAlphaPic (0, 0, sb_scorebar, sbar_alpha_bg.value); Sbar_DrawScoreboard (); } else if (sb_lines) { Sbar_DrawAlphaPic (0, 0, sb_sbar, sbar_alpha_bg.value); // keys (hipnotic only) //MED 01/04/97 moved keys here so they would not be overwritten if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) { if (cl.stats[STAT_ITEMS] & IT_KEY1) Sbar_DrawPic (209, 3, sb_items[0]); if (cl.stats[STAT_ITEMS] & IT_KEY2) Sbar_DrawPic (209, 12, sb_items[1]); } // armor if (gamemode != GAME_GOODVSBAD2) { if (cl.stats[STAT_ITEMS] & IT_INVULNERABILITY) { Sbar_DrawNum (24, 0, 666, 3, 1); Sbar_DrawPic (0, 0, sb_disc); } else { if (gamemode == GAME_ROGUE) { Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); if (cl.stats[STAT_ITEMS] & RIT_ARMOR3) Sbar_DrawPic (0, 0, sb_armor[2]); else if (cl.stats[STAT_ITEMS] & RIT_ARMOR2) Sbar_DrawPic (0, 0, sb_armor[1]); else if (cl.stats[STAT_ITEMS] & RIT_ARMOR1) Sbar_DrawPic (0, 0, sb_armor[0]); } else { Sbar_DrawNum (24, 0, cl.stats[STAT_ARMOR], 3, cl.stats[STAT_ARMOR] <= 25); if (cl.stats[STAT_ITEMS] & IT_ARMOR3) Sbar_DrawPic (0, 0, sb_armor[2]); else if (cl.stats[STAT_ITEMS] & IT_ARMOR2) Sbar_DrawPic (0, 0, sb_armor[1]); else if (cl.stats[STAT_ITEMS] & IT_ARMOR1) Sbar_DrawPic (0, 0, sb_armor[0]); } } } // face Sbar_DrawFace (); // health Sbar_DrawNum (136, 0, cl.stats[STAT_HEALTH], 3, cl.stats[STAT_HEALTH] <= 25); // ammo icon if (gamemode == GAME_ROGUE) { if (cl.stats[STAT_ITEMS] & RIT_SHELLS) Sbar_DrawPic (224, 0, sb_ammo[0]); else if (cl.stats[STAT_ITEMS] & RIT_NAILS) Sbar_DrawPic (224, 0, sb_ammo[1]); else if (cl.stats[STAT_ITEMS] & RIT_ROCKETS) Sbar_DrawPic (224, 0, sb_ammo[2]); else if (cl.stats[STAT_ITEMS] & RIT_CELLS) Sbar_DrawPic (224, 0, sb_ammo[3]); else if (cl.stats[STAT_ITEMS] & RIT_LAVA_NAILS) Sbar_DrawPic (224, 0, rsb_ammo[0]); else if (cl.stats[STAT_ITEMS] & RIT_PLASMA_AMMO) Sbar_DrawPic (224, 0, rsb_ammo[1]); else if (cl.stats[STAT_ITEMS] & RIT_MULTI_ROCKETS) Sbar_DrawPic (224, 0, rsb_ammo[2]); } else { if (cl.stats[STAT_ITEMS] & IT_SHELLS) Sbar_DrawPic (224, 0, sb_ammo[0]); else if (cl.stats[STAT_ITEMS] & IT_NAILS) Sbar_DrawPic (224, 0, sb_ammo[1]); else if (cl.stats[STAT_ITEMS] & IT_ROCKETS) Sbar_DrawPic (224, 0, sb_ammo[2]); else if (cl.stats[STAT_ITEMS] & IT_CELLS) Sbar_DrawPic (224, 0, sb_ammo[3]); } Sbar_DrawNum (248, 0, cl.stats[STAT_AMMO], 3, cl.stats[STAT_AMMO] <= 10); // LordHavoc: changed to draw the deathmatch overlays in any multiplayer mode if ((!cl.islocalgame || cl.gametype != GAME_COOP)) { if (gamemode == GAME_TRANSFUSION) Sbar_MiniDeathmatchOverlay (0, 0); else Sbar_MiniDeathmatchOverlay (sbar_x + 324, vid_conheight.integer - 8*8); Sbar_Score(24); } } } } if (cl.csqc_vidvars.drawcrosshair && crosshair.integer >= 1 && !cl.intermission && !r_letterbox.value) { pic = Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/crosshair%i", crosshair.integer)); DrawQ_Pic((vid_conwidth.integer - pic->width * crosshair_size.value) * 0.5f, (vid_conheight.integer - pic->height * crosshair_size.value) * 0.5f, pic, pic->width * crosshair_size.value, pic->height * crosshair_size.value, crosshair_color_red.value, crosshair_color_green.value, crosshair_color_blue.value, crosshair_color_alpha.value, 0); } if (cl_prydoncursor.integer > 0) DrawQ_Pic((cl.cmd.cursor_screen[0] + 1) * 0.5 * vid_conwidth.integer, (cl.cmd.cursor_screen[1] + 1) * 0.5 * vid_conheight.integer, Draw_CachePic (va(vabuf, sizeof(vabuf), "gfx/prydoncursor%03i", cl_prydoncursor.integer)), 0, 0, 1, 1, 1, 1, 0); } //============================================================================= /* ================== Sbar_DeathmatchOverlay ================== */ static float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y) { int minutes; qboolean myself = false; unsigned char *c; char vabuf[1024]; minutes = (int)((cl.intermission ? cl.completed_time - s->qw_entertime : cl.time - s->qw_entertime) / 60.0); if((s - cl.scores) == cl.playerentity - 1) myself = true; if((s - teams) >= 0 && (s - teams) < MAX_SCOREBOARD) if((s->colors & 15) == (cl.scores[cl.playerentity - 1].colors & 15)) myself = true; if (cls.protocol == PROTOCOL_QUAKEWORLD) { if (s->qw_spectator) { if (s->qw_ping || s->qw_packetloss) DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %4i spectator %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %4i spectator %c%s", minutes, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { // draw colors behind score // // // // // c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; DrawQ_Fill(x + 14*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // print the text //DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); if (s->qw_ping || s->qw_packetloss) DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %4i %5i %-4s %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %4i %5i %-4s %c%s", minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } } else { if (s->qw_spectator) { if (s->qw_ping || s->qw_packetloss) DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i spect %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " spect %c%s", myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { // draw colors behind score c = palette_rgb_pantsscoreboard[(s->colors & 0xf0) >> 4]; DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+1, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); c = palette_rgb_shirtscoreboard[s->colors & 0xf]; DrawQ_Fill(x + 9*8*FONT_SBAR->maxwidth, y+4, 40*FONT_SBAR->maxwidth, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), sbar_alpha_fg.value, 0); // print the text //DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%c%4i %s", myself ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, true, FONT_DEFAULT); if (s->qw_ping || s->qw_packetloss) DrawQ_String(x, y, va(vabuf, sizeof(vabuf), "%4i %3i %5i %c%s", bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); else DrawQ_String(x, y, va(vabuf, sizeof(vabuf), " %5i %c%s", (int) s->frags, myself ? 13 : ' ', s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } } return 8; } void Sbar_DeathmatchOverlay (void) { int i, y, xmin, xmax, ymin, ymax; char vabuf[1024]; // request new ping times every two second if (cl.last_ping_request < realtime - 2 && cls.netcon) { cl.last_ping_request = realtime; if (cls.protocol == PROTOCOL_QUAKEWORLD) { MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, "pings"); } else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6/* || cls.protocol == PROTOCOL_DARKPLACES7*/) { // these servers usually lack the pings command and so a less efficient "ping" command must be sent, which on modern DP servers will also reply with a pingplreport command after the ping listing static int ping_anyway_counter = 0; if(cl.parsingtextexpectingpingforscores == 1) { Con_DPrintf("want to send ping, but still waiting for other reply\n"); if(++ping_anyway_counter >= 5) cl.parsingtextexpectingpingforscores = 0; } if(cl.parsingtextexpectingpingforscores != 1) { ping_anyway_counter = 0; cl.parsingtextexpectingpingforscores = 1; // hide the output of the next ping report MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, "ping"); } } else { // newer server definitely has pings command, so use it for more efficiency, avoids ping reports spamming the console if they are misparsed, and saves a little bandwidth MSG_WriteByte(&cls.netcon->message, clc_stringcmd); MSG_WriteString(&cls.netcon->message, "pings"); } } // scores Sbar_SortFrags (); ymin = 8; ymax = 40 + 8 + (Sbar_IsTeammatch() ? (teamlines * 8 + 5): 0) + scoreboardlines * 8 - 1; if (cls.protocol == PROTOCOL_QUAKEWORLD) xmin = (int) (vid_conwidth.integer - (26 + 15) * 8 * FONT_SBAR->maxwidth) / 2; // 26 characters until name, then we assume 15 character names (they can be longer but usually aren't) else xmin = (int) (vid_conwidth.integer - (16 + 25) * 8 * FONT_SBAR->maxwidth) / 2; // 16 characters until name, then we assume 25 character names (they can be longer but usually aren't) xmax = vid_conwidth.integer - xmin; if(IS_OLDNEXUIZ_DERIVED(gamemode)) DrawQ_Pic (xmin - 8, ymin - 8, 0, xmax-xmin+1 + 2*8, ymax-ymin+1 + 2*8, 0, 0, 0, sbar_alpha_bg.value, 0); DrawQ_Pic ((vid_conwidth.integer - sb_ranking->width)/2, 8, sb_ranking, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); // draw the text y = 40; if (cls.protocol == PROTOCOL_QUAKEWORLD) { DrawQ_String(xmin, y, va(vabuf, sizeof(vabuf), "ping pl%% time frags team name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } else { DrawQ_String(xmin, y, va(vabuf, sizeof(vabuf), "ping pl%% frags name"), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL, false, FONT_SBAR ); } y += 8; if (Sbar_IsTeammatch ()) { // show team scores first for (i = 0;i < teamlines && y < vid_conheight.integer;i++) y += (int)Sbar_PrintScoreboardItem((teams + teamsort[i]), xmin, y); y += 5; } for (i = 0;i < scoreboardlines && y < vid_conheight.integer;i++) y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], xmin, y); } /* ================== Sbar_MiniDeathmatchOverlay ================== */ void Sbar_MiniDeathmatchOverlay (int x, int y) { int i, j, numlines, range_begin, range_end, myteam, teamsep; // do not draw this if sbar_miniscoreboard_size is zero if(sbar_miniscoreboard_size.value == 0) return; // adjust the given y if sbar_miniscoreboard_size doesn't indicate default (< 0) if(sbar_miniscoreboard_size.value > 0) y = (int) (vid_conheight.integer - sbar_miniscoreboard_size.value * 8); // scores Sbar_SortFrags (); // decide where to print if (gamemode == GAME_TRANSFUSION) numlines = (vid_conwidth.integer - x + 127) / 128; else numlines = (vid_conheight.integer - y + 7) / 8; // give up if there isn't room if (x >= vid_conwidth.integer || y >= vid_conheight.integer || numlines < 1) return; //find us for (i = 0; i < scoreboardlines; i++) if (fragsort[i] == cl.playerentity - 1) break; range_begin = 0; range_end = scoreboardlines; teamsep = 0; if (gamemode != GAME_TRANSFUSION) if (Sbar_IsTeammatch ()) { // reserve space for the team scores numlines -= teamlines; // find first and last player of my team (only draw the team totals and my own team) range_begin = range_end = i; myteam = cl.scores[fragsort[i]].colors & 15; while(range_begin > 0 && (cl.scores[fragsort[range_begin-1]].colors & 15) == myteam) --range_begin; while(range_end < scoreboardlines && (cl.scores[fragsort[range_end]].colors & 15) == myteam) ++range_end; // looks better than two players if(numlines == 2) { teamsep = 8; numlines = 1; } } // figure out start i -= numlines/2; i = min(i, range_end - numlines); i = max(i, range_begin); if (gamemode == GAME_TRANSFUSION) { for (;i < range_end && x < vid_conwidth.integer;i++) x += 128 + (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); } else { if(range_end - i < numlines) // won't draw to bottom? y += 8 * (numlines - (range_end - i)); // bottom align // show team scores first for (j = 0;j < teamlines && y < vid_conheight.integer;j++) y += (int)Sbar_PrintScoreboardItem((teams + teamsort[j]), x, y); y += teamsep; for (;i < range_end && y < vid_conheight.integer;i++) y += (int)Sbar_PrintScoreboardItem(cl.scores + fragsort[i], x, y); } } static int Sbar_TeamColorCompare(const void *t1_, const void *t2_) { static int const sortorder[16] = { 1001, 1002, 1003, 1004, 1, // red 1005, 1006, 1007, 1008, 4, // pink 1009, 1010, 3, // yellow 2, // blue 1011, 1012 }; const scoreboard_t *t1 = *(scoreboard_t **) t1_; const scoreboard_t *t2 = *(scoreboard_t **) t2_; int tc1 = sortorder[t1->colors & 15]; int tc2 = sortorder[t2->colors & 15]; return tc1 - tc2; } void Sbar_Score (int margin) { int i, me, score, otherleader, place, distribution, minutes, seconds; double timeleft; int sbar_x_save = sbar_x; int sbar_y_save = sbar_y; sbar_y = (int) (vid_conheight.value - (32+12)); sbar_x -= margin; me = cl.playerentity - 1; if (sbar_scorerank.integer && me >= 0 && me < cl.maxclients) { if(Sbar_IsTeammatch()) { // Layout: // // team1 team3 team4 // // TEAM2 scoreboard_t *teamcolorsort[16]; Sbar_SortFrags(); for(i = 0; i < teamlines; ++i) teamcolorsort[i] = &(teams[i]); // Now sort them by color qsort(teamcolorsort, teamlines, sizeof(*teamcolorsort), Sbar_TeamColorCompare); // : margin // -12*4: four digits space place = (teamlines - 1) * (-12 * 4); for(i = 0; i < teamlines; ++i) { int cindex = teamcolorsort[i]->colors & 15; unsigned char *c = palette_rgb_shirtscoreboard[cindex]; float cm = max(max(c[0], c[1]), c[2]); float cr = c[0] / cm; float cg = c[1] / cm; float cb = c[2] / cm; if(cindex == (cl.scores[cl.playerentity - 1].colors & 15)) // my team { Sbar_DrawXNum(-32*4, 0, teamcolorsort[i]->frags, 4, 32, cr, cg, cb, 1, 0); } else // other team { Sbar_DrawXNum(place, -12, teamcolorsort[i]->frags, 4, 12, cr, cg, cb, 1, 0); place += 4 * 12; } } } else { // Layout: // // leading place // // FRAGS // // find leading score other than ourselves, to calculate distribution // find our place in the scoreboard score = cl.scores[me].frags; for (i = 0, otherleader = -1, place = 1;i < cl.maxclients;i++) { if (cl.scores[i].name[0] && i != me) { if (otherleader == -1 || cl.scores[i].frags > cl.scores[otherleader].frags) otherleader = i; if (score < cl.scores[i].frags || (score == cl.scores[i].frags && i < me)) place++; } } distribution = otherleader >= 0 ? score - cl.scores[otherleader].frags : 0; if (place == 1) Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 1, 1, 0); else if (place == 2) Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 1, 0, 1, 0); else Sbar_DrawXNum(-3*12, -12, place, 3, 12, 1, 0, 0, 1, 0); if (otherleader < 0) Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); if (distribution >= 0) { Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 1, 1, 0); Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 1, 1, 0); } else if (distribution >= -5) { Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 1, 0, 1, 0); Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 1, 0, 1, 0); } else { Sbar_DrawXNum(-7*12, -12, distribution, 4, 12, 1, 0, 0, 1, 0); Sbar_DrawXNum(-32*4, 0, score, 4, 32, 1, 0, 0, 1, 0); } } } if (sbar_gametime.integer && cl.statsf[STAT_TIMELIMIT]) { timeleft = max(0, cl.statsf[STAT_TIMELIMIT] * 60 - cl.time); minutes = (int)floor(timeleft / 60); seconds = (int)(floor(timeleft) - minutes * 60); if (minutes >= 5) { Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); if(sb_colon && sb_colon->tex != r_texture_notexture) DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); } else if (minutes >= 1) { Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 0, 1, 0); if(sb_colon && sb_colon->tex != r_texture_notexture) DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 0, sbar_alpha_fg.value, 0); Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 0, 1, 0); } else if ((int)(timeleft * 4) & 1) Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); else Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 0, 0, 1, 0); } else if (sbar_gametime.integer) { minutes = (int)floor(cl.time / 60); seconds = (int)(floor(cl.time) - minutes * 60); Sbar_DrawXNum(-12*6, 32, minutes, 3, 12, 1, 1, 1, 1, 0); if(sb_colon && sb_colon->tex != r_texture_notexture) DrawQ_Pic(sbar_x + -12*3, sbar_y + 32, sb_colon, 12, 12, 1, 1, 1, sbar_alpha_fg.value, 0); Sbar_DrawXNum(-12*2, 32, seconds, -2, 12, 1, 1, 1, 1, 0); } sbar_x = sbar_x_save; sbar_y = sbar_y_save; } /* ================== Sbar_IntermissionOverlay ================== */ void Sbar_IntermissionOverlay (void) { int dig; int num; if (cl.gametype == GAME_DEATHMATCH) { Sbar_DeathmatchOverlay (); return; } sbar_x = (vid_conwidth.integer - 320) >> 1; sbar_y = (vid_conheight.integer - 200) >> 1; DrawQ_Pic (sbar_x + 64, sbar_y + 24, sb_complete, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); DrawQ_Pic (sbar_x + 0, sbar_y + 56, sb_inter, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); // time dig = (int)cl.completed_time / 60; Sbar_DrawNum (160, 64, dig, 3, 0); num = (int)cl.completed_time - dig*60; Sbar_DrawPic (234,64,sb_colon); Sbar_DrawPic (246,64,sb_nums[0][num/10]); Sbar_DrawPic (266,64,sb_nums[0][num%10]); // LA: Display as "a" instead of "a/b" if b is 0 if(cl.stats[STAT_TOTALSECRETS]) { Sbar_DrawNum (160, 104, cl.stats[STAT_SECRETS], 3, 0); if (!IS_OLDNEXUIZ_DERIVED(gamemode)) Sbar_DrawPic (232, 104, sb_slash); Sbar_DrawNum (240, 104, cl.stats[STAT_TOTALSECRETS], 3, 0); } else { Sbar_DrawNum (240, 104, cl.stats[STAT_SECRETS], 3, 0); } if(cl.stats[STAT_TOTALMONSTERS]) { Sbar_DrawNum (160, 144, cl.stats[STAT_MONSTERS], 3, 0); if (!IS_OLDNEXUIZ_DERIVED(gamemode)) Sbar_DrawPic (232, 144, sb_slash); Sbar_DrawNum (240, 144, cl.stats[STAT_TOTALMONSTERS], 3, 0); } else { Sbar_DrawNum (240, 144, cl.stats[STAT_MONSTERS], 3, 0); } } /* ================== Sbar_FinaleOverlay ================== */ void Sbar_FinaleOverlay (void) { DrawQ_Pic((vid_conwidth.integer - sb_finale->width)/2, 16, sb_finale, 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0); } darkplaces/cap_ogg.h0000664000175000017500000000025313067716216013740 0ustar kalevkalevvoid SCR_CaptureVideo_Ogg_Init(void); qboolean SCR_CaptureVideo_Ogg_Available(void); void SCR_CaptureVideo_Ogg_BeginVideo(void); void SCR_CaptureVideo_Ogg_CloseDLL(void); darkplaces/palette.h0000664000175000017500000000322513067716222013776 0ustar kalevkalev #ifndef PALLETE_H #define PALLETE_H #define PALETTEFEATURE_STANDARD 1 #define PALETTEFEATURE_REVERSED 2 #define PALETTEFEATURE_PANTS 4 #define PALETTEFEATURE_SHIRT 8 #define PALETTEFEATURE_GLOW 16 #define PALETTEFEATURE_ZERO 32 #define PALETTEFEATURE_TRANSPARENT 128 extern unsigned char palette_rgb[256][3]; extern unsigned char palette_rgb_pantscolormap[16][3]; extern unsigned char palette_rgb_shirtcolormap[16][3]; extern unsigned char palette_rgb_pantsscoreboard[16][3]; extern unsigned char palette_rgb_shirtscoreboard[16][3]; extern unsigned int palette_bgra_complete[256]; extern unsigned int palette_bgra_font[256]; extern unsigned int palette_bgra_alpha[256]; extern unsigned int palette_bgra_nocolormap[256]; extern unsigned int palette_bgra_nocolormapnofullbrights[256]; extern unsigned int palette_bgra_nofullbrights[256]; extern unsigned int palette_bgra_nofullbrights_transparent[256]; extern unsigned int palette_bgra_onlyfullbrights[256]; extern unsigned int palette_bgra_onlyfullbrights_transparent[256]; extern unsigned int palette_bgra_pantsaswhite[256]; extern unsigned int palette_bgra_shirtaswhite[256]; extern unsigned int palette_bgra_transparent[256]; extern unsigned int palette_bgra_embeddedpic[256]; extern unsigned char palette_featureflags[256]; extern unsigned int q2palette_bgra_complete[256]; // used by hardware gamma functions in vid_* files void BuildGammaTable8(float prescale, float gamma, float scale, float base, float contrastboost, unsigned char *out, int rampsize); void BuildGammaTable16(float prescale, float gamma, float scale, float base, float contrastboost, unsigned short *out, int rampsize); void Palette_Init(void); #endif darkplaces/darkplaces-wgl-vs2013.vcxproj0000664000175000017500000004505513067716220017445 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28} darkplaceswgl Win32Proj darkplaces-wgl-vs2013 Application v120 MultiByte true Application v120 MultiByte Application v120 MultiByte true Application v120 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX86 X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level3 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/vid_null.c0000664000175000017500000000400413067716222014143 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include int cl_available = false; qboolean vid_supportrefreshrate = false; void VID_Shutdown(void) { } static void signal_handler(int sig) { Con_Printf("Received signal %d, exiting...\n", sig); Sys_Quit(1); } static void InitSig(void) { #ifndef WIN32 signal(SIGHUP, signal_handler); signal(SIGINT, signal_handler); signal(SIGQUIT, signal_handler); signal(SIGILL, signal_handler); signal(SIGTRAP, signal_handler); signal(SIGIOT, signal_handler); signal(SIGBUS, signal_handler); signal(SIGFPE, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGTERM, signal_handler); #endif } void VID_SetMouse (qboolean fullscreengrab, qboolean relative, qboolean hidecursor) { } void VID_Finish (void) { } int VID_SetGamma(unsigned short *ramps, int rampsize) { return FALSE; } int VID_GetGamma(unsigned short *ramps, int rampsize) { return FALSE; } void VID_Init(void) { InitSig(); // trap evil signals } qboolean VID_InitMode(viddef_mode_t *mode) { return false; } void *GL_GetProcAddress(const char *name) { return NULL; } void Sys_SendKeyEvents(void) { } void VID_BuildJoyState(vid_joystate_t *joystate) { } void IN_Move(void) { } vid_mode_t *VID_GetDesktopMode(void) { return NULL; } size_t VID_ListModes(vid_mode_t *modes, size_t maxcount) { return 0; } darkplaces/cvar.h0000664000175000017500000001744513067716216013307 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cvar.h /* cvar_t variables are used to hold scalar or string variables that can be changed or displayed at the console or prog code as well as accessed directly in C code. it is sufficient to initialize a cvar_t with just the first two fields, or you can add a ,true flag for variables that you want saved to the configuration file when the game is quit: cvar_t r_draworder = {"r_draworder","1"}; cvar_t scr_screensize = {"screensize","1",true}; Cvars must be registered before use, or they will have a 0 value instead of the float interpretation of the string. Generally, all cvar_t declarations should be registered in the apropriate init function before any console commands are executed: Cvar_RegisterVariable (&host_framerate); C code usually just references a cvar in place: if ( r_draworder.value ) It could optionally ask for the value to be looked up for a string name: if (Cvar_VariableValue ("r_draworder")) Interpreted prog code can access cvars with the cvar(name) or cvar_set (name, value) internal functions: teamplay = cvar("teamplay"); cvar_set ("registered", "1"); The user can access cvars from the console in two ways: r_draworder prints the current value r_draworder 0 sets the current value to 0 Cvars are restricted from having the same names as commands to keep this interface from being ambiguous. */ #ifndef CVAR_H #define CVAR_H // cvar flags #define CVAR_SAVE 1 #define CVAR_NOTIFY 2 #define CVAR_READONLY 4 #define CVAR_SERVERINFO 8 #define CVAR_USERINFO 16 // CVAR_PRIVATE means do not $ expand or sendcvar this cvar under any circumstances (rcon_password uses this) #define CVAR_PRIVATE 32 // this means that this cvar should update a userinfo key but the name does not correspond directly to the userinfo key to update, and may require additional conversion ("_cl_color" for example should update "topcolor" and "bottomcolor") #define CVAR_NQUSERINFOHACK 64 // used to determine if flags is valid #define CVAR_NORESETTODEFAULTS 128 // for engine-owned cvars that must not be reset on gametype switch (e.g. scr_screenshot_name, which otherwise isn't set to the mod name properly) #define CVAR_MAXFLAGSVAL 255 // for internal use only! #define CVAR_DEFAULTSET (1<<30) #define CVAR_ALLOCATED (1<<31) /* // type of a cvar for menu purposes #define CVARMENUTYPE_FLOAT 1 #define CVARMENUTYPE_INTEGER 2 #define CVARMENUTYPE_SLIDER 3 #define CVARMENUTYPE_BOOL 4 #define CVARMENUTYPE_STRING 5 #define CVARMENUTYPE_OPTION 6 // which menu to put a cvar in #define CVARMENU_GRAPHICS 1 #define CVARMENU_SOUND 2 #define CVARMENU_INPUT 3 #define CVARMENU_NETWORK 4 #define CVARMENU_SERVER 5 #define MAX_CVAROPTIONS 16 typedef struct cvaroption_s { int value; const char *name; } cvaroption_t; typedef struct menucvar_s { int type; float valuemin, valuemax, valuestep; int numoptions; cvaroption_t optionlist[MAX_CVAROPTIONS]; } menucvar_t; */ typedef struct cvar_s { int flags; const char *name; const char *string; const char *description; int integer; float value; float vector[3]; const char *defstring; // values at init (for Cvar_RestoreInitState) qboolean initstate; // indicates this existed at init int initflags; const char *initstring; const char *initdescription; int initinteger; float initvalue; float initvector[3]; const char *initdefstring; int globaldefindex[3]; int globaldefindex_stringno[3]; //menucvar_t menuinfo; struct cvar_s *next; struct cvar_s *nextonhashchain; } cvar_t; /* void Cvar_MenuSlider(cvar_t *variable, int menu, float slider_min, float slider_max, float slider_step); void Cvar_MenuBool(cvar_t *variable, int menu, const char *name_false, const char *name_true); void Cvar_MenuFloat(cvar_t *variable, int menu, float range_min, float range_max); void Cvar_MenuInteger(cvar_t *variable, int menu, int range_min, int range_max); void Cvar_MenuString(cvar_t *variable, int menu); void Cvar_MenuOption(cvar_t *variable, int menu, int value[16], const char *name[16]); */ /// registers a cvar that already has the name, string, and optionally the /// archive elements set. void Cvar_RegisterVariable (cvar_t *variable); /// equivelant to " " typed at the console void Cvar_Set (const char *var_name, const char *value); /// expands value to a string and calls Cvar_Set void Cvar_SetValue (const char *var_name, float value); void Cvar_SetQuick (cvar_t *var, const char *value); void Cvar_SetValueQuick (cvar_t *var, float value); float Cvar_VariableValueOr (const char *var_name, float def); // returns def if not defined float Cvar_VariableValue (const char *var_name); // returns 0 if not defined or non numeric const char *Cvar_VariableStringOr (const char *var_name, const char *def); // returns def if not defined const char *Cvar_VariableString (const char *var_name); // returns an empty string if not defined const char *Cvar_VariableDefString (const char *var_name); // returns an empty string if not defined const char *Cvar_VariableDescription (const char *var_name); // returns an empty string if not defined const char *Cvar_CompleteVariable (const char *partial); // attempts to match a partial variable name for command line completion // returns NULL if nothing fits void Cvar_CompleteCvarPrint (const char *partial); qboolean Cvar_Command (void); // called by Cmd_ExecuteString when Cmd_Argv(0) doesn't match a known // command. Returns true if the command was a variable reference that // was handled. (print or change) void Cvar_SaveInitState(void); void Cvar_RestoreInitState(void); void Cvar_UnlockDefaults (void); void Cvar_LockDefaults_f (void); void Cvar_ResetToDefaults_All_f (void); void Cvar_ResetToDefaults_NoSaveOnly_f (void); void Cvar_ResetToDefaults_SaveOnly_f (void); void Cvar_WriteVariables (qfile_t *f); // Writes lines containing "set variable value" for all variables // with the archive flag set to true. cvar_t *Cvar_FindVar (const char *var_name); cvar_t *Cvar_FindVarAfter (const char *prev_var_name, int neededflags); int Cvar_CompleteCountPossible (const char *partial); const char **Cvar_CompleteBuildList (const char *partial); // Added by EvilTypeGuy - functions for tab completion system // Thanks to Fett erich@heintz.com // Thanks to taniwha /// Prints a list of Cvars including a count of them to the user console /// Referenced in cmd.c in Cmd_Init hence it's inclusion here. /// Added by EvilTypeGuy eviltypeguy@qeradiant.com /// Thanks to Matthias "Maddes" Buecher, http://www.inside3d.com/qip/ void Cvar_List_f (void); void Cvar_Set_f (void); void Cvar_SetA_f (void); void Cvar_Del_f (void); // commands to create new cvars (or set existing ones) // seta creates an archived cvar (saved to config) /// allocates a cvar by name and returns its address, /// or merely sets its value if it already exists. cvar_t *Cvar_Get (const char *name, const char *value, int flags, const char *newdescription); extern const char *cvar_dummy_description; // ALWAYS the same pointer extern cvar_t *cvar_vars; // used to list all cvars void Cvar_UpdateAllAutoCvars(void); // updates ALL autocvars of the active prog to the cvar values (savegame loading) #ifdef FILLALLCVARSWITHRUBBISH void Cvar_FillAll_f(); #endif /* FILLALLCVARSWITHRUBBISH */ #endif darkplaces/cmd.h0000664000175000017500000001462213067716216013111 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cmd.h -- Command buffer and command execution //=========================================================================== /* Any number of commands can be added in a frame, from several different sources. Most commands come from either keybindings or console line input, but remote servers can also send across commands and entire text files can be execed. The + command line options are also added to the command buffer. The game starts with a Cbuf_AddText ("exec quake.rc\n"); Cbuf_Execute (); */ #ifndef CMD_H #define CMD_H extern void *cmd_text_mutex; #define Cbuf_LockThreadMutex() (void)(cmd_text_mutex ? Thread_LockMutex(cmd_text_mutex) : 0) #define Cbuf_UnlockThreadMutex() (void)(cmd_text_mutex ? Thread_UnlockMutex(cmd_text_mutex) : 0) /// allocates an initial text buffer that will grow as needed void Cbuf_Init (void); void Cmd_Init_Commands (void); void Cbuf_Shutdown (void); /*! as new commands are generated from the console or keybindings, * the text is added to the end of the command buffer. */ void Cbuf_AddText (const char *text); /*! when a command wants to issue other commands immediately, the text is * inserted at the beginning of the buffer, before any remaining unexecuted * commands. */ void Cbuf_InsertText (const char *text); /*! Pulls off terminated lines of text from the command buffer and sends * them through Cmd_ExecuteString. Stops when the buffer is empty. * Normally called once per frame, but may be explicitly invoked. * \note Do not call inside a command function! */ void Cbuf_Execute (void); /*! Performs deferred commands and runs Cbuf_Execute, called by Host_Main */ void Cbuf_Frame (void); //=========================================================================== /* Command execution takes a null terminated string, breaks it into tokens, then searches for a command or variable that matches the first token. Commands can come from three sources, but the handler functions may choose to dissallow the action or forward it to a remote server if the source is not apropriate. */ typedef void (*xcommand_t) (void); typedef enum { src_client, ///< came in over a net connection as a clc_stringcmd ///< host_client will be valid during this state. src_command ///< from the command buffer } cmd_source_t; extern cmd_source_t cmd_source; void Cmd_Init (void); void Cmd_Shutdown (void); // called by Host_Init, this marks cvars, commands and aliases with their init values void Cmd_SaveInitState (void); // called by FS_GameDir_f, this restores cvars, commands and aliases to init values void Cmd_RestoreInitState (void); void Cmd_AddCommand_WithClientCommand (const char *cmd_name, xcommand_t consolefunction, xcommand_t clientfunction, const char *description); void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *description); // called by the init functions of other parts of the program to // register commands and functions to call for them. // The cmd_name is referenced later, so it should not be in temp memory /// used by the cvar code to check for cvar / command name overlap qboolean Cmd_Exists (const char *cmd_name); /// attempts to match a partial command for automatic command line completion /// returns NULL if nothing fits const char *Cmd_CompleteCommand (const char *partial); int Cmd_CompleteAliasCountPossible (const char *partial); const char **Cmd_CompleteAliasBuildList (const char *partial); int Cmd_CompleteCountPossible (const char *partial); const char **Cmd_CompleteBuildList (const char *partial); void Cmd_CompleteCommandPrint (const char *partial); const char *Cmd_CompleteAlias (const char *partial); void Cmd_CompleteAliasPrint (const char *partial); // Enhanced console completion by Fett erich@heintz.com // Added by EvilTypeGuy eviltypeguy@qeradiant.com int Cmd_Argc (void); const char *Cmd_Argv (int arg); const char *Cmd_Args (void); // The functions that execute commands get their parameters with these // functions. Cmd_Argv () will return an empty string, not a NULL // if arg > argc, so string operations are always safe. /// Returns the position (1 to argc-1) in the command's argument list /// where the given parameter apears, or 0 if not present int Cmd_CheckParm (const char *parm); //void Cmd_TokenizeString (char *text); // Takes a null terminated string. Does not need to be /n terminated. // breaks the string up into arg tokens. /// Parses a single line of text into arguments and tries to execute it. /// The text can come from the command buffer, a remote client, or stdin. void Cmd_ExecuteString (const char *text, cmd_source_t src, qboolean lockmutex); /// adds the string as a clc_stringcmd to the client message. /// (used when there is no reason to generate a local command to do it) void Cmd_ForwardStringToServer (const char *s); /// adds the current command line as a clc_stringcmd to the client message. /// things like godmode, noclip, etc, are commands directed to the server, /// so when they are typed in at the console, they will need to be forwarded. void Cmd_ForwardToServer (void); /// used by command functions to send output to either the graphics console or /// passed as a print message to the client void Cmd_Print(const char *text); /// quotes a string so that it can be used as a command argument again; /// quoteset is a string that contains one or more of ", \, $ and specifies /// the characters to be quoted (you usually want to either pass "\"\\" or /// "\"\\$"). Returns true on success, and false on overrun (in which case out /// will contain a part of the quoted string). If putquotes is set, the /// enclosing quote marks are also put. qboolean Cmd_QuoteString(char *out, size_t outlen, const char *in, const char *quoteset, qboolean putquotes); void Cmd_ClearCsqcFuncs (void); #endif darkplaces/ft2_defs.h0000664000175000017500000003512213067716220014033 0ustar kalevkalev/* FreeType 2 definitions from the freetype header mostly. */ #ifndef FT2_DEFS_H_H__ #define FT2_DEFS_H_H__ #ifdef _MSC_VER typedef __int32 FT_Int32; typedef unsigned __int32 FT_UInt32; #else # include typedef int32_t FT_Int32; typedef uint32_t FT_UInt32; #endif typedef int FT_Error; typedef signed char FT_Char; typedef unsigned char FT_Byte; typedef const FT_Byte *FT_Bytes; typedef char FT_String; typedef signed short FT_Short; typedef unsigned short FT_UShort; typedef signed int FT_Int; typedef unsigned int FT_UInt; typedef signed long FT_Long; typedef signed long FT_Fixed; typedef unsigned long FT_ULong; typedef void *FT_Pointer; typedef size_t FT_Offset; typedef signed long FT_F26Dot6; typedef void *FT_Stream; typedef void *FT_Module; typedef void *FT_Library; typedef struct FT_FaceRec_ *FT_Face; typedef struct FT_CharMapRec_* FT_CharMap; typedef struct FT_SizeRec_* FT_Size; typedef struct FT_Size_InternalRec_* FT_Size_Internal; typedef struct FT_GlyphSlotRec_* FT_GlyphSlot; typedef struct FT_SubGlyphRec_* FT_SubGlyph; typedef struct FT_Slot_InternalRec_* FT_Slot_Internal; // Taken from the freetype headers: typedef signed long FT_Pos; typedef struct FT_Vector_ { FT_Pos x; FT_Pos y; } FT_Vector; typedef struct FT_BBox_ { FT_Pos xMin, yMin; FT_Pos xMax, yMax; } FT_BBox; typedef enum FT_Pixel_Mode_ { FT_PIXEL_MODE_NONE = 0, FT_PIXEL_MODE_MONO, FT_PIXEL_MODE_GRAY, FT_PIXEL_MODE_GRAY2, FT_PIXEL_MODE_GRAY4, FT_PIXEL_MODE_LCD, FT_PIXEL_MODE_LCD_V, FT_PIXEL_MODE_MAX /* do not remove */ } FT_Pixel_Mode; typedef enum FT_Render_Mode_ { FT_RENDER_MODE_NORMAL = 0, FT_RENDER_MODE_LIGHT, FT_RENDER_MODE_MONO, FT_RENDER_MODE_LCD, FT_RENDER_MODE_LCD_V, FT_RENDER_MODE_MAX } FT_Render_Mode; #define ft_pixel_mode_none FT_PIXEL_MODE_NONE #define ft_pixel_mode_mono FT_PIXEL_MODE_MONO #define ft_pixel_mode_grays FT_PIXEL_MODE_GRAY #define ft_pixel_mode_pal2 FT_PIXEL_MODE_GRAY2 #define ft_pixel_mode_pal4 FT_PIXEL_MODE_GRAY4 typedef struct FT_Bitmap_ { int rows; int width; int pitch; unsigned char* buffer; short num_grays; char pixel_mode; char palette_mode; void* palette; } FT_Bitmap; typedef struct FT_Outline_ { short n_contours; /* number of contours in glyph */ short n_points; /* number of points in the glyph */ FT_Vector* points; /* the outline's points */ char* tags; /* the points flags */ short* contours; /* the contour end points */ int flags; /* outline masks */ } FT_Outline; #define FT_OUTLINE_NONE 0x0 #define FT_OUTLINE_OWNER 0x1 #define FT_OUTLINE_EVEN_ODD_FILL 0x2 #define FT_OUTLINE_REVERSE_FILL 0x4 #define FT_OUTLINE_IGNORE_DROPOUTS 0x8 #define FT_OUTLINE_SMART_DROPOUTS 0x10 #define FT_OUTLINE_INCLUDE_STUBS 0x20 #define FT_OUTLINE_HIGH_PRECISION 0x100 #define FT_OUTLINE_SINGLE_PASS 0x200 #define ft_outline_none FT_OUTLINE_NONE #define ft_outline_owner FT_OUTLINE_OWNER #define ft_outline_even_odd_fill FT_OUTLINE_EVEN_ODD_FILL #define ft_outline_reverse_fill FT_OUTLINE_REVERSE_FILL #define ft_outline_ignore_dropouts FT_OUTLINE_IGNORE_DROPOUTS #define ft_outline_high_precision FT_OUTLINE_HIGH_PRECISION #define ft_outline_single_pass FT_OUTLINE_SINGLE_PASS #define FT_CURVE_TAG( flag ) ( flag & 3 ) #define FT_CURVE_TAG_ON 1 #define FT_CURVE_TAG_CONIC 0 #define FT_CURVE_TAG_CUBIC 2 #define FT_CURVE_TAG_TOUCH_X 8 /* reserved for the TrueType hinter */ #define FT_CURVE_TAG_TOUCH_Y 16 /* reserved for the TrueType hinter */ #define FT_CURVE_TAG_TOUCH_BOTH ( FT_CURVE_TAG_TOUCH_X | \ FT_CURVE_TAG_TOUCH_Y ) #define FT_Curve_Tag_On FT_CURVE_TAG_ON #define FT_Curve_Tag_Conic FT_CURVE_TAG_CONIC #define FT_Curve_Tag_Cubic FT_CURVE_TAG_CUBIC #define FT_Curve_Tag_Touch_X FT_CURVE_TAG_TOUCH_X #define FT_Curve_Tag_Touch_Y FT_CURVE_TAG_TOUCH_Y typedef int (*FT_Outline_MoveToFunc)( const FT_Vector* to, void* user ); #define FT_Outline_MoveTo_Func FT_Outline_MoveToFunc typedef int (*FT_Outline_LineToFunc)( const FT_Vector* to, void* user ); #define FT_Outline_LineTo_Func FT_Outline_LineToFunc typedef int (*FT_Outline_ConicToFunc)( const FT_Vector* control, const FT_Vector* to, void* user ); #define FT_Outline_ConicTo_Func FT_Outline_ConicToFunc typedef int (*FT_Outline_CubicToFunc)( const FT_Vector* control1, const FT_Vector* control2, const FT_Vector* to, void* user ); #define FT_Outline_CubicTo_Func FT_Outline_CubicToFunc typedef struct FT_Outline_Funcs_ { FT_Outline_MoveToFunc move_to; FT_Outline_LineToFunc line_to; FT_Outline_ConicToFunc conic_to; FT_Outline_CubicToFunc cubic_to; int shift; FT_Pos delta; } FT_Outline_Funcs; #ifndef FT_IMAGE_TAG #define FT_IMAGE_TAG( value, _x1, _x2, _x3, _x4 ) \ value = ( ( (unsigned long)_x1 << 24 ) | \ ( (unsigned long)_x2 << 16 ) | \ ( (unsigned long)_x3 << 8 ) | \ (unsigned long)_x4 ) #endif /* FT_IMAGE_TAG */ typedef enum FT_Glyph_Format_ { FT_IMAGE_TAG( FT_GLYPH_FORMAT_NONE, 0, 0, 0, 0 ), FT_IMAGE_TAG( FT_GLYPH_FORMAT_COMPOSITE, 'c', 'o', 'm', 'p' ), FT_IMAGE_TAG( FT_GLYPH_FORMAT_BITMAP, 'b', 'i', 't', 's' ), FT_IMAGE_TAG( FT_GLYPH_FORMAT_OUTLINE, 'o', 'u', 't', 'l' ), FT_IMAGE_TAG( FT_GLYPH_FORMAT_PLOTTER, 'p', 'l', 'o', 't' ) } FT_Glyph_Format; #define ft_glyph_format_none FT_GLYPH_FORMAT_NONE #define ft_glyph_format_composite FT_GLYPH_FORMAT_COMPOSITE #define ft_glyph_format_bitmap FT_GLYPH_FORMAT_BITMAP #define ft_glyph_format_outline FT_GLYPH_FORMAT_OUTLINE #define ft_glyph_format_plotter FT_GLYPH_FORMAT_PLOTTER typedef struct FT_Glyph_Metrics_ { FT_Pos width; FT_Pos height; FT_Pos horiBearingX; FT_Pos horiBearingY; FT_Pos horiAdvance; FT_Pos vertBearingX; FT_Pos vertBearingY; FT_Pos vertAdvance; } FT_Glyph_Metrics; #define FT_EXPORT( x ) x #define FT_OPEN_MEMORY 0x1 #define FT_OPEN_STREAM 0x2 #define FT_OPEN_PATHNAME 0x4 #define FT_OPEN_DRIVER 0x8 #define FT_OPEN_PARAMS 0x10 typedef struct FT_Parameter_ { FT_ULong tag; FT_Pointer data; } FT_Parameter; typedef struct FT_Open_Args_ { FT_UInt flags; const FT_Byte* memory_base; FT_Long memory_size; FT_String* pathname; FT_Stream stream; FT_Module driver; FT_Int num_params; FT_Parameter* params; } FT_Open_Args; typedef enum FT_Size_Request_Type_ { FT_SIZE_REQUEST_TYPE_NOMINAL, FT_SIZE_REQUEST_TYPE_REAL_DIM, FT_SIZE_REQUEST_TYPE_BBOX, FT_SIZE_REQUEST_TYPE_CELL, FT_SIZE_REQUEST_TYPE_SCALES, FT_SIZE_REQUEST_TYPE_MAX } FT_Size_Request_Type; typedef struct FT_Size_RequestRec_ { FT_Size_Request_Type type; FT_Long width; FT_Long height; FT_UInt horiResolution; FT_UInt vertResolution; } FT_Size_RequestRec; typedef struct FT_Size_RequestRec_ *FT_Size_Request; #define FT_LOAD_DEFAULT 0x0 #define FT_LOAD_NO_SCALE 0x1 #define FT_LOAD_NO_HINTING 0x2 #define FT_LOAD_RENDER 0x4 #define FT_LOAD_NO_BITMAP 0x8 #define FT_LOAD_VERTICAL_LAYOUT 0x10 #define FT_LOAD_FORCE_AUTOHINT 0x20 #define FT_LOAD_CROP_BITMAP 0x40 #define FT_LOAD_PEDANTIC 0x80 #define FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH 0x200 #define FT_LOAD_NO_RECURSE 0x400 #define FT_LOAD_IGNORE_TRANSFORM 0x800 #define FT_LOAD_MONOCHROME 0x1000 #define FT_LOAD_LINEAR_DESIGN 0x2000 #define FT_LOAD_NO_AUTOHINT 0x8000U #define FT_LOAD_TARGET_( x ) ( (FT_Int32)( (x) & 15 ) << 16 ) #define FT_LOAD_TARGET_NORMAL FT_LOAD_TARGET_( FT_RENDER_MODE_NORMAL ) #define FT_LOAD_TARGET_LIGHT FT_LOAD_TARGET_( FT_RENDER_MODE_LIGHT ) #define FT_LOAD_TARGET_MONO FT_LOAD_TARGET_( FT_RENDER_MODE_MONO ) #define FT_LOAD_TARGET_LCD FT_LOAD_TARGET_( FT_RENDER_MODE_LCD ) #define FT_LOAD_TARGET_LCD_V FT_LOAD_TARGET_( FT_RENDER_MODE_LCD_V ) #define FT_ENC_TAG( value, a, b, c, d ) \ value = ( ( (FT_UInt32)(a) << 24 ) | \ ( (FT_UInt32)(b) << 16 ) | \ ( (FT_UInt32)(c) << 8 ) | \ (FT_UInt32)(d) ) typedef enum FT_Encoding_ { FT_ENC_TAG( FT_ENCODING_NONE, 0, 0, 0, 0 ), FT_ENC_TAG( FT_ENCODING_MS_SYMBOL, 's', 'y', 'm', 'b' ), FT_ENC_TAG( FT_ENCODING_UNICODE, 'u', 'n', 'i', 'c' ), FT_ENC_TAG( FT_ENCODING_SJIS, 's', 'j', 'i', 's' ), FT_ENC_TAG( FT_ENCODING_GB2312, 'g', 'b', ' ', ' ' ), FT_ENC_TAG( FT_ENCODING_BIG5, 'b', 'i', 'g', '5' ), FT_ENC_TAG( FT_ENCODING_WANSUNG, 'w', 'a', 'n', 's' ), FT_ENC_TAG( FT_ENCODING_JOHAB, 'j', 'o', 'h', 'a' ), /* for backwards compatibility */ FT_ENCODING_MS_SJIS = FT_ENCODING_SJIS, FT_ENCODING_MS_GB2312 = FT_ENCODING_GB2312, FT_ENCODING_MS_BIG5 = FT_ENCODING_BIG5, FT_ENCODING_MS_WANSUNG = FT_ENCODING_WANSUNG, FT_ENCODING_MS_JOHAB = FT_ENCODING_JOHAB, FT_ENC_TAG( FT_ENCODING_ADOBE_STANDARD, 'A', 'D', 'O', 'B' ), FT_ENC_TAG( FT_ENCODING_ADOBE_EXPERT, 'A', 'D', 'B', 'E' ), FT_ENC_TAG( FT_ENCODING_ADOBE_CUSTOM, 'A', 'D', 'B', 'C' ), FT_ENC_TAG( FT_ENCODING_ADOBE_LATIN_1, 'l', 'a', 't', '1' ), FT_ENC_TAG( FT_ENCODING_OLD_LATIN_2, 'l', 'a', 't', '2' ), FT_ENC_TAG( FT_ENCODING_APPLE_ROMAN, 'a', 'r', 'm', 'n' ) } FT_Encoding; #define ft_encoding_none FT_ENCODING_NONE #define ft_encoding_unicode FT_ENCODING_UNICODE #define ft_encoding_symbol FT_ENCODING_MS_SYMBOL #define ft_encoding_latin_1 FT_ENCODING_ADOBE_LATIN_1 #define ft_encoding_latin_2 FT_ENCODING_OLD_LATIN_2 #define ft_encoding_sjis FT_ENCODING_SJIS #define ft_encoding_gb2312 FT_ENCODING_GB2312 #define ft_encoding_big5 FT_ENCODING_BIG5 #define ft_encoding_wansung FT_ENCODING_WANSUNG #define ft_encoding_johab FT_ENCODING_JOHAB #define ft_encoding_adobe_standard FT_ENCODING_ADOBE_STANDARD #define ft_encoding_adobe_expert FT_ENCODING_ADOBE_EXPERT #define ft_encoding_adobe_custom FT_ENCODING_ADOBE_CUSTOM #define ft_encoding_apple_roman FT_ENCODING_APPLE_ROMAN typedef struct FT_Bitmap_Size_ { FT_Short height; FT_Short width; FT_Pos size; FT_Pos x_ppem; FT_Pos y_ppem; } FT_Bitmap_Size; typedef struct FT_CharMapRec_ { FT_Face face; FT_Encoding encoding; FT_UShort platform_id; FT_UShort encoding_id; } FT_CharMapRec; typedef void (*FT_Generic_Finalizer)(void* object); typedef struct FT_Generic_ { void* data; FT_Generic_Finalizer finalizer; } FT_Generic; typedef struct FT_Size_Metrics_ { FT_UShort x_ppem; /* horizontal pixels per EM */ FT_UShort y_ppem; /* vertical pixels per EM */ FT_Fixed x_scale; /* scaling values used to convert font */ FT_Fixed y_scale; /* units to 26.6 fractional pixels */ FT_Pos ascender; /* ascender in 26.6 frac. pixels */ FT_Pos descender; /* descender in 26.6 frac. pixels */ FT_Pos height; /* text height in 26.6 frac. pixels */ FT_Pos max_advance; /* max horizontal advance, in 26.6 pixels */ } FT_Size_Metrics; typedef struct FT_SizeRec_ { FT_Face face; /* parent face object */ FT_Generic generic; /* generic pointer for client uses */ FT_Size_Metrics metrics; /* size metrics */ FT_Size_Internal internal; } FT_SizeRec; typedef struct FT_FaceRec_ { FT_Long num_faces; FT_Long face_index; FT_Long face_flags; FT_Long style_flags; FT_Long num_glyphs; FT_String* family_name; FT_String* style_name; FT_Int num_fixed_sizes; FT_Bitmap_Size* available_sizes; FT_Int num_charmaps; FT_CharMap* charmaps; FT_Generic generic; /*# The following member variables (down to `underline_thickness') */ /*# are only relevant to scalable outlines; cf. @FT_Bitmap_Size */ /*# for bitmap fonts. */ FT_BBox bbox; FT_UShort units_per_EM; FT_Short ascender; FT_Short descender; FT_Short height; FT_Short max_advance_width; FT_Short max_advance_height; FT_Short underline_position; FT_Short underline_thickness; FT_GlyphSlot glyph; FT_Size size; FT_CharMap charmap; /* ft2 private FT_Driver driver; FT_Memory memory; FT_Stream stream; FT_ListRec sizes_list; FT_Generic autohint; void* extensions; FT_Face_Internal internal; */ } FT_FaceRec; typedef struct FT_GlyphSlotRec_ { FT_Library library; FT_Face face; FT_GlyphSlot next; FT_UInt reserved; /* retained for binary compatibility */ FT_Generic generic; FT_Glyph_Metrics metrics; FT_Fixed linearHoriAdvance; FT_Fixed linearVertAdvance; FT_Vector advance; FT_Glyph_Format format; FT_Bitmap bitmap; FT_Int bitmap_left; FT_Int bitmap_top; FT_Outline outline; FT_UInt num_subglyphs; FT_SubGlyph subglyphs; void* control_data; long control_len; FT_Pos lsb_delta; FT_Pos rsb_delta; void* other; FT_Slot_Internal internal; } FT_GlyphSlotRec; #define FT_FACE_FLAG_SCALABLE ( 1L << 0 ) #define FT_FACE_FLAG_FIXED_SIZES ( 1L << 1 ) #define FT_FACE_FLAG_FIXED_WIDTH ( 1L << 2 ) #define FT_FACE_FLAG_SFNT ( 1L << 3 ) #define FT_FACE_FLAG_HORIZONTAL ( 1L << 4 ) #define FT_FACE_FLAG_VERTICAL ( 1L << 5 ) #define FT_FACE_FLAG_KERNING ( 1L << 6 ) #define FT_FACE_FLAG_FAST_GLYPHS ( 1L << 7 ) #define FT_FACE_FLAG_MULTIPLE_MASTERS ( 1L << 8 ) #define FT_FACE_FLAG_GLYPH_NAMES ( 1L << 9 ) #define FT_FACE_FLAG_EXTERNAL_STREAM ( 1L << 10 ) #define FT_FACE_FLAG_HINTER ( 1L << 11 ) #define FT_FACE_FLAG_CID_KEYED ( 1L << 12 ) #define FT_FACE_FLAG_TRICKY ( 1L << 13 ) typedef enum FT_Kerning_Mode_ { FT_KERNING_DEFAULT = 0, FT_KERNING_UNFITTED, FT_KERNING_UNSCALED } FT_Kerning_Mode; #endif // FT2_DEFS_H_H__ darkplaces/sv_demo.h0000664000175000017500000000047213067716222013775 0ustar kalevkalev#ifndef SV_DEMO_H #define SV_DEMO_H void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack); void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver); void SV_StopDemoRecording(client_t *client); void SV_WriteNetnameIntoDemo(client_t *client); #endif darkplaces/mod_skeletal_animatevertices_generic.h0000664000175000017500000000053713067716220021743 0ustar kalevkalev#ifndef MOD_SKELETAL_ANIMATEVERTICES_GENERIC_H #define MOD_H #include "quakedef.h" void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); #endif darkplaces/cdaudio.h0000664000175000017500000000367713067716216013766 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ typedef struct cl_cdstate_s { qboolean Valid; qboolean Playing; qboolean PlayLooping; unsigned char PlayTrack; } cl_cdstate_t; //extern cl_cdstate_t cd; extern qboolean cdValid; extern qboolean cdPlaying; extern qboolean cdPlayLooping; extern unsigned char cdPlayTrack; extern cvar_t cdaudioinitialized; int CDAudio_Init(void); void CDAudio_Open(void); void CDAudio_Close(void); void CDAudio_Play(int track, qboolean looping); void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition); void CDAudio_Stop(void); void CDAudio_Pause(void); void CDAudio_Resume(void); int CDAudio_Startup(void); void CDAudio_Shutdown(void); void CDAudio_Update(void); float CDAudio_GetPosition(void); void CDAudio_StartPlaylist(qboolean resume); // Prototypes of the system dependent functions void CDAudio_SysEject (void); void CDAudio_SysCloseDoor (void); int CDAudio_SysGetAudioDiskInfo (void); float CDAudio_SysGetVolume (void); void CDAudio_SysSetVolume (float volume); int CDAudio_SysPlay (int track); int CDAudio_SysStop (void); int CDAudio_SysPause (void); int CDAudio_SysResume (void); int CDAudio_SysUpdate (void); void CDAudio_SysInit (void); int CDAudio_SysStartup (void); void CDAudio_SysShutdown (void); darkplaces/matrixlib.c0000664000175000017500000017744313067716220014342 0ustar kalevkalev#include "quakedef.h" #include #include "matrixlib.h" #ifdef _MSC_VER #pragma warning(disable : 4244) // LordHavoc: MSVC++ 4 x86, double/float #pragma warning(disable : 4305) // LordHavoc: MSVC++ 6 x86, double/float #endif const matrix4x4_t identitymatrix = { { {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1} } }; void Matrix4x4_Copy (matrix4x4_t *out, const matrix4x4_t *in) { *out = *in; } void Matrix4x4_CopyRotateOnly (matrix4x4_t *out, const matrix4x4_t *in) { out->m[0][0] = in->m[0][0]; out->m[0][1] = in->m[0][1]; out->m[0][2] = in->m[0][2]; out->m[0][3] = 0.0f; out->m[1][0] = in->m[1][0]; out->m[1][1] = in->m[1][1]; out->m[1][2] = in->m[1][2]; out->m[1][3] = 0.0f; out->m[2][0] = in->m[2][0]; out->m[2][1] = in->m[2][1]; out->m[2][2] = in->m[2][2]; out->m[2][3] = 0.0f; out->m[3][0] = 0.0f; out->m[3][1] = 0.0f; out->m[3][2] = 0.0f; out->m[3][3] = 1.0f; } void Matrix4x4_CopyTranslateOnly (matrix4x4_t *out, const matrix4x4_t *in) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = 1.0f; out->m[1][0] = 0.0f; out->m[2][0] = 0.0f; out->m[3][0] = in->m[0][3]; out->m[0][1] = 0.0f; out->m[1][1] = 1.0f; out->m[2][1] = 0.0f; out->m[3][1] = in->m[1][3]; out->m[0][2] = 0.0f; out->m[1][2] = 0.0f; out->m[2][2] = 1.0f; out->m[3][2] = in->m[2][3]; out->m[0][3] = 0.0f; out->m[1][3] = 0.0f; out->m[2][3] = 0.0f; out->m[3][3] = 1.0f; #else out->m[0][0] = 1.0f; out->m[0][1] = 0.0f; out->m[0][2] = 0.0f; out->m[0][3] = in->m[0][3]; out->m[1][0] = 0.0f; out->m[1][1] = 1.0f; out->m[1][2] = 0.0f; out->m[1][3] = in->m[1][3]; out->m[2][0] = 0.0f; out->m[2][1] = 0.0f; out->m[2][2] = 1.0f; out->m[2][3] = in->m[2][3]; out->m[3][0] = 0.0f; out->m[3][1] = 0.0f; out->m[3][2] = 0.0f; out->m[3][3] = 1.0f; #endif } void Matrix4x4_Concat (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[1][0] * in2->m[0][1] + in1->m[2][0] * in2->m[0][2] + in1->m[3][0] * in2->m[0][3]; out->m[1][0] = in1->m[0][0] * in2->m[1][0] + in1->m[1][0] * in2->m[1][1] + in1->m[2][0] * in2->m[1][2] + in1->m[3][0] * in2->m[1][3]; out->m[2][0] = in1->m[0][0] * in2->m[2][0] + in1->m[1][0] * in2->m[2][1] + in1->m[2][0] * in2->m[2][2] + in1->m[3][0] * in2->m[2][3]; out->m[3][0] = in1->m[0][0] * in2->m[3][0] + in1->m[1][0] * in2->m[3][1] + in1->m[2][0] * in2->m[3][2] + in1->m[3][0] * in2->m[3][3]; out->m[0][1] = in1->m[0][1] * in2->m[0][0] + in1->m[1][1] * in2->m[0][1] + in1->m[2][1] * in2->m[0][2] + in1->m[3][1] * in2->m[0][3]; out->m[1][1] = in1->m[0][1] * in2->m[1][0] + in1->m[1][1] * in2->m[1][1] + in1->m[2][1] * in2->m[1][2] + in1->m[3][1] * in2->m[1][3]; out->m[2][1] = in1->m[0][1] * in2->m[2][0] + in1->m[1][1] * in2->m[2][1] + in1->m[2][1] * in2->m[2][2] + in1->m[3][1] * in2->m[2][3]; out->m[3][1] = in1->m[0][1] * in2->m[3][0] + in1->m[1][1] * in2->m[3][1] + in1->m[2][1] * in2->m[3][2] + in1->m[3][1] * in2->m[3][3]; out->m[0][2] = in1->m[0][2] * in2->m[0][0] + in1->m[1][2] * in2->m[0][1] + in1->m[2][2] * in2->m[0][2] + in1->m[3][2] * in2->m[0][3]; out->m[1][2] = in1->m[0][2] * in2->m[1][0] + in1->m[1][2] * in2->m[1][1] + in1->m[2][2] * in2->m[1][2] + in1->m[3][2] * in2->m[1][3]; out->m[2][2] = in1->m[0][2] * in2->m[2][0] + in1->m[1][2] * in2->m[2][1] + in1->m[2][2] * in2->m[2][2] + in1->m[3][2] * in2->m[2][3]; out->m[3][2] = in1->m[0][2] * in2->m[3][0] + in1->m[1][2] * in2->m[3][1] + in1->m[2][2] * in2->m[3][2] + in1->m[3][2] * in2->m[3][3]; out->m[0][3] = in1->m[0][3] * in2->m[0][0] + in1->m[1][3] * in2->m[0][1] + in1->m[2][3] * in2->m[0][2] + in1->m[3][3] * in2->m[0][3]; out->m[1][3] = in1->m[0][3] * in2->m[1][0] + in1->m[1][3] * in2->m[1][1] + in1->m[2][3] * in2->m[1][2] + in1->m[3][3] * in2->m[1][3]; out->m[2][3] = in1->m[0][3] * in2->m[2][0] + in1->m[1][3] * in2->m[2][1] + in1->m[2][3] * in2->m[2][2] + in1->m[3][3] * in2->m[2][3]; out->m[3][3] = in1->m[0][3] * in2->m[3][0] + in1->m[1][3] * in2->m[3][1] + in1->m[2][3] * in2->m[3][2] + in1->m[3][3] * in2->m[3][3]; #else out->m[0][0] = in1->m[0][0] * in2->m[0][0] + in1->m[0][1] * in2->m[1][0] + in1->m[0][2] * in2->m[2][0] + in1->m[0][3] * in2->m[3][0]; out->m[0][1] = in1->m[0][0] * in2->m[0][1] + in1->m[0][1] * in2->m[1][1] + in1->m[0][2] * in2->m[2][1] + in1->m[0][3] * in2->m[3][1]; out->m[0][2] = in1->m[0][0] * in2->m[0][2] + in1->m[0][1] * in2->m[1][2] + in1->m[0][2] * in2->m[2][2] + in1->m[0][3] * in2->m[3][2]; out->m[0][3] = in1->m[0][0] * in2->m[0][3] + in1->m[0][1] * in2->m[1][3] + in1->m[0][2] * in2->m[2][3] + in1->m[0][3] * in2->m[3][3]; out->m[1][0] = in1->m[1][0] * in2->m[0][0] + in1->m[1][1] * in2->m[1][0] + in1->m[1][2] * in2->m[2][0] + in1->m[1][3] * in2->m[3][0]; out->m[1][1] = in1->m[1][0] * in2->m[0][1] + in1->m[1][1] * in2->m[1][1] + in1->m[1][2] * in2->m[2][1] + in1->m[1][3] * in2->m[3][1]; out->m[1][2] = in1->m[1][0] * in2->m[0][2] + in1->m[1][1] * in2->m[1][2] + in1->m[1][2] * in2->m[2][2] + in1->m[1][3] * in2->m[3][2]; out->m[1][3] = in1->m[1][0] * in2->m[0][3] + in1->m[1][1] * in2->m[1][3] + in1->m[1][2] * in2->m[2][3] + in1->m[1][3] * in2->m[3][3]; out->m[2][0] = in1->m[2][0] * in2->m[0][0] + in1->m[2][1] * in2->m[1][0] + in1->m[2][2] * in2->m[2][0] + in1->m[2][3] * in2->m[3][0]; out->m[2][1] = in1->m[2][0] * in2->m[0][1] + in1->m[2][1] * in2->m[1][1] + in1->m[2][2] * in2->m[2][1] + in1->m[2][3] * in2->m[3][1]; out->m[2][2] = in1->m[2][0] * in2->m[0][2] + in1->m[2][1] * in2->m[1][2] + in1->m[2][2] * in2->m[2][2] + in1->m[2][3] * in2->m[3][2]; out->m[2][3] = in1->m[2][0] * in2->m[0][3] + in1->m[2][1] * in2->m[1][3] + in1->m[2][2] * in2->m[2][3] + in1->m[2][3] * in2->m[3][3]; out->m[3][0] = in1->m[3][0] * in2->m[0][0] + in1->m[3][1] * in2->m[1][0] + in1->m[3][2] * in2->m[2][0] + in1->m[3][3] * in2->m[3][0]; out->m[3][1] = in1->m[3][0] * in2->m[0][1] + in1->m[3][1] * in2->m[1][1] + in1->m[3][2] * in2->m[2][1] + in1->m[3][3] * in2->m[3][1]; out->m[3][2] = in1->m[3][0] * in2->m[0][2] + in1->m[3][1] * in2->m[1][2] + in1->m[3][2] * in2->m[2][2] + in1->m[3][3] * in2->m[3][2]; out->m[3][3] = in1->m[3][0] * in2->m[0][3] + in1->m[3][1] * in2->m[1][3] + in1->m[3][2] * in2->m[2][3] + in1->m[3][3] * in2->m[3][3]; #endif } void Matrix4x4_Transpose (matrix4x4_t *out, const matrix4x4_t *in1) { out->m[0][0] = in1->m[0][0]; out->m[0][1] = in1->m[1][0]; out->m[0][2] = in1->m[2][0]; out->m[0][3] = in1->m[3][0]; out->m[1][0] = in1->m[0][1]; out->m[1][1] = in1->m[1][1]; out->m[1][2] = in1->m[2][1]; out->m[1][3] = in1->m[3][1]; out->m[2][0] = in1->m[0][2]; out->m[2][1] = in1->m[1][2]; out->m[2][2] = in1->m[2][2]; out->m[2][3] = in1->m[3][2]; out->m[3][0] = in1->m[0][3]; out->m[3][1] = in1->m[1][3]; out->m[3][2] = in1->m[2][3]; out->m[3][3] = in1->m[3][3]; } #if 1 // Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) // added helper for common subexpression elimination by eihrul, and other optimizations by div0 int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) { float det; // note: orientation does not matter, as transpose(invert(transpose(m))) == invert(m), proof: // transpose(invert(transpose(m))) * m // = transpose(invert(transpose(m))) * transpose(transpose(m)) // = transpose(transpose(m) * invert(transpose(m))) // = transpose(identity) // = identity // this seems to help gcc's common subexpression elimination, and also makes the code look nicer float m00 = in1->m[0][0], m01 = in1->m[0][1], m02 = in1->m[0][2], m03 = in1->m[0][3], m10 = in1->m[1][0], m11 = in1->m[1][1], m12 = in1->m[1][2], m13 = in1->m[1][3], m20 = in1->m[2][0], m21 = in1->m[2][1], m22 = in1->m[2][2], m23 = in1->m[2][3], m30 = in1->m[3][0], m31 = in1->m[3][1], m32 = in1->m[3][2], m33 = in1->m[3][3]; // calculate the adjoint out->m[0][0] = (m11*(m22*m33 - m23*m32) - m21*(m12*m33 - m13*m32) + m31*(m12*m23 - m13*m22)); out->m[0][1] = -(m01*(m22*m33 - m23*m32) - m21*(m02*m33 - m03*m32) + m31*(m02*m23 - m03*m22)); out->m[0][2] = (m01*(m12*m33 - m13*m32) - m11*(m02*m33 - m03*m32) + m31*(m02*m13 - m03*m12)); out->m[0][3] = -(m01*(m12*m23 - m13*m22) - m11*(m02*m23 - m03*m22) + m21*(m02*m13 - m03*m12)); out->m[1][0] = -(m10*(m22*m33 - m23*m32) - m20*(m12*m33 - m13*m32) + m30*(m12*m23 - m13*m22)); out->m[1][1] = (m00*(m22*m33 - m23*m32) - m20*(m02*m33 - m03*m32) + m30*(m02*m23 - m03*m22)); out->m[1][2] = -(m00*(m12*m33 - m13*m32) - m10*(m02*m33 - m03*m32) + m30*(m02*m13 - m03*m12)); out->m[1][3] = (m00*(m12*m23 - m13*m22) - m10*(m02*m23 - m03*m22) + m20*(m02*m13 - m03*m12)); out->m[2][0] = (m10*(m21*m33 - m23*m31) - m20*(m11*m33 - m13*m31) + m30*(m11*m23 - m13*m21)); out->m[2][1] = -(m00*(m21*m33 - m23*m31) - m20*(m01*m33 - m03*m31) + m30*(m01*m23 - m03*m21)); out->m[2][2] = (m00*(m11*m33 - m13*m31) - m10*(m01*m33 - m03*m31) + m30*(m01*m13 - m03*m11)); out->m[2][3] = -(m00*(m11*m23 - m13*m21) - m10*(m01*m23 - m03*m21) + m20*(m01*m13 - m03*m11)); out->m[3][0] = -(m10*(m21*m32 - m22*m31) - m20*(m11*m32 - m12*m31) + m30*(m11*m22 - m12*m21)); out->m[3][1] = (m00*(m21*m32 - m22*m31) - m20*(m01*m32 - m02*m31) + m30*(m01*m22 - m02*m21)); out->m[3][2] = -(m00*(m11*m32 - m12*m31) - m10*(m01*m32 - m02*m31) + m30*(m01*m12 - m02*m11)); out->m[3][3] = (m00*(m11*m22 - m12*m21) - m10*(m01*m22 - m02*m21) + m20*(m01*m12 - m02*m11)); // calculate the determinant (as inverse == 1/det * adjoint, adjoint * m == identity * det, so this calculates the det) det = m00*out->m[0][0] + m10*out->m[0][1] + m20*out->m[0][2] + m30*out->m[0][3]; if (det == 0.0f) return 0; // multiplications are faster than divisions, usually det = 1.0f / det; // manually unrolled loop to multiply all matrix elements by 1/det out->m[0][0] *= det; out->m[0][1] *= det; out->m[0][2] *= det; out->m[0][3] *= det; out->m[1][0] *= det; out->m[1][1] *= det; out->m[1][2] *= det; out->m[1][3] *= det; out->m[2][0] *= det; out->m[2][1] *= det; out->m[2][2] *= det; out->m[2][3] *= det; out->m[3][0] *= det; out->m[3][1] *= det; out->m[3][2] *= det; out->m[3][3] *= det; return 1; } #elif 1 // Adapted from code contributed to Mesa by David Moore (Mesa 7.6 under SGI Free License B - which is MIT/X11-type) int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) { matrix4x4_t temp; double det; int i, j; #ifdef MATRIX4x4_OPENGLORIENTATION temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[2][3]*in1->m[3][2] - in1->m[2][1]*in1->m[1][2]*in1->m[3][3] + in1->m[2][1]*in1->m[1][3]*in1->m[3][2] + in1->m[3][1]*in1->m[1][2]*in1->m[2][3] - in1->m[3][1]*in1->m[1][3]*in1->m[2][2]; temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[2][3]*in1->m[3][2] + in1->m[2][0]*in1->m[1][2]*in1->m[3][3] - in1->m[2][0]*in1->m[1][3]*in1->m[3][2] - in1->m[3][0]*in1->m[1][2]*in1->m[2][3] + in1->m[3][0]*in1->m[1][3]*in1->m[2][2]; temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[2][3]*in1->m[3][1] - in1->m[2][0]*in1->m[1][1]*in1->m[3][3] + in1->m[2][0]*in1->m[1][3]*in1->m[3][1] + in1->m[3][0]*in1->m[1][1]*in1->m[2][3] - in1->m[3][0]*in1->m[1][3]*in1->m[2][1]; temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[2][2]*in1->m[3][1] + in1->m[2][0]*in1->m[1][1]*in1->m[3][2] - in1->m[2][0]*in1->m[1][2]*in1->m[3][1] - in1->m[3][0]*in1->m[1][1]*in1->m[2][2] + in1->m[3][0]*in1->m[1][2]*in1->m[2][1]; temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[2][3]*in1->m[3][2] + in1->m[2][1]*in1->m[0][2]*in1->m[3][3] - in1->m[2][1]*in1->m[0][3]*in1->m[3][2] - in1->m[3][1]*in1->m[0][2]*in1->m[2][3] + in1->m[3][1]*in1->m[0][3]*in1->m[2][2]; temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[2][3]*in1->m[3][2] - in1->m[2][0]*in1->m[0][2]*in1->m[3][3] + in1->m[2][0]*in1->m[0][3]*in1->m[3][2] + in1->m[3][0]*in1->m[0][2]*in1->m[2][3] - in1->m[3][0]*in1->m[0][3]*in1->m[2][2]; temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[2][3]*in1->m[3][1] + in1->m[2][0]*in1->m[0][1]*in1->m[3][3] - in1->m[2][0]*in1->m[0][3]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[2][3] + in1->m[3][0]*in1->m[0][3]*in1->m[2][1]; temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[2][2]*in1->m[3][1] - in1->m[2][0]*in1->m[0][1]*in1->m[3][2] + in1->m[2][0]*in1->m[0][2]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[2][2] - in1->m[3][0]*in1->m[0][2]*in1->m[2][1]; temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[1][3]*in1->m[3][2] - in1->m[1][1]*in1->m[0][2]*in1->m[3][3] + in1->m[1][1]*in1->m[0][3]*in1->m[3][2] + in1->m[3][1]*in1->m[0][2]*in1->m[1][3] - in1->m[3][1]*in1->m[0][3]*in1->m[1][2]; temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[1][3]*in1->m[3][2] + in1->m[1][0]*in1->m[0][2]*in1->m[3][3] - in1->m[1][0]*in1->m[0][3]*in1->m[3][2] - in1->m[3][0]*in1->m[0][2]*in1->m[1][3] + in1->m[3][0]*in1->m[0][3]*in1->m[1][2]; temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[1][3]*in1->m[3][1] - in1->m[1][0]*in1->m[0][1]*in1->m[3][3] + in1->m[1][0]*in1->m[0][3]*in1->m[3][1] + in1->m[3][0]*in1->m[0][1]*in1->m[1][3] - in1->m[3][0]*in1->m[0][3]*in1->m[1][1]; temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[1][2]*in1->m[3][1] + in1->m[1][0]*in1->m[0][1]*in1->m[3][2] - in1->m[1][0]*in1->m[0][2]*in1->m[3][1] - in1->m[3][0]*in1->m[0][1]*in1->m[1][2] + in1->m[3][0]*in1->m[0][2]*in1->m[1][1]; temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[1][3]*in1->m[2][2] + in1->m[1][1]*in1->m[0][2]*in1->m[2][3] - in1->m[1][1]*in1->m[0][3]*in1->m[2][2] - in1->m[2][1]*in1->m[0][2]*in1->m[1][3] + in1->m[2][1]*in1->m[0][3]*in1->m[1][2]; temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[1][3]*in1->m[2][2] - in1->m[1][0]*in1->m[0][2]*in1->m[2][3] + in1->m[1][0]*in1->m[0][3]*in1->m[2][2] + in1->m[2][0]*in1->m[0][2]*in1->m[1][3] - in1->m[2][0]*in1->m[0][3]*in1->m[1][2]; temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[1][3]*in1->m[2][1] + in1->m[1][0]*in1->m[0][1]*in1->m[2][3] - in1->m[1][0]*in1->m[0][3]*in1->m[2][1] - in1->m[2][0]*in1->m[0][1]*in1->m[1][3] + in1->m[2][0]*in1->m[0][3]*in1->m[1][1]; temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[1][2]*in1->m[2][1] - in1->m[1][0]*in1->m[0][1]*in1->m[2][2] + in1->m[1][0]*in1->m[0][2]*in1->m[2][1] + in1->m[2][0]*in1->m[0][1]*in1->m[1][2] - in1->m[2][0]*in1->m[0][2]*in1->m[1][1]; #else temp.m[0][0] = in1->m[1][1]*in1->m[2][2]*in1->m[3][3] - in1->m[1][1]*in1->m[3][2]*in1->m[2][3] - in1->m[1][2]*in1->m[2][1]*in1->m[3][3] + in1->m[1][2]*in1->m[3][1]*in1->m[2][3] + in1->m[1][3]*in1->m[2][1]*in1->m[3][2] - in1->m[1][3]*in1->m[3][1]*in1->m[2][2]; temp.m[0][1] = -in1->m[0][1]*in1->m[2][2]*in1->m[3][3] + in1->m[0][1]*in1->m[3][2]*in1->m[2][3] + in1->m[0][2]*in1->m[2][1]*in1->m[3][3] - in1->m[0][2]*in1->m[3][1]*in1->m[2][3] - in1->m[0][3]*in1->m[2][1]*in1->m[3][2] + in1->m[0][3]*in1->m[3][1]*in1->m[2][2]; temp.m[0][2] = in1->m[0][1]*in1->m[1][2]*in1->m[3][3] - in1->m[0][1]*in1->m[3][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][1]*in1->m[3][3] + in1->m[0][2]*in1->m[3][1]*in1->m[1][3] + in1->m[0][3]*in1->m[1][1]*in1->m[3][2] - in1->m[0][3]*in1->m[3][1]*in1->m[1][2]; temp.m[0][3] = -in1->m[0][1]*in1->m[1][2]*in1->m[2][3] + in1->m[0][1]*in1->m[2][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][1]*in1->m[2][3] - in1->m[0][2]*in1->m[2][1]*in1->m[1][3] - in1->m[0][3]*in1->m[1][1]*in1->m[2][2] + in1->m[0][3]*in1->m[2][1]*in1->m[1][2]; temp.m[1][0] = -in1->m[1][0]*in1->m[2][2]*in1->m[3][3] + in1->m[1][0]*in1->m[3][2]*in1->m[2][3] + in1->m[1][2]*in1->m[2][0]*in1->m[3][3] - in1->m[1][2]*in1->m[3][0]*in1->m[2][3] - in1->m[1][3]*in1->m[2][0]*in1->m[3][2] + in1->m[1][3]*in1->m[3][0]*in1->m[2][2]; temp.m[1][1] = in1->m[0][0]*in1->m[2][2]*in1->m[3][3] - in1->m[0][0]*in1->m[3][2]*in1->m[2][3] - in1->m[0][2]*in1->m[2][0]*in1->m[3][3] + in1->m[0][2]*in1->m[3][0]*in1->m[2][3] + in1->m[0][3]*in1->m[2][0]*in1->m[3][2] - in1->m[0][3]*in1->m[3][0]*in1->m[2][2]; temp.m[1][2] = -in1->m[0][0]*in1->m[1][2]*in1->m[3][3] + in1->m[0][0]*in1->m[3][2]*in1->m[1][3] + in1->m[0][2]*in1->m[1][0]*in1->m[3][3] - in1->m[0][2]*in1->m[3][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[3][2] + in1->m[0][3]*in1->m[3][0]*in1->m[1][2]; temp.m[1][3] = in1->m[0][0]*in1->m[1][2]*in1->m[2][3] - in1->m[0][0]*in1->m[2][2]*in1->m[1][3] - in1->m[0][2]*in1->m[1][0]*in1->m[2][3] + in1->m[0][2]*in1->m[2][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[2][2] - in1->m[0][3]*in1->m[2][0]*in1->m[1][2]; temp.m[2][0] = in1->m[1][0]*in1->m[2][1]*in1->m[3][3] - in1->m[1][0]*in1->m[3][1]*in1->m[2][3] - in1->m[1][1]*in1->m[2][0]*in1->m[3][3] + in1->m[1][1]*in1->m[3][0]*in1->m[2][3] + in1->m[1][3]*in1->m[2][0]*in1->m[3][1] - in1->m[1][3]*in1->m[3][0]*in1->m[2][1]; temp.m[2][1] = -in1->m[0][0]*in1->m[2][1]*in1->m[3][3] + in1->m[0][0]*in1->m[3][1]*in1->m[2][3] + in1->m[0][1]*in1->m[2][0]*in1->m[3][3] - in1->m[0][1]*in1->m[3][0]*in1->m[2][3] - in1->m[0][3]*in1->m[2][0]*in1->m[3][1] + in1->m[0][3]*in1->m[3][0]*in1->m[2][1]; temp.m[2][2] = in1->m[0][0]*in1->m[1][1]*in1->m[3][3] - in1->m[0][0]*in1->m[3][1]*in1->m[1][3] - in1->m[0][1]*in1->m[1][0]*in1->m[3][3] + in1->m[0][1]*in1->m[3][0]*in1->m[1][3] + in1->m[0][3]*in1->m[1][0]*in1->m[3][1] - in1->m[0][3]*in1->m[3][0]*in1->m[1][1]; temp.m[2][3] = -in1->m[0][0]*in1->m[1][1]*in1->m[2][3] + in1->m[0][0]*in1->m[2][1]*in1->m[1][3] + in1->m[0][1]*in1->m[1][0]*in1->m[2][3] - in1->m[0][1]*in1->m[2][0]*in1->m[1][3] - in1->m[0][3]*in1->m[1][0]*in1->m[2][1] + in1->m[0][3]*in1->m[2][0]*in1->m[1][1]; temp.m[3][0] = -in1->m[1][0]*in1->m[2][1]*in1->m[3][2] + in1->m[1][0]*in1->m[3][1]*in1->m[2][2] + in1->m[1][1]*in1->m[2][0]*in1->m[3][2] - in1->m[1][1]*in1->m[3][0]*in1->m[2][2] - in1->m[1][2]*in1->m[2][0]*in1->m[3][1] + in1->m[1][2]*in1->m[3][0]*in1->m[2][1]; temp.m[3][1] = in1->m[0][0]*in1->m[2][1]*in1->m[3][2] - in1->m[0][0]*in1->m[3][1]*in1->m[2][2] - in1->m[0][1]*in1->m[2][0]*in1->m[3][2] + in1->m[0][1]*in1->m[3][0]*in1->m[2][2] + in1->m[0][2]*in1->m[2][0]*in1->m[3][1] - in1->m[0][2]*in1->m[3][0]*in1->m[2][1]; temp.m[3][2] = -in1->m[0][0]*in1->m[1][1]*in1->m[3][2] + in1->m[0][0]*in1->m[3][1]*in1->m[1][2] + in1->m[0][1]*in1->m[1][0]*in1->m[3][2] - in1->m[0][1]*in1->m[3][0]*in1->m[1][2] - in1->m[0][2]*in1->m[1][0]*in1->m[3][1] + in1->m[0][2]*in1->m[3][0]*in1->m[1][1]; temp.m[3][3] = in1->m[0][0]*in1->m[1][1]*in1->m[2][2] - in1->m[0][0]*in1->m[2][1]*in1->m[1][2] - in1->m[0][1]*in1->m[1][0]*in1->m[2][2] + in1->m[0][1]*in1->m[2][0]*in1->m[1][2] + in1->m[0][2]*in1->m[1][0]*in1->m[2][1] - in1->m[0][2]*in1->m[2][0]*in1->m[1][1]; #endif det = in1->m[0][0]*temp.m[0][0] + in1->m[1][0]*temp.m[0][1] + in1->m[2][0]*temp.m[0][2] + in1->m[3][0]*temp.m[0][3]; if (det == 0.0f) return 0; det = 1.0f / det; for (i = 0;i < 4;i++) for (j = 0;j < 4;j++) out->m[i][j] = temp.m[i][j] * det; return 1; } #else int Matrix4x4_Invert_Full (matrix4x4_t *out, const matrix4x4_t *in1) { double *temp; double *r[4]; double rtemp[4][8]; double m[4]; double s; r[0] = rtemp[0]; r[1] = rtemp[1]; r[2] = rtemp[2]; r[3] = rtemp[3]; #ifdef MATRIX4x4_OPENGLORIENTATION r[0][0] = in1->m[0][0]; r[0][1] = in1->m[1][0]; r[0][2] = in1->m[2][0]; r[0][3] = in1->m[3][0]; r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; r[1][0] = in1->m[0][1]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[2][1]; r[1][3] = in1->m[3][1]; r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; r[2][0] = in1->m[0][2]; r[2][1] = in1->m[1][2]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[3][2]; r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; r[3][0] = in1->m[0][3]; r[3][1] = in1->m[1][3]; r[3][2] = in1->m[2][3]; r[3][3] = in1->m[3][3]; r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; #else r[0][0] = in1->m[0][0]; r[0][1] = in1->m[0][1]; r[0][2] = in1->m[0][2]; r[0][3] = in1->m[0][3]; r[0][4] = 1.0; r[0][5] = r[0][6] = r[0][7] = 0.0; r[1][0] = in1->m[1][0]; r[1][1] = in1->m[1][1]; r[1][2] = in1->m[1][2]; r[1][3] = in1->m[1][3]; r[1][5] = 1.0; r[1][4] = r[1][6] = r[1][7] = 0.0; r[2][0] = in1->m[2][0]; r[2][1] = in1->m[2][1]; r[2][2] = in1->m[2][2]; r[2][3] = in1->m[2][3]; r[2][6] = 1.0; r[2][4] = r[2][5] = r[2][7] = 0.0; r[3][0] = in1->m[3][0]; r[3][1] = in1->m[3][1]; r[3][2] = in1->m[3][2]; r[3][3] = in1->m[3][3]; r[3][7] = 1.0; r[3][4] = r[3][5] = r[3][6] = 0.0; #endif if (fabs (r[3][0]) > fabs (r[2][0])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } if (fabs (r[2][0]) > fabs (r[1][0])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } if (fabs (r[1][0]) > fabs (r[0][0])) { temp = r[1]; r[1] = r[0]; r[0] = temp; } if (r[0][0]) { m[1] = r[1][0] / r[0][0]; m[2] = r[2][0] / r[0][0]; m[3] = r[3][0] / r[0][0]; s = r[0][1]; r[1][1] -= m[1] * s; r[2][1] -= m[2] * s; r[3][1] -= m[3] * s; s = r[0][2]; r[1][2] -= m[1] * s; r[2][2] -= m[2] * s; r[3][2] -= m[3] * s; s = r[0][3]; r[1][3] -= m[1] * s; r[2][3] -= m[2] * s; r[3][3] -= m[3] * s; s = r[0][4]; if (s) { r[1][4] -= m[1] * s; r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } s = r[0][5]; if (s) { r[1][5] -= m[1] * s; r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } s = r[0][6]; if (s) { r[1][6] -= m[1] * s; r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } s = r[0][7]; if (s) { r[1][7] -= m[1] * s; r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } if (fabs (r[3][1]) > fabs (r[2][1])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } if (fabs (r[2][1]) > fabs (r[1][1])) { temp = r[2]; r[2] = r[1]; r[1] = temp; } if (r[1][1]) { m[2] = r[2][1] / r[1][1]; m[3] = r[3][1] / r[1][1]; r[2][2] -= m[2] * r[1][2]; r[3][2] -= m[3] * r[1][2]; r[2][3] -= m[2] * r[1][3]; r[3][3] -= m[3] * r[1][3]; s = r[1][4]; if (s) { r[2][4] -= m[2] * s; r[3][4] -= m[3] * s; } s = r[1][5]; if (s) { r[2][5] -= m[2] * s; r[3][5] -= m[3] * s; } s = r[1][6]; if (s) { r[2][6] -= m[2] * s; r[3][6] -= m[3] * s; } s = r[1][7]; if (s) { r[2][7] -= m[2] * s; r[3][7] -= m[3] * s; } if (fabs (r[3][2]) > fabs (r[2][2])) { temp = r[3]; r[3] = r[2]; r[2] = temp; } if (r[2][2]) { m[3] = r[3][2] / r[2][2]; r[3][3] -= m[3] * r[2][3]; r[3][4] -= m[3] * r[2][4]; r[3][5] -= m[3] * r[2][5]; r[3][6] -= m[3] * r[2][6]; r[3][7] -= m[3] * r[2][7]; if (r[3][3]) { s = 1.0 / r[3][3]; r[3][4] *= s; r[3][5] *= s; r[3][6] *= s; r[3][7] *= s; m[2] = r[2][3]; s = 1.0 / r[2][2]; r[2][4] = s * (r[2][4] - r[3][4] * m[2]); r[2][5] = s * (r[2][5] - r[3][5] * m[2]); r[2][6] = s * (r[2][6] - r[3][6] * m[2]); r[2][7] = s * (r[2][7] - r[3][7] * m[2]); m[1] = r[1][3]; r[1][4] -= r[3][4] * m[1], r[1][5] -= r[3][5] * m[1]; r[1][6] -= r[3][6] * m[1], r[1][7] -= r[3][7] * m[1]; m[0] = r[0][3]; r[0][4] -= r[3][4] * m[0], r[0][5] -= r[3][5] * m[0]; r[0][6] -= r[3][6] * m[0], r[0][7] -= r[3][7] * m[0]; m[1] = r[1][2]; s = 1.0 / r[1][1]; r[1][4] = s * (r[1][4] - r[2][4] * m[1]), r[1][5] = s * (r[1][5] - r[2][5] * m[1]); r[1][6] = s * (r[1][6] - r[2][6] * m[1]), r[1][7] = s * (r[1][7] - r[2][7] * m[1]); m[0] = r[0][2]; r[0][4] -= r[2][4] * m[0], r[0][5] -= r[2][5] * m[0]; r[0][6] -= r[2][6] * m[0], r[0][7] -= r[2][7] * m[0]; m[0] = r[0][1]; s = 1.0 / r[0][0]; r[0][4] = s * (r[0][4] - r[1][4] * m[0]), r[0][5] = s * (r[0][5] - r[1][5] * m[0]); r[0][6] = s * (r[0][6] - r[1][6] * m[0]), r[0][7] = s * (r[0][7] - r[1][7] * m[0]); #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = r[0][4]; out->m[0][1] = r[1][4]; out->m[0][2] = r[2][4]; out->m[0][3] = r[3][4]; out->m[1][0] = r[0][5]; out->m[1][1] = r[1][5]; out->m[1][2] = r[2][5]; out->m[1][3] = r[3][5]; out->m[2][0] = r[0][6]; out->m[2][1] = r[1][6]; out->m[2][2] = r[2][6]; out->m[2][3] = r[3][6]; out->m[3][0] = r[0][7]; out->m[3][1] = r[1][7]; out->m[3][2] = r[2][7]; out->m[3][3] = r[3][7]; #else out->m[0][0] = r[0][4]; out->m[0][1] = r[0][5]; out->m[0][2] = r[0][6]; out->m[0][3] = r[0][7]; out->m[1][0] = r[1][4]; out->m[1][1] = r[1][5]; out->m[1][2] = r[1][6]; out->m[1][3] = r[1][7]; out->m[2][0] = r[2][4]; out->m[2][1] = r[2][5]; out->m[2][2] = r[2][6]; out->m[2][3] = r[2][7]; out->m[3][0] = r[3][4]; out->m[3][1] = r[3][5]; out->m[3][2] = r[3][6]; out->m[3][3] = r[3][7]; #endif return 1; } } } } return 0; } #endif void Matrix4x4_Invert_Simple (matrix4x4_t *out, const matrix4x4_t *in1) { // we only support uniform scaling, so assume the first row is enough // (note the lack of sqrt here, because we're trying to undo the scaling, // this means multiplying by the inverse scale twice - squaring it, which // makes the sqrt a waste of time) #if 1 double scale = 1.0 / (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); #else double scale = 3.0 / sqrt (in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2] + in1->m[1][0] * in1->m[1][0] + in1->m[1][1] * in1->m[1][1] + in1->m[1][2] * in1->m[1][2] + in1->m[2][0] * in1->m[2][0] + in1->m[2][1] * in1->m[2][1] + in1->m[2][2] * in1->m[2][2]); scale *= scale; #endif // invert the rotation by transposing and multiplying by the squared // recipricol of the input matrix scale as described above out->m[0][0] = in1->m[0][0] * scale; out->m[0][1] = in1->m[1][0] * scale; out->m[0][2] = in1->m[2][0] * scale; out->m[1][0] = in1->m[0][1] * scale; out->m[1][1] = in1->m[1][1] * scale; out->m[1][2] = in1->m[2][1] * scale; out->m[2][0] = in1->m[0][2] * scale; out->m[2][1] = in1->m[1][2] * scale; out->m[2][2] = in1->m[2][2] * scale; #ifdef MATRIX4x4_OPENGLORIENTATION // invert the translate out->m[3][0] = -(in1->m[3][0] * out->m[0][0] + in1->m[3][1] * out->m[1][0] + in1->m[3][2] * out->m[2][0]); out->m[3][1] = -(in1->m[3][0] * out->m[0][1] + in1->m[3][1] * out->m[1][1] + in1->m[3][2] * out->m[2][1]); out->m[3][2] = -(in1->m[3][0] * out->m[0][2] + in1->m[3][1] * out->m[1][2] + in1->m[3][2] * out->m[2][2]); // don't know if there's anything worth doing here out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else // invert the translate out->m[0][3] = -(in1->m[0][3] * out->m[0][0] + in1->m[1][3] * out->m[0][1] + in1->m[2][3] * out->m[0][2]); out->m[1][3] = -(in1->m[0][3] * out->m[1][0] + in1->m[1][3] * out->m[1][1] + in1->m[2][3] * out->m[1][2]); out->m[2][3] = -(in1->m[0][3] * out->m[2][0] + in1->m[1][3] * out->m[2][1] + in1->m[2][3] * out->m[2][2]); // don't know if there's anything worth doing here out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } void Matrix4x4_Interpolate (matrix4x4_t *out, matrix4x4_t *in1, matrix4x4_t *in2, double frac) { int i, j; for (i = 0;i < 4;i++) for (j = 0;j < 4;j++) out->m[i][j] = in1->m[i][j] + frac * (in2->m[i][j] - in1->m[i][j]); } void Matrix4x4_Clear (matrix4x4_t *out) { int i, j; for (i = 0;i < 4;i++) for (j = 0;j < 4;j++) out->m[i][j] = 0; } void Matrix4x4_Accumulate (matrix4x4_t *out, matrix4x4_t *in, double weight) { int i, j; for (i = 0;i < 4;i++) for (j = 0;j < 4;j++) out->m[i][j] += in->m[i][j] * weight; } void Matrix4x4_Normalize (matrix4x4_t *out, matrix4x4_t *in1) { // scale rotation matrix vectors to a length of 1 // note: this is only designed to undo uniform scaling double scale = 1.0 / sqrt(in1->m[0][0] * in1->m[0][0] + in1->m[0][1] * in1->m[0][1] + in1->m[0][2] * in1->m[0][2]); *out = *in1; Matrix4x4_Scale(out, scale, 1); } void Matrix4x4_Normalize3 (matrix4x4_t *out, matrix4x4_t *in1) { int i; double scale; // scale each rotation matrix vector to a length of 1 // intended for use after Matrix4x4_Interpolate or Matrix4x4_Accumulate *out = *in1; for (i = 0;i < 3;i++) { #ifdef MATRIX4x4_OPENGLORIENTATION scale = sqrt(in1->m[i][0] * in1->m[i][0] + in1->m[i][1] * in1->m[i][1] + in1->m[i][2] * in1->m[i][2]); if (scale) scale = 1.0 / scale; out->m[i][0] *= scale; out->m[i][1] *= scale; out->m[i][2] *= scale; #else scale = sqrt(in1->m[0][i] * in1->m[0][i] + in1->m[1][i] * in1->m[1][i] + in1->m[2][i] * in1->m[2][i]); if (scale) scale = 1.0 / scale; out->m[0][i] *= scale; out->m[1][i] *= scale; out->m[2][i] *= scale; #endif } } void Matrix4x4_Reflect (matrix4x4_t *out, double normalx, double normaly, double normalz, double dist, double axisscale) { int i; double d; double p[4], p2[4]; p[0] = normalx; p[1] = normaly; p[2] = normalz; p[3] = -dist; p2[0] = p[0] * axisscale; p2[1] = p[1] * axisscale; p2[2] = p[2] * axisscale; p2[3] = 0; for (i = 0;i < 4;i++) { #ifdef MATRIX4x4_OPENGLORIENTATION d = out->m[i][0] * p[0] + out->m[i][1] * p[1] + out->m[i][2] * p[2] + out->m[i][3] * p[3]; out->m[i][0] += p2[0] * d; out->m[i][1] += p2[1] * d; out->m[i][2] += p2[2] * d; #else d = out->m[0][i] * p[0] + out->m[1][i] * p[1] + out->m[2][i] * p[2] + out->m[3][i] * p[3]; out->m[0][i] += p2[0] * d; out->m[1][i] += p2[1] * d; out->m[2][i] += p2[2] * d; #endif } } void Matrix4x4_CreateIdentity (matrix4x4_t *out) { out->m[0][0]=1.0f; out->m[0][1]=0.0f; out->m[0][2]=0.0f; out->m[0][3]=0.0f; out->m[1][0]=0.0f; out->m[1][1]=1.0f; out->m[1][2]=0.0f; out->m[1][3]=0.0f; out->m[2][0]=0.0f; out->m[2][1]=0.0f; out->m[2][2]=1.0f; out->m[2][3]=0.0f; out->m[3][0]=0.0f; out->m[3][1]=0.0f; out->m[3][2]=0.0f; out->m[3][3]=1.0f; } void Matrix4x4_CreateTranslate (matrix4x4_t *out, double x, double y, double z) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0]=1.0f; out->m[1][0]=0.0f; out->m[2][0]=0.0f; out->m[3][0]=x; out->m[0][1]=0.0f; out->m[1][1]=1.0f; out->m[2][1]=0.0f; out->m[3][1]=y; out->m[0][2]=0.0f; out->m[1][2]=0.0f; out->m[2][2]=1.0f; out->m[3][2]=z; out->m[0][3]=0.0f; out->m[1][3]=0.0f; out->m[2][3]=0.0f; out->m[3][3]=1.0f; #else out->m[0][0]=1.0f; out->m[0][1]=0.0f; out->m[0][2]=0.0f; out->m[0][3]=x; out->m[1][0]=0.0f; out->m[1][1]=1.0f; out->m[1][2]=0.0f; out->m[1][3]=y; out->m[2][0]=0.0f; out->m[2][1]=0.0f; out->m[2][2]=1.0f; out->m[2][3]=z; out->m[3][0]=0.0f; out->m[3][1]=0.0f; out->m[3][2]=0.0f; out->m[3][3]=1.0f; #endif } void Matrix4x4_CreateRotate (matrix4x4_t *out, double angle, double x, double y, double z) { double len, c, s; len = x*x+y*y+z*z; if (len != 0.0f) len = 1.0f / sqrt(len); x *= len; y *= len; z *= len; angle *= (-M_PI / 180.0); c = cos(angle); s = sin(angle); #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0]=x * x + c * (1 - x * x); out->m[1][0]=x * y * (1 - c) + z * s; out->m[2][0]=z * x * (1 - c) - y * s; out->m[3][0]=0.0f; out->m[0][1]=x * y * (1 - c) - z * s; out->m[1][1]=y * y + c * (1 - y * y); out->m[2][1]=y * z * (1 - c) + x * s; out->m[3][1]=0.0f; out->m[0][2]=z * x * (1 - c) + y * s; out->m[1][2]=y * z * (1 - c) - x * s; out->m[2][2]=z * z + c * (1 - z * z); out->m[3][2]=0.0f; out->m[0][3]=0.0f; out->m[1][3]=0.0f; out->m[2][3]=0.0f; out->m[3][3]=1.0f; #else out->m[0][0]=x * x + c * (1 - x * x); out->m[0][1]=x * y * (1 - c) + z * s; out->m[0][2]=z * x * (1 - c) - y * s; out->m[0][3]=0.0f; out->m[1][0]=x * y * (1 - c) - z * s; out->m[1][1]=y * y + c * (1 - y * y); out->m[1][2]=y * z * (1 - c) + x * s; out->m[1][3]=0.0f; out->m[2][0]=z * x * (1 - c) + y * s; out->m[2][1]=y * z * (1 - c) - x * s; out->m[2][2]=z * z + c * (1 - z * z); out->m[2][3]=0.0f; out->m[3][0]=0.0f; out->m[3][1]=0.0f; out->m[3][2]=0.0f; out->m[3][3]=1.0f; #endif } void Matrix4x4_CreateScale (matrix4x4_t *out, double x) { out->m[0][0]=x; out->m[0][1]=0.0f; out->m[0][2]=0.0f; out->m[0][3]=0.0f; out->m[1][0]=0.0f; out->m[1][1]=x; out->m[1][2]=0.0f; out->m[1][3]=0.0f; out->m[2][0]=0.0f; out->m[2][1]=0.0f; out->m[2][2]=x; out->m[2][3]=0.0f; out->m[3][0]=0.0f; out->m[3][1]=0.0f; out->m[3][2]=0.0f; out->m[3][3]=1.0f; } void Matrix4x4_CreateScale3 (matrix4x4_t *out, double x, double y, double z) { out->m[0][0]=x; out->m[0][1]=0.0f; out->m[0][2]=0.0f; out->m[0][3]=0.0f; out->m[1][0]=0.0f; out->m[1][1]=y; out->m[1][2]=0.0f; out->m[1][3]=0.0f; out->m[2][0]=0.0f; out->m[2][1]=0.0f; out->m[2][2]=z; out->m[2][3]=0.0f; out->m[3][0]=0.0f; out->m[3][1]=0.0f; out->m[3][2]=0.0f; out->m[3][3]=1.0f; } void Matrix4x4_CreateFromQuakeEntity(matrix4x4_t *out, double x, double y, double z, double pitch, double yaw, double roll, double scale) { double angle, sr, sp, sy, cr, cp, cy; if (roll) { angle = yaw * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = pitch * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); angle = roll * (M_PI*2 / 360); sr = sin(angle); cr = cos(angle); #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = (cp*cy) * scale; out->m[1][0] = (sr*sp*cy+cr*-sy) * scale; out->m[2][0] = (cr*sp*cy+-sr*-sy) * scale; out->m[3][0] = x; out->m[0][1] = (cp*sy) * scale; out->m[1][1] = (sr*sp*sy+cr*cy) * scale; out->m[2][1] = (cr*sp*sy+-sr*cy) * scale; out->m[3][1] = y; out->m[0][2] = (-sp) * scale; out->m[1][2] = (sr*cp) * scale; out->m[2][2] = (cr*cp) * scale; out->m[3][2] = z; out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else out->m[0][0] = (cp*cy) * scale; out->m[0][1] = (sr*sp*cy+cr*-sy) * scale; out->m[0][2] = (cr*sp*cy+-sr*-sy) * scale; out->m[0][3] = x; out->m[1][0] = (cp*sy) * scale; out->m[1][1] = (sr*sp*sy+cr*cy) * scale; out->m[1][2] = (cr*sp*sy+-sr*cy) * scale; out->m[1][3] = y; out->m[2][0] = (-sp) * scale; out->m[2][1] = (sr*cp) * scale; out->m[2][2] = (cr*cp) * scale; out->m[2][3] = z; out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } else if (pitch) { angle = yaw * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); angle = pitch * (M_PI*2 / 360); sp = sin(angle); cp = cos(angle); #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = (cp*cy) * scale; out->m[1][0] = (-sy) * scale; out->m[2][0] = (sp*cy) * scale; out->m[3][0] = x; out->m[0][1] = (cp*sy) * scale; out->m[1][1] = (cy) * scale; out->m[2][1] = (sp*sy) * scale; out->m[3][1] = y; out->m[0][2] = (-sp) * scale; out->m[1][2] = 0; out->m[2][2] = (cp) * scale; out->m[3][2] = z; out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else out->m[0][0] = (cp*cy) * scale; out->m[0][1] = (-sy) * scale; out->m[0][2] = (sp*cy) * scale; out->m[0][3] = x; out->m[1][0] = (cp*sy) * scale; out->m[1][1] = (cy) * scale; out->m[1][2] = (sp*sy) * scale; out->m[1][3] = y; out->m[2][0] = (-sp) * scale; out->m[2][1] = 0; out->m[2][2] = (cp) * scale; out->m[2][3] = z; out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } else if (yaw) { angle = yaw * (M_PI*2 / 360); sy = sin(angle); cy = cos(angle); #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = (cy) * scale; out->m[1][0] = (-sy) * scale; out->m[2][0] = 0; out->m[3][0] = x; out->m[0][1] = (sy) * scale; out->m[1][1] = (cy) * scale; out->m[2][1] = 0; out->m[3][1] = y; out->m[0][2] = 0; out->m[1][2] = 0; out->m[2][2] = scale; out->m[3][2] = z; out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else out->m[0][0] = (cy) * scale; out->m[0][1] = (-sy) * scale; out->m[0][2] = 0; out->m[0][3] = x; out->m[1][0] = (sy) * scale; out->m[1][1] = (cy) * scale; out->m[1][2] = 0; out->m[1][3] = y; out->m[2][0] = 0; out->m[2][1] = 0; out->m[2][2] = scale; out->m[2][3] = z; out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } else { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = scale; out->m[1][0] = 0; out->m[2][0] = 0; out->m[3][0] = x; out->m[0][1] = 0; out->m[1][1] = scale; out->m[2][1] = 0; out->m[3][1] = y; out->m[0][2] = 0; out->m[1][2] = 0; out->m[2][2] = scale; out->m[3][2] = z; out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else out->m[0][0] = scale; out->m[0][1] = 0; out->m[0][2] = 0; out->m[0][3] = x; out->m[1][0] = 0; out->m[1][1] = scale; out->m[1][2] = 0; out->m[1][3] = y; out->m[2][0] = 0; out->m[2][1] = 0; out->m[2][2] = scale; out->m[2][3] = z; out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } } void Matrix4x4_QuakeToDuke3D(const matrix4x4_t *in, matrix4x4_t *out, double maxShearAngle) { // Sorry - this isn't direct at all. We can't just use an alternative to // Matrix4x4_CreateFromQuakeEntity as in some cases the input for // generating the view matrix is generated externally. vec3_t forward, left, up, angles; double scaleforward, scaleleft, scaleup; #ifdef MATRIX4x4_OPENGLORIENTATION VectorSet(forward, in->m[0][0], in->m[0][1], in->m[0][2]); VectorSet(left, in->m[1][0], in->m[1][1], in->m[1][2]); VectorSet(up, in->m[2][0], in->m[2][1], in->m[2][2]); #else VectorSet(forward, in->m[0][0], in->m[1][0], in->m[2][0]); VectorSet(left, in->m[0][1], in->m[1][1], in->m[2][1]); VectorSet(up, in->m[0][2], in->m[1][2], in->m[2][2]); #endif scaleforward = VectorNormalizeLength(forward); scaleleft = VectorNormalizeLength(left); scaleup = VectorNormalizeLength(up); AnglesFromVectors(angles, forward, up, false); AngleVectorsDuke3DFLU(angles, forward, left, up, maxShearAngle); VectorScale(forward, scaleforward, forward); VectorScale(left, scaleleft, left); VectorScale(up, scaleup, up); *out = *in; #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = forward[0]; out->m[1][0] = left[0]; out->m[2][0] = up[0]; out->m[0][1] = forward[1]; out->m[1][1] = left[1]; out->m[2][1] = up[1]; out->m[0][2] = forward[2]; out->m[1][2] = left[2]; out->m[2][2] = up[2]; #else out->m[0][0] = forward[0]; out->m[0][1] = left[0]; out->m[0][2] = up[0]; out->m[1][0] = forward[1]; out->m[1][1] = left[1]; out->m[1][2] = up[1]; out->m[2][0] = forward[2]; out->m[2][1] = left[2]; out->m[2][2] = up[2]; #endif } void Matrix4x4_ToVectors(const matrix4x4_t *in, float vx[3], float vy[3], float vz[3], float t[3]) { #ifdef MATRIX4x4_OPENGLORIENTATION vx[0] = in->m[0][0]; vx[1] = in->m[0][1]; vx[2] = in->m[0][2]; vy[0] = in->m[1][0]; vy[1] = in->m[1][1]; vy[2] = in->m[1][2]; vz[0] = in->m[2][0]; vz[1] = in->m[2][1]; vz[2] = in->m[2][2]; t [0] = in->m[3][0]; t [1] = in->m[3][1]; t [2] = in->m[3][2]; #else vx[0] = in->m[0][0]; vx[1] = in->m[1][0]; vx[2] = in->m[2][0]; vy[0] = in->m[0][1]; vy[1] = in->m[1][1]; vy[2] = in->m[2][1]; vz[0] = in->m[0][2]; vz[1] = in->m[1][2]; vz[2] = in->m[2][2]; t [0] = in->m[0][3]; t [1] = in->m[1][3]; t [2] = in->m[2][3]; #endif } void Matrix4x4_FromVectors(matrix4x4_t *out, const float vx[3], const float vy[3], const float vz[3], const float t[3]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = vx[0]; out->m[1][0] = vy[0]; out->m[2][0] = vz[0]; out->m[3][0] = t[0]; out->m[0][1] = vx[1]; out->m[1][1] = vy[1]; out->m[2][1] = vz[1]; out->m[3][1] = t[1]; out->m[0][2] = vx[2]; out->m[1][2] = vy[2]; out->m[2][2] = vz[2]; out->m[3][2] = t[2]; out->m[0][3] = 0.0f; out->m[1][3] = 0.0f; out->m[2][3] = 0.0f; out->m[3][3] = 1.0f; #else out->m[0][0] = vx[0]; out->m[0][1] = vy[0]; out->m[0][2] = vz[0]; out->m[0][3] = t[0]; out->m[1][0] = vx[1]; out->m[1][1] = vy[1]; out->m[1][2] = vz[1]; out->m[1][3] = t[1]; out->m[2][0] = vx[2]; out->m[2][1] = vy[2]; out->m[2][2] = vz[2]; out->m[2][3] = t[2]; out->m[3][0] = 0.0f; out->m[3][1] = 0.0f; out->m[3][2] = 0.0f; out->m[3][3] = 1.0f; #endif } void Matrix4x4_ToArrayDoubleGL(const matrix4x4_t *in, double out[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[0][3]; out[ 4] = in->m[1][0]; out[ 5] = in->m[1][1]; out[ 6] = in->m[1][2]; out[ 7] = in->m[1][3]; out[ 8] = in->m[2][0]; out[ 9] = in->m[2][1]; out[10] = in->m[2][2]; out[11] = in->m[2][3]; out[12] = in->m[3][0]; out[13] = in->m[3][1]; out[14] = in->m[3][2]; out[15] = in->m[3][3]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[3][0]; out[ 4] = in->m[0][1]; out[ 5] = in->m[1][1]; out[ 6] = in->m[2][1]; out[ 7] = in->m[3][1]; out[ 8] = in->m[0][2]; out[ 9] = in->m[1][2]; out[10] = in->m[2][2]; out[11] = in->m[3][2]; out[12] = in->m[0][3]; out[13] = in->m[1][3]; out[14] = in->m[2][3]; out[15] = in->m[3][3]; #endif } void Matrix4x4_FromArrayDoubleGL (matrix4x4_t *out, const double in[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = in[3]; out->m[1][0] = in[4]; out->m[1][1] = in[5]; out->m[1][2] = in[6]; out->m[1][3] = in[7]; out->m[2][0] = in[8]; out->m[2][1] = in[9]; out->m[2][2] = in[10]; out->m[2][3] = in[11]; out->m[3][0] = in[12]; out->m[3][1] = in[13]; out->m[3][2] = in[14]; out->m[3][3] = in[15]; #else out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = in[3]; out->m[0][1] = in[4]; out->m[1][1] = in[5]; out->m[2][1] = in[6]; out->m[3][1] = in[7]; out->m[0][2] = in[8]; out->m[1][2] = in[9]; out->m[2][2] = in[10]; out->m[3][2] = in[11]; out->m[0][3] = in[12]; out->m[1][3] = in[13]; out->m[2][3] = in[14]; out->m[3][3] = in[15]; #endif } void Matrix4x4_ToArrayDoubleD3D(const matrix4x4_t *in, double out[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[3][0]; out[ 4] = in->m[0][1]; out[ 5] = in->m[1][1]; out[ 6] = in->m[2][1]; out[ 7] = in->m[3][1]; out[ 8] = in->m[0][2]; out[ 9] = in->m[1][2]; out[10] = in->m[2][2]; out[11] = in->m[3][2]; out[12] = in->m[0][3]; out[13] = in->m[1][3]; out[14] = in->m[2][3]; out[15] = in->m[3][3]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[0][3]; out[ 4] = in->m[1][0]; out[ 5] = in->m[1][1]; out[ 6] = in->m[1][2]; out[ 7] = in->m[1][3]; out[ 8] = in->m[2][0]; out[ 9] = in->m[2][1]; out[10] = in->m[2][2]; out[11] = in->m[2][3]; out[12] = in->m[3][0]; out[13] = in->m[3][1]; out[14] = in->m[3][2]; out[15] = in->m[3][3]; #endif } void Matrix4x4_FromArrayDoubleD3D (matrix4x4_t *out, const double in[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = in[3]; out->m[0][1] = in[4]; out->m[1][1] = in[5]; out->m[2][1] = in[6]; out->m[3][1] = in[7]; out->m[0][2] = in[8]; out->m[1][2] = in[9]; out->m[2][2] = in[10]; out->m[3][2] = in[11]; out->m[0][3] = in[12]; out->m[1][3] = in[13]; out->m[2][3] = in[14]; out->m[3][3] = in[15]; #else out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = in[3]; out->m[1][0] = in[4]; out->m[1][1] = in[5]; out->m[1][2] = in[6]; out->m[1][3] = in[7]; out->m[2][0] = in[8]; out->m[2][1] = in[9]; out->m[2][2] = in[10]; out->m[2][3] = in[11]; out->m[3][0] = in[12]; out->m[3][1] = in[13]; out->m[3][2] = in[14]; out->m[3][3] = in[15]; #endif } void Matrix4x4_ToArrayFloatGL(const matrix4x4_t *in, float out[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[0][3]; out[ 4] = in->m[1][0]; out[ 5] = in->m[1][1]; out[ 6] = in->m[1][2]; out[ 7] = in->m[1][3]; out[ 8] = in->m[2][0]; out[ 9] = in->m[2][1]; out[10] = in->m[2][2]; out[11] = in->m[2][3]; out[12] = in->m[3][0]; out[13] = in->m[3][1]; out[14] = in->m[3][2]; out[15] = in->m[3][3]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[3][0]; out[ 4] = in->m[0][1]; out[ 5] = in->m[1][1]; out[ 6] = in->m[2][1]; out[ 7] = in->m[3][1]; out[ 8] = in->m[0][2]; out[ 9] = in->m[1][2]; out[10] = in->m[2][2]; out[11] = in->m[3][2]; out[12] = in->m[0][3]; out[13] = in->m[1][3]; out[14] = in->m[2][3]; out[15] = in->m[3][3]; #endif } void Matrix4x4_FromArrayFloatGL (matrix4x4_t *out, const float in[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = in[3]; out->m[1][0] = in[4]; out->m[1][1] = in[5]; out->m[1][2] = in[6]; out->m[1][3] = in[7]; out->m[2][0] = in[8]; out->m[2][1] = in[9]; out->m[2][2] = in[10]; out->m[2][3] = in[11]; out->m[3][0] = in[12]; out->m[3][1] = in[13]; out->m[3][2] = in[14]; out->m[3][3] = in[15]; #else out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = in[3]; out->m[0][1] = in[4]; out->m[1][1] = in[5]; out->m[2][1] = in[6]; out->m[3][1] = in[7]; out->m[0][2] = in[8]; out->m[1][2] = in[9]; out->m[2][2] = in[10]; out->m[3][2] = in[11]; out->m[0][3] = in[12]; out->m[1][3] = in[13]; out->m[2][3] = in[14]; out->m[3][3] = in[15]; #endif } void Matrix4x4_ToArrayFloatD3D(const matrix4x4_t *in, float out[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[3][0]; out[ 4] = in->m[0][1]; out[ 5] = in->m[1][1]; out[ 6] = in->m[2][1]; out[ 7] = in->m[3][1]; out[ 8] = in->m[0][2]; out[ 9] = in->m[1][2]; out[10] = in->m[2][2]; out[11] = in->m[3][2]; out[12] = in->m[0][3]; out[13] = in->m[1][3]; out[14] = in->m[2][3]; out[15] = in->m[3][3]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[0][3]; out[ 4] = in->m[1][0]; out[ 5] = in->m[1][1]; out[ 6] = in->m[1][2]; out[ 7] = in->m[1][3]; out[ 8] = in->m[2][0]; out[ 9] = in->m[2][1]; out[10] = in->m[2][2]; out[11] = in->m[2][3]; out[12] = in->m[3][0]; out[13] = in->m[3][1]; out[14] = in->m[3][2]; out[15] = in->m[3][3]; #endif } void Matrix4x4_FromArrayFloatD3D (matrix4x4_t *out, const float in[16]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = in[3]; out->m[0][1] = in[4]; out->m[1][1] = in[5]; out->m[2][1] = in[6]; out->m[3][1] = in[7]; out->m[0][2] = in[8]; out->m[1][2] = in[9]; out->m[2][2] = in[10]; out->m[3][2] = in[11]; out->m[0][3] = in[12]; out->m[1][3] = in[13]; out->m[2][3] = in[14]; out->m[3][3] = in[15]; #else out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = in[3]; out->m[1][0] = in[4]; out->m[1][1] = in[5]; out->m[1][2] = in[6]; out->m[1][3] = in[7]; out->m[2][0] = in[8]; out->m[2][1] = in[9]; out->m[2][2] = in[10]; out->m[2][3] = in[11]; out->m[3][0] = in[12]; out->m[3][1] = in[13]; out->m[3][2] = in[14]; out->m[3][3] = in[15]; #endif } void Matrix4x4_ToArray12FloatGL(const matrix4x4_t *in, float out[12]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[1][0]; out[ 4] = in->m[1][1]; out[ 5] = in->m[1][2]; out[ 6] = in->m[2][0]; out[ 7] = in->m[2][1]; out[ 8] = in->m[2][2]; out[ 9] = in->m[3][0]; out[10] = in->m[3][1]; out[11] = in->m[3][2]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[0][1]; out[ 4] = in->m[1][1]; out[ 5] = in->m[2][1]; out[ 6] = in->m[0][2]; out[ 7] = in->m[1][2]; out[ 8] = in->m[2][2]; out[ 9] = in->m[0][3]; out[10] = in->m[1][3]; out[11] = in->m[2][3]; #endif } void Matrix4x4_FromArray12FloatGL(matrix4x4_t *out, const float in[12]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = 0; out->m[1][0] = in[3]; out->m[1][1] = in[4]; out->m[1][2] = in[5]; out->m[1][3] = 0; out->m[2][0] = in[6]; out->m[2][1] = in[7]; out->m[2][2] = in[8]; out->m[2][3] = 0; out->m[3][0] = in[9]; out->m[3][1] = in[10]; out->m[3][2] = in[11]; out->m[3][3] = 1; #else out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = 0; out->m[0][1] = in[3]; out->m[1][1] = in[4]; out->m[2][1] = in[5]; out->m[3][1] = 0; out->m[0][2] = in[6]; out->m[1][2] = in[7]; out->m[2][2] = in[8]; out->m[3][2] = 0; out->m[0][3] = in[9]; out->m[1][3] = in[10]; out->m[2][3] = in[11]; out->m[3][3] = 1; #endif } void Matrix4x4_ToArray12FloatD3D(const matrix4x4_t *in, float out[12]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[ 0] = in->m[0][0]; out[ 1] = in->m[1][0]; out[ 2] = in->m[2][0]; out[ 3] = in->m[3][0]; out[ 4] = in->m[0][1]; out[ 5] = in->m[1][1]; out[ 6] = in->m[2][1]; out[ 7] = in->m[3][1]; out[ 8] = in->m[0][2]; out[ 9] = in->m[1][2]; out[10] = in->m[2][2]; out[11] = in->m[3][2]; #else out[ 0] = in->m[0][0]; out[ 1] = in->m[0][1]; out[ 2] = in->m[0][2]; out[ 3] = in->m[0][3]; out[ 4] = in->m[1][0]; out[ 5] = in->m[1][1]; out[ 6] = in->m[1][2]; out[ 7] = in->m[1][3]; out[ 8] = in->m[2][0]; out[ 9] = in->m[2][1]; out[10] = in->m[2][2]; out[11] = in->m[2][3]; #endif } void Matrix4x4_FromArray12FloatD3D(matrix4x4_t *out, const float in[12]) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[0][0] = in[0]; out->m[1][0] = in[1]; out->m[2][0] = in[2]; out->m[3][0] = in[3]; out->m[0][1] = in[4]; out->m[1][1] = in[5]; out->m[2][1] = in[6]; out->m[3][1] = in[7]; out->m[0][2] = in[8]; out->m[1][2] = in[9]; out->m[2][2] = in[10]; out->m[3][2] = in[11]; out->m[0][3] = 0; out->m[1][3] = 0; out->m[2][3] = 0; out->m[3][3] = 1; #else out->m[0][0] = in[0]; out->m[0][1] = in[1]; out->m[0][2] = in[2]; out->m[0][3] = in[3]; out->m[1][0] = in[4]; out->m[1][1] = in[5]; out->m[1][2] = in[6]; out->m[1][3] = in[7]; out->m[2][0] = in[8]; out->m[2][1] = in[9]; out->m[2][2] = in[10]; out->m[2][3] = in[11]; out->m[3][0] = 0; out->m[3][1] = 0; out->m[3][2] = 0; out->m[3][3] = 1; #endif } void Matrix4x4_FromOriginQuat(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z, double w) { #ifdef MATRIX4x4_OPENGLORIENTATION m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; #else m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; #endif } // see http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm void Matrix4x4_ToOrigin3Quat4Float(const matrix4x4_t *m, float *origin, float *quat) { #if 0 float s; quat[3] = sqrt(1.0f + m->m[0][0] + m->m[1][1] + m->m[2][2]) * 0.5f; s = 0.25f / quat[3]; #ifdef MATRIX4x4_OPENGLORIENTATION origin[0] = m->m[3][0]; origin[1] = m->m[3][1]; origin[2] = m->m[3][2]; quat[0] = (m->m[1][2] - m->m[2][1]) * s; quat[1] = (m->m[2][0] - m->m[0][2]) * s; quat[2] = (m->m[0][1] - m->m[1][0]) * s; #else origin[0] = m->m[0][3]; origin[1] = m->m[1][3]; origin[2] = m->m[2][3]; quat[0] = (m->m[2][1] - m->m[1][2]) * s; quat[1] = (m->m[0][2] - m->m[2][0]) * s; quat[2] = (m->m[1][0] - m->m[0][1]) * s; #endif #else #ifdef MATRIX4x4_OPENGLORIENTATION float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; origin[0] = m->m[3][0]; origin[1] = m->m[3][1]; origin[2] = m->m[3][2]; if(trace > 0) { float r = sqrt(1.0f + trace), inv = 0.5f / r; quat[0] = (m->m[1][2] - m->m[2][1]) * inv; quat[1] = (m->m[2][0] - m->m[0][2]) * inv; quat[2] = (m->m[0][1] - m->m[1][0]) * inv; quat[3] = 0.5f * r; } else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) { float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; quat[0] = 0.5f * r; quat[1] = (m->m[0][1] + m->m[1][0]) * inv; quat[2] = (m->m[2][0] + m->m[0][2]) * inv; quat[3] = (m->m[1][2] - m->m[2][1]) * inv; } else if(m->m[1][1] > m->m[2][2]) { float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; quat[0] = (m->m[0][1] + m->m[1][0]) * inv; quat[1] = 0.5f * r; quat[2] = (m->m[1][2] + m->m[2][1]) * inv; quat[3] = (m->m[2][0] - m->m[0][2]) * inv; } else { float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; quat[0] = (m->m[2][0] + m->m[0][2]) * inv; quat[1] = (m->m[1][2] + m->m[2][1]) * inv; quat[2] = 0.5f * r; quat[3] = (m->m[0][1] - m->m[1][0]) * inv; } #else float trace = m->m[0][0] + m->m[1][1] + m->m[2][2]; origin[0] = m->m[0][3]; origin[1] = m->m[1][3]; origin[2] = m->m[2][3]; if(trace > 0) { float r = sqrt(1.0f + trace), inv = 0.5f / r; quat[0] = (m->m[2][1] - m->m[1][2]) * inv; quat[1] = (m->m[0][2] - m->m[2][0]) * inv; quat[2] = (m->m[1][0] - m->m[0][1]) * inv; quat[3] = 0.5f * r; } else if(m->m[0][0] > m->m[1][1] && m->m[0][0] > m->m[2][2]) { float r = sqrt(1.0f + m->m[0][0] - m->m[1][1] - m->m[2][2]), inv = 0.5f / r; quat[0] = 0.5f * r; quat[1] = (m->m[1][0] + m->m[0][1]) * inv; quat[2] = (m->m[0][2] + m->m[2][0]) * inv; quat[3] = (m->m[2][1] - m->m[1][2]) * inv; } else if(m->m[1][1] > m->m[2][2]) { float r = sqrt(1.0f + m->m[1][1] - m->m[0][0] - m->m[2][2]), inv = 0.5f / r; quat[0] = (m->m[1][0] + m->m[0][1]) * inv; quat[1] = 0.5f * r; quat[2] = (m->m[2][1] + m->m[1][2]) * inv; quat[3] = (m->m[0][2] - m->m[2][0]) * inv; } else { float r = sqrt(1.0f + m->m[2][2] - m->m[0][0] - m->m[1][1]), inv = 0.5f / r; quat[0] = (m->m[0][2] + m->m[2][0]) * inv; quat[1] = (m->m[2][1] + m->m[1][2]) * inv; quat[2] = 0.5f * r; quat[3] = (m->m[1][0] - m->m[0][1]) * inv; } #endif #endif } // LordHavoc: I got this code from: //http://www.doom3world.org/phpbb2/viewtopic.php?t=2884 void Matrix4x4_FromDoom3Joint(matrix4x4_t *m, double ox, double oy, double oz, double x, double y, double z) { double w = 1.0f - (x*x+y*y+z*z); w = w > 0.0f ? -sqrt(w) : 0.0f; #ifdef MATRIX4x4_OPENGLORIENTATION m->m[0][0]=1-2*(y*y+z*z);m->m[1][0]= 2*(x*y-z*w);m->m[2][0]= 2*(x*z+y*w);m->m[3][0]=ox; m->m[0][1]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[2][1]= 2*(y*z-x*w);m->m[3][1]=oy; m->m[0][2]= 2*(x*z-y*w);m->m[1][2]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[3][2]=oz; m->m[0][3]= 0 ;m->m[1][3]= 0 ;m->m[2][3]= 0 ;m->m[3][3]=1; #else m->m[0][0]=1-2*(y*y+z*z);m->m[0][1]= 2*(x*y-z*w);m->m[0][2]= 2*(x*z+y*w);m->m[0][3]=ox; m->m[1][0]= 2*(x*y+z*w);m->m[1][1]=1-2*(x*x+z*z);m->m[1][2]= 2*(y*z-x*w);m->m[1][3]=oy; m->m[2][0]= 2*(x*z-y*w);m->m[2][1]= 2*(y*z+x*w);m->m[2][2]=1-2*(x*x+y*y);m->m[2][3]=oz; m->m[3][0]= 0 ;m->m[3][1]= 0 ;m->m[3][2]= 0 ;m->m[3][3]=1; #endif } void Matrix4x4_FromBonePose7s(matrix4x4_t *m, float originscale, const short *pose7s) { float origin[3]; float quat[4]; float quatscale = pose7s[6] > 0 ? -1.0f / 32767.0f : 1.0f / 32767.0f; origin[0] = pose7s[0] * originscale; origin[1] = pose7s[1] * originscale; origin[2] = pose7s[2] * originscale; quat[0] = pose7s[3] * quatscale; quat[1] = pose7s[4] * quatscale; quat[2] = pose7s[5] * quatscale; quat[3] = pose7s[6] * quatscale; Matrix4x4_FromOriginQuat(m, origin[0], origin[1], origin[2], quat[0], quat[1], quat[2], quat[3]); } void Matrix4x4_ToBonePose7s(const matrix4x4_t *m, float origininvscale, short *pose7s) { float origin[3]; float quat[4]; float quatscale; Matrix4x4_ToOrigin3Quat4Float(m, origin, quat); // normalize quaternion so that it is unit length quatscale = quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]+quat[3]*quat[3]; if (quatscale) quatscale = (quat[3] >= 0 ? -32767.0f : 32767.0f) / sqrt(quatscale); // use a negative scale on the quat because the above function produces a // positive quat[3] and canonical quaternions have negative quat[3] pose7s[0] = origin[0] * origininvscale; pose7s[1] = origin[1] * origininvscale; pose7s[2] = origin[2] * origininvscale; pose7s[3] = quat[0] * quatscale; pose7s[4] = quat[1] * quatscale; pose7s[5] = quat[2] * quatscale; pose7s[6] = quat[3] * quatscale; } void Matrix4x4_Blend (matrix4x4_t *out, const matrix4x4_t *in1, const matrix4x4_t *in2, double blend) { double iblend = 1 - blend; out->m[0][0] = in1->m[0][0] * iblend + in2->m[0][0] * blend; out->m[0][1] = in1->m[0][1] * iblend + in2->m[0][1] * blend; out->m[0][2] = in1->m[0][2] * iblend + in2->m[0][2] * blend; out->m[0][3] = in1->m[0][3] * iblend + in2->m[0][3] * blend; out->m[1][0] = in1->m[1][0] * iblend + in2->m[1][0] * blend; out->m[1][1] = in1->m[1][1] * iblend + in2->m[1][1] * blend; out->m[1][2] = in1->m[1][2] * iblend + in2->m[1][2] * blend; out->m[1][3] = in1->m[1][3] * iblend + in2->m[1][3] * blend; out->m[2][0] = in1->m[2][0] * iblend + in2->m[2][0] * blend; out->m[2][1] = in1->m[2][1] * iblend + in2->m[2][1] * blend; out->m[2][2] = in1->m[2][2] * iblend + in2->m[2][2] * blend; out->m[2][3] = in1->m[2][3] * iblend + in2->m[2][3] * blend; out->m[3][0] = in1->m[3][0] * iblend + in2->m[3][0] * blend; out->m[3][1] = in1->m[3][1] * iblend + in2->m[3][1] * blend; out->m[3][2] = in1->m[3][2] * iblend + in2->m[3][2] * blend; out->m[3][3] = in1->m[3][3] * iblend + in2->m[3][3] * blend; } void Matrix4x4_Transform (const matrix4x4_t *in, const float v[3], float out[3]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + in->m[3][0]; out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + in->m[3][1]; out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + in->m[3][2]; #else out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + in->m[0][3]; out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + in->m[1][3]; out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + in->m[2][3]; #endif } void Matrix4x4_Transform4 (const matrix4x4_t *in, const float v[4], float out[4]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0] + v[3] * in->m[3][0]; out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1] + v[3] * in->m[3][1]; out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2] + v[3] * in->m[3][2]; out[3] = v[0] * in->m[0][3] + v[1] * in->m[1][3] + v[2] * in->m[2][3] + v[3] * in->m[3][3]; #else out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2] + v[3] * in->m[0][3]; out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2] + v[3] * in->m[1][3]; out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2] + v[3] * in->m[2][3]; out[3] = v[0] * in->m[3][0] + v[1] * in->m[3][1] + v[2] * in->m[3][2] + v[3] * in->m[3][3]; #endif } void Matrix4x4_Transform3x3 (const matrix4x4_t *in, const float v[3], float out[3]) { #ifdef MATRIX4x4_OPENGLORIENTATION out[0] = v[0] * in->m[0][0] + v[1] * in->m[1][0] + v[2] * in->m[2][0]; out[1] = v[0] * in->m[0][1] + v[1] * in->m[1][1] + v[2] * in->m[2][1]; out[2] = v[0] * in->m[0][2] + v[1] * in->m[1][2] + v[2] * in->m[2][2]; #else out[0] = v[0] * in->m[0][0] + v[1] * in->m[0][1] + v[2] * in->m[0][2]; out[1] = v[0] * in->m[1][0] + v[1] * in->m[1][1] + v[2] * in->m[1][2]; out[2] = v[0] * in->m[2][0] + v[1] * in->m[2][1] + v[2] * in->m[2][2]; #endif } // transforms a positive distance plane (A*x+B*y+C*z-D=0) through a rotation or translation matrix void Matrix4x4_TransformPositivePlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) { float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); float iscale = 1.0f / scale; #ifdef MATRIX4x4_OPENGLORIENTATION o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; o[3] = d * scale + (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); #else o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; o[3] = d * scale + (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); #endif } // transforms a standard plane (A*x+B*y+C*z+D=0) through a rotation or translation matrix void Matrix4x4_TransformStandardPlane(const matrix4x4_t *in, float x, float y, float z, float d, float *o) { float scale = sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); float iscale = 1.0f / scale; #ifdef MATRIX4x4_OPENGLORIENTATION o[0] = (x * in->m[0][0] + y * in->m[1][0] + z * in->m[2][0]) * iscale; o[1] = (x * in->m[0][1] + y * in->m[1][1] + z * in->m[2][1]) * iscale; o[2] = (x * in->m[0][2] + y * in->m[1][2] + z * in->m[2][2]) * iscale; o[3] = d * scale - (o[0] * in->m[3][0] + o[1] * in->m[3][1] + o[2] * in->m[3][2]); #else o[0] = (x * in->m[0][0] + y * in->m[0][1] + z * in->m[0][2]) * iscale; o[1] = (x * in->m[1][0] + y * in->m[1][1] + z * in->m[1][2]) * iscale; o[2] = (x * in->m[2][0] + y * in->m[2][1] + z * in->m[2][2]) * iscale; o[3] = d * scale - (o[0] * in->m[0][3] + o[1] * in->m[1][3] + o[2] * in->m[2][3]); #endif } /* void Matrix4x4_SimpleUntransform (const matrix4x4_t *in, const float v[3], float out[3]) { double t[3]; #ifdef MATRIX4x4_OPENGLORIENTATION t[0] = v[0] - in->m[3][0]; t[1] = v[1] - in->m[3][1]; t[2] = v[2] - in->m[3][2]; out[0] = t[0] * in->m[0][0] + t[1] * in->m[0][1] + t[2] * in->m[0][2]; out[1] = t[0] * in->m[1][0] + t[1] * in->m[1][1] + t[2] * in->m[1][2]; out[2] = t[0] * in->m[2][0] + t[1] * in->m[2][1] + t[2] * in->m[2][2]; #else t[0] = v[0] - in->m[0][3]; t[1] = v[1] - in->m[1][3]; t[2] = v[2] - in->m[2][3]; out[0] = t[0] * in->m[0][0] + t[1] * in->m[1][0] + t[2] * in->m[2][0]; out[1] = t[0] * in->m[0][1] + t[1] * in->m[1][1] + t[2] * in->m[2][1]; out[2] = t[0] * in->m[0][2] + t[1] * in->m[1][2] + t[2] * in->m[2][2]; #endif } */ // FIXME: optimize void Matrix4x4_ConcatTranslate (matrix4x4_t *out, double x, double y, double z) { matrix4x4_t base, temp; base = *out; Matrix4x4_CreateTranslate(&temp, x, y, z); Matrix4x4_Concat(out, &base, &temp); } // FIXME: optimize void Matrix4x4_ConcatRotate (matrix4x4_t *out, double angle, double x, double y, double z) { matrix4x4_t base, temp; base = *out; Matrix4x4_CreateRotate(&temp, angle, x, y, z); Matrix4x4_Concat(out, &base, &temp); } // FIXME: optimize void Matrix4x4_ConcatScale (matrix4x4_t *out, double x) { matrix4x4_t base, temp; base = *out; Matrix4x4_CreateScale(&temp, x); Matrix4x4_Concat(out, &base, &temp); } // FIXME: optimize void Matrix4x4_ConcatScale3 (matrix4x4_t *out, double x, double y, double z) { matrix4x4_t base, temp; base = *out; Matrix4x4_CreateScale3(&temp, x, y, z); Matrix4x4_Concat(out, &base, &temp); } void Matrix4x4_OriginFromMatrix (const matrix4x4_t *in, float *out) { #ifdef MATRIX4x4_OPENGLORIENTATION out[0] = in->m[3][0]; out[1] = in->m[3][1]; out[2] = in->m[3][2]; #else out[0] = in->m[0][3]; out[1] = in->m[1][3]; out[2] = in->m[2][3]; #endif } double Matrix4x4_ScaleFromMatrix (const matrix4x4_t *in) { // we only support uniform scaling, so assume the first row is enough return sqrt(in->m[0][0] * in->m[0][0] + in->m[0][1] * in->m[0][1] + in->m[0][2] * in->m[0][2]); } void Matrix4x4_SetOrigin (matrix4x4_t *out, double x, double y, double z) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[3][0] = x; out->m[3][1] = y; out->m[3][2] = z; #else out->m[0][3] = x; out->m[1][3] = y; out->m[2][3] = z; #endif } void Matrix4x4_AdjustOrigin (matrix4x4_t *out, double x, double y, double z) { #ifdef MATRIX4x4_OPENGLORIENTATION out->m[3][0] += x; out->m[3][1] += y; out->m[3][2] += z; #else out->m[0][3] += x; out->m[1][3] += y; out->m[2][3] += z; #endif } void Matrix4x4_Scale (matrix4x4_t *out, double rotatescale, double originscale) { out->m[0][0] *= rotatescale; out->m[0][1] *= rotatescale; out->m[0][2] *= rotatescale; out->m[1][0] *= rotatescale; out->m[1][1] *= rotatescale; out->m[1][2] *= rotatescale; out->m[2][0] *= rotatescale; out->m[2][1] *= rotatescale; out->m[2][2] *= rotatescale; #ifdef MATRIX4x4_OPENGLORIENTATION out->m[3][0] *= originscale; out->m[3][1] *= originscale; out->m[3][2] *= originscale; #else out->m[0][3] *= originscale; out->m[1][3] *= originscale; out->m[2][3] *= originscale; #endif } void Matrix4x4_Abs (matrix4x4_t *out) { out->m[0][0] = fabs(out->m[0][0]); out->m[0][1] = fabs(out->m[0][1]); out->m[0][2] = fabs(out->m[0][2]); out->m[1][0] = fabs(out->m[1][0]); out->m[1][1] = fabs(out->m[1][1]); out->m[1][2] = fabs(out->m[1][2]); out->m[2][0] = fabs(out->m[2][0]); out->m[2][1] = fabs(out->m[2][1]); out->m[2][2] = fabs(out->m[2][2]); } darkplaces/darkplaces-vs2010.sln0000664000175000017500000000572513067716220015754 0ustar kalevkalev Microsoft Visual Studio Solution File, Format Version 11.00 # Visual C++ Express 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl-vs2010", "darkplaces-wgl-vs2010.vcxproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl-vs2010", "darkplaces-sdl-vs2010.vcxproj", "{7470C8B3-FCA7-42D3-9909-5F9E735C7C51}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated-vs2010", "darkplaces-dedicated-vs2010.vcxproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl2-vs2010", "darkplaces-sdl2-vs2010.vcxproj", "{72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.ActiveCfg = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.Build.0 = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.ActiveCfg = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.Build.0 = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.ActiveCfg = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.Build.0 = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.ActiveCfg = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal darkplaces/sv_move.c0000664000175000017500000002660113067716222014014 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_move.c -- monster movement #include "quakedef.h" #include "prvm_cmds.h" /* ============= SV_CheckBottom Returns false if any part of the bottom of the entity is off an edge that is not a staircase. ============= */ int c_yes, c_no; qboolean SV_CheckBottom (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; vec3_t mins, maxs, start, stop; trace_t trace; int x, y; float mid, bottom; VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); VectorAdd (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); // if all of the points under the corners are solid world, don't bother // with the tougher checks // the corners must be within 16 of the midpoint start[2] = mins[2] - 1; for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = x ? maxs[0] : mins[0]; start[1] = y ? maxs[1] : mins[1]; if (!(SV_PointSuperContents(start) & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY))) goto realcheck; } c_yes++; return true; // we got out easy realcheck: c_no++; // // check it for real... // start[2] = mins[2]; // the midpoint must be within 16 of the bottom start[0] = stop[0] = (mins[0] + maxs[0])*0.5; start[1] = stop[1] = (mins[1] + maxs[1])*0.5; stop[2] = start[2] - 2*sv_stepheight.value; trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.fraction == 1.0) return false; mid = bottom = trace.endpos[2]; // the corners must be within 16 of the midpoint for (x=0 ; x<=1 ; x++) for (y=0 ; y<=1 ; y++) { start[0] = stop[0] = x ? maxs[0] : mins[0]; start[1] = stop[1] = y ? maxs[1] : mins[1]; trace = SV_TraceLine(start, stop, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.fraction != 1.0 && trace.endpos[2] > bottom) bottom = trace.endpos[2]; if (trace.fraction == 1.0 || mid - trace.endpos[2] > sv_stepheight.value) return false; } c_yes++; return true; } /* ============= SV_movestep Called by monster program code. The move will be adjusted for slopes and stairs, but if the move isn't possible, no move is done and false is returned ============= */ qboolean SV_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean noenemy, qboolean settrace) { prvm_prog_t *prog = SVVM_prog; float dz; vec3_t oldorg, neworg, end, traceendpos, entorigin, entmins, entmaxs; trace_t trace; int i; prvm_edict_t *enemy; // try the move VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); VectorCopy(PRVM_serveredictvector(ent, mins), entmins); VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); // flying monsters don't step up if ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) { // try one move with vertical motion, then one without for (i=0 ; i<2 ; i++) { VectorAdd (PRVM_serveredictvector(ent, origin), move, neworg); if (noenemy) enemy = prog->edicts; else { enemy = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)); if (i == 0 && enemy != prog->edicts) { dz = PRVM_serveredictvector(ent, origin)[2] - PRVM_serveredictvector(enemy, origin)[2]; if (dz > 40) neworg[2] -= 8; if (dz < 30) neworg[2] += 8; } } VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); trace = SV_TraceBox(entorigin, entmins, entmaxs, neworg, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.fraction == 1) { VectorCopy(trace.endpos, traceendpos); if (((int)PRVM_serveredictfloat(ent, flags) & FL_SWIM) && !(SV_PointSuperContents(traceendpos) & SUPERCONTENTS_LIQUIDSMASK)) return false; // swim monster left water VectorCopy (traceendpos, PRVM_serveredictvector(ent, origin)); if (relink) { SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } return true; } if (enemy == prog->edicts) break; } return false; } // push down from a step height above the wished position neworg[2] += sv_stepheight.value; VectorCopy (neworg, end); end[2] -= sv_stepheight.value*2; trace = SV_TraceBox(neworg, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.startsolid) { neworg[2] -= sv_stepheight.value; trace = SV_TraceBox(neworg, entmins, entmaxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (trace.startsolid) return false; } if (trace.fraction == 1) { // if monster had the ground pulled out, go ahead and fall if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) { VectorAdd (PRVM_serveredictvector(ent, origin), move, PRVM_serveredictvector(ent, origin)); if (relink) { SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; return true; } return false; // walked off an edge } // check point traces down for dangling corners VectorCopy (trace.endpos, PRVM_serveredictvector(ent, origin)); if (!SV_CheckBottom (ent)) { if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) { // entity had floor mostly pulled out from underneath it // and is trying to correct if (relink) { SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } return true; } VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); return false; } if ( (int)PRVM_serveredictfloat(ent, flags) & FL_PARTIALGROUND ) PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_PARTIALGROUND; // gameplayfix: check if reached pretty steep plane and bail if ( ! ( (int)PRVM_serveredictfloat(ent, flags) & (FL_SWIM | FL_FLY) ) && sv_gameplayfix_nostepmoveonsteepslopes.integer ) { if (trace.plane.normal[ 2 ] < 0.5) { VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); return false; } } PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); // the move is ok if (relink) { SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } return true; } //============================================================================ /* ====================== SV_StepDirection Turns to the movement direction, and walks the current distance if facing it. ====================== */ static qboolean SV_StepDirection (prvm_edict_t *ent, float yaw, float dist) { prvm_prog_t *prog = SVVM_prog; vec3_t move, oldorigin; float delta; PRVM_serveredictfloat(ent, ideal_yaw) = yaw; VM_changeyaw(prog); yaw = yaw*M_PI*2 / 360; move[0] = cos(yaw)*dist; move[1] = sin(yaw)*dist; move[2] = 0; VectorCopy (PRVM_serveredictvector(ent, origin), oldorigin); if (SV_movestep (ent, move, false, false, false)) { delta = PRVM_serveredictvector(ent, angles)[YAW] - PRVM_serveredictfloat(ent, ideal_yaw); if (delta > 45 && delta < 315) { // not turned far enough, so don't take the step VectorCopy (oldorigin, PRVM_serveredictvector(ent, origin)); } SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); return true; } SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); return false; } /* ====================== SV_FixCheckBottom ====================== */ static void SV_FixCheckBottom (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_PARTIALGROUND; } /* ================ SV_NewChaseDir ================ */ #define DI_NODIR -1 static void SV_NewChaseDir (prvm_edict_t *actor, prvm_edict_t *enemy, float dist) { prvm_prog_t *prog = SVVM_prog; float deltax,deltay; float d[3]; float tdir, olddir, turnaround; olddir = ANGLEMOD((int)(PRVM_serveredictfloat(actor, ideal_yaw)/45)*45); turnaround = ANGLEMOD(olddir - 180); deltax = PRVM_serveredictvector(enemy, origin)[0] - PRVM_serveredictvector(actor, origin)[0]; deltay = PRVM_serveredictvector(enemy, origin)[1] - PRVM_serveredictvector(actor, origin)[1]; if (deltax>10) d[1]= 0; else if (deltax<-10) d[1]= 180; else d[1]= DI_NODIR; if (deltay<-10) d[2]= 270; else if (deltay>10) d[2]= 90; else d[2]= DI_NODIR; // try direct route if (d[1] != DI_NODIR && d[2] != DI_NODIR) { if (d[1] == 0) tdir = d[2] == 90 ? 45 : 315; else tdir = d[2] == 90 ? 135 : 215; if (tdir != turnaround && SV_StepDirection(actor, tdir, dist)) return; } // try other directions if ( ((rand()&3) & 1) || fabs(deltay)>fabs(deltax)) { tdir=d[1]; d[1]=d[2]; d[2]=tdir; } if (d[1]!=DI_NODIR && d[1]!=turnaround && SV_StepDirection(actor, d[1], dist)) return; if (d[2]!=DI_NODIR && d[2]!=turnaround && SV_StepDirection(actor, d[2], dist)) return; /* there is no direct path to the player, so pick another direction */ if (olddir!=DI_NODIR && SV_StepDirection(actor, olddir, dist)) return; if (rand()&1) /*randomly determine direction of search*/ { for (tdir=0 ; tdir<=315 ; tdir += 45) if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) return; } else { for (tdir=315 ; tdir >=0 ; tdir -= 45) if (tdir!=turnaround && SV_StepDirection(actor, tdir, dist) ) return; } if (turnaround != DI_NODIR && SV_StepDirection(actor, turnaround, dist) ) return; PRVM_serveredictfloat(actor, ideal_yaw) = olddir; // can't move // if a bridge was pulled out from underneath a monster, it may not have // a valid standing position at all if (!SV_CheckBottom (actor)) SV_FixCheckBottom (actor); } /* ====================== SV_CloseEnough ====================== */ static qboolean SV_CloseEnough (prvm_edict_t *ent, prvm_edict_t *goal, float dist) { int i; for (i=0 ; i<3 ; i++) { if (goal->priv.server->areamins[i] > ent->priv.server->areamaxs[i] + dist) return false; if (goal->priv.server->areamaxs[i] < ent->priv.server->areamins[i] - dist) return false; } return true; } /* ====================== SV_MoveToGoal ====================== */ void VM_SV_MoveToGoal(prvm_prog_t *prog) { prvm_edict_t *ent, *goal; float dist; VM_SAFEPARMCOUNT(1, SV_MoveToGoal); ent = PRVM_PROG_TO_EDICT(PRVM_serverglobaledict(self)); goal = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, goalentity)); dist = PRVM_G_FLOAT(OFS_PARM0); if ( !( (int)PRVM_serveredictfloat(ent, flags) & (FL_ONGROUND|FL_FLY|FL_SWIM) ) ) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // if the next step hits the enemy, return immediately if ( PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, enemy)) != prog->edicts && SV_CloseEnough (ent, goal, dist) ) return; // bump around... if ( (rand()&3)==1 || !SV_StepDirection (ent, PRVM_serveredictfloat(ent, ideal_yaw), dist)) { SV_NewChaseDir (ent, goal, dist); } } darkplaces/darkplaces-sdl-vs2010.vcxproj0000664000175000017500000003215613067716220017431 0ustar kalevkalev Debug Win32 Release Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51} darkplacessdl Win32Proj Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(ProjectName) $(ProjectName) Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 darkplaces/darkplaces-wgl.vcproj0000664000175000017500000004662413067716220016324 0ustar kalevkalev darkplaces/mod_skeletal_animatevertices_sse.c0000664000175000017500000003752713067716220021125 0ustar kalevkalev#include "mod_skeletal_animatevertices_sse.h" #ifdef SSE_POSSIBLE #ifdef MATRIX4x4_OPENGLORIENTATION #error "SSE skeletal requires D3D matrix layout" #endif #include void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) { // vertex weighted skeletal int i, k; int blends; matrix4x4_t *bonepose; matrix4x4_t *boneposerelative; const blendweights_t * RESTRICT weights; int num_vertices_minus_one; num_vertices_minus_one = model->surfmesh.num_vertices - 1; //unsigned long long ts = rdtsc(); bonepose = (matrix4x4_t *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(matrix4x4_t) * (model->num_bones*2 + model->surfmesh.num_blends)); boneposerelative = bonepose + model->num_bones; if (skeleton && !skeleton->relativetransforms) skeleton = NULL; // interpolate matrices if (skeleton) { for (i = 0;i < model->num_bones;i++) { const float * RESTRICT n = model->data_baseboneposeinverse + i * 12; matrix4x4_t * RESTRICT s = &skeleton->relativetransforms[i]; matrix4x4_t * RESTRICT b = &bonepose[i]; matrix4x4_t * RESTRICT r = &boneposerelative[i]; __m128 b0, b1, b2, b3, r0, r1, r2, r3, nr; if (model->data_bones[i].parent >= 0) { const matrix4x4_t * RESTRICT p = &bonepose[model->data_bones[i].parent]; __m128 s0 = _mm_loadu_ps(s->m[0]), s1 = _mm_loadu_ps(s->m[1]), s2 = _mm_loadu_ps(s->m[2]); #ifdef OPENGLORIENTATION __m128 s3 = _mm_loadu_ps(s->m[3]); #define SKELETON_MATRIX(r, c) _mm_shuffle_ps(s##c, s##c, _MM_SHUFFLE(r, r, r, r)) #else #define SKELETON_MATRIX(r, c) _mm_shuffle_ps(s##r, s##r, _MM_SHUFFLE(c, c, c, c)) #endif __m128 pr = _mm_load_ps(p->m[0]); b0 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 0)); b1 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 1)); b2 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 2)); b3 = _mm_mul_ps(pr, SKELETON_MATRIX(0, 3)); pr = _mm_load_ps(p->m[1]); b0 = _mm_add_ps(b0, _mm_mul_ps(pr, SKELETON_MATRIX(1, 0))); b1 = _mm_add_ps(b1, _mm_mul_ps(pr, SKELETON_MATRIX(1, 1))); b2 = _mm_add_ps(b2, _mm_mul_ps(pr, SKELETON_MATRIX(1, 2))); b3 = _mm_add_ps(b3, _mm_mul_ps(pr, SKELETON_MATRIX(1, 3))); pr = _mm_load_ps(p->m[2]); b0 = _mm_add_ps(b0, _mm_mul_ps(pr, SKELETON_MATRIX(2, 0))); b1 = _mm_add_ps(b1, _mm_mul_ps(pr, SKELETON_MATRIX(2, 1))); b2 = _mm_add_ps(b2, _mm_mul_ps(pr, SKELETON_MATRIX(2, 2))); b3 = _mm_add_ps(b3, _mm_mul_ps(pr, SKELETON_MATRIX(2, 3))); b3 = _mm_add_ps(b3, _mm_load_ps(p->m[3])); } else { b0 = _mm_loadu_ps(s->m[0]); b1 = _mm_loadu_ps(s->m[1]); b2 = _mm_loadu_ps(s->m[2]); b3 = _mm_loadu_ps(s->m[3]); #ifndef OPENGLORIENTATION _MM_TRANSPOSE4_PS(b0, b1, b2, b3); #endif } _mm_store_ps(b->m[0], b0); _mm_store_ps(b->m[1], b1); _mm_store_ps(b->m[2], b2); _mm_store_ps(b->m[3], b3); nr = _mm_loadu_ps(n); r0 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0))); r1 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1))); r2 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2))); r3 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3))); nr = _mm_loadu_ps(n+4); r0 = _mm_add_ps(r0, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); r1 = _mm_add_ps(r1, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); r2 = _mm_add_ps(r2, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); r3 = _mm_add_ps(r3, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); nr = _mm_loadu_ps(n+8); r0 = _mm_add_ps(r0, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); r1 = _mm_add_ps(r1, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); r2 = _mm_add_ps(r2, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); r3 = _mm_add_ps(r3, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); r3 = _mm_add_ps(r3, b3); _mm_store_ps(r->m[0], r0); _mm_store_ps(r->m[1], r1); _mm_store_ps(r->m[2], r2); _mm_store_ps(r->m[3], r3); } } else { for (i = 0;i < model->num_bones;i++) { float m[12]; const short * RESTRICT firstpose7s = model->data_poses7s + 7 * (frameblend[0].subframe * model->num_bones + i); float firstlerp = frameblend[0].lerp, firsttx = firstpose7s[0], firstty = firstpose7s[1], firsttz = firstpose7s[2], rx = firstpose7s[3] * firstlerp, ry = firstpose7s[4] * firstlerp, rz = firstpose7s[5] * firstlerp, rw = firstpose7s[6] * firstlerp, dx = firsttx*rw + firstty*rz - firsttz*ry, dy = -firsttx*rz + firstty*rw + firsttz*rx, dz = firsttx*ry - firstty*rx + firsttz*rw, dw = -firsttx*rx - firstty*ry - firsttz*rz, scale, sx, sy, sz, sw; for (blends = 1;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) { const short * RESTRICT blendpose7s = model->data_poses7s + 7 * (frameblend[blends].subframe * model->num_bones + i); float blendlerp = frameblend[blends].lerp, blendtx = blendpose7s[0], blendty = blendpose7s[1], blendtz = blendpose7s[2], qx = blendpose7s[3], qy = blendpose7s[4], qz = blendpose7s[5], qw = blendpose7s[6]; if(rx*qx + ry*qy + rz*qz + rw*qw < 0) blendlerp = -blendlerp; qx *= blendlerp; qy *= blendlerp; qz *= blendlerp; qw *= blendlerp; rx += qx; ry += qy; rz += qz; rw += qw; dx += blendtx*qw + blendty*qz - blendtz*qy; dy += -blendtx*qz + blendty*qw + blendtz*qx; dz += blendtx*qy - blendty*qx + blendtz*qw; dw += -blendtx*qx - blendty*qy - blendtz*qz; } scale = 1.0f / (rx*rx + ry*ry + rz*rz + rw*rw); sx = rx * scale; sy = ry * scale; sz = rz * scale; sw = rw * scale; m[0] = sw*rw + sx*rx - sy*ry - sz*rz; m[1] = 2*(sx*ry - sw*rz); m[2] = 2*(sx*rz + sw*ry); m[3] = model->num_posescale*(dx*sw - dy*sz + dz*sy - dw*sx); m[4] = 2*(sx*ry + sw*rz); m[5] = sw*rw + sy*ry - sx*rx - sz*rz; m[6] = 2*(sy*rz - sw*rx); m[7] = model->num_posescale*(dx*sz + dy*sw - dz*sx - dw*sy); m[8] = 2*(sx*rz - sw*ry); m[9] = 2*(sy*rz + sw*rx); m[10] = sw*rw + sz*rz - sx*rx - sy*ry; m[11] = model->num_posescale*(dy*sx + dz*sw - dx*sy - dw*sz); if (i == r_skeletal_debugbone.integer) m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; m[3] *= r_skeletal_debugtranslatex.value; m[7] *= r_skeletal_debugtranslatey.value; m[11] *= r_skeletal_debugtranslatez.value; { const float * RESTRICT n = model->data_baseboneposeinverse + i * 12; matrix4x4_t * RESTRICT b = &bonepose[i]; matrix4x4_t * RESTRICT r = &boneposerelative[i]; __m128 b0, b1, b2, b3, r0, r1, r2, r3, nr; if (model->data_bones[i].parent >= 0) { const matrix4x4_t * RESTRICT p = &bonepose[model->data_bones[i].parent]; __m128 pr = _mm_load_ps(p->m[0]); b0 = _mm_mul_ps(pr, _mm_set1_ps(m[0])); b1 = _mm_mul_ps(pr, _mm_set1_ps(m[1])); b2 = _mm_mul_ps(pr, _mm_set1_ps(m[2])); b3 = _mm_mul_ps(pr, _mm_set1_ps(m[3])); pr = _mm_load_ps(p->m[1]); b0 = _mm_add_ps(b0, _mm_mul_ps(pr, _mm_set1_ps(m[4]))); b1 = _mm_add_ps(b1, _mm_mul_ps(pr, _mm_set1_ps(m[5]))); b2 = _mm_add_ps(b2, _mm_mul_ps(pr, _mm_set1_ps(m[6]))); b3 = _mm_add_ps(b3, _mm_mul_ps(pr, _mm_set1_ps(m[7]))); pr = _mm_load_ps(p->m[2]); b0 = _mm_add_ps(b0, _mm_mul_ps(pr, _mm_set1_ps(m[8]))); b1 = _mm_add_ps(b1, _mm_mul_ps(pr, _mm_set1_ps(m[9]))); b2 = _mm_add_ps(b2, _mm_mul_ps(pr, _mm_set1_ps(m[10]))); b3 = _mm_add_ps(b3, _mm_mul_ps(pr, _mm_set1_ps(m[11]))); b3 = _mm_add_ps(b3, _mm_load_ps(p->m[3])); } else { b0 = _mm_setr_ps(m[0], m[4], m[8], 0.0f); b1 = _mm_setr_ps(m[1], m[5], m[9], 0.0f); b2 = _mm_setr_ps(m[2], m[6], m[10], 0.0f); b3 = _mm_setr_ps(m[3], m[7], m[11], 1.0f); } _mm_store_ps(b->m[0], b0); _mm_store_ps(b->m[1], b1); _mm_store_ps(b->m[2], b2); _mm_store_ps(b->m[3], b3); nr = _mm_loadu_ps(n); r0 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0))); r1 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1))); r2 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2))); r3 = _mm_mul_ps(b0, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3))); nr = _mm_loadu_ps(n+4); r0 = _mm_add_ps(r0, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); r1 = _mm_add_ps(r1, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); r2 = _mm_add_ps(r2, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); r3 = _mm_add_ps(r3, _mm_mul_ps(b1, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); nr = _mm_loadu_ps(n+8); r0 = _mm_add_ps(r0, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(0, 0, 0, 0)))); r1 = _mm_add_ps(r1, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(1, 1, 1, 1)))); r2 = _mm_add_ps(r2, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(2, 2, 2, 2)))); r3 = _mm_add_ps(r3, _mm_mul_ps(b2, _mm_shuffle_ps(nr, nr, _MM_SHUFFLE(3, 3, 3, 3)))); r3 = _mm_add_ps(r3, b3); _mm_store_ps(r->m[0], r0); _mm_store_ps(r->m[1], r1); _mm_store_ps(r->m[2], r2); _mm_store_ps(r->m[3], r3); } } } // generate matrices for all blend combinations weights = model->surfmesh.data_blendweights; for (i = 0;i < model->surfmesh.num_blends;i++, weights++) { float * RESTRICT b = &boneposerelative[model->num_bones + i].m[0][0]; const float * RESTRICT m = &boneposerelative[weights->index[0]].m[0][0]; float f = weights->influence[0] * (1.0f / 255.0f); __m128 fv = _mm_set_ps1(f); __m128 b0 = _mm_load_ps(m); __m128 b1 = _mm_load_ps(m+4); __m128 b2 = _mm_load_ps(m+8); __m128 b3 = _mm_load_ps(m+12); __m128 m0, m1, m2, m3; b0 = _mm_mul_ps(b0, fv); b1 = _mm_mul_ps(b1, fv); b2 = _mm_mul_ps(b2, fv); b3 = _mm_mul_ps(b3, fv); for (k = 1;k < 4 && weights->influence[k];k++) { m = &boneposerelative[weights->index[k]].m[0][0]; f = weights->influence[k] * (1.0f / 255.0f); fv = _mm_set_ps1(f); m0 = _mm_load_ps(m); m1 = _mm_load_ps(m+4); m2 = _mm_load_ps(m+8); m3 = _mm_load_ps(m+12); m0 = _mm_mul_ps(m0, fv); m1 = _mm_mul_ps(m1, fv); m2 = _mm_mul_ps(m2, fv); m3 = _mm_mul_ps(m3, fv); b0 = _mm_add_ps(m0, b0); b1 = _mm_add_ps(m1, b1); b2 = _mm_add_ps(m2, b2); b3 = _mm_add_ps(m3, b3); } _mm_store_ps(b, b0); _mm_store_ps(b+4, b1); _mm_store_ps(b+8, b2); _mm_store_ps(b+12, b3); } #define LOAD_MATRIX_SCALAR() const float * RESTRICT m = &boneposerelative[*b].m[0][0] #define LOAD_MATRIX3() \ const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ /* bonepose array is 16 byte aligned */ \ __m128 m1 = _mm_load_ps((m)); \ __m128 m2 = _mm_load_ps((m)+4); \ __m128 m3 = _mm_load_ps((m)+8); #define LOAD_MATRIX4() \ const float * RESTRICT m = &boneposerelative[*b].m[0][0]; \ /* bonepose array is 16 byte aligned */ \ __m128 m1 = _mm_load_ps((m)); \ __m128 m2 = _mm_load_ps((m)+4); \ __m128 m3 = _mm_load_ps((m)+8); \ __m128 m4 = _mm_load_ps((m)+12) /* Note that matrix is 4x4 and transposed compared to non-USE_SSE codepath */ #define TRANSFORM_POSITION_SCALAR(in, out) \ (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8] + m[12]); \ (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9] + m[13]); \ (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10] + m[14]); #define TRANSFORM_VECTOR_SCALAR(in, out) \ (out)[0] = ((in)[0] * m[0] + (in)[1] * m[4] + (in)[2] * m[ 8]); \ (out)[1] = ((in)[0] * m[1] + (in)[1] * m[5] + (in)[2] * m[ 9]); \ (out)[2] = ((in)[0] * m[2] + (in)[1] * m[6] + (in)[2] * m[10]); #define TRANSFORM_POSITION(in, out) { \ __m128 pin = _mm_loadu_ps(in); /* we ignore the value in the last element (x from the next vertex) */ \ __m128 x = _mm_shuffle_ps(pin, pin, 0x0); \ __m128 t1 = _mm_mul_ps(x, m1); \ \ /* y, + x */ \ __m128 y = _mm_shuffle_ps(pin, pin, 0x55); \ __m128 t2 = _mm_mul_ps(y, m2); \ __m128 t3 = _mm_add_ps(t1, t2); \ \ /* z, + (y+x) */ \ __m128 z = _mm_shuffle_ps(pin, pin, 0xaa); \ __m128 t4 = _mm_mul_ps(z, m3); \ __m128 t5 = _mm_add_ps(t3, t4); \ \ /* + m3 */ \ __m128 pout = _mm_add_ps(t5, m4); \ _mm_storeu_ps((out), pout); \ } #define TRANSFORM_VECTOR(in, out) { \ __m128 vin = _mm_loadu_ps(in); \ \ /* x */ \ __m128 x = _mm_shuffle_ps(vin, vin, 0x0); \ __m128 t1 = _mm_mul_ps(x, m1); \ \ /* y, + x */ \ __m128 y = _mm_shuffle_ps(vin, vin, 0x55); \ __m128 t2 = _mm_mul_ps(y, m2); \ __m128 t3 = _mm_add_ps(t1, t2); \ \ /* nz, + (ny + nx) */ \ __m128 z = _mm_shuffle_ps(vin, vin, 0xaa); \ __m128 t4 = _mm_mul_ps(z, m3); \ __m128 vout = _mm_add_ps(t3, t4); \ _mm_storeu_ps((out), vout); \ } // transform vertex attributes by blended matrices if (vertex3f) { const float * RESTRICT v = model->surfmesh.data_vertex3f; const unsigned short * RESTRICT b = model->surfmesh.blends; // special case common combinations of attributes to avoid repeated loading of matrices if (normal3f) { const float * RESTRICT n = model->surfmesh.data_normal3f; if (svector3f && tvector3f) { const float * RESTRICT svec = model->surfmesh.data_svector3f; const float * RESTRICT tvec = model->surfmesh.data_tvector3f; // Note that for SSE each iteration stores one element past end, so we break one vertex short // and handle that with scalars in that case for (i = 0; i < num_vertices_minus_one; i++, v += 3, n += 3, svec += 3, tvec += 3, b++, vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); TRANSFORM_VECTOR(n, normal3f); TRANSFORM_VECTOR(svec, svector3f); TRANSFORM_VECTOR(tvec, tvector3f); } // Last vertex needs to be done with scalars to avoid reading/writing 1 word past end of arrays { LOAD_MATRIX_SCALAR(); TRANSFORM_POSITION_SCALAR(v, vertex3f); TRANSFORM_VECTOR_SCALAR(n, normal3f); TRANSFORM_VECTOR_SCALAR(svec, svector3f); TRANSFORM_VECTOR_SCALAR(tvec, tvector3f); } //printf("elapsed ticks: %llu\n", rdtsc() - ts); // XXX return; } for (i = 0;i < num_vertices_minus_one; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); TRANSFORM_VECTOR(n, normal3f); } { LOAD_MATRIX_SCALAR(); TRANSFORM_POSITION_SCALAR(v, vertex3f); TRANSFORM_VECTOR_SCALAR(n, normal3f); } } else { for (i = 0;i < num_vertices_minus_one; i++, v += 3, b++, vertex3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); } { LOAD_MATRIX_SCALAR(); TRANSFORM_POSITION_SCALAR(v, vertex3f); } } } else if (normal3f) { const float * RESTRICT n = model->surfmesh.data_normal3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < num_vertices_minus_one; i++, n += 3, b++, normal3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(n, normal3f); } { LOAD_MATRIX_SCALAR(); TRANSFORM_VECTOR_SCALAR(n, normal3f); } } if (svector3f) { const float * RESTRICT svec = model->surfmesh.data_svector3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < num_vertices_minus_one; i++, svec += 3, b++, svector3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(svec, svector3f); } { LOAD_MATRIX_SCALAR(); TRANSFORM_VECTOR_SCALAR(svec, svector3f); } } if (tvector3f) { const float * RESTRICT tvec = model->surfmesh.data_tvector3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < num_vertices_minus_one; i++, tvec += 3, b++, tvector3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(tvec, tvector3f); } { LOAD_MATRIX_SCALAR(); TRANSFORM_VECTOR_SCALAR(tvec, tvector3f); } } #undef LOAD_MATRIX3 #undef LOAD_MATRIX4 #undef TRANSFORM_POSITION #undef TRANSFORM_VECTOR #undef LOAD_MATRIX_SCALAR #undef TRANSFORM_POSITION_SCALAR #undef TRANSFORM_VECTOR_SCALAR } #endif darkplaces/Info.plist0000664000175000017500000000254413067716216014145 0ustar kalevkalev CFBundleDevelopmentRegion en CFBundleDisplayName ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile darkplaces64x64 CFBundleIdentifier com.ghdigital.${PRODUCT_NAME:identifier} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${PRODUCT_NAME} CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 LSRequiresIPhoneOS UISupportedInterfaceOrientations UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationPortrait darkplaces/portals.c0000664000175000017500000003446313067716222014027 0ustar kalevkalev #include "quakedef.h" #include "polygon.h" #include "portals.h" #define MAXRECURSIVEPORTALPLANES 1024 #define MAXRECURSIVEPORTALS 256 static tinyplane_t portalplanes[MAXRECURSIVEPORTALPLANES]; static int ranoutofportalplanes; static int ranoutofportals; static float portaltemppoints[2][256][3]; static float portaltemppoints2[256][3]; static int portal_markid = 0; static float boxpoints[4*3]; static int Portal_PortalThroughPortalPlanes(tinyplane_t *clipplanes, int clipnumplanes, float *targpoints, int targnumpoints, float *out, int maxpoints) { int numpoints = targnumpoints, i, w; if (numpoints < 1) return numpoints; if (maxpoints > 256) maxpoints = 256; w = 0; memcpy(&portaltemppoints[w][0][0], targpoints, numpoints * 3 * sizeof(float)); for (i = 0;i < clipnumplanes && numpoints > 0;i++) { PolygonF_Divide(numpoints, &portaltemppoints[w][0][0], clipplanes[i].normal[0], clipplanes[i].normal[1], clipplanes[i].normal[2], clipplanes[i].dist, 1.0f/32.0f, 256, &portaltemppoints[1-w][0][0], &numpoints, 0, NULL, NULL, NULL); w = 1-w; numpoints = min(numpoints, 256); } numpoints = min(numpoints, maxpoints); if (numpoints > 0) memcpy(out, &portaltemppoints[w][0][0], numpoints * 3 * sizeof(float)); return numpoints; } static int Portal_RecursiveFlowSearch (mleaf_t *leaf, vec3_t eye, int firstclipplane, int numclipplanes) { mportal_t *p; int newpoints, i, prev; vec3_t center, v1, v2; tinyplane_t *newplanes; if (leaf->portalmarkid == portal_markid) return true; // follow portals into other leafs for (p = leaf->portals;p;p = p->next) { // only flow through portals facing away from the viewer if (PlaneDiff(eye, (&p->plane)) < 0) { newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); if (newpoints < 3) continue; else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) ranoutofportalplanes = true; else { // find the center by averaging VectorClear(center); for (i = 0;i < newpoints;i++) VectorAdd(center, portaltemppoints2[i], center); // ixtable is a 1.0f / N table VectorScale(center, ixtable[newpoints], center); // calculate the planes, and make sure the polygon can see it's own center newplanes = &portalplanes[firstclipplane + numclipplanes]; for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) { VectorSubtract(eye, portaltemppoints2[i], v1); VectorSubtract(portaltemppoints2[prev], portaltemppoints2[i], v2); CrossProduct(v1, v2, newplanes[i].normal); VectorNormalize(newplanes[i].normal); newplanes[i].dist = DotProduct(eye, newplanes[i].normal); if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) { // polygon can't see it's own center, discard and use parent portal break; } } if (i == newpoints) { if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane + numclipplanes, newpoints)) return true; } else { if (Portal_RecursiveFlowSearch(p->past, eye, firstclipplane, numclipplanes)) return true; } } } } return false; } static void Portal_PolygonRecursiveMarkLeafs(mnode_t *node, float *polypoints, int numpoints) { int i, front; float *p; loc0: if (!node->plane) { ((mleaf_t *)node)->portalmarkid = portal_markid; return; } front = 0; for (i = 0, p = polypoints;i < numpoints;i++, p += 3) { if (DotProduct(p, node->plane->normal) > node->plane->dist) front++; } if (front > 0) { if (front == numpoints) { node = node->children[0]; goto loc0; } else Portal_PolygonRecursiveMarkLeafs(node->children[0], polypoints, numpoints); } node = node->children[1]; goto loc0; } int Portal_CheckPolygon(dp_model_t *model, vec3_t eye, float *polypoints, int numpoints) { int i, prev, returnvalue; mleaf_t *eyeleaf; vec3_t center, v1, v2; // if there is no model, it can not block visibility if (model == NULL || !model->brush.PointInLeaf) return true; portal_markid++; Portal_PolygonRecursiveMarkLeafs(model->brush.data_nodes, polypoints, numpoints); eyeleaf = model->brush.PointInLeaf(model, eye); // find the center by averaging VectorClear(center); for (i = 0;i < numpoints;i++) VectorAdd(center, (&polypoints[i * 3]), center); // ixtable is a 1.0f / N table VectorScale(center, ixtable[numpoints], center); // calculate the planes, and make sure the polygon can see it's own center for (prev = numpoints - 1, i = 0;i < numpoints;prev = i, i++) { VectorSubtract(eye, (&polypoints[i * 3]), v1); VectorSubtract((&polypoints[prev * 3]), (&polypoints[i * 3]), v2); CrossProduct(v1, v2, portalplanes[i].normal); VectorNormalize(portalplanes[i].normal); portalplanes[i].dist = DotProduct(eye, portalplanes[i].normal); if (DotProduct(portalplanes[i].normal, center) <= portalplanes[i].dist) { // polygon can't see it's own center, discard return false; } } ranoutofportalplanes = false; ranoutofportals = false; returnvalue = Portal_RecursiveFlowSearch(eyeleaf, eye, 0, numpoints); if (ranoutofportalplanes) Con_Printf("Portal_RecursiveFlowSearch: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); if (ranoutofportals) Con_Printf("Portal_RecursiveFlowSearch: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); return returnvalue; } #define Portal_MinsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ {\ if (eye[(axis)] < ((axisvalue) - 0.5f))\ {\ boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ return true;\ }\ } #define Portal_MaxsBoxPolygon(axis, axisvalue, x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) \ {\ if (eye[(axis)] > ((axisvalue) + 0.5f))\ {\ boxpoints[ 0] = x1;boxpoints[ 1] = y1;boxpoints[ 2] = z1;\ boxpoints[ 3] = x2;boxpoints[ 4] = y2;boxpoints[ 5] = z2;\ boxpoints[ 6] = x3;boxpoints[ 7] = y3;boxpoints[ 8] = z3;\ boxpoints[ 9] = x4;boxpoints[10] = y4;boxpoints[11] = z4;\ if (Portal_CheckPolygon(model, eye, boxpoints, 4))\ return true;\ }\ } int Portal_CheckBox(dp_model_t *model, vec3_t eye, vec3_t a, vec3_t b) { if (eye[0] >= (a[0] - 1.0f) && eye[0] < (b[0] + 1.0f) && eye[1] >= (a[1] - 1.0f) && eye[1] < (b[1] + 1.0f) && eye[2] >= (a[2] - 1.0f) && eye[2] < (b[2] + 1.0f)) return true; Portal_MinsBoxPolygon ( 0, a[0], a[0], a[1], a[2], a[0], b[1], a[2], a[0], b[1], b[2], a[0], a[1], b[2] ); Portal_MaxsBoxPolygon ( 0, b[0], b[0], b[1], a[2], b[0], a[1], a[2], b[0], a[1], b[2], b[0], b[1], b[2] ); Portal_MinsBoxPolygon ( 1, a[1], b[0], a[1], a[2], a[0], a[1], a[2], a[0], a[1], b[2], b[0], a[1], b[2] ); Portal_MaxsBoxPolygon ( 1, b[1], a[0], b[1], a[2], b[0], b[1], a[2], b[0], b[1], b[2], a[0], b[1], b[2] ); Portal_MinsBoxPolygon ( 2, a[2], a[0], a[1], a[2], b[0], a[1], a[2], b[0], b[1], a[2], a[0], b[1], a[2] ); Portal_MaxsBoxPolygon ( 2, b[2], b[0], a[1], b[2], a[0], a[1], b[2], a[0], b[1], b[2], b[0], b[1], b[2] ); return false; } typedef struct portalrecursioninfo_s { int exact; int numfrustumplanes; vec3_t boxmins; vec3_t boxmaxs; int numsurfaces; int *surfacelist; unsigned char *surfacepvs; int numleafs; unsigned char *visitingleafpvs; // used to prevent infinite loops int *leaflist; unsigned char *leafpvs; unsigned char *shadowtrispvs; unsigned char *lighttrispvs; dp_model_t *model; vec3_t eye; float *updateleafsmins; float *updateleafsmaxs; } portalrecursioninfo_t; static void Portal_RecursiveFlow (portalrecursioninfo_t *info, mleaf_t *leaf, int firstclipplane, int numclipplanes) { mportal_t *p; int newpoints, i, prev; float dist; vec3_t center; tinyplane_t *newplanes; int leafindex = leaf - info->model->brush.data_leafs; if (CHECKPVSBIT(info->visitingleafpvs, leafindex)) return; // recursive loop of leafs (cmc.bsp for megatf coop) SETPVSBIT(info->visitingleafpvs, leafindex); for (i = 0;i < 3;i++) { if (info->updateleafsmins && info->updateleafsmins[i] > leaf->mins[i]) info->updateleafsmins[i] = leaf->mins[i]; if (info->updateleafsmaxs && info->updateleafsmaxs[i] < leaf->maxs[i]) info->updateleafsmaxs[i] = leaf->maxs[i]; } if (info->leafpvs) { if (!CHECKPVSBIT(info->leafpvs, leafindex)) { SETPVSBIT(info->leafpvs, leafindex); info->leaflist[info->numleafs++] = leafindex; } } // mark surfaces in leaf that can be seen through portal if (leaf->numleafsurfaces && info->surfacepvs) { for (i = 0;i < leaf->numleafsurfaces;i++) { int surfaceindex = leaf->firstleafsurface[i]; msurface_t *surface = info->model->data_surfaces + surfaceindex; if (BoxesOverlap(surface->mins, surface->maxs, info->boxmins, info->boxmaxs)) { qboolean insidebox = BoxInsideBox(surface->mins, surface->maxs, info->boxmins, info->boxmaxs); qboolean addedtris = false; int t, tend; const int *elements; const float *vertex3f; float v[9]; vertex3f = info->model->surfmesh.data_vertex3f; elements = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle); for (t = surface->num_firsttriangle, tend = t + surface->num_triangles;t < tend;t++, elements += 3) { VectorCopy(vertex3f + elements[0] * 3, v + 0); VectorCopy(vertex3f + elements[1] * 3, v + 3); VectorCopy(vertex3f + elements[2] * 3, v + 6); if (PointInfrontOfTriangle(info->eye, v + 0, v + 3, v + 6) && (insidebox || TriangleBBoxOverlapsBox(v, v + 3, v + 6, info->boxmins, info->boxmaxs)) && (!info->exact || Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, v, 3, &portaltemppoints2[0][0], 256) > 0)) { addedtris = true; if (info->shadowtrispvs) SETPVSBIT(info->shadowtrispvs, t); if (info->lighttrispvs) SETPVSBIT(info->lighttrispvs, t); } } if (addedtris && !CHECKPVSBIT(info->surfacepvs, surfaceindex)) { SETPVSBIT(info->surfacepvs, surfaceindex); info->surfacelist[info->numsurfaces++] = surfaceindex; } } } } // follow portals into other leafs for (p = leaf->portals;p;p = p->next) { // only flow through portals facing the viewer dist = PlaneDiff(info->eye, (&p->plane)); if (dist < 0 && BoxesOverlap(p->past->mins, p->past->maxs, info->boxmins, info->boxmaxs)) { newpoints = Portal_PortalThroughPortalPlanes(&portalplanes[firstclipplane], numclipplanes, (float *) p->points, p->numpoints, &portaltemppoints2[0][0], 256); if (newpoints < 3) continue; else if (firstclipplane + numclipplanes + newpoints > MAXRECURSIVEPORTALPLANES) ranoutofportalplanes = true; else { // find the center by averaging VectorClear(center); for (i = 0;i < newpoints;i++) VectorAdd(center, portaltemppoints2[i], center); // ixtable is a 1.0f / N table VectorScale(center, ixtable[newpoints], center); // calculate the planes, and make sure the polygon can see its own center newplanes = &portalplanes[firstclipplane + numclipplanes]; for (prev = newpoints - 1, i = 0;i < newpoints;prev = i, i++) { TriangleNormal(portaltemppoints2[prev], portaltemppoints2[i], info->eye, newplanes[i].normal); VectorNormalize(newplanes[i].normal); newplanes[i].dist = DotProduct(info->eye, newplanes[i].normal); if (DotProduct(newplanes[i].normal, center) <= newplanes[i].dist) { // polygon can't see its own center, discard and use parent portal break; } } if (i == newpoints) Portal_RecursiveFlow(info, p->past, firstclipplane + numclipplanes, newpoints); else Portal_RecursiveFlow(info, p->past, firstclipplane, numclipplanes); } } } CLEARPVSBIT(info->visitingleafpvs, leafindex); } static void Portal_RecursiveFindLeafForFlow(portalrecursioninfo_t *info, mnode_t *node) { if (node->plane) { float f = DotProduct(info->eye, node->plane->normal) - node->plane->dist; if (f > -0.1) Portal_RecursiveFindLeafForFlow(info, node->children[0]); if (f < 0.1) Portal_RecursiveFindLeafForFlow(info, node->children[1]); } else { mleaf_t *leaf = (mleaf_t *)node; if (leaf->clusterindex >= 0) Portal_RecursiveFlow(info, leaf, 0, info->numfrustumplanes); } } void Portal_Visibility(dp_model_t *model, const vec3_t eye, int *leaflist, unsigned char *leafpvs, int *numleafspointer, int *surfacelist, unsigned char *surfacepvs, int *numsurfacespointer, const mplane_t *frustumplanes, int numfrustumplanes, int exact, const float *boxmins, const float *boxmaxs, float *updateleafsmins, float *updateleafsmaxs, unsigned char *shadowtrispvs, unsigned char *lighttrispvs, unsigned char *visitingleafpvs) { int i; portalrecursioninfo_t info; // if there is no model, it can not block visibility if (model == NULL) { Con_Print("Portal_Visibility: NULL model\n"); return; } if (!model->brush.data_nodes) { Con_Print("Portal_Visibility: not a brush model\n"); return; } // put frustum planes (if any) into tinyplane format at start of buffer for (i = 0;i < numfrustumplanes;i++) { VectorCopy(frustumplanes[i].normal, portalplanes[i].normal); portalplanes[i].dist = frustumplanes[i].dist; } ranoutofportalplanes = false; ranoutofportals = false; VectorCopy(boxmins, info.boxmins); VectorCopy(boxmaxs, info.boxmaxs); info.exact = exact; info.numsurfaces = 0; info.surfacelist = surfacelist; info.surfacepvs = surfacepvs; info.numleafs = 0; info.visitingleafpvs = visitingleafpvs; info.leaflist = leaflist; info.leafpvs = leafpvs; info.model = model; VectorCopy(eye, info.eye); info.numfrustumplanes = numfrustumplanes; info.updateleafsmins = updateleafsmins; info.updateleafsmaxs = updateleafsmaxs; info.shadowtrispvs = shadowtrispvs; info.lighttrispvs = lighttrispvs; Portal_RecursiveFindLeafForFlow(&info, model->brush.data_nodes); if (ranoutofportalplanes) Con_Printf("Portal_RecursiveFlow: ran out of %d plane stack when recursing through portals\n", MAXRECURSIVEPORTALPLANES); if (ranoutofportals) Con_Printf("Portal_RecursiveFlow: ran out of %d portal stack when recursing through portals\n", MAXRECURSIVEPORTALS); if (numsurfacespointer) *numsurfacespointer = info.numsurfaces; if (numleafspointer) *numleafspointer = info.numleafs; } darkplaces/mvm_cmds.c0000664000175000017500000013252613067716222014147 0ustar kalevkalev#include "quakedef.h" #include "prvm_cmds.h" #include "clvm_cmds.h" #include "menu.h" #include "csprogs.h" // TODO check which strings really should be engine strings //============================================================================ // Menu const char *vm_m_extensions = "BX_WAL_SUPPORT " "DP_CINEMATIC_DPV " "DP_COVERAGE " "DP_CRYPTO " "DP_CSQC_BINDMAPS " "DP_GFX_FONTS " "DP_GFX_FONTS_FREETYPE " "DP_UTF8 " "DP_FONT_VARIABLEWIDTH " "DP_MENU_EXTRESPONSEPACKET " "DP_QC_ASINACOSATANATAN2TAN " "DP_QC_AUTOCVARS " "DP_QC_CMD " "DP_QC_CRC16 " "DP_QC_CVAR_TYPE " "DP_QC_CVAR_DESCRIPTION " "DP_QC_DIGEST " "DP_QC_DIGEST_SHA256 " "DP_QC_FINDCHAIN_TOFIELD " "DP_QC_I18N " "DP_QC_LOG " "DP_QC_RENDER_SCENE " "DP_QC_SPRINTF " "DP_QC_STRFTIME " "DP_QC_STRINGBUFFERS " "DP_QC_STRINGBUFFERS_CVARLIST " "DP_QC_STRINGBUFFERS_EXT_WIP " "DP_QC_STRINGCOLORFUNCTIONS " "DP_QC_STRING_CASE_FUNCTIONS " "DP_QC_STRREPLACE " "DP_QC_TOKENIZEBYSEPARATOR " "DP_QC_TOKENIZE_CONSOLE " "DP_QC_UNLIMITEDTEMPSTRINGS " "DP_QC_URI_ESCAPE " "DP_QC_URI_GET " "DP_QC_URI_POST " "DP_QC_WHICHPACK " "FTE_STRINGS " ; /* ========= VM_M_setmousetarget setmousetarget(float target) ========= */ static void VM_M_setmousetarget(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_setmousetarget); switch((int)PRVM_G_FLOAT(OFS_PARM0)) { case 1: in_client_mouse = false; break; case 2: in_client_mouse = true; break; default: prog->error_cmd("VM_M_setmousetarget: wrong destination %f !",PRVM_G_FLOAT(OFS_PARM0)); } } /* ========= VM_M_getmousetarget float getmousetarget ========= */ static void VM_M_getmousetarget(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_M_getmousetarget); if(in_client_mouse) PRVM_G_FLOAT(OFS_RETURN) = 2; else PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_M_setkeydest setkeydest(float dest) ========= */ static void VM_M_setkeydest(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_M_setkeydest); switch((int)PRVM_G_FLOAT(OFS_PARM0)) { case 0: // key_game key_dest = key_game; break; case 2: // key_menu key_dest = key_menu; break; case 3: // key_menu_grabbed key_dest = key_menu_grabbed; break; case 1: // key_message // key_dest = key_message // break; default: prog->error_cmd("VM_M_setkeydest: wrong destination %f !", PRVM_G_FLOAT(OFS_PARM0)); } } /* ========= VM_M_getkeydest float getkeydest ========= */ static void VM_M_getkeydest(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_M_getkeydest); // key_game = 0, key_message = 1, key_menu = 2, key_menu_grabbed = 3, unknown = -1 switch(key_dest) { case key_game: PRVM_G_FLOAT(OFS_RETURN) = 0; break; case key_menu: PRVM_G_FLOAT(OFS_RETURN) = 2; break; case key_menu_grabbed: PRVM_G_FLOAT(OFS_RETURN) = 3; break; case key_message: // not supported // PRVM_G_FLOAT(OFS_RETURN) = 1; // break; default: PRVM_G_FLOAT(OFS_RETURN) = -1; } } /* ========= VM_M_getresolution vector getresolution(float number) ========= */ static void VM_M_getresolution(prvm_prog_t *prog) { int nr, fs; VM_SAFEPARMCOUNTRANGE(1, 2, VM_getresolution); nr = (int)PRVM_G_FLOAT(OFS_PARM0); fs = ((prog->argc <= 1) || ((int)PRVM_G_FLOAT(OFS_PARM1))); if(nr < -1 || nr >= (fs ? video_resolutions_count : video_resolutions_hardcoded_count)) { PRVM_G_VECTOR(OFS_RETURN)[0] = 0; PRVM_G_VECTOR(OFS_RETURN)[1] = 0; PRVM_G_VECTOR(OFS_RETURN)[2] = 0; } else if(nr == -1) { vid_mode_t *m = VID_GetDesktopMode(); if (m) { PRVM_G_VECTOR(OFS_RETURN)[0] = m->width; PRVM_G_VECTOR(OFS_RETURN)[1] = m->height; PRVM_G_VECTOR(OFS_RETURN)[2] = m->pixelheight_num / (prvm_vec_t) m->pixelheight_denom; } else { PRVM_G_VECTOR(OFS_RETURN)[0] = 0; PRVM_G_VECTOR(OFS_RETURN)[1] = 0; PRVM_G_VECTOR(OFS_RETURN)[2] = 0; } } else { video_resolution_t *r = &((fs ? video_resolutions : video_resolutions_hardcoded)[nr]); PRVM_G_VECTOR(OFS_RETURN)[0] = r->width; PRVM_G_VECTOR(OFS_RETURN)[1] = r->height; PRVM_G_VECTOR(OFS_RETURN)[2] = r->pixelheight; } } static void VM_M_getgamedirinfo(prvm_prog_t *prog) { int nr, item; VM_SAFEPARMCOUNT(2, VM_getgamedirinfo); nr = (int)PRVM_G_FLOAT(OFS_PARM0); item = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_INT( OFS_RETURN ) = OFS_NULL; if(nr >= 0 && nr < fs_all_gamedirs_count) { if(item == 0) PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, fs_all_gamedirs[nr].name ); else if(item == 1) PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, fs_all_gamedirs[nr].description ); } } /* ========= VM_M_getserverliststat float getserverliststat(float type) ========= */ /* type: 0 serverlist_viewcount 1 serverlist_totalcount 2 masterquerycount 3 masterreplycount 4 serverquerycount 5 serverreplycount 6 sortfield 7 sortflags */ static void VM_M_getserverliststat(prvm_prog_t *prog) { int type; VM_SAFEPARMCOUNT ( 1, VM_M_getserverliststat ); PRVM_G_FLOAT( OFS_RETURN ) = 0; type = (int)PRVM_G_FLOAT( OFS_PARM0 ); switch(type) { case 0: PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_viewcount; return; case 1: PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_cachecount; return; case 2: PRVM_G_FLOAT ( OFS_RETURN ) = masterquerycount; return; case 3: PRVM_G_FLOAT ( OFS_RETURN ) = masterreplycount; return; case 4: PRVM_G_FLOAT ( OFS_RETURN ) = serverquerycount; return; case 5: PRVM_G_FLOAT ( OFS_RETURN ) = serverreplycount; return; case 6: PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortbyfield; return; case 7: PRVM_G_FLOAT ( OFS_RETURN ) = serverlist_sortflags; return; default: VM_Warning(prog, "VM_M_getserverliststat: bad type %i!\n", type ); } } /* ======================== VM_M_resetserverlistmasks resetserverlistmasks() ======================== */ static void VM_M_resetserverlistmasks(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_M_resetserverlistmasks); ServerList_ResetMasks(); } /* ======================== VM_M_setserverlistmaskstring setserverlistmaskstring(float mask, float fld, string str, float op) 0-511 and 512 - 1024 or ======================== */ static void VM_M_setserverlistmaskstring(prvm_prog_t *prog) { const char *str; int masknr; serverlist_mask_t *mask; int field; VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmaskstring ); str = PRVM_G_STRING( OFS_PARM2 ); masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); if( masknr >= 0 && masknr < SERVERLIST_ANDMASKCOUNT ) mask = &serverlist_andmasks[masknr]; else if( masknr >= 512 && masknr - 512 < SERVERLIST_ORMASKCOUNT ) mask = &serverlist_ormasks[masknr - 512 ]; else { VM_Warning(prog, "VM_M_setserverlistmaskstring: invalid mask number %i\n", masknr ); return; } field = (int) PRVM_G_FLOAT( OFS_PARM1 ); switch( field ) { case SLIF_CNAME: strlcpy( mask->info.cname, str, sizeof(mask->info.cname) ); break; case SLIF_NAME: strlcpy( mask->info.name, str, sizeof(mask->info.name) ); break; case SLIF_QCSTATUS: strlcpy( mask->info.qcstatus, str, sizeof(mask->info.qcstatus) ); break; case SLIF_PLAYERS: strlcpy( mask->info.players, str, sizeof(mask->info.players) ); break; case SLIF_MAP: strlcpy( mask->info.map, str, sizeof(mask->info.map) ); break; case SLIF_MOD: strlcpy( mask->info.mod, str, sizeof(mask->info.mod) ); break; case SLIF_GAME: strlcpy( mask->info.game, str, sizeof(mask->info.game) ); break; default: VM_Warning(prog, "VM_M_setserverlistmaskstring: Bad field number %i passed!\n", field ); return; } mask->active = true; mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); } /* ======================== VM_M_setserverlistmasknumber setserverlistmasknumber(float mask, float fld, float num, float op) 0-511 and 512 - 1024 or ======================== */ static void VM_M_setserverlistmasknumber(prvm_prog_t *prog) { int number; serverlist_mask_t *mask; int masknr; int field; VM_SAFEPARMCOUNT( 4, VM_M_setserverlistmasknumber ); masknr = (int)PRVM_G_FLOAT( OFS_PARM0 ); if( masknr >= 0 && masknr < SERVERLIST_ANDMASKCOUNT ) mask = &serverlist_andmasks[masknr]; else if( masknr >= 512 && masknr - 512 < SERVERLIST_ORMASKCOUNT ) mask = &serverlist_ormasks[masknr - 512 ]; else { VM_Warning(prog, "VM_M_setserverlistmasknumber: invalid mask number %i\n", masknr ); return; } number = (int)PRVM_G_FLOAT( OFS_PARM2 ); field = (int) PRVM_G_FLOAT( OFS_PARM1 ); switch( field ) { case SLIF_MAXPLAYERS: mask->info.maxplayers = number; break; case SLIF_NUMPLAYERS: mask->info.numplayers = number; break; case SLIF_NUMBOTS: mask->info.numbots = number; break; case SLIF_NUMHUMANS: mask->info.numhumans = number; break; case SLIF_PING: mask->info.ping = number; break; case SLIF_PROTOCOL: mask->info.protocol = number; break; case SLIF_FREESLOTS: mask->info.freeslots = number; break; case SLIF_CATEGORY: mask->info.category = number; break; case SLIF_ISFAVORITE: mask->info.isfavorite = number != 0; break; default: VM_Warning(prog, "VM_M_setserverlistmasknumber: Bad field number %i passed!\n", field ); return; } mask->active = true; mask->tests[field] = (serverlist_maskop_t)((int)PRVM_G_FLOAT( OFS_PARM3 )); } /* ======================== VM_M_resortserverlist resortserverlist ======================== */ static void VM_M_resortserverlist(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_M_resortserverlist); ServerList_RebuildViewList(); } /* ========= VM_M_getserverliststring string getserverliststring(float field, float hostnr) ========= */ static void VM_M_getserverliststring(prvm_prog_t *prog) { const serverlist_entry_t *cache; int hostnr; VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); PRVM_G_INT(OFS_RETURN) = OFS_NULL; hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); if(hostnr == -1 && serverlist_callbackentry) { cache = serverlist_callbackentry; } else { if(hostnr < 0 || hostnr >= serverlist_viewcount) { Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); return; } cache = ServerList_GetViewEntry(hostnr); } switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { case SLIF_CNAME: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.cname ); break; case SLIF_NAME: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.name ); break; case SLIF_QCSTATUS: PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.qcstatus ); break; case SLIF_PLAYERS: PRVM_G_INT (OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.players ); break; case SLIF_GAME: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.game ); break; case SLIF_MOD: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.mod ); break; case SLIF_MAP: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.map ); break; // TODO remove this again case 1024: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->line1 ); break; case 1025: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->line2 ); break; default: Con_Print("VM_M_getserverliststring: bad field number passed!\n"); } } /* ========= VM_M_getserverlistnumber float getserverlistnumber(float field, float hostnr) ========= */ static void VM_M_getserverlistnumber(prvm_prog_t *prog) { const serverlist_entry_t *cache; int hostnr; VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); PRVM_G_INT(OFS_RETURN) = OFS_NULL; hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); if(hostnr == -1 && serverlist_callbackentry) { cache = serverlist_callbackentry; } else { if(hostnr < 0 || hostnr >= serverlist_viewcount) { Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); return; } cache = ServerList_GetViewEntry(hostnr); } switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { case SLIF_MAXPLAYERS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.maxplayers; break; case SLIF_NUMPLAYERS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numplayers; break; case SLIF_NUMBOTS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numbots; break; case SLIF_NUMHUMANS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.numhumans; break; case SLIF_FREESLOTS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.freeslots; break; case SLIF_PING: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.ping; break; case SLIF_PROTOCOL: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.protocol; break; case SLIF_CATEGORY: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.category; break; case SLIF_ISFAVORITE: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.isfavorite; break; default: Con_Print("VM_M_getserverlistnumber: bad field number passed!\n"); } } /* ======================== VM_M_setserverlistsort setserverlistsort(float field, float flags) ======================== */ static void VM_M_setserverlistsort(prvm_prog_t *prog) { VM_SAFEPARMCOUNT( 2, VM_M_setserverlistsort ); serverlist_sortbyfield = (serverlist_infofield_t)((int)PRVM_G_FLOAT( OFS_PARM0 )); serverlist_sortflags = (int) PRVM_G_FLOAT( OFS_PARM1 ); } /* ======================== VM_M_refreshserverlist refreshserverlist() ======================== */ static void VM_M_refreshserverlist(prvm_prog_t *prog) { qboolean do_reset = false; VM_SAFEPARMCOUNTRANGE( 0, 1, VM_M_refreshserverlist ); if (prog->argc >= 1 && PRVM_G_FLOAT(OFS_PARM0)) do_reset = true; ServerList_QueryList(do_reset, true, false, false); } /* ======================== VM_M_getserverlistindexforkey float getserverlistindexforkey(string key) ======================== */ static void VM_M_getserverlistindexforkey(prvm_prog_t *prog) { const char *key; VM_SAFEPARMCOUNT( 1, VM_M_getserverlistindexforkey ); key = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString( prog, key ); if( !strcmp( key, "cname" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_CNAME; else if( !strcmp( key, "ping" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PING; else if( !strcmp( key, "game" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_GAME; else if( !strcmp( key, "mod" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MOD; else if( !strcmp( key, "map" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAP; else if( !strcmp( key, "name" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NAME; else if( !strcmp( key, "qcstatus" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_QCSTATUS; else if( !strcmp( key, "players" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PLAYERS; else if( !strcmp( key, "maxplayers" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_MAXPLAYERS; else if( !strcmp( key, "numplayers" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMPLAYERS; else if( !strcmp( key, "numbots" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMBOTS; else if( !strcmp( key, "numhumans" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_NUMHUMANS; else if( !strcmp( key, "freeslots" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_FREESLOTS; else if( !strcmp( key, "protocol" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PROTOCOL; else if( !strcmp( key, "category" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_CATEGORY; else if( !strcmp( key, "isfavorite" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_ISFAVORITE; else PRVM_G_FLOAT( OFS_RETURN ) = -1; } /* ======================== VM_M_addwantedserverlistkey addwantedserverlistkey(string key) ======================== */ static void VM_M_addwantedserverlistkey(prvm_prog_t *prog) { VM_SAFEPARMCOUNT( 1, VM_M_addwantedserverlistkey ); } /* =============================================================================== MESSAGE WRITING used only for client and menu server uses VM_SV_... Write*(* data, float type, float to) =============================================================================== */ #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) #define MSG_ALL 2 // reliable to all #define MSG_INIT 3 // write to the init string static sizebuf_t *VM_M_WriteDest (prvm_prog_t *prog) { int dest; int destclient; if(!sv.active) prog->error_cmd("VM_M_WriteDest: game is not server (%s)", prog->name); dest = (int)PRVM_G_FLOAT(OFS_PARM1); switch (dest) { case MSG_BROADCAST: return &sv.datagram; case MSG_ONE: destclient = (int) PRVM_G_FLOAT(OFS_PARM2); if (destclient < 0 || destclient >= svs.maxclients || !svs.clients[destclient].active || !svs.clients[destclient].netconnection) prog->error_cmd("VM_clientcommand: %s: invalid client !", prog->name); return &svs.clients[destclient].netconnection->message; case MSG_ALL: return &sv.reliable_datagram; case MSG_INIT: return &sv.signon; default: prog->error_cmd("WriteDest: bad destination"); break; } return NULL; } static void VM_M_WriteByte (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteByte); MSG_WriteByte (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); } static void VM_M_WriteChar (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteChar); MSG_WriteChar (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); } static void VM_M_WriteShort (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteShort); MSG_WriteShort (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); } static void VM_M_WriteLong (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteLong); MSG_WriteLong (VM_M_WriteDest(prog), (int)PRVM_G_FLOAT(OFS_PARM0)); } static void VM_M_WriteAngle (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteAngle); MSG_WriteAngle (VM_M_WriteDest(prog), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); } static void VM_M_WriteCoord (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteCoord); MSG_WriteCoord (VM_M_WriteDest(prog), PRVM_G_FLOAT(OFS_PARM0), sv.protocol); } static void VM_M_WriteString (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteString); MSG_WriteString (VM_M_WriteDest(prog), PRVM_G_STRING(OFS_PARM0)); } static void VM_M_WriteEntity (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_M_WriteEntity); MSG_WriteShort (VM_M_WriteDest(prog), PRVM_G_EDICTNUM(OFS_PARM0)); } /* ================= VM_M_copyentity copies data from one entity to another copyentity(entity src, entity dst) ================= */ static void VM_M_copyentity (prvm_prog_t *prog) { prvm_edict_t *in, *out; VM_SAFEPARMCOUNT(2,VM_M_copyentity); in = PRVM_G_EDICT(OFS_PARM0); out = PRVM_G_EDICT(OFS_PARM1); memcpy(out->fields.fp, in->fields.fp, prog->entityfields * sizeof(prvm_vec_t)); } //#66 vector() getmousepos (EXT_CSQC) static void VM_M_getmousepos(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_M_getmousepos); if (key_consoleactive || (key_dest != key_menu && key_dest != key_menu_grabbed)) VectorSet(PRVM_G_VECTOR(OFS_RETURN), 0, 0, 0); else if (in_client_mouse) VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_windowmouse_x * vid_conwidth.integer / vid.width, in_windowmouse_y * vid_conheight.integer / vid.height, 0); else VectorSet(PRVM_G_VECTOR(OFS_RETURN), in_mouse_x * vid_conwidth.integer / vid.width, in_mouse_y * vid_conheight.integer / vid.height, 0); } static void VM_M_crypto_getkeyfp(prvm_prog_t *prog) { lhnetaddress_t addr; const char *s; char keyfp[FP64_SIZE + 1]; VM_SAFEPARMCOUNT(1,VM_M_crypto_getkeyfp); s = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString( prog, s ); if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, keyfp, sizeof(keyfp), NULL, 0, NULL, NULL)) PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, keyfp ); else PRVM_G_INT( OFS_RETURN ) = OFS_NULL; } static void VM_M_crypto_getidfp(prvm_prog_t *prog) { lhnetaddress_t addr; const char *s; char idfp[FP64_SIZE + 1]; VM_SAFEPARMCOUNT(1,VM_M_crypto_getidfp); s = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString( prog, s ); if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, idfp, sizeof(idfp), NULL, NULL)) PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, idfp ); else PRVM_G_INT( OFS_RETURN ) = OFS_NULL; } static void VM_M_crypto_getidstatus(prvm_prog_t *prog) { lhnetaddress_t addr; const char *s; qboolean issigned; VM_SAFEPARMCOUNT(1,VM_M_crypto_getidstatus); s = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString( prog, s ); if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, NULL, 0, NULL, &issigned)) PRVM_G_FLOAT( OFS_RETURN ) = issigned ? 2 : 1; else PRVM_G_FLOAT( OFS_RETURN ) = 0; } static void VM_M_crypto_getencryptlevel(prvm_prog_t *prog) { lhnetaddress_t addr; const char *s; int aeslevel; char vabuf[1024]; VM_SAFEPARMCOUNT(1,VM_M_crypto_getencryptlevel); s = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString( prog, s ); if(LHNETADDRESS_FromString(&addr, s, 26000) && Crypto_RetrieveHostKey(&addr, NULL, NULL, 0, NULL, 0, &aeslevel, NULL)) PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, aeslevel ? va(vabuf, sizeof(vabuf), "%d AES128", aeslevel) : "0"); else PRVM_G_INT( OFS_RETURN ) = OFS_NULL; } static void VM_M_crypto_getmykeyfp(prvm_prog_t *prog) { int i; char keyfp[FP64_SIZE + 1]; VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); i = PRVM_G_FLOAT( OFS_PARM0 ); switch(Crypto_RetrieveLocalKey(i, keyfp, sizeof(keyfp), NULL, 0, NULL)) { case -1: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, ""); break; case 0: PRVM_G_INT( OFS_RETURN ) = OFS_NULL; break; default: case 1: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, keyfp); break; } } static void VM_M_crypto_getmyidfp(prvm_prog_t *prog) { int i; char idfp[FP64_SIZE + 1]; VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); i = PRVM_G_FLOAT( OFS_PARM0 ); switch(Crypto_RetrieveLocalKey(i, NULL, 0, idfp, sizeof(idfp), NULL)) { case -1: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, ""); break; case 0: PRVM_G_INT( OFS_RETURN ) = OFS_NULL; break; default: case 1: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, idfp); break; } } static void VM_M_crypto_getmyidstatus(prvm_prog_t *prog) { int i; qboolean issigned; VM_SAFEPARMCOUNT(1,VM_M_crypto_getmykey); i = PRVM_G_FLOAT( OFS_PARM0 ); switch(Crypto_RetrieveLocalKey(i, NULL, 0, NULL, 0, &issigned)) { case -1: PRVM_G_FLOAT( OFS_RETURN ) = 0; // have no ID there break; case 0: PRVM_G_FLOAT( OFS_RETURN ) = -1; // out of range break; default: case 1: PRVM_G_FLOAT( OFS_RETURN ) = issigned ? 2 : 1; break; } } prvm_builtin_t vm_m_builtins[] = { NULL, // #0 NULL function (not callable) VM_checkextension, // #1 VM_error, // #2 VM_objerror, // #3 VM_print, // #4 VM_bprint, // #5 VM_sprint, // #6 VM_centerprint, // #7 VM_normalize, // #8 VM_vlen, // #9 VM_vectoyaw, // #10 VM_vectoangles, // #11 VM_random, // #12 VM_localcmd, // #13 VM_cvar, // #14 VM_cvar_set, // #15 VM_dprint, // #16 VM_ftos, // #17 VM_fabs, // #18 VM_vtos, // #19 VM_etos, // #20 VM_stof, // #21 VM_spawn, // #22 VM_remove, // #23 VM_find, // #24 VM_findfloat, // #25 VM_findchain, // #26 VM_findchainfloat, // #27 VM_precache_file, // #28 VM_precache_sound, // #29 VM_coredump, // #30 VM_traceon, // #31 VM_traceoff, // #32 VM_eprint, // #33 VM_rint, // #34 VM_floor, // #35 VM_ceil, // #36 VM_nextent, // #37 VM_sin, // #38 VM_cos, // #39 VM_sqrt, // #40 VM_randomvec, // #41 VM_registercvar, // #42 VM_min, // #43 VM_max, // #44 VM_bound, // #45 VM_pow, // #46 VM_M_copyentity, // #47 VM_fopen, // #48 VM_fclose, // #49 VM_fgets, // #50 VM_fputs, // #51 VM_strlen, // #52 VM_strcat, // #53 VM_substring, // #54 VM_stov, // #55 VM_strzone, // #56 VM_strunzone, // #57 VM_tokenize, // #58 VM_argv, // #59 VM_isserver, // #60 VM_clientcount, // #61 VM_clientstate, // #62 VM_clcommand, // #63 VM_changelevel, // #64 VM_localsound, // #65 VM_M_getmousepos, // #66 VM_gettime, // #67 VM_loadfromdata, // #68 VM_loadfromfile, // #69 VM_modulo, // #70 VM_cvar_string, // #71 VM_crash, // #72 VM_stackdump, // #73 VM_search_begin, // #74 VM_search_end, // #75 VM_search_getsize, // #76 VM_search_getfilename, // #77 VM_chr, // #78 VM_itof, // #79 VM_ftoe, // #80 VM_itof, // #81 isString VM_altstr_count, // #82 VM_altstr_prepare, // #83 VM_altstr_get, // #84 VM_altstr_set, // #85 VM_altstr_ins, // #86 VM_findflags, // #87 VM_findchainflags, // #88 VM_cvar_defstring, // #89 // deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] #if 0 VM_CL_setmodel, // #90 void(entity e, string m) setmodel (QUAKE) VM_CL_precache_model, // #91 void(string s) precache_model (QUAKE) VM_CL_setorigin, // #92 void(entity e, vector o) setorigin (QUAKE) #else NULL, NULL, NULL, #endif NULL, // #93 NULL, // #94 NULL, // #95 NULL, // #96 NULL, // #97 NULL, // #98 NULL, // #99 NULL, // #100 NULL, // #101 NULL, // #102 NULL, // #103 NULL, // #104 NULL, // #105 NULL, // #106 NULL, // #107 NULL, // #108 NULL, // #109 NULL, // #110 NULL, // #111 NULL, // #112 NULL, // #113 NULL, // #114 NULL, // #115 NULL, // #116 NULL, // #117 NULL, // #118 NULL, // #119 NULL, // #120 NULL, // #121 NULL, // #122 NULL, // #123 NULL, // #124 NULL, // #125 NULL, // #126 NULL, // #127 NULL, // #128 NULL, // #129 NULL, // #130 NULL, // #131 NULL, // #132 NULL, // #133 NULL, // #134 NULL, // #135 NULL, // #136 NULL, // #137 NULL, // #138 NULL, // #139 NULL, // #140 NULL, // #141 NULL, // #142 NULL, // #143 NULL, // #144 NULL, // #145 NULL, // #146 NULL, // #147 NULL, // #148 NULL, // #149 NULL, // #150 NULL, // #151 NULL, // #152 NULL, // #153 NULL, // #154 NULL, // #155 NULL, // #156 NULL, // #157 NULL, // #158 NULL, // #159 NULL, // #160 NULL, // #161 NULL, // #162 NULL, // #163 NULL, // #164 NULL, // #165 NULL, // #166 NULL, // #167 NULL, // #168 NULL, // #169 NULL, // #170 NULL, // #171 NULL, // #172 NULL, // #173 NULL, // #174 NULL, // #175 NULL, // #176 NULL, // #177 NULL, // #178 NULL, // #179 NULL, // #180 NULL, // #181 NULL, // #182 NULL, // #183 NULL, // #184 NULL, // #185 NULL, // #186 NULL, // #187 NULL, // #188 NULL, // #189 NULL, // #190 NULL, // #191 NULL, // #192 NULL, // #193 NULL, // #194 NULL, // #195 NULL, // #196 NULL, // #197 NULL, // #198 NULL, // #199 NULL, // #200 NULL, // #201 NULL, // #202 NULL, // #203 NULL, // #204 NULL, // #205 NULL, // #206 NULL, // #207 NULL, // #208 NULL, // #209 NULL, // #210 NULL, // #211 NULL, // #212 NULL, // #213 NULL, // #214 NULL, // #215 NULL, // #216 NULL, // #217 NULL, // #218 NULL, // #219 NULL, // #220 VM_strstrofs, // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) VM_str2chr, // #222 float(string str, float ofs) str2chr (FTE_STRINGS) VM_chr2str, // #223 string(float c, ...) chr2str (FTE_STRINGS) VM_strconv, // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) VM_strpad, // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) VM_infoadd, // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) VM_infoget, // #227 string(string info, string key) infoget (FTE_STRINGS) VM_strncmp, // #228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) VM_strncasecmp, // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) VM_strncasecmp, // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) NULL, // #231 NULL, // #232 NULL, // #233 NULL, // #234 NULL, // #235 NULL, // #236 NULL, // #237 NULL, // #238 NULL, // #239 NULL, // #240 NULL, // #241 NULL, // #242 NULL, // #243 NULL, // #244 NULL, // #245 NULL, // #246 NULL, // #247 NULL, // #248 NULL, // #249 NULL, // #250 NULL, // #251 NULL, // #252 NULL, // #253 NULL, // #254 NULL, // #255 NULL, // #256 NULL, // #257 NULL, // #258 NULL, // #259 NULL, // #260 NULL, // #261 NULL, // #262 NULL, // #263 NULL, // #264 NULL, // #265 NULL, // #266 NULL, // #267 NULL, // #268 NULL, // #269 NULL, // #270 NULL, // #271 NULL, // #272 NULL, // #273 NULL, // #274 NULL, // #275 NULL, // #276 NULL, // #277 NULL, // #278 NULL, // #279 NULL, // #280 NULL, // #281 NULL, // #282 NULL, // #283 NULL, // #284 NULL, // #285 NULL, // #286 NULL, // #287 NULL, // #288 NULL, // #289 NULL, // #290 NULL, // #291 NULL, // #292 NULL, // #293 NULL, // #294 NULL, // #295 NULL, // #296 NULL, // #297 NULL, // #298 NULL, // #299 // deactivate support for model rendering in the menu until someone has time to do it right [3/2/2008 Andreas] #if 0 // CSQC range #300-#399 VM_CL_R_ClearScene, // #300 void() clearscene (DP_QC_RENDER_SCENE) VM_CL_R_AddEntities, // #301 void(float mask) addentities (DP_QC_RENDER_SCENE) VM_CL_R_AddEntity, // #302 void(entity ent) addentity (DP_QC_RENDER_SCENE) VM_CL_R_SetView, // #303 float(float property, ...) setproperty (DP_QC_RENDER_SCENE) VM_CL_R_RenderScene, // #304 void() renderscene (DP_QC_RENDER_SCENE) VM_CL_R_AddDynamicLight, // #305 void(vector org, float radius, vector lightcolours) adddynamiclight (DP_QC_RENDER_SCENE) VM_CL_R_PolygonBegin, // #306 void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon (DP_QC_RENDER_SCENE) VM_CL_R_PolygonVertex, // #307 void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex (DP_QC_RENDER_SCENE) VM_CL_R_PolygonEnd, // #308 void() R_EndPolygon NULL/*VM_CL_R_LoadWorldModel*/, // #309 void(string modelname) R_LoadWorldModel // TODO: rearrange and merge all builtin lists and share as many extensions as possible between all VM instances [1/27/2008 Andreas] VM_CL_setattachment, // #310 void(entity e, entity tagentity, string tagname) setattachment (DP_GFX_QUAKE3MODELTAGS) (DP_QC_RENDER_SCENE) VM_CL_gettagindex, // #311 float(entity ent, string tagname) gettagindex (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) VM_CL_gettaginfo, // #312 vector(entity ent, float tagindex) gettaginfo (DP_QC_GETTAGINFO) (DP_QC_RENDER_SCENE) #else // CSQC range #300-#399 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, #endif NULL, // #313 NULL, // #314 NULL, // #315 NULL, // #316 NULL, // #317 NULL, // #318 NULL, // #319 NULL, // #320 NULL, // #321 NULL, // #322 NULL, // #323 NULL, // #324 NULL, // #325 NULL, // #326 NULL, // #327 NULL, // #328 NULL, // #329 NULL, // #330 NULL, // #331 NULL, // #332 NULL, // #333 NULL, // #334 NULL, // #335 NULL, // #336 NULL, // #337 NULL, // #338 NULL, // #339 VM_keynumtostring, // #340 string keynumtostring(float keynum) VM_stringtokeynum, // #341 float stringtokeynum(string key) VM_getkeybind, // #342 string(float keynum[, float bindmap]) getkeybind (EXT_CSQC) NULL, // #343 NULL, // #344 NULL, // #345 NULL, // #346 NULL, // #347 NULL, // #348 VM_CL_isdemo, // #349 NULL, // #350 NULL, // #351 NULL, // #352 VM_wasfreed, // #353 float(entity ent) wasfreed NULL, // #354 VM_CL_videoplaying, // #355 VM_findfont, // #356 float(string fontname) loadfont (DP_GFX_FONTS) VM_loadfont, // #357 float(string fontname, string fontmaps, string sizes, float slot) loadfont (DP_GFX_FONTS) NULL, // #358 NULL, // #359 NULL, // #360 NULL, // #361 NULL, // #362 NULL, // #363 NULL, // #364 NULL, // #365 NULL, // #366 NULL, // #367 NULL, // #368 NULL, // #369 NULL, // #370 NULL, // #371 NULL, // #372 NULL, // #373 NULL, // #374 NULL, // #375 NULL, // #376 NULL, // #377 NULL, // #378 NULL, // #379 NULL, // #380 NULL, // #381 NULL, // #382 NULL, // #383 NULL, // #384 NULL, // #385 NULL, // #386 NULL, // #387 NULL, // #388 NULL, // #389 NULL, // #390 NULL, // #391 NULL, // #392 NULL, // #393 NULL, // #394 NULL, // #395 NULL, // #396 NULL, // #397 NULL, // #398 NULL, // #399 NULL, // #400 VM_M_WriteByte, // #401 VM_M_WriteChar, // #402 VM_M_WriteShort, // #403 VM_M_WriteLong, // #404 VM_M_WriteAngle, // #405 VM_M_WriteCoord, // #406 VM_M_WriteString, // #407 VM_M_WriteEntity, // #408 NULL, // #409 NULL, // #410 NULL, // #411 NULL, // #412 NULL, // #413 NULL, // #414 NULL, // #415 NULL, // #416 NULL, // #417 NULL, // #418 NULL, // #419 NULL, // #420 NULL, // #421 NULL, // #422 NULL, // #423 NULL, // #424 NULL, // #425 NULL, // #426 NULL, // #427 NULL, // #428 NULL, // #429 NULL, // #430 NULL, // #431 NULL, // #432 NULL, // #433 NULL, // #434 NULL, // #435 NULL, // #436 NULL, // #437 NULL, // #438 NULL, // #439 VM_buf_create, // #440 float() buf_create (DP_QC_STRINGBUFFERS) VM_buf_del, // #441 void(float bufhandle) buf_del (DP_QC_STRINGBUFFERS) VM_buf_getsize, // #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) VM_buf_copy, // #443 void(float bufhandle_from, float bufhandle_to) buf_copy (DP_QC_STRINGBUFFERS) VM_buf_sort, // #444 void(float bufhandle, float sortpower, float backward) buf_sort (DP_QC_STRINGBUFFERS) VM_buf_implode, // #445 string(float bufhandle, string glue) buf_implode (DP_QC_STRINGBUFFERS) VM_bufstr_get, // #446 string(float bufhandle, float string_index) bufstr_get (DP_QC_STRINGBUFFERS) VM_bufstr_set, // #447 void(float bufhandle, float string_index, string str) bufstr_set (DP_QC_STRINGBUFFERS) VM_bufstr_add, // #448 float(float bufhandle, string str, float order) bufstr_add (DP_QC_STRINGBUFFERS) VM_bufstr_free, // #449 void(float bufhandle, float string_index) bufstr_free (DP_QC_STRINGBUFFERS) NULL, // #450 VM_iscachedpic, // #451 draw functions... VM_precache_pic, // #452 VM_freepic, // #453 VM_drawcharacter, // #454 VM_drawstring, // #455 VM_drawpic, // #456 VM_drawfill, // #457 VM_drawsetcliparea, // #458 VM_drawresetcliparea, // #459 VM_getimagesize, // #460 VM_cin_open, // #461 VM_cin_close, // #462 VM_cin_setstate, // #463 VM_cin_getstate, // #464 VM_cin_restart, // #465 VM_drawline, // #466 VM_drawcolorcodedstring, // #467 VM_stringwidth, // #468 VM_drawsubpic, // #469 VM_drawrotpic, // #470 VM_asin, // #471 float(float s) VM_asin (DP_QC_ASINACOSATANATAN2TAN) VM_acos, // #472 float(float c) VM_acos (DP_QC_ASINACOSATANATAN2TAN) VM_atan, // #473 float(float t) VM_atan (DP_QC_ASINACOSATANATAN2TAN) VM_atan2, // #474 float(float c, float s) VM_atan2 (DP_QC_ASINACOSATANATAN2TAN) VM_tan, // #475 float(float a) VM_tan (DP_QC_ASINACOSATANATAN2TAN) VM_strlennocol, // #476 float(string s) : DRESK - String Length (not counting color codes) (DP_QC_STRINGCOLORFUNCTIONS) VM_strdecolorize, // #477 string(string s) : DRESK - Decolorized String (DP_QC_STRINGCOLORFUNCTIONS) VM_strftime, // #478 string(float uselocaltime, string format, ...) (DP_QC_STRFTIME) VM_tokenizebyseparator, // #479 float(string s) tokenizebyseparator (DP_QC_TOKENIZEBYSEPARATOR) VM_strtolower, // #480 string(string s) VM_strtolower : DRESK - Return string as lowercase VM_strtoupper, // #481 string(string s) VM_strtoupper : DRESK - Return string as uppercase NULL, // #482 NULL, // #483 VM_strreplace, // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE) VM_strireplace, // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE) NULL, // #486 VM_gecko_create, // #487 float gecko_create( string name ) VM_gecko_destroy, // #488 void gecko_destroy( string name ) VM_gecko_navigate, // #489 void gecko_navigate( string name, string URI ) VM_gecko_keyevent, // #490 float gecko_keyevent( string name, float key, float eventtype ) VM_gecko_movemouse, // #491 void gecko_mousemove( string name, float x, float y ) VM_gecko_resize, // #492 void gecko_resize( string name, float w, float h ) VM_gecko_get_texture_extent, // #493 vector gecko_get_texture_extent( string name ) VM_crc16, // #494 float(float caseinsensitive, string s, ...) crc16 = #494 (DP_QC_CRC16) VM_cvar_type, // #495 float(string name) cvar_type = #495; (DP_QC_CVAR_TYPE) VM_numentityfields, // #496 float() numentityfields = #496; (QP_QC_ENTITYDATA) VM_entityfieldname, // #497 string(float fieldnum) entityfieldname = #497; (DP_QC_ENTITYDATA) VM_entityfieldtype, // #498 float(float fieldnum) entityfieldtype = #498; (DP_QC_ENTITYDATA) VM_getentityfieldstring, // #499 string(float fieldnum, entity ent) getentityfieldstring = #499; (DP_QC_ENTITYDATA) VM_putentityfieldstring, // #500 float(float fieldnum, entity ent, string s) putentityfieldstring = #500; (DP_QC_ENTITYDATA) NULL, // #501 NULL, // #502 VM_whichpack, // #503 string(string) whichpack = #503; NULL, // #504 NULL, // #505 NULL, // #506 NULL, // #507 NULL, // #508 NULL, // #509 VM_uri_escape, // #510 string(string in) uri_escape = #510; VM_uri_unescape, // #511 string(string in) uri_unescape = #511; VM_etof, // #512 float(entity ent) num_for_edict = #512 (DP_QC_NUM_FOR_EDICT) VM_uri_get, // #513 float(string uri, float id, [string post_contenttype, string post_delim, [float buf]]) uri_get = #513; (DP_QC_URI_GET, DP_QC_URI_POST) VM_tokenize_console, // #514 float(string str) tokenize_console = #514; (DP_QC_TOKENIZE_CONSOLE) VM_argv_start_index, // #515 float(float idx) argv_start_index = #515; (DP_QC_TOKENIZE_CONSOLE) VM_argv_end_index, // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE) VM_buf_cvarlist, // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST) VM_cvar_description, // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION) NULL, // #519 NULL, // #520 NULL, // #521 NULL, // #522 NULL, // #523 NULL, // #524 NULL, // #525 NULL, // #526 NULL, // #527 NULL, // #528 NULL, // #529 NULL, // #530 NULL, // #531 VM_log, // #532 VM_getsoundtime, // #533 float(entity e, float channel) getsoundtime = #533; (DP_SND_GETSOUNDTIME) VM_soundlength, // #534 float(string sample) soundlength = #534; (DP_SND_GETSOUNDTIME) VM_buf_loadfile, // #535 float(string filename, float bufhandle) buf_loadfile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_buf_writefile, // #536 float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile (DP_QC_STRINGBUFFERS_EXT_WIP) VM_bufstr_find, // #537 float(float bufhandle, string match, float matchrule, float startpos) bufstr_find (DP_QC_STRINGBUFFERS_EXT_WIP) VM_matchpattern, // #538 float(string s, string pattern, float matchrule) matchpattern (DP_QC_STRINGBUFFERS_EXT_WIP) NULL, // #539 NULL, // #540 NULL, // #541 NULL, // #542 NULL, // #543 NULL, // #544 NULL, // #545 NULL, // #546 NULL, // #547 NULL, // #548 NULL, // #549 NULL, // #550 NULL, // #551 NULL, // #552 NULL, // #553 NULL, // #554 NULL, // #555 NULL, // #556 NULL, // #557 NULL, // #558 NULL, // #559 NULL, // #560 NULL, // #561 NULL, // #562 NULL, // #563 NULL, // #564 NULL, // #565 NULL, // #566 NULL, // #567 NULL, // #568 NULL, // #569 NULL, // #570 NULL, // #571 NULL, // #572 NULL, // #573 NULL, // #574 NULL, // #575 NULL, // #576 NULL, // #577 NULL, // #578 NULL, // #579 NULL, // #580 NULL, // #581 NULL, // #582 NULL, // #583 NULL, // #584 NULL, // #585 NULL, // #586 NULL, // #587 NULL, // #588 NULL, // #589 NULL, // #590 NULL, // #591 NULL, // #592 NULL, // #593 NULL, // #594 NULL, // #595 NULL, // #596 NULL, // #597 NULL, // #598 NULL, // #599 NULL, // #600 VM_M_setkeydest, // #601 void setkeydest(float dest) VM_M_getkeydest, // #602 float getkeydest(void) VM_M_setmousetarget, // #603 void setmousetarget(float trg) VM_M_getmousetarget, // #604 float getmousetarget(void) VM_callfunction, // #605 void callfunction(...) VM_writetofile, // #606 void writetofile(float fhandle, entity ent) VM_isfunction, // #607 float isfunction(string function_name) VM_M_getresolution, // #608 vector getresolution(float number, [float forfullscreen]) VM_keynumtostring, // #609 string keynumtostring(float keynum) VM_findkeysforcommand, // #610 string findkeysforcommand(string command[, float bindmap]) VM_M_getserverliststat, // #611 float gethostcachevalue(float type) VM_M_getserverliststring, // #612 string gethostcachestring(float type, float hostnr) VM_parseentitydata, // #613 void parseentitydata(entity ent, string data) VM_stringtokeynum, // #614 float stringtokeynum(string key) VM_M_resetserverlistmasks, // #615 void resethostcachemasks(void) VM_M_setserverlistmaskstring, // #616 void sethostcachemaskstring(float mask, float fld, string str, float op) VM_M_setserverlistmasknumber, // #617 void sethostcachemasknumber(float mask, float fld, float num, float op) VM_M_resortserverlist, // #618 void resorthostcache(void) VM_M_setserverlistsort, // #619 void sethostcachesort(float fld, float descending) VM_M_refreshserverlist, // #620 void refreshhostcache(void) VM_M_getserverlistnumber, // #621 float gethostcachenumber(float fld, float hostnr) VM_M_getserverlistindexforkey,// #622 float gethostcacheindexforkey(string key) VM_M_addwantedserverlistkey, // #623 void addwantedhostcachekey(string key) VM_CL_getextresponse, // #624 string getextresponse(void) VM_netaddress_resolve, // #625 string netaddress_resolve(string, float) VM_M_getgamedirinfo, // #626 string getgamedirinfo(float n, float prop) VM_sprintf, // #627 string sprintf(string format, ...) NULL, // #628 NULL, // #629 VM_setkeybind, // #630 float(float key, string bind[, float bindmap]) setkeybind VM_getbindmaps, // #631 vector(void) getbindmap VM_setbindmaps, // #632 float(vector bm) setbindmap VM_M_crypto_getkeyfp, // #633 string(string addr) crypto_getkeyfp VM_M_crypto_getidfp, // #634 string(string addr) crypto_getidfp VM_M_crypto_getencryptlevel, // #635 string(string addr) crypto_getencryptlevel VM_M_crypto_getmykeyfp, // #636 string(float addr) crypto_getmykeyfp VM_M_crypto_getmyidfp, // #637 string(float addr) crypto_getmyidfp NULL, // #638 VM_digest_hex, // #639 NULL, // #640 VM_M_crypto_getmyidstatus, // #641 float(float i) crypto_getmyidstatus VM_coverage, // #642 VM_M_crypto_getidstatus, // #643 float(string addr) crypto_getidstatus NULL }; const int vm_m_numbuiltins = sizeof(vm_m_builtins) / sizeof(prvm_builtin_t); void MVM_init_cmd(prvm_prog_t *prog) { r_refdef_scene_t *scene; VM_Cmd_Init(prog); VM_Polygons_Reset(prog); scene = R_GetScenePointer( RST_MENU ); memset (scene, 0, sizeof (*scene)); scene->maxtempentities = 128; scene->tempentities = (entity_render_t*) Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t) * scene->maxtempentities); scene->maxentities = MAX_EDICTS + 256 + 512; scene->entities = (entity_render_t **)Mem_Alloc(prog->progs_mempool, sizeof(entity_render_t *) * scene->maxentities); scene->ambient = 32.0f; } void MVM_reset_cmd(prvm_prog_t *prog) { // note: the menu's render entities are automatically freed when the prog's pool is freed //VM_Cmd_Init(); VM_Cmd_Reset(prog); VM_Polygons_Reset(prog); } darkplaces/host_cmd.c0000664000175000017500000025101513067716220014133 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "sv_demo.h" #include "image.h" #include "prvm_cmds.h" #include "utf8lib.h" // for secure rcon authentication #include "hmac.h" #include "mdfour.h" #include int current_skill; cvar_t sv_cheats = {0, "sv_cheats", "0", "enables cheat commands in any game, and cheat impulses in dpmod"}; cvar_t sv_adminnick = {CVAR_SAVE, "sv_adminnick", "", "nick name to use for admin messages instead of host name"}; cvar_t sv_status_privacy = {CVAR_SAVE, "sv_status_privacy", "0", "do not show IP addresses in 'status' replies to clients"}; cvar_t sv_status_show_qcstatus = {CVAR_SAVE, "sv_status_show_qcstatus", "0", "show the 'qcstatus' field in status replies, not the 'frags' field. Turn this on if your mod uses this field, and the 'frags' field on the other hand has no meaningful value."}; cvar_t sv_namechangetimer = {CVAR_SAVE, "sv_namechangetimer", "5", "how often to allow name changes, in seconds (prevents people from using animated names and other tricks"}; cvar_t rcon_password = {CVAR_PRIVATE, "rcon_password", "", "password to authenticate rcon commands; NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password; may be set to a string of the form user1:pass1 user2:pass2 user3:pass3 to allow multiple user accounts - the client then has to specify ONE of these combinations"}; cvar_t rcon_secure = {CVAR_NQUSERINFOHACK, "rcon_secure", "0", "force secure rcon authentication (1 = time based, 2 = challenge based); NOTE: changing rcon_secure clears rcon_password, so set rcon_secure always before rcon_password"}; cvar_t rcon_secure_challengetimeout = {0, "rcon_secure_challengetimeout", "5", "challenge-based secure rcon: time out requests if no challenge came within this time interval"}; cvar_t rcon_address = {0, "rcon_address", "", "server address to send rcon commands to (when not connected to a server)"}; cvar_t team = {CVAR_USERINFO | CVAR_SAVE, "team", "none", "QW team (4 character limit, example: blue)"}; cvar_t skin = {CVAR_USERINFO | CVAR_SAVE, "skin", "", "QW player skin name (example: base)"}; cvar_t noaim = {CVAR_USERINFO | CVAR_SAVE, "noaim", "1", "QW option to disable vertical autoaim"}; cvar_t r_fixtrans_auto = {0, "r_fixtrans_auto", "0", "automatically fixtrans textures (when set to 2, it also saves the fixed versions to a fixtrans directory)"}; qboolean allowcheats = false; extern qboolean host_shuttingdown; extern cvar_t developer_entityparsing; /* ================== Host_Quit_f ================== */ void Host_Quit_f (void) { if(host_shuttingdown) Con_Printf("shutting down already!\n"); else Sys_Quit (0); } /* ================== Host_Status_f ================== */ static void Host_Status_f (void) { prvm_prog_t *prog = SVVM_prog; char qcstatus[256]; client_t *client; int seconds = 0, minutes = 0, hours = 0, i, j, k, in, players, ping = 0, packetloss = 0; void (*print) (const char *fmt, ...); char ip[48]; // can contain a full length v6 address with [] and a port int frags; char vabuf[1024]; if (cmd_source == src_command) { // if running a client, try to send over network so the client's status report parser will see the report if (cls.state == ca_connected) { Cmd_ForwardToServer (); return; } print = Con_Printf; } else print = SV_ClientPrintf; if (!sv.active) return; in = 0; if (Cmd_Argc() == 2) { if (strcmp(Cmd_Argv(1), "1") == 0) in = 1; else if (strcmp(Cmd_Argv(1), "2") == 0) in = 2; } for (players = 0, i = 0;i < svs.maxclients;i++) if (svs.clients[i].active) players++; print ("host: %s\n", Cvar_VariableString ("hostname")); print ("version: %s build %s (gamename %s)\n", gamename, buildstring, gamenetworkfiltername); print ("protocol: %i (%s)\n", Protocol_NumberForEnum(sv.protocol), Protocol_NameForEnum(sv.protocol)); print ("map: %s\n", sv.name); print ("timing: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); print ("players: %i active (%i max)\n\n", players, svs.maxclients); if (in == 1) print ("^2IP %%pl ping time frags no name\n"); else if (in == 2) print ("^5IP no name\n"); for (i = 0, k = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (!client->active) continue; ++k; if (in == 0 || in == 1) { seconds = (int)(realtime - client->connecttime); minutes = seconds / 60; if (minutes) { seconds -= (minutes * 60); hours = minutes / 60; if (hours) minutes -= (hours * 60); } else hours = 0; packetloss = 0; if (client->netconnection) for (j = 0;j < NETGRAPH_PACKETS;j++) if (client->netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) packetloss++; packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; ping = bound(0, (int)floor(client->ping*1000+0.5), 9999); } if(sv_status_privacy.integer && cmd_source != src_command) strlcpy(ip, client->netconnection ? "hidden" : "botclient", 48); else strlcpy(ip, (client->netconnection && client->netconnection->address) ? client->netconnection->address : "botclient", 48); frags = client->frags; if(sv_status_show_qcstatus.integer) { prvm_edict_t *ed = PRVM_EDICT_NUM(i + 1); const char *str = PRVM_GetString(prog, PRVM_serveredictstring(ed, clientstatus)); if(str && *str) { char *p; const char *q; p = qcstatus; for(q = str; *q && p != qcstatus + sizeof(qcstatus) - 1; ++q) if(*q != '\\' && *q != '"' && !ISWHITESPACE(*q)) *p++ = *q; *p = 0; if(*qcstatus) frags = atoi(qcstatus); } } if (in == 0) // default layout { if (sv.protocol == PROTOCOL_QUAKE && svs.maxclients <= 99) { // LordHavoc: this is very touchy because we must maintain ProQuake compatible status output print ("#%-2u %-16.16s %3i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); print (" %s\n", ip); } else { // LordHavoc: no real restrictions here, not a ProQuake-compatible protocol anyway... print ("#%-3u %-16.16s %4i %2i:%02i:%02i\n", i+1, client->name, frags, hours, minutes, seconds); print (" %s\n", ip); } } else if (in == 1) // extended layout { print ("%s%-47s %2i %4i %2i:%02i:%02i %4i #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, packetloss, ping, hours, minutes, seconds, frags, i+1, client->name); } else if (in == 2) // reduced layout { print ("%s%-47s #%-3u ^7%s\n", k%2 ? "^3" : "^7", ip, i+1, client->name); } } } /* ================== Host_God_f Sets client to godmode ================== */ static void Host_God_f (void) { prvm_prog_t *prog = SVVM_prog; if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); return; } PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_GODMODE; if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_GODMODE) ) SV_ClientPrint("godmode OFF\n"); else SV_ClientPrint("godmode ON\n"); } static void Host_Notarget_f (void) { prvm_prog_t *prog = SVVM_prog; if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); return; } PRVM_serveredictfloat(host_client->edict, flags) = (int)PRVM_serveredictfloat(host_client->edict, flags) ^ FL_NOTARGET; if (!((int)PRVM_serveredictfloat(host_client->edict, flags) & FL_NOTARGET) ) SV_ClientPrint("notarget OFF\n"); else SV_ClientPrint("notarget ON\n"); } qboolean noclip_anglehack; static void Host_Noclip_f (void) { prvm_prog_t *prog = SVVM_prog; if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); return; } if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_NOCLIP) { noclip_anglehack = true; PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_NOCLIP; SV_ClientPrint("noclip ON\n"); } else { noclip_anglehack = false; PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; SV_ClientPrint("noclip OFF\n"); } } /* ================== Host_Fly_f Sets client to flymode ================== */ static void Host_Fly_f (void) { prvm_prog_t *prog = SVVM_prog; if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); return; } if (PRVM_serveredictfloat(host_client->edict, movetype) != MOVETYPE_FLY) { PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_FLY; SV_ClientPrint("flymode ON\n"); } else { PRVM_serveredictfloat(host_client->edict, movetype) = MOVETYPE_WALK; SV_ClientPrint("flymode OFF\n"); } } /* ================== Host_Ping_f ================== */ void Host_Pings_f (void); // called by Host_Ping_f static void Host_Ping_f (void) { int i; client_t *client; void (*print) (const char *fmt, ...); if (cmd_source == src_command) { // if running a client, try to send over network so the client's ping report parser will see the report if (cls.state == ca_connected) { Cmd_ForwardToServer (); return; } print = Con_Printf; } else print = SV_ClientPrintf; if (!sv.active) return; print("Client ping times:\n"); for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (!client->active) continue; print("%4i %s\n", bound(0, (int)floor(client->ping*1000+0.5), 9999), client->name); } // now call the Pings command also, which will send a report that contains packet loss for the scoreboard (as well as a simpler ping report) // actually, don't, it confuses old clients (resulting in "unknown command pingplreport" flooding the console) //Host_Pings_f(); } /* =============================================================================== SERVER TRANSITIONS =============================================================================== */ /* ====================== Host_Map_f handle a map command from the console. Active clients are kicked off. ====================== */ static void Host_Map_f (void) { char level[MAX_QPATH]; if (Cmd_Argc() != 2) { Con_Print("map : start a new game (kicks off all players)\n"); return; } // GAME_DELUXEQUAKE - clear warpmark (used by QC) if (gamemode == GAME_DELUXEQUAKE) Cvar_Set("warpmark", ""); cls.demonum = -1; // stop demo loop in case this fails CL_Disconnect (); Host_ShutdownServer(); if(svs.maxclients != svs.maxclients_next) { svs.maxclients = svs.maxclients_next; if (svs.clients) Mem_Free(svs.clients); svs.clients = (client_t *)Mem_Alloc(sv_mempool, sizeof(client_t) * svs.maxclients); } #ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); #endif key_dest = key_game; svs.serverflags = 0; // haven't completed an episode yet allowcheats = sv_cheats.integer != 0; strlcpy(level, Cmd_Argv(1), sizeof(level)); SV_SpawnServer(level); if (sv.active && cls.state == ca_disconnected) CL_EstablishConnection("local:1", -2); } /* ================== Host_Changelevel_f Goes to a new map, taking all clients along ================== */ static void Host_Changelevel_f (void) { char level[MAX_QPATH]; if (Cmd_Argc() != 2) { Con_Print("changelevel : continue game on a new level\n"); return; } // HACKHACKHACK if (!sv.active) { Host_Map_f(); return; } #ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); #endif key_dest = key_game; SV_SaveSpawnparms (); allowcheats = sv_cheats.integer != 0; strlcpy(level, Cmd_Argv(1), sizeof(level)); SV_SpawnServer(level); if (sv.active && cls.state == ca_disconnected) CL_EstablishConnection("local:1", -2); } /* ================== Host_Restart_f Restarts the current server for a dead player ================== */ static void Host_Restart_f (void) { char mapname[MAX_QPATH]; if (Cmd_Argc() != 1) { Con_Print("restart : restart current level\n"); return; } if (!sv.active) { Con_Print("Only the server may restart\n"); return; } #ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); #endif key_dest = key_game; allowcheats = sv_cheats.integer != 0; strlcpy(mapname, sv.name, sizeof(mapname)); SV_SpawnServer(mapname); if (sv.active && cls.state == ca_disconnected) CL_EstablishConnection("local:1", -2); } /* ================== Host_Reconnect_f This command causes the client to wait for the signon messages again. This is sent just before a server changes levels ================== */ void Host_Reconnect_f (void) { char temp[128]; // if not connected, reconnect to the most recent server if (!cls.netcon) { // if we have connected to a server recently, the userinfo // will still contain its IP address, so get the address... InfoString_GetValue(cls.userinfo, "*ip", temp, sizeof(temp)); if (temp[0]) CL_EstablishConnection(temp, -1); else Con_Printf("Reconnect to what server? (you have not connected to a server yet)\n"); return; } // if connected, do something based on protocol if (cls.protocol == PROTOCOL_QUAKEWORLD) { // quakeworld can just re-login if (cls.qw_downloadmemory) // don't change when downloading return; S_StopAllSounds(); if (cls.state == ca_connected && cls.signon < SIGNONS) { Con_Printf("reconnecting...\n"); MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, "new"); } } else { // netquake uses reconnect on level changes (silly) if (Cmd_Argc() != 1) { Con_Print("reconnect : wait for signon messages again\n"); return; } if (!cls.signon) { Con_Print("reconnect: no signon, ignoring reconnect\n"); return; } cls.signon = 0; // need new connection messages } } /* ===================== Host_Connect_f User command to connect to server ===================== */ static void Host_Connect_f (void) { if (Cmd_Argc() < 2) { Con_Print("connect [ ...]: connect to a multiplayer game\n"); return; } // clear the rcon password, to prevent vulnerability by stuffcmd-ing a connect command if(rcon_secure.integer <= 0) Cvar_SetQuick(&rcon_password, ""); CL_EstablishConnection(Cmd_Argv(1), 2); } /* =============================================================================== LOAD / SAVE GAME =============================================================================== */ #define SAVEGAME_VERSION 5 void Host_Savegame_to(prvm_prog_t *prog, const char *name) { qfile_t *f; int i, k, l, numbuffers, lightstyles = 64; char comment[SAVEGAME_COMMENT_LENGTH+1]; char line[MAX_INPUTLINE]; qboolean isserver; char *s; // first we have to figure out if this can be saved in 64 lightstyles // (for Quake compatibility) for (i=64 ; iedicts, message)), (int)PRVM_serverglobalfloat(killed_monsters), (int)PRVM_serverglobalfloat(total_monsters)); else dpsnprintf(comment, sizeof(comment), "(crash dump of %s progs)", prog->name); // convert space to _ to make stdio happy // LordHavoc: convert control characters to _ as well for (i=0 ; inum_edicts ; i++) { FS_Printf(f,"// edict %d\n", i); //Con_Printf("edict %d...\n", i); PRVM_ED_Write (prog, f, PRVM_EDICT_NUM(i)); } #if 1 FS_Printf(f,"/*\n"); FS_Printf(f,"// DarkPlaces extended savegame\n"); // darkplaces extension - extra lightstyles, support for color lightstyles for (i=0 ; istringbuffersarray); for (i = 0; i < numbuffers; i++) { prvm_stringbuffer_t *stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i); if(stringbuffer && (stringbuffer->flags & STRINGBUFFER_SAVED)) { FS_Printf(f,"sv.buffer %i %i \"string\"\n", i, stringbuffer->flags & STRINGBUFFER_QCFLAGS); for(k = 0; k < stringbuffer->num_strings; k++) { if (!stringbuffer->strings[k]) continue; // Parse the string a bit to turn special characters // (like newline, specifically) into escape codes s = stringbuffer->strings[k]; for (l = 0;l < (int)sizeof(line) - 2 && *s;) { if (*s == '\n') { line[l++] = '\\'; line[l++] = 'n'; } else if (*s == '\r') { line[l++] = '\\'; line[l++] = 'r'; } else if (*s == '\\') { line[l++] = '\\'; line[l++] = '\\'; } else if (*s == '"') { line[l++] = '\\'; line[l++] = '"'; } else line[l++] = *s; s++; } line[l] = '\0'; FS_Printf(f,"sv.bufstr %i %i \"%s\"\n", i, k, line); } } } FS_Printf(f,"*/\n"); #endif FS_Close (f); Con_Print("done.\n"); } /* =============== Host_Savegame_f =============== */ static void Host_Savegame_f (void) { prvm_prog_t *prog = SVVM_prog; char name[MAX_QPATH]; qboolean deadflag = false; if (!sv.active) { Con_Print("Can't save - no server running.\n"); return; } deadflag = cl.islocalgame && svs.clients[0].active && PRVM_serveredictfloat(svs.clients[0].edict, deadflag); if (cl.islocalgame) { // singleplayer checks if (cl.intermission) { Con_Print("Can't save in intermission.\n"); return; } if (deadflag) { Con_Print("Can't savegame with a dead player\n"); return; } } else Con_Print("Warning: saving a multiplayer game may have strange results when restored (to properly resume, all players must join in the same player slots and then the game can be reloaded).\n"); if (Cmd_Argc() != 2) { Con_Print("save : save a game\n"); return; } if (strstr(Cmd_Argv(1), "..")) { Con_Print("Relative pathnames are not allowed.\n"); return; } strlcpy (name, Cmd_Argv(1), sizeof (name)); FS_DefaultExtension (name, ".sav", sizeof (name)); Host_Savegame_to(prog, name); } /* =============== Host_Loadgame_f =============== */ static void Host_Loadgame_f (void) { prvm_prog_t *prog = SVVM_prog; char filename[MAX_QPATH]; char mapname[MAX_QPATH]; float time; const char *start; const char *end; const char *t; char *text; prvm_edict_t *ent; int i, k, numbuffers; int entnum; int version; float spawn_parms[NUM_SPAWN_PARMS]; prvm_stringbuffer_t *stringbuffer; if (Cmd_Argc() != 2) { Con_Print("load : load a game\n"); return; } strlcpy (filename, Cmd_Argv(1), sizeof(filename)); FS_DefaultExtension (filename, ".sav", sizeof (filename)); Con_Printf("Loading game from %s...\n", filename); // stop playing demos if (cls.demoplayback) CL_Disconnect (); #ifdef CONFIG_MENU // remove menu if (key_dest == key_menu || key_dest == key_menu_grabbed) MR_ToggleMenu(0); #endif key_dest = key_game; cls.demonum = -1; // stop demo loop in case this fails t = text = (char *)FS_LoadFile (filename, tempmempool, false, NULL); if (!text) { Con_Print("ERROR: couldn't open.\n"); return; } if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading version\n"); // version COM_ParseToken_Simple(&t, false, false, true); version = atoi(com_token); if (version != SAVEGAME_VERSION) { Mem_Free(text); Con_Printf("Savegame is version %i, not %i\n", version, SAVEGAME_VERSION); return; } if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading description\n"); // description COM_ParseToken_Simple(&t, false, false, true); for (i = 0;i < NUM_SPAWN_PARMS;i++) { COM_ParseToken_Simple(&t, false, false, true); spawn_parms[i] = atof(com_token); } // skill COM_ParseToken_Simple(&t, false, false, true); // this silliness is so we can load 1.06 save files, which have float skill values current_skill = (int)(atof(com_token) + 0.5); Cvar_SetValue ("skill", (float)current_skill); if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading mapname\n"); // mapname COM_ParseToken_Simple(&t, false, false, true); strlcpy (mapname, com_token, sizeof(mapname)); if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading time\n"); // time COM_ParseToken_Simple(&t, false, false, true); time = atof(com_token); allowcheats = sv_cheats.integer != 0; if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: spawning server\n"); SV_SpawnServer (mapname); if (!sv.active) { Mem_Free(text); Con_Print("Couldn't load map\n"); return; } sv.paused = true; // pause until all clients connect sv.loadgame = true; if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading light styles\n"); // load the light styles // -1 is the globals entnum = -1; for (i = 0;i < MAX_LIGHTSTYLES;i++) { // light style start = t; COM_ParseToken_Simple(&t, false, false, true); // if this is a 64 lightstyle savegame produced by Quake, stop now // we have to check this because darkplaces may save more than 64 if (com_token[0] == '{') { t = start; break; } strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); } if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: skipping until globals\n"); // now skip everything before the first opening brace // (this is for forward compatibility, so that older versions (at // least ones with this fix) can load savegames with extra data before the // first brace, as might be produced by a later engine version) for (;;) { start = t; if (!COM_ParseToken_Simple(&t, false, false, true)) break; if (com_token[0] == '{') { t = start; break; } } // unlink all entities World_UnlinkAll(&sv.world); // load the edicts out of the savegame file end = t; for (;;) { start = t; while (COM_ParseToken_Simple(&t, false, false, true)) if (!strcmp(com_token, "}")) break; if (!COM_ParseToken_Simple(&start, false, false, true)) { // end of file break; } if (strcmp(com_token,"{")) { Mem_Free(text); Host_Error ("First token isn't a brace"); } if (entnum == -1) { if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading globals\n"); // parse the global vars PRVM_ED_ParseGlobals (prog, start); // restore the autocvar globals Cvar_UpdateAllAutoCvars(); } else { // parse an edict if (entnum >= MAX_EDICTS) { Mem_Free(text); Host_Error("Host_PerformLoadGame: too many edicts in save file (reached MAX_EDICTS %i)", MAX_EDICTS); } while (entnum >= prog->max_edicts) PRVM_MEM_IncreaseEdicts(prog); ent = PRVM_EDICT_NUM(entnum); memset(ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); ent->priv.server->free = false; if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading edict %d\n", entnum); PRVM_ED_ParseEdict (prog, start, ent); // link it into the bsp tree if (!ent->priv.server->free) SV_LinkEdict(ent); } end = t; entnum++; } prog->num_edicts = entnum; sv.time = time; for (i = 0;i < NUM_SPAWN_PARMS;i++) svs.clients[0].spawn_parms[i] = spawn_parms[i]; if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: skipping until extended data\n"); // read extended data if present // the extended data is stored inside a /* */ comment block, which the // parser intentionally skips, so we have to check for it manually here if(end) { while (*end == '\r' || *end == '\n') end++; if (end[0] == '/' && end[1] == '*' && (end[2] == '\r' || end[2] == '\n')) { if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: loading extended data\n"); Con_Printf("Loading extended DarkPlaces savegame\n"); t = end + 2; memset(sv.lightstyles[0], 0, sizeof(sv.lightstyles)); memset(sv.model_precache[0], 0, sizeof(sv.model_precache)); memset(sv.sound_precache[0], 0, sizeof(sv.sound_precache)); BufStr_Flush(prog); while (COM_ParseToken_Simple(&t, false, false, true)) { if (!strcmp(com_token, "sv.lightstyles")) { COM_ParseToken_Simple(&t, false, false, true); i = atoi(com_token); COM_ParseToken_Simple(&t, false, false, true); if (i >= 0 && i < MAX_LIGHTSTYLES) strlcpy(sv.lightstyles[i], com_token, sizeof(sv.lightstyles[i])); else Con_Printf("unsupported lightstyle %i \"%s\"\n", i, com_token); } else if (!strcmp(com_token, "sv.model_precache")) { COM_ParseToken_Simple(&t, false, false, true); i = atoi(com_token); COM_ParseToken_Simple(&t, false, false, true); if (i >= 0 && i < MAX_MODELS) { strlcpy(sv.model_precache[i], com_token, sizeof(sv.model_precache[i])); sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, sv.model_precache[i][0] == '*' ? sv.worldname : NULL); } else Con_Printf("unsupported model %i \"%s\"\n", i, com_token); } else if (!strcmp(com_token, "sv.sound_precache")) { COM_ParseToken_Simple(&t, false, false, true); i = atoi(com_token); COM_ParseToken_Simple(&t, false, false, true); if (i >= 0 && i < MAX_SOUNDS) strlcpy(sv.sound_precache[i], com_token, sizeof(sv.sound_precache[i])); else Con_Printf("unsupported sound %i \"%s\"\n", i, com_token); } else if (!strcmp(com_token, "sv.buffer")) { if (COM_ParseToken_Simple(&t, false, false, true)) { i = atoi(com_token); if (i >= 0) { k = STRINGBUFFER_SAVED; if (COM_ParseToken_Simple(&t, false, false, true)) k |= atoi(com_token); if (!BufStr_FindCreateReplace(prog, i, k, "string")) Con_Printf("failed to create stringbuffer %i\n", i); } else Con_Printf("unsupported stringbuffer index %i \"%s\"\n", i, com_token); } else Con_Printf("unexpected end of line when parsing sv.buffer (expected buffer index)\n"); } else if (!strcmp(com_token, "sv.bufstr")) { if (!COM_ParseToken_Simple(&t, false, false, true)) Con_Printf("unexpected end of line when parsing sv.bufstr\n"); else { i = atoi(com_token); stringbuffer = BufStr_FindCreateReplace(prog, i, STRINGBUFFER_SAVED, "string"); if (stringbuffer) { if (COM_ParseToken_Simple(&t, false, false, true)) { k = atoi(com_token); if (COM_ParseToken_Simple(&t, false, false, true)) BufStr_Set(prog, stringbuffer, k, com_token); else Con_Printf("unexpected end of line when parsing sv.bufstr (expected string)\n"); } else Con_Printf("unexpected end of line when parsing sv.bufstr (expected strindex)\n"); } else Con_Printf("failed to create stringbuffer %i \"%s\"\n", i, com_token); } } // skip any trailing text or unrecognized commands while (COM_ParseToken_Simple(&t, true, false, true) && strcmp(com_token, "\n")) ; } } } Mem_Free(text); // remove all temporary flagged string buffers (ones created with BufStr_FindCreateReplace) numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); for (i = 0; i < numbuffers; i++) { if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) ) if (stringbuffer->flags & STRINGBUFFER_TEMP) BufStr_Del(prog, stringbuffer); } if(developer_entityparsing.integer) Con_Printf("Host_Loadgame_f: finished\n"); // make sure we're connected to loopback if (sv.active && cls.state == ca_disconnected) CL_EstablishConnection("local:1", -2); } //============================================================================ /* ====================== Host_Name_f ====================== */ cvar_t cl_name = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_name", "player", "internal storage cvar for current player name (changed by name command)"}; static void Host_Name_f (void) { prvm_prog_t *prog = SVVM_prog; int i, j; qboolean valid_colors; const char *newNameSource; char newName[sizeof(host_client->name)]; if (Cmd_Argc () == 1) { if (cmd_source == src_command) { Con_Printf("name: %s\n", cl_name.string); } return; } if (Cmd_Argc () == 2) newNameSource = Cmd_Argv(1); else newNameSource = Cmd_Args(); strlcpy(newName, newNameSource, sizeof(newName)); if (cmd_source == src_command) { Cvar_Set ("_cl_name", newName); if (strlen(newNameSource) >= sizeof(newName)) // overflowed { Con_Printf("Your name is longer than %i chars! It has been truncated.\n", (int) (sizeof(newName) - 1)); Con_Printf("name: %s\n", cl_name.string); } return; } if (realtime < host_client->nametime) { SV_ClientPrintf("You can't change name more than once every %.1f seconds!\n", max(0.0f, sv_namechangetimer.value)); return; } host_client->nametime = realtime + max(0.0f, sv_namechangetimer.value); // point the string back at updateclient->name to keep it safe strlcpy (host_client->name, newName, sizeof (host_client->name)); for (i = 0, j = 0;host_client->name[i];i++) if (host_client->name[i] != '\r' && host_client->name[i] != '\n') host_client->name[j++] = host_client->name[i]; host_client->name[j] = 0; if(host_client->name[0] == 1 || host_client->name[0] == 2) // may interfere with chat area, and will needlessly beep; so let's add a ^7 { memmove(host_client->name + 2, host_client->name, sizeof(host_client->name) - 2); host_client->name[sizeof(host_client->name) - 1] = 0; host_client->name[0] = STRING_COLOR_TAG; host_client->name[1] = '0' + STRING_COLOR_DEFAULT; } u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors); if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string { size_t l; l = strlen(host_client->name); if(l < sizeof(host_client->name) - 1) { // duplicate the color tag to escape it host_client->name[i] = STRING_COLOR_TAG; host_client->name[i+1] = 0; //Con_DPrintf("abuse detected, adding another trailing color tag\n"); } else { // remove the last character to fix the color code host_client->name[l-1] = 0; //Con_DPrintf("abuse detected, removing a trailing color tag\n"); } } // find the last color tag offset and decide if we need to add a reset tag for (i = 0, j = -1;host_client->name[i];i++) { if (host_client->name[i] == STRING_COLOR_TAG) { if (host_client->name[i+1] >= '0' && host_client->name[i+1] <= '9') { j = i; // if this happens to be a reset tag then we don't need one if (host_client->name[i+1] == '0' + STRING_COLOR_DEFAULT) j = -1; i++; continue; } if (host_client->name[i+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(host_client->name[i+2]) && isxdigit(host_client->name[i+3]) && isxdigit(host_client->name[i+4])) { j = i; i += 4; continue; } if (host_client->name[i+1] == STRING_COLOR_TAG) { i++; continue; } } } // does not end in the default color string, so add it if (j >= 0 && strlen(host_client->name) < sizeof(host_client->name) - 2) memcpy(host_client->name + strlen(host_client->name), STRING_COLOR_DEFAULT_STR, strlen(STRING_COLOR_DEFAULT_STR) + 1); PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name); if (strcmp(host_client->old_name, host_client->name)) { if (host_client->begun) SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatename); MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->name); SV_WriteNetnameIntoDemo(host_client); } } /* ====================== Host_Playermodel_f ====================== */ cvar_t cl_playermodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playermodel", "", "internal storage cvar for current player model in Nexuiz/Xonotic (changed by playermodel command)"}; // the old cl_playermodel in cl_main has been renamed to __cl_playermodel static void Host_Playermodel_f (void) { prvm_prog_t *prog = SVVM_prog; int i, j; char newPath[sizeof(host_client->playermodel)]; if (Cmd_Argc () == 1) { if (cmd_source == src_command) { Con_Printf("\"playermodel\" is \"%s\"\n", cl_playermodel.string); } return; } if (Cmd_Argc () == 2) strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); else strlcpy (newPath, Cmd_Args(), sizeof (newPath)); for (i = 0, j = 0;newPath[i];i++) if (newPath[i] != '\r' && newPath[i] != '\n') newPath[j++] = newPath[i]; newPath[j] = 0; if (cmd_source == src_command) { Cvar_Set ("_cl_playermodel", newPath); return; } /* if (realtime < host_client->nametime) { SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); return; } host_client->nametime = realtime + 5; */ // point the string back at updateclient->name to keep it safe strlcpy (host_client->playermodel, newPath, sizeof (host_client->playermodel)); PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel); if (strcmp(host_client->old_model, host_client->playermodel)) { strlcpy(host_client->old_model, host_client->playermodel, sizeof(host_client->old_model)); /*// send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatepmodel); MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->playermodel);*/ } } /* ====================== Host_Playerskin_f ====================== */ cvar_t cl_playerskin = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_playerskin", "", "internal storage cvar for current player skin in Nexuiz/Xonotic (changed by playerskin command)"}; static void Host_Playerskin_f (void) { prvm_prog_t *prog = SVVM_prog; int i, j; char newPath[sizeof(host_client->playerskin)]; if (Cmd_Argc () == 1) { if (cmd_source == src_command) { Con_Printf("\"playerskin\" is \"%s\"\n", cl_playerskin.string); } return; } if (Cmd_Argc () == 2) strlcpy (newPath, Cmd_Argv(1), sizeof (newPath)); else strlcpy (newPath, Cmd_Args(), sizeof (newPath)); for (i = 0, j = 0;newPath[i];i++) if (newPath[i] != '\r' && newPath[i] != '\n') newPath[j++] = newPath[i]; newPath[j] = 0; if (cmd_source == src_command) { Cvar_Set ("_cl_playerskin", newPath); return; } /* if (realtime < host_client->nametime) { SV_ClientPrintf("You can't change playermodel more than once every 5 seconds!\n"); return; } host_client->nametime = realtime + 5; */ // point the string back at updateclient->name to keep it safe strlcpy (host_client->playerskin, newPath, sizeof (host_client->playerskin)); PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin); if (strcmp(host_client->old_skin, host_client->playerskin)) { //if (host_client->begun) // SV_BroadcastPrintf("%s changed skin to %s\n", host_client->name, host_client->playerskin); strlcpy(host_client->old_skin, host_client->playerskin, sizeof(host_client->old_skin)); /*// send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatepskin); MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteString (&sv.reliable_datagram, host_client->playerskin);*/ } } static void Host_Version_f (void) { Con_Printf("Version: %s build %s\n", gamename, buildstring); } static void Host_Say(qboolean teamonly) { prvm_prog_t *prog = SVVM_prog; client_t *save; int j, quoted; const char *p1; char *p2; // LordHavoc: long say messages char text[1024]; qboolean fromServer = false; if (cmd_source == src_command) { if (cls.state == ca_dedicated) { fromServer = true; teamonly = false; } else { Cmd_ForwardToServer (); return; } } if (Cmd_Argc () < 2) return; if (!teamplay.integer) teamonly = false; p1 = Cmd_Args(); quoted = false; if (*p1 == '\"') { quoted = true; p1++; } // note this uses the chat prefix \001 if (!fromServer && !teamonly) dpsnprintf (text, sizeof(text), "\001%s: %s", host_client->name, p1); else if (!fromServer && teamonly) dpsnprintf (text, sizeof(text), "\001(%s): %s", host_client->name, p1); else if(*(sv_adminnick.string)) dpsnprintf (text, sizeof(text), "\001<%s> %s", sv_adminnick.string, p1); else dpsnprintf (text, sizeof(text), "\001<%s> %s", hostname.string, p1); p2 = text + strlen(text); while ((const char *)p2 > (const char *)text && (p2[-1] == '\r' || p2[-1] == '\n' || (p2[-1] == '\"' && quoted))) { if (p2[-1] == '\"' && quoted) quoted = false; p2[-1] = 0; p2--; } strlcat(text, "\n", sizeof(text)); // note: save is not a valid edict if fromServer is true save = host_client; for (j = 0, host_client = svs.clients;j < svs.maxclients;j++, host_client++) if (host_client->active && (!teamonly || PRVM_serveredictfloat(host_client->edict, team) == PRVM_serveredictfloat(save->edict, team))) SV_ClientPrint(text); host_client = save; if (cls.state == ca_dedicated) Con_Print(&text[1]); } static void Host_Say_f(void) { Host_Say(false); } static void Host_Say_Team_f(void) { Host_Say(true); } static void Host_Tell_f(void) { const char *playername_start = NULL; size_t playername_length = 0; int playernumber = 0; client_t *save; int j; const char *p1, *p2; char text[MAX_INPUTLINE]; // LordHavoc: FIXME: temporary buffer overflow fix (was 64) qboolean fromServer = false; if (cmd_source == src_command) { if (cls.state == ca_dedicated) fromServer = true; else { Cmd_ForwardToServer (); return; } } if (Cmd_Argc () < 2) return; // note this uses the chat prefix \001 if (!fromServer) dpsnprintf (text, sizeof(text), "\001%s tells you: ", host_client->name); else if(*(sv_adminnick.string)) dpsnprintf (text, sizeof(text), "\001<%s tells you> ", sv_adminnick.string); else dpsnprintf (text, sizeof(text), "\001<%s tells you> ", hostname.string); p1 = Cmd_Args(); p2 = p1 + strlen(p1); // remove the target name while (p1 < p2 && *p1 == ' ') p1++; if(*p1 == '#') { ++p1; while (p1 < p2 && *p1 == ' ') p1++; while (p1 < p2 && isdigit(*p1)) { playernumber = playernumber * 10 + (*p1 - '0'); p1++; } --playernumber; } else if(*p1 == '"') { ++p1; playername_start = p1; while (p1 < p2 && *p1 != '"') p1++; playername_length = p1 - playername_start; if(p1 < p2) p1++; } else { playername_start = p1; while (p1 < p2 && *p1 != ' ') p1++; playername_length = p1 - playername_start; } while (p1 < p2 && *p1 == ' ') p1++; if(playername_start) { // set playernumber to the right client char namebuf[128]; if(playername_length >= sizeof(namebuf)) { if (fromServer) Con_Print("Host_Tell: too long player name/ID\n"); else SV_ClientPrint("Host_Tell: too long player name/ID\n"); return; } memcpy(namebuf, playername_start, playername_length); namebuf[playername_length] = 0; for (playernumber = 0; playernumber < svs.maxclients; playernumber++) { if (!svs.clients[playernumber].active) continue; if (strcasecmp(svs.clients[playernumber].name, namebuf) == 0) break; } } if(playernumber < 0 || playernumber >= svs.maxclients || !(svs.clients[playernumber].active)) { if (fromServer) Con_Print("Host_Tell: invalid player name/ID\n"); else SV_ClientPrint("Host_Tell: invalid player name/ID\n"); return; } // remove trailing newlines while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) p2--; // remove quotes if present if (*p1 == '"') { p1++; if (p2[-1] == '"') p2--; else if (fromServer) Con_Print("Host_Tell: missing end quote\n"); else SV_ClientPrint("Host_Tell: missing end quote\n"); } while (p2 > p1 && (p2[-1] == '\n' || p2[-1] == '\r')) p2--; if(p1 == p2) return; // empty say for (j = (int)strlen(text);j < (int)(sizeof(text) - 2) && p1 < p2;) text[j++] = *p1++; text[j++] = '\n'; text[j++] = 0; save = host_client; host_client = svs.clients + playernumber; SV_ClientPrint(text); host_client = save; } /* ================== Host_Color_f ================== */ cvar_t cl_color = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_color", "0", "internal storage cvar for current player colors (changed by color command)"}; static void Host_Color(int changetop, int changebottom) { prvm_prog_t *prog = SVVM_prog; int top, bottom, playercolor; // get top and bottom either from the provided values or the current values // (allows changing only top or bottom, or both at once) top = changetop >= 0 ? changetop : (cl_color.integer >> 4); bottom = changebottom >= 0 ? changebottom : cl_color.integer; top &= 15; bottom &= 15; // LordHavoc: allowing skin colormaps 14 and 15 by commenting this out //if (top > 13) // top = 13; //if (bottom > 13) // bottom = 13; playercolor = top*16 + bottom; if (cmd_source == src_command) { Cvar_SetValueQuick(&cl_color, playercolor); return; } if (cls.protocol == PROTOCOL_QUAKEWORLD) return; if (host_client->edict && PRVM_serverfunction(SV_ChangeTeam)) { Con_DPrint("Calling SV_ChangeTeam\n"); prog->globals.fp[OFS_PARM0] = playercolor; PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(SV_ChangeTeam), "QC function SV_ChangeTeam is missing"); } else { if (host_client->edict) { PRVM_serveredictfloat(host_client->edict, clientcolors) = playercolor; PRVM_serveredictfloat(host_client->edict, team) = bottom + 1; } host_client->colors = playercolor; if (host_client->old_colors != host_client->colors) { host_client->old_colors = host_client->colors; // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); MSG_WriteByte (&sv.reliable_datagram, host_client - svs.clients); MSG_WriteByte (&sv.reliable_datagram, host_client->colors); } } } static void Host_Color_f(void) { int top, bottom; if (Cmd_Argc() == 1) { if (cmd_source == src_command) { Con_Printf("\"color\" is \"%i %i\"\n", cl_color.integer >> 4, cl_color.integer & 15); Con_Print("color <0-15> [0-15]\n"); } return; } if (Cmd_Argc() == 2) top = bottom = atoi(Cmd_Argv(1)); else { top = atoi(Cmd_Argv(1)); bottom = atoi(Cmd_Argv(2)); } Host_Color(top, bottom); } static void Host_TopColor_f(void) { if (Cmd_Argc() == 1) { if (cmd_source == src_command) { Con_Printf("\"topcolor\" is \"%i\"\n", (cl_color.integer >> 4) & 15); Con_Print("topcolor <0-15>\n"); } return; } Host_Color(atoi(Cmd_Argv(1)), -1); } static void Host_BottomColor_f(void) { if (Cmd_Argc() == 1) { if (cmd_source == src_command) { Con_Printf("\"bottomcolor\" is \"%i\"\n", cl_color.integer & 15); Con_Print("bottomcolor <0-15>\n"); } return; } Host_Color(-1, atoi(Cmd_Argv(1))); } cvar_t cl_rate = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate", "20000", "internal storage cvar for current rate (changed by rate command)"}; cvar_t cl_rate_burstsize = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_rate_burstsize", "1024", "internal storage cvar for current rate control burst size (changed by rate_burstsize command)"}; static void Host_Rate_f(void) { int rate; if (Cmd_Argc() != 2) { if (cmd_source == src_command) { Con_Printf("\"rate\" is \"%i\"\n", cl_rate.integer); Con_Print("rate \n"); } return; } rate = atoi(Cmd_Argv(1)); if (cmd_source == src_command) { Cvar_SetValue ("_cl_rate", max(NET_MINRATE, rate)); return; } host_client->rate = rate; } static void Host_Rate_BurstSize_f(void) { int rate_burstsize; if (Cmd_Argc() != 2) { Con_Printf("\"rate_burstsize\" is \"%i\"\n", cl_rate_burstsize.integer); Con_Print("rate_burstsize \n"); return; } rate_burstsize = atoi(Cmd_Argv(1)); if (cmd_source == src_command) { Cvar_SetValue ("_cl_rate_burstsize", rate_burstsize); return; } host_client->rate_burstsize = rate_burstsize; } /* ================== Host_Kill_f ================== */ static void Host_Kill_f (void) { prvm_prog_t *prog = SVVM_prog; if (PRVM_serveredictfloat(host_client->edict, health) <= 0) { SV_ClientPrint("Can't suicide -- already dead!\n"); return; } PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(ClientKill), "QC function ClientKill is missing"); } /* ================== Host_Pause_f ================== */ static void Host_Pause_f (void) { void (*print) (const char *fmt, ...); if (cmd_source == src_command) { // if running a client, try to send over network so the pause is handled by the server if (cls.state == ca_connected) { Cmd_ForwardToServer (); return; } print = Con_Printf; } else print = SV_ClientPrintf; if (!pausable.integer) { if (cmd_source == src_client) { if(cls.state == ca_dedicated || host_client != &svs.clients[0]) // non-admin { print("Pause not allowed.\n"); return; } } } sv.paused ^= 1; if (cmd_source != src_command) SV_BroadcastPrintf("%s %spaused the game\n", host_client->name, sv.paused ? "" : "un"); else if(*(sv_adminnick.string)) SV_BroadcastPrintf("%s %spaused the game\n", sv_adminnick.string, sv.paused ? "" : "un"); else SV_BroadcastPrintf("%s %spaused the game\n", hostname.string, sv.paused ? "" : "un"); // send notification to all clients MSG_WriteByte(&sv.reliable_datagram, svc_setpause); MSG_WriteByte(&sv.reliable_datagram, sv.paused); } /* ====================== Host_PModel_f LordHavoc: only supported for Nehahra, I personally think this is dumb, but Mindcrime won't listen. LordHavoc: correction, Mindcrime will be removing pmodel in the future, but it's still stuck here for compatibility. ====================== */ cvar_t cl_pmodel = {CVAR_SAVE | CVAR_NQUSERINFOHACK, "_cl_pmodel", "0", "internal storage cvar for current player model number in nehahra (changed by pmodel command)"}; static void Host_PModel_f (void) { prvm_prog_t *prog = SVVM_prog; int i; if (Cmd_Argc () == 1) { if (cmd_source == src_command) { Con_Printf("\"pmodel\" is \"%s\"\n", cl_pmodel.string); } return; } i = atoi(Cmd_Argv(1)); if (cmd_source == src_command) { if (cl_pmodel.integer == i) return; Cvar_SetValue ("_cl_pmodel", i); if (cls.state == ca_connected) Cmd_ForwardToServer (); return; } PRVM_serveredictfloat(host_client->edict, pmodel) = i; } //=========================================================================== /* ================== Host_PreSpawn_f ================== */ static void Host_PreSpawn_f (void) { if (host_client->prespawned) { Con_Print("prespawn not valid -- already prespawned\n"); return; } host_client->prespawned = true; if (host_client->netconnection) { SZ_Write (&host_client->netconnection->message, sv.signon.data, sv.signon.cursize); MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); MSG_WriteByte (&host_client->netconnection->message, 2); host_client->sendsignon = 0; // enable unlimited sends again } // reset the name change timer because the client will send name soon host_client->nametime = 0; } /* ================== Host_Spawn_f ================== */ static void Host_Spawn_f (void) { prvm_prog_t *prog = SVVM_prog; int i; client_t *client; int stats[MAX_CL_STATS]; if (!host_client->prespawned) { Con_Print("Spawn not valid -- not yet prespawned\n"); return; } if (host_client->spawned) { Con_Print("Spawn not valid -- already spawned\n"); return; } host_client->spawned = true; // reset name change timer again because they might want to change name // again in the first 5 seconds after connecting host_client->nametime = 0; // LordHavoc: moved this above the QC calls at FrikaC's request // LordHavoc: commented this out //if (host_client->netconnection) // SZ_Clear (&host_client->netconnection->message); // run the entrance script if (sv.loadgame) { // loaded games are fully initialized already if (PRVM_serverfunction(RestoreGame)) { Con_DPrint("Calling RestoreGame\n"); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(RestoreGame), "QC function RestoreGame is missing"); } } else { //Con_Printf("Host_Spawn_f: host_client->edict->netname = %s, host_client->edict->netname = %s, host_client->name = %s\n", PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), PRVM_GetString(PRVM_serveredictstring(host_client->edict, netname)), host_client->name); // copy spawn parms out of the client_t for (i=0 ; i< NUM_SPAWN_PARMS ; i++) (&PRVM_serverglobalfloat(parm1))[i] = host_client->spawn_parms[i]; // call the spawn function host_client->clientconnectcalled = true; PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); if (cls.state == ca_dedicated) Con_Printf("%s connected\n", host_client->name); PRVM_serverglobalfloat(time) = sv.time; prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); } if (!host_client->netconnection) return; // send time of update MSG_WriteByte (&host_client->netconnection->message, svc_time); MSG_WriteFloat (&host_client->netconnection->message, sv.time); // send all current names, colors, and frag counts for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (!client->active) continue; MSG_WriteByte (&host_client->netconnection->message, svc_updatename); MSG_WriteByte (&host_client->netconnection->message, i); MSG_WriteString (&host_client->netconnection->message, client->name); MSG_WriteByte (&host_client->netconnection->message, svc_updatefrags); MSG_WriteByte (&host_client->netconnection->message, i); MSG_WriteShort (&host_client->netconnection->message, client->frags); MSG_WriteByte (&host_client->netconnection->message, svc_updatecolors); MSG_WriteByte (&host_client->netconnection->message, i); MSG_WriteByte (&host_client->netconnection->message, client->colors); } // send all current light styles for (i=0 ; inetconnection->message, svc_lightstyle); MSG_WriteByte (&host_client->netconnection->message, (char)i); MSG_WriteString (&host_client->netconnection->message, sv.lightstyles[i]); } } // send some stats MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALSECRETS); MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_secrets)); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_TOTALMONSTERS); MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(total_monsters)); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_SECRETS); MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(found_secrets)); MSG_WriteByte (&host_client->netconnection->message, svc_updatestat); MSG_WriteByte (&host_client->netconnection->message, STAT_MONSTERS); MSG_WriteLong (&host_client->netconnection->message, (int)PRVM_serverglobalfloat(killed_monsters)); // send a fixangle // Never send a roll angle, because savegames can catch the server // in a state where it is expecting the client to correct the angle // and it won't happen if the game was just loaded, so you wind up // with a permanent head tilt if (sv.loadgame) { MSG_WriteByte (&host_client->netconnection->message, svc_setangle); MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[0], sv.protocol); MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, v_angle)[1], sv.protocol); MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); } else { MSG_WriteByte (&host_client->netconnection->message, svc_setangle); MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[0], sv.protocol); MSG_WriteAngle (&host_client->netconnection->message, PRVM_serveredictvector(host_client->edict, angles)[1], sv.protocol); MSG_WriteAngle (&host_client->netconnection->message, 0, sv.protocol); } SV_WriteClientdataToMessage (host_client, host_client->edict, &host_client->netconnection->message, stats); MSG_WriteByte (&host_client->netconnection->message, svc_signonnum); MSG_WriteByte (&host_client->netconnection->message, 3); } /* ================== Host_Begin_f ================== */ static void Host_Begin_f (void) { if (!host_client->spawned) { Con_Print("Begin not valid -- not yet spawned\n"); return; } if (host_client->begun) { Con_Print("Begin not valid -- already begun\n"); return; } host_client->begun = true; // LordHavoc: note: this code also exists in SV_DropClient if (sv.loadgame) { int i; for (i = 0;i < svs.maxclients;i++) if (svs.clients[i].active && !svs.clients[i].spawned) break; if (i == svs.maxclients) { Con_Printf("Loaded game, everyone rejoined - unpausing\n"); sv.paused = sv.loadgame = false; // we're basically done with loading now } } } //=========================================================================== /* ================== Host_Kick_f Kicks a user off of the server ================== */ static void Host_Kick_f (void) { const char *who; const char *message = NULL; client_t *save; int i; qboolean byNumber = false; if (!sv.active) return; save = host_client; if (Cmd_Argc() > 2 && strcmp(Cmd_Argv(1), "#") == 0) { i = (int)(atof(Cmd_Argv(2)) - 1); if (i < 0 || i >= svs.maxclients || !(host_client = svs.clients + i)->active) return; byNumber = true; } else { for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { if (!host_client->active) continue; if (strcasecmp(host_client->name, Cmd_Argv(1)) == 0) break; } } if (i < svs.maxclients) { if (cmd_source == src_command) { if (cls.state == ca_dedicated) who = "Console"; else who = cl_name.string; } else who = save->name; // can't kick yourself! if (host_client == save) return; if (Cmd_Argc() > 2) { message = Cmd_Args(); COM_ParseToken_Simple(&message, false, false, true); if (byNumber) { message++; // skip the # while (*message == ' ') // skip white space message++; message += strlen(Cmd_Argv(2)); // skip the number } while (*message && *message == ' ') message++; } if (message) SV_ClientPrintf("Kicked by %s: %s\n", who, message); else SV_ClientPrintf("Kicked by %s\n", who); SV_DropClient (false); // kicked } host_client = save; } /* =============================================================================== DEBUGGING TOOLS =============================================================================== */ /* ================== Host_Give_f ================== */ static void Host_Give_f (void) { prvm_prog_t *prog = SVVM_prog; const char *t; int v; if (!allowcheats) { SV_ClientPrint("No cheats allowed, use sv_cheats 1 and restart level to enable.\n"); return; } t = Cmd_Argv(1); v = atoi (Cmd_Argv(2)); switch (t[0]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // MED 01/04/97 added hipnotic give stuff if (gamemode == GAME_HIPNOTIC || gamemode == GAME_QUOTH) { if (t[0] == '6') { if (t[1] == 'a') PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_PROXIMITY_GUN; else PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | IT_GRENADE_LAUNCHER; } else if (t[0] == '9') PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_LASER_CANNON; else if (t[0] == '0') PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | HIT_MJOLNIR; else if (t[0] >= '2') PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); } else { if (t[0] >= '2') PRVM_serveredictfloat(host_client->edict, items) = (int)PRVM_serveredictfloat(host_client->edict, items) | (IT_SHOTGUN << (t[0] - '2')); } break; case 's': if (gamemode == GAME_ROGUE) PRVM_serveredictfloat(host_client->edict, ammo_shells1) = v; PRVM_serveredictfloat(host_client->edict, ammo_shells) = v; break; case 'n': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_nails1) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; } else { PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; } break; case 'l': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_lava_nails) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_nails) = v; } break; case 'r': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_rockets1) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; } else { PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; } break; case 'm': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_multi_rockets) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_rockets) = v; } break; case 'h': PRVM_serveredictfloat(host_client->edict, health) = v; break; case 'c': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_cells1) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) <= IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; } else { PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; } break; case 'p': if (gamemode == GAME_ROGUE) { PRVM_serveredictfloat(host_client->edict, ammo_plasma) = v; if (PRVM_serveredictfloat(host_client->edict, weapon) > IT_LIGHTNING) PRVM_serveredictfloat(host_client->edict, ammo_cells) = v; } break; } } static prvm_edict_t *FindViewthing(prvm_prog_t *prog) { int i; prvm_edict_t *e; for (i=0 ; inum_edicts ; i++) { e = PRVM_EDICT_NUM(i); if (!strcmp (PRVM_GetString(prog, PRVM_serveredictstring(e, classname)), "viewthing")) return e; } Con_Print("No viewthing on map\n"); return NULL; } /* ================== Host_Viewmodel_f ================== */ static void Host_Viewmodel_f (void) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *e; dp_model_t *m; if (!sv.active) return; e = FindViewthing(prog); if (e) { m = Mod_ForName (Cmd_Argv(1), false, true, NULL); if (m && m->loaded && m->Draw) { PRVM_serveredictfloat(e, frame) = 0; cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)] = m; } else Con_Printf("viewmodel: can't load %s\n", Cmd_Argv(1)); } } /* ================== Host_Viewframe_f ================== */ static void Host_Viewframe_f (void) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *e; int f; dp_model_t *m; if (!sv.active) return; e = FindViewthing(prog); if (e) { m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; f = atoi(Cmd_Argv(1)); if (f >= m->numframes) f = m->numframes-1; PRVM_serveredictfloat(e, frame) = f; } } static void PrintFrameName (dp_model_t *m, int frame) { if (m->animscenes) Con_Printf("frame %i: %s\n", frame, m->animscenes[frame].name); else Con_Printf("frame %i\n", frame); } /* ================== Host_Viewnext_f ================== */ static void Host_Viewnext_f (void) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *e; dp_model_t *m; if (!sv.active) return; e = FindViewthing(prog); if (e) { m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) + 1; if (PRVM_serveredictfloat(e, frame) >= m->numframes) PRVM_serveredictfloat(e, frame) = m->numframes - 1; PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); } } /* ================== Host_Viewprev_f ================== */ static void Host_Viewprev_f (void) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *e; dp_model_t *m; if (!sv.active) return; e = FindViewthing(prog); if (e) { m = cl.model_precache[(int)PRVM_serveredictfloat(e, modelindex)]; PRVM_serveredictfloat(e, frame) = PRVM_serveredictfloat(e, frame) - 1; if (PRVM_serveredictfloat(e, frame) < 0) PRVM_serveredictfloat(e, frame) = 0; PrintFrameName (m, (int)PRVM_serveredictfloat(e, frame)); } } /* =============================================================================== DEMO LOOP CONTROL =============================================================================== */ /* ================== Host_Startdemos_f ================== */ static void Host_Startdemos_f (void) { int i, c; if (cls.state == ca_dedicated || COM_CheckParm("-listen") || COM_CheckParm("-benchmark") || COM_CheckParm("-demo") || COM_CheckParm("-capturedemo")) return; c = Cmd_Argc() - 1; if (c > MAX_DEMOS) { Con_Printf("Max %i demos in demoloop\n", MAX_DEMOS); c = MAX_DEMOS; } Con_DPrintf("%i demo(s) in loop\n", c); for (i=1 ; iflags & CVAR_PRIVATE)) Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s", cvarname)); else Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "sentcvar %s \"%s\"", c->name, c->string)); return; } if(!sv.active)// || !PRVM_serverfunction(SV_ParseClientCommand)) return; old = host_client; if (cls.state != ca_dedicated) i = 1; else i = 0; for(;i 0) { Con_Printf ("You must set rcon_password before issuing an pqrcon command, and rcon_secure must be 0.\n"); return; } e = strchr(rcon_password.string, ' '); n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) cls.rcon_address = cls.netcon->peeraddress; else { if (!rcon_address.string[0]) { Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); return; } LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer); } mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address); if (mysocket) { sizebuf_t buf; unsigned char bufdata[64]; buf.data = bufdata; SZ_Clear(&buf); MSG_WriteLong(&buf, 0); MSG_WriteByte(&buf, CCREQ_RCON); SZ_Write(&buf, (const unsigned char*)rcon_password.string, n); MSG_WriteByte(&buf, 0); // terminate the (possibly partial) string MSG_WriteString(&buf, Cmd_Args()); StoreBigLong(buf.data, NETFLAG_CTL | (buf.cursize & NETFLAG_LENGTH_MASK)); NetConn_Write(mysocket, buf.data, buf.cursize, &cls.rcon_address); SZ_Clear(&buf); } } //============================================================================= // QuakeWorld commands /* ===================== Host_Rcon_f Send the rest of the command line over as an unconnected command. ===================== */ static void Host_Rcon_f (void) // credit: taken from QuakeWorld { int i, n; const char *e; lhnetsocket_t *mysocket; if (Cmd_Argc() == 1) { Con_Printf("%s: Usage: %s command\n", Cmd_Argv(0), Cmd_Argv(0)); return; } if (!rcon_password.string || !rcon_password.string[0]) { Con_Printf ("You must set rcon_password before issuing an rcon command.\n"); return; } e = strchr(rcon_password.string, ' '); n = e ? e-rcon_password.string : (int)strlen(rcon_password.string); if (cls.netcon) cls.rcon_address = cls.netcon->peeraddress; else { if (!rcon_address.string[0]) { Con_Printf ("You must either be connected, or set the rcon_address cvar to issue rcon commands\n"); return; } LHNETADDRESS_FromString(&cls.rcon_address, rcon_address.string, sv_netport.integer); } mysocket = NetConn_ChooseClientSocketForAddress(&cls.rcon_address); if (mysocket && Cmd_Args()[0]) { // simply put together the rcon packet and send it if(Cmd_Argv(0)[0] == 's' || rcon_secure.integer > 1) { if(cls.rcon_commands[cls.rcon_ringpos][0]) { char s[128]; LHNETADDRESS_ToString(&cls.rcon_addresses[cls.rcon_ringpos], s, sizeof(s), true); Con_Printf("rcon to %s (for command %s) failed: too many buffered commands (possibly increase MAX_RCONS)\n", s, cls.rcon_commands[cls.rcon_ringpos]); cls.rcon_commands[cls.rcon_ringpos][0] = 0; --cls.rcon_trying; } for (i = 0;i < MAX_RCONS;i++) if(cls.rcon_commands[i][0]) if (!LHNETADDRESS_Compare(&cls.rcon_address, &cls.rcon_addresses[i])) break; ++cls.rcon_trying; if(i >= MAX_RCONS) NetConn_WriteString(mysocket, "\377\377\377\377getchallenge", &cls.rcon_address); // otherwise we'll request the challenge later strlcpy(cls.rcon_commands[cls.rcon_ringpos], Cmd_Args(), sizeof(cls.rcon_commands[cls.rcon_ringpos])); cls.rcon_addresses[cls.rcon_ringpos] = cls.rcon_address; cls.rcon_timeout[cls.rcon_ringpos] = realtime + rcon_secure_challengetimeout.value; cls.rcon_ringpos = (cls.rcon_ringpos + 1) % MAX_RCONS; } else if(rcon_secure.integer > 0) { char buf[1500]; char argbuf[1500]; dpsnprintf(argbuf, sizeof(argbuf), "%ld.%06d %s", (long) time(NULL), (int) (rand() % 1000000), Cmd_Args()); memcpy(buf, "\377\377\377\377srcon HMAC-MD4 TIME ", 24); if(HMAC_MDFOUR_16BYTES((unsigned char *) (buf + 24), (unsigned char *) argbuf, (int)strlen(argbuf), (unsigned char *) rcon_password.string, n)) { buf[40] = ' '; strlcpy(buf + 41, argbuf, sizeof(buf) - 41); NetConn_Write(mysocket, buf, 41 + (int)strlen(buf + 41), &cls.rcon_address); } } else { char buf[1500]; memcpy(buf, "\377\377\377\377", 4); dpsnprintf(buf+4, sizeof(buf)-4, "rcon %.*s %s", n, rcon_password.string, Cmd_Args()); NetConn_WriteString(mysocket, buf, &cls.rcon_address); } } } /* ==================== Host_User_f user Dump userdata / masterdata for a user ==================== */ static void Host_User_f (void) // credit: taken from QuakeWorld { int uid; int i; if (Cmd_Argc() != 2) { Con_Printf ("Usage: user \n"); return; } uid = atoi(Cmd_Argv(1)); for (i = 0;i < cl.maxclients;i++) { if (!cl.scores[i].name[0]) continue; if (cl.scores[i].qw_userid == uid || !strcasecmp(cl.scores[i].name, Cmd_Argv(1))) { InfoString_Print(cl.scores[i].qw_userinfo); return; } } Con_Printf ("User not in server.\n"); } /* ==================== Host_Users_f Dump userids for all current players ==================== */ static void Host_Users_f (void) // credit: taken from QuakeWorld { int i; int c; c = 0; Con_Printf ("userid frags name\n"); Con_Printf ("------ ----- ----\n"); for (i = 0;i < cl.maxclients;i++) { if (cl.scores[i].name[0]) { Con_Printf ("%6i %4i %s\n", cl.scores[i].qw_userid, cl.scores[i].frags, cl.scores[i].name); c++; } } Con_Printf ("%i total users\n", c); } /* ================== Host_FullServerinfo_f Sent by server when serverinfo changes ================== */ // TODO: shouldn't this be a cvar instead? static void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld { char temp[512]; if (Cmd_Argc() != 2) { Con_Printf ("usage: fullserverinfo \n"); return; } strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo)); InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); cl.qw_teamplay = atoi(temp); } /* ================== Host_FullInfo_f Allow clients to change userinfo ================== Casey was here :) */ static void Host_FullInfo_f (void) // credit: taken from QuakeWorld { char key[512]; char value[512]; const char *s; if (Cmd_Argc() != 2) { Con_Printf ("fullinfo \n"); return; } s = Cmd_Argv(1); if (*s == '\\') s++; while (*s) { size_t len = strcspn(s, "\\"); if (len >= sizeof(key)) { len = sizeof(key) - 1; } strlcpy(key, s, len + 1); s += len; if (!*s) { Con_Printf ("MISSING VALUE\n"); return; } ++s; // Skip over backslash. len = strcspn(s, "\\"); if (len >= sizeof(value)) { len = sizeof(value) - 1; } strlcpy(value, s, len + 1); CL_SetInfo(key, value, false, false, false, false); s += len; if (!*s) { break; } ++s; // Skip over backslash. } } /* ================== CL_SetInfo_f Allow clients to change userinfo ================== */ static void Host_SetInfo_f (void) // credit: taken from QuakeWorld { if (Cmd_Argc() == 1) { InfoString_Print(cls.userinfo); return; } if (Cmd_Argc() != 3) { Con_Printf ("usage: setinfo [ ]\n"); return; } CL_SetInfo(Cmd_Argv(1), Cmd_Argv(2), true, false, false, false); } /* ==================== Host_Packet_f packet Contents allows \n escape character ==================== */ static void Host_Packet_f (void) // credit: taken from QuakeWorld { char send[2048]; int i, l; const char *in; char *out; lhnetaddress_t address; lhnetsocket_t *mysocket; if (Cmd_Argc() != 3) { Con_Printf ("packet \n"); return; } if (!LHNETADDRESS_FromString (&address, Cmd_Argv(1), sv_netport.integer)) { Con_Printf ("Bad address\n"); return; } in = Cmd_Argv(2); out = send+4; send[0] = send[1] = send[2] = send[3] = -1; l = (int)strlen (in); for (i=0 ; i= send + sizeof(send) - 1) break; if (in[i] == '\\' && in[i+1] == 'n') { *out++ = '\n'; i++; } else if (in[i] == '\\' && in[i+1] == '0') { *out++ = '\0'; i++; } else if (in[i] == '\\' && in[i+1] == 't') { *out++ = '\t'; i++; } else if (in[i] == '\\' && in[i+1] == 'r') { *out++ = '\r'; i++; } else if (in[i] == '\\' && in[i+1] == '"') { *out++ = '\"'; i++; } else *out++ = in[i]; } mysocket = NetConn_ChooseClientSocketForAddress(&address); if (!mysocket) mysocket = NetConn_ChooseServerSocketForAddress(&address); if (mysocket) NetConn_Write(mysocket, send, out - send, &address); } /* ==================== Host_Pings_f Send back ping and packet loss update for all current players to this player ==================== */ void Host_Pings_f (void) { int i, j, ping, packetloss, movementloss; char temp[128]; if (!host_client->netconnection) return; if (sv.protocol != PROTOCOL_QUAKEWORLD) { MSG_WriteByte(&host_client->netconnection->message, svc_stufftext); MSG_WriteUnterminatedString(&host_client->netconnection->message, "pingplreport"); } for (i = 0;i < svs.maxclients;i++) { packetloss = 0; movementloss = 0; if (svs.clients[i].netconnection) { for (j = 0;j < NETGRAPH_PACKETS;j++) if (svs.clients[i].netconnection->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) packetloss++; for (j = 0;j < NETGRAPH_PACKETS;j++) if (svs.clients[i].movement_count[j] < 0) movementloss++; } packetloss = (packetloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; movementloss = (movementloss * 100 + NETGRAPH_PACKETS - 1) / NETGRAPH_PACKETS; ping = (int)floor(svs.clients[i].ping*1000+0.5); ping = bound(0, ping, 9999); if (sv.protocol == PROTOCOL_QUAKEWORLD) { // send qw_svc_updateping and qw_svc_updatepl messages MSG_WriteByte(&host_client->netconnection->message, qw_svc_updateping); MSG_WriteShort(&host_client->netconnection->message, ping); MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatepl); MSG_WriteByte(&host_client->netconnection->message, packetloss); } else { // write the string into the packet as multiple unterminated strings to avoid needing a local buffer if(movementloss) dpsnprintf(temp, sizeof(temp), " %d %d,%d", ping, packetloss, movementloss); else dpsnprintf(temp, sizeof(temp), " %d %d", ping, packetloss); MSG_WriteUnterminatedString(&host_client->netconnection->message, temp); } } if (sv.protocol != PROTOCOL_QUAKEWORLD) MSG_WriteString(&host_client->netconnection->message, "\n"); } static void Host_PingPLReport_f(void) { char *errbyte; int i; int l = Cmd_Argc(); if (l > cl.maxclients) l = cl.maxclients; for (i = 0;i < l;i++) { cl.scores[i].qw_ping = atoi(Cmd_Argv(1+i*2)); cl.scores[i].qw_packetloss = strtol(Cmd_Argv(1+i*2+1), &errbyte, 0); if(errbyte && *errbyte == ',') cl.scores[i].qw_movementloss = atoi(errbyte + 1); else cl.scores[i].qw_movementloss = 0; } } //============================================================================= /* ================== Host_InitCommands ================== */ void Host_InitCommands (void) { dpsnprintf(cls.userinfo, sizeof(cls.userinfo), "\\name\\player\\team\\none\\topcolor\\0\\bottomcolor\\0\\rate\\10000\\msg\\1\\noaim\\1\\*ver\\dp"); Cmd_AddCommand_WithClientCommand ("status", Host_Status_f, Host_Status_f, "print server status information"); Cmd_AddCommand ("quit", Host_Quit_f, "quit the game"); Cmd_AddCommand_WithClientCommand ("god", NULL, Host_God_f, "god mode (invulnerability)"); Cmd_AddCommand_WithClientCommand ("notarget", NULL, Host_Notarget_f, "notarget mode (monsters do not see you)"); Cmd_AddCommand_WithClientCommand ("fly", NULL, Host_Fly_f, "fly mode (flight)"); Cmd_AddCommand_WithClientCommand ("noclip", NULL, Host_Noclip_f, "noclip mode (flight without collisions, move through walls)"); Cmd_AddCommand_WithClientCommand ("give", NULL, Host_Give_f, "alter inventory"); Cmd_AddCommand ("map", Host_Map_f, "kick everyone off the server and start a new level"); Cmd_AddCommand ("restart", Host_Restart_f, "restart current level"); Cmd_AddCommand ("changelevel", Host_Changelevel_f, "change to another level, bringing along all connected clients"); Cmd_AddCommand ("connect", Host_Connect_f, "connect to a server by IP address or hostname"); Cmd_AddCommand ("reconnect", Host_Reconnect_f, "reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server)"); Cmd_AddCommand ("version", Host_Version_f, "print engine version"); Cmd_AddCommand_WithClientCommand ("say", Host_Say_f, Host_Say_f, "send a chat message to everyone on the server"); Cmd_AddCommand_WithClientCommand ("say_team", Host_Say_Team_f, Host_Say_Team_f, "send a chat message to your team on the server"); Cmd_AddCommand_WithClientCommand ("tell", Host_Tell_f, Host_Tell_f, "send a chat message to only one person on the server"); Cmd_AddCommand_WithClientCommand ("kill", NULL, Host_Kill_f, "die instantly"); Cmd_AddCommand_WithClientCommand ("pause", Host_Pause_f, Host_Pause_f, "pause the game (if the server allows pausing)"); Cmd_AddCommand ("kick", Host_Kick_f, "kick a player off the server by number or name"); Cmd_AddCommand_WithClientCommand ("ping", Host_Ping_f, Host_Ping_f, "print ping times of all players on the server"); Cmd_AddCommand ("load", Host_Loadgame_f, "load a saved game file"); Cmd_AddCommand ("save", Host_Savegame_f, "save the game to a file"); Cmd_AddCommand ("startdemos", Host_Startdemos_f, "start playing back the selected demos sequentially (used at end of startup script)"); Cmd_AddCommand ("demos", Host_Demos_f, "restart looping demos defined by the last startdemos command"); Cmd_AddCommand ("stopdemo", Host_Stopdemo_f, "stop playing or recording demo (like stop command) and return to looping demos"); Cmd_AddCommand ("viewmodel", Host_Viewmodel_f, "change model of viewthing entity in current level"); Cmd_AddCommand ("viewframe", Host_Viewframe_f, "change animation frame of viewthing entity in current level"); Cmd_AddCommand ("viewnext", Host_Viewnext_f, "change to next animation frame of viewthing entity in current level"); Cmd_AddCommand ("viewprev", Host_Viewprev_f, "change to previous animation frame of viewthing entity in current level"); Cvar_RegisterVariable (&cl_name); Cmd_AddCommand_WithClientCommand ("name", Host_Name_f, Host_Name_f, "change your player name"); Cvar_RegisterVariable (&cl_color); Cmd_AddCommand_WithClientCommand ("color", Host_Color_f, Host_Color_f, "change your player shirt and pants colors"); Cvar_RegisterVariable (&cl_rate); Cmd_AddCommand_WithClientCommand ("rate", Host_Rate_f, Host_Rate_f, "change your network connection speed"); Cvar_RegisterVariable (&cl_rate_burstsize); Cmd_AddCommand_WithClientCommand ("rate_burstsize", Host_Rate_BurstSize_f, Host_Rate_BurstSize_f, "change your network connection speed"); Cvar_RegisterVariable (&cl_pmodel); Cmd_AddCommand_WithClientCommand ("pmodel", Host_PModel_f, Host_PModel_f, "(Nehahra-only) change your player model choice"); // BLACK: This isnt game specific anymore (it was GAME_NEXUIZ at first) Cvar_RegisterVariable (&cl_playermodel); Cmd_AddCommand_WithClientCommand ("playermodel", Host_Playermodel_f, Host_Playermodel_f, "change your player model"); Cvar_RegisterVariable (&cl_playerskin); Cmd_AddCommand_WithClientCommand ("playerskin", Host_Playerskin_f, Host_Playerskin_f, "change your player skin number"); Cmd_AddCommand_WithClientCommand ("prespawn", NULL, Host_PreSpawn_f, "signon 1 (client acknowledges that server information has been received)"); Cmd_AddCommand_WithClientCommand ("spawn", NULL, Host_Spawn_f, "signon 2 (client has sent player information, and is asking server to send scoreboard rankings)"); Cmd_AddCommand_WithClientCommand ("begin", NULL, Host_Begin_f, "signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received)"); Cmd_AddCommand ("maxplayers", MaxPlayers_f, "sets limit on how many players (or bots) may be connected to the server at once"); Cmd_AddCommand ("sendcvar", Host_SendCvar_f, "sends the value of a cvar to the server as a sentcvar command, for use by QuakeC"); Cvar_RegisterVariable (&rcon_password); Cvar_RegisterVariable (&rcon_address); Cvar_RegisterVariable (&rcon_secure); Cvar_RegisterVariable (&rcon_secure_challengetimeout); Cmd_AddCommand ("rcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); note: if rcon_secure is set, client and server clocks must be synced e.g. via NTP"); Cmd_AddCommand ("srcon", Host_Rcon_f, "sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's); this always works as if rcon_secure is set; note: client and server clocks must be synced e.g. via NTP"); Cmd_AddCommand ("pqrcon", Host_PQRcon_f, "sends a command to a proquake server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's)"); Cmd_AddCommand ("user", Host_User_f, "prints additional information about a player number or name on the scoreboard"); Cmd_AddCommand ("users", Host_Users_f, "prints additional information about all players on the scoreboard"); Cmd_AddCommand ("fullserverinfo", Host_FullServerinfo_f, "internal use only, sent by server to client to update client's local copy of serverinfo string"); Cmd_AddCommand ("fullinfo", Host_FullInfo_f, "allows client to modify their userinfo"); Cmd_AddCommand ("setinfo", Host_SetInfo_f, "modifies your userinfo"); Cmd_AddCommand ("packet", Host_Packet_f, "send a packet to the specified address:port containing a text string"); Cmd_AddCommand ("topcolor", Host_TopColor_f, "QW command to set top color without changing bottom color"); Cmd_AddCommand ("bottomcolor", Host_BottomColor_f, "QW command to set bottom color without changing top color"); Cmd_AddCommand_WithClientCommand ("pings", NULL, Host_Pings_f, "command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers)"); Cmd_AddCommand ("pingplreport", Host_PingPLReport_f, "command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers)"); Cmd_AddCommand ("fixtrans", Image_FixTransparentPixels_f, "change alpha-zero pixels in an image file to sensible values, and write out a new TGA (warning: SLOW)"); Cvar_RegisterVariable (&r_fixtrans_auto); Cvar_RegisterVariable (&team); Cvar_RegisterVariable (&skin); Cvar_RegisterVariable (&noaim); Cvar_RegisterVariable(&sv_cheats); Cvar_RegisterVariable(&sv_adminnick); Cvar_RegisterVariable(&sv_status_privacy); Cvar_RegisterVariable(&sv_status_show_qcstatus); Cvar_RegisterVariable(&sv_namechangetimer); } void Host_NoOperation_f(void) { } darkplaces/jpeg.c0000664000175000017500000007774513067716220013300 0ustar kalevkalev/* Copyright (C) 2002 Mathieu Olivier This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "quakedef.h" #include "image.h" #include "jpeg.h" #include "image_png.h" cvar_t sv_writepicture_quality = {CVAR_SAVE, "sv_writepicture_quality", "10", "WritePicture quality offset (higher means better quality, but slower)"}; cvar_t r_texture_jpeg_fastpicmip = {CVAR_SAVE, "r_texture_jpeg_fastpicmip", "1", "perform gl_picmip during decompression for JPEG files (faster)"}; // jboolean is unsigned char instead of int on Win32 #ifdef WIN32 typedef unsigned char jboolean; #else typedef int jboolean; #endif #ifdef LINK_TO_LIBJPEG #include #define qjpeg_create_compress jpeg_create_compress #define qjpeg_create_decompress jpeg_create_decompress #define qjpeg_destroy_compress jpeg_destroy_compress #define qjpeg_destroy_decompress jpeg_destroy_decompress #define qjpeg_finish_compress jpeg_finish_compress #define qjpeg_finish_decompress jpeg_finish_decompress #define qjpeg_resync_to_restart jpeg_resync_to_restart #define qjpeg_read_header jpeg_read_header #define qjpeg_read_scanlines jpeg_read_scanlines #define qjpeg_set_defaults jpeg_set_defaults #define qjpeg_set_quality jpeg_set_quality #define qjpeg_start_compress jpeg_start_compress #define qjpeg_start_decompress jpeg_start_decompress #define qjpeg_std_error jpeg_std_error #define qjpeg_write_scanlines jpeg_write_scanlines #define qjpeg_simple_progression jpeg_simple_progression #define jpeg_dll true #else /* ================================================================= Minimal set of definitions from the JPEG lib WARNING: for a matter of simplicity, several pointer types are casted to "void*", and most enumerated values are not included ================================================================= */ typedef void *j_common_ptr; typedef struct jpeg_compress_struct *j_compress_ptr; typedef struct jpeg_decompress_struct *j_decompress_ptr; #define JPEG_LIB_VERSION 62 // Version 6b typedef enum { JCS_UNKNOWN, JCS_GRAYSCALE, JCS_RGB, JCS_YCbCr, JCS_CMYK, JCS_YCCK } J_COLOR_SPACE; typedef enum {JPEG_DUMMY1} J_DCT_METHOD; typedef enum {JPEG_DUMMY2} J_DITHER_MODE; typedef unsigned int JDIMENSION; #define JPOOL_PERMANENT 0 // lasts until master record is destroyed #define JPOOL_IMAGE 1 // lasts until done with image/datastream #define JPEG_EOI 0xD9 // EOI marker code #define JMSG_STR_PARM_MAX 80 #define DCTSIZE2 64 #define NUM_QUANT_TBLS 4 #define NUM_HUFF_TBLS 4 #define NUM_ARITH_TBLS 16 #define MAX_COMPS_IN_SCAN 4 #define C_MAX_BLOCKS_IN_MCU 10 #define D_MAX_BLOCKS_IN_MCU 10 struct jpeg_memory_mgr { void* (*alloc_small) (j_common_ptr cinfo, int pool_id, size_t sizeofobject); void (*_reserve_space_for_alloc_large) (void *dummy, ...); void (*_reserve_space_for_alloc_sarray) (void *dummy, ...); void (*_reserve_space_for_alloc_barray) (void *dummy, ...); void (*_reserve_space_for_request_virt_sarray) (void *dummy, ...); void (*_reserve_space_for_request_virt_barray) (void *dummy, ...); void (*_reserve_space_for_realize_virt_arrays) (void *dummy, ...); void (*_reserve_space_for_access_virt_sarray) (void *dummy, ...); void (*_reserve_space_for_access_virt_barray) (void *dummy, ...); void (*_reserve_space_for_free_pool) (void *dummy, ...); void (*_reserve_space_for_self_destruct) (void *dummy, ...); long max_memory_to_use; long max_alloc_chunk; }; struct jpeg_error_mgr { void (*error_exit) (j_common_ptr cinfo); void (*emit_message) (j_common_ptr cinfo, int msg_level); void (*output_message) (j_common_ptr cinfo); void (*format_message) (j_common_ptr cinfo, char * buffer); void (*reset_error_mgr) (j_common_ptr cinfo); int msg_code; union { int i[8]; char s[JMSG_STR_PARM_MAX]; } msg_parm; int trace_level; long num_warnings; const char * const * jpeg_message_table; int last_jpeg_message; const char * const * addon_message_table; int first_addon_message; int last_addon_message; }; struct jpeg_source_mgr { const unsigned char *next_input_byte; size_t bytes_in_buffer; void (*init_source) (j_decompress_ptr cinfo); jboolean (*fill_input_buffer) (j_decompress_ptr cinfo); void (*skip_input_data) (j_decompress_ptr cinfo, long num_bytes); jboolean (*resync_to_restart) (j_decompress_ptr cinfo, int desired); void (*term_source) (j_decompress_ptr cinfo); }; typedef struct { /* These values are fixed over the whole image. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOF marker. */ int component_id; /* identifier for this component (0..255) */ int component_index; /* its index in SOF or cinfo->comp_info[] */ int h_samp_factor; /* horizontal sampling factor (1..4) */ int v_samp_factor; /* vertical sampling factor (1..4) */ int quant_tbl_no; /* quantization table selector (0..3) */ /* These values may vary between scans. */ /* For compression, they must be supplied by parameter setup; */ /* for decompression, they are read from the SOS marker. */ /* The decompressor output side may not use these variables. */ int dc_tbl_no; /* DC entropy table selector (0..3) */ int ac_tbl_no; /* AC entropy table selector (0..3) */ /* Remaining fields should be treated as private by applications. */ /* These values are computed during compression or decompression startup: */ /* Component's size in DCT blocks. * Any dummy blocks added to complete an MCU are not counted; therefore * these values do not depend on whether a scan is interleaved or not. */ JDIMENSION width_in_blocks; JDIMENSION height_in_blocks; /* Size of a DCT block in samples. Always DCTSIZE for compression. * For decompression this is the size of the output from one DCT block, * reflecting any scaling we choose to apply during the IDCT step. * Values of 1,2,4,8 are likely to be supported. Note that different * components may receive different IDCT scalings. */ int DCT_scaled_size; /* The downsampled dimensions are the component's actual, unpadded number * of samples at the main buffer (preprocessing/compression interface), thus * downsampled_width = ceil(image_width * Hi/Hmax) * and similarly for height. For decompression, IDCT scaling is included, so * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) */ JDIMENSION downsampled_width; /* actual width in samples */ JDIMENSION downsampled_height; /* actual height in samples */ /* This flag is used only for decompression. In cases where some of the * components will be ignored (eg grayscale output from YCbCr image), * we can skip most computations for the unused components. */ jboolean component_needed; /* do we need the value of this component? */ /* These values are computed before starting a scan of the component. */ /* The decompressor output side may not use these variables. */ int MCU_width; /* number of blocks per MCU, horizontally */ int MCU_height; /* number of blocks per MCU, vertically */ int MCU_blocks; /* MCU_width * MCU_height */ int MCU_sample_width; /* MCU width in samples, MCU_width*DCT_scaled_size */ int last_col_width; /* # of non-dummy blocks across in last MCU */ int last_row_height; /* # of non-dummy blocks down in last MCU */ /* Saved quantization table for component; NULL if none yet saved. * See jdinput.c comments about the need for this information. * This field is currently used only for decompression. */ void *quant_table; /* Private per-component storage for DCT or IDCT subsystem. */ void * dct_table; } jpeg_component_info; struct jpeg_decompress_struct { struct jpeg_error_mgr *err; // USED struct jpeg_memory_mgr *mem; // USED void *progress; void *client_data; jboolean is_decompressor; int global_state; struct jpeg_source_mgr *src; // USED JDIMENSION image_width; // USED JDIMENSION image_height; // USED int num_components; J_COLOR_SPACE jpeg_color_space; J_COLOR_SPACE out_color_space; unsigned int scale_num, scale_denom; double output_gamma; jboolean buffered_image; jboolean raw_data_out; J_DCT_METHOD dct_method; jboolean do_fancy_upsampling; jboolean do_block_smoothing; jboolean quantize_colors; J_DITHER_MODE dither_mode; jboolean two_pass_quantize; int desired_number_of_colors; jboolean enable_1pass_quant; jboolean enable_external_quant; jboolean enable_2pass_quant; JDIMENSION output_width; JDIMENSION output_height; // USED int out_color_components; int output_components; // USED int rec_outbuf_height; int actual_number_of_colors; void *colormap; JDIMENSION output_scanline; // USED int input_scan_number; JDIMENSION input_iMCU_row; int output_scan_number; JDIMENSION output_iMCU_row; int (*coef_bits)[DCTSIZE2]; void *quant_tbl_ptrs[NUM_QUANT_TBLS]; void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; int data_precision; jpeg_component_info *comp_info; jboolean progressive_mode; jboolean arith_code; unsigned char arith_dc_L[NUM_ARITH_TBLS]; unsigned char arith_dc_U[NUM_ARITH_TBLS]; unsigned char arith_ac_K[NUM_ARITH_TBLS]; unsigned int restart_interval; jboolean saw_JFIF_marker; unsigned char JFIF_major_version; unsigned char JFIF_minor_version; unsigned char density_unit; unsigned short X_density; unsigned short Y_density; jboolean saw_Adobe_marker; unsigned char Adobe_transform; jboolean CCIR601_sampling; void *marker_list; int max_h_samp_factor; int max_v_samp_factor; int min_DCT_scaled_size; JDIMENSION total_iMCU_rows; void *sample_range_limit; int comps_in_scan; jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; JDIMENSION MCUs_per_row; JDIMENSION MCU_rows_in_scan; int blocks_in_MCU; int MCU_membership[D_MAX_BLOCKS_IN_MCU]; int Ss, Se, Ah, Al; int unread_marker; void *master; void *main; void *coef; void *post; void *inputctl; void *marker; void *entropy; void *idct; void *upsample; void *cconvert; void *cquantize; }; struct jpeg_compress_struct { struct jpeg_error_mgr *err; struct jpeg_memory_mgr *mem; void *progress; void *client_data; jboolean is_decompressor; int global_state; void *dest; JDIMENSION image_width; JDIMENSION image_height; int input_components; J_COLOR_SPACE in_color_space; double input_gamma; int data_precision; int num_components; J_COLOR_SPACE jpeg_color_space; jpeg_component_info *comp_info; void *quant_tbl_ptrs[NUM_QUANT_TBLS]; void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; unsigned char arith_dc_L[NUM_ARITH_TBLS]; unsigned char arith_dc_U[NUM_ARITH_TBLS]; unsigned char arith_ac_K[NUM_ARITH_TBLS]; int num_scans; const void *scan_info; jboolean raw_data_in; jboolean arith_code; jboolean optimize_coding; jboolean CCIR601_sampling; int smoothing_factor; J_DCT_METHOD dct_method; unsigned int restart_interval; int restart_in_rows; jboolean write_JFIF_header; unsigned char JFIF_major_version; unsigned char JFIF_minor_version; unsigned char density_unit; unsigned short X_density; unsigned short Y_density; jboolean write_Adobe_marker; JDIMENSION next_scanline; jboolean progressive_mode; int max_h_samp_factor; int max_v_samp_factor; JDIMENSION total_iMCU_rows; int comps_in_scan; jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; JDIMENSION MCUs_per_row; JDIMENSION MCU_rows_in_scan; int blocks_in_MCU; int MCU_membership[C_MAX_BLOCKS_IN_MCU]; int Ss, Se, Ah, Al; void *master; void *main; void *prep; void *coef; void *marker; void *cconvert; void *downsample; void *fdct; void *entropy; void *script_space; int script_space_size; }; struct jpeg_destination_mgr { unsigned char* next_output_byte; size_t free_in_buffer; void (*init_destination) (j_compress_ptr cinfo); jboolean (*empty_output_buffer) (j_compress_ptr cinfo); void (*term_destination) (j_compress_ptr cinfo); }; /* ================================================================= DarkPlaces definitions ================================================================= */ // Functions exported from libjpeg #define qjpeg_create_compress(cinfo) \ qjpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_compress_struct)) #define qjpeg_create_decompress(cinfo) \ qjpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof(struct jpeg_decompress_struct)) static void (*qjpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize); static void (*qjpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize); static void (*qjpeg_destroy_compress) (j_compress_ptr cinfo); static void (*qjpeg_destroy_decompress) (j_decompress_ptr cinfo); static void (*qjpeg_finish_compress) (j_compress_ptr cinfo); static jboolean (*qjpeg_finish_decompress) (j_decompress_ptr cinfo); static jboolean (*qjpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired); static int (*qjpeg_read_header) (j_decompress_ptr cinfo, jboolean require_image); static JDIMENSION (*qjpeg_read_scanlines) (j_decompress_ptr cinfo, unsigned char** scanlines, JDIMENSION max_lines); static void (*qjpeg_set_defaults) (j_compress_ptr cinfo); static void (*qjpeg_set_quality) (j_compress_ptr cinfo, int quality, jboolean force_baseline); static jboolean (*qjpeg_start_compress) (j_compress_ptr cinfo, jboolean write_all_tables); static jboolean (*qjpeg_start_decompress) (j_decompress_ptr cinfo); static struct jpeg_error_mgr* (*qjpeg_std_error) (struct jpeg_error_mgr *err); static JDIMENSION (*qjpeg_write_scanlines) (j_compress_ptr cinfo, unsigned char** scanlines, JDIMENSION num_lines); static void (*qjpeg_simple_progression) (j_compress_ptr cinfo); static dllfunction_t jpegfuncs[] = { {"jpeg_CreateCompress", (void **) &qjpeg_CreateCompress}, {"jpeg_CreateDecompress", (void **) &qjpeg_CreateDecompress}, {"jpeg_destroy_compress", (void **) &qjpeg_destroy_compress}, {"jpeg_destroy_decompress", (void **) &qjpeg_destroy_decompress}, {"jpeg_finish_compress", (void **) &qjpeg_finish_compress}, {"jpeg_finish_decompress", (void **) &qjpeg_finish_decompress}, {"jpeg_resync_to_restart", (void **) &qjpeg_resync_to_restart}, {"jpeg_read_header", (void **) &qjpeg_read_header}, {"jpeg_read_scanlines", (void **) &qjpeg_read_scanlines}, {"jpeg_set_defaults", (void **) &qjpeg_set_defaults}, {"jpeg_set_quality", (void **) &qjpeg_set_quality}, {"jpeg_start_compress", (void **) &qjpeg_start_compress}, {"jpeg_start_decompress", (void **) &qjpeg_start_decompress}, {"jpeg_std_error", (void **) &qjpeg_std_error}, {"jpeg_write_scanlines", (void **) &qjpeg_write_scanlines}, {"jpeg_simple_progression", (void **) &qjpeg_simple_progression}, {NULL, NULL} }; // Handle for JPEG DLL dllhandle_t jpeg_dll = NULL; qboolean jpeg_tried_loading = 0; #endif static unsigned char jpeg_eoi_marker [2] = {0xFF, JPEG_EOI}; static jmp_buf error_in_jpeg; static qboolean jpeg_toolarge; // Our own output manager for JPEG compression typedef struct { struct jpeg_destination_mgr pub; qfile_t* outfile; unsigned char* buffer; size_t bufsize; // used if outfile is NULL } my_destination_mgr; typedef my_destination_mgr* my_dest_ptr; /* ================================================================= DLL load & unload ================================================================= */ /* ==================== JPEG_OpenLibrary Try to load the JPEG DLL ==================== */ qboolean JPEG_OpenLibrary (void) { #ifdef LINK_TO_LIBJPEG return true; #else const char* dllnames [] = { #if defined(WIN32) "libjpeg.dll", #elif defined(MACOSX) "libjpeg.62.dylib", #else "libjpeg.so.62", "libjpeg.so", #endif NULL }; // Already loaded? if (jpeg_dll) return true; if (jpeg_tried_loading) // only try once return false; jpeg_tried_loading = true; #ifdef __ANDROID__ // loading the native Android libjpeg.so causes crashes Con_Printf("Not opening libjpeg.so dynamically on Android - use LINK_TO_LIBJPEG instead if it is needed.\n"); return false; #endif // Load the DLL return Sys_LoadLibrary (dllnames, &jpeg_dll, jpegfuncs); #endif } /* ==================== JPEG_CloseLibrary Unload the JPEG DLL ==================== */ void JPEG_CloseLibrary (void) { #ifndef LINK_TO_LIBJPEG Sys_UnloadLibrary (&jpeg_dll); jpeg_tried_loading = false; // allow retry #endif } /* ================================================================= JPEG decompression ================================================================= */ static void JPEG_Noop (j_decompress_ptr cinfo) {} static jboolean JPEG_FillInputBuffer (j_decompress_ptr cinfo) { // Insert a fake EOI marker cinfo->src->next_input_byte = jpeg_eoi_marker; cinfo->src->bytes_in_buffer = 2; return TRUE; } static void JPEG_SkipInputData (j_decompress_ptr cinfo, long num_bytes) { if (cinfo->src->bytes_in_buffer <= (unsigned long)num_bytes) { cinfo->src->bytes_in_buffer = 0; return; } cinfo->src->next_input_byte += num_bytes; cinfo->src->bytes_in_buffer -= num_bytes; } static void JPEG_MemSrc (j_decompress_ptr cinfo, const unsigned char *buffer, size_t filesize) { cinfo->src = (struct jpeg_source_mgr *)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof (struct jpeg_source_mgr)); cinfo->src->next_input_byte = buffer; cinfo->src->bytes_in_buffer = filesize; cinfo->src->init_source = JPEG_Noop; cinfo->src->fill_input_buffer = JPEG_FillInputBuffer; cinfo->src->skip_input_data = JPEG_SkipInputData; cinfo->src->resync_to_restart = qjpeg_resync_to_restart; // use the default method cinfo->src->term_source = JPEG_Noop; } static void JPEG_ErrorExit (j_common_ptr cinfo) { ((struct jpeg_decompress_struct*)cinfo)->err->output_message (cinfo); longjmp(error_in_jpeg, 1); } /* ==================== JPEG_LoadImage Load a JPEG image into a BGRA buffer ==================== */ unsigned char* JPEG_LoadImage_BGRA (const unsigned char *f, int filesize, int *miplevel) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; unsigned char *image_buffer = NULL, *scanline = NULL; unsigned int line; int submip = 0; // No DLL = no JPEGs if (!jpeg_dll) return NULL; if(miplevel && r_texture_jpeg_fastpicmip.integer) submip = bound(0, *miplevel, 3); cinfo.err = qjpeg_std_error (&jerr); qjpeg_create_decompress (&cinfo); if(setjmp(error_in_jpeg)) goto error_caught; cinfo.err = qjpeg_std_error (&jerr); cinfo.err->error_exit = JPEG_ErrorExit; JPEG_MemSrc (&cinfo, f, filesize); qjpeg_read_header (&cinfo, TRUE); cinfo.scale_num = 1; cinfo.scale_denom = (1 << submip); qjpeg_start_decompress (&cinfo); image_width = cinfo.output_width; image_height = cinfo.output_height; if (image_width > 32768 || image_height > 32768 || image_width <= 0 || image_height <= 0) { Con_Printf("JPEG_LoadImage: invalid image size %ix%i\n", image_width, image_height); return NULL; } image_buffer = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); scanline = (unsigned char *)Mem_Alloc(tempmempool, image_width * cinfo.output_components); if (!image_buffer || !scanline) { if (image_buffer) Mem_Free (image_buffer); if (scanline) Mem_Free (scanline); Con_Printf("JPEG_LoadImage: not enough memory for %i by %i image\n", image_width, image_height); qjpeg_finish_decompress (&cinfo); qjpeg_destroy_decompress (&cinfo); return NULL; } // Decompress the image, line by line line = 0; while (cinfo.output_scanline < cinfo.output_height) { unsigned char *buffer_ptr; int ind; qjpeg_read_scanlines (&cinfo, &scanline, 1); // Convert the image to BGRA switch (cinfo.output_components) { // RGB images case 3: buffer_ptr = &image_buffer[image_width * line * 4]; for (ind = 0; ind < image_width * 3; ind += 3, buffer_ptr += 4) { buffer_ptr[2] = scanline[ind]; buffer_ptr[1] = scanline[ind + 1]; buffer_ptr[0] = scanline[ind + 2]; buffer_ptr[3] = 255; } break; // Greyscale images (default to it, just in case) case 1: default: buffer_ptr = &image_buffer[image_width * line * 4]; for (ind = 0; ind < image_width; ind++, buffer_ptr += 4) { buffer_ptr[0] = scanline[ind]; buffer_ptr[1] = scanline[ind]; buffer_ptr[2] = scanline[ind]; buffer_ptr[3] = 255; } } line++; } Mem_Free (scanline); scanline = NULL; qjpeg_finish_decompress (&cinfo); qjpeg_destroy_decompress (&cinfo); if(miplevel) *miplevel -= submip; return image_buffer; error_caught: if(scanline) Mem_Free (scanline); if(image_buffer) Mem_Free (image_buffer); qjpeg_destroy_decompress (&cinfo); return NULL; } /* ================================================================= JPEG compression ================================================================= */ #define JPEG_OUTPUT_BUF_SIZE 4096 static void JPEG_InitDestination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; dest->buffer = (unsigned char*)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_IMAGE, JPEG_OUTPUT_BUF_SIZE * sizeof(unsigned char)); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; } static jboolean JPEG_EmptyOutputBuffer (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; if (FS_Write (dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE) != (size_t) JPEG_OUTPUT_BUF_SIZE) longjmp(error_in_jpeg, 1); dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; return true; } static void JPEG_TermDestination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; size_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; // Write any data remaining in the buffer if (datacount > 0) if (FS_Write (dest->outfile, dest->buffer, datacount) != (fs_offset_t)datacount) longjmp(error_in_jpeg, 1); } static void JPEG_FileDest (j_compress_ptr cinfo, qfile_t* outfile) { my_dest_ptr dest; // First time for this JPEG object? if (cinfo->dest == NULL) cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); dest = (my_dest_ptr)cinfo->dest; dest->pub.init_destination = JPEG_InitDestination; dest->pub.empty_output_buffer = JPEG_EmptyOutputBuffer; dest->pub.term_destination = JPEG_TermDestination; dest->outfile = outfile; } static void JPEG_Mem_InitDestination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = dest->bufsize; } static jboolean JPEG_Mem_EmptyOutputBuffer (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; jpeg_toolarge = true; dest->pub.next_output_byte = dest->buffer; dest->pub.free_in_buffer = dest->bufsize; return true; } static void JPEG_Mem_TermDestination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr)cinfo->dest; dest->bufsize = dest->pub.next_output_byte - dest->buffer; } static void JPEG_MemDest (j_compress_ptr cinfo, void* buf, size_t bufsize) { my_dest_ptr dest; // First time for this JPEG object? if (cinfo->dest == NULL) cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(my_destination_mgr)); dest = (my_dest_ptr)cinfo->dest; dest->pub.init_destination = JPEG_Mem_InitDestination; dest->pub.empty_output_buffer = JPEG_Mem_EmptyOutputBuffer; dest->pub.term_destination = JPEG_Mem_TermDestination; dest->outfile = NULL; dest->buffer = (unsigned char *) buf; dest->bufsize = bufsize; } /* ==================== JPEG_SaveImage_preflipped Save a preflipped JPEG image to a file ==================== */ qboolean JPEG_SaveImage_preflipped (const char *filename, int width, int height, unsigned char *data) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; unsigned char *scanline; unsigned int offset, linesize; qfile_t* file; // No DLL = no JPEGs if (!jpeg_dll) { Con_Print("You need the libjpeg library to save JPEG images\n"); return false; } // Open the file file = FS_OpenRealFile(filename, "wb", true); if (!file) return false; if(setjmp(error_in_jpeg)) goto error_caught; cinfo.err = qjpeg_std_error (&jerr); cinfo.err->error_exit = JPEG_ErrorExit; qjpeg_create_compress (&cinfo); JPEG_FileDest (&cinfo, file); // Set the parameters for compression cinfo.image_width = width; cinfo.image_height = height; cinfo.in_color_space = JCS_RGB; cinfo.input_components = 3; qjpeg_set_defaults (&cinfo); qjpeg_set_quality (&cinfo, (int)(scr_screenshot_jpeg_quality.value * 100), TRUE); qjpeg_simple_progression (&cinfo); // turn off subsampling (to make text look better) cinfo.optimize_coding = 1; cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; qjpeg_start_compress (&cinfo, true); // Compress each scanline linesize = cinfo.image_width * 3; offset = linesize * (cinfo.image_height - 1); while (cinfo.next_scanline < cinfo.image_height) { scanline = &data[offset - cinfo.next_scanline * linesize]; qjpeg_write_scanlines (&cinfo, &scanline, 1); } qjpeg_finish_compress (&cinfo); qjpeg_destroy_compress (&cinfo); FS_Close (file); return true; error_caught: qjpeg_destroy_compress (&cinfo); FS_Close (file); return false; } static size_t JPEG_try_SaveImage_to_Buffer (struct jpeg_compress_struct *cinfo, char *jpegbuf, size_t jpegsize, int quality, int width, int height, unsigned char *data) { unsigned char *scanline; unsigned int linesize; jpeg_toolarge = false; JPEG_MemDest (cinfo, jpegbuf, jpegsize); // Set the parameters for compression cinfo->image_width = width; cinfo->image_height = height; cinfo->in_color_space = JCS_RGB; cinfo->input_components = 3; qjpeg_set_defaults (cinfo); qjpeg_set_quality (cinfo, quality, FALSE); cinfo->comp_info[0].h_samp_factor = 2; cinfo->comp_info[0].v_samp_factor = 2; cinfo->comp_info[1].h_samp_factor = 1; cinfo->comp_info[1].v_samp_factor = 1; cinfo->comp_info[2].h_samp_factor = 1; cinfo->comp_info[2].v_samp_factor = 1; cinfo->optimize_coding = 1; qjpeg_start_compress (cinfo, true); // Compress each scanline linesize = width * 3; while (cinfo->next_scanline < cinfo->image_height) { scanline = &data[cinfo->next_scanline * linesize]; qjpeg_write_scanlines (cinfo, &scanline, 1); } qjpeg_finish_compress (cinfo); if(jpeg_toolarge) return 0; return ((my_dest_ptr) cinfo->dest)->bufsize; } size_t JPEG_SaveImage_to_Buffer (char *jpegbuf, size_t jpegsize, int width, int height, unsigned char *data) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; int quality; int quality_guess; size_t result; // No DLL = no JPEGs if (!jpeg_dll) { Con_Print("You need the libjpeg library to save JPEG images\n"); return false; } if(setjmp(error_in_jpeg)) goto error_caught; cinfo.err = qjpeg_std_error (&jerr); cinfo.err->error_exit = JPEG_ErrorExit; qjpeg_create_compress (&cinfo); #if 0 // used to get the formula below { char buf[1048576]; unsigned char *img; int i; img = Mem_Alloc(tempmempool, width * height * 3); for(i = 0; i < width * height * 3; ++i) img[i] = rand() & 0xFF; for(i = 0; i <= 100; ++i) { Con_Printf("! %d %d %d %d\n", width, height, i, (int) JPEG_try_SaveImage_to_Buffer(&cinfo, buf, sizeof(buf), i, width, height, img)); } Mem_Free(img); } #endif //quality_guess = (int)((100 * jpegsize - 41000) / (width*height) + 2); // fits random data quality_guess = (int)((256 * jpegsize - 81920) / (width*height) - 8); // fits Nexuiz's/Xonotic's map pictures quality_guess = bound(0, quality_guess, 100); quality = bound(0, quality_guess + sv_writepicture_quality.integer, 100); // assume it can do 10 failed attempts while(!(result = JPEG_try_SaveImage_to_Buffer(&cinfo, jpegbuf, jpegsize, quality, width, height, data))) { --quality; if(quality < 0) { Con_Printf("couldn't write image at all, probably too big\n"); return 0; } } qjpeg_destroy_compress (&cinfo); Con_DPrintf("JPEG_SaveImage_to_Buffer: guessed quality/size %d/%d, actually got %d/%d\n", quality_guess, (int)jpegsize, quality, (int)result); return result; error_caught: qjpeg_destroy_compress (&cinfo); return 0; } typedef struct CompressedImageCacheItem { char imagename[MAX_QPATH]; size_t maxsize; void *compressed; size_t compressed_size; struct CompressedImageCacheItem *next; } CompressedImageCacheItem; #define COMPRESSEDIMAGECACHE_SIZE 4096 static CompressedImageCacheItem *CompressedImageCache[COMPRESSEDIMAGECACHE_SIZE]; static void CompressedImageCache_Add(const char *imagename, size_t maxsize, void *compressed, size_t compressed_size) { char vabuf[1024]; const char *hashkey = va(vabuf, sizeof(vabuf), "%s:%d", imagename, (int) maxsize); int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; CompressedImageCacheItem *i; if(strlen(imagename) >= MAX_QPATH) return; // can't add this i = (CompressedImageCacheItem*) Z_Malloc(sizeof(CompressedImageCacheItem)); strlcpy(i->imagename, imagename, sizeof(i->imagename)); i->maxsize = maxsize; i->compressed = compressed; i->compressed_size = compressed_size; i->next = CompressedImageCache[hashindex]; CompressedImageCache[hashindex] = i; } static CompressedImageCacheItem *CompressedImageCache_Find(const char *imagename, size_t maxsize) { char vabuf[1024]; const char *hashkey = va(vabuf, sizeof(vabuf), "%s:%d", imagename, (int) maxsize); int hashindex = CRC_Block((unsigned char *) hashkey, strlen(hashkey)) % COMPRESSEDIMAGECACHE_SIZE; CompressedImageCacheItem *i = CompressedImageCache[hashindex]; while(i) { if(i->maxsize == maxsize) if(!strcmp(i->imagename, imagename)) return i; i = i->next; } return NULL; } qboolean Image_Compress(const char *imagename, size_t maxsize, void **buf, size_t *size) { unsigned char *imagedata, *newimagedata; int maxPixelCount; int components[3] = {2, 1, 0}; CompressedImageCacheItem *i; JPEG_OpenLibrary (); // for now; LH had the idea of replacing this by a better format PNG_OpenLibrary (); // for loading // No DLL = no JPEGs if (!jpeg_dll) { Con_Print("You need the libjpeg library to save JPEG images\n"); return false; } i = CompressedImageCache_Find(imagename, maxsize); if(i) { *size = i->compressed_size; *buf = i->compressed; } // load the image imagedata = loadimagepixelsbgra(imagename, true, false, false, NULL); if(!imagedata) return false; // find an appropriate size for somewhat okay compression if(maxsize <= 768) maxPixelCount = 32 * 32; else if(maxsize <= 1024) maxPixelCount = 64 * 64; else if(maxsize <= 4096) maxPixelCount = 128 * 128; else maxPixelCount = 256 * 256; while(image_width * image_height > maxPixelCount) { int one = 1; Image_MipReduce32(imagedata, imagedata, &image_width, &image_height, &one, image_width/2, image_height/2, 1); } newimagedata = (unsigned char *) Mem_Alloc(tempmempool, image_width * image_height * 3); // convert the image from BGRA to RGB Image_CopyMux(newimagedata, imagedata, image_width, image_height, false, false, false, 3, 4, components); Mem_Free(imagedata); // try to compress it to JPEG *buf = Z_Malloc(maxsize); *size = JPEG_SaveImage_to_Buffer((char *) *buf, maxsize, image_width, image_height, newimagedata); Mem_Free(newimagedata); if(!*size) { Z_Free(*buf); *buf = NULL; Con_Printf("could not compress image %s to %d bytes\n", imagename, (int)maxsize); // return false; // also cache failures! } // store it in the cache CompressedImageCache_Add(imagename, maxsize, *buf, *size); return (*buf != NULL); } darkplaces/input.h0000664000175000017500000000304713067716220013477 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /// \file input.h -- external (non-keyboard) input devices #ifndef INPUT_H #define INPUT_H extern cvar_t in_pitch_min; extern cvar_t in_pitch_max; extern qboolean in_client_mouse; extern float in_windowmouse_x, in_windowmouse_y; extern float in_mouse_x, in_mouse_y; //enum input_dest_e {input_game,input_message,input_menu} input_dest; void IN_Move (void); // add additional movement on top of the keyboard move cmd #define IN_BESTWEAPON_MAX 32 typedef struct { char name[32]; int impulse; int activeweaponcode; int weaponbit; int ammostat; int ammomin; /// \TODO add a parameter for the picture to be used by the sbar, and use it there } in_bestweapon_info_t; extern in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; void IN_BestWeapon_ResetData(void); ///< call before each map so QC can start from a clean state #endif darkplaces/darkplaces.dsp0000664000175000017500000003412113067716220015005 0ustar kalevkalev# Microsoft Developer Studio Project File - Name="darkplaces" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Application" 0x0101 CFG=darkplaces - Win32 Debug !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "darkplaces.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "darkplaces.mak" CFG="darkplaces - Win32 Debug" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "darkplaces - Win32 Release" (based on "Win32 (x86) Application") !MESSAGE "darkplaces - Win32 Debug" (based on "Win32 (x86) Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName "" # PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe !IF "$(CFG)" == "darkplaces - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release" # PROP Intermediate_Dir "Release" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "NDEBUG" # ADD RSC /l 0x40c /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 # ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib comctl32.lib /nologo /subsystem:windows /LARGEADDRESSAWARE /machine:I386 # SUBTRACT LINK32 /nodefaultlib !ELSEIF "$(CFG)" == "darkplaces - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug" # PROP Intermediate_Dir "Debug" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "_DEBUG" # ADD RSC /l 0x40c /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD LINK32 ws2_32.lib winmm.lib user32.lib gdi32.lib comctl32.lib /nologo /subsystem:windows /LARGEADDRESSAWARE /debug /machine:I386 /out:"Debug/darkplaces-debug.exe" /pdbtype:sept # SUBTRACT LINK32 /nodefaultlib !ENDIF # Begin Target # Name "darkplaces - Win32 Release" # Name "darkplaces - Win32 Debug" # Begin Group "Source Files" # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File SOURCE=.\builddate.c # End Source File # Begin Source File SOURCE=.\cd_shared.c # End Source File # Begin Source File SOURCE=.\cd_win.c # End Source File # Begin Source File SOURCE=.\cl_collision.c # End Source File # Begin Source File SOURCE=.\cl_demo.c # End Source File # Begin Source File SOURCE=.\cl_dyntexture.c # End Source File # Begin Source File SOURCE=.\cl_gecko.c # End Source File # Begin Source File SOURCE=.\cl_input.c # End Source File # Begin Source File SOURCE=.\cl_main.c # End Source File # Begin Source File SOURCE=.\cl_parse.c # End Source File # Begin Source File SOURCE=.\cl_particles.c # End Source File # Begin Source File SOURCE=.\cl_screen.c # End Source File # Begin Source File SOURCE=.\cl_video.c # End Source File # Begin Source File SOURCE=.\clvm_cmds.c # End Source File # Begin Source File SOURCE=.\cmd.c # End Source File # Begin Source File SOURCE=.\collision.c # End Source File # Begin Source File SOURCE=.\common.c # End Source File # Begin Source File SOURCE=.\conproc.c # End Source File # Begin Source File SOURCE=.\console.c # End Source File # Begin Source File SOURCE=.\csprogs.c # End Source File # Begin Source File SOURCE=.\curves.c # End Source File # Begin Source File SOURCE=.\cvar.c # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.c # End Source File # Begin Source File SOURCE=.\filematch.c # End Source File # Begin Source File SOURCE=.\fractalnoise.c # End Source File # Begin Source File SOURCE=.\fs.c # End Source File # Begin Source File SOURCE=.\gl_backend.c # End Source File # Begin Source File SOURCE=.\gl_draw.c # End Source File # Begin Source File SOURCE=.\gl_rmain.c # End Source File # Begin Source File SOURCE=.\gl_rsurf.c # End Source File # Begin Source File SOURCE=.\gl_textures.c # End Source File # Begin Source File SOURCE=.\host.c # End Source File # Begin Source File SOURCE=.\host_cmd.c # End Source File # Begin Source File SOURCE=.\image.c # End Source File # Begin Source File SOURCE=.\image_png.c # End Source File # Begin Source File SOURCE=.\jpeg.c # End Source File # Begin Source File SOURCE=.\keys.c # End Source File # Begin Source File SOURCE=.\lhnet.c # End Source File # Begin Source File SOURCE=.\libcurl.c # End Source File # Begin Source File SOURCE=.\mathlib.c # End Source File # Begin Source File SOURCE=.\matrixlib.c # End Source File # Begin Source File SOURCE=.\mdfour.c # End Source File # Begin Source File SOURCE=.\menu.c # End Source File # Begin Source File SOURCE=.\meshqueue.c # End Source File # Begin Source File SOURCE=.\model_alias.c # End Source File # Begin Source File SOURCE=.\model_brush.c # End Source File # Begin Source File SOURCE=.\model_shared.c # End Source File # Begin Source File SOURCE=.\model_sprite.c # End Source File # Begin Source File SOURCE=.\mvm_cmds.c # End Source File # Begin Source File SOURCE=.\netconn.c # End Source File # Begin Source File SOURCE=.\palette.c # End Source File # Begin Source File SOURCE=.\polygon.c # End Source File # Begin Source File SOURCE=.\portals.c # End Source File # Begin Source File SOURCE=.\protocol.c # End Source File # Begin Source File SOURCE=.\prvm_cmds.c # End Source File # Begin Source File SOURCE=.\prvm_edict.c # End Source File # Begin Source File SOURCE=.\prvm_exec.c # End Source File # Begin Source File SOURCE=.\r_explosion.c # End Source File # Begin Source File SOURCE=.\r_lerpanim.c # End Source File # Begin Source File SOURCE=.\r_lightning.c # End Source File # Begin Source File SOURCE=.\r_modules.c # End Source File # Begin Source File SOURCE=.\r_shadow.c # End Source File # Begin Source File SOURCE=.\r_sky.c # End Source File # Begin Source File SOURCE=.\r_sprites.c # End Source File # Begin Source File SOURCE=.\sbar.c # End Source File # Begin Source File SOURCE=.\snd_main.c # End Source File # Begin Source File SOURCE=.\snd_mem.c # End Source File # Begin Source File SOURCE=.\snd_mix.c # End Source File # Begin Source File SOURCE=.\snd_ogg.c # End Source File # Begin Source File SOURCE=.\snd_wav.c # End Source File # Begin Source File SOURCE=.\snd_win.c # End Source File # Begin Source File SOURCE=.\sv_demo.c # End Source File # Begin Source File SOURCE=.\sv_main.c # End Source File # Begin Source File SOURCE=.\sv_move.c # End Source File # Begin Source File SOURCE=.\sv_phys.c # End Source File # Begin Source File SOURCE=.\sv_user.c # End Source File # Begin Source File SOURCE=.\svbsp.c # End Source File # Begin Source File SOURCE=.\svvm_cmds.c # End Source File # Begin Source File SOURCE=.\sys_shared.c # End Source File # Begin Source File SOURCE=.\sys_win.c # End Source File # Begin Source File SOURCE=.\vid_shared.c # End Source File # Begin Source File SOURCE=.\vid_wgl.c # End Source File # Begin Source File SOURCE=.\view.c # End Source File # Begin Source File SOURCE=.\wad.c # End Source File # Begin Source File SOURCE=.\world.c # End Source File # Begin Source File SOURCE=.\zone.c # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File SOURCE=.\bspfile.h # End Source File # Begin Source File SOURCE=.\cdaudio.h # End Source File # Begin Source File SOURCE=.\cl_collision.h # End Source File # Begin Source File SOURCE=.\cl_dyntexture.h # End Source File # Begin Source File SOURCE=.\cl_gecko.h # End Source File # Begin Source File SOURCE=.\cl_screen.h # End Source File # Begin Source File SOURCE=.\cl_video.h # End Source File # Begin Source File SOURCE=.\client.h # End Source File # Begin Source File SOURCE=.\clprogdefs.h # End Source File # Begin Source File SOURCE=.\cmd.h # End Source File # Begin Source File SOURCE=.\collision.h # End Source File # Begin Source File SOURCE=.\common.h # End Source File # Begin Source File SOURCE=.\conproc.h # End Source File # Begin Source File SOURCE=.\console.h # End Source File # Begin Source File SOURCE=.\csprogs.h # End Source File # Begin Source File SOURCE=.\curves.h # End Source File # Begin Source File SOURCE=.\cvar.h # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.h # End Source File # Begin Source File SOURCE=.\draw.h # End Source File # Begin Source File SOURCE=.\fs.h # End Source File # Begin Source File SOURCE=.\gl_backend.h # End Source File # Begin Source File SOURCE=.\glquake.h # End Source File # Begin Source File SOURCE=.\image.h # End Source File # Begin Source File SOURCE=.\image_png.h # End Source File # Begin Source File SOURCE=.\input.h # End Source File # Begin Source File SOURCE=.\jpeg.h # End Source File # Begin Source File SOURCE=.\keys.h # End Source File # Begin Source File SOURCE=.\lhfont.h # End Source File # Begin Source File SOURCE=.\lhnet.h # End Source File # Begin Source File SOURCE=.\libcurl.h # End Source File # Begin Source File SOURCE=.\mathlib.h # End Source File # Begin Source File SOURCE=.\matrixlib.h # End Source File # Begin Source File SOURCE=.\mdfour.h # End Source File # Begin Source File SOURCE=.\menu.h # End Source File # Begin Source File SOURCE=.\meshqueue.h # End Source File # Begin Source File SOURCE=.\model_alias.h # End Source File # Begin Source File SOURCE=.\model_brush.h # End Source File # Begin Source File SOURCE=.\model_dpmodel.h # End Source File # Begin Source File SOURCE=.\model_psk.h # End Source File # Begin Source File SOURCE=.\model_shared.h # End Source File # Begin Source File SOURCE=.\model_sprite.h # End Source File # Begin Source File SOURCE=.\model_zymotic.h # End Source File # Begin Source File SOURCE=.\modelgen.h # End Source File # Begin Source File SOURCE=.\mprogdefs.h # End Source File # Begin Source File SOURCE=.\netconn.h # End Source File # Begin Source File SOURCE=.\palette.h # End Source File # Begin Source File SOURCE=.\polygon.h # End Source File # Begin Source File SOURCE=.\portals.h # End Source File # Begin Source File SOURCE=.\pr_comp.h # End Source File # Begin Source File SOURCE=.\pr_execprogram.h # End Source File # Begin Source File SOURCE=.\progdefs.h # End Source File # Begin Source File SOURCE=.\progs.h # End Source File # Begin Source File SOURCE=.\progsvm.h # End Source File # Begin Source File SOURCE=.\protocol.h # End Source File # Begin Source File SOURCE=.\prvm_cmds.h # End Source File # Begin Source File SOURCE=.\prvm_execprogram.h # End Source File # Begin Source File SOURCE=.\qtypes.h # End Source File # Begin Source File SOURCE=.\quakedef.h # End Source File # Begin Source File SOURCE=.\r_lerpanim.h # End Source File # Begin Source File SOURCE=.\r_modules.h # End Source File # Begin Source File SOURCE=.\r_shadow.h # End Source File # Begin Source File SOURCE=.\r_textures.h # End Source File # Begin Source File SOURCE=.\render.h # End Source File # Begin Source File SOURCE=.\sbar.h # End Source File # Begin Source File SOURCE=.\screen.h # End Source File # Begin Source File SOURCE=.\server.h # End Source File # Begin Source File SOURCE=.\snd_main.h # End Source File # Begin Source File SOURCE=.\snd_ogg.h # End Source File # Begin Source File SOURCE=.\snd_wav.h # End Source File # Begin Source File SOURCE=.\sound.h # End Source File # Begin Source File SOURCE=.\spritegn.h # End Source File # Begin Source File SOURCE=.\sv_demo.h # End Source File # Begin Source File SOURCE=.\svbsp.h # End Source File # Begin Source File SOURCE=.\sys.h # End Source File # Begin Source File SOURCE=.\vid.h # End Source File # Begin Source File SOURCE=.\wad.h # End Source File # Begin Source File SOURCE=.\world.h # End Source File # Begin Source File SOURCE=.\zone.h # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # Begin Source File SOURCE=.\darkplaces.ico # End Source File # Begin Source File SOURCE=.\darkplaces.rc # End Source File # Begin Source File SOURCE=.\resource.h # End Source File # End Group # End Target # End Project darkplaces/dpvsimpledecode.c0000664000175000017500000004233213067716220015502 0ustar kalevkalev#include "quakedef.h" #include "dpvsimpledecode.h" #define HZREADERROR_OK 0 #define HZREADERROR_EOF 1 #define HZREADERROR_MALLOCFAILED 2 //#define HZREADBLOCKSIZE 16000 #define HZREADBLOCKSIZE 1048576 typedef struct hz_bitstream_read_s { qfile_t *file; int endoffile; } hz_bitstream_read_t; typedef struct hz_bitstream_readblock_s { struct hz_bitstream_readblock_s *next; unsigned int size; unsigned char data[HZREADBLOCKSIZE]; } hz_bitstream_readblock_t; typedef struct hz_bitstream_readblocks_s { hz_bitstream_readblock_t *blocks; hz_bitstream_readblock_t *current; unsigned int position; unsigned int store; int count; } hz_bitstream_readblocks_t; static hz_bitstream_read_t *hz_bitstream_read_open(char *filename) { qfile_t *file; hz_bitstream_read_t *stream; if ((file = FS_OpenVirtualFile(filename, false))) { stream = (hz_bitstream_read_t *)Z_Malloc(sizeof(hz_bitstream_read_t)); memset(stream, 0, sizeof(*stream)); stream->file = file; return stream; } else return NULL; } static void hz_bitstream_read_close(hz_bitstream_read_t *stream) { if (stream) { FS_Close(stream->file); Z_Free(stream); } } static hz_bitstream_readblocks_t *hz_bitstream_read_blocks_new(void) { hz_bitstream_readblocks_t *blocks; blocks = (hz_bitstream_readblocks_t *)Z_Malloc(sizeof(hz_bitstream_readblocks_t)); if (blocks == NULL) return NULL; memset(blocks, 0, sizeof(hz_bitstream_readblocks_t)); return blocks; } static void hz_bitstream_read_blocks_free(hz_bitstream_readblocks_t *blocks) { hz_bitstream_readblock_t *b, *n; if (blocks == NULL) return; for (b = blocks->blocks;b;b = n) { n = b->next; Z_Free(b); } Z_Free(blocks); } static void hz_bitstream_read_flushbits(hz_bitstream_readblocks_t *blocks) { blocks->store = 0; blocks->count = 0; } static int hz_bitstream_read_blocks_read(hz_bitstream_readblocks_t *blocks, hz_bitstream_read_t *stream, unsigned int size) { int s; hz_bitstream_readblock_t *b, *p; s = size; p = NULL; b = blocks->blocks; while (s > 0) { if (b == NULL) { b = (hz_bitstream_readblock_t *)Z_Malloc(sizeof(hz_bitstream_readblock_t)); if (b == NULL) return HZREADERROR_MALLOCFAILED; b->next = NULL; b->size = 0; if (p != NULL) p->next = b; else blocks->blocks = b; } if (s > HZREADBLOCKSIZE) b->size = HZREADBLOCKSIZE; else b->size = s; s -= b->size; if (FS_Read(stream->file, b->data, b->size) != (fs_offset_t)b->size) { stream->endoffile = 1; break; } p = b; b = b->next; } while (b) { b->size = 0; b = b->next; } blocks->current = blocks->blocks; blocks->position = 0; hz_bitstream_read_flushbits(blocks); if (stream->endoffile) return HZREADERROR_EOF; return HZREADERROR_OK; } static unsigned int hz_bitstream_read_blocks_getbyte(hz_bitstream_readblocks_t *blocks) { while (blocks->current != NULL && blocks->position >= blocks->current->size) { blocks->position = 0; blocks->current = blocks->current->next; } if (blocks->current == NULL) return 0; return blocks->current->data[blocks->position++]; } static int hz_bitstream_read_bit(hz_bitstream_readblocks_t *blocks) { if (!blocks->count) { blocks->count += 8; blocks->store <<= 8; blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; } blocks->count--; return (blocks->store >> blocks->count) & 1; } static unsigned int hz_bitstream_read_bits(hz_bitstream_readblocks_t *blocks, int size) { unsigned int num = 0; // we can only handle about 24 bits at a time safely // (there might be up to 7 bits more than we need in the bit store) if (size > 24) { size -= 8; num |= hz_bitstream_read_bits(blocks, 8) << size; } while (blocks->count < size) { blocks->count += 8; blocks->store <<= 8; blocks->store |= hz_bitstream_read_blocks_getbyte(blocks) & 0xFF; } blocks->count -= size; num |= (blocks->store >> blocks->count) & ((1 << size) - 1); return num; } static unsigned int hz_bitstream_read_byte(hz_bitstream_readblocks_t *blocks) { return hz_bitstream_read_blocks_getbyte(blocks); } static unsigned int hz_bitstream_read_short(hz_bitstream_readblocks_t *blocks) { return (hz_bitstream_read_byte(blocks) << 8) | (hz_bitstream_read_byte(blocks)); } static unsigned int hz_bitstream_read_int(hz_bitstream_readblocks_t *blocks) { return (hz_bitstream_read_byte(blocks) << 24) | (hz_bitstream_read_byte(blocks) << 16) | (hz_bitstream_read_byte(blocks) << 8) | (hz_bitstream_read_byte(blocks)); } static void hz_bitstream_read_bytes(hz_bitstream_readblocks_t *blocks, void *outdata, unsigned int size) { unsigned char *out; out = (unsigned char *)outdata; while (size--) *out++ = hz_bitstream_read_byte(blocks); } #define BLOCKSIZE 8 typedef struct dpvsimpledecodestream_s { hz_bitstream_read_t *bitstream; hz_bitstream_readblocks_t *framedatablocks; int error; double info_framerate; unsigned int info_frames; unsigned int info_imagewidth; unsigned int info_imageheight; unsigned int info_imagebpp; unsigned int info_imageRloss; unsigned int info_imageRmask; unsigned int info_imageRshift; unsigned int info_imageGloss; unsigned int info_imageGmask; unsigned int info_imageGshift; unsigned int info_imageBloss; unsigned int info_imageBmask; unsigned int info_imageBshift; unsigned int info_imagesize; double info_aspectratio; // current video frame (needed because of delta compression) int videoframenum; // current video frame data (needed because of delta compression) unsigned int *videopixels; // channel the sound file is being played on int sndchan; } dpvsimpledecodestream_t; static int dpvsimpledecode_setpixelformat(dpvsimpledecodestream_t *s, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel) { int Rshift, Rbits, Gshift, Gbits, Bshift, Bbits; if (!Rmask) { s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; return s->error; } if (!Gmask) { s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; return s->error; } if (!Bmask) { s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; return s->error; } if (Rmask & Gmask || Rmask & Bmask || Gmask & Bmask) { s->error = DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP; return s->error; } switch (bytesperpixel) { case 2: if ((Rmask | Gmask | Bmask) > 65536) { s->error = DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP; return s->error; } break; case 4: break; default: s->error = DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP; return s->error; } for (Rshift = 0;!(Rmask & 1);Rshift++, Rmask >>= 1); for (Gshift = 0;!(Gmask & 1);Gshift++, Gmask >>= 1); for (Bshift = 0;!(Bmask & 1);Bshift++, Bmask >>= 1); if (((Rmask + 1) & Rmask) != 0) { s->error = DPVSIMPLEDECODEERROR_INVALIDRMASK; return s->error; } if (((Gmask + 1) & Gmask) != 0) { s->error = DPVSIMPLEDECODEERROR_INVALIDGMASK; return s->error; } if (((Bmask + 1) & Bmask) != 0) { s->error = DPVSIMPLEDECODEERROR_INVALIDBMASK; return s->error; } for (Rbits = 0;Rmask & 1;Rbits++, Rmask >>= 1); for (Gbits = 0;Gmask & 1;Gbits++, Gmask >>= 1); for (Bbits = 0;Bmask & 1;Bbits++, Bmask >>= 1); if (Rbits > 8) { Rshift += (Rbits - 8); Rbits = 8; } if (Gbits > 8) { Gshift += (Gbits - 8); Gbits = 8; } if (Bbits > 8) { Bshift += (Bbits - 8); Bbits = 8; } s->info_imagebpp = bytesperpixel; s->info_imageRloss = 16 + (8 - Rbits); s->info_imageGloss = 8 + (8 - Gbits); s->info_imageBloss = 0 + (8 - Bbits); s->info_imageRmask = (1 << Rbits) - 1; s->info_imageGmask = (1 << Gbits) - 1; s->info_imageBmask = (1 << Bbits) - 1; s->info_imageRshift = Rshift; s->info_imageGshift = Gshift; s->info_imageBshift = Bshift; s->info_imagesize = s->info_imagewidth * s->info_imageheight * s->info_imagebpp; return s->error; } // opening and closing streams // opens a stream void *dpvsimpledecode_open(clvideo_t *video, char *filename, const char **errorstring) { dpvsimpledecodestream_t *s; char t[8], *wavename; if (errorstring != NULL) *errorstring = NULL; s = (dpvsimpledecodestream_t *)Z_Malloc(sizeof(dpvsimpledecodestream_t)); if (s != NULL) { s->bitstream = hz_bitstream_read_open(filename); if (s->bitstream != NULL) { // check file identification s->framedatablocks = hz_bitstream_read_blocks_new(); if (s->framedatablocks != NULL) { hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); hz_bitstream_read_bytes(s->framedatablocks, t, 8); if (!memcmp(t, "DPVideo", 8)) { // check version number hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 2); if (hz_bitstream_read_short(s->framedatablocks) == 1) { hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 12); s->info_imagewidth = hz_bitstream_read_short(s->framedatablocks); s->info_imageheight = hz_bitstream_read_short(s->framedatablocks); s->info_framerate = (double) hz_bitstream_read_int(s->framedatablocks) * (1.0 / 65536.0); s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; if (s->info_framerate > 0.0) { s->videopixels = (unsigned int *)Z_Malloc(s->info_imagewidth * s->info_imageheight * sizeof(*s->videopixels)); if (s->videopixels != NULL) { size_t namelen; namelen = strlen(filename) + 10; wavename = (char *)Z_Malloc(namelen); if (wavename) { sfx_t* sfx; FS_StripExtension(filename, wavename, namelen); strlcat(wavename, ".wav", namelen); sfx = S_PrecacheSound (wavename, false, false); if (sfx != NULL) s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); else s->sndchan = -1; Z_Free(wavename); } // all is well... // set the module functions s->videoframenum = -10000; video->close = dpvsimpledecode_close; video->getwidth = dpvsimpledecode_getwidth; video->getheight = dpvsimpledecode_getheight; video->getframerate = dpvsimpledecode_getframerate; video->decodeframe = dpvsimpledecode_video; video->getaspectratio = dpvsimpledecode_getaspectratio; return s; } else if (errorstring != NULL) *errorstring = "unable to allocate video image buffer"; } else if (errorstring != NULL) *errorstring = "error in video info chunk"; } else if (errorstring != NULL) *errorstring = "read error"; } else if (errorstring != NULL) *errorstring = "not a dpvideo file"; hz_bitstream_read_blocks_free(s->framedatablocks); } else if (errorstring != NULL) *errorstring = "unable to allocate memory for reading buffer"; hz_bitstream_read_close(s->bitstream); } else if (errorstring != NULL) *errorstring = "unable to open file"; Z_Free(s); } else if (errorstring != NULL) *errorstring = "unable to allocate memory for stream info structure"; return NULL; } // closes a stream void dpvsimpledecode_close(void *stream) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; if (s == NULL) return; if (s->videopixels) Z_Free(s->videopixels); if (s->sndchan != -1) S_StopChannel (s->sndchan, true, true); if (s->framedatablocks) hz_bitstream_read_blocks_free(s->framedatablocks); if (s->bitstream) hz_bitstream_read_close(s->bitstream); Z_Free(s); } // utilitarian functions // returns the current error number for the stream, and resets the error // number to DPVSIMPLEDECODEERROR_NONE // if the supplied string pointer variable is not NULL, it will be set to the // error message int dpvsimpledecode_error(void *stream, const char **errorstring) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; int e; e = s->error; s->error = 0; if (errorstring) { switch (e) { case DPVSIMPLEDECODEERROR_NONE: *errorstring = "no error"; break; case DPVSIMPLEDECODEERROR_EOF: *errorstring = "end of file reached (this is not an error)"; break; case DPVSIMPLEDECODEERROR_READERROR: *errorstring = "read error (corrupt or incomplete file)"; break; case DPVSIMPLEDECODEERROR_SOUNDBUFFERTOOSMALL: *errorstring = "sound buffer is too small for decoding frame (please allocate it as large as dpvsimpledecode_getneededsoundbufferlength suggests)"; break; case DPVSIMPLEDECODEERROR_INVALIDRMASK: *errorstring = "invalid red bits mask"; break; case DPVSIMPLEDECODEERROR_INVALIDGMASK: *errorstring = "invalid green bits mask"; break; case DPVSIMPLEDECODEERROR_INVALIDBMASK: *errorstring = "invalid blue bits mask"; break; case DPVSIMPLEDECODEERROR_COLORMASKSOVERLAP: *errorstring = "color bit masks overlap"; break; case DPVSIMPLEDECODEERROR_COLORMASKSEXCEEDBPP: *errorstring = "color masks too big for specified bytes per pixel"; break; case DPVSIMPLEDECODEERROR_UNSUPPORTEDBPP: *errorstring = "unsupported bytes per pixel (must be 2 for 16bit, or 4 for 32bit)"; break; default: *errorstring = "unknown error"; break; } } return e; } // returns the width of the image data unsigned int dpvsimpledecode_getwidth(void *stream) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; return s->info_imagewidth; } // returns the height of the image data unsigned int dpvsimpledecode_getheight(void *stream) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; return s->info_imageheight; } // returns the framerate of the stream double dpvsimpledecode_getframerate(void *stream) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; return s->info_framerate; } // return aspect ratio of the stream double dpvsimpledecode_getaspectratio(void *stream) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; return s->info_aspectratio; } static int dpvsimpledecode_convertpixels(dpvsimpledecodestream_t *s, void *imagedata, int imagebytesperrow) { unsigned int a, x, y, width, height; unsigned int Rloss, Rmask, Rshift, Gloss, Gmask, Gshift, Bloss, Bmask, Bshift; unsigned int *in; width = s->info_imagewidth; height = s->info_imageheight; Rloss = s->info_imageRloss; Rmask = s->info_imageRmask; Rshift = s->info_imageRshift; Gloss = s->info_imageGloss; Gmask = s->info_imageGmask; Gshift = s->info_imageGshift; Bloss = s->info_imageBloss; Bmask = s->info_imageBmask; Bshift = s->info_imageBshift; in = s->videopixels; if (s->info_imagebpp == 4) { unsigned int *outrow; for (y = 0;y < height;y++) { outrow = (unsigned int *)((unsigned char *)imagedata + y * imagebytesperrow); for (x = 0;x < width;x++) { a = *in++; outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); } } } else { unsigned short *outrow; for (y = 0;y < height;y++) { outrow = (unsigned short *)((unsigned char *)imagedata + y * imagebytesperrow); if (Rloss == 19 && Gloss == 10 && Bloss == 3 && Rshift == 11 && Gshift == 5 && Bshift == 0) { // optimized for (x = 0;x < width;x++) { a = *in++; outrow[x] = ((a >> 8) & 0xF800) | ((a >> 5) & 0x07E0) | ((a >> 3) & 0x001F); } } else { for (x = 0;x < width;x++) { a = *in++; outrow[x] = (((a >> Rloss) & Rmask) << Rshift) | (((a >> Gloss) & Gmask) << Gshift) | (((a >> Bloss) & Bmask) << Bshift); } } } } return s->error; } static int dpvsimpledecode_decompressimage(dpvsimpledecodestream_t *s) { int i, a, b, colors, g, x1, y1, bw, bh, width, height, palettebits; unsigned int palette[256], *outrow, *out; g = BLOCKSIZE; width = s->info_imagewidth; height = s->info_imageheight; for (y1 = 0;y1 < height;y1 += g) { outrow = s->videopixels + y1 * width; bh = g; if (y1 + bh > height) bh = height - y1; for (x1 = 0;x1 < width;x1 += g) { out = outrow + x1; bw = g; if (x1 + bw > width) bw = width - x1; if (hz_bitstream_read_bit(s->framedatablocks)) { // updated block palettebits = hz_bitstream_read_bits(s->framedatablocks, 3); colors = 1 << palettebits; for (i = 0;i < colors;i++) palette[i] = hz_bitstream_read_bits(s->framedatablocks, 24); if (palettebits) { for (b = 0;b < bh;b++, out += width) for (a = 0;a < bw;a++) out[a] = palette[hz_bitstream_read_bits(s->framedatablocks, palettebits)]; } else { for (b = 0;b < bh;b++, out += width) for (a = 0;a < bw;a++) out[a] = palette[0]; } } } } return s->error; } // decodes a video frame to the supplied output pixels int dpvsimpledecode_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) { dpvsimpledecodestream_t *s = (dpvsimpledecodestream_t *)stream; unsigned int framedatasize; char t[4]; s->error = DPVSIMPLEDECODEERROR_NONE; if (dpvsimpledecode_setpixelformat(s, Rmask, Gmask, Bmask, bytesperpixel)) return s->error; hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, 8); hz_bitstream_read_bytes(s->framedatablocks, t, 4); if (memcmp(t, "VID0", 4)) { if (t[0] == 0) return (s->error = DPVSIMPLEDECODEERROR_EOF); else return (s->error = DPVSIMPLEDECODEERROR_READERROR); } framedatasize = hz_bitstream_read_int(s->framedatablocks); hz_bitstream_read_blocks_read(s->framedatablocks, s->bitstream, framedatasize); if (dpvsimpledecode_decompressimage(s)) return s->error; dpvsimpledecode_convertpixels(s, imagedata, imagebytesperrow); return s->error; } darkplaces/r_lerpanim.c0000664000175000017500000000000013067716222014447 0ustar kalevkalevdarkplaces/svbsp.c0000664000175000017500000003722313067716222013475 0ustar kalevkalev // Shadow Volume BSP code written by Forest "LordHavoc" Hale on 2003-11-06 and placed into public domain. // Modified by LordHavoc (to make it work and other nice things like that) on 2007-01-24 and 2007-01-25 // Optimized by LordHavoc on 2009-12-24 and 2009-12-25 #include #include #include "svbsp.h" #include "polygon.h" #define MAX_SVBSP_POLYGONPOINTS 64 #define SVBSP_CLIP_EPSILON (1.0f / 1024.0f) #define SVBSP_DotProduct(a,b) ((a)[0]*(b)[0]+(a)[1]*(b)[1]+(a)[2]*(b)[2]) typedef struct svbsp_polygon_s { float points[MAX_SVBSP_POLYGONPOINTS][3]; //unsigned char splitflags[MAX_SVBSP_POLYGONPOINTS]; int facesplitflag; int numpoints; } svbsp_polygon_t; static void SVBSP_PlaneFromPoints(float *plane4f, const float *p1, const float *p2, const float *p3) { float ilength; // calculate unnormalized plane plane4f[0] = (p1[1] - p2[1]) * (p3[2] - p2[2]) - (p1[2] - p2[2]) * (p3[1] - p2[1]); plane4f[1] = (p1[2] - p2[2]) * (p3[0] - p2[0]) - (p1[0] - p2[0]) * (p3[2] - p2[2]); plane4f[2] = (p1[0] - p2[0]) * (p3[1] - p2[1]) - (p1[1] - p2[1]) * (p3[0] - p2[0]); plane4f[3] = SVBSP_DotProduct(plane4f, p1); // normalize the plane normal and adjust distance accordingly ilength = (float)sqrt(SVBSP_DotProduct(plane4f, plane4f)); if (ilength) ilength = 1.0f / ilength; plane4f[0] *= ilength; plane4f[1] *= ilength; plane4f[2] *= ilength; plane4f[3] *= ilength; } static void SVBSP_DividePolygon(const svbsp_polygon_t *poly, const float *plane, svbsp_polygon_t *front, svbsp_polygon_t *back, const float *dists, const int *sides) { int i, j, count = poly->numpoints, frontcount = 0, backcount = 0; float frac, ifrac, c[3], pdist, ndist; const float *nextpoint; const float *points = poly->points[0]; float *outfront = front->points[0]; float *outback = back->points[0]; for(i = 0;i < count;i++, points += 3) { j = i + 1; if (j >= count) j = 0; if (!(sides[i] & 2)) { outfront[frontcount*3+0] = points[0]; outfront[frontcount*3+1] = points[1]; outfront[frontcount*3+2] = points[2]; frontcount++; } if (!(sides[i] & 1)) { outback[backcount*3+0] = points[0]; outback[backcount*3+1] = points[1]; outback[backcount*3+2] = points[2]; backcount++; } if ((sides[i] | sides[j]) == 3) { // don't allow splits if remaining points would overflow point buffer if (frontcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) continue; if (backcount + (count - i) > MAX_SVBSP_POLYGONPOINTS - 1) continue; nextpoint = poly->points[j]; pdist = dists[i]; ndist = dists[j]; frac = pdist / (pdist - ndist); ifrac = 1.0f - frac; c[0] = points[0] * ifrac + frac * nextpoint[0]; c[1] = points[1] * ifrac + frac * nextpoint[1]; c[2] = points[2] * ifrac + frac * nextpoint[2]; outfront[frontcount*3+0] = c[0]; outfront[frontcount*3+1] = c[1]; outfront[frontcount*3+2] = c[2]; frontcount++; outback[backcount*3+0] = c[0]; outback[backcount*3+1] = c[1]; outback[backcount*3+2] = c[2]; backcount++; } } front->numpoints = frontcount; back->numpoints = backcount; } void SVBSP_Init(svbsp_t *b, const float *origin, int maxnodes, svbsp_node_t *nodes) { memset(b, 0, sizeof(*b)); b->origin[0] = origin[0]; b->origin[1] = origin[1]; b->origin[2] = origin[2]; b->numnodes = 3; b->maxnodes = maxnodes; b->nodes = nodes; b->ranoutofnodes = 0; b->stat_occluders_rejected = 0; b->stat_occluders_accepted = 0; b->stat_occluders_fragments_accepted = 0; b->stat_occluders_fragments_rejected = 0; b->stat_queries_rejected = 0; b->stat_queries_accepted = 0; b->stat_queries_fragments_accepted = 0; b->stat_queries_fragments_rejected = 0; // the bsp tree must be initialized to have two perpendicular splits axes // through origin, otherwise the polygon insertions would affect the // opposite side of the tree, which would be disasterous. // // so this code has to make 3 nodes and 4 leafs, and since the leafs are // represented by empty/solid state numbers in this system rather than // actual structs, we only need to make the 3 nodes. // root node // this one splits the world into +X and -X sides b->nodes[0].plane[0] = 1; b->nodes[0].plane[1] = 0; b->nodes[0].plane[2] = 0; b->nodes[0].plane[3] = origin[0]; b->nodes[0].parent = -1; b->nodes[0].children[0] = 1; b->nodes[0].children[1] = 2; // +X side node // this one splits the +X half of the world into +Y and -Y b->nodes[1].plane[0] = 0; b->nodes[1].plane[1] = 1; b->nodes[1].plane[2] = 0; b->nodes[1].plane[3] = origin[1]; b->nodes[1].parent = 0; b->nodes[1].children[0] = -1; b->nodes[1].children[1] = -1; // -X side node // this one splits the -X half of the world into +Y and -Y b->nodes[2].plane[0] = 0; b->nodes[2].plane[1] = 1; b->nodes[2].plane[2] = 0; b->nodes[2].plane[3] = origin[1]; b->nodes[2].parent = 0; b->nodes[2].children[0] = -1; b->nodes[2].children[1] = -1; } static void SVBSP_InsertOccluderPolygonNodes(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) { // now we need to create up to numpoints + 1 new nodes, forming a BSP tree // describing the occluder polygon's shadow volume int i, j, p; svbsp_node_t *node; // points and lines are valid testers but not occluders if (poly->numpoints < 3) return; // if there aren't enough nodes remaining, skip it if (b->numnodes + poly->numpoints + 1 >= b->maxnodes) { b->ranoutofnodes = 1; return; } // add one node per side, then the actual occluding face node // thread safety notes: // DO NOT multithread insertion, it could be made 'safe' but the results // would be inconsistent. // // it is completely safe to multithread queries in all cases. // // if an insertion is occurring the query will give intermediate results, // being blocked by some volumes but not others, which is perfectly okay // for visibility culling intended only to reduce rendering work // note down the first available nodenum for the *parentnodenumpointer // line which is done last to allow multithreaded queries during an // insertion for (i = 0, p = poly->numpoints - 1;i < poly->numpoints;p = i, i++) { #if 1 // see if a parent plane describes this side for (j = parentnodenum;j >= 0;j = b->nodes[j].parent) { float *parentnodeplane = b->nodes[j].plane; if (fabs(SVBSP_DotProduct(poly->points[p], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON && fabs(SVBSP_DotProduct(poly->points[i], parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON && fabs(SVBSP_DotProduct(b->origin , parentnodeplane) - parentnodeplane[3]) < SVBSP_CLIP_EPSILON) break; } if (j >= 0) continue; // already have a matching parent plane #endif #if 0 // skip any sides that were classified as belonging to a parent plane if (poly->splitflags[i]) continue; #endif // create a side plane // anything infront of this is not inside the shadow volume node = b->nodes + b->numnodes++; SVBSP_PlaneFromPoints(node->plane, b->origin, poly->points[p], poly->points[i]); // we need to flip the plane if it puts any part of the polygon on the // wrong side // (in this way this code treats all polygons as float sided) // // because speed is important this stops as soon as it finds proof // that the orientation is right or wrong // (we know that the plane is on one edge of the polygon, so there is // never a case where points lie on both sides, so the first hint is // sufficient) for (j = 0;j < poly->numpoints;j++) { float d = SVBSP_DotProduct(poly->points[j], node->plane) - node->plane[3]; if (d < -SVBSP_CLIP_EPSILON) break; if (d > SVBSP_CLIP_EPSILON) { node->plane[0] *= -1; node->plane[1] *= -1; node->plane[2] *= -1; node->plane[3] *= -1; break; } } node->parent = parentnodenum; node->children[0] = -1; // empty node->children[1] = -1; // empty // link this child into the tree *parentnodenumpointer = parentnodenum = (int)(node - b->nodes); // now point to the child pointer for the next node to update later parentnodenumpointer = &node->children[1]; } #if 1 // skip the face plane if it lies on a parent plane if (!poly->facesplitflag) #endif { // add the face-plane node // infront is empty, behind is shadow node = b->nodes + b->numnodes++; SVBSP_PlaneFromPoints(node->plane, poly->points[0], poly->points[1], poly->points[2]); // this is a flip check similar to the one above // this one checks if the plane faces the origin, if not, flip it if (SVBSP_DotProduct(b->origin, node->plane) - node->plane[3] < -SVBSP_CLIP_EPSILON) { node->plane[0] *= -1; node->plane[1] *= -1; node->plane[2] *= -1; node->plane[3] *= -1; } node->parent = parentnodenum; node->children[0] = -1; // empty node->children[1] = -2; // shadow // link this child into the tree // (with the addition of this node, queries will now be culled by it) *parentnodenumpointer = (int)(node - b->nodes); } } static int SVBSP_AddPolygonNode(svbsp_t *b, int *parentnodenumpointer, int parentnodenum, const svbsp_polygon_t *poly, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) { int i; int s; int facesplitflag = poly->facesplitflag; int bothsides; float plane[4]; float d; svbsp_polygon_t front; svbsp_polygon_t back; svbsp_node_t *node; int sides[MAX_SVBSP_POLYGONPOINTS]; float dists[MAX_SVBSP_POLYGONPOINTS]; if (poly->numpoints < 1) return 0; // recurse through plane nodes while (*parentnodenumpointer >= 0) { // get node info parentnodenum = *parentnodenumpointer; node = b->nodes + parentnodenum; plane[0] = node->plane[0]; plane[1] = node->plane[1]; plane[2] = node->plane[2]; plane[3] = node->plane[3]; // calculate point dists for clipping bothsides = 0; for (i = 0;i < poly->numpoints;i++) { d = SVBSP_DotProduct(poly->points[i], plane) - plane[3]; s = 0; if (d > SVBSP_CLIP_EPSILON) s = 1; if (d < -SVBSP_CLIP_EPSILON) s = 2; bothsides |= s; dists[i] = d; sides[i] = s; } // see which side the polygon is on switch(bothsides) { default: case 0: // no need to split, this polygon is on the plane // this case only occurs for polygons on the face plane, usually // the same polygon (inserted twice - once as occluder, once as // tester) // if this is an occluder, it is redundant if (insertoccluder) return 1; // occluded // if this is a tester, test the front side, because it is // probably the same polygon that created this node... facesplitflag = 1; parentnodenumpointer = &node->children[0]; continue; case 1: // no need to split, just go to one side parentnodenumpointer = &node->children[0]; continue; case 2: // no need to split, just go to one side parentnodenumpointer = &node->children[1]; continue; case 3: // lies on both sides of the plane, we need to split it #if 1 SVBSP_DividePolygon(poly, plane, &front, &back, dists, sides); #else PolygonF_Divide(poly->numpoints, poly->points[0], plane[0], plane[1], plane[2], plane[3], SVBSP_CLIP_EPSILON, MAX_SVBSP_POLYGONPOINTS, front.points[0], &front.numpoints, MAX_SVBSP_POLYGONPOINTS, back.points[0], &back.numpoints, NULL); if (front.numpoints > MAX_SVBSP_POLYGONPOINTS) front.numpoints = MAX_SVBSP_POLYGONPOINTS; if (back.numpoints > MAX_SVBSP_POLYGONPOINTS) back.numpoints = MAX_SVBSP_POLYGONPOINTS; #endif front.facesplitflag = facesplitflag; back.facesplitflag = facesplitflag; // recurse the sides and return the resulting occlusion flags i = SVBSP_AddPolygonNode(b, &node->children[0], *parentnodenumpointer, &front, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); i |= SVBSP_AddPolygonNode(b, &node->children[1], *parentnodenumpointer, &back , insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); return i; } } // leaf node if (*parentnodenumpointer == -1) { // empty leaf node; and some geometry survived // if inserting an occluder, replace this empty leaf with a shadow volume #if 0 for (i = 0;i < poly->numpoints-2;i++) { Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.25f, 0.0f, 0.0f, 1.0f); Debug_PolygonEnd(); } #endif if (insertoccluder) { b->stat_occluders_fragments_accepted++; SVBSP_InsertOccluderPolygonNodes(b, parentnodenumpointer, parentnodenum, poly, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); } else b->stat_queries_fragments_accepted++; if (fragmentcallback) fragmentcallback(fragmentcallback_pointer1, fragmentcallback_number1, b, poly->numpoints, poly->points[0]); return 2; } else { // otherwise it's a solid leaf which destroys all polygons inside it if (insertoccluder) b->stat_occluders_fragments_rejected++; else b->stat_queries_fragments_rejected++; #if 0 for (i = 0;i < poly->numpoints-2;i++) { Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); Debug_PolygonVertex(poly->points[ 0][0], poly->points[ 0][1], poly->points[ 0][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); Debug_PolygonVertex(poly->points[i+1][0], poly->points[i+1][1], poly->points[i+1][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); Debug_PolygonVertex(poly->points[i+2][0], poly->points[i+2][1], poly->points[i+2][2], 0.0f, 0.0f, 0.0f, 0.0f, 0.25f, 1.0f); Debug_PolygonEnd(); } #endif } return 1; } int SVBSP_AddPolygon(svbsp_t *b, int numpoints, const float *points, int insertoccluder, void (*fragmentcallback)(void *fragmentcallback_pointer1, int fragmentcallback_number1, svbsp_t *b, int numpoints, const float *points), void *fragmentcallback_pointer1, int fragmentcallback_number1) { int i; int nodenum; svbsp_polygon_t poly; // don't even consider an empty polygon // note we still allow points and lines to be tested... if (numpoints < 1) return 0; // if the polygon has too many points, we would crash if (numpoints > MAX_SVBSP_POLYGONPOINTS) return 0; poly.numpoints = numpoints; for (i = 0;i < numpoints;i++) { poly.points[i][0] = points[i*3+0]; poly.points[i][1] = points[i*3+1]; poly.points[i][2] = points[i*3+2]; //poly.splitflags[i] = 0; // this edge is a valid BSP splitter - clipped edges are not (because they lie on a bsp plane) poly.facesplitflag = 0; // this face is a valid BSP Splitter - if it lies on a bsp plane it is not } #if 0 //if (insertoccluder) for (i = 0;i < poly.numpoints-2;i++) { Debug_PolygonBegin(NULL, DRAWFLAG_ADDITIVE); Debug_PolygonVertex(poly.points[ 0][0], poly.points[ 0][1], poly.points[ 0][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); Debug_PolygonVertex(poly.points[i+1][0], poly.points[i+1][1], poly.points[i+1][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); Debug_PolygonVertex(poly.points[i+2][0], poly.points[i+2][1], poly.points[i+2][2], 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 1.0f); Debug_PolygonEnd(); } #endif nodenum = 0; i = SVBSP_AddPolygonNode(b, &nodenum, -1, &poly, insertoccluder, fragmentcallback, fragmentcallback_pointer1, fragmentcallback_number1); if (insertoccluder) { if (i & 2) b->stat_occluders_accepted++; else b->stat_occluders_rejected++; } else { if (i & 2) b->stat_queries_accepted++; else b->stat_queries_rejected++; } return i; } darkplaces/r_modules.h0000664000175000017500000000065113067716222014331 0ustar kalevkalev #ifndef R_MODULES_H #define R_MODULES_H void R_Modules_Init(void); void R_RegisterModule(const char *name, void(*start)(void), void(*shutdown)(void), void(*newmap)(void), void(*devicelost)(void), void(*devicerestored)(void)); void R_Modules_Start(void); void R_Modules_Shutdown(void); void R_Modules_NewMap(void); void R_Modules_Restart(void); void R_Modules_DeviceLost(void); void R_Modules_DeviceRestored(void); #endif darkplaces/cl_screen.c0000664000175000017500000032514113067716216014277 0ustar kalevkalev #include "quakedef.h" #include "cl_video.h" #include "image.h" #include "jpeg.h" #include "image_png.h" #include "cl_collision.h" #include "libcurl.h" #include "csprogs.h" #ifdef CONFIG_VIDEO_CAPTURE #include "cap_avi.h" #include "cap_ogg.h" #endif // we have to include snd_main.h here only to get access to snd_renderbuffer->format.speed when writing the AVI headers #include "snd_main.h" cvar_t scr_viewsize = {CVAR_SAVE, "viewsize","100", "how large the view should be, 110 disables inventory bar, 120 disables status bar"}; cvar_t scr_fov = {CVAR_SAVE, "fov","90", "field of vision, 1-170 degrees, default 90, some players use 110-130"}; cvar_t scr_conalpha = {CVAR_SAVE, "scr_conalpha", "1", "opacity of console background gfx/conback"}; cvar_t scr_conalphafactor = {CVAR_SAVE, "scr_conalphafactor", "1", "opacity of console background gfx/conback relative to scr_conalpha; when 0, gfx/conback is not drawn"}; cvar_t scr_conalpha2factor = {CVAR_SAVE, "scr_conalpha2factor", "0", "opacity of console background gfx/conback2 relative to scr_conalpha; when 0, gfx/conback2 is not drawn"}; cvar_t scr_conalpha3factor = {CVAR_SAVE, "scr_conalpha3factor", "0", "opacity of console background gfx/conback3 relative to scr_conalpha; when 0, gfx/conback3 is not drawn"}; cvar_t scr_conbrightness = {CVAR_SAVE, "scr_conbrightness", "1", "brightness of console background (0 = black, 1 = image)"}; cvar_t scr_conforcewhiledisconnected = {0, "scr_conforcewhiledisconnected", "1", "forces fullscreen console while disconnected"}; cvar_t scr_conscroll_x = {CVAR_SAVE, "scr_conscroll_x", "0", "scroll speed of gfx/conback in x direction"}; cvar_t scr_conscroll_y = {CVAR_SAVE, "scr_conscroll_y", "0", "scroll speed of gfx/conback in y direction"}; cvar_t scr_conscroll2_x = {CVAR_SAVE, "scr_conscroll2_x", "0", "scroll speed of gfx/conback2 in x direction"}; cvar_t scr_conscroll2_y = {CVAR_SAVE, "scr_conscroll2_y", "0", "scroll speed of gfx/conback2 in y direction"}; cvar_t scr_conscroll3_x = {CVAR_SAVE, "scr_conscroll3_x", "0", "scroll speed of gfx/conback3 in x direction"}; cvar_t scr_conscroll3_y = {CVAR_SAVE, "scr_conscroll3_y", "0", "scroll speed of gfx/conback3 in y direction"}; #ifdef CONFIG_MENU cvar_t scr_menuforcewhiledisconnected = {0, "scr_menuforcewhiledisconnected", "0", "forces menu while disconnected"}; #endif cvar_t scr_centertime = {0, "scr_centertime","2", "how long centerprint messages show"}; cvar_t scr_showram = {CVAR_SAVE, "showram","1", "show ram icon if low on surface cache memory (not used)"}; cvar_t scr_showturtle = {CVAR_SAVE, "showturtle","0", "show turtle icon when framerate is too low"}; cvar_t scr_showpause = {CVAR_SAVE, "showpause","1", "show pause icon when game is paused"}; cvar_t scr_showbrand = {0, "showbrand","0", "shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered)"}; cvar_t scr_printspeed = {0, "scr_printspeed","0", "speed of intermission printing (episode end texts), a value of 0 disables the slow printing"}; cvar_t scr_loadingscreen_background = {0, "scr_loadingscreen_background","0", "show the last visible background during loading screen (costs one screenful of video memory)"}; cvar_t scr_loadingscreen_scale = {0, "scr_loadingscreen_scale","1", "scale factor of the background"}; cvar_t scr_loadingscreen_scale_base = {0, "scr_loadingscreen_scale_base","0", "0 = console pixels, 1 = video pixels"}; cvar_t scr_loadingscreen_scale_limit = {0, "scr_loadingscreen_scale_limit","0", "0 = no limit, 1 = until first edge hits screen edge, 2 = until last edge hits screen edge, 3 = until width hits screen width, 4 = until height hits screen height"}; cvar_t scr_loadingscreen_picture = {CVAR_SAVE, "scr_loadingscreen_picture", "gfx/loading", "picture shown during loading"}; cvar_t scr_loadingscreen_count = {0, "scr_loadingscreen_count","1", "number of loading screen files to use randomly (named loading.tga, loading2.tga, loading3.tga, ...)"}; cvar_t scr_loadingscreen_firstforstartup = {0, "scr_loadingscreen_firstforstartup","0", "remove loading.tga from random scr_loadingscreen_count selection and only display it on client startup, 0 = normal, 1 = firstforstartup"}; cvar_t scr_loadingscreen_barcolor = {0, "scr_loadingscreen_barcolor", "0 0 1", "rgb color of loadingscreen progress bar"}; cvar_t scr_loadingscreen_barheight = {0, "scr_loadingscreen_barheight", "8", "the height of the loadingscreen progress bar"}; cvar_t scr_loadingscreen_maxfps = {0, "scr_loadingscreen_maxfps", "10", "restrict maximal FPS for loading screen so it will not update very often (this will make lesser loading times on a maps loading large number of models)"}; cvar_t scr_infobar_height = {0, "scr_infobar_height", "8", "the height of the infobar items"}; cvar_t vid_conwidth = {CVAR_SAVE, "vid_conwidth", "640", "virtual width of 2D graphics system"}; cvar_t vid_conheight = {CVAR_SAVE, "vid_conheight", "480", "virtual height of 2D graphics system"}; cvar_t vid_pixelheight = {CVAR_SAVE, "vid_pixelheight", "1", "adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example)"}; cvar_t scr_screenshot_jpeg = {CVAR_SAVE, "scr_screenshot_jpeg","1", "save jpeg instead of targa"}; cvar_t scr_screenshot_jpeg_quality = {CVAR_SAVE, "scr_screenshot_jpeg_quality","0.9", "image quality of saved jpeg"}; cvar_t scr_screenshot_png = {CVAR_SAVE, "scr_screenshot_png","0", "save png instead of targa"}; cvar_t scr_screenshot_gammaboost = {CVAR_SAVE, "scr_screenshot_gammaboost","1", "gamma correction on saved screenshots and videos, 1.0 saves unmodified images"}; cvar_t scr_screenshot_hwgamma = {CVAR_SAVE, "scr_screenshot_hwgamma","1", "apply the video gamma ramp to saved screenshots and videos"}; cvar_t scr_screenshot_alpha = {0, "scr_screenshot_alpha","0", "try to write an alpha channel to screenshots (debugging feature)"}; cvar_t scr_screenshot_timestamp = {CVAR_SAVE, "scr_screenshot_timestamp", "1", "use a timestamp based number of the type YYYYMMDDHHMMSSsss instead of sequential numbering"}; // scr_screenshot_name is defined in fs.c #ifdef CONFIG_VIDEO_CAPTURE cvar_t cl_capturevideo = {0, "cl_capturevideo", "0", "enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output)"}; cvar_t cl_capturevideo_demo_stop = {CVAR_SAVE, "cl_capturevideo_demo_stop", "1", "automatically stops video recording when demo ends"}; cvar_t cl_capturevideo_printfps = {CVAR_SAVE, "cl_capturevideo_printfps", "1", "prints the frames per second captured in capturevideo (is only written to the log file, not to the console, as that would be visible on the video)"}; cvar_t cl_capturevideo_width = {CVAR_SAVE, "cl_capturevideo_width", "0", "scales all frames to this resolution before saving the video"}; cvar_t cl_capturevideo_height = {CVAR_SAVE, "cl_capturevideo_height", "0", "scales all frames to this resolution before saving the video"}; cvar_t cl_capturevideo_realtime = {0, "cl_capturevideo_realtime", "0", "causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over a minute"}; cvar_t cl_capturevideo_fps = {CVAR_SAVE, "cl_capturevideo_fps", "30", "how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful)"}; cvar_t cl_capturevideo_nameformat = {CVAR_SAVE, "cl_capturevideo_nameformat", "dpvideo", "prefix for saved videos (the date is encoded using strftime escapes)"}; cvar_t cl_capturevideo_number = {CVAR_SAVE, "cl_capturevideo_number", "1", "number to append to video filename, incremented each time a capture begins"}; cvar_t cl_capturevideo_ogg = {CVAR_SAVE, "cl_capturevideo_ogg", "1", "save captured video data as Ogg/Vorbis/Theora streams"}; cvar_t cl_capturevideo_framestep = {CVAR_SAVE, "cl_capturevideo_framestep", "1", "when set to n >= 1, render n frames to capture one (useful for motion blur like effects)"}; #endif cvar_t r_letterbox = {0, "r_letterbox", "0", "reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes)"}; cvar_t r_stereo_separation = {0, "r_stereo_separation", "4", "separation distance of eyes in the world (negative values are only useful for cross-eyed viewing)"}; cvar_t r_stereo_sidebyside = {0, "r_stereo_sidebyside", "0", "side by side views for those who can't afford glasses but can afford eye strain (note: use a negative r_stereo_separation if you want cross-eyed viewing)"}; cvar_t r_stereo_horizontal = {0, "r_stereo_horizontal", "0", "aspect skewed side by side view for special decoder/display hardware"}; cvar_t r_stereo_vertical = {0, "r_stereo_vertical", "0", "aspect skewed top and bottom view for special decoder/display hardware"}; cvar_t r_stereo_redblue = {0, "r_stereo_redblue", "0", "red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too)"}; cvar_t r_stereo_redcyan = {0, "r_stereo_redcyan", "0", "red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D"}; cvar_t r_stereo_redgreen = {0, "r_stereo_redgreen", "0", "red/green anaglyph stereo glasses (for those who don't mind yellow)"}; cvar_t r_stereo_angle = {0, "r_stereo_angle", "0", "separation angle of eyes (makes the views look different directions, as an example, 90 gives a 90 degree separation where the views are 45 degrees left and 45 degrees right)"}; cvar_t scr_stipple = {0, "scr_stipple", "0", "interlacing-like stippling of the display"}; cvar_t scr_refresh = {0, "scr_refresh", "1", "allows you to completely shut off rendering for benchmarking purposes"}; cvar_t scr_screenshot_name_in_mapdir = {CVAR_SAVE, "scr_screenshot_name_in_mapdir", "0", "if set to 1, screenshots are placed in a subdirectory named like the map they are from"}; cvar_t shownetgraph = {CVAR_SAVE, "shownetgraph", "0", "shows a graph of packet sizes and other information, 0 = off, 1 = show client netgraph, 2 = show client and server netgraphs (when hosting a server)"}; cvar_t cl_demo_mousegrab = {0, "cl_demo_mousegrab", "0", "Allows reading the mouse input while playing demos. Useful for camera mods developed in csqc. (0: never, 1: always)"}; cvar_t timedemo_screenshotframelist = {0, "timedemo_screenshotframelist", "", "when performing a timedemo, take screenshots of each frame in this space-separated list - example: 1 201 401"}; cvar_t vid_touchscreen_outlinealpha = {0, "vid_touchscreen_outlinealpha", "0", "opacity of touchscreen area outlines"}; cvar_t vid_touchscreen_overlayalpha = {0, "vid_touchscreen_overlayalpha", "0.25", "opacity of touchscreen area icons"}; cvar_t r_speeds_graph = {CVAR_SAVE, "r_speeds_graph", "0", "display a graph of renderer statistics "}; cvar_t r_speeds_graph_filter[8] = { {CVAR_SAVE, "r_speeds_graph_filter_r", "timedelta", "Red - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_g", "batch_batches", "Green - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_b", "batch_triangles", "Blue - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_y", "fast_triangles", "Yellow - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_c", "copytriangles_triangles", "Cyan - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_m", "dynamic_triangles", "Magenta - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_w", "animcache_shade_vertices", "White - display the specified renderer statistic"}, {CVAR_SAVE, "r_speeds_graph_filter_o", "animcache_shape_vertices", "Orange - display the specified renderer statistic"}, }; cvar_t r_speeds_graph_length = {CVAR_SAVE, "r_speeds_graph_length", "1024", "number of frames in statistics graph, can be from 4 to 8192"}; cvar_t r_speeds_graph_seconds = {CVAR_SAVE, "r_speeds_graph_seconds", "2", "number of seconds in graph, can be from 0.1 to 120"}; cvar_t r_speeds_graph_x = {CVAR_SAVE, "r_speeds_graph_x", "0", "position of graph"}; cvar_t r_speeds_graph_y = {CVAR_SAVE, "r_speeds_graph_y", "0", "position of graph"}; cvar_t r_speeds_graph_width = {CVAR_SAVE, "r_speeds_graph_width", "256", "size of graph"}; cvar_t r_speeds_graph_height = {CVAR_SAVE, "r_speeds_graph_height", "128", "size of graph"}; cvar_t r_speeds_graph_maxtimedelta = {CVAR_SAVE, "r_speeds_graph_maxtimedelta", "16667", "maximum timedelta to display in the graph (this value will be the top line)"}; cvar_t r_speeds_graph_maxdefault = {CVAR_SAVE, "r_speeds_graph_maxdefault", "100", "if the minimum and maximum observed values are closer than this, use this value as the graph range (keeps small numbers from being big graphs)"}; extern cvar_t v_glslgamma; extern cvar_t sbar_info_pos; extern cvar_t r_fog_clear; #define WANT_SCREENSHOT_HWGAMMA (scr_screenshot_hwgamma.integer && vid_usinghwgamma) int jpeg_supported = false; qboolean scr_initialized; // ready to draw float scr_con_current; int scr_con_margin_bottom; extern int con_vislines; static void SCR_ScreenShot_f (void); static void R_Envmap_f (void); // backend void R_ClearScreen(qboolean fogcolor); /* =============================================================================== CENTER PRINTING =============================================================================== */ char scr_centerstring[MAX_INPUTLINE]; float scr_centertime_start; // for slow victory printing float scr_centertime_off; int scr_center_lines; int scr_erase_lines; int scr_erase_center; char scr_infobarstring[MAX_INPUTLINE]; float scr_infobartime_off; /* ============== SCR_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void SCR_CenterPrint(const char *str) { strlcpy (scr_centerstring, str, sizeof (scr_centerstring)); scr_centertime_off = scr_centertime.value; scr_centertime_start = cl.time; // count the number of lines for centering scr_center_lines = 1; while (*str) { if (*str == '\n') scr_center_lines++; str++; } } static void SCR_DrawCenterString (void) { char *start; int x, y; int remaining; int color; if(cl.intermission == 2) // in finale, if(sb_showscores) // make TAB hide the finale message (sb_showscores overrides finale in sbar.c) return; if(scr_centertime.value <= 0 && !cl.intermission) return; // the finale prints the characters one at a time, except if printspeed is an absurdly high value if (cl.intermission && scr_printspeed.value > 0 && scr_printspeed.value < 1000000) remaining = (int)(scr_printspeed.value * (cl.time - scr_centertime_start)); else remaining = 9999; scr_erase_center = 0; start = scr_centerstring; if (remaining < 1) return; if (scr_center_lines <= 4) y = (int)(vid_conheight.integer*0.35); else y = 48; color = -1; do { // scan the number of characters on the line, not counting color codes char *newline = strchr(start, '\n'); int l = newline ? (newline - start) : (int)strlen(start); float width = DrawQ_TextWidth(start, l, 8, 8, false, FONT_CENTERPRINT); x = (int) (vid_conwidth.integer - width)/2; if (l > 0) { if (remaining < l) l = remaining; DrawQ_String(x, y, start, l, 8, 8, 1, 1, 1, 1, 0, &color, false, FONT_CENTERPRINT); remaining -= l; if (remaining <= 0) return; } y += 8; if (!newline) break; start = newline + 1; // skip the \n } while (1); } static void SCR_CheckDrawCenterString (void) { if (scr_center_lines > scr_erase_lines) scr_erase_lines = scr_center_lines; if (cl.time > cl.oldtime) scr_centertime_off -= cl.time - cl.oldtime; // don't draw if this is a normal stats-screen intermission, // only if it is not an intermission, or a finale intermission if (cl.intermission == 1) return; if (scr_centertime_off <= 0 && !cl.intermission) return; if (key_dest != key_game) return; SCR_DrawCenterString (); } static void SCR_DrawNetGraph_DrawGraph (int graphx, int graphy, int graphwidth, int graphheight, float graphscale, int graphlimit, const char *label, float textsize, int packetcounter, netgraphitem_t *netgraph) { netgraphitem_t *graph; int j, x, y, numlines; int totalbytes = 0; char bytesstring[128]; float g[NETGRAPH_PACKETS][7]; float *a; float *b; r_vertexgeneric_t vertex[(NETGRAPH_PACKETS+2)*6*2]; r_vertexgeneric_t *v; DrawQ_Fill(graphx, graphy, graphwidth, graphheight + textsize * 2, 0, 0, 0, 0.5, 0); // draw the bar graph itself memset(g, 0, sizeof(g)); for (j = 0;j < NETGRAPH_PACKETS;j++) { graph = netgraph + j; g[j][0] = 1.0f - 0.25f * (realtime - graph->time); g[j][1] = 1.0f; g[j][2] = 1.0f; g[j][3] = 1.0f; g[j][4] = 1.0f; g[j][5] = 1.0f; g[j][6] = 1.0f; if (graph->unreliablebytes == NETGRAPH_LOSTPACKET) g[j][1] = 0.00f; else if (graph->unreliablebytes == NETGRAPH_CHOKEDPACKET) g[j][2] = 0.90f; else { if(netgraph[j].time >= netgraph[(j+NETGRAPH_PACKETS-1)%NETGRAPH_PACKETS].time) if(graph->unreliablebytes + graph->reliablebytes + graph->ackbytes >= graphlimit * (netgraph[j].time - netgraph[(j+NETGRAPH_PACKETS-1)%NETGRAPH_PACKETS].time)) g[j][2] = 0.98f; g[j][3] = 1.0f - graph->unreliablebytes * graphscale; g[j][4] = g[j][3] - graph->reliablebytes * graphscale; g[j][5] = g[j][4] - graph->ackbytes * graphscale; // count bytes in the last second if (realtime - graph->time < 1.0f) totalbytes += graph->unreliablebytes + graph->reliablebytes + graph->ackbytes; } if(graph->cleartime >= 0) g[j][6] = 0.5f + 0.5f * (2.0 / M_PI) * atan((M_PI / 2.0) * (graph->cleartime - graph->time)); g[j][1] = bound(0.0f, g[j][1], 1.0f); g[j][2] = bound(0.0f, g[j][2], 1.0f); g[j][3] = bound(0.0f, g[j][3], 1.0f); g[j][4] = bound(0.0f, g[j][4], 1.0f); g[j][5] = bound(0.0f, g[j][5], 1.0f); g[j][6] = bound(0.0f, g[j][6], 1.0f); } // render the lines for the graph numlines = 0; v = vertex; for (j = 0;j < NETGRAPH_PACKETS;j++) { a = g[j]; b = g[(j+1)%NETGRAPH_PACKETS]; if (a[0] < 0.0f || b[0] > 1.0f || b[0] < a[0]) continue; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[2], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[2], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[1], 0.0f);Vector4Set(v->color4f, 1.0f, 0.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[1], 0.0f);Vector4Set(v->color4f, 1.0f, 0.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[5], 0.0f);Vector4Set(v->color4f, 0.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[5], 0.0f);Vector4Set(v->color4f, 0.0f, 1.0f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[4], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[4], 0.0f);Vector4Set(v->color4f, 1.0f, 1.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[3], 0.0f);Vector4Set(v->color4f, 1.0f, 0.5f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[3], 0.0f);Vector4Set(v->color4f, 1.0f, 0.5f, 0.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * a[0], graphy + graphheight * a[6], 0.0f);Vector4Set(v->color4f, 0.0f, 0.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; VectorSet(v->vertex3f, graphx + graphwidth * b[0], graphy + graphheight * b[6], 0.0f);Vector4Set(v->color4f, 0.0f, 0.0f, 1.0f, 1.0f);Vector2Set(v->texcoord2f, 0.0f, 0.0f);v++; numlines += 6; } if (numlines > 0) { R_Mesh_PrepareVertices_Generic(numlines*2, vertex, NULL, 0); DrawQ_Lines(0.0f, numlines, 0, false); } x = graphx; y = graphy + graphheight; dpsnprintf(bytesstring, sizeof(bytesstring), "%i", totalbytes); DrawQ_String(x, y, label , 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; DrawQ_String(x, y, bytesstring, 0, textsize, textsize, 1.0f, 1.0f, 1.0f, 1.0f, 0, NULL, false, FONT_DEFAULT);y += textsize; } /* ============== SCR_DrawNetGraph ============== */ static void SCR_DrawNetGraph (void) { int i, separator1, separator2, graphwidth, graphheight, netgraph_x, netgraph_y, textsize, index, netgraphsperrow, graphlimit; float graphscale; netconn_t *c; char vabuf[1024]; if (cls.state != ca_connected) return; if (!cls.netcon) return; if (!shownetgraph.integer) return; separator1 = 2; separator2 = 4; textsize = 8; graphwidth = 120; graphheight = 70; graphscale = 1.0f / 1500.0f; graphlimit = cl_rate.integer; netgraphsperrow = (vid_conwidth.integer + separator2) / (graphwidth * 2 + separator1 + separator2); netgraphsperrow = max(netgraphsperrow, 1); index = 0; netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); netgraph_y = (vid_conheight.integer - 48 - sbar_info_pos.integer + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); c = cls.netcon; SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, graphlimit, "incoming", textsize, c->incoming_packetcounter, c->incoming_netgraph); SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, graphlimit, "outgoing", textsize, c->outgoing_packetcounter, c->outgoing_netgraph); index++; if (sv.active && shownetgraph.integer >= 2) { for (i = 0;i < svs.maxclients;i++) { c = svs.clients[i].netconnection; if (!c) continue; netgraph_x = (vid_conwidth.integer + separator2) - (1 + (index % netgraphsperrow)) * (graphwidth * 2 + separator1 + separator2); netgraph_y = (vid_conheight.integer - 48 + separator2) - (1 + (index / netgraphsperrow)) * (graphheight + textsize + separator2); SCR_DrawNetGraph_DrawGraph(netgraph_x , netgraph_y, graphwidth, graphheight, graphscale, graphlimit, va(vabuf, sizeof(vabuf), "%s", svs.clients[i].name), textsize, c->outgoing_packetcounter, c->outgoing_netgraph); SCR_DrawNetGraph_DrawGraph(netgraph_x + graphwidth + separator1, netgraph_y, graphwidth, graphheight, graphscale, graphlimit, "" , textsize, c->incoming_packetcounter, c->incoming_netgraph); index++; } } } /* ============== SCR_DrawTurtle ============== */ static void SCR_DrawTurtle (void) { static int count; if (cls.state != ca_connected) return; if (!scr_showturtle.integer) return; if (cl.realframetime < 0.1) { count = 0; return; } count++; if (count < 3) return; DrawQ_Pic (0, 0, Draw_CachePic ("gfx/turtle"), 0, 0, 1, 1, 1, 1, 0); } /* ============== SCR_DrawNet ============== */ static void SCR_DrawNet (void) { if (cls.state != ca_connected) return; if (realtime - cl.last_received_message < 0.3) return; if (cls.demoplayback) return; DrawQ_Pic (64, 0, Draw_CachePic ("gfx/net"), 0, 0, 1, 1, 1, 1, 0); } /* ============== DrawPause ============== */ static void SCR_DrawPause (void) { cachepic_t *pic; if (cls.state != ca_connected) return; if (!scr_showpause.integer) // turn off for screenshots return; if (!cl.paused) return; pic = Draw_CachePic ("gfx/pause"); DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, (vid_conheight.integer - pic->height)/2, pic, 0, 0, 1, 1, 1, 1, 0); } /* ============== SCR_DrawBrand ============== */ static void SCR_DrawBrand (void) { cachepic_t *pic; float x, y; if (!scr_showbrand.value) return; pic = Draw_CachePic ("gfx/brand"); switch ((int)scr_showbrand.value) { case 1: // bottom left x = 0; y = vid_conheight.integer - pic->height; break; case 2: // bottom centre x = (vid_conwidth.integer - pic->width) / 2; y = vid_conheight.integer - pic->height; break; case 3: // bottom right x = vid_conwidth.integer - pic->width; y = vid_conheight.integer - pic->height; break; case 4: // centre right x = vid_conwidth.integer - pic->width; y = (vid_conheight.integer - pic->height) / 2; break; case 5: // top right x = vid_conwidth.integer - pic->width; y = 0; break; case 6: // top centre x = (vid_conwidth.integer - pic->width) / 2; y = 0; break; case 7: // top left x = 0; y = 0; break; case 8: // centre left x = 0; y = (vid_conheight.integer - pic->height) / 2; break; default: return; } DrawQ_Pic (x, y, pic, 0, 0, 1, 1, 1, 1, 0); } /* ============== SCR_DrawQWDownload ============== */ static int SCR_DrawQWDownload(int offset) { // sync with SCR_InfobarHeight int len; float x, y; float size = scr_infobar_height.value; char temp[256]; if (!cls.qw_downloadname[0]) { cls.qw_downloadspeedrate = 0; cls.qw_downloadspeedtime = realtime; cls.qw_downloadspeedcount = 0; return 0; } if (realtime >= cls.qw_downloadspeedtime + 1) { cls.qw_downloadspeedrate = cls.qw_downloadspeedcount; cls.qw_downloadspeedtime = realtime; cls.qw_downloadspeedcount = 0; } if (cls.protocol == PROTOCOL_QUAKEWORLD) dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadspeedrate); else dpsnprintf(temp, sizeof(temp), "Downloading %s %3i%% (%i/%i) at %i bytes/s", cls.qw_downloadname, cls.qw_downloadpercent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize, cls.qw_downloadspeedrate); len = (int)strlen(temp); x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; y = vid_conheight.integer - size - offset; DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); return size; } /* ============== SCR_DrawInfobarString ============== */ static int SCR_DrawInfobarString(int offset) { int len; float x, y; float size = scr_infobar_height.value; len = (int)strlen(scr_infobarstring); x = (vid_conwidth.integer - DrawQ_TextWidth(scr_infobarstring, len, size, size, false, FONT_INFOBAR)) / 2; y = vid_conheight.integer - size - offset; DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); DrawQ_String(x, y, scr_infobarstring, len, size, size, 1, 1, 1, 1, 0, NULL, false, FONT_INFOBAR); return size; } /* ============== SCR_DrawCurlDownload ============== */ static int SCR_DrawCurlDownload(int offset) { // sync with SCR_InfobarHeight int len; int nDownloads; int i; float x, y; float size = scr_infobar_height.value; Curl_downloadinfo_t *downinfo; char temp[256]; char addinfobuf[128]; const char *addinfo; downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo, addinfobuf, sizeof(addinfobuf)); if(!downinfo) return 0; y = vid_conheight.integer - size * nDownloads - offset; if(addinfo) { len = (int)strlen(addinfo); x = (vid_conwidth.integer - DrawQ_TextWidth(addinfo, len, size, size, true, FONT_INFOBAR)) / 2; DrawQ_Fill(0, y - size, vid_conwidth.integer, size, 1, 1, 1, cls.signon == SIGNONS ? 0.8 : 1, 0); DrawQ_String(x, y - size, addinfo, len, size, size, 0, 0, 0, 1, 0, NULL, true, FONT_INFOBAR); } for(i = 0; i != nDownloads; ++i) { if(downinfo[i].queued) dpsnprintf(temp, sizeof(temp), "Still in queue: %s", downinfo[i].filename); else if(downinfo[i].progress <= 0) dpsnprintf(temp, sizeof(temp), "Downloading %s ... ???.?%% @ %.1f KiB/s", downinfo[i].filename, downinfo[i].speed / 1024.0); else dpsnprintf(temp, sizeof(temp), "Downloading %s ... %5.1f%% @ %.1f KiB/s", downinfo[i].filename, 100.0 * downinfo[i].progress, downinfo[i].speed / 1024.0); len = (int)strlen(temp); x = (vid_conwidth.integer - DrawQ_TextWidth(temp, len, size, size, true, FONT_INFOBAR)) / 2; DrawQ_Fill(0, y + i * size, vid_conwidth.integer, size, 0, 0, 0, cls.signon == SIGNONS ? 0.5 : 1, 0); DrawQ_String(x, y + i * size, temp, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); } Z_Free(downinfo); return size * (nDownloads + (addinfo ? 1 : 0)); } /* ============== SCR_DrawInfobar ============== */ static void SCR_DrawInfobar(void) { int offset = 0; offset += SCR_DrawQWDownload(offset); offset += SCR_DrawCurlDownload(offset); if(scr_infobartime_off > 0) offset += SCR_DrawInfobarString(offset); if(offset != scr_con_margin_bottom) Con_DPrintf("broken console margin calculation: %d != %d\n", offset, scr_con_margin_bottom); } static int SCR_InfobarHeight(void) { int offset = 0; Curl_downloadinfo_t *downinfo; const char *addinfo; int nDownloads; char addinfobuf[128]; if (cl.time > cl.oldtime) scr_infobartime_off -= cl.time - cl.oldtime; if(scr_infobartime_off > 0) offset += 1; if(cls.qw_downloadname[0]) offset += 1; downinfo = Curl_GetDownloadInfo(&nDownloads, &addinfo, addinfobuf, sizeof(addinfobuf)); if(downinfo) { offset += (nDownloads + (addinfo ? 1 : 0)); Z_Free(downinfo); } offset *= scr_infobar_height.value; return offset; } /* ============== SCR_InfoBar_f ============== */ static void SCR_InfoBar_f(void) { if(Cmd_Argc() == 3) { scr_infobartime_off = atof(Cmd_Argv(1)); strlcpy(scr_infobarstring, Cmd_Argv(2), sizeof(scr_infobarstring)); } else { Con_Printf("usage:\ninfobar expiretime \"string\"\n"); } } //============================================================================= /* ================== SCR_SetUpToDrawConsole ================== */ static void SCR_SetUpToDrawConsole (void) { // lines of console to display float conlines; #ifdef CONFIG_MENU static int framecounter = 0; #endif Con_CheckResize (); #ifdef CONFIG_MENU if (scr_menuforcewhiledisconnected.integer && key_dest == key_game && cls.state == ca_disconnected) { if (framecounter >= 2) MR_ToggleMenu(1); else framecounter++; } else framecounter = 0; #endif if (scr_conforcewhiledisconnected.integer && key_dest == key_game && cls.signon != SIGNONS) key_consoleactive |= KEY_CONSOLEACTIVE_FORCED; else key_consoleactive &= ~KEY_CONSOLEACTIVE_FORCED; // decide on the height of the console if (key_consoleactive & KEY_CONSOLEACTIVE_USER) conlines = vid_conheight.integer/2; // half screen else conlines = 0; // none visible scr_con_current = conlines; } /* ================== SCR_DrawConsole ================== */ void SCR_DrawConsole (void) { scr_con_margin_bottom = SCR_InfobarHeight(); if (key_consoleactive & KEY_CONSOLEACTIVE_FORCED) { // full screen Con_DrawConsole (vid_conheight.integer - scr_con_margin_bottom); } else if (scr_con_current) Con_DrawConsole (min((int)scr_con_current, vid_conheight.integer - scr_con_margin_bottom)); else con_vislines = 0; } /* =============== SCR_BeginLoadingPlaque ================ */ void SCR_BeginLoadingPlaque (qboolean startup) { // save console log up to this point to log_file if it was set by configs Log_Start(); Host_StartVideo(); SCR_UpdateLoadingScreen(false, startup); } //============================================================================= const char *r_stat_name[r_stat_count] = { "timedelta", "quality", "renders", "entities", "entities_surfaces", "entities_triangles", "world_leafs", "world_portals", "world_surfaces", "world_triangles", "lightmapupdates", "lightmapupdatepixels", "particles", "drawndecals", "totaldecals", "draws", "draws_vertices", "draws_elements", "lights", "lights_clears", "lights_scissored", "lights_lighttriangles", "lights_shadowtriangles", "lights_dynamicshadowtriangles", "bouncegrid_lights", "bouncegrid_particles", "bouncegrid_traces", "bouncegrid_hits", "bouncegrid_splats", "bouncegrid_bounces", "photoncache_animated", "photoncache_cached", "photoncache_traced", "bloom", "bloom_copypixels", "bloom_drawpixels", "indexbufferuploadcount", "indexbufferuploadsize", "vertexbufferuploadcount", "vertexbufferuploadsize", "framedatacurrent", "framedatasize", "bufferdatacurrent_vertex", // R_BUFFERDATA_ types are added to this index "bufferdatacurrent_index16", "bufferdatacurrent_index32", "bufferdatacurrent_uniform", "bufferdatasize_vertex", // R_BUFFERDATA_ types are added to this index "bufferdatasize_index16", "bufferdatasize_index32", "bufferdatasize_uniform", "animcache_vertexmesh_count", "animcache_vertexmesh_vertices", "animcache_vertexmesh_maxvertices", "animcache_skeletal_count", "animcache_skeletal_bones", "animcache_skeletal_maxbones", "animcache_shade_count", "animcache_shade_vertices", "animcache_shade_maxvertices", "animcache_shape_count", "animcache_shape_vertices", "animcache_shape_maxvertices", "batch_batches", "batch_withgaps", "batch_surfaces", "batch_vertices", "batch_triangles", "fast_batches", "fast_surfaces", "fast_vertices", "fast_triangles", "copytriangles_batches", "copytriangles_surfaces", "copytriangles_vertices", "copytriangles_triangles", "dynamic_batches", "dynamic_surfaces", "dynamic_vertices", "dynamic_triangles", "dynamicskeletal_batches", "dynamicskeletal_surfaces", "dynamicskeletal_vertices", "dynamicskeletal_triangles", "dynamic_batches_because_cvar", "dynamic_surfaces_because_cvar", "dynamic_vertices_because_cvar", "dynamic_triangles_because_cvar", "dynamic_batches_because_lightmapvertex", "dynamic_surfaces_because_lightmapvertex", "dynamic_vertices_because_lightmapvertex", "dynamic_triangles_because_lightmapvertex", "dynamic_batches_because_deformvertexes_autosprite", "dynamic_surfaces_because_deformvertexes_autosprite", "dynamic_vertices_because_deformvertexes_autosprite", "dynamic_triangles_because_deformvertexes_autosprite", "dynamic_batches_because_deformvertexes_autosprite2", "dynamic_surfaces_because_deformvertexes_autosprite2", "dynamic_vertices_because_deformvertexes_autosprite2", "dynamic_triangles_because_deformvertexes_autosprite2", "dynamic_batches_because_deformvertexes_normal", "dynamic_surfaces_because_deformvertexes_normal", "dynamic_vertices_because_deformvertexes_normal", "dynamic_triangles_because_deformvertexes_normal", "dynamic_batches_because_deformvertexes_wave", "dynamic_surfaces_because_deformvertexes_wave", "dynamic_vertices_because_deformvertexes_wave", "dynamic_triangles_because_deformvertexes_wave", "dynamic_batches_because_deformvertexes_bulge", "dynamic_surfaces_because_deformvertexes_bulge", "dynamic_vertices_because_deformvertexes_bulge", "dynamic_triangles_because_deformvertexes_bulge", "dynamic_batches_because_deformvertexes_move", "dynamic_surfaces_because_deformvertexes_move", "dynamic_vertices_because_deformvertexes_move", "dynamic_triangles_because_deformvertexes_move", "dynamic_batches_because_tcgen_lightmap", "dynamic_surfaces_because_tcgen_lightmap", "dynamic_vertices_because_tcgen_lightmap", "dynamic_triangles_because_tcgen_lightmap", "dynamic_batches_because_tcgen_vector", "dynamic_surfaces_because_tcgen_vector", "dynamic_vertices_because_tcgen_vector", "dynamic_triangles_because_tcgen_vector", "dynamic_batches_because_tcgen_environment", "dynamic_surfaces_because_tcgen_environment", "dynamic_vertices_because_tcgen_environment", "dynamic_triangles_because_tcgen_environment", "dynamic_batches_because_tcmod_turbulent", "dynamic_surfaces_because_tcmod_turbulent", "dynamic_vertices_because_tcmod_turbulent", "dynamic_triangles_because_tcmod_turbulent", "dynamic_batches_because_interleavedarrays", "dynamic_surfaces_because_interleavedarrays", "dynamic_vertices_because_interleavedarrays", "dynamic_triangles_because_interleavedarrays", "dynamic_batches_because_nogaps", "dynamic_surfaces_because_nogaps", "dynamic_vertices_because_nogaps", "dynamic_triangles_because_nogaps", "dynamic_batches_because_derived", "dynamic_surfaces_because_derived", "dynamic_vertices_because_derived", "dynamic_triangles_because_derived", "entitycache_count", "entitycache_surfaces", "entitycache_vertices", "entitycache_triangles", "entityanimate_count", "entityanimate_surfaces", "entityanimate_vertices", "entityanimate_triangles", "entityskeletal_count", "entityskeletal_surfaces", "entityskeletal_vertices", "entityskeletal_triangles", "entitystatic_count", "entitystatic_surfaces", "entitystatic_vertices", "entitystatic_triangles", "entitycustom_count", "entitycustom_surfaces", "entitycustom_vertices", "entitycustom_triangles", }; char r_speeds_timestring[4096]; int speedstringcount, r_timereport_active; double r_timereport_temp = 0, r_timereport_current = 0, r_timereport_start = 0; int r_speeds_longestitem = 0; void R_TimeReport(const char *desc) { char tempbuf[256]; int length; int t; if (r_speeds.integer < 2 || !r_timereport_active) return; CHECKGLERROR if (r_speeds.integer == 2) GL_Finish(); CHECKGLERROR r_timereport_temp = r_timereport_current; r_timereport_current = Sys_DirtyTime(); t = (int) ((r_timereport_current - r_timereport_temp) * 1000000.0 + 0.5); length = dpsnprintf(tempbuf, sizeof(tempbuf), "%8i %s", t, desc); if (length < 0) length = (int)sizeof(tempbuf) - 1; if (r_speeds_longestitem < length) r_speeds_longestitem = length; for (;length < r_speeds_longestitem;length++) tempbuf[length] = ' '; tempbuf[length] = 0; if (speedstringcount + length > (vid_conwidth.integer / 8)) { strlcat(r_speeds_timestring, "\n", sizeof(r_speeds_timestring)); speedstringcount = 0; } strlcat(r_speeds_timestring, tempbuf, sizeof(r_speeds_timestring)); speedstringcount += length; } static void R_TimeReport_BeginFrame(void) { speedstringcount = 0; r_speeds_timestring[0] = 0; r_timereport_active = false; memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); if (r_speeds.integer >= 2) { r_timereport_active = true; r_timereport_start = r_timereport_current = Sys_DirtyTime(); } } static int R_CountLeafTriangles(const dp_model_t *model, const mleaf_t *leaf) { int i, triangles = 0; for (i = 0;i < leaf->numleafsurfaces;i++) triangles += model->data_surfaces[leaf->firstleafsurface[i]].num_triangles; return triangles; } #define R_SPEEDS_GRAPH_COLORS 8 #define R_SPEEDS_GRAPH_TEXTLENGTH 64 static float r_speeds_graph_colors[R_SPEEDS_GRAPH_COLORS][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}, {0, 1, 1, 1}, {1, 0, 1, 1}, {1, 1, 1, 1}, {1, 0.5f, 0, 1}}; extern cvar_t r_viewscale; extern float viewscalefpsadjusted; static void R_TimeReport_EndFrame(void) { int j, lines; cl_locnode_t *loc; char string[1024+4096]; mleaf_t *viewleaf; static double oldtime = 0; r_refdef.stats[r_stat_timedelta] = (int)((realtime - oldtime) * 1000000.0); oldtime = realtime; r_refdef.stats[r_stat_quality] = (int)(100 * r_refdef.view.quality); string[0] = 0; if (r_speeds.integer) { // put the location name in the r_speeds display as it greatly helps // when creating loc files loc = CL_Locs_FindNearest(cl.movement_origin); viewleaf = (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) ? r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, r_refdef.view.origin) : NULL; dpsnprintf(string, sizeof(string), "%6ius time delta %s%s %.3f cl.time%2.4f brightness\n" "%3i renders org:'%+8.2f %+8.2f %+8.2f' dir:'%+2.3f %+2.3f %+2.3f'\n" "%5i viewleaf%5i cluster%3i area%4i brushes%4i surfaces(%7i triangles)\n" "%7i surfaces%7i triangles %5i entities (%7i surfaces%7i triangles)\n" "%5i leafs%5i portals%6i/%6i particles%6i/%6i decals %3i%% quality\n" "%7i lightmap updates (%7i pixels)%8i/%8i framedata\n" "%4i lights%4i clears%4i scissored%7i light%7i shadow%7i dynamic\n" "bouncegrid:%4i lights%6i particles%6i traces%6i hits%6i splats%6i bounces\n" "photon cache efficiency:%6i cached%6i traced%6ianimated\n" "%6i draws%8i vertices%8i triangles bloompixels%8i copied%8i drawn\n" "updated%5i indexbuffers%8i bytes%5i vertexbuffers%8i bytes\n" "animcache%5ib gpuskeletal%7i vertices (%7i with normals)\n" "fastbatch%5i count%5i surfaces%7i vertices %7i triangles\n" "copytris%5i count%5i surfaces%7i vertices %7i triangles\n" "dynamic%5i count%5i surfaces%7i vertices%7i triangles\n" "%s" , r_refdef.stats[r_stat_timedelta], loc ? "Location: " : "", loc ? loc->name : "", cl.time, r_refdef.view.colorscale , r_refdef.stats[r_stat_renders], r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], r_refdef.view.forward[0], r_refdef.view.forward[1], r_refdef.view.forward[2] , viewleaf ? (int)(viewleaf - r_refdef.scene.worldmodel->brush.data_leafs) : -1, viewleaf ? viewleaf->clusterindex : -1, viewleaf ? viewleaf->areaindex : -1, viewleaf ? viewleaf->numleafbrushes : 0, viewleaf ? viewleaf->numleafsurfaces : 0, viewleaf ? R_CountLeafTriangles(r_refdef.scene.worldmodel, viewleaf) : 0 , r_refdef.stats[r_stat_world_surfaces], r_refdef.stats[r_stat_world_triangles], r_refdef.stats[r_stat_entities], r_refdef.stats[r_stat_entities_surfaces], r_refdef.stats[r_stat_entities_triangles] , r_refdef.stats[r_stat_world_leafs], r_refdef.stats[r_stat_world_portals], r_refdef.stats[r_stat_particles], cl.num_particles, r_refdef.stats[r_stat_drawndecals], r_refdef.stats[r_stat_totaldecals], r_refdef.stats[r_stat_quality] , r_refdef.stats[r_stat_lightmapupdates], r_refdef.stats[r_stat_lightmapupdatepixels], r_refdef.stats[r_stat_framedatacurrent], r_refdef.stats[r_stat_framedatasize] , r_refdef.stats[r_stat_lights], r_refdef.stats[r_stat_lights_clears], r_refdef.stats[r_stat_lights_scissored], r_refdef.stats[r_stat_lights_lighttriangles], r_refdef.stats[r_stat_lights_shadowtriangles], r_refdef.stats[r_stat_lights_dynamicshadowtriangles] , r_refdef.stats[r_stat_bouncegrid_lights], r_refdef.stats[r_stat_bouncegrid_particles], r_refdef.stats[r_stat_bouncegrid_traces], r_refdef.stats[r_stat_bouncegrid_hits], r_refdef.stats[r_stat_bouncegrid_splats], r_refdef.stats[r_stat_bouncegrid_bounces] , r_refdef.stats[r_stat_photoncache_cached], r_refdef.stats[r_stat_photoncache_traced], r_refdef.stats[r_stat_photoncache_animated] , r_refdef.stats[r_stat_draws], r_refdef.stats[r_stat_draws_vertices], r_refdef.stats[r_stat_draws_elements] / 3, r_refdef.stats[r_stat_bloom_copypixels], r_refdef.stats[r_stat_bloom_drawpixels] , r_refdef.stats[r_stat_indexbufferuploadcount], r_refdef.stats[r_stat_indexbufferuploadsize], r_refdef.stats[r_stat_vertexbufferuploadcount], r_refdef.stats[r_stat_vertexbufferuploadsize] , r_refdef.stats[r_stat_animcache_skeletal_bones], r_refdef.stats[r_stat_animcache_shape_vertices], r_refdef.stats[r_stat_animcache_shade_vertices] , r_refdef.stats[r_stat_batch_fast_batches], r_refdef.stats[r_stat_batch_fast_surfaces], r_refdef.stats[r_stat_batch_fast_vertices], r_refdef.stats[r_stat_batch_fast_triangles] , r_refdef.stats[r_stat_batch_copytriangles_batches], r_refdef.stats[r_stat_batch_copytriangles_surfaces], r_refdef.stats[r_stat_batch_copytriangles_vertices], r_refdef.stats[r_stat_batch_copytriangles_triangles] , r_refdef.stats[r_stat_batch_dynamic_batches], r_refdef.stats[r_stat_batch_dynamic_surfaces], r_refdef.stats[r_stat_batch_dynamic_vertices], r_refdef.stats[r_stat_batch_dynamic_triangles] , r_speeds_timestring); } speedstringcount = 0; r_speeds_timestring[0] = 0; r_timereport_active = false; if (r_speeds.integer >= 2) { r_timereport_active = true; r_timereport_start = r_timereport_current = Sys_DirtyTime(); } if (string[0]) { int i, y; if (string[strlen(string)-1] == '\n') string[strlen(string)-1] = 0; lines = 1; for (i = 0;string[i];i++) if (string[i] == '\n') lines++; y = vid_conheight.integer - sb_lines - lines * 8; i = j = 0; r_draw2d_force = true; DrawQ_Fill(0, y, vid_conwidth.integer, lines * 8, 0, 0, 0, 0.5, 0); while (string[i]) { j = i; while (string[i] && string[i] != '\n') i++; if (i - j > 0) DrawQ_String(0, y, string + j, i - j, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT); if (string[i] == '\n') i++; y += 8; } r_draw2d_force = false; } if (r_speeds_graph_length.integer != bound(4, r_speeds_graph_length.integer, 8192)) Cvar_SetValueQuick(&r_speeds_graph_length, bound(4, r_speeds_graph_length.integer, 8192)); if (fabs(r_speeds_graph_seconds.value - bound(0.1f, r_speeds_graph_seconds.value, 120.0f)) > 0.01f) Cvar_SetValueQuick(&r_speeds_graph_seconds, bound(0.1f, r_speeds_graph_seconds.value, 120.0f)); if (r_speeds_graph.integer) { // if we currently have no graph data, reset the graph data entirely int i; if (!cls.r_speeds_graph_data) for (i = 0;i < r_stat_count;i++) cls.r_speeds_graph_datamin[i] = cls.r_speeds_graph_datamax[i] = 0; if (cls.r_speeds_graph_length != r_speeds_graph_length.integer) { int stat, index, d, graph_length, *graph_data; cls.r_speeds_graph_length = r_speeds_graph_length.integer; cls.r_speeds_graph_current = 0; if (cls.r_speeds_graph_data) Mem_Free(cls.r_speeds_graph_data); cls.r_speeds_graph_data = (int *)Mem_Alloc(cls.permanentmempool, cls.r_speeds_graph_length * sizeof(r_refdef.stats)); // initialize the graph to have the current values throughout history graph_data = cls.r_speeds_graph_data; graph_length = cls.r_speeds_graph_length; index = 0; for (stat = 0;stat < r_stat_count;stat++) { d = r_refdef.stats[stat]; if (stat == r_stat_timedelta) d = 0; for (i = 0;i < graph_length;i++) graph_data[index++] = d; } } } else { if (cls.r_speeds_graph_length) { cls.r_speeds_graph_length = 0; Mem_Free(cls.r_speeds_graph_data); cls.r_speeds_graph_data = NULL; cls.r_speeds_graph_current = 0; } } if (cls.r_speeds_graph_length) { char legend[128]; r_vertexgeneric_t *v; int i, numlines; const int *data; float x, y, width, height, scalex, scaley; int range_default = max(r_speeds_graph_maxdefault.integer, 1); int color, stat, stats, index, range_min, range_max; int graph_current, graph_length, *graph_data; int statindex[R_SPEEDS_GRAPH_COLORS]; int sum; // add current stats to the graph_data cls.r_speeds_graph_current++; if (cls.r_speeds_graph_current >= cls.r_speeds_graph_length) cls.r_speeds_graph_current = 0; // poke each new stat into the current offset of its graph graph_data = cls.r_speeds_graph_data; graph_current = cls.r_speeds_graph_current; graph_length = cls.r_speeds_graph_length; for (stat = 0;stat < r_stat_count;stat++) graph_data[stat * graph_length + graph_current] = r_refdef.stats[stat]; // update the graph ranges for (stat = 0;stat < r_stat_count;stat++) { if (cls.r_speeds_graph_datamin[stat] > r_refdef.stats[stat]) cls.r_speeds_graph_datamin[stat] = r_refdef.stats[stat]; if (cls.r_speeds_graph_datamax[stat] < r_refdef.stats[stat]) cls.r_speeds_graph_datamax[stat] = r_refdef.stats[stat]; } // force 2D drawing to occur even if r_render is 0 r_draw2d_force = true; // position the graph width = r_speeds_graph_width.value; height = r_speeds_graph_height.value; x = bound(0, r_speeds_graph_x.value, vid_conwidth.value - width); y = bound(0, r_speeds_graph_y.value, vid_conheight.value - height); // fill background with a pattern of gray and black at one second intervals scalex = (float)width / (float)r_speeds_graph_seconds.value; for (i = 0;i < r_speeds_graph_seconds.integer + 1;i++) { float x1 = x + width - (i + 1) * scalex; float x2 = x + width - i * scalex; if (x1 < x) x1 = x; if (i & 1) DrawQ_Fill(x1, y, x2 - x1, height, 0.0f, 0.0f, 0.0f, 0.5f, 0); else DrawQ_Fill(x1, y, x2 - x1, height, 0.2f, 0.2f, 0.2f, 0.5f, 0); } // count how many stats match our pattern stats = 0; color = 0; for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++) { // look at all stat names and find ones matching the filter statindex[color] = -1; if (!r_speeds_graph_filter[color].string) continue; for (stat = 0;stat < r_stat_count;stat++) if (!strcmp(r_stat_name[stat], r_speeds_graph_filter[color].string)) break; if (stat >= r_stat_count) continue; // record that this color is this stat for the line drawing loop statindex[color] = stat; // draw the legend text in the background of the graph dpsnprintf(legend, sizeof(legend), "%10i :%s", graph_data[stat * graph_length + graph_current], r_stat_name[stat]); DrawQ_String(x, y + stats * 8, legend, 0, 8, 8, r_speeds_graph_colors[color][0], r_speeds_graph_colors[color][1], r_speeds_graph_colors[color][2], r_speeds_graph_colors[color][3] * 1.00f, 0, NULL, true, FONT_DEFAULT); // count how many stats we need to graph in vertex buffer stats++; } if (stats) { // legend text is drawn after the graphs // render the graph lines, we'll go back and render the legend text later scalex = (float)width / (1000000.0 * r_speeds_graph_seconds.value); // get space in a vertex buffer to draw this numlines = stats * (graph_length - 1); v = R_Mesh_PrepareVertices_Generic_Lock(numlines * 2); stats = 0; for (color = 0;color < R_SPEEDS_GRAPH_COLORS;color++) { // look at all stat names and find ones matching the filter stat = statindex[color]; if (stat < 0) continue; // prefer to graph stats with 0 base, but if they are // negative we have no choice range_min = cls.r_speeds_graph_datamin[stat]; range_max = max(cls.r_speeds_graph_datamax[stat], range_min + range_default); // some stats we specifically override the graph scale on if (stat == r_stat_timedelta) range_max = r_speeds_graph_maxtimedelta.integer; scaley = height / (range_max - range_min); // generate lines (2 vertices each) // to deal with incomplete data we walk right to left data = graph_data + stat * graph_length; index = graph_current; sum = 0; for (i = 0;i < graph_length - 1;) { v->vertex3f[0] = x + width - sum * scalex; if (v->vertex3f[0] < x) v->vertex3f[0] = x; v->vertex3f[1] = y + height - (data[index] - range_min) * scaley; v->vertex3f[2] = 0; v->color4f[0] = r_speeds_graph_colors[color][0]; v->color4f[1] = r_speeds_graph_colors[color][1]; v->color4f[2] = r_speeds_graph_colors[color][2]; v->color4f[3] = r_speeds_graph_colors[color][3]; v->texcoord2f[0] = 0; v->texcoord2f[1] = 0; v++; sum += graph_data[r_stat_timedelta * graph_length + index]; index--; if (index < 0) index = graph_length - 1; i++; v->vertex3f[0] = x + width - sum * scalex; if (v->vertex3f[0] < x) v->vertex3f[0] = x; v->vertex3f[1] = y + height - (data[index] - range_min) * scaley; v->vertex3f[2] = 0; v->color4f[0] = r_speeds_graph_colors[color][0]; v->color4f[1] = r_speeds_graph_colors[color][1]; v->color4f[2] = r_speeds_graph_colors[color][2]; v->color4f[3] = r_speeds_graph_colors[color][3]; v->texcoord2f[0] = 0; v->texcoord2f[1] = 0; v++; } } R_Mesh_PrepareVertices_Generic_Unlock(); DrawQ_Lines(0.0f, numlines, 0, false); } // return to not drawing anything if r_render is 0 r_draw2d_force = false; } memset(&r_refdef.stats, 0, sizeof(r_refdef.stats)); } /* ================= SCR_SizeUp_f Keybinding command ================= */ static void SCR_SizeUp_f (void) { Cvar_SetValue ("viewsize",scr_viewsize.value+10); } /* ================= SCR_SizeDown_f Keybinding command ================= */ static void SCR_SizeDown_f (void) { Cvar_SetValue ("viewsize",scr_viewsize.value-10); } #ifdef CONFIG_VIDEO_CAPTURE void SCR_CaptureVideo_EndVideo(void); #endif void CL_Screen_Shutdown(void) { #ifdef CONFIG_VIDEO_CAPTURE SCR_CaptureVideo_EndVideo(); #endif } void CL_Screen_Init(void) { int i; Cvar_RegisterVariable (&scr_fov); Cvar_RegisterVariable (&scr_viewsize); Cvar_RegisterVariable (&scr_conalpha); Cvar_RegisterVariable (&scr_conalphafactor); Cvar_RegisterVariable (&scr_conalpha2factor); Cvar_RegisterVariable (&scr_conalpha3factor); Cvar_RegisterVariable (&scr_conscroll_x); Cvar_RegisterVariable (&scr_conscroll_y); Cvar_RegisterVariable (&scr_conscroll2_x); Cvar_RegisterVariable (&scr_conscroll2_y); Cvar_RegisterVariable (&scr_conscroll3_x); Cvar_RegisterVariable (&scr_conscroll3_y); Cvar_RegisterVariable (&scr_conbrightness); Cvar_RegisterVariable (&scr_conforcewhiledisconnected); #ifdef CONFIG_MENU Cvar_RegisterVariable (&scr_menuforcewhiledisconnected); #endif Cvar_RegisterVariable (&scr_loadingscreen_background); Cvar_RegisterVariable (&scr_loadingscreen_scale); Cvar_RegisterVariable (&scr_loadingscreen_scale_base); Cvar_RegisterVariable (&scr_loadingscreen_scale_limit); Cvar_RegisterVariable (&scr_loadingscreen_picture); Cvar_RegisterVariable (&scr_loadingscreen_count); Cvar_RegisterVariable (&scr_loadingscreen_firstforstartup); Cvar_RegisterVariable (&scr_loadingscreen_barcolor); Cvar_RegisterVariable (&scr_loadingscreen_barheight); Cvar_RegisterVariable (&scr_loadingscreen_maxfps); Cvar_RegisterVariable (&scr_infobar_height); Cvar_RegisterVariable (&scr_showram); Cvar_RegisterVariable (&scr_showturtle); Cvar_RegisterVariable (&scr_showpause); Cvar_RegisterVariable (&scr_showbrand); Cvar_RegisterVariable (&scr_centertime); Cvar_RegisterVariable (&scr_printspeed); Cvar_RegisterVariable (&vid_conwidth); Cvar_RegisterVariable (&vid_conheight); Cvar_RegisterVariable (&vid_pixelheight); Cvar_RegisterVariable (&scr_screenshot_jpeg); Cvar_RegisterVariable (&scr_screenshot_jpeg_quality); Cvar_RegisterVariable (&scr_screenshot_png); Cvar_RegisterVariable (&scr_screenshot_gammaboost); Cvar_RegisterVariable (&scr_screenshot_hwgamma); Cvar_RegisterVariable (&scr_screenshot_name_in_mapdir); Cvar_RegisterVariable (&scr_screenshot_alpha); Cvar_RegisterVariable (&scr_screenshot_timestamp); #ifdef CONFIG_VIDEO_CAPTURE Cvar_RegisterVariable (&cl_capturevideo); Cvar_RegisterVariable (&cl_capturevideo_demo_stop); Cvar_RegisterVariable (&cl_capturevideo_printfps); Cvar_RegisterVariable (&cl_capturevideo_width); Cvar_RegisterVariable (&cl_capturevideo_height); Cvar_RegisterVariable (&cl_capturevideo_realtime); Cvar_RegisterVariable (&cl_capturevideo_fps); Cvar_RegisterVariable (&cl_capturevideo_nameformat); Cvar_RegisterVariable (&cl_capturevideo_number); Cvar_RegisterVariable (&cl_capturevideo_ogg); Cvar_RegisterVariable (&cl_capturevideo_framestep); #endif Cvar_RegisterVariable (&r_letterbox); Cvar_RegisterVariable(&r_stereo_separation); Cvar_RegisterVariable(&r_stereo_sidebyside); Cvar_RegisterVariable(&r_stereo_horizontal); Cvar_RegisterVariable(&r_stereo_vertical); Cvar_RegisterVariable(&r_stereo_redblue); Cvar_RegisterVariable(&r_stereo_redcyan); Cvar_RegisterVariable(&r_stereo_redgreen); Cvar_RegisterVariable(&r_stereo_angle); Cvar_RegisterVariable(&scr_stipple); Cvar_RegisterVariable(&scr_refresh); Cvar_RegisterVariable(&shownetgraph); Cvar_RegisterVariable(&cl_demo_mousegrab); Cvar_RegisterVariable(&timedemo_screenshotframelist); Cvar_RegisterVariable(&vid_touchscreen_outlinealpha); Cvar_RegisterVariable(&vid_touchscreen_overlayalpha); Cvar_RegisterVariable(&r_speeds_graph); for (i = 0;i < (int)(sizeof(r_speeds_graph_filter)/sizeof(r_speeds_graph_filter[0]));i++) Cvar_RegisterVariable(&r_speeds_graph_filter[i]); Cvar_RegisterVariable(&r_speeds_graph_length); Cvar_RegisterVariable(&r_speeds_graph_seconds); Cvar_RegisterVariable(&r_speeds_graph_x); Cvar_RegisterVariable(&r_speeds_graph_y); Cvar_RegisterVariable(&r_speeds_graph_width); Cvar_RegisterVariable(&r_speeds_graph_height); Cvar_RegisterVariable(&r_speeds_graph_maxtimedelta); Cvar_RegisterVariable(&r_speeds_graph_maxdefault); // if we want no console, turn it off here too if (COM_CheckParm ("-noconsole")) Cvar_SetQuick(&scr_conforcewhiledisconnected, "0"); Cmd_AddCommand ("sizeup",SCR_SizeUp_f, "increase view size (increases viewsize cvar)"); Cmd_AddCommand ("sizedown",SCR_SizeDown_f, "decrease view size (decreases viewsize cvar)"); Cmd_AddCommand ("screenshot",SCR_ScreenShot_f, "takes a screenshot of the next rendered frame"); Cmd_AddCommand ("envmap", R_Envmap_f, "render a cubemap (skybox) of the current scene"); Cmd_AddCommand ("infobar", SCR_InfoBar_f, "display a text in the infobar (usage: infobar expiretime string)"); #ifdef CONFIG_VIDEO_CAPTURE SCR_CaptureVideo_Ogg_Init(); #endif scr_initialized = true; } /* ================== SCR_ScreenShot_f ================== */ void SCR_ScreenShot_f (void) { static int shotnumber; static char old_prefix_name[MAX_QPATH]; char prefix_name[MAX_QPATH]; char filename[MAX_QPATH]; unsigned char *buffer1; unsigned char *buffer2; qboolean jpeg = (scr_screenshot_jpeg.integer != 0); qboolean png = (scr_screenshot_png.integer != 0) && !jpeg; char vabuf[1024]; if (Cmd_Argc() == 2) { const char *ext; strlcpy(filename, Cmd_Argv(1), sizeof(filename)); ext = FS_FileExtension(filename); if (!strcasecmp(ext, "jpg")) { jpeg = true; png = false; } else if (!strcasecmp(ext, "tga")) { jpeg = false; png = false; } else if (!strcasecmp(ext, "png")) { jpeg = false; png = true; } else { Con_Printf("screenshot: supplied filename must end in .jpg or .tga or .png\n"); return; } } else if (scr_screenshot_timestamp.integer) { int shotnumber100; // TODO maybe make capturevideo and screenshot use similar name patterns? if (scr_screenshot_name_in_mapdir.integer && cl.worldbasename[0]) dpsnprintf(prefix_name, sizeof(prefix_name), "%s/%s%s", cl.worldbasename, scr_screenshot_name.string, Sys_TimeString("%Y%m%d%H%M%S")); else dpsnprintf(prefix_name, sizeof(prefix_name), "%s%s", scr_screenshot_name.string, Sys_TimeString("%Y%m%d%H%M%S")); // find a file name to save it to for (shotnumber100 = 0;shotnumber100 < 100;shotnumber100++) if (!FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.tga", fs_gamedir, prefix_name, shotnumber100)) && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.jpg", fs_gamedir, prefix_name, shotnumber100)) && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s-%02d.png", fs_gamedir, prefix_name, shotnumber100))) break; if (shotnumber100 >= 100) { Con_Print("Couldn't create the image file - already 100 shots taken this second!\n"); return; } dpsnprintf(filename, sizeof(filename), "screenshots/%s-%02d.%s", prefix_name, shotnumber100, jpeg ? "jpg" : png ? "png" : "tga"); } else { // TODO maybe make capturevideo and screenshot use similar name patterns? if (scr_screenshot_name_in_mapdir.integer && cl.worldbasename[0]) dpsnprintf(prefix_name, sizeof(prefix_name), "%s/%s", cl.worldbasename, Sys_TimeString(scr_screenshot_name.string)); else dpsnprintf(prefix_name, sizeof(prefix_name), "%s", Sys_TimeString(scr_screenshot_name.string)); // if prefix changed, gamedir or map changed, reset the shotnumber so // we scan again // FIXME: should probably do this whenever FS_Rescan or something like that occurs? if (strcmp(old_prefix_name, prefix_name)) { dpsnprintf(old_prefix_name, sizeof(old_prefix_name), "%s", prefix_name ); shotnumber = 0; } // find a file name to save it to for (;shotnumber < 1000000;shotnumber++) if (!FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.tga", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.jpg", fs_gamedir, prefix_name, shotnumber)) && !FS_SysFileExists(va(vabuf, sizeof(vabuf), "%s/screenshots/%s%06d.png", fs_gamedir, prefix_name, shotnumber))) break; if (shotnumber >= 1000000) { Con_Print("Couldn't create the image file - you already have 1000000 screenshots!\n"); return; } dpsnprintf(filename, sizeof(filename), "screenshots/%s%06d.%s", prefix_name, shotnumber, jpeg ? "jpg" : png ? "png" : "tga"); shotnumber++; } buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * (scr_screenshot_alpha.integer ? 4 : 3)); if (SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, jpeg, png, true, scr_screenshot_alpha.integer != 0)) Con_Printf("Wrote %s\n", filename); else { Con_Printf("Unable to write %s\n", filename); if(jpeg || png) { if(SCR_ScreenShot (filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, scr_screenshot_alpha.integer != 0)) { strlcpy(filename + strlen(filename) - 3, "tga", 4); Con_Printf("Wrote %s\n", filename); } } } Mem_Free (buffer1); Mem_Free (buffer2); } #ifdef CONFIG_VIDEO_CAPTURE static void SCR_CaptureVideo_BeginVideo(void) { double r, g, b; unsigned int i; int width = cl_capturevideo_width.integer, height = cl_capturevideo_height.integer; if (cls.capturevideo.active) return; memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); // soundrate is figured out on the first SoundFrame if(width == 0 && height != 0) width = (int) (height * (double)vid.width / ((double)vid.height * vid_pixelheight.value)); // keep aspect if(width != 0 && height == 0) height = (int) (width * ((double)vid.height * vid_pixelheight.value) / (double)vid.width); // keep aspect if(width < 2 || width > vid.width) // can't scale up width = vid.width; if(height < 2 || height > vid.height) // can't scale up height = vid.height; // ensure it's all even; if not, scale down a little if(width % 1) --width; if(height % 1) --height; cls.capturevideo.width = width; cls.capturevideo.height = height; cls.capturevideo.active = true; cls.capturevideo.framerate = bound(1, cl_capturevideo_fps.value, 1001) * bound(1, cl_capturevideo_framestep.integer, 64); cls.capturevideo.framestep = cl_capturevideo_framestep.integer; cls.capturevideo.soundrate = S_GetSoundRate(); cls.capturevideo.soundchannels = S_GetSoundChannels(); cls.capturevideo.startrealtime = realtime; cls.capturevideo.frame = cls.capturevideo.lastfpsframe = 0; cls.capturevideo.starttime = cls.capturevideo.lastfpstime = realtime; cls.capturevideo.soundsampleframe = 0; cls.capturevideo.realtime = cl_capturevideo_realtime.integer != 0; cls.capturevideo.screenbuffer = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); cls.capturevideo.outbuffer = (unsigned char *)Mem_Alloc(tempmempool, width * height * (4+4) + 18); dpsnprintf(cls.capturevideo.basename, sizeof(cls.capturevideo.basename), "video/%s%03i", Sys_TimeString(cl_capturevideo_nameformat.string), cl_capturevideo_number.integer); Cvar_SetValueQuick(&cl_capturevideo_number, cl_capturevideo_number.integer + 1); /* for (i = 0;i < 256;i++) { unsigned char j = (unsigned char)bound(0, 255*pow(i/255.0, gamma), 255); cls.capturevideo.rgbgammatable[0][i] = j; cls.capturevideo.rgbgammatable[1][i] = j; cls.capturevideo.rgbgammatable[2][i] = j; } */ /* R = Y + 1.4075 * (Cr - 128); G = Y + -0.3455 * (Cb - 128) + -0.7169 * (Cr - 128); B = Y + 1.7790 * (Cb - 128); Y = R * .299 + G * .587 + B * .114; Cb = R * -.169 + G * -.332 + B * .500 + 128.; Cr = R * .500 + G * -.419 + B * -.0813 + 128.; */ if(WANT_SCREENSHOT_HWGAMMA) { VID_BuildGammaTables(&cls.capturevideo.vidramp[0], 256); } else { // identity gamma table BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp, 256); BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256, 256); BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, cls.capturevideo.vidramp + 256*2, 256); } if(scr_screenshot_gammaboost.value != 1) { double igamma = 1 / scr_screenshot_gammaboost.value; for (i = 0;i < 256 * 3;i++) cls.capturevideo.vidramp[i] = (unsigned short) (0.5 + pow(cls.capturevideo.vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); } for (i = 0;i < 256;i++) { r = 255*cls.capturevideo.vidramp[i]/65535.0; g = 255*cls.capturevideo.vidramp[i+256]/65535.0; b = 255*cls.capturevideo.vidramp[i+512]/65535.0; // NOTE: we have to round DOWN here, or integer overflows happen. Sorry for slightly wrong looking colors sometimes... // Y weights from RGB cls.capturevideo.rgbtoyuvscaletable[0][0][i] = (short)(r * 0.299); cls.capturevideo.rgbtoyuvscaletable[0][1][i] = (short)(g * 0.587); cls.capturevideo.rgbtoyuvscaletable[0][2][i] = (short)(b * 0.114); // Cb weights from RGB cls.capturevideo.rgbtoyuvscaletable[1][0][i] = (short)(r * -0.169); cls.capturevideo.rgbtoyuvscaletable[1][1][i] = (short)(g * -0.332); cls.capturevideo.rgbtoyuvscaletable[1][2][i] = (short)(b * 0.500); // Cr weights from RGB cls.capturevideo.rgbtoyuvscaletable[2][0][i] = (short)(r * 0.500); cls.capturevideo.rgbtoyuvscaletable[2][1][i] = (short)(g * -0.419); cls.capturevideo.rgbtoyuvscaletable[2][2][i] = (short)(b * -0.0813); // range reduction of YCbCr to valid signal range cls.capturevideo.yuvnormalizetable[0][i] = 16 + i * (236-16) / 256; cls.capturevideo.yuvnormalizetable[1][i] = 16 + i * (240-16) / 256; cls.capturevideo.yuvnormalizetable[2][i] = 16 + i * (240-16) / 256; } if (cl_capturevideo_ogg.integer) { if(SCR_CaptureVideo_Ogg_Available()) { SCR_CaptureVideo_Ogg_BeginVideo(); return; } else Con_Print("cl_capturevideo_ogg: libraries not available. Capturing in AVI instead.\n"); } SCR_CaptureVideo_Avi_BeginVideo(); } void SCR_CaptureVideo_EndVideo(void) { if (!cls.capturevideo.active) return; cls.capturevideo.active = false; Con_Printf("Finishing capture of %s.%s (%d frames, %d audio frames)\n", cls.capturevideo.basename, cls.capturevideo.formatextension, cls.capturevideo.frame, cls.capturevideo.soundsampleframe); if (cls.capturevideo.videofile) { cls.capturevideo.endvideo(); } if (cls.capturevideo.screenbuffer) { Mem_Free (cls.capturevideo.screenbuffer); cls.capturevideo.screenbuffer = NULL; } if (cls.capturevideo.outbuffer) { Mem_Free (cls.capturevideo.outbuffer); cls.capturevideo.outbuffer = NULL; } memset(&cls.capturevideo, 0, sizeof(cls.capturevideo)); } static void SCR_ScaleDownBGRA(unsigned char *in, int inw, int inh, unsigned char *out, int outw, int outh) { // TODO optimize this function int x, y; float area; // memcpy is faster than me if(inw == outw && inh == outh) { memcpy(out, in, 4 * inw * inh); return; } // otherwise: a box filter area = (float)outw * (float)outh / (float)inw / (float)inh; for(y = 0; y < outh; ++y) { float iny0 = y / (float)outh * inh; int iny0_i = (int) floor(iny0); float iny1 = (y+1) / (float)outh * inh; int iny1_i = (int) ceil(iny1); for(x = 0; x < outw; ++x) { float inx0 = x / (float)outw * inw; int inx0_i = (int) floor(inx0); float inx1 = (x+1) / (float)outw * inw; int inx1_i = (int) ceil(inx1); float r = 0, g = 0, b = 0, alpha = 0; int xx, yy; for(yy = iny0_i; yy < iny1_i; ++yy) { float ya = min(yy+1, iny1) - max(iny0, yy); for(xx = inx0_i; xx < inx1_i; ++xx) { float a = ya * (min(xx+1, inx1) - max(inx0, xx)); r += a * in[4*(xx + inw * yy)+0]; g += a * in[4*(xx + inw * yy)+1]; b += a * in[4*(xx + inw * yy)+2]; alpha += a * in[4*(xx + inw * yy)+3]; } } out[4*(x + outw * y)+0] = (unsigned char) (r * area); out[4*(x + outw * y)+1] = (unsigned char) (g * area); out[4*(x + outw * y)+2] = (unsigned char) (b * area); out[4*(x + outw * y)+3] = (unsigned char) (alpha * area); } } } static void SCR_CaptureVideo_VideoFrame(int newframestepframenum) { int x = 0, y = 0; int width = cls.capturevideo.width, height = cls.capturevideo.height; if(newframestepframenum == cls.capturevideo.framestepframe) return; CHECKGLERROR // speed is critical here, so do saving as directly as possible GL_ReadPixelsBGRA(x, y, vid.width, vid.height, cls.capturevideo.screenbuffer); SCR_ScaleDownBGRA (cls.capturevideo.screenbuffer, vid.width, vid.height, cls.capturevideo.outbuffer, width, height); cls.capturevideo.videoframes(newframestepframenum - cls.capturevideo.framestepframe); cls.capturevideo.framestepframe = newframestepframenum; if(cl_capturevideo_printfps.integer) { char buf[80]; double t = realtime; if(t > cls.capturevideo.lastfpstime + 1) { double fps1 = (cls.capturevideo.frame - cls.capturevideo.lastfpsframe) / (t - cls.capturevideo.lastfpstime + 0.0000001); double fps = (cls.capturevideo.frame ) / (t - cls.capturevideo.starttime + 0.0000001); dpsnprintf(buf, sizeof(buf), "capturevideo: (%.1fs) last second %.3ffps, total %.3ffps\n", cls.capturevideo.frame / cls.capturevideo.framerate, fps1, fps); Sys_PrintToTerminal(buf); cls.capturevideo.lastfpstime = t; cls.capturevideo.lastfpsframe = cls.capturevideo.frame; } } } void SCR_CaptureVideo_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length) { cls.capturevideo.soundsampleframe += (int)length; cls.capturevideo.soundframe(paintbuffer, length); } static void SCR_CaptureVideo(void) { int newframenum; if (cl_capturevideo.integer) { if (!cls.capturevideo.active) SCR_CaptureVideo_BeginVideo(); if (cls.capturevideo.framerate != cl_capturevideo_fps.value * cl_capturevideo_framestep.integer) { Con_Printf("You can not change the video framerate while recording a video.\n"); Cvar_SetValueQuick(&cl_capturevideo_fps, cls.capturevideo.framerate / (double) cl_capturevideo_framestep.integer); } // for AVI saving we have to make sure that sound is saved before video if (cls.capturevideo.soundrate && !cls.capturevideo.soundsampleframe) return; if (cls.capturevideo.realtime) { // preserve sound sync by duplicating frames when running slow newframenum = (int)((realtime - cls.capturevideo.startrealtime) * cls.capturevideo.framerate); } else newframenum = cls.capturevideo.frame + 1; // if falling behind more than one second, stop if (newframenum - cls.capturevideo.frame > 60 * (int)ceil(cls.capturevideo.framerate)) { Cvar_SetValueQuick(&cl_capturevideo, 0); Con_Printf("video saving failed on frame %i, your machine is too slow for this capture speed.\n", cls.capturevideo.frame); SCR_CaptureVideo_EndVideo(); return; } // write frames SCR_CaptureVideo_VideoFrame(newframenum / cls.capturevideo.framestep); cls.capturevideo.frame = newframenum; if (cls.capturevideo.error) { Cvar_SetValueQuick(&cl_capturevideo, 0); Con_Printf("video saving failed on frame %i, out of disk space? stopping video capture.\n", cls.capturevideo.frame); SCR_CaptureVideo_EndVideo(); } } else if (cls.capturevideo.active) SCR_CaptureVideo_EndVideo(); } #endif /* =============== R_Envmap_f Grab six views for environment mapping tests =============== */ struct envmapinfo_s { float angles[3]; const char *name; qboolean flipx, flipy, flipdiagonaly; } envmapinfo[12] = { {{ 0, 0, 0}, "rt", false, false, false}, {{ 0, 270, 0}, "ft", false, false, false}, {{ 0, 180, 0}, "lf", false, false, false}, {{ 0, 90, 0}, "bk", false, false, false}, {{-90, 180, 0}, "up", true, true, false}, {{ 90, 180, 0}, "dn", true, true, false}, {{ 0, 0, 0}, "px", true, true, true}, {{ 0, 90, 0}, "py", false, true, false}, {{ 0, 180, 0}, "nx", false, false, true}, {{ 0, 270, 0}, "ny", true, false, false}, {{-90, 180, 0}, "pz", false, false, true}, {{ 90, 180, 0}, "nz", false, false, true} }; static void R_Envmap_f (void) { int j, size; char filename[MAX_QPATH], basename[MAX_QPATH]; unsigned char *buffer1; unsigned char *buffer2; if (Cmd_Argc() != 3) { Con_Print("envmap : save out 6 cubic environment map images, usable with loadsky, note that size must one of 128, 256, 512, or 1024 and can't be bigger than your current resolution\n"); return; } strlcpy (basename, Cmd_Argv(1), sizeof (basename)); size = atoi(Cmd_Argv(2)); if (size != 128 && size != 256 && size != 512 && size != 1024) { Con_Print("envmap: size must be one of 128, 256, 512, or 1024\n"); return; } if (size > vid.width || size > vid.height) { Con_Print("envmap: your resolution is not big enough to render that size\n"); return; } r_refdef.envmap = true; R_UpdateVariables(); r_refdef.view.x = 0; r_refdef.view.y = 0; r_refdef.view.z = 0; r_refdef.view.width = size; r_refdef.view.height = size; r_refdef.view.depth = 1; r_refdef.view.useperspective = true; r_refdef.view.isoverlay = false; r_refdef.view.frustum_x = 1; // tan(45 * M_PI / 180.0); r_refdef.view.frustum_y = 1; // tan(45 * M_PI / 180.0); r_refdef.view.ortho_x = 90; // abused as angle by VM_CL_R_SetView r_refdef.view.ortho_y = 90; // abused as angle by VM_CL_R_SetView buffer1 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 4); buffer2 = (unsigned char *)Mem_Alloc(tempmempool, size * size * 3); for (j = 0;j < 12;j++) { dpsnprintf(filename, sizeof(filename), "env/%s%s.tga", basename, envmapinfo[j].name); Matrix4x4_CreateFromQuakeEntity(&r_refdef.view.matrix, r_refdef.view.origin[0], r_refdef.view.origin[1], r_refdef.view.origin[2], envmapinfo[j].angles[0], envmapinfo[j].angles[1], envmapinfo[j].angles[2], 1); r_refdef.view.quality = 1; r_refdef.view.clear = true; R_Mesh_Start(); R_RenderView(); R_Mesh_Finish(); SCR_ScreenShot(filename, buffer1, buffer2, 0, vid.height - (r_refdef.view.y + r_refdef.view.height), size, size, envmapinfo[j].flipx, envmapinfo[j].flipy, envmapinfo[j].flipdiagonaly, false, false, false, false); } Mem_Free (buffer1); Mem_Free (buffer2); r_refdef.envmap = false; } //============================================================================= void SHOWLMP_decodehide(void) { int i; char *lmplabel; lmplabel = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); for (i = 0;i < cl.num_showlmps;i++) if (cl.showlmps[i].isactive && strcmp(cl.showlmps[i].label, lmplabel) == 0) { cl.showlmps[i].isactive = false; return; } } void SHOWLMP_decodeshow(void) { int k; char lmplabel[256], picname[256]; float x, y; strlcpy (lmplabel,MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (lmplabel)); strlcpy (picname, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (picname)); if (gamemode == GAME_NEHAHRA) // LordHavoc: nasty old legacy junk { x = MSG_ReadByte(&cl_message); y = MSG_ReadByte(&cl_message); } else { x = MSG_ReadShort(&cl_message); y = MSG_ReadShort(&cl_message); } if (!cl.showlmps || cl.num_showlmps >= cl.max_showlmps) { showlmp_t *oldshowlmps = cl.showlmps; cl.max_showlmps += 16; cl.showlmps = (showlmp_t *) Mem_Alloc(cls.levelmempool, cl.max_showlmps * sizeof(showlmp_t)); if (oldshowlmps) { if (cl.num_showlmps) memcpy(cl.showlmps, oldshowlmps, cl.num_showlmps * sizeof(showlmp_t)); Mem_Free(oldshowlmps); } } for (k = 0;k < cl.max_showlmps;k++) if (cl.showlmps[k].isactive && !strcmp(cl.showlmps[k].label, lmplabel)) break; if (k == cl.max_showlmps) for (k = 0;k < cl.max_showlmps;k++) if (!cl.showlmps[k].isactive) break; cl.showlmps[k].isactive = true; strlcpy (cl.showlmps[k].label, lmplabel, sizeof (cl.showlmps[k].label)); strlcpy (cl.showlmps[k].pic, picname, sizeof (cl.showlmps[k].pic)); cl.showlmps[k].x = x; cl.showlmps[k].y = y; cl.num_showlmps = max(cl.num_showlmps, k + 1); } void SHOWLMP_drawall(void) { int i; for (i = 0;i < cl.num_showlmps;i++) if (cl.showlmps[i].isactive) DrawQ_Pic(cl.showlmps[i].x, cl.showlmps[i].y, Draw_CachePic_Flags (cl.showlmps[i].pic, CACHEPICFLAG_NOTPERSISTENT), 0, 0, 1, 1, 1, 1, 0); } /* ============================================================================== SCREEN SHOTS ============================================================================== */ // buffer1: 4*w*h // buffer2: 3*w*h (or 4*w*h if screenshotting alpha too) qboolean SCR_ScreenShot(char *filename, unsigned char *buffer1, unsigned char *buffer2, int x, int y, int width, int height, qboolean flipx, qboolean flipy, qboolean flipdiagonal, qboolean jpeg, qboolean png, qboolean gammacorrect, qboolean keep_alpha) { int indices[4] = {0,1,2,3}; // BGRA qboolean ret; GL_ReadPixelsBGRA(x, y, width, height, buffer1); if(gammacorrect && (scr_screenshot_gammaboost.value != 1 || WANT_SCREENSHOT_HWGAMMA)) { int i; double igamma = 1.0 / scr_screenshot_gammaboost.value; unsigned short vidramp[256 * 3]; if(WANT_SCREENSHOT_HWGAMMA) { VID_BuildGammaTables(&vidramp[0], 256); } else { // identity gamma table BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp, 256); BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256, 256); BuildGammaTable16(1.0f, 1.0f, 1.0f, 0.0f, 1.0f, vidramp + 256*2, 256); } if(scr_screenshot_gammaboost.value != 1) { for (i = 0;i < 256 * 3;i++) vidramp[i] = (unsigned short) (0.5 + pow(vidramp[i] * (1.0 / 65535.0), igamma) * 65535.0); } for (i = 0;i < width*height*4;i += 4) { buffer1[i] = (unsigned char) (vidramp[buffer1[i] + 512] * 255.0 / 65535.0 + 0.5); // B buffer1[i+1] = (unsigned char) (vidramp[buffer1[i+1] + 256] * 255.0 / 65535.0 + 0.5); // G buffer1[i+2] = (unsigned char) (vidramp[buffer1[i+2]] * 255.0 / 65535.0 + 0.5); // R // A } } if(keep_alpha && !jpeg) { if(!png) flipy = !flipy; // TGA: not preflipped Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 4, 4, indices); if (png) ret = PNG_SaveImage_preflipped (filename, width, height, true, buffer2); else ret = Image_WriteTGABGRA(filename, width, height, buffer2); } else { if(jpeg) { indices[0] = 2; indices[2] = 0; // RGB } Image_CopyMux (buffer2, buffer1, width, height, flipx, flipy, flipdiagonal, 3, 4, indices); if (jpeg) ret = JPEG_SaveImage_preflipped (filename, width, height, buffer2); else if (png) ret = PNG_SaveImage_preflipped (filename, width, height, false, buffer2); else ret = Image_WriteTGABGR_preflipped (filename, width, height, buffer2); } return ret; } //============================================================================= int scr_numtouchscreenareas; scr_touchscreenarea_t scr_touchscreenareas[128]; static void SCR_DrawTouchscreenOverlay(void) { int i; scr_touchscreenarea_t *a; cachepic_t *pic; for (i = 0, a = scr_touchscreenareas;i < scr_numtouchscreenareas;i++, a++) { if (vid_touchscreen_outlinealpha.value > 0 && a->rect[0] >= 0 && a->rect[1] >= 0 && a->rect[2] >= 4 && a->rect[3] >= 4) { DrawQ_Fill(a->rect[0] + 2, a->rect[1] , a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); DrawQ_Fill(a->rect[0] + 1, a->rect[1] + 1, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); DrawQ_Fill(a->rect[0] , a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); DrawQ_Fill(a->rect[0] + a->rect[2] - 2, a->rect[1] + 2, 2 , a->rect[3] - 2, 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); DrawQ_Fill(a->rect[0] + 1, a->rect[1] + a->rect[3] - 2, a->rect[2] - 2, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); DrawQ_Fill(a->rect[0] + 2, a->rect[1] + a->rect[3] - 1, a->rect[2] - 4, 1 , 1, 1, 1, vid_touchscreen_outlinealpha.value * (0.5f + 0.5f * a->active), 0); } pic = a->pic ? Draw_CachePic(a->pic) : NULL; if (pic && pic->tex != r_texture_notexture) DrawQ_Pic(a->rect[0], a->rect[1], Draw_CachePic(a->pic), a->rect[2], a->rect[3], 1, 1, 1, vid_touchscreen_overlayalpha.value * (0.5f + 0.5f * a->active), 0); if (a->text && a->text[0]) { int textwidth = DrawQ_TextWidth(a->text, 0, a->textheight, a->textheight, false, FONT_CHAT); DrawQ_String(a->rect[0] + (a->rect[2] - textwidth) * 0.5f, a->rect[1] + (a->rect[3] - a->textheight) * 0.5f, a->text, 0, a->textheight, a->textheight, 1.0f, 1.0f, 1.0f, vid_touchscreen_overlayalpha.value, 0, NULL, false, FONT_CHAT); } } } void R_ClearScreen(qboolean fogcolor) { float clearcolor[4]; if (scr_screenshot_alpha.integer) // clear to transparency (so png screenshots can contain alpha channel, useful for building model pictures) Vector4Set(clearcolor, 0.0f, 0.0f, 0.0f, 0.0f); else // clear to opaque black (if we're being composited it might otherwise render as transparent) Vector4Set(clearcolor, 0.0f, 0.0f, 0.0f, 1.0f); if (fogcolor && r_fog_clear.integer) { R_UpdateFog(); VectorCopy(r_refdef.fogcolor, clearcolor); } // clear depth is 1.0 // LordHavoc: we use a stencil centered around 128 instead of 0, // to avoid clamping interfering with strange shadow volume // drawing orders // clear the screen GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (vid.stencil ? GL_STENCIL_BUFFER_BIT : 0), clearcolor, 1.0f, 128); } int r_stereo_side; static void SCR_DrawScreen (void) { Draw_Frame(); R_Mesh_Start(); R_UpdateVariables(); // Quake uses clockwise winding, so these are swapped r_refdef.view.cullface_front = GL_BACK; r_refdef.view.cullface_back = GL_FRONT; if (cls.signon == SIGNONS) { float size; size = scr_viewsize.value * (1.0 / 100.0); size = min(size, 1); if (r_stereo_sidebyside.integer) { r_refdef.view.width = (int)(vid.width * size / 2.5); r_refdef.view.height = (int)(vid.height * size / 2.5 * (1 - bound(0, r_letterbox.value, 100) / 100)); r_refdef.view.depth = 1; r_refdef.view.x = (int)((vid.width - r_refdef.view.width * 2.5) * 0.5); r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); r_refdef.view.z = 0; if (r_stereo_side) r_refdef.view.x += (int)(r_refdef.view.width * 1.5); } else if (r_stereo_horizontal.integer) { r_refdef.view.width = (int)(vid.width * size / 2); r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100)); r_refdef.view.depth = 1; r_refdef.view.x = (int)((vid.width - r_refdef.view.width * 2.0)/2); r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); r_refdef.view.z = 0; if (r_stereo_side) r_refdef.view.x += (int)(r_refdef.view.width); } else if (r_stereo_vertical.integer) { r_refdef.view.width = (int)(vid.width * size); r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100) / 2); r_refdef.view.depth = 1; r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2); r_refdef.view.y = (int)((vid.height - r_refdef.view.height * 2.0)/2); r_refdef.view.z = 0; if (r_stereo_side) r_refdef.view.y += (int)(r_refdef.view.height); } else { r_refdef.view.width = (int)(vid.width * size); r_refdef.view.height = (int)(vid.height * size * (1 - bound(0, r_letterbox.value, 100) / 100)); r_refdef.view.depth = 1; r_refdef.view.x = (int)((vid.width - r_refdef.view.width)/2); r_refdef.view.y = (int)((vid.height - r_refdef.view.height)/2); r_refdef.view.z = 0; } // LordHavoc: viewzoom (zoom in for sniper rifles, etc) // LordHavoc: this is designed to produce widescreen fov values // when the screen is wider than 4/3 width/height aspect, to do // this it simply assumes the requested fov is the vertical fov // for a 4x3 display, if the ratio is not 4x3 this makes the fov // higher/lower according to the ratio r_refdef.view.useperspective = true; r_refdef.view.frustum_y = tan(scr_fov.value * M_PI / 360.0) * (3.0/4.0) * cl.viewzoom; r_refdef.view.frustum_x = r_refdef.view.frustum_y * (float)r_refdef.view.width / (float)r_refdef.view.height / vid_pixelheight.value; r_refdef.view.frustum_x *= r_refdef.frustumscale_x; r_refdef.view.frustum_y *= r_refdef.frustumscale_y; r_refdef.view.ortho_x = atan(r_refdef.view.frustum_x) * (360.0 / M_PI); // abused as angle by VM_CL_R_SetView r_refdef.view.ortho_y = atan(r_refdef.view.frustum_y) * (360.0 / M_PI); // abused as angle by VM_CL_R_SetView if(!CL_VM_UpdateView(r_stereo_side ? 0.0 : max(0.0, cl.time - cl.oldtime))) R_RenderView(); } if (!r_stereo_sidebyside.integer && !r_stereo_horizontal.integer && !r_stereo_vertical.integer) { r_refdef.view.width = vid.width; r_refdef.view.height = vid.height; r_refdef.view.depth = 1; r_refdef.view.x = 0; r_refdef.view.y = 0; r_refdef.view.z = 0; r_refdef.view.useperspective = false; } if (cls.timedemo && cls.td_frames > 0 && timedemo_screenshotframelist.string && timedemo_screenshotframelist.string[0]) { const char *t; int framenum; t = timedemo_screenshotframelist.string; while (*t) { while (*t == ' ') t++; if (!*t) break; framenum = atof(t); if (framenum == cls.td_frames) break; while (*t && *t != ' ') t++; } if (*t) { // we need to take a screenshot of this frame... char filename[MAX_QPATH]; unsigned char *buffer1; unsigned char *buffer2; dpsnprintf(filename, sizeof(filename), "timedemoscreenshots/%s%06d.tga", cls.demoname, cls.td_frames); buffer1 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 4); buffer2 = (unsigned char *)Mem_Alloc(tempmempool, vid.width * vid.height * 3); SCR_ScreenShot(filename, buffer1, buffer2, 0, 0, vid.width, vid.height, false, false, false, false, false, true, false); Mem_Free(buffer1); Mem_Free(buffer2); } } // draw 2D stuff if(!scr_con_current && !(key_consoleactive & KEY_CONSOLEACTIVE_FORCED)) if ((key_dest == key_game || key_dest == key_message) && !r_letterbox.value) Con_DrawNotify (); // only draw notify in game if (cls.signon == SIGNONS) { SCR_DrawNet (); SCR_DrawTurtle (); SCR_DrawPause (); if (!r_letterbox.value) Sbar_Draw(); SHOWLMP_drawall(); SCR_CheckDrawCenterString(); } SCR_DrawNetGraph (); #ifdef CONFIG_MENU MR_Draw(); #endif CL_DrawVideo(); R_Shadow_EditLights_DrawSelectedLightProperties(); SCR_DrawConsole(); SCR_DrawBrand(); SCR_DrawInfobar(); SCR_DrawTouchscreenOverlay(); if (r_timereport_active) R_TimeReport("2d"); R_TimeReport_EndFrame(); R_TimeReport_BeginFrame(); Sbar_ShowFPS(); DrawQ_Finish(); R_DrawGamma(); R_Mesh_Finish(); } typedef struct loadingscreenstack_s { struct loadingscreenstack_s *prev; char msg[MAX_QPATH]; float absolute_loading_amount_min; // this corresponds to relative completion 0 of this item float absolute_loading_amount_len; // this corresponds to relative completion 1 of this item float relative_completion; // 0 .. 1 } loadingscreenstack_t; static loadingscreenstack_t *loadingscreenstack = NULL; static qboolean loadingscreendone = false; static qboolean loadingscreencleared = false; static float loadingscreenheight = 0; rtexture_t *loadingscreentexture = NULL; static float loadingscreentexture_vertex3f[12]; static float loadingscreentexture_texcoord2f[8]; static int loadingscreenpic_number = 0; static void SCR_ClearLoadingScreenTexture(void) { if(loadingscreentexture) R_FreeTexture(loadingscreentexture); loadingscreentexture = NULL; } extern rtexturepool_t *r_main_texturepool; static void SCR_SetLoadingScreenTexture(void) { int w, h; float loadingscreentexture_w; float loadingscreentexture_h; SCR_ClearLoadingScreenTexture(); if (vid.support.arb_texture_non_power_of_two) { w = vid.width; h = vid.height; loadingscreentexture_w = loadingscreentexture_h = 1; } else { w = CeilPowerOf2(vid.width); h = CeilPowerOf2(vid.height); loadingscreentexture_w = vid.width / (float) w; loadingscreentexture_h = vid.height / (float) h; } loadingscreentexture = R_LoadTexture2D(r_main_texturepool, "loadingscreentexture", w, h, NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP, -1, NULL); R_Mesh_CopyToTexture(loadingscreentexture, 0, 0, 0, 0, vid.width, vid.height); loadingscreentexture_vertex3f[2] = loadingscreentexture_vertex3f[5] = loadingscreentexture_vertex3f[8] = loadingscreentexture_vertex3f[11] = 0; loadingscreentexture_vertex3f[0] = loadingscreentexture_vertex3f[9] = 0; loadingscreentexture_vertex3f[1] = loadingscreentexture_vertex3f[4] = 0; loadingscreentexture_vertex3f[3] = loadingscreentexture_vertex3f[6] = vid_conwidth.integer; loadingscreentexture_vertex3f[7] = loadingscreentexture_vertex3f[10] = vid_conheight.integer; loadingscreentexture_texcoord2f[0] = 0;loadingscreentexture_texcoord2f[1] = loadingscreentexture_h; loadingscreentexture_texcoord2f[2] = loadingscreentexture_w;loadingscreentexture_texcoord2f[3] = loadingscreentexture_h; loadingscreentexture_texcoord2f[4] = loadingscreentexture_w;loadingscreentexture_texcoord2f[5] = 0; loadingscreentexture_texcoord2f[6] = 0;loadingscreentexture_texcoord2f[7] = 0; } void SCR_UpdateLoadingScreenIfShown(void) { if(loadingscreendone) SCR_UpdateLoadingScreen(loadingscreencleared, false); } void SCR_PushLoadingScreen (qboolean redraw, const char *msg, float len_in_parent) { loadingscreenstack_t *s = (loadingscreenstack_t *) Z_Malloc(sizeof(loadingscreenstack_t)); s->prev = loadingscreenstack; loadingscreenstack = s; strlcpy(s->msg, msg, sizeof(s->msg)); s->relative_completion = 0; if(s->prev) { s->absolute_loading_amount_min = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len * s->prev->relative_completion; s->absolute_loading_amount_len = s->prev->absolute_loading_amount_len * len_in_parent; if(s->absolute_loading_amount_len > s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min) s->absolute_loading_amount_len = s->prev->absolute_loading_amount_min + s->prev->absolute_loading_amount_len - s->absolute_loading_amount_min; } else { s->absolute_loading_amount_min = 0; s->absolute_loading_amount_len = 1; } if(redraw) SCR_UpdateLoadingScreenIfShown(); } void SCR_PopLoadingScreen (qboolean redraw) { loadingscreenstack_t *s = loadingscreenstack; if(!s) { Con_DPrintf("Popping a loading screen item from an empty stack!\n"); return; } loadingscreenstack = s->prev; if(s->prev) s->prev->relative_completion = (s->absolute_loading_amount_min + s->absolute_loading_amount_len - s->prev->absolute_loading_amount_min) / s->prev->absolute_loading_amount_len; Z_Free(s); if(redraw) SCR_UpdateLoadingScreenIfShown(); } void SCR_ClearLoadingScreen (qboolean redraw) { while(loadingscreenstack) SCR_PopLoadingScreen(redraw && !loadingscreenstack->prev); } static float SCR_DrawLoadingStack_r(loadingscreenstack_t *s, float y, float size) { float x; size_t len; float total; total = 0; #if 0 if(s) { total += SCR_DrawLoadingStack_r(s->prev, y, 8); y -= total; if(!s->prev || strcmp(s->msg, s->prev->msg)) { len = strlen(s->msg); x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; y -= size; DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); total += size; } } #else if(s) { len = strlen(s->msg); x = (vid_conwidth.integer - DrawQ_TextWidth(s->msg, len, size, size, true, FONT_INFOBAR)) / 2; y -= size; DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 1, 0); DrawQ_String(x, y, s->msg, len, size, size, 1, 1, 1, 1, 0, NULL, true, FONT_INFOBAR); total += size; } #endif return total; } static void SCR_DrawLoadingStack(void) { float verts[12]; float colors[16]; loadingscreenheight = SCR_DrawLoadingStack_r(loadingscreenstack, vid_conheight.integer, scr_loadingscreen_barheight.value); if(loadingscreenstack) { // height = 32; // sorry, using the actual one is ugly GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(false); // R_Mesh_ResetTextureState(); verts[2] = verts[5] = verts[8] = verts[11] = 0; verts[0] = verts[9] = 0; verts[1] = verts[4] = vid_conheight.integer - scr_loadingscreen_barheight.value; verts[3] = verts[6] = vid_conwidth.integer * loadingscreenstack->absolute_loading_amount_min; verts[7] = verts[10] = vid_conheight.integer; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif // ^^^^^^^^^^ blue component // ^^^^^^ bottom row // ^^^^^^^^^^^^ alpha is always on colors[0] = 0; colors[1] = 0; colors[2] = 0; colors[3] = 1; colors[4] = 0; colors[5] = 0; colors[6] = 0; colors[7] = 1; sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[8], &colors[9], &colors[10]); colors[11] = 1; sscanf(scr_loadingscreen_barcolor.string, "%f %f %f", &colors[12], &colors[13], &colors[14]); colors[15] = 1; R_Mesh_PrepareVertices_Generic_Arrays(4, verts, colors, NULL); R_SetupShader_Generic_NoTexture(true, true); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); // make sure everything is cleared, including the progress indicator if(loadingscreenheight < 8) loadingscreenheight = 8; } } static cachepic_t *loadingscreenpic; static float loadingscreenpic_vertex3f[12]; static float loadingscreenpic_texcoord2f[8]; static void SCR_DrawLoadingScreen_SharedSetup (qboolean clear) { r_viewport_t viewport; float x, y, w, h, sw, sh, f; char vabuf[1024]; // release mouse grab while loading if (!vid.fullscreen) VID_SetMouse(false, false, false); // CHECKGLERROR r_refdef.draw2dstage = true; R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); R_SetViewport(&viewport); GL_ColorMask(1,1,1,1); // when starting up a new video mode, make sure the screen is cleared to black if (clear || loadingscreentexture) GL_Clear(GL_COLOR_BUFFER_BIT, NULL, 1.0f, 0); R_Textures_Frame(); R_Mesh_Start(); R_EntityMatrix(&identitymatrix); // draw the loading plaque loadingscreenpic = Draw_CachePic_Flags (loadingscreenpic_number ? va(vabuf, sizeof(vabuf), "%s%d", scr_loadingscreen_picture.string, loadingscreenpic_number+1) : scr_loadingscreen_picture.string, loadingscreenpic_number ? CACHEPICFLAG_NOTPERSISTENT : 0); w = loadingscreenpic->width; h = loadingscreenpic->height; // apply scale w *= scr_loadingscreen_scale.value; h *= scr_loadingscreen_scale.value; // apply scale base if(scr_loadingscreen_scale_base.integer) { w *= vid_conwidth.integer / (float) vid.width; h *= vid_conheight.integer / (float) vid.height; } // apply scale limit sw = w / vid_conwidth.integer; sh = h / vid_conheight.integer; f = 1; switch(scr_loadingscreen_scale_limit.integer) { case 1: f = max(sw, sh); break; case 2: f = min(sw, sh); break; case 3: f = sw; break; case 4: f = sh; break; } if(f > 1) { w /= f; h /= f; } x = (vid_conwidth.integer - w)/2; y = (vid_conheight.integer - h)/2; loadingscreenpic_vertex3f[2] = loadingscreenpic_vertex3f[5] = loadingscreenpic_vertex3f[8] = loadingscreenpic_vertex3f[11] = 0; loadingscreenpic_vertex3f[0] = loadingscreenpic_vertex3f[9] = x; loadingscreenpic_vertex3f[1] = loadingscreenpic_vertex3f[4] = y; loadingscreenpic_vertex3f[3] = loadingscreenpic_vertex3f[6] = x + w; loadingscreenpic_vertex3f[7] = loadingscreenpic_vertex3f[10] = y + h; loadingscreenpic_texcoord2f[0] = 0;loadingscreenpic_texcoord2f[1] = 0; loadingscreenpic_texcoord2f[2] = 1;loadingscreenpic_texcoord2f[3] = 0; loadingscreenpic_texcoord2f[4] = 1;loadingscreenpic_texcoord2f[5] = 1; loadingscreenpic_texcoord2f[6] = 0;loadingscreenpic_texcoord2f[7] = 1; } static void SCR_DrawLoadingScreen (qboolean clear) { // we only need to draw the image if it isn't already there GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(false); // R_Mesh_ResetTextureState(); GL_Color(1,1,1,1); if(loadingscreentexture) { R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreentexture_vertex3f, NULL, loadingscreentexture_texcoord2f); R_SetupShader_Generic(loadingscreentexture, NULL, GL_MODULATE, 1, true, true, true); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } R_Mesh_PrepareVertices_Generic_Arrays(4, loadingscreenpic_vertex3f, NULL, loadingscreenpic_texcoord2f); R_SetupShader_Generic(Draw_GetPicTexture(loadingscreenpic), NULL, GL_MODULATE, 1, true, true, false); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); SCR_DrawLoadingStack(); } static void SCR_DrawLoadingScreen_SharedFinish (qboolean clear) { R_Mesh_Finish(); // refresh VID_Finish(); } static double loadingscreen_lastupdate; void SCR_UpdateLoadingScreen (qboolean clear, qboolean startup) { keydest_t old_key_dest; int old_key_consoleactive; // don't do anything if not initialized yet if (vid_hidden || cls.state == ca_dedicated) return; // limit update rate if (scr_loadingscreen_maxfps.value) { double t = Sys_DirtyTime(); if ((t - loadingscreen_lastupdate) < 1.0f/scr_loadingscreen_maxfps.value) return; loadingscreen_lastupdate = t; } if(!scr_loadingscreen_background.integer) clear = true; if(loadingscreendone) clear |= loadingscreencleared; if(!loadingscreendone) { if(startup && scr_loadingscreen_firstforstartup.integer) loadingscreenpic_number = 0; else if(scr_loadingscreen_firstforstartup.integer) if(scr_loadingscreen_count.integer > 1) loadingscreenpic_number = rand() % (scr_loadingscreen_count.integer - 1) + 1; else loadingscreenpic_number = 0; else loadingscreenpic_number = rand() % (scr_loadingscreen_count.integer > 1 ? scr_loadingscreen_count.integer : 1); } if(clear) SCR_ClearLoadingScreenTexture(); else if(!loadingscreendone) SCR_SetLoadingScreenTexture(); if(!loadingscreendone) { loadingscreendone = true; loadingscreenheight = 0; } loadingscreencleared = clear; #ifdef USE_GLES2 SCR_DrawLoadingScreen_SharedSetup(clear); SCR_DrawLoadingScreen(clear); #else if (qglDrawBuffer) qglDrawBuffer(GL_BACK); SCR_DrawLoadingScreen_SharedSetup(clear); if (vid.stereobuffer && qglDrawBuffer) { qglDrawBuffer(GL_BACK_LEFT); SCR_DrawLoadingScreen(clear); qglDrawBuffer(GL_BACK_RIGHT); SCR_DrawLoadingScreen(clear); } else { if (qglDrawBuffer) qglDrawBuffer(GL_BACK); SCR_DrawLoadingScreen(clear); } #endif SCR_DrawLoadingScreen_SharedFinish(clear); // this goes into the event loop, and should prevent unresponsive cursor on vista old_key_dest = key_dest; old_key_consoleactive = key_consoleactive; key_dest = key_void; key_consoleactive = false; Key_EventQueue_Block(); Sys_SendKeyEvents(); key_dest = old_key_dest; key_consoleactive = old_key_consoleactive; } qboolean R_Stereo_ColorMasking(void) { return r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer; } qboolean R_Stereo_Active(void) { return (vid.stereobuffer || r_stereo_sidebyside.integer || r_stereo_horizontal.integer || r_stereo_vertical.integer || R_Stereo_ColorMasking()); } extern cvar_t cl_minfps; extern cvar_t cl_minfps_fade; extern cvar_t cl_minfps_qualitymax; extern cvar_t cl_minfps_qualitymin; extern cvar_t cl_minfps_qualitymultiply; extern cvar_t cl_minfps_qualityhysteresis; extern cvar_t cl_minfps_qualitystepmax; extern cvar_t cl_minfps_force; static double cl_updatescreen_quality = 1; void CL_UpdateScreen(void) { vec3_t vieworigin; static double drawscreenstart = 0.0; double drawscreendelta; float conwidth, conheight; r_viewport_t viewport; if(drawscreenstart) { drawscreendelta = Sys_DirtyTime() - drawscreenstart; if (cl_minfps.value > 0 && (cl_minfps_force.integer || !(cls.timedemo || (cls.capturevideo.active && !cls.capturevideo.realtime))) && drawscreendelta >= 0 && drawscreendelta < 60) { // quality adjustment according to render time double actualframetime; double targetframetime; double adjust; double f; double h; // fade lastdrawscreentime r_refdef.lastdrawscreentime += (drawscreendelta - r_refdef.lastdrawscreentime) * cl_minfps_fade.value; // find actual and target frame times actualframetime = r_refdef.lastdrawscreentime; targetframetime = (1.0 / cl_minfps.value); // we scale hysteresis by quality h = cl_updatescreen_quality * cl_minfps_qualityhysteresis.value; // calculate adjustment assuming linearity f = cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value; adjust = (targetframetime - actualframetime) * f; // one sided hysteresis if(adjust > 0) adjust = max(0, adjust - h); // adjust > 0 if: // (targetframetime - actualframetime) * f > h // ((1.0 / cl_minfps.value) - actualframetime) * (cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value) > (cl_updatescreen_quality * cl_minfps_qualityhysteresis.value) // ((1.0 / cl_minfps.value) - actualframetime) * (cl_minfps_qualitymultiply.value / actualframetime) > cl_minfps_qualityhysteresis.value // (1.0 / cl_minfps.value) * (cl_minfps_qualitymultiply.value / actualframetime) - cl_minfps_qualitymultiply.value > cl_minfps_qualityhysteresis.value // (1.0 / cl_minfps.value) * (cl_minfps_qualitymultiply.value / actualframetime) > cl_minfps_qualityhysteresis.value + cl_minfps_qualitymultiply.value // (1.0 / cl_minfps.value) / actualframetime > (cl_minfps_qualityhysteresis.value + cl_minfps_qualitymultiply.value) / cl_minfps_qualitymultiply.value // (1.0 / cl_minfps.value) / actualframetime > 1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value // cl_minfps.value * actualframetime < 1.0 / (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) // actualframetime < 1.0 / cl_minfps.value / (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) // actualfps > cl_minfps.value * (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value) // adjust < 0 if: // (targetframetime - actualframetime) * f < 0 // ((1.0 / cl_minfps.value) - actualframetime) * (cl_updatescreen_quality / actualframetime * cl_minfps_qualitymultiply.value) < 0 // ((1.0 / cl_minfps.value) - actualframetime) < 0 // -actualframetime) < -(1.0 / cl_minfps.value) // actualfps < cl_minfps.value /* Con_Printf("adjust UP if fps > %f, adjust DOWN if fps < %f\n", cl_minfps.value * (1.0 + cl_minfps_qualityhysteresis.value / cl_minfps_qualitymultiply.value), cl_minfps.value); */ // don't adjust too much at once adjust = bound(-cl_minfps_qualitystepmax.value, adjust, cl_minfps_qualitystepmax.value); // adjust! cl_updatescreen_quality += adjust; cl_updatescreen_quality = bound(max(0.01, cl_minfps_qualitymin.value), cl_updatescreen_quality, cl_minfps_qualitymax.value); } else { cl_updatescreen_quality = 1; r_refdef.lastdrawscreentime = 0; } } drawscreenstart = Sys_DirtyTime(); Sbar_ShowFPS_Update(); if (!scr_initialized || !con_initialized || !scr_refresh.integer) return; // not initialized yet loadingscreendone = false; if(IS_NEXUIZ_DERIVED(gamemode)) { // play a bit with the palette (experimental) palette_rgb_pantscolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 0.0f*M_PI/3.0f)); palette_rgb_pantscolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 2.0f*M_PI/3.0f)); palette_rgb_pantscolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / exp(1.0f) + 4.0f*M_PI/3.0f)); palette_rgb_shirtcolormap[15][0] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 5.0f*M_PI/3.0f)); palette_rgb_shirtcolormap[15][1] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 3.0f*M_PI/3.0f)); palette_rgb_shirtcolormap[15][2] = (unsigned char) (128 + 127 * sin(cl.time / M_PI + 1.0f*M_PI/3.0f)); memcpy(palette_rgb_pantsscoreboard[15], palette_rgb_pantscolormap[15], sizeof(*palette_rgb_pantscolormap)); memcpy(palette_rgb_shirtscoreboard[15], palette_rgb_shirtcolormap[15], sizeof(*palette_rgb_shirtcolormap)); } if (vid_hidden) { VID_Finish(); return; } conwidth = bound(160, vid_conwidth.value, 32768); conheight = bound(90, vid_conheight.value, 24576); if (vid_conwidth.value != conwidth) Cvar_SetValue("vid_conwidth", conwidth); if (vid_conheight.value != conheight) Cvar_SetValue("vid_conheight", conheight); // bound viewsize if (scr_viewsize.value < 30) Cvar_Set ("viewsize","30"); if (scr_viewsize.value > 120) Cvar_Set ("viewsize","120"); // bound field of view if (scr_fov.value < 1) Cvar_Set ("fov","1"); if (scr_fov.value > 170) Cvar_Set ("fov","170"); // intermission is always full screen if (cl.intermission) sb_lines = 0; else { if (scr_viewsize.value >= 120) sb_lines = 0; // no status bar at all else if (scr_viewsize.value >= 110) sb_lines = 24; // no inventory else sb_lines = 24+16+8; } R_FrameData_NewFrame(); R_BufferData_NewFrame(); Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, vieworigin); R_HDR_UpdateIrisAdaptation(vieworigin); r_refdef.view.colormask[0] = 1; r_refdef.view.colormask[1] = 1; r_refdef.view.colormask[2] = 1; SCR_SetUpToDrawConsole(); #ifndef USE_GLES2 if (qglDrawBuffer) { CHECKGLERROR qglDrawBuffer(GL_BACK);CHECKGLERROR // set dithering mode if (gl_dither.integer) { qglEnable(GL_DITHER);CHECKGLERROR } else { qglDisable(GL_DITHER);CHECKGLERROR } } #endif R_Viewport_InitOrtho(&viewport, &identitymatrix, 0, 0, vid.width, vid.height, 0, 0, vid_conwidth.integer, vid_conheight.integer, -10, 100, NULL); R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); R_SetViewport(&viewport); GL_ScissorTest(false); GL_ColorMask(1,1,1,1); GL_DepthMask(true); R_ClearScreen(false); r_refdef.view.clear = false; r_refdef.view.isoverlay = false; // calculate r_refdef.view.quality r_refdef.view.quality = cl_updatescreen_quality; #ifndef USE_GLES2 if (qglPolygonStipple) { if(scr_stipple.integer) { GLubyte stipple[128]; int i, s, width, parts; static int frame = 0; ++frame; s = scr_stipple.integer; parts = (s & 007); width = (s & 070) >> 3; qglEnable(GL_POLYGON_STIPPLE);CHECKGLERROR // 0x0B42 for(i = 0; i < 128; ++i) { int line = i/4; stipple[i] = (((line >> width) + frame) & ((1 << parts) - 1)) ? 0x00 : 0xFF; } qglPolygonStipple(stipple);CHECKGLERROR } else { qglDisable(GL_POLYGON_STIPPLE);CHECKGLERROR } } #endif #ifndef USE_GLES2 if (R_Stereo_Active()) { r_stereo_side = 0; if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer) { r_refdef.view.colormask[0] = 1; r_refdef.view.colormask[1] = 0; r_refdef.view.colormask[2] = 0; } if (vid.stereobuffer) qglDrawBuffer(GL_BACK_RIGHT); SCR_DrawScreen(); r_stereo_side = 1; r_refdef.view.clear = true; if (r_stereo_redblue.integer || r_stereo_redgreen.integer || r_stereo_redcyan.integer) { r_refdef.view.colormask[0] = 0; r_refdef.view.colormask[1] = r_stereo_redcyan.integer || r_stereo_redgreen.integer; r_refdef.view.colormask[2] = r_stereo_redcyan.integer || r_stereo_redblue.integer; } if (vid.stereobuffer) qglDrawBuffer(GL_BACK_LEFT); SCR_DrawScreen(); r_stereo_side = 0; } else #endif { r_stereo_side = 0; SCR_DrawScreen(); } #ifdef CONFIG_VIDEO_CAPTURE SCR_CaptureVideo(); #endif if (qglFlush) qglFlush(); // FIXME: should we really be using qglFlush here? if (!vid_activewindow) VID_SetMouse(false, false, false); else if (key_consoleactive) VID_SetMouse(vid.fullscreen, false, false); else if (key_dest == key_menu_grabbed) VID_SetMouse(true, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); else if (key_dest == key_menu) VID_SetMouse(vid.fullscreen, vid_mouse.integer && !in_client_mouse && !vid_touchscreen.integer, !vid_touchscreen.integer); else VID_SetMouse(vid.fullscreen, vid_mouse.integer && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0 && (!cls.demoplayback || cl_demo_mousegrab.integer) && !vid_touchscreen.integer, !vid_touchscreen.integer); VID_Finish(); } void CL_Screen_NewMap(void) { } darkplaces/sys_shared.c0000664000175000017500000004076013067716222014504 0ustar kalevkalev#ifdef WIN32 # ifndef DONT_USE_SETDLLDIRECTORY # define _WIN32_WINNT 0x0502 # endif #endif #include "quakedef.h" #include "thread.h" #define SUPPORTDLL #ifdef WIN32 # include # include // timeGetTime # include // localtime #ifdef _MSC_VER #pragma comment(lib, "winmm.lib") #endif #else # include # include # include # include # ifdef SUPPORTDLL # include # endif #endif static char sys_timestring[128]; char *Sys_TimeString(const char *timeformat) { time_t mytime = time(NULL); #if _MSC_VER >= 1400 struct tm mytm; localtime_s(&mytm, &mytime); strftime(sys_timestring, sizeof(sys_timestring), timeformat, &mytm); #else strftime(sys_timestring, sizeof(sys_timestring), timeformat, localtime(&mytime)); #endif return sys_timestring; } extern qboolean host_shuttingdown; void Sys_Quit (int returnvalue) { // Unlock mutexes because the quit command may jump directly here, causing a deadlock Cbuf_UnlockThreadMutex(); SV_UnlockThreadMutex(); if (COM_CheckParm("-profilegameonly")) Sys_AllowProfiling(false); host_shuttingdown = true; Host_Shutdown(); exit(returnvalue); } #ifdef __cplusplus extern "C" #endif void Sys_AllowProfiling(qboolean enable) { #ifdef __ANDROID__ #ifdef USE_PROFILER extern void monstartup(const char *libname); extern void moncleanup(void); if (enable) monstartup("libmain.so"); else moncleanup(); #endif #elif defined(__linux__) || defined(__FreeBSD__) extern int moncontrol(int); moncontrol(enable); #endif } /* =============================================================================== DLL MANAGEMENT =============================================================================== */ static qboolean Sys_LoadLibraryFunctions(dllhandle_t dllhandle, const dllfunction_t *fcts, qboolean complain, qboolean has_next) { const dllfunction_t *func; if(dllhandle) { for (func = fcts; func && func->name != NULL; func++) if (!(*func->funcvariable = (void *) Sys_GetProcAddress (dllhandle, func->name))) { if(complain) { Con_DPrintf (" - missing function \"%s\" - broken library!", func->name); if(has_next) Con_DPrintf("\nContinuing with"); } goto notfound; } return true; notfound: for (func = fcts; func && func->name != NULL; func++) *func->funcvariable = NULL; } return false; } qboolean Sys_LoadLibrary (const char** dllnames, dllhandle_t* handle, const dllfunction_t *fcts) { #ifdef SUPPORTDLL const dllfunction_t *func; dllhandle_t dllhandle = 0; unsigned int i; if (handle == NULL) return false; #ifndef WIN32 #ifdef PREFER_PRELOAD dllhandle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL); if(Sys_LoadLibraryFunctions(dllhandle, fcts, false, false)) { Con_DPrintf ("All of %s's functions were already linked in! Not loading dynamically...\n", dllnames[0]); *handle = dllhandle; return true; } else Sys_UnloadLibrary(&dllhandle); notfound: #endif #endif // Initializations for (func = fcts; func && func->name != NULL; func++) *func->funcvariable = NULL; // Try every possible name Con_DPrintf ("Trying to load library..."); for (i = 0; dllnames[i] != NULL; i++) { Con_DPrintf (" \"%s\"", dllnames[i]); #ifdef WIN32 # ifndef DONT_USE_SETDLLDIRECTORY # ifdef _WIN64 SetDllDirectory("bin64"); # else SetDllDirectory("bin32"); # endif # endif dllhandle = LoadLibrary (dllnames[i]); // no need to unset this - we want ALL dlls to be loaded from there, anyway #else dllhandle = dlopen (dllnames[i], RTLD_LAZY | RTLD_GLOBAL); #endif if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, (dllnames[i+1] != NULL) || (strrchr(com_argv[0], '/')))) break; else Sys_UnloadLibrary (&dllhandle); } // see if the names can be loaded relative to the executable path // (this is for Mac OSX which does not check next to the executable) if (!dllhandle && strrchr(com_argv[0], '/')) { char path[MAX_OSPATH]; strlcpy(path, com_argv[0], sizeof(path)); strrchr(path, '/')[1] = 0; for (i = 0; dllnames[i] != NULL; i++) { char temp[MAX_OSPATH]; strlcpy(temp, path, sizeof(temp)); strlcat(temp, dllnames[i], sizeof(temp)); Con_DPrintf (" \"%s\"", temp); #ifdef WIN32 dllhandle = LoadLibrary (temp); #else dllhandle = dlopen (temp, RTLD_LAZY | RTLD_GLOBAL); #endif if (Sys_LoadLibraryFunctions(dllhandle, fcts, true, dllnames[i+1] != NULL)) break; else Sys_UnloadLibrary (&dllhandle); } } // No DLL found if (! dllhandle) { Con_DPrintf(" - failed.\n"); return false; } Con_DPrintf(" - loaded.\n"); *handle = dllhandle; return true; #else return false; #endif } void Sys_UnloadLibrary (dllhandle_t* handle) { #ifdef SUPPORTDLL if (handle == NULL || *handle == NULL) return; #ifdef WIN32 FreeLibrary (*handle); #else dlclose (*handle); #endif *handle = NULL; #endif } void* Sys_GetProcAddress (dllhandle_t handle, const char* name) { #ifdef SUPPORTDLL #ifdef WIN32 return (void *)GetProcAddress (handle, name); #else return (void *)dlsym (handle, name); #endif #else return NULL; #endif } #ifdef WIN32 # define HAVE_TIMEGETTIME 1 # define HAVE_QUERYPERFORMANCECOUNTER 1 # define HAVE_Sleep 1 #endif #ifndef WIN32 #if defined(CLOCK_MONOTONIC) || defined(CLOCK_HIRES) # define HAVE_CLOCKGETTIME 1 #endif // FIXME improve this check, manpage hints to DST_NONE # define HAVE_GETTIMEOFDAY 1 #endif #ifndef WIN32 // on Win32, select() cannot be used with all three FD list args being NULL according to MSDN // (so much for POSIX...) # ifdef FD_SET # define HAVE_SELECT 1 # endif #endif #ifndef WIN32 // FIXME improve this check # define HAVE_USLEEP 1 #endif // this one is referenced elsewhere cvar_t sys_usenoclockbutbenchmark = {CVAR_SAVE, "sys_usenoclockbutbenchmark", "0", "don't use ANY real timing, and simulate a clock (for benchmarking); the game then runs as fast as possible. Run a QC mod with bots that does some stuff, then does a quit at the end, to benchmark a server. NEVER do this on a public server."}; // these are not static cvar_t sys_debugsleep = {0, "sys_debugsleep", "0", "write requested and attained sleep times to standard output, to be used with gnuplot"}; static cvar_t sys_usesdlgetticks = {CVAR_SAVE, "sys_usesdlgetticks", "0", "use SDL_GetTicks() timer (less accurate, for debugging)"}; static cvar_t sys_usesdldelay = {CVAR_SAVE, "sys_usesdldelay", "0", "use SDL_Delay() (less accurate, for debugging)"}; #if HAVE_QUERYPERFORMANCECOUNTER static cvar_t sys_usequeryperformancecounter = {CVAR_SAVE, "sys_usequeryperformancecounter", "0", "use windows QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) for timing rather than timeGetTime function (which has issues on some motherboards)"}; #endif #if HAVE_CLOCKGETTIME static cvar_t sys_useclockgettime = {CVAR_SAVE, "sys_useclockgettime", "1", "use POSIX clock_gettime function (not adjusted by NTP on some older Linux kernels) for timing rather than gettimeofday (which has issues if the system time is stepped by ntpdate, or apparently on some Xen installations)"}; #endif static double benchmark_time; // actually always contains an integer amount of milliseconds, will eventually "overflow" void Sys_Init_Commands (void) { Cvar_RegisterVariable(&sys_debugsleep); Cvar_RegisterVariable(&sys_usenoclockbutbenchmark); #if HAVE_TIMEGETTIME || HAVE_QUERYPERFORMANCECOUNTER || HAVE_CLOCKGETTIME || HAVE_GETTIMEOFDAY if(sys_supportsdlgetticks) { Cvar_RegisterVariable(&sys_usesdlgetticks); Cvar_RegisterVariable(&sys_usesdldelay); } #endif #if HAVE_QUERYPERFORMANCECOUNTER Cvar_RegisterVariable(&sys_usequeryperformancecounter); #endif #if HAVE_CLOCKGETTIME Cvar_RegisterVariable(&sys_useclockgettime); #endif } double Sys_DirtyTime(void) { // first all the OPTIONAL timers // benchmark timer (fake clock) if(sys_usenoclockbutbenchmark.integer) { double old_benchmark_time = benchmark_time; benchmark_time += 1; if(benchmark_time == old_benchmark_time) Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); return benchmark_time * 0.000001; } #if HAVE_QUERYPERFORMANCECOUNTER if (sys_usequeryperformancecounter.integer) { // LordHavoc: note to people modifying this code, DWORD is specifically defined as an unsigned 32bit number, therefore the 65536.0 * 65536.0 is fine. // QueryPerformanceCounter // platform: // Windows 95/98/ME/NT/2000/XP // features: // very accurate (CPU cycles) // known issues: // does not necessarily match realtime too well (tends to get faster and faster in win98) // wraps around occasionally on some platforms (depends on CPU speed and probably other unknown factors) double timescale; LARGE_INTEGER PerformanceFreq; LARGE_INTEGER PerformanceCount; if (QueryPerformanceFrequency (&PerformanceFreq)) { QueryPerformanceCounter (&PerformanceCount); #ifdef __BORLANDC__ timescale = 1.0 / ((double) PerformanceFreq.u.LowPart + (double) PerformanceFreq.u.HighPart * 65536.0 * 65536.0); return ((double) PerformanceCount.u.LowPart + (double) PerformanceCount.u.HighPart * 65536.0 * 65536.0) * timescale; #else timescale = 1.0 / ((double) PerformanceFreq.LowPart + (double) PerformanceFreq.HighPart * 65536.0 * 65536.0); return ((double) PerformanceCount.LowPart + (double) PerformanceCount.HighPart * 65536.0 * 65536.0) * timescale; #endif } else { Con_Printf("No hardware timer available\n"); // fall back to other clock sources Cvar_SetValueQuick(&sys_usequeryperformancecounter, false); } } #endif #if HAVE_CLOCKGETTIME if (sys_useclockgettime.integer) { struct timespec ts; # ifdef CLOCK_MONOTONIC // linux clock_gettime(CLOCK_MONOTONIC, &ts); # else // sunos clock_gettime(CLOCK_HIGHRES, &ts); # endif return (double) ts.tv_sec + ts.tv_nsec / 1000000000.0; } #endif // now all the FALLBACK timers if(sys_supportsdlgetticks && sys_usesdlgetticks.integer) return (double) Sys_SDL_GetTicks() / 1000.0; #if HAVE_GETTIMEOFDAY { struct timeval tp; gettimeofday(&tp, NULL); return (double) tp.tv_sec + tp.tv_usec / 1000000.0; } #elif HAVE_TIMEGETTIME { static int firsttimegettime = true; // timeGetTime // platform: // Windows 95/98/ME/NT/2000/XP // features: // reasonable accuracy (millisecond) // issues: // wraps around every 47 days or so (but this is non-fatal to us, odd times are rejected, only causes a one frame stutter) // make sure the timer is high precision, otherwise different versions of windows have varying accuracy if (firsttimegettime) { timeBeginPeriod(1); firsttimegettime = false; } return (double) timeGetTime() / 1000.0; } #else // fallback for using the SDL timer if no other timer is available // this calls Sys_Error() if not linking against SDL return (double) Sys_SDL_GetTicks() / 1000.0; #endif } void Sys_Sleep(int microseconds) { double t = 0; if(sys_usenoclockbutbenchmark.integer) { if(microseconds) { double old_benchmark_time = benchmark_time; benchmark_time += microseconds; if(benchmark_time == old_benchmark_time) Sys_Error("sys_usenoclockbutbenchmark cannot run any longer, sorry"); } return; } if(sys_debugsleep.integer) { t = Sys_DirtyTime(); } if(sys_supportsdlgetticks && sys_usesdldelay.integer) { Sys_SDL_Delay(microseconds / 1000); } #if HAVE_SELECT else { struct timeval tv; tv.tv_sec = microseconds / 1000000; tv.tv_usec = microseconds % 1000000; select(0, NULL, NULL, NULL, &tv); } #elif HAVE_USLEEP else { usleep(microseconds); } #elif HAVE_Sleep else { Sleep(microseconds / 1000); } #else else { Sys_SDL_Delay(microseconds / 1000); } #endif if(sys_debugsleep.integer) { t = Sys_DirtyTime() - t; Sys_PrintfToTerminal("%d %d # debugsleep\n", microseconds, (unsigned int)(t * 1000000)); } } void Sys_PrintfToTerminal(const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); Sys_PrintToTerminal(msg); } #ifndef WIN32 static const char *Sys_FindInPATH(const char *name, char namesep, const char *PATH, char pathsep, char *buf, size_t bufsize) { const char *p = PATH; const char *q; if(p && name) { while((q = strchr(p, ':'))) { dpsnprintf(buf, bufsize, "%.*s%c%s", (int)(q-p), p, namesep, name); if(FS_SysFileExists(buf)) return buf; p = q + 1; } if(!q) // none found - try the last item { dpsnprintf(buf, bufsize, "%s%c%s", p, namesep, name); if(FS_SysFileExists(buf)) return buf; } } return name; } #endif static const char *Sys_FindExecutableName(void) { #if defined(WIN32) return com_argv[0]; #else static char exenamebuf[MAX_OSPATH+1]; ssize_t n = -1; #if defined(__FreeBSD__) n = readlink("/proc/curproc/file", exenamebuf, sizeof(exenamebuf)-1); #elif defined(__linux__) n = readlink("/proc/self/exe", exenamebuf, sizeof(exenamebuf)-1); #endif if(n > 0 && (size_t)(n) < sizeof(exenamebuf)) { exenamebuf[n] = 0; return exenamebuf; } if(strchr(com_argv[0], '/')) return com_argv[0]; // possibly a relative path else return Sys_FindInPATH(com_argv[0], '/', getenv("PATH"), ':', exenamebuf, sizeof(exenamebuf)); #endif } void Sys_ProvideSelfFD(void) { if(com_selffd != -1) return; com_selffd = FS_SysOpenFD(Sys_FindExecutableName(), "rb", false); } // for x86 cpus only... (x64 has SSE2_PRESENT) #if defined(SSE_POSSIBLE) && !defined(SSE2_PRESENT) // code from SDL, shortened as we can expect CPUID to work static int CPUID_Features(void) { int features = 0; # if defined(__GNUC__) && defined(__i386__) __asm__ ( " movl %%ebx,%%edi\n" " xorl %%eax,%%eax \n" " incl %%eax \n" " cpuid # Get family/model/stepping/features\n" " movl %%edx,%0 \n" " movl %%edi,%%ebx\n" : "=m" (features) : : "%eax", "%ecx", "%edx", "%edi" ); # elif (defined(_MSC_VER) && defined(_M_IX86)) || defined(__WATCOMC__) __asm { xor eax, eax inc eax cpuid ; Get family/model/stepping/features mov features, edx } # else # error SSE_POSSIBLE set but no CPUID implementation # endif return features; } #endif #ifdef SSE_POSSIBLE qboolean Sys_HaveSSE(void) { // COMMANDLINEOPTION: SSE: -nosse disables SSE support and detection if(COM_CheckParm("-nosse")) return false; #ifdef SSE_PRESENT return true; #else // COMMANDLINEOPTION: SSE: -forcesse enables SSE support and disables detection if(COM_CheckParm("-forcesse") || COM_CheckParm("-forcesse2")) return true; if(CPUID_Features() & (1 << 25)) return true; return false; #endif } qboolean Sys_HaveSSE2(void) { // COMMANDLINEOPTION: SSE2: -nosse2 disables SSE2 support and detection if(COM_CheckParm("-nosse") || COM_CheckParm("-nosse2")) return false; #ifdef SSE2_PRESENT return true; #else // COMMANDLINEOPTION: SSE2: -forcesse2 enables SSE2 support and disables detection if(COM_CheckParm("-forcesse2")) return true; if((CPUID_Features() & (3 << 25)) == (3 << 25)) // SSE is 1<<25, SSE2 is 1<<26 return true; return false; #endif } #endif /// called to set process priority for dedicated servers #if defined(__linux__) #include #include static int nicelevel; static qboolean nicepossible; static qboolean isnice; void Sys_InitProcessNice (void) { struct rlimit lim; nicepossible = false; if(COM_CheckParm("-nonice")) return; errno = 0; nicelevel = getpriority(PRIO_PROCESS, 0); if(errno) { Con_Printf("Kernel does not support reading process priority - cannot use niceness\n"); return; } if(getrlimit(RLIMIT_NICE, &lim)) { Con_Printf("Kernel does not support lowering nice level again - cannot use niceness\n"); return; } if(lim.rlim_cur != RLIM_INFINITY && nicelevel < (int) (20 - lim.rlim_cur)) { Con_Printf("Current nice level is below the soft limit - cannot use niceness\n"); return; } nicepossible = true; isnice = false; } void Sys_MakeProcessNice (void) { if(!nicepossible) return; if(isnice) return; Con_DPrintf("Process is becoming 'nice'...\n"); if(setpriority(PRIO_PROCESS, 0, 19)) Con_Printf("Failed to raise nice level to %d\n", 19); isnice = true; } void Sys_MakeProcessMean (void) { if(!nicepossible) return; if(!isnice) return; Con_DPrintf("Process is becoming 'mean'...\n"); if(setpriority(PRIO_PROCESS, 0, nicelevel)) Con_Printf("Failed to lower nice level to %d\n", nicelevel); isnice = false; } #else void Sys_InitProcessNice (void) { } void Sys_MakeProcessNice (void) { } void Sys_MakeProcessMean (void) { } #endif darkplaces/cl_collision.h0000664000175000017500000000314513067716216015015 0ustar kalevkalev #ifndef CL_COLLISION_H #define CL_COLLISION_H float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent); void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius); dp_model_t *CL_GetModelByIndex(int modelindex); dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed); void CL_LinkEdict(prvm_edict_t *ent); int CL_GenericHitSuperContentsMask(const prvm_edict_t *edict); trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities); trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces); trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities); trace_t CL_Cache_TraceLineSurfaces(const vec3_t start, const vec3_t end, int type, int hitsupercontentsmask, int skipsupercontentsmask); #define CL_PointSuperContents(point) (CL_TracePoint((point), sv_gameplayfix_swiminbmodels.integer ? MOVE_NOMONSTERS : MOVE_WORLDONLY, NULL, 0, 0, true, false, NULL, false).startsupercontents) #endif darkplaces/meshqueue.c0000664000175000017500000001160013067716220014326 0ustar kalevkalev #include "quakedef.h" #include "meshqueue.h" typedef struct meshqueue_s { struct meshqueue_s *next; void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); const entity_render_t *ent; int surfacenumber; const rtlight_t *rtlight; float dist; dptransparentsortcategory_t category; } meshqueue_t; int trans_sortarraysize; meshqueue_t **trans_hash = NULL; meshqueue_t ***trans_hashpointer = NULL; float mqt_viewplanedist; float mqt_viewmaxdist; meshqueue_t *mqt_array; int mqt_count; int mqt_total; void R_MeshQueue_BeginScene(void) { mqt_count = 0; mqt_viewplanedist = DotProduct(r_refdef.view.origin, r_refdef.view.forward); mqt_viewmaxdist = 0; } void R_MeshQueue_AddTransparent(dptransparentsortcategory_t category, const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight) { meshqueue_t *mq; if (mqt_count >= mqt_total || !mqt_array) { int newtotal = max(1024, mqt_total * 2); meshqueue_t *newarray = (meshqueue_t *)Mem_Alloc(cls.permanentmempool, newtotal * sizeof(meshqueue_t)); if (mqt_array) { memcpy(newarray, mqt_array, mqt_total * sizeof(meshqueue_t)); Mem_Free(mqt_array); } mqt_array = newarray; mqt_total = newtotal; } mq = &mqt_array[mqt_count++]; mq->callback = callback; mq->ent = ent; mq->surfacenumber = surfacenumber; mq->rtlight = rtlight; mq->category = category; if (r_transparent_useplanardistance.integer) mq->dist = DotProduct(center, r_refdef.view.forward) - mqt_viewplanedist; else mq->dist = VectorDistance(center, r_refdef.view.origin); mq->next = NULL; mqt_viewmaxdist = max(mqt_viewmaxdist, mq->dist); } void R_MeshQueue_RenderTransparent(void) { int i, hashindex, maxhashindex, batchnumsurfaces; float distscale; const entity_render_t *ent; const rtlight_t *rtlight; void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfaceindices); int batchsurfaceindex[MESHQUEUE_TRANSPARENT_BATCHSIZE]; meshqueue_t *mqt; if (!mqt_count) return; // check for bad cvars if (r_transparent_sortarraysize.integer < 1 || r_transparent_sortarraysize.integer > 32768) Cvar_SetValueQuick(&r_transparent_sortarraysize, bound(1, r_transparent_sortarraysize.integer, 32768)); if (r_transparent_sortmindist.integer < 1 || r_transparent_sortmindist.integer >= r_transparent_sortmaxdist.integer) Cvar_SetValueQuick(&r_transparent_sortmindist, 0); if (r_transparent_sortmaxdist.integer < r_transparent_sortmindist.integer || r_transparent_sortmaxdist.integer > 32768) Cvar_SetValueQuick(&r_transparent_sortmaxdist, bound(r_transparent_sortmindist.integer, r_transparent_sortmaxdist.integer, 32768)); // update hash array if (trans_sortarraysize != r_transparent_sortarraysize.integer) { trans_sortarraysize = r_transparent_sortarraysize.integer; if (trans_hash) Mem_Free(trans_hash); trans_hash = (meshqueue_t **)Mem_Alloc(cls.permanentmempool, sizeof(meshqueue_t *) * trans_sortarraysize); if (trans_hashpointer) Mem_Free(trans_hashpointer); trans_hashpointer = (meshqueue_t ***)Mem_Alloc(cls.permanentmempool, sizeof(meshqueue_t **) * trans_sortarraysize); } // build index memset(trans_hash, 0, sizeof(meshqueue_t *) * trans_sortarraysize); for (i = 0; i < trans_sortarraysize; i++) trans_hashpointer[i] = &trans_hash[i]; distscale = (trans_sortarraysize - 1) / min(mqt_viewmaxdist, r_transparent_sortmaxdist.integer); maxhashindex = trans_sortarraysize - 1; for (i = 0, mqt = mqt_array; i < mqt_count; i++, mqt++) { switch(mqt->category) { default: case TRANSPARENTSORT_HUD: hashindex = 0; break; case TRANSPARENTSORT_DISTANCE: // this could use a reduced range if we need more categories hashindex = bound(0, (int)(bound(0, mqt->dist - r_transparent_sortmindist.integer, r_transparent_sortmaxdist.integer) * distscale), maxhashindex); break; case TRANSPARENTSORT_SKY: hashindex = maxhashindex; break; } // link to tail of hash chain (to preserve render order) mqt->next = NULL; *trans_hashpointer[hashindex] = mqt; trans_hashpointer[hashindex] = &mqt->next; } callback = NULL; ent = NULL; rtlight = NULL; batchnumsurfaces = 0; // draw for (i = maxhashindex; i >= 0; i--) { if (trans_hash[i]) { for (mqt = trans_hash[i]; mqt; mqt = mqt->next) { if (ent != mqt->ent || rtlight != mqt->rtlight || callback != mqt->callback || batchnumsurfaces >= MESHQUEUE_TRANSPARENT_BATCHSIZE) { if (batchnumsurfaces) callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); batchnumsurfaces = 0; ent = mqt->ent; rtlight = mqt->rtlight; callback = mqt->callback; } batchsurfaceindex[batchnumsurfaces++] = mqt->surfacenumber; } } } if (batchnumsurfaces) callback(ent, rtlight, batchnumsurfaces, batchsurfaceindex); mqt_count = 0; } darkplaces/darkplaces.rc0000664000175000017500000000116713067716220014627 0ustar kalevkalev#include // include for version info constants 107 ICON "darkplaces.ico" 1 VERSIONINFO FILEVERSION 1,0,0,0 PRODUCTVERSION 1,0,0,0 FILETYPE VFT_APP { BLOCK "StringFileInfo" { BLOCK "040904E4" { VALUE "CompanyName", "DarkPlaces Contributors" VALUE "FileVersion", "1.0" VALUE "FileDescription", "DarkPlaces Game Engine" VALUE "InternalName", "darkplaces.exe" VALUE "LegalCopyright", "id Software, Forest Hale, and contributors" VALUE "LegalTrademarks", "" VALUE "OriginalFilename", "darkplaces.exe" VALUE "ProductName", "DarkPlaces" VALUE "ProductVersion", "1.0" } } } darkplaces/fractalnoise.c0000664000175000017500000002332413067716220015005 0ustar kalevkalev #include "quakedef.h" void fractalnoise(unsigned char *noise, int size, int startgrid) { int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower; int *noisebuf; #define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)] for (sizepower = 0;(1 << sizepower) < size;sizepower++); if (size != (1 << sizepower)) { Con_Printf("fractalnoise: size must be power of 2\n"); return; } for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); if (startgrid != (1 << gridpower)) { Con_Printf("fractalnoise: grid must be power of 2\n"); return; } startgrid = bound(0, startgrid, size); amplitude = 0xFFFF; // this gets halved before use noisebuf = (int *)Mem_Alloc(tempmempool, size*size*sizeof(int)); memset(noisebuf, 0, size*size*sizeof(int)); for (g2 = startgrid;g2;g2 >>= 1) { // brownian motion (at every smaller level there is random behavior) amplitude >>= 1; for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) n(x,y) += (rand()&litude); g = g2 >> 1; if (g) { // subdivide, diamond-square algorithm (really this has little to do with squares) // diamond for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2; // square for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) { n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2; n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2; } } } // find range of noise values min = max = 0; for (y = 0;y < size;y++) for (x = 0;x < size;x++) { if (n(x,y) < min) min = n(x,y); if (n(x,y) > max) max = n(x,y); } max -= min; max++; // normalize noise and copy to output for (y = 0;y < size;y++) for (x = 0;x < size;x++) *noise++ = (unsigned char) (((n(x,y) - min) * 256) / max); Mem_Free(noisebuf); #undef n } // unnormalized, used for explosions mainly, does not allocate/free memory (hence the name quick) void fractalnoisequick(unsigned char *noise, int size, int startgrid) { int x, y, g, g2, amplitude, size1 = size - 1, sizepower, gridpower; #define n(x,y) noise[((y)&size1)*size+((x)&size1)] for (sizepower = 0;(1 << sizepower) < size;sizepower++); if (size != (1 << sizepower)) { Con_Printf("fractalnoise: size must be power of 2\n"); return; } for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++); if (startgrid != (1 << gridpower)) { Con_Printf("fractalnoise: grid must be power of 2\n"); return; } startgrid = bound(0, startgrid, size); amplitude = 255; // this gets halved before use memset(noise, 0, size*size); for (g2 = startgrid;g2;g2 >>= 1) { // brownian motion (at every smaller level there is random behavior) amplitude >>= 1; for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) n(x,y) += (rand()&litude); g = g2 >> 1; if (g) { // subdivide, diamond-square algorithm (really this has little to do with squares) // diamond for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) n(x+g,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x,y+g2) + (int) n(x+g2,y+g2)) >> 2); // square for (y = 0;y < size;y += g2) for (x = 0;x < size;x += g2) { n(x+g,y) = (unsigned char) (((int) n(x,y) + (int) n(x+g2,y) + (int) n(x+g,y-g) + (int) n(x+g,y+g)) >> 2); n(x,y+g) = (unsigned char) (((int) n(x,y) + (int) n(x,y+g2) + (int) n(x-g,y+g) + (int) n(x+g,y+g)) >> 2); } } } #undef n } #define NOISE_SIZE 256 #define NOISE_MASK 255 float noise4f(float x, float y, float z, float w) { int i; int index[4][2]; float frac[4][2]; float v[4]; static float noisetable[NOISE_SIZE]; static int r[NOISE_SIZE]; // LordHavoc: this is inspired by code I saw in Quake3, however I think my // version is much cleaner and substantially faster as well // // the following changes were made: // 1. for the permutation indexing (r[] array in this code) I substituted // the ^ operator (which never overflows) for the original addition and // masking code, this should not have any effect on quality. // 2. removed the outermost randomization array lookup. // (it really wasn't necessary, it's fine if X indexes the array // directly without permutation indexing) // 3. reimplemented the blending using frac[] arrays rather than a macro. // (the original macro read one parameter twice - not good) // 4. cleaned up the code by using 4 nested loops to make it read nicer // (but then I unrolled it completely for speed, it still looks nicer). if (!noisetable[0]) { // noisetable is a random-ish series of float values in +/- 1 range for (i = 0;i < NOISE_SIZE;i++) noisetable[i] = (rand() / (double)RAND_MAX) * 2 - 1; // r is a remapping table to make each dimension of the index have different indexing behavior for (i = 0;i < NOISE_SIZE;i++) r[i] = (int)(rand() * (double)NOISE_SIZE / ((double)RAND_MAX + 1)) & NOISE_MASK; // that & is only needed if RAND_MAX is > the range of double, which isn't the case on most platforms } frac[0][1] = x - floor(x);index[0][0] = ((int)floor(x)) & NOISE_MASK; frac[1][1] = y - floor(y);index[1][0] = ((int)floor(y)) & NOISE_MASK; frac[2][1] = z - floor(z);index[2][0] = ((int)floor(z)) & NOISE_MASK; frac[3][1] = w - floor(w);index[3][0] = ((int)floor(w)) & NOISE_MASK; for (i = 0;i < 4;i++) frac[i][0] = 1 - frac[i][1]; for (i = 0;i < 4;i++) index[i][1] = (index[i][0] < NOISE_SIZE - 1) ? (index[i][0] + 1) : 0; #if 1 // short version v[0] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); v[1] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); v[2] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]); v[3] = frac[1][0] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]) + frac[1][1] * (frac[0][0] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]] + frac[0][1] * noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]); return frac[3][0] * (frac[2][0] * v[0] + frac[2][1] * v[1]) + frac[3][1] * (frac[2][0] * v[2] + frac[2][1] * v[3]); #elif 1 // longer version v[ 0] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; v[ 1] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; v[ 2] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; v[ 3] = noisetable[r[r[r[index[3][0]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; v[ 4] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; v[ 5] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; v[ 6] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; v[ 7] = noisetable[r[r[r[index[3][0]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; v[ 8] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][0]]; v[ 9] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][0]] ^ index[0][1]]; v[10] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][0]]; v[11] = noisetable[r[r[r[index[3][1]] ^ index[2][0]] ^ index[1][1]] ^ index[0][1]]; v[12] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][0]]; v[13] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][0]] ^ index[0][1]]; v[14] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][0]]; v[15] = noisetable[r[r[r[index[3][1]] ^ index[2][1]] ^ index[1][1]] ^ index[0][1]]; v[16] = frac[0][0] * v[ 0] + frac[0][1] * v[ 1]; v[17] = frac[0][0] * v[ 2] + frac[0][1] * v[ 3]; v[18] = frac[0][0] * v[ 4] + frac[0][1] * v[ 5]; v[19] = frac[0][0] * v[ 6] + frac[0][1] * v[ 7]; v[20] = frac[0][0] * v[ 8] + frac[0][1] * v[ 9]; v[21] = frac[0][0] * v[10] + frac[0][1] * v[11]; v[22] = frac[0][0] * v[12] + frac[0][1] * v[13]; v[23] = frac[0][0] * v[14] + frac[0][1] * v[15]; v[24] = frac[1][0] * v[16] + frac[1][1] * v[17]; v[25] = frac[1][0] * v[18] + frac[1][1] * v[19]; v[26] = frac[1][0] * v[20] + frac[1][1] * v[21]; v[27] = frac[1][0] * v[22] + frac[1][1] * v[23]; v[28] = frac[2][0] * v[24] + frac[2][1] * v[25]; v[29] = frac[2][0] * v[26] + frac[2][1] * v[27]; return frac[3][0] * v[28] + frac[3][1] * v[29]; #else // the algorithm... for (l = 0;l < 2;l++) { for (k = 0;k < 2;k++) { for (j = 0;j < 2;j++) { for (i = 0;i < 2;i++) v[l][k][j][i] = noisetable[r[r[r[index[l][3]] ^ index[k][2]] ^ index[j][1]] ^ index[i][0]]; v[l][k][j][2] = frac[0][0] * v[l][k][j][0] + frac[0][1] * v[l][k][j][1]; } v[l][k][2][2] = frac[1][0] * v[l][k][0][2] + frac[1][1] * v[l][k][1][2]; } v[l][2][2][2] = frac[2][0] * v[l][0][2][2] + frac[2][1] * v[l][1][2][2]; } v[2][2][2][2] = frac[3][0] * v[0][2][2][2] + frac[3][1] * v[1][2][2][2]; #endif } darkplaces/thread.h0000664000175000017500000000514113067716222013606 0ustar kalevkalev#ifndef THREAD_H // enable Sys_PrintfToTerminal calls on nearly every threading call //#define THREADDEBUG //#define THREADDISABLE // use recursive mutex (non-posix) extensions in thread_pthread #define THREADRECURSIVE #define Thread_CreateMutex() (_Thread_CreateMutex(__FILE__, __LINE__)) #define Thread_DestroyMutex(m) (_Thread_DestroyMutex(m, __FILE__, __LINE__)) #define Thread_LockMutex(m) (_Thread_LockMutex(m, __FILE__, __LINE__)) #define Thread_UnlockMutex(m) (_Thread_UnlockMutex(m, __FILE__, __LINE__)) #define Thread_CreateCond() (_Thread_CreateCond(__FILE__, __LINE__)) #define Thread_DestroyCond(cond) (_Thread_DestroyCond(cond, __FILE__, __LINE__)) #define Thread_CondSignal(cond) (_Thread_CondSignal(cond, __FILE__, __LINE__)) #define Thread_CondBroadcast(cond) (_Thread_CondBroadcast(cond, __FILE__, __LINE__)) #define Thread_CondWait(cond, mutex) (_Thread_CondWait(cond, mutex, __FILE__, __LINE__)) #define Thread_CreateThread(fn, data) (_Thread_CreateThread(fn, data, __FILE__, __LINE__)) #define Thread_WaitThread(thread, retval) (_Thread_WaitThread(thread, retval, __FILE__, __LINE__)) #define Thread_CreateBarrier(count) (_Thread_CreateBarrier(count, __FILE__, __LINE__)) #define Thread_DestroyBarrier(barrier) (_Thread_DestroyBarrier(barrier, __FILE__, __LINE__)) #define Thread_WaitBarrier(barrier) (_Thread_WaitBarrier(barrier, __FILE__, __LINE__)) int Thread_Init(void); void Thread_Shutdown(void); qboolean Thread_HasThreads(void); void *_Thread_CreateMutex(const char *filename, int fileline); void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline); int _Thread_LockMutex(void *mutex, const char *filename, int fileline); int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline); void *_Thread_CreateCond(const char *filename, int fileline); void _Thread_DestroyCond(void *cond, const char *filename, int fileline); int _Thread_CondSignal(void *cond, const char *filename, int fileline); int _Thread_CondBroadcast(void *cond, const char *filename, int fileline); int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline); void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline); int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline); void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline); void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline); void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline); #endif darkplaces/gl_backend.c0000664000175000017500000053104213067716220014405 0ustar kalevkalev #include "quakedef.h" #include "cl_collision.h" #include "dpsoftrast.h" #ifdef SUPPORTD3D #include extern LPDIRECT3DDEVICE9 vid_d3d9dev; extern D3DCAPS9 vid_d3d9caps; #endif // on GLES we have to use some proper #define's #ifndef GL_FRAMEBUFFER #define GL_FRAMEBUFFER 0x8D40 #define GL_DEPTH_ATTACHMENT 0x8D00 #define GL_COLOR_ATTACHMENT0 0x8CE0 #define GL_INVALID_FRAMEBUFFER_OPERATION 0x0506 #endif #ifndef GL_COLOR_ATTACHMENT1 #define GL_COLOR_ATTACHMENT1 0x8CE1 #define GL_COLOR_ATTACHMENT2 0x8CE2 #define GL_COLOR_ATTACHMENT3 0x8CE3 #define GL_COLOR_ATTACHMENT4 0x8CE4 #define GL_COLOR_ATTACHMENT5 0x8CE5 #define GL_COLOR_ATTACHMENT6 0x8CE6 #define GL_COLOR_ATTACHMENT7 0x8CE7 #define GL_COLOR_ATTACHMENT8 0x8CE8 #define GL_COLOR_ATTACHMENT9 0x8CE9 #define GL_COLOR_ATTACHMENT10 0x8CEA #define GL_COLOR_ATTACHMENT11 0x8CEB #define GL_COLOR_ATTACHMENT12 0x8CEC #define GL_COLOR_ATTACHMENT13 0x8CED #define GL_COLOR_ATTACHMENT14 0x8CEE #define GL_COLOR_ATTACHMENT15 0x8CEF #endif #ifndef GL_ARRAY_BUFFER #define GL_ARRAY_BUFFER 0x8892 #define GL_ELEMENT_ARRAY_BUFFER 0x8893 #endif //#ifndef GL_VERTEX_ARRAY //#define GL_VERTEX_ARRAY 0x8074 //#define GL_COLOR_ARRAY 0x8076 //#define GL_TEXTURE_COORD_ARRAY 0x8078 //#endif #ifndef GL_TEXTURE0 #define GL_TEXTURE0 0x84C0 #define GL_TEXTURE1 0x84C1 #define GL_TEXTURE2 0x84C2 #define GL_TEXTURE3 0x84C3 #define GL_TEXTURE4 0x84C4 #define GL_TEXTURE5 0x84C5 #define GL_TEXTURE6 0x84C6 #define GL_TEXTURE7 0x84C7 #define GL_TEXTURE8 0x84C8 #define GL_TEXTURE9 0x84C9 #define GL_TEXTURE10 0x84CA #define GL_TEXTURE11 0x84CB #define GL_TEXTURE12 0x84CC #define GL_TEXTURE13 0x84CD #define GL_TEXTURE14 0x84CE #define GL_TEXTURE15 0x84CF #define GL_TEXTURE16 0x84D0 #define GL_TEXTURE17 0x84D1 #define GL_TEXTURE18 0x84D2 #define GL_TEXTURE19 0x84D3 #define GL_TEXTURE20 0x84D4 #define GL_TEXTURE21 0x84D5 #define GL_TEXTURE22 0x84D6 #define GL_TEXTURE23 0x84D7 #define GL_TEXTURE24 0x84D8 #define GL_TEXTURE25 0x84D9 #define GL_TEXTURE26 0x84DA #define GL_TEXTURE27 0x84DB #define GL_TEXTURE28 0x84DC #define GL_TEXTURE29 0x84DD #define GL_TEXTURE30 0x84DE #define GL_TEXTURE31 0x84DF #endif #ifndef GL_TEXTURE_3D #define GL_TEXTURE_3D 0x806F #endif #ifndef GL_TEXTURE_CUBE_MAP #define GL_TEXTURE_CUBE_MAP 0x8513 #endif //#ifndef GL_MODELVIEW //#define GL_MODELVIEW 0x1700 //#endif //#ifndef GL_PROJECTION //#define GL_PROJECTION 0x1701 //#endif //#ifndef GL_DECAL //#define GL_DECAL 0x2101 //#endif //#ifndef GL_INTERPOLATE //#define GL_INTERPOLATE 0x8575 //#endif #define MAX_RENDERTARGETS 4 cvar_t gl_mesh_drawrangeelements = {0, "gl_mesh_drawrangeelements", "1", "use glDrawRangeElements function if available instead of glDrawElements (for performance comparisons or bug testing)"}; cvar_t gl_mesh_testmanualfeeding = {0, "gl_mesh_testmanualfeeding", "0", "use glBegin(GL_TRIANGLES);glTexCoord2f();glVertex3f();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements)"}; cvar_t gl_paranoid = {0, "gl_paranoid", "0", "enables OpenGL error checking and other tests"}; cvar_t gl_printcheckerror = {0, "gl_printcheckerror", "0", "prints all OpenGL error checks, useful to identify location of driver crashes"}; cvar_t r_render = {0, "r_render", "1", "enables rendering 3D views (you want this on!)"}; cvar_t r_renderview = {0, "r_renderview", "1", "enables rendering 3D views (you want this on!)"}; cvar_t r_waterwarp = {CVAR_SAVE, "r_waterwarp", "1", "warp view while underwater"}; cvar_t gl_polyblend = {CVAR_SAVE, "gl_polyblend", "1", "tints view while underwater, hurt, etc"}; cvar_t gl_dither = {CVAR_SAVE, "gl_dither", "1", "enables OpenGL dithering (16bit looks bad with this off)"}; cvar_t gl_vbo = {CVAR_SAVE, "gl_vbo", "3", "make use of GL_ARB_vertex_buffer_object extension to store static geometry in video memory for faster rendering, 0 disables VBO allocation or use, 1 enables VBOs for vertex and triangle data, 2 only for vertex data, 3 for vertex data and triangle data of simple meshes (ones with only one surface)"}; cvar_t gl_vbo_dynamicvertex = {CVAR_SAVE, "gl_vbo_dynamicvertex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; cvar_t gl_vbo_dynamicindex = {CVAR_SAVE, "gl_vbo_dynamicindex", "0", "make use of GL_ARB_vertex_buffer_object extension when rendering dynamic (animated/procedural) geometry such as text and particles"}; cvar_t gl_fbo = {CVAR_SAVE, "gl_fbo", "1", "make use of GL_ARB_framebuffer_object extension to enable shadowmaps and other features using pixel formats different from the framebuffer"}; cvar_t v_flipped = {0, "v_flipped", "0", "mirror the screen (poor man's left handed mode)"}; qboolean v_flipped_state = false; r_viewport_t gl_viewport; matrix4x4_t gl_modelmatrix; matrix4x4_t gl_viewmatrix; matrix4x4_t gl_modelviewmatrix; matrix4x4_t gl_projectionmatrix; matrix4x4_t gl_modelviewprojectionmatrix; float gl_modelview16f[16]; float gl_modelviewprojection16f[16]; qboolean gl_modelmatrixchanged; int gl_maxdrawrangeelementsvertices; int gl_maxdrawrangeelementsindices; #ifdef DEBUGGL int gl_errornumber = 0; void GL_PrintError(int errornumber, const char *filename, int linenumber) { switch(errornumber) { #ifdef GL_INVALID_ENUM case GL_INVALID_ENUM: Con_Printf("GL_INVALID_ENUM at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_INVALID_VALUE case GL_INVALID_VALUE: Con_Printf("GL_INVALID_VALUE at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_INVALID_OPERATION case GL_INVALID_OPERATION: Con_Printf("GL_INVALID_OPERATION at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_STACK_OVERFLOW case GL_STACK_OVERFLOW: Con_Printf("GL_STACK_OVERFLOW at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_STACK_UNDERFLOW case GL_STACK_UNDERFLOW: Con_Printf("GL_STACK_UNDERFLOW at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_OUT_OF_MEMORY case GL_OUT_OF_MEMORY: Con_Printf("GL_OUT_OF_MEMORY at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_TABLE_TOO_LARGE case GL_TABLE_TOO_LARGE: Con_Printf("GL_TABLE_TOO_LARGE at %s:%i\n", filename, linenumber); break; #endif #ifdef GL_INVALID_FRAMEBUFFER_OPERATION case GL_INVALID_FRAMEBUFFER_OPERATION: Con_Printf("GL_INVALID_FRAMEBUFFER_OPERATION at %s:%i\n", filename, linenumber); break; #endif default: Con_Printf("GL UNKNOWN (%i) at %s:%i\n", errornumber, filename, linenumber); break; } } #endif #define BACKENDACTIVECHECK if (!gl_state.active) Sys_Error("GL backend function called when backend is not active"); void SCR_ScreenShot_f (void); typedef struct gltextureunit_s { int pointer_texcoord_components; int pointer_texcoord_gltype; size_t pointer_texcoord_stride; const void *pointer_texcoord_pointer; const r_meshbuffer_t *pointer_texcoord_vertexbuffer; size_t pointer_texcoord_offset; rtexture_t *texture; int t2d, t3d, tcubemap; int arrayenabled; int rgbscale, alphascale; int combine; int combinergb, combinealpha; // texmatrixenabled exists only to avoid unnecessary texmatrix compares int texmatrixenabled; matrix4x4_t matrix; } gltextureunit_t; typedef struct gl_state_s { int cullface; int cullfaceenable; int blendfunc1; int blendfunc2; qboolean blend; GLboolean depthmask; int colormask; // stored as bottom 4 bits: r g b a (3 2 1 0 order) int depthtest; int depthfunc; float depthrange[2]; float polygonoffset[2]; int alphatest; int alphafunc; float alphafuncvalue; qboolean alphatocoverage; int scissortest; unsigned int unit; unsigned int clientunit; gltextureunit_t units[MAX_TEXTUREUNITS]; float color4f[4]; int lockrange_first; int lockrange_count; int vertexbufferobject; int elementbufferobject; int uniformbufferobject; int framebufferobject; int defaultframebufferobject; // deal with platforms that use a non-zero default fbo qboolean pointer_color_enabled; int pointer_vertex_components; int pointer_vertex_gltype; size_t pointer_vertex_stride; const void *pointer_vertex_pointer; const r_meshbuffer_t *pointer_vertex_vertexbuffer; size_t pointer_vertex_offset; int pointer_color_components; int pointer_color_gltype; size_t pointer_color_stride; const void *pointer_color_pointer; const r_meshbuffer_t *pointer_color_vertexbuffer; size_t pointer_color_offset; void *preparevertices_tempdata; size_t preparevertices_tempdatamaxsize; r_vertexgeneric_t *preparevertices_vertexgeneric; r_vertexmesh_t *preparevertices_vertexmesh; int preparevertices_numvertices; qboolean usevbo_staticvertex; qboolean usevbo_staticindex; qboolean usevbo_dynamicvertex; qboolean usevbo_dynamicindex; memexpandablearray_t meshbufferarray; qboolean active; #ifdef SUPPORTD3D // rtexture_t *d3drt_depthtexture; // rtexture_t *d3drt_colortextures[MAX_RENDERTARGETS]; IDirect3DSurface9 *d3drt_depthsurface; IDirect3DSurface9 *d3drt_colorsurfaces[MAX_RENDERTARGETS]; IDirect3DSurface9 *d3drt_backbufferdepthsurface; IDirect3DSurface9 *d3drt_backbuffercolorsurface; void *d3dvertexbuffer; void *d3dvertexdata; int d3dvertexsize; #endif } gl_state_t; static gl_state_t gl_state; /* note: here's strip order for a terrain row: 0--1--2--3--4 |\ |\ |\ |\ | | \| \| \| \| A--B--C--D--E clockwise A0B, 01B, B1C, 12C, C2D, 23D, D3E, 34E *elements++ = i + row; *elements++ = i; *elements++ = i + row + 1; *elements++ = i; *elements++ = i + 1; *elements++ = i + row + 1; for (y = 0;y < rows - 1;y++) { for (x = 0;x < columns - 1;x++) { i = y * rows + x; *elements++ = i + columns; *elements++ = i; *elements++ = i + columns + 1; *elements++ = i; *elements++ = i + 1; *elements++ = i + columns + 1; } } alternative: 0--1--2--3--4 | /| /|\ | /| |/ |/ | \|/ | A--B--C--D--E counterclockwise for (y = 0;y < rows - 1;y++) { for (x = 0;x < columns - 1;x++) { i = y * rows + x; *elements++ = i; *elements++ = i + columns; *elements++ = i + columns + 1; *elements++ = i + columns; *elements++ = i + columns + 1; *elements++ = i + 1; } } */ int polygonelement3i[(POLYGONELEMENTS_MAXPOINTS-2)*3]; unsigned short polygonelement3s[(POLYGONELEMENTS_MAXPOINTS-2)*3]; int quadelement3i[QUADELEMENTS_MAXQUADS*6]; unsigned short quadelement3s[QUADELEMENTS_MAXQUADS*6]; static void GL_VBOStats_f(void) { GL_Mesh_ListVBOs(true); } static void GL_Backend_ResetState(void); static void R_Mesh_InitVertexDeclarations(void); static void R_Mesh_DestroyVertexDeclarations(void); static void R_Mesh_SetUseVBO(void) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && (gl_vbo.integer == 1 || gl_vbo.integer == 3)) || vid.forcevbo; gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer && gl_vbo.integer) || vid.forcevbo; gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicindex.integer && gl_vbo.integer) || vid.forcevbo; break; case RENDERPATH_D3D9: gl_state.usevbo_staticvertex = gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; gl_state.usevbo_dynamicvertex = gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer && gl_vbo_dynamicindex.integer && gl_vbo.integer) || vid.forcevbo; break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: gl_state.usevbo_staticvertex = false; gl_state.usevbo_staticindex = false; gl_state.usevbo_dynamicvertex = false; gl_state.usevbo_dynamicindex = false; break; case RENDERPATH_GLES2: gl_state.usevbo_staticvertex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; gl_state.usevbo_staticindex = (vid.support.arb_vertex_buffer_object && gl_vbo.integer) || vid.forcevbo; gl_state.usevbo_dynamicvertex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicvertex.integer) || vid.forcevbo; gl_state.usevbo_dynamicindex = (vid.support.arb_vertex_buffer_object && gl_vbo_dynamicindex.integer) || vid.forcevbo; break; } } static void gl_backend_start(void) { memset(&gl_state, 0, sizeof(gl_state)); R_Mesh_InitVertexDeclarations(); R_Mesh_SetUseVBO(); Mem_ExpandableArray_NewArray(&gl_state.meshbufferarray, r_main_mempool, sizeof(r_meshbuffer_t), 128); Con_DPrintf("OpenGL backend started.\n"); CHECKGLERROR GL_Backend_ResetState(); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // fetch current fbo here (default fbo is not 0 on some GLES devices) if (vid.support.ext_framebuffer_object) qglGetIntegerv(GL_FRAMEBUFFER_BINDING, &gl_state.defaultframebufferobject); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } } static void gl_backend_shutdown(void) { Con_DPrint("OpenGL Backend shutting down\n"); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } if (gl_state.preparevertices_tempdata) Mem_Free(gl_state.preparevertices_tempdata); Mem_ExpandableArray_FreeArray(&gl_state.meshbufferarray); R_Mesh_DestroyVertexDeclarations(); memset(&gl_state, 0, sizeof(gl_state)); } static void gl_backend_newmap(void) { } static void gl_backend_devicelost(void) { int i, endindex; r_meshbuffer_t *buffer; #ifdef SUPPORTD3D gl_state.d3dvertexbuffer = NULL; #endif switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DSurface9_Release(gl_state.d3drt_backbufferdepthsurface); IDirect3DSurface9_Release(gl_state.d3drt_backbuffercolorsurface); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } endindex = (int)Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); for (i = 0;i < endindex;i++) { buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); if (!buffer || !buffer->isdynamic) continue; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (buffer->devicebuffer) { if (buffer->isindexbuffer) IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); else IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); buffer->devicebuffer = NULL; } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } } } static void gl_backend_devicerestored(void) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_GetDepthStencilSurface(vid_d3d9dev, &gl_state.d3drt_backbufferdepthsurface); IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, &gl_state.d3drt_backbuffercolorsurface); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } } void gl_backend_init(void) { int i; for (i = 0;i < POLYGONELEMENTS_MAXPOINTS - 2;i++) { polygonelement3s[i * 3 + 0] = 0; polygonelement3s[i * 3 + 1] = i + 1; polygonelement3s[i * 3 + 2] = i + 2; } // elements for rendering a series of quads as triangles for (i = 0;i < QUADELEMENTS_MAXQUADS;i++) { quadelement3s[i * 6 + 0] = i * 4; quadelement3s[i * 6 + 1] = i * 4 + 1; quadelement3s[i * 6 + 2] = i * 4 + 2; quadelement3s[i * 6 + 3] = i * 4; quadelement3s[i * 6 + 4] = i * 4 + 2; quadelement3s[i * 6 + 5] = i * 4 + 3; } for (i = 0;i < (POLYGONELEMENTS_MAXPOINTS - 2)*3;i++) polygonelement3i[i] = polygonelement3s[i]; for (i = 0;i < QUADELEMENTS_MAXQUADS*6;i++) quadelement3i[i] = quadelement3s[i]; Cvar_RegisterVariable(&r_render); Cvar_RegisterVariable(&r_renderview); Cvar_RegisterVariable(&r_waterwarp); Cvar_RegisterVariable(&gl_polyblend); Cvar_RegisterVariable(&v_flipped); Cvar_RegisterVariable(&gl_dither); Cvar_RegisterVariable(&gl_vbo); Cvar_RegisterVariable(&gl_vbo_dynamicvertex); Cvar_RegisterVariable(&gl_vbo_dynamicindex); Cvar_RegisterVariable(&gl_paranoid); Cvar_RegisterVariable(&gl_printcheckerror); Cvar_RegisterVariable(&gl_mesh_drawrangeelements); Cvar_RegisterVariable(&gl_mesh_testmanualfeeding); Cmd_AddCommand("gl_vbostats", GL_VBOStats_f, "prints a list of all buffer objects (vertex data and triangle elements) and total video memory used by them"); R_RegisterModule("GL_Backend", gl_backend_start, gl_backend_shutdown, gl_backend_newmap, gl_backend_devicelost, gl_backend_devicerestored); } void GL_SetMirrorState(qboolean state); void R_Viewport_TransformToScreen(const r_viewport_t *v, const vec4_t in, vec4_t out) { vec4_t temp; float iw; Matrix4x4_Transform4 (&v->viewmatrix, in, temp); Matrix4x4_Transform4 (&v->projectmatrix, temp, out); iw = 1.0f / out[3]; out[0] = v->x + (out[0] * iw + 1.0f) * v->width * 0.5f; // for an odd reason, inverting this is wrong for R_Shadow_ScissorForBBox (we then get badly scissored lights) //out[1] = v->y + v->height - (out[1] * iw + 1.0f) * v->height * 0.5f; out[1] = v->y + (out[1] * iw + 1.0f) * v->height * 0.5f; out[2] = v->z + (out[2] * iw + 1.0f) * v->depth * 0.5f; } void GL_Finish(void) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglFinish(); break; case RENDERPATH_D3D9: //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_Finish(); break; } } static int bboxedges[12][2] = { // top {0, 1}, // +X {0, 2}, // +Y {1, 3}, // Y, +X {2, 3}, // X, +Y // bottom {4, 5}, // +X {4, 6}, // +Y {5, 7}, // Y, +X {6, 7}, // X, +Y // verticals {0, 4}, // +Z {1, 5}, // X, +Z {2, 6}, // Y, +Z {3, 7}, // XY, +Z }; qboolean R_ScissorForBBox(const float *mins, const float *maxs, int *scissor) { int i, ix1, iy1, ix2, iy2; float x1, y1, x2, y2; vec4_t v, v2; float vertex[20][3]; int j, k; vec4_t plane4f; int numvertices; float corner[8][4]; float dist[8]; int sign[8]; float f; scissor[0] = r_refdef.view.viewport.x; scissor[1] = r_refdef.view.viewport.y; scissor[2] = r_refdef.view.viewport.width; scissor[3] = r_refdef.view.viewport.height; // if view is inside the box, just say yes it's visible if (BoxesOverlap(r_refdef.view.origin, r_refdef.view.origin, mins, maxs)) return false; // transform all corners that are infront of the nearclip plane VectorNegate(r_refdef.view.frustum[4].normal, plane4f); plane4f[3] = r_refdef.view.frustum[4].dist; numvertices = 0; for (i = 0;i < 8;i++) { Vector4Set(corner[i], (i & 1) ? maxs[0] : mins[0], (i & 2) ? maxs[1] : mins[1], (i & 4) ? maxs[2] : mins[2], 1); dist[i] = DotProduct4(corner[i], plane4f); sign[i] = dist[i] > 0; if (!sign[i]) { VectorCopy(corner[i], vertex[numvertices]); numvertices++; } } // if some points are behind the nearclip, add clipped edge points to make // sure that the scissor boundary is complete if (numvertices > 0 && numvertices < 8) { // add clipped edge points for (i = 0;i < 12;i++) { j = bboxedges[i][0]; k = bboxedges[i][1]; if (sign[j] != sign[k]) { f = dist[j] / (dist[j] - dist[k]); VectorLerp(corner[j], f, corner[k], vertex[numvertices]); numvertices++; } } } // if we have no points to check, it is behind the view plane if (!numvertices) return true; // if we have some points to transform, check what screen area is covered x1 = y1 = x2 = y2 = 0; v[3] = 1.0f; //Con_Printf("%i vertices to transform...\n", numvertices); for (i = 0;i < numvertices;i++) { VectorCopy(vertex[i], v); R_Viewport_TransformToScreen(&r_refdef.view.viewport, v, v2); //Con_Printf("%.3f %.3f %.3f %.3f transformed to %.3f %.3f %.3f %.3f\n", v[0], v[1], v[2], v[3], v2[0], v2[1], v2[2], v2[3]); if (i) { if (x1 > v2[0]) x1 = v2[0]; if (x2 < v2[0]) x2 = v2[0]; if (y1 > v2[1]) y1 = v2[1]; if (y2 < v2[1]) y2 = v2[1]; } else { x1 = x2 = v2[0]; y1 = y2 = v2[1]; } } // now convert the scissor rectangle to integer screen coordinates ix1 = (int)(x1 - 1.0f); //iy1 = vid.height - (int)(y2 - 1.0f); //iy1 = r_refdef.view.viewport.width + 2 * r_refdef.view.viewport.x - (int)(y2 - 1.0f); iy1 = (int)(y1 - 1.0f); ix2 = (int)(x2 + 1.0f); //iy2 = vid.height - (int)(y1 + 1.0f); //iy2 = r_refdef.view.viewport.height + 2 * r_refdef.view.viewport.y - (int)(y1 + 1.0f); iy2 = (int)(y2 + 1.0f); //Con_Printf("%f %f %f %f\n", x1, y1, x2, y2); // clamp it to the screen if (ix1 < r_refdef.view.viewport.x) ix1 = r_refdef.view.viewport.x; if (iy1 < r_refdef.view.viewport.y) iy1 = r_refdef.view.viewport.y; if (ix2 > r_refdef.view.viewport.x + r_refdef.view.viewport.width) ix2 = r_refdef.view.viewport.x + r_refdef.view.viewport.width; if (iy2 > r_refdef.view.viewport.y + r_refdef.view.viewport.height) iy2 = r_refdef.view.viewport.y + r_refdef.view.viewport.height; // if it is inside out, it's not visible if (ix2 <= ix1 || iy2 <= iy1) return true; // the light area is visible, set up the scissor rectangle scissor[0] = ix1; scissor[1] = iy1; scissor[2] = ix2 - ix1; scissor[3] = iy2 - iy1; // D3D Y coordinate is top to bottom, OpenGL is bottom to top, fix the D3D one switch(vid.renderpath) { case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: scissor[1] = vid.height - scissor[1] - scissor[3]; break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; } return false; } static void R_Viewport_ApplyNearClipPlaneFloatGL(const r_viewport_t *v, float *m, float normalx, float normaly, float normalz, float dist) { float q[4]; float d; float clipPlane[4], v3[3], v4[3]; float normal[3]; // This is inspired by Oblique Depth Projection from http://www.terathon.com/code/oblique.php VectorSet(normal, normalx, normaly, normalz); Matrix4x4_Transform3x3(&v->viewmatrix, normal, clipPlane); VectorScale(normal, -dist, v3); Matrix4x4_Transform(&v->viewmatrix, v3, v4); // FIXME: LordHavoc: I think this can be done more efficiently somehow but I can't remember the technique clipPlane[3] = -DotProduct(v4, clipPlane); #if 0 { // testing code for comparing results float clipPlane2[4]; VectorCopy4(clipPlane, clipPlane2); R_EntityMatrix(&identitymatrix); VectorSet(q, normal[0], normal[1], normal[2], -dist); qglClipPlane(GL_CLIP_PLANE0, q); qglGetClipPlane(GL_CLIP_PLANE0, q); VectorCopy4(q, clipPlane); } #endif // Calculate the clip-space corner point opposite the clipping plane // as (sgn(clipPlane.x), sgn(clipPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix q[0] = ((clipPlane[0] < 0.0f ? -1.0f : clipPlane[0] > 0.0f ? 1.0f : 0.0f) + m[8]) / m[0]; q[1] = ((clipPlane[1] < 0.0f ? -1.0f : clipPlane[1] > 0.0f ? 1.0f : 0.0f) + m[9]) / m[5]; q[2] = -1.0f; q[3] = (1.0f + m[10]) / m[14]; // Calculate the scaled plane vector d = 2.0f / DotProduct4(clipPlane, q); // Replace the third row of the projection matrix m[2] = clipPlane[0] * d; m[6] = clipPlane[1] * d; m[10] = clipPlane[2] * d + 1.0f; m[14] = clipPlane[3] * d; } void R_Viewport_InitOrtho(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float x1, float y1, float x2, float y2, float nearclip, float farclip, const float *nearplane) { float left = x1, right = x2, bottom = y2, top = y1, zNear = nearclip, zFar = farclip; float m[16]; memset(v, 0, sizeof(*v)); v->type = R_VIEWPORTTYPE_ORTHO; v->cameramatrix = *cameramatrix; v->x = x; v->y = y; v->z = 0; v->width = width; v->height = height; v->depth = 1; memset(m, 0, sizeof(m)); m[0] = 2/(right - left); m[5] = 2/(top - bottom); m[10] = -2/(zFar - zNear); m[12] = - (right + left)/(right - left); m[13] = - (top + bottom)/(top - bottom); m[14] = - (zFar + zNear)/(zFar - zNear); m[15] = 1; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: m[10] = -1/(zFar - zNear); m[14] = -zNear/(zFar-zNear); break; } v->screentodepth[0] = -farclip / (farclip - nearclip); v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); Matrix4x4_Invert_Full(&v->viewmatrix, &v->cameramatrix); if (nearplane) R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); #if 0 { vec4_t test1; vec4_t test2; Vector4Set(test1, (x1+x2)*0.5f, (y1+y2)*0.5f, 0.0f, 1.0f); R_Viewport_TransformToScreen(v, test1, test2); Con_Printf("%f %f %f -> %f %f %f\n", test1[0], test1[1], test1[2], test2[0], test2[1], test2[2]); } #endif } void R_Viewport_InitPerspective(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, float farclip, const float *nearplane) { matrix4x4_t tempmatrix, basematrix; float m[16]; memset(v, 0, sizeof(*v)); v->type = R_VIEWPORTTYPE_PERSPECTIVE; v->cameramatrix = *cameramatrix; v->x = x; v->y = y; v->z = 0; v->width = width; v->height = height; v->depth = 1; memset(m, 0, sizeof(m)); m[0] = 1.0 / frustumx; m[5] = 1.0 / frustumy; m[10] = -(farclip + nearclip) / (farclip - nearclip); m[11] = -1; m[14] = -2 * nearclip * farclip / (farclip - nearclip); v->screentodepth[0] = -farclip / (farclip - nearclip); v->screentodepth[1] = farclip * nearclip / (farclip - nearclip); Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); if (nearplane) R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); if(v_flipped.integer) { m[0] = -m[0]; m[4] = -m[4]; m[8] = -m[8]; m[12] = -m[12]; } Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); } void R_Viewport_InitPerspectiveInfinite(r_viewport_t *v, const matrix4x4_t *cameramatrix, int x, int y, int width, int height, float frustumx, float frustumy, float nearclip, const float *nearplane) { matrix4x4_t tempmatrix, basematrix; const float nudge = 1.0 - 1.0 / (1<<23); float m[16]; memset(v, 0, sizeof(*v)); v->type = R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP; v->cameramatrix = *cameramatrix; v->x = x; v->y = y; v->z = 0; v->width = width; v->height = height; v->depth = 1; memset(m, 0, sizeof(m)); m[ 0] = 1.0 / frustumx; m[ 5] = 1.0 / frustumy; m[10] = -nudge; m[11] = -1; m[14] = -2 * nearclip * nudge; v->screentodepth[0] = (m[10] + 1) * 0.5 - 1; v->screentodepth[1] = m[14] * -0.5; Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); Matrix4x4_CreateRotate(&basematrix, -90, 1, 0, 0); Matrix4x4_ConcatRotate(&basematrix, 90, 0, 0, 1); Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); if (nearplane) R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); if(v_flipped.integer) { m[0] = -m[0]; m[4] = -m[4]; m[8] = -m[8]; m[12] = -m[12]; } Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); } float cubeviewmatrix[6][16] = { // standard cubemap projections { // +X 0, 0,-1, 0, 0,-1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1, }, { // -X 0, 0, 1, 0, 0,-1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, }, { // +Y 1, 0, 0, 0, 0, 0,-1, 0, 0, 1, 0, 0, 0, 0, 0, 1, }, { // -Y 1, 0, 0, 0, 0, 0, 1, 0, 0,-1, 0, 0, 0, 0, 0, 1, }, { // +Z 1, 0, 0, 0, 0,-1, 0, 0, 0, 0,-1, 0, 0, 0, 0, 1, }, { // -Z -1, 0, 0, 0, 0,-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }, }; float rectviewmatrix[6][16] = { // sign-preserving cubemap projections { // +X 0, 0,-1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, }, { // -X 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, }, { // +Y 1, 0, 0, 0, 0, 0,-1, 0, 0, 1, 0, 0, 0, 0, 0, 1, }, { // -Y 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, }, { // +Z 1, 0, 0, 0, 0, 1, 0, 0, 0, 0,-1, 0, 0, 0, 0, 1, }, { // -Z 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }, }; void R_Viewport_InitCubeSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, float nearclip, float farclip, const float *nearplane) { matrix4x4_t tempmatrix, basematrix; float m[16]; memset(v, 0, sizeof(*v)); v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; v->cameramatrix = *cameramatrix; v->width = size; v->height = size; v->depth = 1; memset(m, 0, sizeof(m)); m[0] = m[5] = 1.0f; m[10] = -(farclip + nearclip) / (farclip - nearclip); m[11] = -1; m[14] = -2 * nearclip * farclip / (farclip - nearclip); Matrix4x4_FromArrayFloatGL(&basematrix, cubeviewmatrix[side]); Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); if (nearplane) R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); } void R_Viewport_InitRectSideView(r_viewport_t *v, const matrix4x4_t *cameramatrix, int side, int size, int border, float nearclip, float farclip, const float *nearplane) { matrix4x4_t tempmatrix, basematrix; float m[16]; memset(v, 0, sizeof(*v)); v->type = R_VIEWPORTTYPE_PERSPECTIVECUBESIDE; v->cameramatrix = *cameramatrix; v->x = (side & 1) * size; v->y = (side >> 1) * size; v->width = size; v->height = size; v->depth = 1; memset(m, 0, sizeof(m)); m[0] = m[5] = 1.0f * ((float)size - border) / size; m[10] = -(farclip + nearclip) / (farclip - nearclip); m[11] = -1; m[14] = -2 * nearclip * farclip / (farclip - nearclip); Matrix4x4_FromArrayFloatGL(&basematrix, rectviewmatrix[side]); Matrix4x4_Invert_Full(&tempmatrix, &v->cameramatrix); Matrix4x4_Concat(&v->viewmatrix, &basematrix, &tempmatrix); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GL13: case RENDERPATH_GL11: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: m[5] *= -1; break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } if (nearplane) R_Viewport_ApplyNearClipPlaneFloatGL(v, m, nearplane[0], nearplane[1], nearplane[2], nearplane[3]); Matrix4x4_FromArrayFloatGL(&v->projectmatrix, m); } void R_SetViewport(const r_viewport_t *v) { gl_viewport = *v; // FIXME: v_flipped_state is evil, this probably breaks somewhere GL_SetMirrorState(v_flipped.integer && (v->type == R_VIEWPORTTYPE_PERSPECTIVE || v->type == R_VIEWPORTTYPE_PERSPECTIVE_INFINITEFARCLIP)); // copy over the matrices to our state gl_viewmatrix = v->viewmatrix; gl_projectionmatrix = v->projectmatrix; switch(vid.renderpath) { case RENDERPATH_GL13: case RENDERPATH_GL11: case RENDERPATH_GLES1: #ifndef USE_GLES2 { float m[16]; CHECKGLERROR qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR // Load the projection matrix into OpenGL qglMatrixMode(GL_PROJECTION);CHECKGLERROR Matrix4x4_ToArrayFloatGL(&gl_projectionmatrix, m); qglLoadMatrixf(m);CHECKGLERROR qglMatrixMode(GL_MODELVIEW);CHECKGLERROR } #endif break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DVIEWPORT9 d3dviewport; d3dviewport.X = gl_viewport.x; d3dviewport.Y = gl_viewport.y; d3dviewport.Width = gl_viewport.width; d3dviewport.Height = gl_viewport.height; d3dviewport.MinZ = gl_state.depthrange[0]; d3dviewport.MaxZ = gl_state.depthrange[1]; IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_Viewport(v->x, v->y, v->width, v->height); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: CHECKGLERROR qglViewport(v->x, v->y, v->width, v->height);CHECKGLERROR break; } // force an update of the derived matrices gl_modelmatrixchanged = true; R_EntityMatrix(&gl_modelmatrix); } void R_GetViewport(r_viewport_t *v) { *v = gl_viewport; } static void GL_BindVBO(int bufferobject) { if (gl_state.vertexbufferobject != bufferobject) { gl_state.vertexbufferobject = bufferobject; CHECKGLERROR qglBindBufferARB(GL_ARRAY_BUFFER, bufferobject);CHECKGLERROR } } static void GL_BindEBO(int bufferobject) { if (gl_state.elementbufferobject != bufferobject) { gl_state.elementbufferobject = bufferobject; CHECKGLERROR qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, bufferobject);CHECKGLERROR } } static void GL_BindUBO(int bufferobject) { if (gl_state.uniformbufferobject != bufferobject) { gl_state.uniformbufferobject = bufferobject; #ifdef GL_UNIFORM_BUFFER CHECKGLERROR qglBindBufferARB(GL_UNIFORM_BUFFER, bufferobject);CHECKGLERROR #endif } } static const GLuint drawbuffers[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3}; int R_Mesh_CreateFramebufferObject(rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (vid.support.arb_framebuffer_object) { int temp; GLuint status; qglGenFramebuffers(1, (GLuint*)&temp);CHECKGLERROR R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL); // GL_ARB_framebuffer_object (GL3-class hardware) - depth stencil attachment #ifdef USE_GLES2 // FIXME: separate stencil attachment on GLES if (depthtexture && depthtexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR if (depthtexture && depthtexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR #else if (depthtexture && depthtexture->texnum ) { qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR if (depthtexture->glisdepthstencil) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR } if (depthtexture && depthtexture->renderbuffernum ) { qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR if (depthtexture->glisdepthstencil) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR } #endif if (colortexture && colortexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , colortexture->gltexturetypeenum , colortexture->texnum , 0);CHECKGLERROR if (colortexture2 && colortexture2->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , colortexture2->gltexturetypeenum, colortexture2->texnum, 0);CHECKGLERROR if (colortexture3 && colortexture3->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , colortexture3->gltexturetypeenum, colortexture3->texnum, 0);CHECKGLERROR if (colortexture4 && colortexture4->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , colortexture4->gltexturetypeenum, colortexture4->texnum, 0);CHECKGLERROR if (colortexture && colortexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , GL_RENDERBUFFER, colortexture->renderbuffernum );CHECKGLERROR if (colortexture2 && colortexture2->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , GL_RENDERBUFFER, colortexture2->renderbuffernum);CHECKGLERROR if (colortexture3 && colortexture3->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , GL_RENDERBUFFER, colortexture3->renderbuffernum);CHECKGLERROR if (colortexture4 && colortexture4->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , GL_RENDERBUFFER, colortexture4->renderbuffernum);CHECKGLERROR #ifndef USE_GLES2 if (colortexture4 && qglDrawBuffersARB) { qglDrawBuffersARB(4, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture3 && qglDrawBuffersARB) { qglDrawBuffersARB(3, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture2 && qglDrawBuffersARB) { qglDrawBuffersARB(2, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture && qglDrawBuffer) { qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR } else if (qglDrawBuffer) { qglDrawBuffer(GL_NONE);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } #endif status = qglCheckFramebufferStatus(GL_FRAMEBUFFER);CHECKGLERROR if (status != GL_FRAMEBUFFER_COMPLETE) { Con_Printf("R_Mesh_CreateFramebufferObject: glCheckFramebufferStatus returned %i\n", status); gl_state.framebufferobject = 0; // GL unbinds it for us qglDeleteFramebuffers(1, (GLuint*)&temp); temp = 0; } return temp; } else if (vid.support.ext_framebuffer_object) { int temp; GLuint status; qglGenFramebuffers(1, (GLuint*)&temp);CHECKGLERROR R_Mesh_SetRenderTargets(temp, NULL, NULL, NULL, NULL, NULL); // GL_EXT_framebuffer_object (GL2-class hardware) - no depth stencil attachment, let it break stencil if (depthtexture && depthtexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , depthtexture->gltexturetypeenum , depthtexture->texnum , 0);CHECKGLERROR if (depthtexture && depthtexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT , GL_RENDERBUFFER, depthtexture->renderbuffernum );CHECKGLERROR if (colortexture && colortexture->texnum ) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , colortexture->gltexturetypeenum , colortexture->texnum , 0);CHECKGLERROR if (colortexture2 && colortexture2->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , colortexture2->gltexturetypeenum, colortexture2->texnum, 0);CHECKGLERROR if (colortexture3 && colortexture3->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , colortexture3->gltexturetypeenum, colortexture3->texnum, 0);CHECKGLERROR if (colortexture4 && colortexture4->texnum) qglFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , colortexture4->gltexturetypeenum, colortexture4->texnum, 0);CHECKGLERROR if (colortexture && colortexture->renderbuffernum ) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 , GL_RENDERBUFFER, colortexture->renderbuffernum );CHECKGLERROR if (colortexture2 && colortexture2->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1 , GL_RENDERBUFFER, colortexture2->renderbuffernum);CHECKGLERROR if (colortexture3 && colortexture3->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2 , GL_RENDERBUFFER, colortexture3->renderbuffernum);CHECKGLERROR if (colortexture4 && colortexture4->renderbuffernum) qglFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3 , GL_RENDERBUFFER, colortexture4->renderbuffernum);CHECKGLERROR #ifndef USE_GLES2 if (colortexture4 && qglDrawBuffersARB) { qglDrawBuffersARB(4, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture3 && qglDrawBuffersARB) { qglDrawBuffersARB(3, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture2 && qglDrawBuffersARB) { qglDrawBuffersARB(2, drawbuffers);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } else if (colortexture && qglDrawBuffer) { qglDrawBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR qglReadBuffer(GL_COLOR_ATTACHMENT0);CHECKGLERROR } else if (qglDrawBuffer) { qglDrawBuffer(GL_NONE);CHECKGLERROR qglReadBuffer(GL_NONE);CHECKGLERROR } #endif status = qglCheckFramebufferStatus(GL_FRAMEBUFFER);CHECKGLERROR if (status != GL_FRAMEBUFFER_COMPLETE) { Con_Printf("R_Mesh_CreateFramebufferObject: glCheckFramebufferStatus returned %i\n", status); gl_state.framebufferobject = 0; // GL unbinds it for us qglDeleteFramebuffers(1, (GLuint*)&temp); temp = 0; } return temp; } return 0; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: return 1; case RENDERPATH_SOFT: return 1; } return 0; } void R_Mesh_DestroyFramebufferObject(int fbo) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (fbo) { // GL clears the binding if we delete something bound if (gl_state.framebufferobject == fbo) gl_state.framebufferobject = 0; qglDeleteFramebuffers(1, (GLuint*)&fbo); } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; } } #ifdef SUPPORTD3D void R_Mesh_SetRenderTargetsD3D9(IDirect3DSurface9 *depthsurface, IDirect3DSurface9 *colorsurface0, IDirect3DSurface9 *colorsurface1, IDirect3DSurface9 *colorsurface2, IDirect3DSurface9 *colorsurface3) { gl_state.framebufferobject = depthsurface != gl_state.d3drt_backbufferdepthsurface || colorsurface0 != gl_state.d3drt_backbuffercolorsurface; if (gl_state.d3drt_depthsurface != depthsurface) { gl_state.d3drt_depthsurface = depthsurface; IDirect3DDevice9_SetDepthStencilSurface(vid_d3d9dev, gl_state.d3drt_depthsurface); } if (gl_state.d3drt_colorsurfaces[0] != colorsurface0) { gl_state.d3drt_colorsurfaces[0] = colorsurface0; IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 0, gl_state.d3drt_colorsurfaces[0]); } if (gl_state.d3drt_colorsurfaces[1] != colorsurface1) { gl_state.d3drt_colorsurfaces[1] = colorsurface1; IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 1, gl_state.d3drt_colorsurfaces[1]); } if (gl_state.d3drt_colorsurfaces[2] != colorsurface2) { gl_state.d3drt_colorsurfaces[2] = colorsurface2; IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 2, gl_state.d3drt_colorsurfaces[2]); } if (gl_state.d3drt_colorsurfaces[3] != colorsurface3) { gl_state.d3drt_colorsurfaces[3] = colorsurface3; IDirect3DDevice9_SetRenderTarget(vid_d3d9dev, 3, gl_state.d3drt_colorsurfaces[3]); } } #endif void R_Mesh_SetRenderTargets(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, rtexture_t *colortexture2, rtexture_t *colortexture3, rtexture_t *colortexture4) { unsigned int i; unsigned int j; rtexture_t *textures[5]; Vector4Set(textures, colortexture, colortexture2, colortexture3, colortexture4); textures[4] = depthtexture; // unbind any matching textures immediately, otherwise D3D will complain about a bound texture being used as a render target for (j = 0;j < 5;j++) if (textures[j]) for (i = 0;i < vid.teximageunits;i++) if (gl_state.units[i].texture == textures[j]) R_Mesh_TexBind(i, NULL); // set up framebuffer object or render targets for the active rendering API switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (gl_state.framebufferobject != fbo) { gl_state.framebufferobject = fbo; qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.framebufferobject ? gl_state.framebufferobject : gl_state.defaultframebufferobject); } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D // set up the new render targets, a NULL depthtexture intentionally binds nothing // TODO: optimize: keep surface pointer around in rtexture_t until texture is freed or lost if (fbo) { IDirect3DSurface9 *surfaces[5]; for (i = 0;i < 5;i++) { surfaces[i] = NULL; if (textures[i]) { if (textures[i]->d3dsurface) surfaces[i] = (IDirect3DSurface9 *)textures[i]->d3dsurface; else IDirect3DTexture9_GetSurfaceLevel((IDirect3DTexture9 *)textures[i]->d3dtexture, 0, &surfaces[i]); } } // set the render targets for real R_Mesh_SetRenderTargetsD3D9(surfaces[4], surfaces[0], surfaces[1], surfaces[2], surfaces[3]); // release the texture surface levels (they won't be lost while bound...) for (i = 0;i < 5;i++) if (textures[i] && !textures[i]->d3dsurface) IDirect3DSurface9_Release(surfaces[i]); } else R_Mesh_SetRenderTargetsD3D9(gl_state.d3drt_backbufferdepthsurface, gl_state.d3drt_backbuffercolorsurface, NULL, NULL, NULL); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (fbo) { int width, height; unsigned int *pointers[5]; memset(pointers, 0, sizeof(pointers)); for (i = 0;i < 5;i++) pointers[i] = textures[i] ? (unsigned int *)DPSOFTRAST_Texture_GetPixelPointer(textures[i]->texnum, 0) : NULL; width = DPSOFTRAST_Texture_GetWidth(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); height = DPSOFTRAST_Texture_GetHeight(textures[0] ? textures[0]->texnum : textures[4]->texnum, 0); DPSOFTRAST_SetRenderTargets(width, height, pointers[4], pointers[0], pointers[1], pointers[2], pointers[3]); } else DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); break; } } #ifdef SUPPORTD3D static int d3dcmpforglfunc(int f) { switch(f) { case GL_NEVER: return D3DCMP_NEVER; case GL_LESS: return D3DCMP_LESS; case GL_EQUAL: return D3DCMP_EQUAL; case GL_LEQUAL: return D3DCMP_LESSEQUAL; case GL_GREATER: return D3DCMP_GREATER; case GL_NOTEQUAL: return D3DCMP_NOTEQUAL; case GL_GEQUAL: return D3DCMP_GREATEREQUAL; case GL_ALWAYS: return D3DCMP_ALWAYS; default: Con_DPrintf("Unknown GL_DepthFunc\n");return D3DCMP_ALWAYS; } } static int d3dstencilopforglfunc(int f) { switch(f) { case GL_KEEP: return D3DSTENCILOP_KEEP; case GL_INCR: return D3DSTENCILOP_INCR; // note: GL_INCR is clamped, D3DSTENCILOP_INCR wraps case GL_DECR: return D3DSTENCILOP_DECR; // note: GL_DECR is clamped, D3DSTENCILOP_DECR wraps default: Con_DPrintf("Unknown GL_StencilFunc\n");return D3DSTENCILOP_KEEP; } } #endif static void GL_Backend_ResetState(void) { unsigned int i; gl_state.active = true; gl_state.depthtest = true; gl_state.alphatest = false; gl_state.alphafunc = GL_GEQUAL; gl_state.alphafuncvalue = 0.5f; gl_state.alphatocoverage = false; gl_state.blendfunc1 = GL_ONE; gl_state.blendfunc2 = GL_ZERO; gl_state.blend = false; gl_state.depthmask = GL_TRUE; gl_state.colormask = 15; gl_state.color4f[0] = gl_state.color4f[1] = gl_state.color4f[2] = gl_state.color4f[3] = 1; gl_state.lockrange_first = 0; gl_state.lockrange_count = 0; gl_state.cullface = GL_FRONT; gl_state.cullfaceenable = false; gl_state.polygonoffset[0] = 0; gl_state.polygonoffset[1] = 0; gl_state.framebufferobject = 0; gl_state.depthfunc = GL_LEQUAL; switch(vid.renderpath) { case RENDERPATH_D3D9: #ifdef SUPPORTD3D { IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, gl_state.colormask); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 CHECKGLERROR qglColorMask(1, 1, 1, 1);CHECKGLERROR qglAlphaFunc(gl_state.alphafunc, gl_state.alphafuncvalue);CHECKGLERROR qglDisable(GL_ALPHA_TEST);CHECKGLERROR if (qglBlendFuncSeparate) { qglBlendFuncSeparate(gl_state.blendfunc1, gl_state.blendfunc2, GL_ZERO, GL_ONE);CHECKGLERROR // ELUAN: Adreno 225 (and others) compositing workaround } else { qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR } qglDisable(GL_BLEND);CHECKGLERROR qglCullFace(gl_state.cullface);CHECKGLERROR qglDisable(GL_CULL_FACE);CHECKGLERROR qglDepthFunc(GL_LEQUAL);CHECKGLERROR qglEnable(GL_DEPTH_TEST);CHECKGLERROR qglDepthMask(gl_state.depthmask);CHECKGLERROR qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); if (vid.support.arb_vertex_buffer_object) { qglBindBufferARB(GL_ARRAY_BUFFER, 0); qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); } if (vid.support.ext_framebuffer_object) { //qglBindRenderbuffer(GL_RENDERBUFFER, 0); qglBindFramebuffer(GL_FRAMEBUFFER, 0); } qglVertexPointer(3, GL_FLOAT, sizeof(float[3]), NULL);CHECKGLERROR qglEnableClientState(GL_VERTEX_ARRAY);CHECKGLERROR qglColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL);CHECKGLERROR qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR qglColor4f(1, 1, 1, 1);CHECKGLERROR if (vid.support.ext_framebuffer_object) qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.framebufferobject); gl_state.unit = MAX_TEXTUREUNITS; gl_state.clientunit = MAX_TEXTUREUNITS; for (i = 0;i < vid.texunits;i++) { GL_ActiveTexture(i); GL_ClientActiveTexture(i); qglDisable(GL_TEXTURE_2D);CHECKGLERROR qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR if (vid.support.ext_texture_3d) { qglDisable(GL_TEXTURE_3D);CHECKGLERROR qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR } if (vid.support.arb_texture_cube_map) { qglDisable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR qglBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHECKGLERROR } GL_BindVBO(0); qglTexCoordPointer(2, GL_FLOAT, sizeof(float[2]), NULL);CHECKGLERROR qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR qglMatrixMode(GL_TEXTURE);CHECKGLERROR qglLoadIdentity();CHECKGLERROR qglMatrixMode(GL_MODELVIEW);CHECKGLERROR qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);CHECKGLERROR } CHECKGLERROR #endif break; case RENDERPATH_SOFT: DPSOFTRAST_ColorMask(1,1,1,1); DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); DPSOFTRAST_CullFace(gl_state.cullface); DPSOFTRAST_DepthFunc(gl_state.depthfunc); DPSOFTRAST_DepthMask(gl_state.depthmask); DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); DPSOFTRAST_SetRenderTargets(vid.width, vid.height, vid.softdepthpixels, vid.softpixels, NULL, NULL, NULL); DPSOFTRAST_Viewport(0, 0, vid.width, vid.height); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: CHECKGLERROR qglColorMask(1, 1, 1, 1);CHECKGLERROR qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR qglDisable(GL_BLEND);CHECKGLERROR qglCullFace(gl_state.cullface);CHECKGLERROR qglDisable(GL_CULL_FACE);CHECKGLERROR qglDepthFunc(GL_LEQUAL);CHECKGLERROR qglEnable(GL_DEPTH_TEST);CHECKGLERROR qglDepthMask(gl_state.depthmask);CHECKGLERROR qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); if (vid.support.arb_vertex_buffer_object) { qglBindBufferARB(GL_ARRAY_BUFFER, 0); qglBindBufferARB(GL_ELEMENT_ARRAY_BUFFER, 0); } if (vid.support.ext_framebuffer_object) qglBindFramebuffer(GL_FRAMEBUFFER, gl_state.defaultframebufferobject); qglEnableVertexAttribArray(GLSLATTRIB_POSITION); qglVertexAttribPointer(GLSLATTRIB_POSITION, 3, GL_FLOAT, false, sizeof(float[3]), NULL);CHECKGLERROR qglDisableVertexAttribArray(GLSLATTRIB_COLOR); qglVertexAttribPointer(GLSLATTRIB_COLOR, 4, GL_FLOAT, false, sizeof(float[4]), NULL);CHECKGLERROR qglVertexAttrib4f(GLSLATTRIB_COLOR, 1, 1, 1, 1); gl_state.unit = MAX_TEXTUREUNITS; gl_state.clientunit = MAX_TEXTUREUNITS; for (i = 0;i < vid.teximageunits;i++) { GL_ActiveTexture(i); qglBindTexture(GL_TEXTURE_2D, 0);CHECKGLERROR if (vid.support.ext_texture_3d) { qglBindTexture(GL_TEXTURE_3D, 0);CHECKGLERROR } if (vid.support.arb_texture_cube_map) { qglBindTexture(GL_TEXTURE_CUBE_MAP, 0);CHECKGLERROR } } for (i = 0;i < vid.texarrayunits;i++) { GL_BindVBO(0); qglVertexAttribPointer(i+GLSLATTRIB_TEXCOORD0, 2, GL_FLOAT, false, sizeof(float[2]), NULL);CHECKGLERROR qglDisableVertexAttribArray(i+GLSLATTRIB_TEXCOORD0);CHECKGLERROR } CHECKGLERROR break; } } void GL_ActiveTexture(unsigned int num) { if (gl_state.unit != num) { gl_state.unit = num; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (qglActiveTexture) { CHECKGLERROR qglActiveTexture(GL_TEXTURE0 + gl_state.unit); CHECKGLERROR } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; } } } void GL_ClientActiveTexture(unsigned int num) { if (gl_state.clientunit != num) { gl_state.clientunit = num; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 if (qglActiveTexture) { CHECKGLERROR qglClientActiveTexture(GL_TEXTURE0 + gl_state.clientunit); CHECKGLERROR } #endif break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; case RENDERPATH_GL20: case RENDERPATH_GLES2: break; } } } void GL_BlendFunc(int blendfunc1, int blendfunc2) { if (gl_state.blendfunc1 != blendfunc1 || gl_state.blendfunc2 != blendfunc2) { qboolean blendenable; gl_state.blendfunc1 = blendfunc1; gl_state.blendfunc2 = blendfunc2; blendenable = (gl_state.blendfunc1 != GL_ONE || gl_state.blendfunc2 != GL_ZERO); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (qglBlendFuncSeparate) { qglBlendFuncSeparate(gl_state.blendfunc1, gl_state.blendfunc2, GL_ZERO, GL_ONE);CHECKGLERROR // ELUAN: Adreno 225 (and others) compositing workaround } else { qglBlendFunc(gl_state.blendfunc1, gl_state.blendfunc2);CHECKGLERROR } if (gl_state.blend != blendenable) { gl_state.blend = blendenable; if (!gl_state.blend) { qglDisable(GL_BLEND);CHECKGLERROR } else { qglEnable(GL_BLEND);CHECKGLERROR } } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { int i; int glblendfunc[2]; D3DBLEND d3dblendfunc[2]; glblendfunc[0] = gl_state.blendfunc1; glblendfunc[1] = gl_state.blendfunc2; for (i = 0;i < 2;i++) { switch(glblendfunc[i]) { case GL_ZERO: d3dblendfunc[i] = D3DBLEND_ZERO;break; case GL_ONE: d3dblendfunc[i] = D3DBLEND_ONE;break; case GL_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_SRCCOLOR;break; case GL_ONE_MINUS_SRC_COLOR: d3dblendfunc[i] = D3DBLEND_INVSRCCOLOR;break; case GL_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_SRCALPHA;break; case GL_ONE_MINUS_SRC_ALPHA: d3dblendfunc[i] = D3DBLEND_INVSRCALPHA;break; case GL_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_DESTALPHA;break; case GL_ONE_MINUS_DST_ALPHA: d3dblendfunc[i] = D3DBLEND_INVDESTALPHA;break; case GL_DST_COLOR: d3dblendfunc[i] = D3DBLEND_DESTCOLOR;break; case GL_ONE_MINUS_DST_COLOR: d3dblendfunc[i] = D3DBLEND_INVDESTCOLOR;break; } } IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SRCBLEND, d3dblendfunc[0]); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DESTBLEND, d3dblendfunc[1]); if (gl_state.blend != blendenable) { gl_state.blend = blendenable; IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ALPHABLENDENABLE, gl_state.blend); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_BlendFunc(gl_state.blendfunc1, gl_state.blendfunc2); break; } } } void GL_DepthMask(int state) { if (gl_state.depthmask != state) { gl_state.depthmask = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglDepthMask(gl_state.depthmask);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZWRITEENABLE, gl_state.depthmask); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_DepthMask(gl_state.depthmask); break; } } } void GL_DepthTest(int state) { if (gl_state.depthtest != state) { gl_state.depthtest = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (gl_state.depthtest) { qglEnable(GL_DEPTH_TEST);CHECKGLERROR } else { qglDisable(GL_DEPTH_TEST);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZENABLE, gl_state.depthtest); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_DepthTest(gl_state.depthtest); break; } } } void GL_DepthFunc(int state) { if (gl_state.depthfunc != state) { gl_state.depthfunc = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglDepthFunc(gl_state.depthfunc);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_ZFUNC, d3dcmpforglfunc(gl_state.depthfunc)); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_DepthFunc(gl_state.depthfunc); break; } } } void GL_DepthRange(float nearfrac, float farfrac) { if (gl_state.depthrange[0] != nearfrac || gl_state.depthrange[1] != farfrac) { gl_state.depthrange[0] = nearfrac; gl_state.depthrange[1] = farfrac; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #ifdef USE_GLES2 qglDepthRangef(gl_state.depthrange[0], gl_state.depthrange[1]); #else qglDepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); #endif break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { D3DVIEWPORT9 d3dviewport; d3dviewport.X = gl_viewport.x; d3dviewport.Y = gl_viewport.y; d3dviewport.Width = gl_viewport.width; d3dviewport.Height = gl_viewport.height; d3dviewport.MinZ = gl_state.depthrange[0]; d3dviewport.MaxZ = gl_state.depthrange[1]; IDirect3DDevice9_SetViewport(vid_d3d9dev, &d3dviewport); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_DepthRange(gl_state.depthrange[0], gl_state.depthrange[1]); break; } } } void R_SetStencilSeparate(qboolean enable, int writemask, int frontfail, int frontzfail, int frontzpass, int backfail, int backzfail, int backzpass, int frontcompare, int backcompare, int comparereference, int comparemask) { switch (vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (enable) { qglEnable(GL_STENCIL_TEST);CHECKGLERROR } else { qglDisable(GL_STENCIL_TEST);CHECKGLERROR } if (vid.support.ati_separate_stencil) { qglStencilMask(writemask);CHECKGLERROR qglStencilOpSeparate(GL_FRONT, frontfail, frontzfail, frontzpass);CHECKGLERROR qglStencilOpSeparate(GL_BACK, backfail, backzfail, backzpass);CHECKGLERROR qglStencilFuncSeparate(GL_FRONT, frontcompare, comparereference, comparereference);CHECKGLERROR qglStencilFuncSeparate(GL_BACK, backcompare, comparereference, comparereference);CHECKGLERROR } else if (vid.support.ext_stencil_two_side) { #if defined(GL_STENCIL_TEST_TWO_SIDE_EXT) && !defined(USE_GLES2) qglEnable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR qglActiveStencilFaceEXT(GL_FRONT);CHECKGLERROR qglStencilMask(writemask);CHECKGLERROR qglStencilOp(frontfail, frontzfail, frontzpass);CHECKGLERROR qglStencilFunc(frontcompare, comparereference, comparemask);CHECKGLERROR qglActiveStencilFaceEXT(GL_BACK);CHECKGLERROR qglStencilMask(writemask);CHECKGLERROR qglStencilOp(backfail, backzfail, backzpass);CHECKGLERROR qglStencilFunc(backcompare, comparereference, comparemask);CHECKGLERROR #endif } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(frontfail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(frontzfail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(frontzpass)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(frontcompare)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFAIL, d3dstencilopforglfunc(backfail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILZFAIL, d3dstencilopforglfunc(backzfail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILPASS, d3dstencilopforglfunc(backzpass)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CCW_STENCILFUNC, d3dcmpforglfunc(backcompare)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } } void R_SetStencil(qboolean enable, int writemask, int fail, int zfail, int zpass, int compare, int comparereference, int comparemask) { switch (vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (enable) { qglEnable(GL_STENCIL_TEST);CHECKGLERROR } else { qglDisable(GL_STENCIL_TEST);CHECKGLERROR } if (vid.support.ext_stencil_two_side) { #ifdef GL_STENCIL_TEST_TWO_SIDE_EXT qglDisable(GL_STENCIL_TEST_TWO_SIDE_EXT);CHECKGLERROR #endif } qglStencilMask(writemask);CHECKGLERROR qglStencilOp(fail, zfail, zpass);CHECKGLERROR qglStencilFunc(compare, comparereference, comparemask);CHECKGLERROR CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (vid.support.ati_separate_stencil) IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_TWOSIDEDSTENCILMODE, true); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILENABLE, enable); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILWRITEMASK, writemask); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFAIL, d3dstencilopforglfunc(fail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILZFAIL, d3dstencilopforglfunc(zfail)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILPASS, d3dstencilopforglfunc(zpass)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILFUNC, d3dcmpforglfunc(compare)); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILREF, comparereference); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_STENCILMASK, comparemask); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } } void GL_PolygonOffset(float planeoffset, float depthoffset) { if (gl_state.polygonoffset[0] != planeoffset || gl_state.polygonoffset[1] != depthoffset) { gl_state.polygonoffset[0] = planeoffset; gl_state.polygonoffset[1] = depthoffset; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglPolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SLOPESCALEDEPTHBIAS, gl_state.polygonoffset[0]); IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_DEPTHBIAS, gl_state.polygonoffset[1] * (1.0f / 16777216.0f)); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_PolygonOffset(gl_state.polygonoffset[0], gl_state.polygonoffset[1]); break; } } } void GL_SetMirrorState(qboolean state) { if (v_flipped_state != state) { v_flipped_state = state; if (gl_state.cullface == GL_BACK) gl_state.cullface = GL_FRONT; else if (gl_state.cullface == GL_FRONT) gl_state.cullface = GL_BACK; else return; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglCullFace(gl_state.cullface);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, gl_state.cullface == GL_FRONT ? D3DCULL_CCW : D3DCULL_CW); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_CullFace(gl_state.cullface); break; } } } void GL_CullFace(int state) { if(v_flipped_state) { if(state == GL_FRONT) state = GL_BACK; else if(state == GL_BACK) state = GL_FRONT; } switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (state != GL_NONE) { if (!gl_state.cullfaceenable) { gl_state.cullfaceenable = true; qglEnable(GL_CULL_FACE);CHECKGLERROR } if (gl_state.cullface != state) { gl_state.cullface = state; qglCullFace(gl_state.cullface);CHECKGLERROR } } else { if (gl_state.cullfaceenable) { gl_state.cullfaceenable = false; qglDisable(GL_CULL_FACE);CHECKGLERROR } } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (gl_state.cullface != state) { gl_state.cullface = state; switch(gl_state.cullface) { case GL_NONE: gl_state.cullfaceenable = false; IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_NONE); break; case GL_FRONT: gl_state.cullfaceenable = true; IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CCW); break; case GL_BACK: gl_state.cullfaceenable = true; IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_CULLMODE, D3DCULL_CW); break; } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (gl_state.cullface != state) { gl_state.cullface = state; gl_state.cullfaceenable = state != GL_NONE ? true : false; DPSOFTRAST_CullFace(gl_state.cullface); } break; } } void GL_AlphaTest(int state) { if (gl_state.alphatest != state) { gl_state.alphatest = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifdef GL_ALPHA_TEST // only fixed function uses alpha test, other paths use pixel kill capability in shaders CHECKGLERROR if (gl_state.alphatest) { qglEnable(GL_ALPHA_TEST);CHECKGLERROR } else { qglDisable(GL_ALPHA_TEST);CHECKGLERROR } #endif break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GL20: case RENDERPATH_GLES2: break; } } } void GL_AlphaToCoverage(qboolean state) { if (gl_state.alphatocoverage != state) { gl_state.alphatocoverage = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: case RENDERPATH_GLES2: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; case RENDERPATH_GL20: #ifdef GL_SAMPLE_ALPHA_TO_COVERAGE_ARB // alpha to coverage turns the alpha value of the pixel into 0%, 25%, 50%, 75% or 100% by masking the multisample fragments accordingly CHECKGLERROR if (gl_state.alphatocoverage) { qglEnable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR // qglEnable(GL_MULTISAMPLE_ARB);CHECKGLERROR } else { qglDisable(GL_SAMPLE_ALPHA_TO_COVERAGE_ARB);CHECKGLERROR // qglDisable(GL_MULTISAMPLE_ARB);CHECKGLERROR } #endif break; } } } void GL_ColorMask(int r, int g, int b, int a) { // NOTE: this matches D3DCOLORWRITEENABLE_RED, GREEN, BLUE, ALPHA int state = (r ? 1 : 0) | (g ? 2 : 0) | (b ? 4 : 0) | (a ? 8 : 0); if (gl_state.colormask != state) { gl_state.colormask = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglColorMask((GLboolean)r, (GLboolean)g, (GLboolean)b, (GLboolean)a);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_COLORWRITEENABLE, state); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_ColorMask(r, g, b, a); break; } } } void GL_Color(float cr, float cg, float cb, float ca) { if (gl_state.pointer_color_enabled || gl_state.color4f[0] != cr || gl_state.color4f[1] != cg || gl_state.color4f[2] != cb || gl_state.color4f[3] != ca) { gl_state.color4f[0] = cr; gl_state.color4f[1] = cg; gl_state.color4f[2] = cb; gl_state.color4f[3] = ca; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 CHECKGLERROR qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]); CHECKGLERROR #endif break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: // no equivalent in D3D break; case RENDERPATH_SOFT: DPSOFTRAST_Color4f(cr, cg, cb, ca); break; case RENDERPATH_GL20: case RENDERPATH_GLES2: qglVertexAttrib4f(GLSLATTRIB_COLOR, cr, cg, cb, ca); break; } } } void GL_Scissor (int x, int y, int width, int height) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR qglScissor(x, y,width,height); CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { RECT d3drect; d3drect.left = x; d3drect.top = y; d3drect.right = x + width; d3drect.bottom = y + height; IDirect3DDevice9_SetScissorRect(vid_d3d9dev, &d3drect); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_Scissor(x, y, width, height); break; } } void GL_ScissorTest(int state) { if (gl_state.scissortest != state) { gl_state.scissortest = state; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if(gl_state.scissortest) qglEnable(GL_SCISSOR_TEST); else qglDisable(GL_SCISSOR_TEST); CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_SCISSORTESTENABLE, gl_state.scissortest); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_ScissorTest(gl_state.scissortest); break; } } } void GL_Clear(int mask, const float *colorvalue, float depthvalue, int stencilvalue) { // opaque black - if you want transparent black, you'll need to pass in a colorvalue static const float blackcolor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; // prevent warnings when trying to clear a buffer that does not exist if (!colorvalue) colorvalue = blackcolor; if (!vid.stencil) { mask &= ~GL_STENCIL_BUFFER_BIT; stencilvalue = 0; } switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR if (mask & GL_COLOR_BUFFER_BIT) { qglClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]);CHECKGLERROR } if (mask & GL_DEPTH_BUFFER_BIT) { #ifdef USE_GLES2 qglClearDepthf(depthvalue);CHECKGLERROR #else qglClearDepth(depthvalue);CHECKGLERROR #endif } if (mask & GL_STENCIL_BUFFER_BIT) { qglClearStencil(stencilvalue);CHECKGLERROR } qglClear(mask);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_Clear(vid_d3d9dev, 0, NULL, ((mask & GL_COLOR_BUFFER_BIT) ? D3DCLEAR_TARGET : 0) | ((mask & GL_STENCIL_BUFFER_BIT) ? D3DCLEAR_STENCIL : 0) | ((mask & GL_DEPTH_BUFFER_BIT) ? D3DCLEAR_ZBUFFER : 0), D3DCOLOR_COLORVALUE(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]), depthvalue, stencilvalue); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (mask & GL_COLOR_BUFFER_BIT) DPSOFTRAST_ClearColor(colorvalue[0], colorvalue[1], colorvalue[2], colorvalue[3]); if (mask & GL_DEPTH_BUFFER_BIT) DPSOFTRAST_ClearDepth(depthvalue); break; } } void GL_ReadPixelsBGRA(int x, int y, int width, int height, unsigned char *outpixels) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: CHECKGLERROR #ifndef GL_BGRA { int i; int r; // int g; int b; // int a; qglReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, outpixels);CHECKGLERROR for (i = 0;i < width * height * 4;i += 4) { r = outpixels[i+0]; // g = outpixels[i+1]; b = outpixels[i+2]; // a = outpixels[i+3]; outpixels[i+0] = b; // outpixels[i+1] = g; outpixels[i+2] = r; // outpixels[i+3] = a; } } #else qglReadPixels(x, y, width, height, GL_BGRA, GL_UNSIGNED_BYTE, outpixels);CHECKGLERROR #endif break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { // LordHavoc: we can't directly download the backbuffer because it may be // multisampled, and it may not be lockable, so we blit it to a lockable // surface of the same dimensions (but without multisample) to resolve the // multisample buffer to a normal image, and then lock that... IDirect3DSurface9 *stretchsurface = NULL; if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, TRUE, &stretchsurface, NULL))) { D3DLOCKED_RECT lockedrect; if (!FAILED(IDirect3DDevice9_StretchRect(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, NULL, stretchsurface, NULL, D3DTEXF_POINT))) { if (!FAILED(IDirect3DSurface9_LockRect(stretchsurface, &lockedrect, NULL, D3DLOCK_READONLY))) { int line; unsigned char *row = (unsigned char *)lockedrect.pBits + x * 4 + lockedrect.Pitch * (vid.height - 1 - y); for (line = 0;line < height;line++, row -= lockedrect.Pitch) memcpy(outpixels + line * width * 4, row, width * 4); IDirect3DSurface9_UnlockRect(stretchsurface); } } IDirect3DSurface9_Release(stretchsurface); } // code scraps //IDirect3DSurface9 *syssurface = NULL; //if (!FAILED(IDirect3DDevice9_CreateRenderTarget(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DMULTISAMPLE_NONE, 0, FALSE, &stretchsurface, NULL))) //if (!FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(vid_d3d9dev, vid.width, vid.height, D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &syssurface, NULL))) //IDirect3DDevice9_GetRenderTargetData(vid_d3d9dev, gl_state.d3drt_backbuffercolorsurface, syssurface); //if (!FAILED(IDirect3DDevice9_GetFrontBufferData(vid_d3d9dev, 0, syssurface))) //if (!FAILED(IDirect3DSurface9_LockRect(syssurface, &lockedrect, NULL, D3DLOCK_READONLY))) //IDirect3DSurface9_UnlockRect(syssurface); //IDirect3DSurface9_Release(syssurface); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_GetPixelsBGRA(x, y, width, height, outpixels); break; } } // called at beginning of frame void R_Mesh_Start(void) { BACKENDACTIVECHECK R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); R_Mesh_SetUseVBO(); if (gl_printcheckerror.integer && !gl_paranoid.integer) { Con_Printf("WARNING: gl_printcheckerror is on but gl_paranoid is off, turning it on...\n"); Cvar_SetValueQuick(&gl_paranoid, 1); } } static qboolean GL_Backend_CompileShader(int programobject, GLenum shadertypeenum, const char *shadertype, int numstrings, const char **strings) { int shaderobject; int shadercompiled; char compilelog[MAX_INPUTLINE]; shaderobject = qglCreateShader(shadertypeenum);CHECKGLERROR if (!shaderobject) return false; qglShaderSource(shaderobject, numstrings, strings, NULL);CHECKGLERROR qglCompileShader(shaderobject);CHECKGLERROR qglGetShaderiv(shaderobject, GL_COMPILE_STATUS, &shadercompiled);CHECKGLERROR qglGetShaderInfoLog(shaderobject, sizeof(compilelog), NULL, compilelog);CHECKGLERROR if (compilelog[0] && ((strstr(compilelog, "error") || strstr(compilelog, "ERROR") || strstr(compilelog, "Error")) || ((strstr(compilelog, "WARNING") || strstr(compilelog, "warning") || strstr(compilelog, "Warning")) && developer.integer) || developer_extra.integer)) { int i, j, pretextlines = 0; for (i = 0;i < numstrings - 1;i++) for (j = 0;strings[i][j];j++) if (strings[i][j] == '\n') pretextlines++; Con_Printf("%s shader compile log:\n%s\n(line offset for any above warnings/errors: %i)\n", shadertype, compilelog, pretextlines); } if (!shadercompiled) { qglDeleteShader(shaderobject);CHECKGLERROR return false; } qglAttachShader(programobject, shaderobject);CHECKGLERROR qglDeleteShader(shaderobject);CHECKGLERROR return true; } unsigned int GL_Backend_CompileProgram(int vertexstrings_count, const char **vertexstrings_list, int geometrystrings_count, const char **geometrystrings_list, int fragmentstrings_count, const char **fragmentstrings_list) { GLint programlinked; GLuint programobject = 0; char linklog[MAX_INPUTLINE]; CHECKGLERROR programobject = qglCreateProgram();CHECKGLERROR if (!programobject) return 0; qglBindAttribLocation(programobject, GLSLATTRIB_POSITION , "Attrib_Position" ); qglBindAttribLocation(programobject, GLSLATTRIB_COLOR , "Attrib_Color" ); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD0, "Attrib_TexCoord0"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD1, "Attrib_TexCoord1"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD2, "Attrib_TexCoord2"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD3, "Attrib_TexCoord3"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD4, "Attrib_TexCoord4"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD5, "Attrib_TexCoord5"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD6, "Attrib_SkeletalIndex"); qglBindAttribLocation(programobject, GLSLATTRIB_TEXCOORD7, "Attrib_SkeletalWeight"); #ifndef USE_GLES2 if(vid.support.gl20shaders130) qglBindFragDataLocation(programobject, 0, "dp_FragColor"); #endif if (vertexstrings_count && !GL_Backend_CompileShader(programobject, GL_VERTEX_SHADER, "vertex", vertexstrings_count, vertexstrings_list)) goto cleanup; #if defined(GL_GEOMETRY_SHADER) && !defined(USE_GLES2) if (geometrystrings_count && !GL_Backend_CompileShader(programobject, GL_GEOMETRY_SHADER, "geometry", geometrystrings_count, geometrystrings_list)) goto cleanup; #endif if (fragmentstrings_count && !GL_Backend_CompileShader(programobject, GL_FRAGMENT_SHADER, "fragment", fragmentstrings_count, fragmentstrings_list)) goto cleanup; qglLinkProgram(programobject);CHECKGLERROR qglGetProgramiv(programobject, GL_LINK_STATUS, &programlinked);CHECKGLERROR qglGetProgramInfoLog(programobject, sizeof(linklog), NULL, linklog);CHECKGLERROR if (linklog[0]) { if (strstr(linklog, "error") || strstr(linklog, "ERROR") || strstr(linklog, "Error") || strstr(linklog, "WARNING") || strstr(linklog, "warning") || strstr(linklog, "Warning") || developer_extra.integer) Con_DPrintf("program link log:\n%s\n", linklog); // software vertex shader is ok but software fragment shader is WAY // too slow, fail program if so. // NOTE: this string might be ATI specific, but that's ok because the // ATI R300 chip (Radeon 9500-9800/X300) is the most likely to use a // software fragment shader due to low instruction and dependent // texture limits. if (strstr(linklog, "fragment shader will run in software")) programlinked = false; } if (!programlinked) goto cleanup; return programobject; cleanup: qglDeleteProgram(programobject);CHECKGLERROR return 0; } void GL_Backend_FreeProgram(unsigned int prog) { CHECKGLERROR qglDeleteProgram(prog); CHECKGLERROR } // renders triangles using vertices from the active arrays int paranoidblah = 0; void R_Mesh_Draw(int firstvertex, int numvertices, int firsttriangle, int numtriangles, const int *element3i, const r_meshbuffer_t *element3i_indexbuffer, int element3i_bufferoffset, const unsigned short *element3s, const r_meshbuffer_t *element3s_indexbuffer, int element3s_bufferoffset) { unsigned int numelements = numtriangles * 3; int bufferobject3i; size_t bufferoffset3i; int bufferobject3s; size_t bufferoffset3s; if (numvertices < 3 || numtriangles < 1) { if (numvertices < 0 || numtriangles < 0 || developer_extra.integer) Con_DPrintf("R_Mesh_Draw(%d, %d, %d, %d, %8p, %8p, %8x, %8p, %8p, %8x);\n", firstvertex, numvertices, firsttriangle, numtriangles, (void *)element3i, (void *)element3i_indexbuffer, (int)element3i_bufferoffset, (void *)element3s, (void *)element3s_indexbuffer, (int)element3s_bufferoffset); return; } // adjust the pointers for firsttriangle if (element3i) element3i += firsttriangle * 3; if (element3i_indexbuffer) element3i_bufferoffset += firsttriangle * 3 * sizeof(*element3i); if (element3s) element3s += firsttriangle * 3; if (element3s_indexbuffer) element3s_bufferoffset += firsttriangle * 3 * sizeof(*element3s); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // check if the user specified to ignore static index buffers if (!gl_state.usevbo_staticindex || (gl_vbo.integer == 3 && !vid.forcevbo && (element3i_bufferoffset || element3s_bufferoffset))) { element3i_indexbuffer = NULL; element3s_indexbuffer = NULL; } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; } // upload a dynamic index buffer if needed if (element3s) { if (!element3s_indexbuffer && gl_state.usevbo_dynamicindex) element3s_indexbuffer = R_BufferData_Store(numelements * sizeof(*element3s), (void *)element3s, R_BUFFERDATA_INDEX16, &element3s_bufferoffset); } else if (element3i) { if (!element3i_indexbuffer && gl_state.usevbo_dynamicindex) element3i_indexbuffer = R_BufferData_Store(numelements * sizeof(*element3i), (void *)element3i, R_BUFFERDATA_INDEX32, &element3i_bufferoffset); } bufferobject3i = element3i_indexbuffer ? element3i_indexbuffer->bufferobject : 0; bufferoffset3i = element3i_bufferoffset; bufferobject3s = element3s_indexbuffer ? element3s_indexbuffer->bufferobject : 0; bufferoffset3s = element3s_bufferoffset; r_refdef.stats[r_stat_draws]++; r_refdef.stats[r_stat_draws_vertices] += numvertices; r_refdef.stats[r_stat_draws_elements] += numelements; if (gl_paranoid.integer) { unsigned int i; // LordHavoc: disabled this - it needs to be updated to handle components and gltype and stride in each array #if 0 unsigned int j, size; const int *p; // note: there's no validation done here on buffer objects because it // is somewhat difficult to get at the data, and gl_paranoid can be // used without buffer objects if the need arises // (the data could be gotten using glMapBuffer but it would be very // slow due to uncachable video memory reads) if (!qglIsEnabled(GL_VERTEX_ARRAY)) Con_Print("R_Mesh_Draw: vertex array not enabled\n"); CHECKGLERROR if (gl_state.pointer_vertex_pointer) for (j = 0, size = numvertices * 3, p = (int *)((float *)gl_state.pointer_vertex + firstvertex * 3);j < size;j++, p++) paranoidblah += *p; if (gl_state.pointer_color_enabled) { if (!qglIsEnabled(GL_COLOR_ARRAY)) Con_Print("R_Mesh_Draw: color array set but not enabled\n"); CHECKGLERROR if (gl_state.pointer_color && gl_state.pointer_color_enabled) for (j = 0, size = numvertices * 4, p = (int *)((float *)gl_state.pointer_color + firstvertex * 4);j < size;j++, p++) paranoidblah += *p; } for (i = 0;i < vid.texarrayunits;i++) { if (gl_state.units[i].arrayenabled) { GL_ClientActiveTexture(i); if (!qglIsEnabled(GL_TEXTURE_COORD_ARRAY)) Con_Print("R_Mesh_Draw: texcoord array set but not enabled\n"); CHECKGLERROR if (gl_state.units[i].pointer_texcoord && gl_state.units[i].arrayenabled) for (j = 0, size = numvertices * gl_state.units[i].arraycomponents, p = (int *)((float *)gl_state.units[i].pointer_texcoord + firstvertex * gl_state.units[i].arraycomponents);j < size;j++, p++) paranoidblah += *p; } } #endif if (element3i) { for (i = 0;i < (unsigned int) numtriangles * 3;i++) { if (element3i[i] < firstvertex || element3i[i] >= firstvertex + numvertices) { Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3i array\n", element3i[i], firstvertex, firstvertex + numvertices); return; } } } if (element3s) { for (i = 0;i < (unsigned int) numtriangles * 3;i++) { if (element3s[i] < firstvertex || element3s[i] >= firstvertex + numvertices) { Con_Printf("R_Mesh_Draw: invalid vertex index %i (outside range %i - %i) in element3s array\n", element3s[i], firstvertex, firstvertex + numvertices); return; } } } } if (r_render.integer || r_refdef.draw2dstage) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: CHECKGLERROR if (gl_mesh_testmanualfeeding.integer) { #ifndef USE_GLES2 unsigned int i, j, element; const GLfloat *p; qglBegin(GL_TRIANGLES); if(vid.renderpath == RENDERPATH_GL20) { for (i = 0;i < (unsigned int) numtriangles * 3;i++) { if (element3i) element = element3i[i]; else if (element3s) element = element3s[i]; else element = firstvertex + i; for (j = 0;j < vid.texarrayunits;j++) { if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) { if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (gl_state.units[j].pointer_texcoord_components == 4) qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2], p[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1], p[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, p[0], p[1]); else qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, p[0]); } else if (gl_state.units[j].pointer_texcoord_gltype == (int)(GL_SHORT | 0x80000000)) { const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (gl_state.units[j].pointer_texcoord_components == 4) qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2], s[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1], s[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, s[0], s[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, s[0]); } else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) { const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (gl_state.units[j].pointer_texcoord_components == 4) qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f), sb[2] * (1.0f / 127.0f), sb[3] * (1.0f / 127.0f)); else if (gl_state.units[j].pointer_texcoord_components == 3) qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f), sb[2] * (1.0f / 127.0f)); else if (gl_state.units[j].pointer_texcoord_components == 2) qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f), sb[1] * (1.0f / 127.0f)); else if (gl_state.units[j].pointer_texcoord_components == 1) qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 127.0f)); } else if (gl_state.units[j].pointer_texcoord_gltype == GL_UNSIGNED_BYTE) { const GLubyte *sb = (const GLubyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (gl_state.units[j].pointer_texcoord_components == 4) qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f), sb[2] * (1.0f / 255.0f), sb[3] * (1.0f / 255.0f)); else if (gl_state.units[j].pointer_texcoord_components == 3) qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f), sb[2] * (1.0f / 255.0f)); else if (gl_state.units[j].pointer_texcoord_components == 2) qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f), sb[1] * (1.0f / 255.0f)); else if (gl_state.units[j].pointer_texcoord_components == 1) qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0] * (1.0f / 255.0f)); } else if (gl_state.units[j].pointer_texcoord_gltype == (int)(GL_UNSIGNED_BYTE | 0x80000000)) { const GLubyte *sb = (const GLubyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (gl_state.units[j].pointer_texcoord_components == 4) qglVertexAttrib4f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2], sb[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglVertexAttrib3f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1], sb[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglVertexAttrib2f(GLSLATTRIB_TEXCOORD0 + j, sb[0], sb[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglVertexAttrib1f(GLSLATTRIB_TEXCOORD0 + j, sb[0]); } } } if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) { if (gl_state.pointer_color_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); qglVertexAttrib4f(GLSLATTRIB_COLOR, p[0], p[1], p[2], p[3]); } else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) { const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); qglVertexAttrib4Nub(GLSLATTRIB_COLOR, ub[0], ub[1], ub[2], ub[3]); } } if (gl_state.pointer_vertex_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); if (gl_state.pointer_vertex_components == 4) qglVertexAttrib4f(GLSLATTRIB_POSITION, p[0], p[1], p[2], p[3]); else if (gl_state.pointer_vertex_components == 3) qglVertexAttrib3f(GLSLATTRIB_POSITION, p[0], p[1], p[2]); else qglVertexAttrib2f(GLSLATTRIB_POSITION, p[0], p[1]); } } } else { for (i = 0;i < (unsigned int) numtriangles * 3;i++) { if (element3i) element = element3i[i]; else if (element3s) element = element3s[i]; else element = firstvertex + i; for (j = 0;j < vid.texarrayunits;j++) { if (gl_state.units[j].pointer_texcoord_pointer && gl_state.units[j].arrayenabled) { if (gl_state.units[j].pointer_texcoord_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (vid.texarrayunits > 1) { if (gl_state.units[j].pointer_texcoord_components == 4) qglMultiTexCoord4f(GL_TEXTURE0 + j, p[0], p[1], p[2], p[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglMultiTexCoord3f(GL_TEXTURE0 + j, p[0], p[1], p[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglMultiTexCoord2f(GL_TEXTURE0 + j, p[0], p[1]); else qglMultiTexCoord1f(GL_TEXTURE0 + j, p[0]); } else { if (gl_state.units[j].pointer_texcoord_components == 4) qglTexCoord4f(p[0], p[1], p[2], p[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglTexCoord3f(p[0], p[1], p[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglTexCoord2f(p[0], p[1]); else qglTexCoord1f(p[0]); } } else if (gl_state.units[j].pointer_texcoord_gltype == GL_SHORT) { const GLshort *s = (const GLshort *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (vid.texarrayunits > 1) { if (gl_state.units[j].pointer_texcoord_components == 4) qglMultiTexCoord4f(GL_TEXTURE0 + j, s[0], s[1], s[2], s[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglMultiTexCoord3f(GL_TEXTURE0 + j, s[0], s[1], s[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglMultiTexCoord2f(GL_TEXTURE0 + j, s[0], s[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglMultiTexCoord1f(GL_TEXTURE0 + j, s[0]); } else { if (gl_state.units[j].pointer_texcoord_components == 4) qglTexCoord4f(s[0], s[1], s[2], s[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglTexCoord3f(s[0], s[1], s[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglTexCoord2f(s[0], s[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglTexCoord1f(s[0]); } } else if (gl_state.units[j].pointer_texcoord_gltype == GL_BYTE) { const GLbyte *sb = (const GLbyte *)((const unsigned char *)gl_state.units[j].pointer_texcoord_pointer + element * gl_state.units[j].pointer_texcoord_stride); if (vid.texarrayunits > 1) { if (gl_state.units[j].pointer_texcoord_components == 4) qglMultiTexCoord4f(GL_TEXTURE0 + j, sb[0], sb[1], sb[2], sb[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglMultiTexCoord3f(GL_TEXTURE0 + j, sb[0], sb[1], sb[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglMultiTexCoord2f(GL_TEXTURE0 + j, sb[0], sb[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglMultiTexCoord1f(GL_TEXTURE0 + j, sb[0]); } else { if (gl_state.units[j].pointer_texcoord_components == 4) qglTexCoord4f(sb[0], sb[1], sb[2], sb[3]); else if (gl_state.units[j].pointer_texcoord_components == 3) qglTexCoord3f(sb[0], sb[1], sb[2]); else if (gl_state.units[j].pointer_texcoord_components == 2) qglTexCoord2f(sb[0], sb[1]); else if (gl_state.units[j].pointer_texcoord_components == 1) qglTexCoord1f(sb[0]); } } } } if (gl_state.pointer_color_pointer && gl_state.pointer_color_enabled && gl_state.pointer_color_components == 4) { if (gl_state.pointer_color_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); qglColor4f(p[0], p[1], p[2], p[3]); } else if (gl_state.pointer_color_gltype == GL_UNSIGNED_BYTE) { const GLubyte *ub = (const GLubyte *)((const unsigned char *)gl_state.pointer_color_pointer + element * gl_state.pointer_color_stride); qglColor4ub(ub[0], ub[1], ub[2], ub[3]); } } if (gl_state.pointer_vertex_gltype == GL_FLOAT) { p = (const GLfloat *)((const unsigned char *)gl_state.pointer_vertex_pointer + element * gl_state.pointer_vertex_stride); if (gl_state.pointer_vertex_components == 4) qglVertex4f(p[0], p[1], p[2], p[3]); else if (gl_state.pointer_vertex_components == 3) qglVertex3f(p[0], p[1], p[2]); else qglVertex2f(p[0], p[1]); } } } qglEnd(); CHECKGLERROR #endif } else if (bufferobject3s) { GL_BindEBO(bufferobject3s); #ifndef USE_GLES2 if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) { qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); CHECKGLERROR } else #endif { qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); CHECKGLERROR } } else if (bufferobject3i) { GL_BindEBO(bufferobject3i); #ifndef USE_GLES2 if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) { qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); CHECKGLERROR } else #endif { qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); CHECKGLERROR } } else if (element3s) { GL_BindEBO(0); #ifndef USE_GLES2 if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) { qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_SHORT, element3s); CHECKGLERROR } else #endif { qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); CHECKGLERROR } } else if (element3i) { GL_BindEBO(0); #ifndef USE_GLES2 if (gl_mesh_drawrangeelements.integer && qglDrawRangeElements != NULL) { qglDrawRangeElements(GL_TRIANGLES, firstvertex, firstvertex + numvertices - 1, numelements, GL_UNSIGNED_INT, element3i); CHECKGLERROR } else #endif { qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); CHECKGLERROR } } else { qglDrawArrays(GL_TRIANGLES, firstvertex, numvertices); CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (gl_state.d3dvertexbuffer && ((element3s && element3s_indexbuffer) || (element3i && element3i_indexbuffer))) { if (element3s_indexbuffer) { IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3s_indexbuffer->devicebuffer); IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3s_bufferoffset>>1, numtriangles); } else if (element3i_indexbuffer) { IDirect3DDevice9_SetIndices(vid_d3d9dev, (IDirect3DIndexBuffer9 *)element3i_indexbuffer->devicebuffer); IDirect3DDevice9_DrawIndexedPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, 0, firstvertex, numvertices, element3i_bufferoffset>>2, numtriangles); } else IDirect3DDevice9_DrawPrimitive(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices); } else { if (element3s) IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3s, D3DFMT_INDEX16, gl_state.d3dvertexdata, gl_state.d3dvertexsize); else if (element3i) IDirect3DDevice9_DrawIndexedPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, firstvertex, numvertices, numtriangles, element3i, D3DFMT_INDEX32, gl_state.d3dvertexdata, gl_state.d3dvertexsize); else IDirect3DDevice9_DrawPrimitiveUP(vid_d3d9dev, D3DPT_TRIANGLELIST, numvertices, (void *)gl_state.d3dvertexdata, gl_state.d3dvertexsize); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_DrawTriangles(firstvertex, numvertices, numtriangles, element3i, element3s); break; case RENDERPATH_GLES1: case RENDERPATH_GLES2: // GLES does not have glDrawRangeElements so this is a bit shorter than the GL20 path if (bufferobject3s) { GL_BindEBO(bufferobject3s); qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, (void *)bufferoffset3s); CHECKGLERROR } else if (bufferobject3i) { GL_BindEBO(bufferobject3i); qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, (void *)bufferoffset3i); CHECKGLERROR } else if (element3s) { GL_BindEBO(0); qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_SHORT, element3s); CHECKGLERROR } else if (element3i) { GL_BindEBO(0); qglDrawElements(GL_TRIANGLES, numelements, GL_UNSIGNED_INT, element3i); CHECKGLERROR } else { qglDrawArrays(GL_TRIANGLES, firstvertex, numvertices); CHECKGLERROR } break; } } } // restores backend state, used when done with 3D rendering void R_Mesh_Finish(void) { R_Mesh_SetRenderTargets(0, NULL, NULL, NULL, NULL, NULL); } r_meshbuffer_t *R_Mesh_CreateMeshBuffer(const void *data, size_t size, const char *name, qboolean isindexbuffer, qboolean isuniformbuffer, qboolean isdynamic, qboolean isindex16) { r_meshbuffer_t *buffer; if (isuniformbuffer) { if (!vid.support.arb_uniform_buffer_object) return NULL; } else { if (!vid.support.arb_vertex_buffer_object) return NULL; if (!isdynamic && !(isindexbuffer ? gl_state.usevbo_staticindex : gl_state.usevbo_staticvertex)) return NULL; } buffer = (r_meshbuffer_t *)Mem_ExpandableArray_AllocRecord(&gl_state.meshbufferarray); memset(buffer, 0, sizeof(*buffer)); buffer->bufferobject = 0; buffer->devicebuffer = NULL; buffer->size = size; buffer->isindexbuffer = isindexbuffer; buffer->isuniformbuffer = isuniformbuffer; buffer->isdynamic = isdynamic; buffer->isindex16 = isindex16; strlcpy(buffer->name, name, sizeof(buffer->name)); R_Mesh_UpdateMeshBuffer(buffer, data, size, false, 0); return buffer; } void R_Mesh_UpdateMeshBuffer(r_meshbuffer_t *buffer, const void *data, size_t size, qboolean subdata, size_t offset) { if (!buffer) return; if (buffer->isindexbuffer) { r_refdef.stats[r_stat_indexbufferuploadcount]++; r_refdef.stats[r_stat_indexbufferuploadsize] += (int)size; } else { r_refdef.stats[r_stat_vertexbufferuploadcount]++; r_refdef.stats[r_stat_vertexbufferuploadsize] += (int)size; } if (!subdata) buffer->size = size; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (!buffer->bufferobject) qglGenBuffersARB(1, (GLuint *)&buffer->bufferobject); if (buffer->isuniformbuffer) GL_BindUBO(buffer->bufferobject); else if (buffer->isindexbuffer) GL_BindEBO(buffer->bufferobject); else GL_BindVBO(buffer->bufferobject); { int buffertype; buffertype = buffer->isindexbuffer ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER; #ifdef GL_UNIFORM_BUFFER if (buffer->isuniformbuffer) buffertype = GL_UNIFORM_BUFFER; #endif if (subdata) qglBufferSubDataARB(buffertype, offset, size, data); else qglBufferDataARB(buffertype, size, data, buffer->isdynamic ? GL_STREAM_DRAW : GL_STATIC_DRAW); } if (buffer->isuniformbuffer) GL_BindUBO(0); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { int result; void *datapointer = NULL; if (buffer->isindexbuffer) { IDirect3DIndexBuffer9 *d3d9indexbuffer = (IDirect3DIndexBuffer9 *)buffer->devicebuffer; if (offset+size > buffer->size || !buffer->devicebuffer) { if (buffer->devicebuffer) IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9*)buffer->devicebuffer); buffer->devicebuffer = NULL; if (FAILED(result = IDirect3DDevice9_CreateIndexBuffer(vid_d3d9dev, (unsigned int)(offset+size), buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9indexbuffer, NULL))) Sys_Error("IDirect3DDevice9_CreateIndexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, buffer->isindex16 ? (int)D3DFMT_INDEX16 : (int)D3DFMT_INDEX32, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9indexbuffer, (int)result); buffer->devicebuffer = (void *)d3d9indexbuffer; buffer->size = offset+size; } if (!FAILED(IDirect3DIndexBuffer9_Lock(d3d9indexbuffer, (unsigned int)offset, (unsigned int)size, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) { if (data) memcpy(datapointer, data, size); else memset(datapointer, 0, size); IDirect3DIndexBuffer9_Unlock(d3d9indexbuffer); } } else { IDirect3DVertexBuffer9 *d3d9vertexbuffer = (IDirect3DVertexBuffer9 *)buffer->devicebuffer; if (offset+size > buffer->size || !buffer->devicebuffer) { if (buffer->devicebuffer) IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9*)buffer->devicebuffer); buffer->devicebuffer = NULL; if (FAILED(result = IDirect3DDevice9_CreateVertexBuffer(vid_d3d9dev, (unsigned int)(offset+size), buffer->isdynamic ? D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? D3DPOOL_DEFAULT : D3DPOOL_MANAGED, &d3d9vertexbuffer, NULL))) Sys_Error("IDirect3DDevice9_CreateVertexBuffer(%p, %d, %x, %x, %x, %p, NULL) returned %x\n", vid_d3d9dev, (int)size, buffer->isdynamic ? (int)D3DUSAGE_DYNAMIC : 0, 0, buffer->isdynamic ? (int)D3DPOOL_DEFAULT : (int)D3DPOOL_MANAGED, &d3d9vertexbuffer, (int)result); buffer->devicebuffer = (void *)d3d9vertexbuffer; buffer->size = offset+size; } if (!FAILED(IDirect3DVertexBuffer9_Lock(d3d9vertexbuffer, (unsigned int)offset, (unsigned int)size, &datapointer, buffer->isdynamic ? D3DLOCK_DISCARD : 0))) { if (data) memcpy(datapointer, data, size); else memset(datapointer, 0, size); IDirect3DVertexBuffer9_Unlock(d3d9vertexbuffer); } } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } } void R_Mesh_DestroyMeshBuffer(r_meshbuffer_t *buffer) { if (!buffer) return; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: // GL clears the binding if we delete something bound if (gl_state.uniformbufferobject == buffer->bufferobject) gl_state.uniformbufferobject = 0; if (gl_state.vertexbufferobject == buffer->bufferobject) gl_state.vertexbufferobject = 0; if (gl_state.elementbufferobject == buffer->bufferobject) gl_state.elementbufferobject = 0; qglDeleteBuffersARB(1, (GLuint *)&buffer->bufferobject); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (gl_state.d3dvertexbuffer == (void *)buffer) gl_state.d3dvertexbuffer = NULL; if (buffer->devicebuffer) { if (buffer->isindexbuffer) IDirect3DIndexBuffer9_Release((IDirect3DIndexBuffer9 *)buffer->devicebuffer); else IDirect3DVertexBuffer9_Release((IDirect3DVertexBuffer9 *)buffer->devicebuffer); buffer->devicebuffer = NULL; } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } Mem_ExpandableArray_FreeRecord(&gl_state.meshbufferarray, (void *)buffer); } static const char *buffertypename[R_BUFFERDATA_COUNT] = {"vertex", "index16", "index32", "uniform"}; void GL_Mesh_ListVBOs(qboolean printeach) { int i, endindex; int type; int isdynamic; int index16count, index16mem; int index32count, index32mem; int vertexcount, vertexmem; int uniformcount, uniformmem; int totalcount, totalmem; size_t bufferstat[R_BUFFERDATA_COUNT][2][2]; r_meshbuffer_t *buffer; memset(bufferstat, 0, sizeof(bufferstat)); endindex = (int)Mem_ExpandableArray_IndexRange(&gl_state.meshbufferarray); for (i = 0;i < endindex;i++) { buffer = (r_meshbuffer_t *) Mem_ExpandableArray_RecordAtIndex(&gl_state.meshbufferarray, i); if (!buffer) continue; if (buffer->isuniformbuffer) type = R_BUFFERDATA_UNIFORM; else if (buffer->isindexbuffer && buffer->isindex16) type = R_BUFFERDATA_INDEX16; else if (buffer->isindexbuffer) type = R_BUFFERDATA_INDEX32; else type = R_BUFFERDATA_VERTEX; isdynamic = buffer->isdynamic; bufferstat[type][isdynamic][0]++; bufferstat[type][isdynamic][1] += buffer->size; if (printeach) Con_Printf("buffer #%i %s = %i bytes (%s %s)\n", i, buffer->name, (int)buffer->size, isdynamic ? "dynamic" : "static", buffertypename[type]); } index16count = (int)(bufferstat[R_BUFFERDATA_INDEX16][0][0] + bufferstat[R_BUFFERDATA_INDEX16][1][0]); index16mem = (int)(bufferstat[R_BUFFERDATA_INDEX16][0][1] + bufferstat[R_BUFFERDATA_INDEX16][1][1]); index32count = (int)(bufferstat[R_BUFFERDATA_INDEX32][0][0] + bufferstat[R_BUFFERDATA_INDEX32][1][0]); index32mem = (int)(bufferstat[R_BUFFERDATA_INDEX32][0][1] + bufferstat[R_BUFFERDATA_INDEX32][1][1]); vertexcount = (int)(bufferstat[R_BUFFERDATA_VERTEX ][0][0] + bufferstat[R_BUFFERDATA_VERTEX ][1][0]); vertexmem = (int)(bufferstat[R_BUFFERDATA_VERTEX ][0][1] + bufferstat[R_BUFFERDATA_VERTEX ][1][1]); uniformcount = (int)(bufferstat[R_BUFFERDATA_UNIFORM][0][0] + bufferstat[R_BUFFERDATA_UNIFORM][1][0]); uniformmem = (int)(bufferstat[R_BUFFERDATA_UNIFORM][0][1] + bufferstat[R_BUFFERDATA_UNIFORM][1][1]); totalcount = index16count + index32count + vertexcount + uniformcount; totalmem = index16mem + index32mem + vertexmem + uniformmem; Con_Printf("%i 16bit indexbuffers totalling %i bytes (%.3f MB)\n%i 32bit indexbuffers totalling %i bytes (%.3f MB)\n%i vertexbuffers totalling %i bytes (%.3f MB)\n%i uniformbuffers totalling %i bytes (%.3f MB)\ncombined %i buffers totalling %i bytes (%.3fMB)\n", index16count, index16mem, index16mem / 10248576.0, index32count, index32mem, index32mem / 10248576.0, vertexcount, vertexmem, vertexmem / 10248576.0, uniformcount, uniformmem, uniformmem / 10248576.0, totalcount, totalmem, totalmem / 10248576.0); } void R_Mesh_VertexPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) { int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; gl_state.pointer_vertex_components = components; gl_state.pointer_vertex_gltype = gltype; gl_state.pointer_vertex_stride = stride; gl_state.pointer_vertex_pointer = pointer; gl_state.pointer_vertex_vertexbuffer = vertexbuffer; gl_state.pointer_vertex_offset = bufferoffset; CHECKGLERROR GL_BindVBO(bufferobject); qglVertexPointer(components, gltype, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } #endif break; case RENDERPATH_GL20: case RENDERPATH_GLES2: if (gl_state.pointer_vertex_components != components || gl_state.pointer_vertex_gltype != gltype || gl_state.pointer_vertex_stride != stride || gl_state.pointer_vertex_pointer != pointer || gl_state.pointer_vertex_vertexbuffer != vertexbuffer || gl_state.pointer_vertex_offset != bufferoffset) { int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; gl_state.pointer_vertex_components = components; gl_state.pointer_vertex_gltype = gltype; gl_state.pointer_vertex_stride = stride; gl_state.pointer_vertex_pointer = pointer; gl_state.pointer_vertex_vertexbuffer = vertexbuffer; gl_state.pointer_vertex_offset = bufferoffset; CHECKGLERROR GL_BindVBO(bufferobject); // LordHavoc: special flag added to gltype for unnormalized types qglVertexAttribPointer(GLSLATTRIB_POSITION, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } } void R_Mesh_ColorPointer(int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) { // note: vertexbuffer may be non-NULL even if pointer is NULL, so check // the pointer only. switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 CHECKGLERROR if (pointer) { // caller wants color array enabled int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; if (!gl_state.pointer_color_enabled) { gl_state.pointer_color_enabled = true; CHECKGLERROR qglEnableClientState(GL_COLOR_ARRAY);CHECKGLERROR } if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) { gl_state.pointer_color_components = components; gl_state.pointer_color_gltype = gltype; gl_state.pointer_color_stride = stride; gl_state.pointer_color_pointer = pointer; gl_state.pointer_color_vertexbuffer = vertexbuffer; gl_state.pointer_color_offset = bufferoffset; CHECKGLERROR GL_BindVBO(bufferobject); qglColorPointer(components, gltype, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } } else { // caller wants color array disabled if (gl_state.pointer_color_enabled) { gl_state.pointer_color_enabled = false; CHECKGLERROR qglDisableClientState(GL_COLOR_ARRAY);CHECKGLERROR // when color array is on the glColor gets trashed, set it again qglColor4f(gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR } } #endif break; case RENDERPATH_GL20: case RENDERPATH_GLES2: CHECKGLERROR if (pointer) { // caller wants color array enabled int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; if (!gl_state.pointer_color_enabled) { gl_state.pointer_color_enabled = true; CHECKGLERROR qglEnableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR } if (gl_state.pointer_color_components != components || gl_state.pointer_color_gltype != gltype || gl_state.pointer_color_stride != stride || gl_state.pointer_color_pointer != pointer || gl_state.pointer_color_vertexbuffer != vertexbuffer || gl_state.pointer_color_offset != bufferoffset) { gl_state.pointer_color_components = components; gl_state.pointer_color_gltype = gltype; gl_state.pointer_color_stride = stride; gl_state.pointer_color_pointer = pointer; gl_state.pointer_color_vertexbuffer = vertexbuffer; gl_state.pointer_color_offset = bufferoffset; CHECKGLERROR GL_BindVBO(bufferobject); // LordHavoc: special flag added to gltype for unnormalized types qglVertexAttribPointer(GLSLATTRIB_COLOR, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } } else { // caller wants color array disabled if (gl_state.pointer_color_enabled) { gl_state.pointer_color_enabled = false; CHECKGLERROR qglDisableVertexAttribArray(GLSLATTRIB_COLOR);CHECKGLERROR // when color array is on the glColor gets trashed, set it again qglVertexAttrib4f(GLSLATTRIB_COLOR, gl_state.color4f[0], gl_state.color4f[1], gl_state.color4f[2], gl_state.color4f[3]);CHECKGLERROR } } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } } void R_Mesh_TexCoordPointer(unsigned int unitnum, int components, int gltype, size_t stride, const void *pointer, const r_meshbuffer_t *vertexbuffer, size_t bufferoffset) { gltextureunit_t *unit = gl_state.units + unitnum; // update array settings // note: there is no need to check bufferobject here because all cases // that involve a valid bufferobject also supply a texcoord array switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: #ifndef USE_GLES2 CHECKGLERROR if (pointer) { int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; // texture array unit is enabled, enable the array if (!unit->arrayenabled) { unit->arrayenabled = true; GL_ClientActiveTexture(unitnum); qglEnableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR } // texcoord array if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) { unit->pointer_texcoord_components = components; unit->pointer_texcoord_gltype = gltype; unit->pointer_texcoord_stride = stride; unit->pointer_texcoord_pointer = pointer; unit->pointer_texcoord_vertexbuffer = vertexbuffer; unit->pointer_texcoord_offset = bufferoffset; GL_ClientActiveTexture(unitnum); GL_BindVBO(bufferobject); qglTexCoordPointer(components, gltype, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } } else { // texture array unit is disabled, disable the array if (unit->arrayenabled) { unit->arrayenabled = false; GL_ClientActiveTexture(unitnum); qglDisableClientState(GL_TEXTURE_COORD_ARRAY);CHECKGLERROR } } #endif break; case RENDERPATH_GL20: case RENDERPATH_GLES2: CHECKGLERROR if (pointer) { int bufferobject = vertexbuffer ? vertexbuffer->bufferobject : 0; // texture array unit is enabled, enable the array if (!unit->arrayenabled) { unit->arrayenabled = true; qglEnableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR } // texcoord array if (unit->pointer_texcoord_components != components || unit->pointer_texcoord_gltype != gltype || unit->pointer_texcoord_stride != stride || unit->pointer_texcoord_pointer != pointer || unit->pointer_texcoord_vertexbuffer != vertexbuffer || unit->pointer_texcoord_offset != bufferoffset) { unit->pointer_texcoord_components = components; unit->pointer_texcoord_gltype = gltype; unit->pointer_texcoord_stride = stride; unit->pointer_texcoord_pointer = pointer; unit->pointer_texcoord_vertexbuffer = vertexbuffer; unit->pointer_texcoord_offset = bufferoffset; GL_BindVBO(bufferobject); // LordHavoc: special flag added to gltype for unnormalized types qglVertexAttribPointer(unitnum+GLSLATTRIB_TEXCOORD0, components, gltype & ~0x80000000, (gltype & 0x80000000) == 0, (GLsizei)stride, bufferobject ? (void *)bufferoffset : pointer);CHECKGLERROR } } else { // texture array unit is disabled, disable the array if (unit->arrayenabled) { unit->arrayenabled = false; qglDisableVertexAttribArray(unitnum+GLSLATTRIB_TEXCOORD0);CHECKGLERROR } } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; } } int R_Mesh_TexBound(unsigned int unitnum, int id) { gltextureunit_t *unit = gl_state.units + unitnum; if (unitnum >= vid.teximageunits) return 0; if (id == GL_TEXTURE_2D) return unit->t2d; if (id == GL_TEXTURE_3D) return unit->t3d; if (id == GL_TEXTURE_CUBE_MAP) return unit->tcubemap; return 0; } void R_Mesh_CopyToTexture(rtexture_t *tex, int tx, int ty, int sx, int sy, int width, int height) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: R_Mesh_TexBind(0, tex); GL_ActiveTexture(0);CHECKGLERROR qglCopyTexSubImage2D(GL_TEXTURE_2D, 0, tx, ty, sx, sy, width, height);CHECKGLERROR break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { IDirect3DSurface9 *currentsurface = NULL; IDirect3DSurface9 *texturesurface = NULL; RECT sourcerect; RECT destrect; sourcerect.left = sx; sourcerect.top = sy; sourcerect.right = sx + width; sourcerect.bottom = sy + height; destrect.left = tx; destrect.top = ty; destrect.right = tx + width; destrect.bottom = ty + height; if (!FAILED(IDirect3DTexture9_GetSurfaceLevel(((IDirect3DTexture9 *)tex->d3dtexture), 0, &texturesurface))) { if (!FAILED(IDirect3DDevice9_GetRenderTarget(vid_d3d9dev, 0, ¤tsurface))) { IDirect3DDevice9_StretchRect(vid_d3d9dev, currentsurface, &sourcerect, texturesurface, &destrect, D3DTEXF_NONE); IDirect3DSurface9_Release(currentsurface); } IDirect3DSurface9_Release(texturesurface); } } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_CopyRectangleToTexture(tex->texnum, 0, tx, ty, sx, sy, width, height); break; } } #ifdef SUPPORTD3D int d3drswrap[16] = {D3DRS_WRAP0, D3DRS_WRAP1, D3DRS_WRAP2, D3DRS_WRAP3, D3DRS_WRAP4, D3DRS_WRAP5, D3DRS_WRAP6, D3DRS_WRAP7, D3DRS_WRAP8, D3DRS_WRAP9, D3DRS_WRAP10, D3DRS_WRAP11, D3DRS_WRAP12, D3DRS_WRAP13, D3DRS_WRAP14, D3DRS_WRAP15}; #endif void R_Mesh_ClearBindingsForTexture(int texnum) { gltextureunit_t *unit; unsigned int unitnum; // this doesn't really unbind the texture, but it does prevent a mistaken "do nothing" behavior on the next time this same texnum is bound on the same unit as the same type (this mainly affects r_shadow_bouncegrid because 3D textures are so rarely used) for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) { unit = gl_state.units + unitnum; if (unit->t2d == texnum) unit->t2d = -1; if (unit->t3d == texnum) unit->t3d = -1; if (unit->tcubemap == texnum) unit->tcubemap = -1; } } void R_Mesh_TexBind(unsigned int unitnum, rtexture_t *tex) { gltextureunit_t *unit = gl_state.units + unitnum; int tex2d, tex3d, texcubemap, texnum; if (unitnum >= vid.teximageunits) return; // if (unit->texture == tex) // return; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (!tex) { tex = r_texture_white; // not initialized enough yet... if (!tex) return; } unit->texture = tex; texnum = R_GetTexture(tex); switch(tex->gltexturetypeenum) { case GL_TEXTURE_2D: if (unit->t2d != texnum) {GL_ActiveTexture(unitnum);unit->t2d = texnum;qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR}break; case GL_TEXTURE_3D: if (unit->t3d != texnum) {GL_ActiveTexture(unitnum);unit->t3d = texnum;qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR}break; case GL_TEXTURE_CUBE_MAP: if (unit->tcubemap != texnum) {GL_ActiveTexture(unitnum);unit->tcubemap = texnum;qglBindTexture(GL_TEXTURE_CUBE_MAP, unit->tcubemap);CHECKGLERROR}break; } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: unit->texture = tex; tex2d = 0; tex3d = 0; texcubemap = 0; if (tex) { texnum = R_GetTexture(tex); switch(tex->gltexturetypeenum) { case GL_TEXTURE_2D: tex2d = texnum; break; case GL_TEXTURE_3D: tex3d = texnum; break; case GL_TEXTURE_CUBE_MAP: texcubemap = texnum; break; } } // update 2d texture binding if (unit->t2d != tex2d) { GL_ActiveTexture(unitnum); if (tex2d) { if (unit->t2d == 0) { qglEnable(GL_TEXTURE_2D);CHECKGLERROR } } else { if (unit->t2d) { qglDisable(GL_TEXTURE_2D);CHECKGLERROR } } unit->t2d = tex2d; qglBindTexture(GL_TEXTURE_2D, unit->t2d);CHECKGLERROR } // update 3d texture binding if (unit->t3d != tex3d) { GL_ActiveTexture(unitnum); if (tex3d) { if (unit->t3d == 0) { qglEnable(GL_TEXTURE_3D);CHECKGLERROR } } else { if (unit->t3d) { qglDisable(GL_TEXTURE_3D);CHECKGLERROR } } unit->t3d = tex3d; qglBindTexture(GL_TEXTURE_3D, unit->t3d);CHECKGLERROR } // update cubemap texture binding if (unit->tcubemap != texcubemap) { GL_ActiveTexture(unitnum); if (texcubemap) { if (unit->tcubemap == 0) { qglEnable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR } } else { if (unit->tcubemap) { qglDisable(GL_TEXTURE_CUBE_MAP);CHECKGLERROR } } unit->tcubemap = texcubemap; qglBindTexture(GL_TEXTURE_CUBE_MAP, unit->tcubemap);CHECKGLERROR } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D { extern cvar_t gl_texture_anisotropy; if (!tex) { tex = r_texture_white; // not initialized enough yet... if (!tex) return; } // upload texture if needed R_GetTexture(tex); if (unit->texture == tex) return; unit->texture = tex; IDirect3DDevice9_SetTexture(vid_d3d9dev, unitnum, (IDirect3DBaseTexture9*)tex->d3dtexture); //IDirect3DDevice9_SetRenderState(vid_d3d9dev, d3drswrap[unitnum], (tex->flags & TEXF_CLAMP) ? (D3DWRAPCOORD_0 | D3DWRAPCOORD_1 | D3DWRAPCOORD_2) : 0); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSU, tex->d3daddressu); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSV, tex->d3daddressv); if (tex->d3daddressw) IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_ADDRESSW, tex->d3daddressw); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAGFILTER, tex->d3dmagfilter); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MINFILTER, tex->d3dminfilter); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPFILTER, tex->d3dmipfilter); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MIPMAPLODBIAS, tex->d3dmipmaplodbias); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXMIPLEVEL, tex->d3dmaxmiplevelfilter); IDirect3DDevice9_SetSamplerState(vid_d3d9dev, unitnum, D3DSAMP_MAXANISOTROPY, gl_texture_anisotropy.integer); } #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: if (!tex) { tex = r_texture_white; // not initialized enough yet... if (!tex) return; } texnum = R_GetTexture(tex); if (unit->texture == tex) return; unit->texture = tex; DPSOFTRAST_SetTexture(unitnum, texnum); break; } } void R_Mesh_TexMatrix(unsigned int unitnum, const matrix4x4_t *matrix) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #ifdef GL_MODELVIEW if (matrix && matrix->m[3][3]) { gltextureunit_t *unit = gl_state.units + unitnum; // texmatrix specified, check if it is different if (!unit->texmatrixenabled || memcmp(&unit->matrix, matrix, sizeof(matrix4x4_t))) { float glmatrix[16]; unit->texmatrixenabled = true; unit->matrix = *matrix; CHECKGLERROR Matrix4x4_ToArrayFloatGL(&unit->matrix, glmatrix); GL_ActiveTexture(unitnum); qglMatrixMode(GL_TEXTURE);CHECKGLERROR qglLoadMatrixf(glmatrix);CHECKGLERROR qglMatrixMode(GL_MODELVIEW);CHECKGLERROR } } else { // no texmatrix specified, revert to identity gltextureunit_t *unit = gl_state.units + unitnum; if (unit->texmatrixenabled) { unit->texmatrixenabled = false; unit->matrix = identitymatrix; CHECKGLERROR GL_ActiveTexture(unitnum); qglMatrixMode(GL_TEXTURE);CHECKGLERROR qglLoadIdentity();CHECKGLERROR qglMatrixMode(GL_MODELVIEW);CHECKGLERROR } } #endif break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; } } void R_Mesh_TexCombine(unsigned int unitnum, int combinergb, int combinealpha, int rgbscale, int alphascale) { #if defined(GL_TEXTURE_ENV) && !defined(USE_GLES2) gltextureunit_t *unit = gl_state.units + unitnum; CHECKGLERROR switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: // do nothing break; case RENDERPATH_GL13: case RENDERPATH_GLES1: // GL_ARB_texture_env_combine if (!combinergb) combinergb = GL_MODULATE; if (!combinealpha) combinealpha = GL_MODULATE; if (!rgbscale) rgbscale = 1; if (!alphascale) alphascale = 1; if (combinergb != combinealpha || rgbscale != 1 || alphascale != 1) { if (combinergb == GL_DECAL) combinergb = GL_INTERPOLATE; if (unit->combine != GL_COMBINE) { unit->combine = GL_COMBINE; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);CHECKGLERROR qglTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_TEXTURE);CHECKGLERROR // for GL_INTERPOLATE mode } if (unit->combinergb != combinergb) { unit->combinergb = combinergb; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, unit->combinergb);CHECKGLERROR } if (unit->combinealpha != combinealpha) { unit->combinealpha = combinealpha; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, unit->combinealpha);CHECKGLERROR } if (unit->rgbscale != rgbscale) { unit->rgbscale = rgbscale; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, unit->rgbscale);CHECKGLERROR } if (unit->alphascale != alphascale) { unit->alphascale = alphascale; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, unit->alphascale);CHECKGLERROR } } else { if (unit->combine != combinergb) { unit->combine = combinergb; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR } } break; case RENDERPATH_GL11: // normal GL texenv if (!combinergb) combinergb = GL_MODULATE; if (unit->combine != combinergb) { unit->combine = combinergb; GL_ActiveTexture(unitnum); qglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, unit->combine);CHECKGLERROR } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: break; } #endif } void R_Mesh_ResetTextureState(void) { unsigned int unitnum; BACKENDACTIVECHECK for (unitnum = 0;unitnum < vid.teximageunits;unitnum++) R_Mesh_TexBind(unitnum, NULL); for (unitnum = 0;unitnum < vid.texarrayunits;unitnum++) R_Mesh_TexCoordPointer(unitnum, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: for (unitnum = 0;unitnum < vid.texunits;unitnum++) { R_Mesh_TexCombine(unitnum, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexMatrix(unitnum, NULL); } break; } } #ifdef SUPPORTD3D //#define r_vertex3f_d3d9fvf (D3DFVF_XYZ) //#define r_vertexgeneric_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1) //#define r_vertexmesh_d3d9fvf (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX5 | D3DFVF_TEXCOORDSIZE1(3) | D3DFVF_TEXCOORDSIZE2(3) | D3DFVF_TEXCOORDSIZE3(3)) D3DVERTEXELEMENT9 r_vertex3f_d3d9elements[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, D3DDECL_END() }; D3DVERTEXELEMENT9 r_vertexgeneric_d3d9elements[] = { {0, (int)((size_t)&((r_vertexgeneric_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, (int)((size_t)&((r_vertexgeneric_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, {0, (int)((size_t)&((r_vertexgeneric_t *)0)->texcoord2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, D3DDECL_END() }; D3DVERTEXELEMENT9 r_vertexmesh_d3d9elements[] = { {0, (int)((size_t)&((r_vertexmesh_t *)0)->vertex3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->color4f ), D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordtexture2f ), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->svector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->tvector3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->normal3f ), D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->texcoordlightmap2f), D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->skeletalindex4ub ), D3DDECLTYPE_UBYTE4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 6}, {0, (int)((size_t)&((r_vertexmesh_t *)0)->skeletalweight4ub ), D3DDECLTYPE_UBYTE4N, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 7}, D3DDECL_END() }; IDirect3DVertexDeclaration9 *r_vertex3f_d3d9decl; IDirect3DVertexDeclaration9 *r_vertexgeneric_d3d9decl; IDirect3DVertexDeclaration9 *r_vertexmesh_d3d9decl; #endif static void R_Mesh_InitVertexDeclarations(void) { #ifdef SUPPORTD3D r_vertex3f_d3d9decl = NULL; r_vertexgeneric_d3d9decl = NULL; r_vertexmesh_d3d9decl = NULL; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GL13: case RENDERPATH_GL11: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9elements, &r_vertex3f_d3d9decl); IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9elements, &r_vertexgeneric_d3d9decl); IDirect3DDevice9_CreateVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9elements, &r_vertexmesh_d3d9decl); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: break; } #endif } static void R_Mesh_DestroyVertexDeclarations(void) { #ifdef SUPPORTD3D if (r_vertex3f_d3d9decl) IDirect3DVertexDeclaration9_Release(r_vertex3f_d3d9decl); r_vertex3f_d3d9decl = NULL; if (r_vertexgeneric_d3d9decl) IDirect3DVertexDeclaration9_Release(r_vertexgeneric_d3d9decl); r_vertexgeneric_d3d9decl = NULL; if (r_vertexmesh_d3d9decl) IDirect3DVertexDeclaration9_Release(r_vertexmesh_d3d9decl); r_vertexmesh_d3d9decl = NULL; #endif } void R_Mesh_PrepareVertices_Vertex3f(int numvertices, const float *vertex3f, const r_meshbuffer_t *vertexbuffer, int bufferoffset) { // upload temporary vertexbuffer for this rendering if (!gl_state.usevbo_staticvertex) vertexbuffer = NULL; if (!vertexbuffer && gl_state.usevbo_dynamicvertex) vertexbuffer = R_BufferData_Store(numvertices * sizeof(float[3]), (void *)vertex3f, R_BUFFERDATA_VERTEX, &bufferoffset); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (vertexbuffer) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); } else { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); } break; case RENDERPATH_GL13: case RENDERPATH_GLES1: if (vertexbuffer) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } else { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } break; case RENDERPATH_GL11: if (vertexbuffer) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, bufferoffset); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } else { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, vertexbuffer, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertex3f_d3d9decl); if (vertexbuffer) IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(float[3])); else IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); gl_state.d3dvertexbuffer = (void *)vertexbuffer; gl_state.d3dvertexdata = (void *)vertex3f; gl_state.d3dvertexsize = sizeof(float[3]); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); DPSOFTRAST_SetColorPointer(NULL, 0); DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); break; } } r_vertexgeneric_t *R_Mesh_PrepareVertices_Generic_Lock(int numvertices) { size_t size; size = sizeof(r_vertexgeneric_t) * numvertices; if (gl_state.preparevertices_tempdatamaxsize < size) { gl_state.preparevertices_tempdatamaxsize = size; gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); } gl_state.preparevertices_vertexgeneric = (r_vertexgeneric_t *)gl_state.preparevertices_tempdata; gl_state.preparevertices_numvertices = numvertices; return gl_state.preparevertices_vertexgeneric; } qboolean R_Mesh_PrepareVertices_Generic_Unlock(void) { R_Mesh_PrepareVertices_Generic(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexgeneric, NULL, 0); gl_state.preparevertices_vertexgeneric = NULL; gl_state.preparevertices_numvertices = 0; return true; } void R_Mesh_PrepareVertices_Generic_Arrays(int numvertices, const float *vertex3f, const float *color4f, const float *texcoord2f) { int i; r_vertexgeneric_t *vertex; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (gl_state.usevbo_dynamicvertex) { r_meshbuffer_t *buffer_vertex3f = NULL; r_meshbuffer_t *buffer_color4f = NULL; r_meshbuffer_t *buffer_texcoord2f = NULL; int bufferoffset_vertex3f = 0; int bufferoffset_color4f = 0; int bufferoffset_texcoord2f = 0; buffer_color4f = R_BufferData_Store(numvertices * sizeof(float[4]), color4f , R_BUFFERDATA_VERTEX, &bufferoffset_color4f ); buffer_vertex3f = R_BufferData_Store(numvertices * sizeof(float[3]), vertex3f , R_BUFFERDATA_VERTEX, &bufferoffset_vertex3f ); buffer_texcoord2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoord2f, R_BUFFERDATA_VERTEX, &bufferoffset_texcoord2f); R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(float[3]) , vertex3f , buffer_vertex3f , bufferoffset_vertex3f ); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(float[4]) , color4f , buffer_color4f , bufferoffset_color4f ); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(float[2]) , texcoord2f , buffer_texcoord2f , bufferoffset_texcoord2f ); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(float[3]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); } else if (!vid.useinterleavedarrays) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); return; } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: if (!vid.useinterleavedarrays) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoord2f, NULL, 0); if (vid.texunits >= 2) R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); if (vid.texunits >= 3) R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); return; } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoord2f); DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(float[2]), NULL); DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), NULL); return; } // no quick path for this case, convert to vertex structs vertex = R_Mesh_PrepareVertices_Generic_Lock(numvertices); for (i = 0;i < numvertices;i++) VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); if (color4f) { for (i = 0;i < numvertices;i++) Vector4Copy(color4f + 4*i, vertex[i].color4f); } else { for (i = 0;i < numvertices;i++) Vector4Copy(gl_state.color4f, vertex[i].color4f); } if (texcoord2f) for (i = 0;i < numvertices;i++) Vector2Copy(texcoord2f + 2*i, vertex[i].texcoord2f); R_Mesh_PrepareVertices_Generic_Unlock(); R_Mesh_PrepareVertices_Generic(numvertices, vertex, NULL, 0); } void R_Mesh_PrepareVertices_Generic(int numvertices, const r_vertexgeneric_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset) { // upload temporary vertexbuffer for this rendering if (!gl_state.usevbo_staticvertex) vertexbuffer = NULL; if (!vertexbuffer && gl_state.usevbo_dynamicvertex) vertexbuffer = R_BufferData_Store(numvertices * sizeof(*vertex), (void *)vertex, R_BUFFERDATA_VERTEX, &bufferoffset); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(3, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); } break; case RENDERPATH_GL13: case RENDERPATH_GLES1: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); } break; case RENDERPATH_GL11: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoord2f - (unsigned char *)vertex)); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoord2f , NULL, 0); } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexgeneric_d3d9decl); if (vertexbuffer) IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(*vertex)); else IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); gl_state.d3dvertexbuffer = (void *)vertexbuffer; gl_state.d3dvertexdata = (void *)vertex; gl_state.d3dvertexsize = sizeof(*vertex); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoord2f); DPSOFTRAST_SetTexCoordPointer(1, 2, sizeof(*vertex), NULL); DPSOFTRAST_SetTexCoordPointer(2, 2, sizeof(*vertex), NULL); DPSOFTRAST_SetTexCoordPointer(3, 2, sizeof(*vertex), NULL); DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), NULL); break; } } r_vertexmesh_t *R_Mesh_PrepareVertices_Mesh_Lock(int numvertices) { size_t size; size = sizeof(r_vertexmesh_t) * numvertices; if (gl_state.preparevertices_tempdatamaxsize < size) { gl_state.preparevertices_tempdatamaxsize = size; gl_state.preparevertices_tempdata = Mem_Realloc(r_main_mempool, gl_state.preparevertices_tempdata, gl_state.preparevertices_tempdatamaxsize); } gl_state.preparevertices_vertexmesh = (r_vertexmesh_t *)gl_state.preparevertices_tempdata; gl_state.preparevertices_numvertices = numvertices; return gl_state.preparevertices_vertexmesh; } qboolean R_Mesh_PrepareVertices_Mesh_Unlock(void) { R_Mesh_PrepareVertices_Mesh(gl_state.preparevertices_numvertices, gl_state.preparevertices_vertexmesh, NULL, 0); gl_state.preparevertices_vertexmesh = NULL; gl_state.preparevertices_numvertices = 0; return true; } void R_Mesh_PrepareVertices_Mesh_Arrays(int numvertices, const float *vertex3f, const float *svector3f, const float *tvector3f, const float *normal3f, const float *color4f, const float *texcoordtexture2f, const float *texcoordlightmap2f) { int i; r_vertexmesh_t *vertex; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (gl_state.usevbo_dynamicvertex) { r_meshbuffer_t *buffer_vertex3f = NULL; r_meshbuffer_t *buffer_color4f = NULL; r_meshbuffer_t *buffer_texcoordtexture2f = NULL; r_meshbuffer_t *buffer_svector3f = NULL; r_meshbuffer_t *buffer_tvector3f = NULL; r_meshbuffer_t *buffer_normal3f = NULL; r_meshbuffer_t *buffer_texcoordlightmap2f = NULL; int bufferoffset_vertex3f = 0; int bufferoffset_color4f = 0; int bufferoffset_texcoordtexture2f = 0; int bufferoffset_svector3f = 0; int bufferoffset_tvector3f = 0; int bufferoffset_normal3f = 0; int bufferoffset_texcoordlightmap2f = 0; buffer_color4f = R_BufferData_Store(numvertices * sizeof(float[4]), color4f , R_BUFFERDATA_VERTEX, &bufferoffset_color4f ); buffer_vertex3f = R_BufferData_Store(numvertices * sizeof(float[3]), vertex3f , R_BUFFERDATA_VERTEX, &bufferoffset_vertex3f ); buffer_svector3f = R_BufferData_Store(numvertices * sizeof(float[3]), svector3f , R_BUFFERDATA_VERTEX, &bufferoffset_svector3f ); buffer_tvector3f = R_BufferData_Store(numvertices * sizeof(float[3]), tvector3f , R_BUFFERDATA_VERTEX, &bufferoffset_tvector3f ); buffer_normal3f = R_BufferData_Store(numvertices * sizeof(float[3]), normal3f , R_BUFFERDATA_VERTEX, &bufferoffset_normal3f ); buffer_texcoordtexture2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoordtexture2f , R_BUFFERDATA_VERTEX, &bufferoffset_texcoordtexture2f ); buffer_texcoordlightmap2f = R_BufferData_Store(numvertices * sizeof(float[2]), texcoordlightmap2f, R_BUFFERDATA_VERTEX, &bufferoffset_texcoordlightmap2f); R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(float[3]) , vertex3f , buffer_vertex3f , bufferoffset_vertex3f ); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(float[4]) , color4f , buffer_color4f , bufferoffset_color4f ); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(float[2]) , texcoordtexture2f , buffer_texcoordtexture2f , bufferoffset_texcoordtexture2f ); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(float[3]) , svector3f , buffer_svector3f , bufferoffset_svector3f ); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(float[3]) , tvector3f , buffer_tvector3f , bufferoffset_tvector3f ); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(float[3]) , normal3f , buffer_normal3f , bufferoffset_normal3f ); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(float[2]) , texcoordlightmap2f, buffer_texcoordlightmap2f, bufferoffset_texcoordlightmap2f); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(float[2]) , NULL , NULL , 0 ); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL , NULL , 0 ); } else if (!vid.useinterleavedarrays) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), svector3f, NULL, 0); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), tvector3f, NULL, 0); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT, sizeof(float[3]), normal3f, NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(unsigned char[4]), NULL, NULL, 0); return; } break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: if (!vid.useinterleavedarrays) { R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), vertex3f, NULL, 0); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), color4f, NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), texcoordtexture2f, NULL, 0); if (vid.texunits >= 2) R_Mesh_TexCoordPointer(1, 2, GL_FLOAT, sizeof(float[2]), texcoordlightmap2f, NULL, 0); if (vid.texunits >= 3) R_Mesh_TexCoordPointer(2, 2, GL_FLOAT, sizeof(float[2]), NULL, NULL, 0); return; } break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: DPSOFTRAST_SetVertexPointer(vertex3f, sizeof(float[3])); DPSOFTRAST_SetColorPointer(color4f, sizeof(float[4])); DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(float[2]), texcoordtexture2f); DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(float[3]), svector3f); DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(float[3]), tvector3f); DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(float[3]), normal3f); DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(float[2]), texcoordlightmap2f); return; } vertex = R_Mesh_PrepareVertices_Mesh_Lock(numvertices); for (i = 0;i < numvertices;i++) VectorCopy(vertex3f + 3*i, vertex[i].vertex3f); if (svector3f) for (i = 0;i < numvertices;i++) VectorCopy(svector3f + 3*i, vertex[i].svector3f); if (tvector3f) for (i = 0;i < numvertices;i++) VectorCopy(tvector3f + 3*i, vertex[i].tvector3f); if (normal3f) for (i = 0;i < numvertices;i++) VectorCopy(normal3f + 3*i, vertex[i].normal3f); if (color4f) { for (i = 0;i < numvertices;i++) Vector4Copy(color4f + 4*i, vertex[i].color4f); } else { for (i = 0;i < numvertices;i++) Vector4Copy(gl_state.color4f, vertex[i].color4f); } if (texcoordtexture2f) for (i = 0;i < numvertices;i++) Vector2Copy(texcoordtexture2f + 2*i, vertex[i].texcoordtexture2f); if (texcoordlightmap2f) for (i = 0;i < numvertices;i++) Vector2Copy(texcoordlightmap2f + 2*i, vertex[i].texcoordlightmap2f); R_Mesh_PrepareVertices_Mesh_Unlock(); R_Mesh_PrepareVertices_Mesh(numvertices, vertex, NULL, 0); } void R_Mesh_PrepareVertices_Mesh(int numvertices, const r_vertexmesh_t *vertex, const r_meshbuffer_t *vertexbuffer, int bufferoffset) { // upload temporary vertexbuffer for this rendering if (!gl_state.usevbo_staticvertex) vertexbuffer = NULL; if (!vertexbuffer && gl_state.usevbo_dynamicvertex) vertexbuffer = R_BufferData_Store(numvertices * sizeof(*vertex), (void *)vertex, R_BUFFERDATA_VERTEX, &bufferoffset); switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES2: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->svector3f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->tvector3f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->normal3f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(*vertex), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(*vertex), vertex->skeletalindex4ub , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->skeletalindex4ub - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(*vertex), vertex->skeletalweight4ub , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->skeletalweight4ub - (unsigned char *)vertex)); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT , sizeof(*vertex), vertex->svector3f , NULL, 0); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT , sizeof(*vertex), vertex->tvector3f , NULL, 0); R_Mesh_TexCoordPointer(3, 3, GL_FLOAT , sizeof(*vertex), vertex->normal3f , NULL, 0); R_Mesh_TexCoordPointer(4, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); R_Mesh_TexCoordPointer(5, 2, GL_FLOAT , sizeof(*vertex), NULL, NULL, 0); R_Mesh_TexCoordPointer(6, 4, GL_UNSIGNED_BYTE | 0x80000000, sizeof(*vertex), vertex->skeletalindex4ub , NULL, 0); R_Mesh_TexCoordPointer(7, 4, GL_UNSIGNED_BYTE, sizeof(*vertex), vertex->skeletalweight4ub , NULL, 0); } break; case RENDERPATH_GL13: case RENDERPATH_GLES1: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordlightmap2f - (unsigned char *)vertex)); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); R_Mesh_TexCoordPointer(1, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordlightmap2f, NULL, 0); } break; case RENDERPATH_GL11: if (vertexbuffer) { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->vertex3f - (unsigned char *)vertex)); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->color4f - (unsigned char *)vertex)); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , vertexbuffer, bufferoffset + (int)((unsigned char *)vertex->texcoordtexture2f - (unsigned char *)vertex)); } else { R_Mesh_VertexPointer( 3, GL_FLOAT , sizeof(*vertex), vertex->vertex3f , NULL, 0); R_Mesh_ColorPointer( 4, GL_FLOAT , sizeof(*vertex), vertex->color4f , NULL, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT , sizeof(*vertex), vertex->texcoordtexture2f , NULL, 0); } break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetVertexDeclaration(vid_d3d9dev, r_vertexmesh_d3d9decl); if (vertexbuffer) IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, (IDirect3DVertexBuffer9*)vertexbuffer->devicebuffer, bufferoffset, sizeof(*vertex)); else IDirect3DDevice9_SetStreamSource(vid_d3d9dev, 0, NULL, 0, 0); gl_state.d3dvertexbuffer = (void *)vertexbuffer; gl_state.d3dvertexdata = (void *)vertex; gl_state.d3dvertexsize = sizeof(*vertex); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_SetVertexPointer(vertex->vertex3f, sizeof(*vertex)); DPSOFTRAST_SetColorPointer(vertex->color4f, sizeof(*vertex)); DPSOFTRAST_SetTexCoordPointer(0, 2, sizeof(*vertex), vertex->texcoordtexture2f); DPSOFTRAST_SetTexCoordPointer(1, 3, sizeof(*vertex), vertex->svector3f); DPSOFTRAST_SetTexCoordPointer(2, 3, sizeof(*vertex), vertex->tvector3f); DPSOFTRAST_SetTexCoordPointer(3, 3, sizeof(*vertex), vertex->normal3f); DPSOFTRAST_SetTexCoordPointer(4, 2, sizeof(*vertex), vertex->texcoordlightmap2f); break; } } void GL_BlendEquationSubtract(qboolean negated) { if(negated) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglBlendEquationEXT(GL_FUNC_REVERSE_SUBTRACT); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_SUBTRACT); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_BlendSubtract(true); break; } } else { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: qglBlendEquationEXT(GL_FUNC_ADD); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D IDirect3DDevice9_SetRenderState(vid_d3d9dev, D3DRS_BLENDOP, D3DBLENDOP_ADD); #endif break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: DPSOFTRAST_BlendSubtract(false); break; } } } darkplaces/conproc.h0000664000175000017500000000213313067716216014003 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // conproc.h #ifndef CONPROC_H #define CONPROC_H #define CCOM_WRITE_TEXT 0x2 // Param1 : Text #define CCOM_GET_TEXT 0x3 // Param1 : Begin line // Param2 : End line #define CCOM_GET_SCR_LINES 0x4 // No params #define CCOM_SET_SCR_LINES 0x5 // Param1 : Number of lines void InitConProc (HANDLE hFile, HANDLE heventParent, HANDLE heventChild); void DeinitConProc (void); #endif darkplaces/cl_video_jamdecode.c0000664000175000017500000002377013067716216016124 0ustar kalevkalev// JAM format decoder, used by Blood Omnicide #ifdef LIBAVCODEC //#define JAM_USELIBAVCODECSCALE #endif typedef struct jamdecodestream_s { qfile_t *file; double info_framerate; unsigned int info_frames; unsigned int info_imagewidth; unsigned int info_imageheight; double info_aspectratio; float colorscale; unsigned char colorsub; // info used during decoding unsigned char *frame; unsigned char *frame_prev; unsigned char frame_palette[768]; unsigned char *frame_compressed; unsigned int framesize; unsigned int framenum; // libavcodec scaling #ifdef JAM_USELIBAVCODECSCALE unsigned char *frame_output_buffer; AVFrame *frame_output; AVFrame *frame_output_scale; unsigned int framewidth; unsigned int frameheight; #endif // channel the sound file is being played on int sndchan; } jamdecodestream_t; // opens a stream void jam_close(void *stream); unsigned int jam_getwidth(void *stream); unsigned int jam_getheight(void *stream); double jam_getframerate(void *stream); double jam_getaspectratio(void *stream); int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow); void *jam_open(clvideo_t *video, char *filename, const char **errorstring) { char jamHead[16]; jamdecodestream_t *s; char *wavename; // allocate stream structure s = (jamdecodestream_t *)Z_Malloc(sizeof(jamdecodestream_t)); memset(s, 0, sizeof(jamdecodestream_t)); if (s == NULL) { *errorstring = "unable to allocate memory for stream info structure"; return NULL; } s->sndchan = -1; // open file s->file = FS_OpenVirtualFile(filename, true); if (!s->file) { *errorstring = "unable to open videofile"; jam_close(s); return NULL; } // read header if (!FS_Read(s->file, jamHead, 16)) { *errorstring = "JamDecoder: unexpected EOF reading header"; jam_close(s); return NULL; } if (memcmp(jamHead, "JAM", 4)) { *errorstring = "JamDecoder: not a JAM file"; jam_close(s); return NULL; } s->info_imagewidth = LittleLong(*(jamHead + 4)); s->info_imageheight = LittleLong(*(jamHead + 8)); s->info_frames = LittleLong(*(jamHead + 12)) - 1; s->info_framerate = 15; s->info_aspectratio = (double)s->info_imagewidth / (double)s->info_imageheight; s->colorscale = 0.90; s->colorsub = 4; s->framesize = s->info_imagewidth * s->info_imageheight; // allocate frame input/output if (s->framesize < 0) { *errorstring = "JamDecoder: bad framesize"; jam_close(s); return NULL; } s->frame = (unsigned char *)Z_Malloc(s->framesize * 2); s->frame_prev = (unsigned char *)Z_Malloc(s->framesize * 2); s->frame_compressed = (unsigned char *)Z_Malloc(s->framesize); if (s->frame_compressed == NULL || s->frame == NULL || s->frame_prev == NULL) { *errorstring = "JamDecoder: unable to allocate memory for video decoder"; jam_close(s); return NULL; } // scale support provided by libavcodec #ifdef JAM_USELIBAVCODECSCALE s->framewidth = s->info_imagewidth; s->frameheight = s->info_imageheight; // min size if (cl_video_libavcodec_minwidth.integer > 0) s->info_imagewidth = max(s->info_imagewidth, (unsigned int)cl_video_libavcodec_minwidth.integer); if (cl_video_libavcodec_minheight.integer > 0) s->info_imageheight = max(s->info_imageheight, (unsigned int)cl_video_libavcodec_minheight.integer); // allocate output s->frame_output_buffer = (unsigned char *)Z_Malloc(s->framesize * 4); s->frame_output = AvCodec_AllocFrame(); s->frame_output_scale = AvCodec_AllocFrame(); if (!s->frame_output_buffer || !s->frame_output || !s->frame_output_scale) { *errorstring = "JamDecoder: failed to allocate LibAvcodec frame"; jam_close(s); Z_Free(s); return NULL; } #endif // everything is ok // set the module functions s->framenum = 0; video->close = jam_close; video->getwidth = jam_getwidth; video->getheight = jam_getheight; video->getframerate = jam_getframerate; video->decodeframe = jam_video; video->getaspectratio = jam_getaspectratio; // set sound size_t namelen; namelen = strlen(filename) + 10; wavename = (char *)Z_Malloc(namelen); if (wavename) { sfx_t* sfx; FS_StripExtension(filename, wavename, namelen); strlcat(wavename, ".wav", namelen); sfx = S_PrecacheSound(wavename, false, false); if (sfx != NULL) s->sndchan = S_StartSound (-1, 0, sfx, vec3_origin, 1.0f, 0); else s->sndchan = -1; Z_Free(wavename); } return s; } // closes a stream void jam_close(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; if (s == NULL) return; if (s->frame_compressed) Z_Free(s->frame_compressed); s->frame_compressed = NULL; if (s->frame) Z_Free(s->frame); s->frame = NULL; if (s->frame_prev) Z_Free(s->frame_prev); s->frame_prev = NULL; if (s->sndchan != -1) S_StopChannel(s->sndchan, true, true); s->sndchan = -1; if (s->file) FS_Close(s->file); s->file = NULL; #ifdef JAM_USELIBAVCODECSCALE if (s->frame_output_buffer) Z_Free(s->frame_output_buffer); s->frame_output_buffer = NULL; if (s->frame_output) AvUtil_Free(s->frame_output); s->frame_output = NULL; if (s->frame_output_scale) AvUtil_Free(s->frame_output_scale); s->frame_output_scale = NULL; #endif Z_Free(s); } // returns the width of the image data unsigned int jam_getwidth(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; return s->info_imagewidth; } // returns the height of the image data unsigned int jam_getheight(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; return s->info_imageheight; } // returns the framerate of the stream double jam_getframerate(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; return s->info_framerate; } // returns aspect ration of the stream double jam_getaspectratio(void *stream) { jamdecodestream_t *s = (jamdecodestream_t *)stream; return s->info_aspectratio; } // decode JAM frame static void jam_decodeframe(unsigned char *inbuf, unsigned char *outbuf, unsigned char *prevbuf, int outsize, int frametype) { unsigned char *srcptr, *destptr, *prevptr; int bytesleft; unsigned int mark; unsigned short int bits; int rep; int backoffs; unsigned char *back; int i; srcptr = inbuf; destptr = outbuf; prevptr = prevbuf; bytesleft = outsize; if (frametype == 2) { memcpy(outbuf, inbuf, outsize); return; } while(bytesleft > 0) { memcpy(&mark, srcptr, 4); srcptr += 4; for(i=0; i<32 && bytesleft > 0; i++,mark=mark>>1) { if(mark & 1) { *destptr = *srcptr; destptr ++; prevptr ++; srcptr ++; bytesleft --; } else { bits = srcptr[0] + 256*srcptr[1]; rep = (bits >> 11) + 3; if(frametype == 1) { backoffs = 0x821 - (bits & 0x7ff); back = destptr - backoffs; } else { backoffs = 0x400 - (bits & 0x7ff); back = prevptr - backoffs; } srcptr += 2; memcpy(destptr, back, rep); destptr += rep; prevptr += rep; bytesleft -= rep; } } } } // decodes a video frame to the supplied output pixels int jam_video(void *stream, void *imagedata, unsigned int Rmask, unsigned int Gmask, unsigned int Bmask, unsigned int bytesperpixel, int imagebytesperrow) { unsigned char frameHead[16], *b; unsigned int compsize, outsize, i; jamdecodestream_t *s = (jamdecodestream_t *)stream; // EOF if (s->framenum >= s->info_frames) return 1; s->framenum++; readframe: // read frame header if (!FS_Read(s->file, &frameHead, 16)) { Con_Printf("JamDecoder: unexpected EOF on frame %i\n", s->framenum); return 1; } compsize = LittleLong(*(frameHead + 8)) - 16; outsize = LittleLong(*(frameHead + 12)); if (compsize > s->framesize || outsize > s->framesize) { Con_Printf("JamDecoder: got bogus header on frame %i\n", s->framenum); return 1; } // read frame contents if (!FS_Read(s->file, s->frame_compressed, compsize)) { Con_Printf("JamDecoder: unexpected EOF on frame %i\n", s->framenum); return 1; } // palette goes interleaved with special flag if (frameHead[0] == 2) { if (compsize == 768) { memcpy(s->frame_palette, s->frame_compressed, 768); for(i = 0; i < 768; i++) s->frame_palette[i] = (unsigned char)(bound(0, (s->frame_palette[i] * s->colorscale) - s->colorsub, 255)); goto readframe; } } else { // decode frame // shift buffers to provide current and previous one, decode b = s->frame_prev; s->frame_prev = s->frame; s->frame = b; jam_decodeframe(s->frame_compressed, s->frame, s->frame_prev, outsize, frameHead[4]); #ifdef JAM_USELIBAVCODECSCALE // make BGRA imagepixels from 8bit palettized frame b = (unsigned char *)s->frame_output_buffer; for(i = 0; i < s->framesize; i++) { *b++ = s->frame_palette[s->frame[i]*3 + 2]; *b++ = s->frame_palette[s->frame[i]*3 + 1]; *b++ = s->frame_palette[s->frame[i]*3]; *b++ = 255; } // scale AvCodec_FillPicture((AVPicture *)s->frame_output, (uint8_t *)s->frame_output_buffer, PIX_FMT_BGRA, s->framewidth, s->frameheight); AvCodec_FillPicture((AVPicture *)s->frame_output_scale, (uint8_t *)imagedata, PIX_FMT_BGRA, s->info_imagewidth, s->info_imageheight); SwsContext *scale_context = SwScale_GetCachedContext(NULL, s->framewidth, s->frameheight, PIX_FMT_BGRA, s->info_imagewidth, s->info_imageheight, PIX_FMT_BGRA, libavcodec_scalers[max(0, min(LIBAVCODEC_SCALERS, cl_video_libavcodec_scaler.integer))], NULL, NULL, NULL); if (!scale_context) { Con_Printf("JamDecoder: LibAvcodec: error creating scale context frame %i\n", s->framenum); return 1; } if (!SwScale_Scale(scale_context, s->frame_output->data, s->frame_output->linesize, 0, s->frameheight, s->frame_output_scale->data, s->frame_output_scale->linesize)) Con_Printf("JamDecoder: LibAvcodec : error scaling frame\n", s->framenum); SwScale_FreeContext(scale_context); #else // make BGRA imagepixels from 8bit palettized frame b = (unsigned char *)imagedata; for(i = 0; i < s->framesize; i++) { // bgra *b++ = s->frame_palette[s->frame[i]*3 + 2]; *b++ = s->frame_palette[s->frame[i]*3 + 1]; *b++ = s->frame_palette[s->frame[i]*3]; *b++ = 255; } #endif } return 0; } darkplaces/nexuiz.ico0000664000175000017500000106227613067716222014221 0ustar kalevkalev ( V00 ¨%~   ¨&F ˆ ÎV hV`( ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3"""3"""Z"""f"""f"""–"""™"""™"""™"""™"""™"""™"""™"""™"""™"""™"""™"""‡"""f"""f"""K"""3"""3""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""B"""f"""™"""½"""Þ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï"""«"""‡"""c"""3"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3"""]""""""¢"""Ì"""ù"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ê"""Æ"""™"""o"""K"""*""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""T""""""Ì"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""·"""~"""E""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""Z"""¨"""á"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ!!!ÿ!!!ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ!!!ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ò"""Š"""H""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""<""""""É"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""´"""o"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""H"""œ"""í"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Õ"""~"""$"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""B"""–"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿ ÿÿÿÿÿÿÿÿÿÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿÿÿÿÿÿÿÿÿÿ ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï"""{"""!""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""„"""ç"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Æ"""c""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""T"""º"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿ ÿÿ ÿ ÿ ÿ ÿ ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ ÿ ÿ ÿ ÿÿÿ ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""™"""6"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""Š"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ ÿ ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ!!!ÿ!!!ÿ!!!ÿ ÿ!!!ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï"""c"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""-"""Ÿ"""ù"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ!!!ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ!!!ÿ!!!ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ê"""x"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""E"""º"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ###ÿ###ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ###ÿ###ÿ"""ÿ"""ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""“"""!""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""E"""É"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ###ÿ$$$ÿ$$$ÿ$$$ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ü"""œ"""!""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888"""<"""Ã"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ$$$ÿ%%%ÿ%%%ÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ%%%ÿ%%%ÿ%%%ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ù"""–"""888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888"""6"""½"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ$$$ÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ&&&ÿ&&&ÿ&&&ÿ%%%ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""“"""888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888""""""¥"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ%%%ÿ&&&ÿ'''ÿ'''ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ***ÿ...ÿ222ÿ...ÿ***ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ'''ÿ'''ÿ'''ÿ&&&ÿ$$$ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ð"""r"""888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888""" """{"""ó"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ$$$ÿ&&&ÿ'''ÿ(((ÿ(((ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ***ÿ333ÿ888ÿ888ÿ888ÿ888ÿ888ÿ222ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ)))ÿ(((ÿ(((ÿ'''ÿ$$$ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Û"""N888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888"""W"""á"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ$$$ÿ&&&ÿ(((ÿ)))ÿ)))ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ+++ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ555ÿ+++ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ)))ÿ)))ÿ(((ÿ%%%ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""·"""*888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888""""""·"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ&&&ÿ)))ÿ***ÿ***ÿ***ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ444ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ555ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ***ÿ***ÿ***ÿ)))ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ù""""""888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888"""o"""ó"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ&&&ÿ***ÿ+++ÿ+++ÿ+++ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ---ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ555ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ+++ÿ+++ÿ***ÿ)))ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Õ"""9888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ"""-"""É"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ+++ÿ,,,ÿ,,,ÿ,,,ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ555ÿ888ÿ888ÿ888ÿ888ÿLLLÿƒƒƒÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ555ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ,,,ÿ,,,ÿ+++ÿ)))ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""“""" ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ"""x"""ù"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ%%%ÿ)))ÿ,,,ÿ---ÿ---ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ000ÿ888ÿ888ÿ888ÿ888ÿ888ÿÿ»»»ÿƒƒƒÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ333ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ---ÿ---ÿ,,,ÿ(((ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Þ"""?ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ""""""À"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ(((ÿ---ÿ...ÿ...ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ666ÿ888ÿ888ÿ888ÿ888ÿRRRÿÍÍÍÿ¿¿¿ÿ¨¨¨ÿ€€€ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ222ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ...ÿ...ÿ...ÿ---ÿ'''ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ü"""„"""ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔ"""Q"""ð"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ---ÿ...ÿ///ÿ///ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ444ÿ888ÿ888ÿ888ÿ888ÿ888ÿŸŸŸÿÓÓÓÿÂÂÂÿªªªÿ¡¡¡ÿyyyÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ222ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ///ÿ///ÿ...ÿ***ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""É"""$ÔÔÔÔÔÔÔÔÔÔÔÔ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕÕÕÕÕÕÕÕ""" """Ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ)))ÿ///ÿ000ÿ000ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ777ÿ888ÿ888ÿ888ÿ888ÿ]]]ÿÓÓÓÿÕÕÕÿÈÈÈÿ°°°ÿ¢¢¢ÿ¢¢¢ÿqqqÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ222ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ000ÿ///ÿ///ÿ(((ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""`ÕÕÕÕÕÕÕÕÕ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕÕÕÕÕ"""$"""Ò"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ...ÿ000ÿ111ÿ111ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ555ÿ888ÿ888ÿ888ÿ888ÿ999ÿ©©©ÿÐÐÐÿÒÒÒÿÊÊÊÿ´´´ÿ¥¥¥ÿ¤¤¤ÿ   ÿeeeÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ111ÿ111ÿ000ÿ***ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""™"""ÕÕÕ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÕÕÕ"""K"""ð"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ***ÿ111ÿ222ÿ222ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ777ÿ888ÿ888ÿ888ÿ888ÿhhhÿÕÕÕÿÒÒÒÿÒÒÒÿÍÍÍÿ¾¾¾ÿ®®®ÿ¦¦¦ÿ¥¥¥ÿ£££ÿXXXÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ222ÿ111ÿ111ÿ'''ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""É"""888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""ÿ"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ///ÿ333ÿ444ÿ444ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ777ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ´´´ÿÔÔÔÿÔÔÔÿÕÕÕÿÒÒÒÿÍÍÍÿ¾¾¾ÿ¬¬¬ÿ£££ÿ¢¢¢ÿšššÿLLLÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ444ÿ444ÿ333ÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ê"""B888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""" """·"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ(((ÿ333ÿ444ÿ555ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ888ÿ888ÿ888ÿ888ÿ888ÿpppÿÕÕÕÿÓÓÓÿÕÕÕÿÕÕÕÿÔÔÔÿÕÕÕÿËËËÿ¹¹¹ÿªªªÿ¢¢¢ÿ¢¢¢ÿÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ555ÿ555ÿ444ÿ000ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ü"""l888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""Ò"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ,,,ÿ555ÿ666ÿ666ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿºººÿÔÔÔÿÔÔÔÿÕÕÕÿÓÓÓÿÕÕÕÿÕÕÕÿÓÓÓÿÈÈÈÿ¶¶¶ÿ¨¨¨ÿ¢¢¢ÿ¢¢¢ÿ€€€ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ666ÿ555ÿ444ÿ(((ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""“"""888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""-"""ä"""ÿ"""ÿ"""ÿ"""ÿ%%%ÿ000ÿ666ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿxxxÿÕÕÕÿÓÓÓÿÕÕÕÿÕÕÕÿÔÔÔÿÕÕÕÿÕÕÕÿÕÕÕÿÒÒÒÿÆÆÆÿ´´´ÿ§§§ÿ¤¤¤ÿ¥¥¥ÿmmmÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ666ÿ+++ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""·""" 888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""H"""ó"""ÿ"""ÿ"""ÿ"""ÿ'''ÿ555ÿ777ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿ¿¿¿ÿÔÔÔÿÕÕÕÿÕÕÕÿÓÓÓÿÕÕÕÿÕÕÕÿÕÕÕÿÕÕÕÿÕÕÕÿÒÒÒÿÃÃÃÿ±±±ÿ§§§ÿ¥¥¥ÿ£££ÿXXXÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ777ÿ///ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""É"""888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""f"""ü"""ÿ"""ÿ"""ÿ"""ÿ)))ÿ777ÿ888ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ~~~ÿÕÕÕÿÓÓÓÿÕÕÕÿÕÕÕÿÔÔÔÿÕÕÕÿÕÕÕÿÕÕÕÿÕÕÕÿÕÕÕÿÕÕÕÿÏÏÏÿ¿¿¿ÿ­­­ÿ¤¤¤ÿ£££ÿ˜˜˜ÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ888ÿ333ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""á"""'888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""r"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ+++ÿ888ÿ:::ÿ:::ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ888ÿ888ÿ888ÿ888ÿBBBÿÂÂÂÿÖÖÖÿÖÖÖÿÖÖÖÿÔÔÔÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÌÌÌÿ¹¹¹ÿªªªÿ¤¤¤ÿ¤¤¤ÿˆˆˆÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ:::ÿ999ÿ777ÿ(((ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ç"""0888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""„"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ---ÿ:::ÿ;;;ÿ;;;ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ}}}ÿÖÖÖÿÕÕÕÿÖÖÖÿÖÖÖÿÕÕÕÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÔÔÔÿÉÉÉÿ···ÿ¨¨¨ÿ¤¤¤ÿ¥¥¥ÿpppÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ÿ:::ÿ999ÿ)))ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""í"""3888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""Š"""ÿ"""ÿ"""ÿ"""ÿ###ÿ111ÿ<<<ÿ===ÿ===ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ:::ÿ888ÿ888ÿ888ÿ888ÿAAAÿÂÂÂÿÖÖÖÿÖÖÖÿÖÖÖÿÔÔÔÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÓÓÓÿÆÆÆÿ´´´ÿ©©©ÿ§§§ÿ£££ÿVVVÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ===ÿ===ÿ;;;ÿ+++ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""<888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""ÿ"""ÿ"""ÿ"""ÿ$$$ÿ333ÿ===ÿ>>>ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ}}}ÿÖÖÖÿÕÕÕÿÖÖÖÿÖÖÖÿÕÕÕÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÑÑÑÿÁÁÁÿ¯¯¯ÿ¥¥¥ÿ¤¤¤ÿ–––ÿBBBÿ888ÿ888ÿ888ÿ888ÿ999ÿ>>>ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ>>>ÿ>>>ÿ<<<ÿ---ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""B888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿ"""Š"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ666ÿ>>>ÿ???ÿ???ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿAAAÿÂÂÂÿÖÖÖÿÖÖÖÿÖÖÖÿÔÔÔÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÌÌÌÿ¹¹¹ÿªªªÿ¤¤¤ÿ¤¤¤ÿÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ???ÿ>>>ÿ===ÿ---ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""<888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿ"""~"""ÿ"""ÿ"""ÿ"""ÿ'''ÿ777ÿ???ÿ@@@ÿ@@@ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ999ÿ888ÿ888ÿ888ÿ888ÿzzzÿÖÖÖÿÕÕÕÿ×××ÿÖÖÖÿÕÕÕÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÔÔÔÿÉÉÉÿ···ÿ¨¨¨ÿ¥¥¥ÿ£££ÿbbbÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ@@@ÿ???ÿ>>>ÿ///ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ð"""6888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿ"""r"""ÿ"""ÿ"""ÿ"""ÿ'''ÿ999ÿ@@@ÿAAAÿAAAÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿ===ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ¼¼¼ÿÖÖÖÿÖÖÖÿ×××ÿÕÕÕÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÖÖÖÿÓÓÓÿÄÄÄÿ²²²ÿ¨¨¨ÿ¦¦¦ÿžžžÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿAAAÿ@@@ÿ???ÿ000ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""í"""-888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿ"""f"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ999ÿAAAÿBBBÿBBBÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿ:::ÿ888ÿ888ÿ888ÿ888ÿoooÿ×××ÿ×××ÿØØØÿ×××ÿÖÖÖÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÑÑÑÿ¿¿¿ÿ®®®ÿ¥¥¥ÿ¤¤¤ÿ‰‰‰ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ:::ÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿBBBÿAAAÿ@@@ÿ000ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ê"""'888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""N"""ü"""ÿ"""ÿ"""ÿ%%%ÿ888ÿBBBÿCCCÿCCCÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿAAAÿ888ÿ888ÿ888ÿ888ÿ999ÿ³³³ÿ×××ÿ×××ÿØØØÿÖÖÖÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÌÌÌÿ¹¹¹ÿªªªÿ¦¦¦ÿ¨¨¨ÿkkkÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿBBBÿAAAÿ///ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ä"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""6"""ö"""ÿ"""ÿ"""ÿ###ÿ777ÿBBBÿCCCÿCCCÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿ;;;ÿ888ÿ888ÿ888ÿ888ÿdddÿØØØÿ×××ÿØØØÿ×××ÿÖÖÖÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÔÔÔÿÇÇÇÿµµµÿ©©©ÿ¨¨¨ÿ¢¢¢ÿLLLÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿBBBÿAAAÿ...ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""É""" ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""$"""ê"""ÿ"""ÿ"""ÿ"""ÿ666ÿ===ÿ;;;ÿ888ÿ888ÿ;;;ÿ>>>ÿCCCÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿ©©©ÿØØØÿØØØÿØØØÿÖÖÖÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÒÒÒÿÁÁÁÿ¯¯¯ÿ¦¦¦ÿ¥¥¥ÿŒŒŒÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿDDDÿCCCÿBBBÿ...ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""±"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""Û"""ÿ"""ÿ"""ÿ"""ÿ333ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ>>>ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿ===ÿ888ÿ888ÿ888ÿ888ÿZZZÿÕÕÕÿØØØÿØØØÿØØØÿÖÖÖÿ×××ÿØØØÿÚÚÚÿÛÛÛÿÛÛÛÿÙÙÙÿØØØÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÌÌÌÿºººÿªªªÿ¦¦¦ÿ¨¨¨ÿmmmÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿEEEÿDDDÿCCCÿ---ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""½"""ÿ"""ÿ"""ÿ"""ÿ000ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ999ÿ888ÿ888ÿ888ÿ888ÿšššÿ×××ÿ×××ÿØØØÿØØØÿØØØÿÚÚÚÿÛÛÛÿÜÜÜÿÞÞÞÿßßßÿÝÝÝÿÛÛÛÿØØØÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿ×××ÿÔÔÔÿÈÈÈÿµµµÿªªªÿ©©©ÿ¡¡¡ÿKKKÿ888ÿ888ÿ888ÿ888ÿ999ÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿFFFÿEEEÿDDDÿ+++ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""oÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""ÿ"""ÿ"""ÿ"""ÿ///ÿEEEÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ???ÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿ???ÿ888ÿ888ÿ888ÿ888ÿJJJÿÏÏÏÿÙÙÙÿÙÙÙÿÚÚÚÿÛÛÛÿÜÜÜÿÝÝÝÿÝÝÝÿÝÝÝÿßßßÿáááÿàààÿßßßÿÜÜÜÿÙÙÙÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÓÓÓÿÃÃÃÿ°°°ÿ§§§ÿ¦¦¦ÿŠŠŠÿ:::ÿ888ÿ888ÿ888ÿ888ÿ===ÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿGGGÿGGGÿFFFÿBBBÿ***ÿ"""ÿ"""ÿ"""ÿ"""ü"""?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿ"""`"""ÿ"""ÿ"""ÿ"""ÿ,,,ÿFFFÿGGGÿ888ÿ888ÿ888ÿ888ÿ:::ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ@@@ÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ|||ÿÙÙÙÿÙÙÙÿÛÛÛÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÐÐÐÿáááÿáááÿàààÿÞÞÞÿÛÛÛÿÙÙÙÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÍÍÍÿ»»»ÿ«««ÿ§§§ÿ©©©ÿgggÿ888ÿ888ÿ888ÿ888ÿ888ÿDDDÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿHHHÿGGGÿ???ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ê"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿ"""0"""ö"""ÿ"""ÿ"""ÿ)))ÿDDDÿGGGÿGGGÿ888ÿ888ÿ888ÿ888ÿ@@@ÿ¦¦¦ÿˆˆˆÿ[[[ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿFFFÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿ888ÿ888ÿ888ÿ888ÿ999ÿ³³³ÿÛÛÛÿÜÜÜÿÝÝÝÿÝÝÝÿÝÝÝÿÞÞÞÿÝÝÝÿ¶¶¶ÿtttÿEEEÿ™™™ÿàààÿàààÿàààÿÞÞÞÿÛÛÛÿÙÙÙÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÕÕÕÿÈÈÈÿµµµÿªªªÿ©©©ÿ   ÿGGGÿ888ÿ888ÿ888ÿ888ÿ:::ÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿHHHÿGGGÿ888ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Æ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿ""""""Þ"""ÿ"""ÿ"""ÿ###ÿ???ÿHHHÿIIIÿIIIÿ;;;ÿ888ÿ888ÿ888ÿ888ÿvvvÿÚÚÚÿÃÃÃÿ–––ÿfffÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿHHHÿ888ÿ888ÿ888ÿ888ÿMMMÿÒÒÒÿÝÝÝÿÞÞÞÿÞÞÞÿÞÞÞÿÖÖÖÿ§§§ÿeeeÿ888ÿ888ÿ888ÿ999ÿ¤¤¤ÿàààÿàààÿßßßÿÝÝÝÿÚÚÚÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÓÓÓÿÁÁÁÿ¯¯¯ÿ§§§ÿ§§§ÿƒƒƒÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿIIIÿGGGÿ333ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""–ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿ"""·"""ÿ"""ÿ"""ÿ"""ÿ888ÿHHHÿJJJÿJJJÿKKKÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ‘‘‘ÿÜÜÜÿÎÎÎÿ²²²ÿ“““ÿeeeÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿEEEÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿGGGÿ888ÿ888ÿ888ÿ888ÿ\\\ÿÝÝÝÿÙÙÙÿÌÌÌÿ®®®ÿÿPPPÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿ¼¼¼ÿàààÿàààÿßßßÿÝÝÝÿÚÚÚÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÍÍÍÿºººÿ«««ÿ¨¨¨ÿ¦¦¦ÿ^^^ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿIIIÿHHHÿ...ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""]ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""l"""ÿ"""ÿ"""ÿ"""ÿ111ÿHHHÿJJJÿJJJÿKKKÿKKKÿKKKÿ:::ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ¦¦¦ÿÝÝÝÿØØØÿ»»»ÿ¨¨¨ÿŽŽŽÿ^^^ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿIIIÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿ888ÿ888ÿ888ÿ888ÿAAAÿdddÿXXXÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿQQQÿÐÐÐÿàààÿàààÿÞÞÞÿÛÛÛÿÙÙÙÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿØØØÿÕÕÕÿÇÇÇÿ´´´ÿ©©©ÿ©©©ÿ™™™ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿIIIÿGGGÿ***ÿ"""ÿ"""ÿ"""ÿ"""ó"""$ÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""*"""ù"""ÿ"""ÿ"""ÿ---ÿHHHÿJJJÿKKKÿLLLÿLLLÿLLLÿLLLÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿ···ÿßßßÿÛÛÛÿÆÆÆÿ®®®ÿ£££ÿ†††ÿOOOÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿAAAÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿmmmÿàààÿàààÿàààÿÞÞÞÿÜÜÜÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÓÓÓÿÁÁÁÿ¯¯¯ÿ¨¨¨ÿ©©©ÿuuuÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿKKKÿKKKÿJJJÿAAAÿ"""ÿ"""ÿ"""ÿ"""ÿ"""É"""ÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""Õ"""ÿ"""ÿ"""ÿ$$$ÿDDDÿJJJÿKKKÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿ¿¿¿ÿàààÿÜÜÜÿÏÏÏÿ²²²ÿ£££ÿžžžÿrrrÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿIIIÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ˜˜˜ÿáááÿáááÿàààÿÞÞÞÿÛÛÛÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÍÍÍÿºººÿ­­­ÿªªªÿ¥¥¥ÿNNNÿ888ÿ888ÿ888ÿ888ÿ:::ÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿKKKÿKKKÿIIIÿ888ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""„ÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿ"""ÿ"""ÿ"""ÿ:::ÿKKKÿLLLÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿÇÇÇÿàààÿÞÞÞÿÕÕÕÿ´´´ÿ£££ÿ£££ÿ“““ÿ]]]ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿEEEÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ???ÿIIIÿJJJÿ:::ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿÀÀÀÿáááÿáááÿßßßÿÝÝÝÿÚÚÚÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÖÖÖÿÅÅÅÿ³³³ÿ¨¨¨ÿ¨¨¨ÿ‹‹‹ÿ999ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿJJJÿ000ÿ"""ÿ"""ÿ"""ÿ"""ü"""<ÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""E"""ÿ"""ÿ"""ÿ"""ÿ111ÿJJJÿKKKÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿVVVÿÔÔÔÿáááÿÞÞÞÿÙÙÙÿºººÿ¦¦¦ÿ£££ÿ   ÿ€€€ÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿKKKÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿAAAÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿAAAÿHHHÿMMMÿMMMÿMMMÿMMMÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ[[[ÿÚÚÚÿàààÿàààÿÞÞÞÿÛÛÛÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÏÏÏÿ¼¼¼ÿ­­­ÿ¨¨¨ÿªªªÿbbbÿ888ÿ888ÿ888ÿ888ÿ888ÿKKKÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿIIIÿ(((ÿ"""ÿ"""ÿ"""ÿ"""Þ""" ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """Þ"""ÿ"""ÿ"""ÿ***ÿIIIÿKKKÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿKKKÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿjjjÿÝÝÝÿáááÿÞÞÞÿÚÚÚÿ¼¼¼ÿ©©©ÿ£££ÿ£££ÿšššÿfffÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿGGGÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿGGGÿIIIÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿˆˆˆÿàààÿàààÿàààÿÝÝÝÿÚÚÚÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÖÖÖÿÉÉÉÿ¶¶¶ÿ«««ÿªªªÿ›››ÿAAAÿ888ÿ888ÿ888ÿ888ÿ>>>ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿLLLÿKKKÿ:::ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""™ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿ"""ÿ"""ÿ"""ÿ???ÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿKKKÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿÿáááÿáááÿßßßÿÚÚÚÿÁÁÁÿªªªÿ£££ÿ£££ÿ£££ÿ‡‡‡ÿLLLÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿÃÃÃÿàààÿàààÿßßßÿÜÜÜÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÙÙÙÿÔÔÔÿÁÁÁÿ¯¯¯ÿ¨¨¨ÿªªªÿuuuÿ888ÿ888ÿ888ÿ888ÿ888ÿIIIÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿKKKÿ444ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""B""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""6"""ü"""ÿ"""ÿ"""ÿ555ÿKKKÿMMMÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿJJJÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿ›››ÿâââÿâââÿàààÿÜÜÜÿÇÇÇÿ®®®ÿ¦¦¦ÿ¤¤¤ÿ£££ÿœœœÿfffÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿKKKÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿKKKÿ888ÿ888ÿ888ÿ888ÿ888ÿnnnÿáááÿáááÿàààÿÞÞÞÿÛÛÛÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÎÎÎÿ»»»ÿ®®®ÿ¬¬¬ÿ¤¤¤ÿKKKÿ888ÿ888ÿ888ÿ888ÿ<<<ÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿJJJÿ+++ÿ"""ÿ"""ÿ"""ÿ"""Û""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""Ï"""ÿ"""ÿ"""ÿ)))ÿJJJÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿ³³³ÿâââÿâââÿáááÿÝÝÝÿËËËÿ³³³ÿ¨¨¨ÿ£££ÿ¤¤¤ÿ¡¡¡ÿƒƒƒÿGGGÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿGGGÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿBBBÿ888ÿ888ÿ888ÿ888ÿ999ÿ···ÿáááÿáááÿßßßÿÝÝÝÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿ×××ÿÆÆÆÿ´´´ÿªªªÿ«««ÿ†††ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿ>>>ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""‡"""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""x"""ÿ"""ÿ"""ÿ"""ÿ;;;ÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿIIIÿËËËÿâââÿâââÿáááÿÝÝÝÿÑÑÑÿ¹¹¹ÿ¬¬¬ÿ¦¦¦ÿ¤¤¤ÿ£££ÿ———ÿ[[[ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ;;;ÿ888ÿ888ÿ888ÿ888ÿeeeÿáááÿáááÿáááÿÞÞÞÿÜÜÜÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÏÏÏÿ½½½ÿ¯¯¯ÿ®®®ÿ«««ÿVVVÿ888ÿ888ÿ888ÿ888ÿ;;;ÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿMMMÿKKKÿ555ÿ"""ÿ"""ÿ"""ÿ"""ù"""$""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""ó"""ÿ"""ÿ"""ÿ333ÿKKKÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ___ÿÜÜÜÿâââÿáááÿàààÿÝÝÝÿÓÓÓÿÀÀÀÿ²²²ÿ¨¨¨ÿ£££ÿ¤¤¤ÿ¡¡¡ÿuuuÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿBBBÿ888ÿ888ÿ888ÿ888ÿ999ÿ²²²ÿáááÿáááÿßßßÿÜÜÜÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿ×××ÿÈÈÈÿµµµÿªªªÿ¨¨¨ÿÿ999ÿ888ÿ888ÿ888ÿ888ÿAAAÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿJJJÿ)))ÿ"""ÿ"""ÿ"""ÿ"""´""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""¥"""ÿ"""ÿ"""ÿ&&&ÿHHHÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ}}}ÿâââÿâââÿáááÿßßßÿÝÝÝÿ×××ÿÈÈÈÿ¸¸¸ÿªªªÿ¥¥¥ÿ¥¥¥ÿ¤¤¤ÿŽŽŽÿMMMÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿJJJÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ<<<ÿ888ÿ888ÿ888ÿ888ÿdddÿßßßÿàààÿàààÿÞÞÞÿÛÛÛÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÚÚÚÿÐÐÐÿ½½½ÿ®®®ÿªªªÿ¬¬¬ÿaaaÿ888ÿ888ÿ888ÿ888ÿ999ÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿ:::ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""K"""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""<"""ÿ"""ÿ"""ÿ"""ÿ888ÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿLLLÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿŸŸŸÿãããÿãããÿâââÿàààÿÝÝÝÿØØØÿÎÎÎÿ¿¿¿ÿ°°°ÿ§§§ÿ¤¤¤ÿ¤¤¤ÿžžžÿ```ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿHHHÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿBBBÿ888ÿ888ÿ888ÿ888ÿ999ÿ···ÿáááÿáááÿàààÿÝÝÝÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÙÙÙÿÌÌÌÿ¹¹¹ÿ­­­ÿ¬¬¬ÿ˜˜˜ÿ===ÿ888ÿ888ÿ888ÿ888ÿAAAÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿKKKÿ///ÿ"""ÿ"""ÿ"""ÿ"""Û"""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""Ã"""ÿ"""ÿ"""ÿ---ÿKKKÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿ½½½ÿãããÿâââÿáááÿßßßÿÜÜÜÿÛÛÛÿÔÔÔÿÆÆÆÿ¶¶¶ÿªªªÿ¤¤¤ÿ¥¥¥ÿ¡¡¡ÿqqqÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿGGGÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ===ÿ888ÿ888ÿ888ÿ888ÿlllÿáááÿáááÿáááÿßßßÿÜÜÜÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÖÖÖÿÃÃÃÿ±±±ÿ¬¬¬ÿ­­­ÿiiiÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿAAAÿ"""ÿ"""ÿ"""ÿ"""ÿ"""x""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""Z"""ÿ"""ÿ"""ÿ"""ÿ>>>ÿLLLÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿQQQÿÔÔÔÿâââÿâââÿáááÿÞÞÞÿÜÜÜÿÛÛÛÿ×××ÿËËËÿºººÿ¬¬¬ÿ§§§ÿ§§§ÿ¦¦¦ÿ………ÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿDDDÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿBBBÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÃÃÃÿáááÿáááÿàààÿÝÝÝÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÎÎÎÿ»»»ÿ®®®ÿ­­­ÿÿ@@@ÿ888ÿ888ÿ888ÿ888ÿAAAÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿMMMÿKKKÿ555ÿ"""ÿ"""ÿ"""ÿ"""í""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""á"""ÿ"""ÿ"""ÿ222ÿKKKÿMMMÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿpppÿãããÿâââÿâââÿàààÿÞÞÞÿÜÜÜÿÛÛÛÿØØØÿÐÐÐÿÀÀÀÿ°°°ÿ§§§ÿ¥¥¥ÿ¥¥¥ÿ‘‘‘ÿKKKÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ}}}ÿáááÿáááÿàààÿÞÞÞÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿØØØÿÅÅÅÿ³³³ÿ¬¬¬ÿ¬¬¬ÿoooÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿMMMÿLLLÿHHHÿ&&&ÿ"""ÿ"""ÿ"""ÿ""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""l"""ÿ"""ÿ"""ÿ"""ÿBBBÿKKKÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ———ÿâââÿâââÿâââÿàààÿÝÝÝÿÛÛÛÿÛÛÛÿÛÛÛÿÓÓÓÿÆÆÆÿ´´´ÿ©©©ÿ¥¥¥ÿ¥¥¥ÿ›››ÿWWWÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿAAAÿ888ÿ888ÿ888ÿ888ÿCCCÿÒÒÒÿáááÿáááÿßßßÿÝÝÝÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÛÛÛÿÏÏÏÿ»»»ÿ®®®ÿ­­­ÿŸŸŸÿAAAÿ888ÿ888ÿ888ÿ888ÿ@@@ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿJJJÿ777ÿ"""ÿ"""ÿ"""ÿ"""ù"""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""" """ä"""ÿ"""ÿ"""ÿ555ÿJJJÿLLLÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿJJJÿ:::ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ¼¼¼ÿãããÿãããÿâââÿàààÿÝÝÝÿÜÜÜÿÜÜÜÿÜÜÜÿÖÖÖÿÉÉÉÿ¹¹¹ÿ«««ÿ¦¦¦ÿ¦¦¦ÿ¢¢¢ÿaaaÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿKKKÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ999ÿ888ÿ888ÿ888ÿ888ÿ˜˜˜ÿâââÿâââÿáááÿÞÞÞÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÆÆÆÿ´´´ÿ­­­ÿ­­­ÿqqqÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿIIIÿ***ÿ"""ÿ"""ÿ"""ÿ"""“""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""r"""ÿ"""ÿ"""ÿ%%%ÿEEEÿKKKÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿGGGÿ888ÿ888ÿ888ÿ888ÿ888ÿSSSÿÖÖÖÿãããÿãããÿâââÿßßßÿÝÝÝÿÜÜÜÿÜÜÜÿÜÜÜÿØØØÿÌÌÌÿºººÿ­­­ÿ¦¦¦ÿ§§§ÿ¢¢¢ÿlllÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿJJJÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿ@@@ÿ888ÿ888ÿ888ÿ888ÿXXXÿßßßÿâââÿáááÿßßßÿÝÝÝÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÑÑÑÿ½½½ÿ°°°ÿ®®®ÿ¡¡¡ÿBBBÿ888ÿ888ÿ888ÿ888ÿ@@@ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿ888ÿ"""ÿ"""ÿ"""ÿ"""ù""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""" """ç"""ÿ"""ÿ"""ÿ555ÿJJJÿLLLÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿsssÿäääÿãããÿãããÿáááÿÞÞÞÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÎÎÎÿ¼¼¼ÿ­­­ÿ¨¨¨ÿ§§§ÿ¢¢¢ÿxxxÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿJJJÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿKKKÿ888ÿ888ÿ888ÿ888ÿ999ÿ···ÿâââÿâââÿàààÿÞÞÞÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÇÇÇÿµµµÿ­­­ÿ­­­ÿqqqÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿLLLÿKKKÿIIIÿ***ÿ"""ÿ"""ÿ"""ÿ"""œ"""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""r"""ÿ"""ÿ"""ÿ%%%ÿEEEÿJJJÿKKKÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿœœœÿãããÿãããÿãããÿàààÿÞÞÞÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÑÑÑÿÁÁÁÿ±±±ÿ©©©ÿ§§§ÿ§§§ÿ‚‚‚ÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿHHHÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿ===ÿ888ÿ888ÿ888ÿ888ÿ{{{ÿâââÿâââÿáááÿßßßÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÑÑÑÿ½½½ÿ°°°ÿ®®®ÿ   ÿAAAÿ888ÿ888ÿ888ÿ888ÿ@@@ÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿKKKÿJJJÿ888ÿ"""ÿ"""ÿ"""ÿ"""ü"""!""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""ç"""ÿ"""ÿ"""ÿ555ÿIIIÿKKKÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿJJJÿ:::ÿ888ÿ888ÿ888ÿ888ÿAAAÿÃÃÃÿãããÿãããÿâââÿßßßÿÝÝÝÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÓÓÓÿÅÅÅÿ³³³ÿªªªÿ§§§ÿ§§§ÿ‰‰‰ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿHHHÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿAAAÿ888ÿ888ÿ888ÿ888ÿGGGÿÕÕÕÿâââÿáááÿßßßÿÝÝÝÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÜÜÜÿÙÙÙÿÇÇÇÿµµµÿ­­­ÿ­­­ÿnnnÿ888ÿ888ÿ888ÿ888ÿ999ÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿKKKÿKKKÿHHHÿ***ÿ"""ÿ"""ÿ"""ÿ"""“""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""f"""ÿ"""ÿ"""ÿ%%%ÿDDDÿIIIÿJJJÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿZZZÿÝÝÝÿäääÿäääÿâââÿàààÿÞÞÞÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÖÖÖÿÇÇÇÿ¶¶¶ÿ«««ÿ§§§ÿ§§§ÿÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿHHHÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿ999ÿ888ÿ888ÿ888ÿ888ÿ§§§ÿãããÿãããÿáááÿßßßÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÑÑÑÿ¾¾¾ÿ±±±ÿ¯¯¯ÿžžžÿ???ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿIIIÿ888ÿ"""ÿ"""ÿ"""ÿ"""ó""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""Û"""ÿ"""ÿ"""ÿ444ÿHHHÿJJJÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿƒƒƒÿäääÿäääÿäääÿâââÿßßßÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿ×××ÿÉÉÉÿ¸¸¸ÿ«««ÿ§§§ÿ§§§ÿ’’’ÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿHHHÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿ???ÿ888ÿ888ÿ888ÿ888ÿnnnÿãããÿãããÿâââÿàààÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÚÚÚÿÇÇÇÿ¶¶¶ÿ¯¯¯ÿ¯¯¯ÿhhhÿ888ÿ888ÿ888ÿ888ÿ;;;ÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿJJJÿGGGÿ)))ÿ"""ÿ"""ÿ"""ÿ"""{"""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""N"""ÿ"""ÿ"""ÿ$$$ÿAAAÿHHHÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ¯¯¯ÿäääÿäääÿãããÿàààÿÞÞÞÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿØØØÿÊÊÊÿ¹¹¹ÿ¬¬¬ÿ§§§ÿ§§§ÿ•••ÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿGGGÿ888ÿ888ÿ888ÿ888ÿCCCÿÐÐÐÿãããÿâââÿàààÿÞÞÞÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÑÑÑÿ½½½ÿ°°°ÿ¯¯¯ÿ™™™ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿEEEÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿHHHÿ555ÿ"""ÿ"""ÿ"""ÿ"""ç"""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""½"""ÿ"""ÿ"""ÿ222ÿGGGÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿGGGÿ999ÿ888ÿ888ÿ888ÿ888ÿIIIÿÑÑÑÿäääÿäääÿâââÿàààÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÙÙÙÿËËËÿ¹¹¹ÿ¬¬¬ÿ§§§ÿ§§§ÿ–––ÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ£££ÿãããÿãããÿáááÿßßßÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÙÙÙÿÆÆÆÿµµµÿ¯¯¯ÿ°°°ÿ```ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿHHHÿDDDÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""c""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""*"""ÿ"""ÿ"""ÿ"""ÿ===ÿGGGÿHHHÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿtttÿäääÿäääÿãããÿáááÿßßßÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÙÙÙÿÌÌÌÿºººÿ¬¬¬ÿ¨¨¨ÿ¨¨¨ÿ”””ÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿ???ÿ888ÿ888ÿ888ÿ888ÿlllÿãããÿãããÿâââÿàààÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÝÝÝÿÏÏÏÿ½½½ÿ°°°ÿ²²²ÿ’’’ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿFFFÿ333ÿ"""ÿ"""ÿ"""ÿ"""Ì""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""ÿ"""ÿ"""ÿ///ÿFFFÿHHHÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ999ÿ³³³ÿåååÿåååÿãããÿáááÿßßßÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÛÛÛÿÍÍÍÿ»»»ÿ­­­ÿ©©©ÿ©©©ÿ’’’ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿFFFÿ888ÿ888ÿ888ÿ888ÿEEEÿÒÒÒÿãããÿãããÿáááÿßßßÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÙÙÙÿÆÆÆÿ¶¶¶ÿ³³³ÿ°°°ÿSSSÿ888ÿ888ÿ888ÿ888ÿ>>>ÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿHHHÿGGGÿ@@@ÿ###ÿ"""ÿ"""ÿ"""ÿ"""3"""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""" """ð"""ÿ"""ÿ"""ÿ777ÿFFFÿGGGÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿGGGÿ:::ÿ888ÿ888ÿ888ÿ888ÿTTTÿÝÝÝÿäääÿäääÿãããÿàààÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÛÛÛÿÍÍÍÿ»»»ÿ­­­ÿ©©©ÿ©©©ÿ‰‰‰ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ¬¬¬ÿãããÿãããÿâââÿàààÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÎÎÎÿºººÿ¯¯¯ÿ°°°ÿ………ÿ888ÿ888ÿ888ÿ888ÿ888ÿGGGÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿGGGÿEEEÿ000ÿ"""ÿ"""ÿ"""ÿ"""œ"""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""`"""ÿ"""ÿ"""ÿ'''ÿDDDÿFFFÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿÿäääÿåååÿäääÿâââÿàààÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÚÚÚÿÍÍÍÿºººÿ­­­ÿ©©©ÿ©©©ÿ|||ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ{{{ÿäääÿäääÿãããÿàààÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÔÔÔÿÁÁÁÿ³³³ÿ²²²ÿªªªÿHHHÿ888ÿ888ÿ888ÿ888ÿ>>>ÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿFFFÿEEEÿ888ÿ"""ÿ"""ÿ"""ÿ"""ó""" """"""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""½"""ÿ"""ÿ"""ÿ222ÿDDDÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ<<<ÿ888ÿ888ÿ888ÿ888ÿAAAÿÉÉÉÿåååÿåååÿãããÿáááÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÚÚÚÿËËËÿ¹¹¹ÿ¬¬¬ÿªªªÿ¥¥¥ÿmmmÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿEEEÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿDDDÿ888ÿ888ÿ888ÿ888ÿQQQÿÜÜÜÿãããÿãããÿáááÿßßßÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÞÞÞÿÜÜÜÿÌÌÌÿ¹¹¹ÿ°°°ÿ±±±ÿtttÿ888ÿ888ÿ888ÿ888ÿ:::ÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿFFFÿFFFÿDDDÿ(((ÿ"""ÿ"""ÿ"""ÿ"""c""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""$"""ÿ"""ÿ"""ÿ"""ÿ===ÿDDDÿEEEÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿkkkÿäääÿåååÿåååÿãããÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÙÙÙÿËËËÿ¸¸¸ÿ­­­ÿ¬¬¬ÿ¨¨¨ÿbbbÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿ888ÿ888ÿ888ÿ888ÿ999ÿ¿¿¿ÿäääÿäääÿãããÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÔÔÔÿÁÁÁÿ³³³ÿ²²²ÿ   ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿBBBÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿEEEÿDDDÿ222ÿ"""ÿ"""ÿ"""ÿ"""É""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""~"""ÿ"""ÿ"""ÿ---ÿBBBÿDDDÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ«««ÿåååÿåååÿäääÿâââÿàààÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿØØØÿÇÇÇÿ´´´ÿ­­­ÿ¬¬¬ÿ§§§ÿVVVÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿ;;;ÿ888ÿ888ÿ888ÿ888ÿÿäääÿåååÿäääÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÜÜÜÿÊÊÊÿ¹¹¹ÿ²²²ÿ²²²ÿbbbÿ888ÿ888ÿ888ÿ888ÿ<<<ÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿDDDÿCCCÿ===ÿ###ÿ"""ÿ"""ÿ"""ÿ"""'"""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""Ø"""ÿ"""ÿ"""ÿ444ÿCCCÿDDDÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿ:::ÿ888ÿ888ÿ888ÿ888ÿRRRÿÜÜÜÿåååÿåååÿãããÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÖÖÖÿÆÆÆÿ²²²ÿ«««ÿªªªÿÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿ>>>ÿ888ÿ888ÿ888ÿ888ÿcccÿãããÿäääÿãããÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÒÒÒÿ¿¿¿ÿ³³³ÿ´´´ÿÿ888ÿ888ÿ888ÿ888ÿ888ÿDDDÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿDDDÿBBBÿ---ÿ"""ÿ"""ÿ"""ÿ""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""3"""ÿ"""ÿ"""ÿ$$$ÿ@@@ÿBBBÿCCCÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿÿåååÿåååÿäääÿâââÿàààÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÔÔÔÿÁÁÁÿ±±±ÿªªªÿªªªÿ’’’ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿ888ÿ888ÿ888ÿ888ÿCCCÿÒÒÒÿãããÿãããÿâââÿàààÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÚÚÚÿÇÇÇÿ···ÿ´´´ÿ®®®ÿLLLÿ888ÿ888ÿ888ÿ888ÿ===ÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿBBBÿ444ÿ"""ÿ"""ÿ"""ÿ"""Û"""""""""""""""""""""ÿÿÿ"""""""""""""""""""""‡"""ÿ"""ÿ"""ÿ...ÿ@@@ÿBBBÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿ<<<ÿ888ÿ888ÿ888ÿ888ÿAAAÿÍÍÍÿåååÿåååÿäääÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÜÜÜÿÑÑÑÿ¿¿¿ÿ¯¯¯ÿªªªÿªªªÿ€€€ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿ:::ÿ888ÿ888ÿ888ÿ888ÿ±±±ÿäääÿäääÿãããÿáááÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿßßßÿÞÞÞÿÎÎÎÿ»»»ÿ±±±ÿ²²²ÿwwwÿ888ÿ888ÿ888ÿ888ÿ:::ÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿBBBÿAAAÿ???ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""-""""""""""""""""""ÿÿÿ"""""""""""""""""""""Û"""ÿ"""ÿ"""ÿ555ÿ@@@ÿAAAÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿwwwÿæææÿæææÿæææÿäääÿáááÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÜÜÜÿÏÏÏÿ½½½ÿ°°°ÿ¬¬¬ÿªªªÿjjjÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ………ÿåååÿåååÿäääÿâââÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÕÕÕÿÂÂÂÿµµµÿ³³³ÿ¡¡¡ÿ===ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿAAAÿ???ÿ---ÿ"""ÿ"""ÿ"""ÿ"""‡""""""""""""""""""ÿÿÿ""""""""""""""""""-"""ÿ"""ÿ"""ÿ$$$ÿ>>>ÿ@@@ÿ@@@ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ¾¾¾ÿæææÿæææÿåååÿâââÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÜÜÜÿÍÍÍÿ¹¹¹ÿ°°°ÿ®®®ÿ©©©ÿTTTÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ???ÿ888ÿ888ÿ888ÿ888ÿ___ÿãããÿåååÿäääÿâââÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÜÜÜÿÌÌÌÿºººÿ³³³ÿ´´´ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ@@@ÿ???ÿ333ÿ"""ÿ"""ÿ"""ÿ"""Ò""""""""""""""""""ÿÿÿ""""""""""""""""""x"""ÿ"""ÿ"""ÿ,,,ÿ===ÿ???ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ999ÿ888ÿ888ÿ888ÿ888ÿeeeÿåååÿæææÿåååÿäääÿáááÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÚÚÚÿÇÇÇÿµµµÿ¬¬¬ÿ«««ÿšššÿAAAÿ888ÿ888ÿ888ÿ888ÿ999ÿ???ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ???ÿÏÏÏÿäääÿäääÿãããÿáááÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÓÓÓÿÀÀÀÿµµµÿµµµÿˆˆˆÿ888ÿ888ÿ888ÿ888ÿ999ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ???ÿ>>>ÿ===ÿ###ÿ"""ÿ"""ÿ"""ÿ""""""""""""""""""ÿÿÿ""""""""""""""""""Ã"""ÿ"""ÿ"""ÿ333ÿ>>>ÿ???ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ­­­ÿæææÿæææÿåååÿãããÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÕÕÕÿÂÂÂÿ±±±ÿ«««ÿ«««ÿ„„„ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ¬¬¬ÿåååÿåååÿäääÿáááÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿàààÿÚÚÚÿÇÇÇÿ¸¸¸ÿµµµÿ©©©ÿDDDÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ???ÿ===ÿ+++ÿ"""ÿ"""ÿ"""ÿ"""r"""""""""""""""ÿÿÿ""""""""""""""""""ü"""ÿ"""ÿ"""ÿ:::ÿ>>>ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ999ÿ888ÿ888ÿ888ÿ888ÿXXXÿâââÿçççÿæææÿåååÿãããÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÞÞÞÿÓÓÓÿ¿¿¿ÿ°°°ÿ®®®ÿ°°°ÿhhhÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ†††ÿæææÿæææÿåååÿãããÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿàààÿÏÏÏÿ½½½ÿ´´´ÿµµµÿhhhÿ888ÿ888ÿ888ÿ888ÿ:::ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ===ÿ111ÿ"""ÿ"""ÿ"""ÿ"""Ã"""""""""""""""ÿÿÿ"""""""""""""""Z"""ÿ"""ÿ"""ÿ'''ÿ;;;ÿ===ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ¡¡¡ÿçççÿçççÿæææÿäääÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÝÝÝÿÎÎÎÿ»»»ÿ°°°ÿ°°°ÿ¦¦¦ÿJJJÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿcccÿäääÿåååÿåååÿãããÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÖÖÖÿÂÂÂÿ¶¶¶ÿ···ÿ’’’ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ===ÿ999ÿ"""ÿ"""ÿ"""ÿ"""ö""" """"""""""""ÿÿÿ"""""""""""""""¥"""ÿ"""ÿ"""ÿ...ÿ;;;ÿ<<<ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ999ÿ888ÿ888ÿ888ÿ888ÿOOOÿÝÝÝÿçççÿæææÿåååÿãããÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÛÛÛÿÈÈÈÿ¶¶¶ÿ®®®ÿ¬¬¬ÿÿ:::ÿ888ÿ888ÿ888ÿ888ÿ:::ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ888ÿ888ÿ888ÿ888ÿDDDÿ×××ÿåååÿåååÿãããÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÝÝÝÿÊÊÊÿ¹¹¹ÿ¶¶¶ÿ¯¯¯ÿHHHÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ<<<ÿ:::ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""K""""""""""""ÿÿÿ"""""""""""""""á"""ÿ"""ÿ"""ÿ444ÿ:::ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ™™™ÿçççÿçççÿæææÿäääÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿÖÖÖÿÂÂÂÿ²²²ÿ®®®ÿ°°°ÿlllÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ999ÿ888ÿ888ÿ888ÿ888ÿ¸¸¸ÿåååÿåååÿäääÿâââÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿáááÿàààÿÐÐÐÿ¾¾¾ÿ³³³ÿµµµÿoooÿ888ÿ888ÿ888ÿ888ÿ999ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ;;;ÿ:::ÿ---ÿ"""ÿ"""ÿ"""ÿ"""""""""""""""ÿÿÿ""""""""""""$"""ÿ"""ÿ"""ÿ$$$ÿ888ÿ:::ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ888ÿ888ÿ888ÿ888ÿKKKÿÜÜÜÿçççÿçççÿæææÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿßßßÿÑÑÑÿ¾¾¾ÿ²²²ÿ±±±ÿ¨¨¨ÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ888ÿ888ÿ888ÿ888ÿ’’’ÿæææÿæææÿåååÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÖÖÖÿÄÄÄÿ···ÿ¸¸¸ÿ–––ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ222ÿ"""ÿ"""ÿ"""ÿ"""Ì""""""""""""ÿÿÿ""""""""""""`"""ÿ"""ÿ"""ÿ)))ÿ888ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ”””ÿçççÿèèèÿçççÿåååÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÝÝÝÿËËËÿ···ÿ¯¯¯ÿ¯¯¯ÿ‰‰‰ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ888ÿ888ÿ888ÿ888ÿoooÿçççÿçççÿæææÿäääÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÞÞÞÿÌÌÌÿ»»»ÿ···ÿ²²²ÿIIIÿ888ÿ888ÿ888ÿ888ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ777ÿ"""ÿ"""ÿ"""ÿ"""ü""" """""""""ÿÿÿ""""""""""""œ"""ÿ"""ÿ"""ÿ...ÿ777ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿJJJÿÛÛÛÿçççÿçççÿæææÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÖÖÖÿÃÃÃÿ³³³ÿ¯¯¯ÿ®®®ÿ___ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿOOOÿáááÿæææÿæææÿäääÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿáááÿÒÒÒÿ¿¿¿ÿµµµÿ¶¶¶ÿnnnÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ777ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""B"""""""""ÿÿÿ""""""""""""Õ"""ÿ"""ÿ"""ÿ111ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ•••ÿçççÿèèèÿçççÿåååÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿßßßÿÐÐÐÿ¼¼¼ÿ±±±ÿ±±±ÿÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÉÉÉÿæææÿæææÿåååÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÖÖÖÿÄÄÄÿ···ÿ¸¸¸ÿ“““ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ666ÿ+++ÿ"""ÿ"""ÿ"""ÿ"""~"""""""""ÿÿÿ""""""""""""ÿ"""ÿ"""ÿ###ÿ444ÿ666ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿKKKÿÜÜÜÿçççÿçççÿæææÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÜÜÜÿÉÉÉÿ···ÿ°°°ÿ±±±ÿsssÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ¨¨¨ÿæææÿæææÿåååÿãããÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿâââÿÞÞÞÿÌÌÌÿ»»»ÿ···ÿ°°°ÿDDDÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ555ÿ...ÿ"""ÿ"""ÿ"""ÿ"""º"""""""""ÿÿÿ"""""""""N"""ÿ"""ÿ"""ÿ'''ÿ444ÿ555ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ888ÿ888ÿ888ÿ888ÿ888ÿ™™™ÿèèèÿèèèÿçççÿåååÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÖÖÖÿÂÂÂÿµµµÿ²²²ÿ¨¨¨ÿGGGÿ888ÿ888ÿ888ÿ888ÿ777ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ777ÿ888ÿ888ÿ888ÿ888ÿˆˆˆÿçççÿçççÿæææÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿâââÿÒÒÒÿ¿¿¿ÿ···ÿ···ÿeeeÿ888ÿ888ÿ888ÿ888ÿ777ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ666ÿ555ÿ222ÿ"""ÿ"""ÿ"""ÿ"""ð"""""""""ÿÿÿ""""""""""""ÿ"""ÿ"""ÿ***ÿ333ÿ444ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ777ÿ888ÿ888ÿ888ÿ888ÿQQQÿàààÿèèèÿèèèÿæææÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿßßßÿÍÍÍÿºººÿ±±±ÿ²²²ÿÿ888ÿ888ÿ888ÿ888ÿ888ÿ666ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ666ÿ888ÿ888ÿ888ÿ888ÿeeeÿçççÿçççÿçççÿåååÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ×××ÿÅÅÅÿ¸¸¸ÿ¹¹¹ÿˆˆˆÿ888ÿ888ÿ888ÿ888ÿ777ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ444ÿ333ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""*""""""ÿÿÿ"""""""""®"""ÿ"""ÿ"""ÿ+++ÿ111ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ888ÿ888ÿ888ÿ888ÿ888ÿ¦¦¦ÿèèèÿèèèÿçççÿåååÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿ×××ÿÅÅÅÿ¶¶¶ÿ´´´ÿ­­­ÿNNNÿ888ÿ888ÿ888ÿ888ÿ777ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ888ÿ888ÿ888ÿ888ÿHHHÿÝÝÝÿçççÿçççÿåååÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿÝÝÝÿËËËÿºººÿ···ÿ©©©ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ222ÿ111ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""W""""""ÿÿÿ"""""""""Û"""ÿ"""ÿ"""ÿ---ÿ111ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ666ÿ888ÿ888ÿ888ÿ888ÿ[[[ÿäääÿèèèÿèèèÿæææÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿàààÿÏÏÏÿ¼¼¼ÿ²²²ÿ²²²ÿŠŠŠÿ888ÿ888ÿ888ÿ888ÿ888ÿ333ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÅÅÅÿçççÿçççÿåååÿäääÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿãããÿáááÿÑÑÑÿ¿¿¿ÿ···ÿ¹¹¹ÿZZZÿ888ÿ888ÿ888ÿ888ÿ444ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ000ÿ(((ÿ"""ÿ"""ÿ"""ÿ"""„""""""ÿÿÿ"""""" """ü"""ÿ"""ÿ"""ÿ///ÿ000ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ222ÿ888ÿ888ÿ888ÿ888ÿ888ÿ´´´ÿéééÿéééÿèèèÿæææÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿÙÙÙÿÆÆÆÿ¸¸¸ÿ¶¶¶ÿ²²²ÿVVVÿ888ÿ888ÿ888ÿ888ÿ666ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ666ÿ888ÿ888ÿ888ÿ888ÿ¬¬¬ÿèèèÿèèèÿçççÿåååÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿ×××ÿÆÆÆÿ»»»ÿ»»»ÿzzzÿ888ÿ888ÿ888ÿ888ÿ666ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ000ÿ***ÿ"""ÿ"""ÿ"""ÿ"""±""""""ÿÿÿ""""""9"""ÿ"""ÿ"""ÿ$$$ÿ...ÿ///ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ666ÿ888ÿ888ÿ888ÿ888ÿkkkÿéééÿéééÿèèèÿçççÿåååÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿáááÿÑÑÑÿ¾¾¾ÿ³³³ÿ´´´ÿÿ888ÿ888ÿ888ÿ888ÿ888ÿ111ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ444ÿ888ÿ888ÿ888ÿ888ÿ’’’ÿèèèÿèèèÿçççÿåååÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿÝÝÝÿÊÊÊÿ»»»ÿ¼¼¼ÿÿ888ÿ888ÿ888ÿ888ÿ777ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ000ÿ///ÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""Þ""""""ÿÿÿ""""""f"""ÿ"""ÿ"""ÿ&&&ÿ---ÿ...ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ111ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÅÅÅÿéééÿéééÿèèèÿåååÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿÙÙÙÿÆÆÆÿ¸¸¸ÿ¶¶¶ÿ³³³ÿOOOÿ888ÿ888ÿ888ÿ888ÿ555ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ222ÿ888ÿ888ÿ888ÿ888ÿxxxÿèèèÿèèèÿçççÿåååÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿàààÿÏÏÏÿ¾¾¾ÿ»»»ÿ³³³ÿHHHÿ888ÿ888ÿ888ÿ888ÿ000ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ...ÿ---ÿ"""ÿ"""ÿ"""ÿ"""ÿ""""""ÿÿÿ""""""“"""ÿ"""ÿ"""ÿ'''ÿ---ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ666ÿ888ÿ888ÿ888ÿ888ÿ}}}ÿéééÿéééÿèèèÿæææÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿâââÿÒÒÒÿ¾¾¾ÿ´´´ÿµµµÿÿ888ÿ888ÿ888ÿ888ÿ888ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ000ÿ888ÿ888ÿ888ÿ888ÿ^^^ÿèèèÿèèèÿçççÿæææÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿäääÿãããÿÔÔÔÿÀÀÀÿ¸¸¸ÿ¹¹¹ÿfffÿ888ÿ888ÿ888ÿ888ÿ333ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ---ÿ,,,ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""9"""ÿÿÿ""""""´"""ÿ"""ÿ"""ÿ(((ÿ,,,ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ333ÿ888ÿ888ÿ888ÿ888ÿBBBÿÔÔÔÿêêêÿéééÿèèèÿæææÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿÚÚÚÿÆÆÆÿ¹¹¹ÿ¶¶¶ÿ¬¬¬ÿEEEÿ888ÿ888ÿ888ÿ888ÿ333ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ...ÿ888ÿ888ÿ888ÿ888ÿIIIÿßßßÿéééÿèèèÿçççÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿÙÙÙÿÇÇÇÿ»»»ÿ¼¼¼ÿˆˆˆÿ888ÿ888ÿ888ÿ888ÿ666ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ---ÿ,,,ÿ+++ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""W"""ÿÿÿ""""""Õ"""ÿ"""ÿ"""ÿ(((ÿ+++ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ777ÿ888ÿ888ÿ888ÿ888ÿ”””ÿêêêÿêêêÿéééÿçççÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿâââÿÑÑÑÿ¾¾¾ÿµµµÿ¶¶¶ÿqqqÿ888ÿ888ÿ888ÿ888ÿ666ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿÊÊÊÿèèèÿèèèÿçççÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿßßßÿÎÎÎÿ¾¾¾ÿ¾¾¾ÿ©©©ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ,,,ÿ***ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""x"""ÿÿÿ""""""ù"""ÿ"""ÿ"""ÿ)))ÿ***ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ333ÿ888ÿ888ÿ888ÿ888ÿTTTÿäääÿéééÿéééÿèèèÿæææÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿÙÙÙÿÆÆÆÿ¸¸¸ÿ¶¶¶ÿ   ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ...ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ555ÿ888ÿ888ÿ888ÿ888ÿµµµÿèèèÿèèèÿçççÿæææÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿãããÿÒÒÒÿÁÁÁÿ¼¼¼ÿ¹¹¹ÿPPPÿ888ÿ888ÿ888ÿ888ÿ---ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ***ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""™"""ÿÿÿ""""""ÿ"""ÿ"""ÿ"""ÿ(((ÿ)))ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ+++ÿ888ÿ888ÿ888ÿ888ÿ888ÿ´´´ÿéééÿéééÿèèèÿæææÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿáááÿÏÏÏÿ½½½ÿ¶¶¶ÿ···ÿ]]]ÿ888ÿ888ÿ888ÿ888ÿ333ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ222ÿ888ÿ888ÿ888ÿ888ÿžžžÿèèèÿèèèÿèèèÿæææÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿåååÿäääÿÕÕÕÿÁÁÁÿ¸¸¸ÿºººÿkkkÿ888ÿ888ÿ888ÿ888ÿ222ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ)))ÿ&&&ÿ"""ÿ"""ÿ"""ÿ"""º"""ÿÿÿ"""6"""ÿ"""ÿ"""ÿ"""ÿ&&&ÿ'''ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ333ÿ888ÿ888ÿ888ÿ888ÿvvvÿêêêÿêêêÿêêêÿèèèÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿØØØÿÅÅÅÿ¹¹¹ÿ¹¹¹ÿŽŽŽÿ888ÿ888ÿ888ÿ888ÿ888ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ///ÿ888ÿ888ÿ888ÿ888ÿ„„„ÿéééÿéééÿéééÿçççÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿÚÚÚÿÈÈÈÿ½½½ÿ½½½ÿŽŽŽÿ888ÿ888ÿ888ÿ888ÿ555ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ(((ÿ'''ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""Û"""ÿÿÿ"""T"""ÿ"""ÿ"""ÿ###ÿ%%%ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ+++ÿ888ÿ888ÿ888ÿ888ÿEEEÿÙÙÙÿêêêÿêêêÿèèèÿçççÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿàààÿÍÍÍÿ½½½ÿºººÿ±±±ÿIIIÿ888ÿ888ÿ888ÿ888ÿ000ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ,,,ÿ888ÿ888ÿ888ÿ888ÿiiiÿêêêÿêêêÿéééÿèèèÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿáááÿÏÏÏÿ¿¿¿ÿ¿¿¿ÿ¬¬¬ÿ===ÿ888ÿ888ÿ888ÿ888ÿ(((ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ'''ÿ&&&ÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ù"""ÿÿÿ"""i"""ÿ"""ÿ"""ÿ"""ÿ%%%ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ666ÿ888ÿ888ÿ888ÿ888ÿ¨¨¨ÿêêêÿêêêÿéééÿçççÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿåååÿÕÕÕÿÁÁÁÿ···ÿ¸¸¸ÿqqqÿ888ÿ888ÿ888ÿ888ÿ444ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ)))ÿ888ÿ888ÿ888ÿ888ÿPPPÿäääÿêêêÿéééÿèèèÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿäääÿÔÔÔÿÂÂÂÿ½½½ÿ»»»ÿRRRÿ888ÿ888ÿ888ÿ888ÿ***ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ%%%ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿÿÿ""""""ÿ"""ÿ"""ÿ"""ÿ$$$ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ000ÿ888ÿ888ÿ888ÿ888ÿrrrÿêêêÿêêêÿêêêÿèèèÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿÚÚÚÿÇÇÇÿºººÿ¼¼¼ÿŸŸŸÿ999ÿ888ÿ888ÿ888ÿ888ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ&&&ÿ888ÿ888ÿ888ÿ888ÿBBBÿÐÐÐÿéééÿéééÿèèèÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿæææÿ×××ÿÂÂÂÿ¹¹¹ÿ»»»ÿnnnÿ888ÿ888ÿ888ÿ888ÿ000ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ$$$ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""$ÿÿÿ"""™"""ÿ"""ÿ"""ÿ!!!ÿ###ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ'''ÿ888ÿ888ÿ888ÿ888ÿEEEÿÜÜÜÿëëëÿëëëÿéééÿèèèÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿãããÿÐÐÐÿÀÀÀÿ¼¼¼ÿ¹¹¹ÿMMMÿ888ÿ888ÿ888ÿ888ÿ///ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ444ÿ888ÿ888ÿ888ÿ888ÿ½½½ÿêêêÿêêêÿéééÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿÛÛÛÿÊÊÊÿ½½½ÿ½½½ÿ‘‘‘ÿ888ÿ888ÿ888ÿ888ÿ444ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""6ÿÿÿ"""«"""ÿ"""ÿ"""ÿ!!!ÿ"""ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ555ÿ888ÿ888ÿ888ÿ888ÿ¬¬¬ÿëëëÿëëëÿêêêÿèèèÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿæææÿ×××ÿÃÃÃÿ¸¸¸ÿºººÿrrrÿ888ÿ888ÿ888ÿ888ÿ333ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ111ÿ888ÿ888ÿ888ÿ888ÿ­­­ÿêêêÿêêêÿéééÿèèèÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿâââÿÐÐÐÿÀÀÀÿÁÁÁÿ®®®ÿ???ÿ888ÿ888ÿ888ÿ888ÿ$$$ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ"""ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""Kÿÿÿ"""º"""ÿ"""ÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ)))ÿ888ÿ888ÿ888ÿ888ÿvvvÿëëëÿëëëÿêêêÿéééÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿÛÛÛÿÈÈÈÿ¼¼¼ÿ¼¼¼ÿ›››ÿ999ÿ888ÿ888ÿ888ÿ888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ'''ÿ888ÿ888ÿ888ÿ888ÿšššÿêêêÿêêêÿéééÿèèèÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿåååÿÕÕÕÿÃÃÃÿ½½½ÿ¾¾¾ÿTTTÿ888ÿ888ÿ888ÿ888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿ"""ÿ"""ÿ"""`ÿÿÿ"""Ì"""ÿ"""ÿ"""ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿ888ÿ888ÿ888ÿ888ÿGGGÿßßßÿëëëÿëëëÿéééÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿãããÿÐÐÐÿ¿¿¿ÿ¼¼¼ÿ···ÿJJJÿ888ÿ888ÿ888ÿ888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888ÿ888ÿ888ÿ888ÿ†††ÿêêêÿêêêÿéééÿèèèÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿçççÿØØØÿÃÃÃÿºººÿ»»»ÿqqqÿ888ÿ888ÿ888ÿ888ÿ###ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿ"""ÿ"""ÿ"""rÿÿÿ"""Ø"""ÿ"""ÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ111ÿ888ÿ888ÿ888ÿ888ÿµµµÿìììÿìììÿëëëÿéééÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿçççÿ×××ÿÄÄÄÿºººÿ»»»ÿmmmÿ888ÿ888ÿ888ÿ888ÿ###ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888ÿ888ÿ888ÿ888ÿsssÿëëëÿëëëÿêêêÿéééÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿÝÝÝÿËËËÿ¾¾¾ÿ¿¿¿ÿ”””ÿ888ÿ888ÿ888ÿ888ÿ...ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿ"""ÿ"""ÿ"""~ÿÿÿ"""á"""ÿ"""ÿ"""ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ&&&ÿ888ÿ888ÿ888ÿ888ÿ„„„ÿìììÿìììÿëëëÿéééÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿÜÜÜÿÉÉÉÿ»»»ÿ¼¼¼ÿ•••ÿ888ÿ888ÿ888ÿ888ÿ333ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ888ÿ888ÿ888ÿ888ÿ___ÿëëëÿëëëÿëëëÿéééÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿãããÿÑÑÑÿÁÁÁÿ¾¾¾ÿ®®®ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿÿ"""ÿ"""ÿ"""ÿ"""‡ÿÿÿ"""í"""ÿ"""ÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!!!ÿ888ÿ888ÿ888ÿ888ÿSSSÿèèèÿìììÿëëëÿêêêÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿãããÿÑÑÑÿ¿¿¿ÿ¼¼¼ÿ´´´ÿEEEÿ888ÿ888ÿ888ÿ888ÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888ÿ888ÿ888ÿ888ÿSSSÿåååÿëëëÿëëëÿêêêÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿçççÿÖÖÖÿÃÃÃÿ¼¼¼ÿ¾¾¾ÿVVVÿ888ÿ888ÿ888ÿ888ÿ!!!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿ"""ÿ"""ÿ"""Šÿÿÿ"""ð"""ÿ"""ÿ"""ÿ...ÿ111ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ888ÿ888ÿ888ÿ888ÿ:::ÿÆÆÆÿìììÿìììÿêêêÿéééÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿçççÿ×××ÿÄÄÄÿ»»»ÿ»»»ÿiiiÿ888ÿ888ÿ888ÿ888ÿ666ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ333ÿ888ÿ888ÿ888ÿ888ÿGGGÿ×××ÿëëëÿëëëÿêêêÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿèèèÿÛÛÛÿÈÈÈÿ¾¾¾ÿ¾¾¾ÿvvvÿ888ÿ888ÿ888ÿ888ÿ666ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ222ÿ000ÿ)))ÿ"""ÿ"""ÿ"""ÿ"""–ÿÿÿ"""ù"""ÿ"""ÿ"""ÿ444ÿ666ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ888ÿ˜˜˜ÿìììÿíííÿìììÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿÝÝÝÿÊÊÊÿ¼¼¼ÿ½½½ÿ‘‘‘ÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿÉÉÉÿìììÿìììÿëëëÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿáááÿÏÏÏÿÀÀÀÿÀÀÀÿ———ÿ888ÿ888ÿ888ÿ888ÿ888ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ777ÿ555ÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""–ÿÿÿ"""ÿ"""ÿ"""ÿ"""ÿ666ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿeeeÿíííÿíííÿìììÿëëëÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿäääÿÑÑÑÿÀÀÀÿ¼¼¼ÿ²²²ÿCCCÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿºººÿìììÿìììÿëëëÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿåååÿÓÓÓÿÂÂÂÿ¿¿¿ÿ°°°ÿAAAÿ888ÿ888ÿ888ÿ888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ777ÿ...ÿ"""ÿ"""ÿ"""ÿ"""¢ÿÿÿ"""ÿ"""ÿ"""ÿ"""ÿ777ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ888ÿ888ÿ888ÿ888ÿCCCÿÙÙÙÿìììÿìììÿëëëÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿèèèÿØØØÿÄÄÄÿ»»»ÿ¼¼¼ÿfffÿ888ÿ888ÿ888ÿ888ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ888ÿ888ÿ888ÿ888ÿ¥¥¥ÿìììÿìììÿëëëÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿèèèÿØØØÿÄÄÄÿ¼¼¼ÿ¿¿¿ÿYYYÿ888ÿ888ÿ888ÿ888ÿ999ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ888ÿ///ÿ"""ÿ"""ÿ"""ÿ"""¥ÿÿÿ"""ÿ"""ÿ"""ÿ"""ÿ888ÿ:::ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ888ÿ888ÿ888ÿ888ÿ±±±ÿìììÿìììÿìììÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿÝÝÝÿÊÊÊÿ½½½ÿ½½½ÿÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ:::ÿ888ÿ888ÿ888ÿ888ÿŽŽŽÿìììÿìììÿëëëÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿÜÜÜÿÊÊÊÿ¿¿¿ÿ¿¿¿ÿzzzÿ888ÿ888ÿ888ÿ888ÿ999ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ999ÿ000ÿ"""ÿ"""ÿ"""ÿ"""¥ÿÿÿ"""ÿ"""ÿ"""ÿ"""ÿ:::ÿ<<<ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ:::ÿ888ÿ888ÿ888ÿ888ÿƒƒƒÿíííÿíííÿìììÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿãããÿÐÐÐÿ¿¿¿ÿ¼¼¼ÿ¯¯¯ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿxxxÿìììÿìììÿëëëÿêêêÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿéééÿâââÿÏÏÏÿÀÀÀÿÀÀÀÿ˜˜˜ÿ888ÿ888ÿ888ÿ888ÿ999ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ===ÿ;;;ÿ000ÿ"""ÿ"""ÿ"""ÿ"""™ÿÿÿ"""ð"""ÿ"""ÿ"""ÿ:::ÿ===ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ===ÿ888ÿ888ÿ888ÿ888ÿWWWÿéééÿíííÿíííÿìììÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿéééÿØØØÿÄÄÄÿ¼¼¼ÿ½½½ÿbbbÿ888ÿ888ÿ888ÿ888ÿ;;;ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ===ÿ888ÿ888ÿ888ÿ888ÿcccÿíííÿíííÿíííÿëëëÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿæææÿÔÔÔÿÃÃÃÿÀÀÀÿ´´´ÿBBBÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ<<<ÿ000ÿ"""ÿ"""ÿ"""ÿ"""–ÿÿÿ"""ð"""ÿ"""ÿ"""ÿ:::ÿ>>>ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ888ÿ888ÿ888ÿ888ÿ===ÿÌÌÌÿíííÿíííÿìììÿëëëÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿÝÝÝÿËËËÿ¾¾¾ÿ¾¾¾ÿ‰‰‰ÿ888ÿ888ÿ888ÿ888ÿ999ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿTTTÿåååÿíííÿíííÿëëëÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿéééÿÙÙÙÿÅÅÅÿ½½½ÿ¿¿¿ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿ===ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ===ÿ000ÿ"""ÿ"""ÿ"""ÿ"""ÿÿÿ"""ä"""ÿ"""ÿ"""ÿ:::ÿ@@@ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ:::ÿ888ÿ888ÿ888ÿ888ÿ¥¥¥ÿíííÿíííÿìììÿëëëÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿäääÿÐÐÐÿÀÀÀÿ½½½ÿ­­­ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ@@@ÿ888ÿ888ÿ888ÿ888ÿFFFÿÖÖÖÿíííÿíííÿìììÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿÞÞÞÿËËËÿÀÀÀÿÀÀÀÿ}}}ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ@@@ÿ???ÿ000ÿ"""ÿ"""ÿ"""ÿ"""‡ÿÿÿ"""Û"""ÿ"""ÿ"""ÿ:::ÿ@@@ÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿ<<<ÿ888ÿ888ÿ888ÿ888ÿvvvÿíííÿîîîÿíííÿëëëÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿèèèÿ×××ÿÄÄÄÿ½½½ÿ½½½ÿ]]]ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿ888ÿ888ÿ888ÿ888ÿ999ÿÊÊÊÿíííÿíííÿìììÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿêêêÿãããÿÐÐÐÿÁÁÁÿÁÁÁÿœœœÿ888ÿ888ÿ888ÿ888ÿ:::ÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿBBBÿAAAÿ@@@ÿ000ÿ"""ÿ"""ÿ"""ÿ"""„ÿÿÿ"""Ï"""ÿ"""ÿ"""ÿ999ÿAAAÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿAAAÿ888ÿ888ÿ888ÿ888ÿRRRÿæææÿîîîÿîîîÿíííÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿÞÞÞÿËËËÿ¿¿¿ÿ¿¿¿ÿ………ÿ888ÿ888ÿ888ÿ888ÿ:::ÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ¸¸¸ÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿçççÿÖÖÖÿÃÃÃÿÁÁÁÿ¶¶¶ÿCCCÿ888ÿ888ÿ888ÿ888ÿBBBÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿBBBÿAAAÿ///ÿ"""ÿ"""ÿ"""ÿ"""uÿÿÿ"""½"""ÿ"""ÿ"""ÿ888ÿBBBÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÉÉÉÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿåååÿÑÑÑÿÂÂÂÿÂÂÂÿ¯¯¯ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿCCCÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿ===ÿ888ÿ888ÿ888ÿ888ÿ£££ÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿêêêÿÙÙÙÿÆÆÆÿ¾¾¾ÿÀÀÀÿ```ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿAAAÿ---ÿ"""ÿ"""ÿ"""ÿ"""cÿÿÿ"""¨"""ÿ"""ÿ"""ÿ666ÿDDDÿEEEÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿ;;;ÿ888ÿ888ÿ888ÿ888ÿŸŸŸÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿèèèÿ×××ÿÅÅÅÿÁÁÁÿÀÀÀÿZZZÿ888ÿ888ÿ888ÿ888ÿ>>>ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿ???ÿ888ÿ888ÿ888ÿ888ÿŽŽŽÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿßßßÿÌÌÌÿÁÁÁÿÁÁÁÿ€€€ÿ888ÿ888ÿ888ÿ888ÿ===ÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿEEEÿCCCÿ+++ÿ"""ÿ"""ÿ"""ÿ"""Qÿÿÿ"""“"""ÿ"""ÿ"""ÿ444ÿEEEÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ???ÿ888ÿ888ÿ888ÿ888ÿsssÿîîîÿîîîÿîîîÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿÚÚÚÿÆÆÆÿºººÿ¼¼¼ÿÿ888ÿ888ÿ888ÿ888ÿ;;;ÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿBBBÿ888ÿ888ÿ888ÿ888ÿxxxÿîîîÿîîîÿíííÿìììÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿëëëÿåååÿÑÑÑÿÂÂÂÿÂÂÂÿ   ÿ888ÿ888ÿ888ÿ888ÿ:::ÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿFFFÿDDDÿ***ÿ"""ÿ"""ÿ"""ÿ"""?ÿÿÿ""""""ÿ"""ÿ"""ÿ333ÿFFFÿGGGÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿFFFÿ888ÿ888ÿ888ÿ888ÿQQQÿæææÿïïïÿïïïÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿáááÿÍÍÍÿÁÁÁÿÁÁÁÿ«««ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿGGGÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿDDDÿ888ÿ888ÿ888ÿ888ÿfffÿïïïÿïïïÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿèèèÿØØØÿÄÄÄÿÁÁÁÿ¹¹¹ÿFFFÿ888ÿ888ÿ888ÿ888ÿFFFÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿHHHÿGGGÿEEEÿ'''ÿ"""ÿ"""ÿ"""ÿ"""*ÿÿÿ"""o"""ÿ"""ÿ"""ÿ111ÿGGGÿIIIÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿ888ÿ888ÿ888ÿ888ÿ999ÿÌÌÌÿïïïÿïïïÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿéééÿØØØÿÅÅÅÿÁÁÁÿ¿¿¿ÿVVVÿ888ÿ888ÿ888ÿ888ÿ@@@ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿGGGÿ888ÿ888ÿ888ÿ888ÿWWWÿèèèÿïïïÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿëëëÿÛÛÛÿÇÇÇÿ¿¿¿ÿÁÁÁÿcccÿ888ÿ888ÿ888ÿ888ÿDDDÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿIIIÿGGGÿ%%%ÿ"""ÿ"""ÿ"""ÿ"""ÿÿÿ"""Z"""ÿ"""ÿ"""ÿ...ÿHHHÿJJJÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ¢¢¢ÿïïïÿïïïÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿÛÛÛÿÇÇÇÿ¼¼¼ÿ¾¾¾ÿ|||ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿIIIÿ888ÿ888ÿ888ÿ888ÿHHHÿÜÜÜÿîîîÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿàààÿÍÍÍÿÁÁÁÿÁÁÁÿ‚‚‚ÿ888ÿ888ÿ888ÿ888ÿ???ÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿJJJÿGGGÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿÿÿ"""?"""ÿ"""ÿ"""ÿ+++ÿIIIÿKKKÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿAAAÿ888ÿ888ÿ888ÿ888ÿwwwÿïïïÿïïïÿïïïÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿáááÿÍÍÍÿÁÁÁÿÁÁÁÿ¦¦¦ÿ:::ÿ888ÿ888ÿ888ÿ888ÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿ888ÿ888ÿ888ÿ888ÿ:::ÿÑÑÑÿîîîÿîîîÿíííÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿìììÿæææÿÒÒÒÿÃÃÃÿÃÃÃÿ¤¤¤ÿ888ÿ888ÿ888ÿ888ÿ:::ÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿJJJÿDDDÿ"""ÿ"""ÿ"""ÿ"""ä"""ÿÿÿ"""!"""ÿ"""ÿ"""ÿ'''ÿJJJÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿJJJÿ888ÿ888ÿ888ÿ888ÿUUUÿéééÿðððÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿéééÿØØØÿÅÅÅÿÁÁÁÿ¿¿¿ÿRRRÿ888ÿ888ÿ888ÿ888ÿBBBÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ===ÿ888ÿ888ÿ888ÿ888ÿ¿¿¿ÿïïïÿïïïÿïïïÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿéééÿÙÙÙÿÆÆÆÿÂÂÂÿºººÿIIIÿ888ÿ888ÿ888ÿ888ÿKKKÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿLLLÿ@@@ÿ"""ÿ"""ÿ"""ÿ"""Ã"""ÿÿÿ""""""ÿ"""ÿ"""ÿ###ÿKKKÿMMMÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿ888ÿ888ÿ888ÿ888ÿ;;;ÿÒÒÒÿðððÿðððÿïïïÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿÜÜÜÿÈÈÈÿ½½½ÿ¿¿¿ÿwwwÿ888ÿ888ÿ888ÿ888ÿ===ÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿAAAÿ888ÿ888ÿ888ÿ888ÿ©©©ÿïïïÿïïïÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿìììÿÜÜÜÿÈÈÈÿ¿¿¿ÿÁÁÁÿeeeÿ888ÿ888ÿ888ÿ888ÿCCCÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿOOOÿNNNÿMMMÿ;;;ÿ"""ÿ"""ÿ"""ÿ"""¢"""ÿÿÿ""""""ä"""ÿ"""ÿ"""ÿFFFÿNNNÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿ===ÿ888ÿ888ÿ888ÿ888ÿ«««ÿðððÿðððÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿáááÿÍÍÍÿÀÀÀÿÂÂÂÿ£££ÿ999ÿ888ÿ888ÿ888ÿ888ÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿDDDÿ888ÿ888ÿ888ÿ888ÿ’’’ÿïïïÿïïïÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿáááÿÎÎÎÿÂÂÂÿÂÂÂÿ………ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿPPPÿOOOÿMMMÿ777ÿ"""ÿ"""ÿ"""ÿ"""„"""ÿÿÿ""""""À"""ÿ"""ÿ"""ÿAAAÿOOOÿPPPÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿCCCÿ888ÿ888ÿ888ÿ888ÿ‚‚‚ÿðððÿðððÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿéééÿ×××ÿÅÅÅÿÁÁÁÿ¾¾¾ÿNNNÿ888ÿ888ÿ888ÿ888ÿDDDÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿHHHÿ888ÿ888ÿ888ÿ888ÿ{{{ÿïïïÿïïïÿïïïÿîîîÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿíííÿæææÿÔÔÔÿÄÄÄÿÄÄÄÿ©©©ÿ999ÿ888ÿ888ÿ888ÿ888ÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿQQQÿPPPÿNNNÿ111ÿ"""ÿ"""ÿ"""ÿ"""c"""ÿÿÿ""""""™"""ÿ"""ÿ"""ÿ<<<ÿPPPÿRRRÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿMMMÿ888ÿ888ÿ888ÿ888ÿ___ÿíííÿñññÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿíííÿÝÝÝÿÉÉÉÿ¾¾¾ÿ¿¿¿ÿsssÿ888ÿ888ÿ888ÿ888ÿ>>>ÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿLLLÿ888ÿ888ÿ888ÿ888ÿiiiÿðððÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿëëëÿÙÙÙÿÈÈÈÿÃÃÃÿ¾¾¾ÿMMMÿ888ÿ888ÿ888ÿ888ÿOOOÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿSSSÿRRRÿOOOÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""?"""ÿÿÿ""""""r"""ÿ"""ÿ"""ÿ555ÿQQQÿSSSÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿSSSÿ888ÿ888ÿ888ÿ888ÿ???ÿÛÛÛÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿâââÿÎÎÎÿÁÁÁÿÂÂÂÿ   ÿ999ÿ888ÿ888ÿ888ÿ888ÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿOOOÿ888ÿ888ÿ888ÿ888ÿYYYÿêêêÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿíííÿÝÝÝÿÉÉÉÿÀÀÀÿÂÂÂÿhhhÿ888ÿ888ÿ888ÿ888ÿEEEÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿTTTÿSSSÿPPPÿ&&&ÿ"""ÿ"""ÿ"""ÿ""""""ÿÿÿ""""""E"""ÿ"""ÿ"""ÿ...ÿQQQÿTTTÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ¸¸¸ÿðððÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿêêêÿ×××ÿÅÅÅÿÁÁÁÿ¼¼¼ÿKKKÿ888ÿ888ÿ888ÿ888ÿNNNÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿRRRÿ888ÿ888ÿ888ÿ888ÿHHHÿßßßÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿâââÿÏÏÏÿÃÃÃÿÃÃÃÿŠŠŠÿ888ÿ888ÿ888ÿ888ÿ>>>ÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿUUUÿSSSÿMMMÿ"""ÿ"""ÿ"""ÿ"""í""""""ÿÿÿ"""""""""ÿ"""ÿ"""ÿ&&&ÿRRRÿUUUÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿEEEÿ888ÿ888ÿ888ÿ888ÿ‘‘‘ÿðððÿñññÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿíííÿÜÜÜÿÉÉÉÿ¾¾¾ÿ¿¿¿ÿoooÿ888ÿ888ÿ888ÿ888ÿCCCÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿ888ÿ888ÿ888ÿ888ÿ999ÿÔÔÔÿðððÿðððÿïïïÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿîîîÿçççÿÖÖÖÿÆÆÆÿÆÆÆÿ­­­ÿ:::ÿ888ÿ888ÿ888ÿ888ÿUUUÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿVVVÿUUUÿTTTÿEEEÿ"""ÿ"""ÿ"""ÿ"""À""""""ÿÿÿ"""""""""ê"""ÿ"""ÿ"""ÿOOOÿVVVÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿOOOÿ888ÿ888ÿ888ÿ888ÿnnnÿñññÿòòòÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿãããÿÏÏÏÿÁÁÁÿÃÃÃÿ™™™ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿ???ÿ888ÿ888ÿ888ÿ888ÿÀÀÀÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿìììÿÛÛÛÿÉÉÉÿÄÄÄÿÁÁÁÿOOOÿ888ÿ888ÿ888ÿ888ÿSSSÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿXXXÿWWWÿUUUÿ===ÿ"""ÿ"""ÿ"""ÿ"""“""""""ÿÿÿ"""""""""½"""ÿ"""ÿ"""ÿFFFÿWWWÿXXXÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿVVVÿ888ÿ888ÿ888ÿ888ÿKKKÿèèèÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿêêêÿØØØÿÅÅÅÿÁÁÁÿ¹¹¹ÿGGGÿ888ÿ888ÿ888ÿ888ÿRRRÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿEEEÿ888ÿ888ÿ888ÿ888ÿªªªÿñññÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿîîîÿÞÞÞÿÊÊÊÿÁÁÁÿÂÂÂÿkkkÿ888ÿ888ÿ888ÿ888ÿGGGÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿXXXÿVVVÿ444ÿ"""ÿ"""ÿ"""ÿ"""c""""""ÿÿÿ""""""""""""ÿ"""ÿ"""ÿ===ÿWWWÿYYYÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿ888ÿ888ÿ888ÿ888ÿ999ÿËËËÿñññÿñññÿñññÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿîîîÿÝÝÝÿÊÊÊÿÀÀÀÿÁÁÁÿlllÿ888ÿ888ÿ888ÿ888ÿEEEÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿIIIÿ888ÿ888ÿ888ÿ888ÿ“““ÿñññÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿãããÿÐÐÐÿÄÄÄÿÄÄÄÿÿ888ÿ888ÿ888ÿ888ÿ???ÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿZZZÿYYYÿVVVÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""6""""""ÿÿÿ"""""""""`"""ÿ"""ÿ"""ÿ444ÿWWWÿZZZÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ¤¤¤ÿñññÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿãããÿÏÏÏÿÁÁÁÿÂÂÂÿ•••ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿNNNÿ888ÿ888ÿ888ÿ888ÿ~~~ÿñññÿñññÿñññÿðððÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿïïïÿèèèÿ×××ÿÇÇÇÿÇÇÇÿ¯¯¯ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿZZZÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿ[[[ÿYYYÿUUUÿ"""ÿ"""ÿ"""ÿ"""ü""" """"""ÿÿÿ"""""""""$"""ÿ"""ÿ"""ÿ)))ÿXXXÿZZZÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿYYYÿTTTÿPPPÿIIIÿ888ÿ888ÿ888ÿ888ÿÿòòòÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿêêêÿØØØÿÆÆÆÿÂÂÂÿ···ÿDDDÿ888ÿ888ÿ888ÿ888ÿVVVÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿRRRÿ888ÿ888ÿ888ÿ888ÿkkkÿòòòÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿîîîÿÜÜÜÿÉÉÉÿÅÅÅÿÃÃÃÿQQQÿ888ÿ888ÿ888ÿ888ÿVVVÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ\\\ÿ[[[ÿYYYÿKKKÿ"""ÿ"""ÿ"""ÿ"""Ì"""""""""ÿÿÿ""""""""""""ç"""ÿ"""ÿ"""ÿSSSÿ\\\ÿ]]]ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ[[[ÿTTTÿJJJÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿZZZÿðððÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿïïïÿÞÞÞÿÊÊÊÿÁÁÁÿÂÂÂÿhhhÿ888ÿ888ÿ888ÿ888ÿGGGÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿWWWÿ888ÿ888ÿ888ÿ888ÿYYYÿìììÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿïïïÿßßßÿÊÊÊÿÁÁÁÿÃÃÃÿnnnÿ888ÿ888ÿ888ÿ888ÿHHHÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ^^^ÿ]]]ÿ[[[ÿ@@@ÿ"""ÿ"""ÿ"""ÿ""""""""""""ÿÿÿ""""""""""""®"""ÿ"""ÿ"""ÿGGGÿ\\\ÿ^^^ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ^^^ÿZZZÿJJJÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿÛÛÛÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿäääÿÐÐÐÿÃÃÃÿÃÃÃÿ‘‘‘ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿFFFÿãããÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿäääÿÒÒÒÿÅÅÅÿÅÅÅÿ“““ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿ^^^ÿ[[[ÿ444ÿ"""ÿ"""ÿ"""ÿ"""W"""""""""ÿÿÿ""""""""""""r"""ÿ"""ÿ"""ÿ:::ÿ\\\ÿ___ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ[[[ÿJJJÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ³³³ÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿéééÿ×××ÿÅÅÅÿÂÂÂÿ´´´ÿAAAÿ888ÿ888ÿ888ÿ888ÿ[[[ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ888ÿ888ÿ888ÿ888ÿ999ÿÖÖÖÿòòòÿòòòÿñññÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿðððÿêêêÿØØØÿÈÈÈÿÈÈÈÿ±±±ÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ^^^ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ```ÿ^^^ÿ[[[ÿ'''ÿ"""ÿ"""ÿ"""ÿ""""""""""""ÿÿÿ""""""""""""6"""ÿ"""ÿ"""ÿ---ÿ\\\ÿ___ÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿ]]]ÿKKKÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿFFFÿRRRÿ©©©ÿòòòÿòòòÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿðððÿÞÞÞÿÊÊÊÿÂÂÂÿÃÃÃÿdddÿ888ÿ888ÿ888ÿ888ÿIIIÿ___ÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿAAAÿ888ÿ888ÿ888ÿ888ÿÀÀÀÿóóóÿóóóÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿïïïÿÝÝÝÿËËËÿÆÆÆÿÅÅÅÿSSSÿ888ÿ888ÿ888ÿ888ÿYYYÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿaaaÿ```ÿ^^^ÿQQQÿ"""ÿ"""ÿ"""ÿ"""Þ""""""""""""ÿÿÿ"""""""""""""""ö"""ÿ"""ÿ"""ÿUUUÿ```ÿaaaÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿYYYÿHHHÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿCCCÿUUUÿgggÿxxxÿÿ¡¡¡ÿ°°°ÿ¸¸¸ÿÁÁÁÿÙÙÙÿðððÿñññÿñññÿðððÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿäääÿÑÑÑÿÄÄÄÿÄÄÄÿŒŒŒÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿIIIÿWWWÿ]]]ÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿHHHÿ888ÿ888ÿ888ÿ888ÿªªªÿóóóÿóóóÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿáááÿÌÌÌÿÂÂÂÿÄÄÄÿrrrÿ888ÿ888ÿ888ÿ888ÿJJJÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿbbbÿaaaÿ___ÿAAAÿ"""ÿ"""ÿ"""ÿ"""¢""""""""""""ÿÿÿ"""""""""""""""´"""ÿ"""ÿ"""ÿDDDÿ```ÿbbbÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿbbbÿVVVÿFFFÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿDDDÿ[[[ÿrrrÿÿ©©©ÿºººÿÃÃÃÿÅÅÅÿÀÀÀÿÀÀÀÿÀÀÀÿ¿¿¿ÿ½½½ÿ½½½ÿËËËÿÞÞÞÿðððÿñññÿðððÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿêêêÿ×××ÿÆÆÆÿÃÃÃÿ²²²ÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿHHHÿTTTÿ[[[ÿbbbÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿNNNÿ888ÿ888ÿ888ÿ888ÿ”””ÿóóóÿóóóÿòòòÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿåååÿÓÓÓÿÆÆÆÿÆÆÆÿ–––ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿcccÿaaaÿ___ÿ///ÿ"""ÿ"""ÿ"""ÿ"""Z""""""""""""ÿÿÿ"""""""""""""""i"""ÿ"""ÿ"""ÿ777ÿaaaÿcccÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿdddÿMMMÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿKKKÿjjjÿ‡‡‡ÿ¥¥¥ÿ½½½ÿÄÄÄÿÁÁÁÿÂÂÂÿÁÁÁÿ¿¿¿ÿÃÃÃÿÅÅÅÿÃÃÃÿÀÀÀÿÀÀÀÿ¿¿¿ÿ½½½ÿ½½½ÿÄÄÄÿÕÕÕÿèèèÿïïïÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿïïïÿÝÝÝÿÊÊÊÿÃÃÃÿÃÃÃÿ^^^ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿLLLÿZZZÿaaaÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿTTTÿ888ÿ888ÿ888ÿ888ÿ€€€ÿóóóÿóóóÿòòòÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿëëëÿÙÙÙÿÈÈÈÿÈÈÈÿµµµÿ???ÿ888ÿ888ÿ888ÿ888ÿcccÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿeeeÿcccÿ^^^ÿ"""ÿ"""ÿ"""ÿ"""ü"""""""""""""""ÿÿÿ""""""""""""""""""ÿ"""ÿ"""ÿ%%%ÿaaaÿdddÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿeeeÿMMMÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿJJJÿkkkÿÿ«««ÿÂÂÂÿÆÆÆÿÃÃÃÿÂÂÂÿÄÄÄÿÄÄÄÿÃÃÃÿÂÂÂÿÁÁÁÿ¿¿¿ÿÃÃÃÿÈÈÈÿÌÌÌÿÐÐÐÿÓÓÓÿÕÕÕÿÖÖÖÿ×××ÿÙÙÙÿåååÿíííÿðððÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿäääÿÐÐÐÿÃÃÃÿÃÃÃÿŠŠŠÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿJJJÿWWWÿ___ÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿYYYÿ888ÿ888ÿ888ÿ888ÿmmmÿóóóÿóóóÿóóóÿòòòÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿñññÿïïïÿÞÞÞÿËËËÿÆÆÆÿÅÅÅÿTTTÿ888ÿ888ÿ888ÿ888ÿ\\\ÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿfffÿeeeÿcccÿMMMÿ"""ÿ"""ÿ"""ÿ"""Ã"""""""""""""""ÿÿÿ""""""""""""""""""Ò"""ÿ"""ÿ"""ÿQQQÿdddÿfffÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿYYYÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿDDDÿ```ÿ‡‡‡ÿ¥¥¥ÿÂÂÂÿÆÆÆÿÃÃÃÿÃÃÃÿÅÅÅÿÆÆÆÿÅÅÅÿÃÃÃÿÄÄÄÿÈÈÈÿÌÌÌÿÒÒÒÿÕÕÕÿ×××ÿÚÚÚÿàààÿæææÿéééÿíííÿïïïÿðððÿñññÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿëëëÿÖÖÖÿÄÄÄÿ¿¿¿ÿ¼¼¼ÿŸŸŸÿ………ÿiiiÿOOOÿ???ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿNNNÿ\\\ÿeeeÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿ___ÿ888ÿ888ÿ888ÿ888ÿXXXÿïïïÿôôôÿôôôÿóóóÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿâââÿÍÍÍÿÃÃÃÿÃÃÃÿvvvÿ888ÿ888ÿ888ÿ888ÿLLLÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿgggÿeeeÿcccÿ???ÿ"""ÿ"""ÿ"""ÿ"""‡"""""""""""""""ÿÿÿ""""""""""""""""""–"""ÿ"""ÿ"""ÿ@@@ÿdddÿgggÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿ___ÿIIIÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿpppÿ˜˜˜ÿ¸¸¸ÿÇÇÇÿÄÄÄÿÃÃÃÿÆÆÆÿÇÇÇÿÆÆÆÿÃÃÃÿÅÅÅÿËËËÿÑÑÑÿÕÕÕÿÚÚÚÿàààÿåååÿéééÿîîîÿñññÿñññÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿðððÿÝÝÝÿÇÇÇÿ¹¹¹ÿ¸¸¸ÿ¸¸¸ÿ···ÿ¶¶¶ÿ¹¹¹ÿ¨¨¨ÿÿtttÿXXXÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿLLLÿYYYÿcccÿgggÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿeeeÿ888ÿ888ÿ888ÿ888ÿDDDÿæææÿôôôÿôôôÿóóóÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿçççÿÔÔÔÿÆÆÆÿÆÆÆÿ˜˜˜ÿ888ÿ888ÿ888ÿ888ÿAAAÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿhhhÿfffÿcccÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""<"""""""""""""""ÿÿÿ""""""""""""""""""H"""ÿ"""ÿ"""ÿ,,,ÿdddÿgggÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿMMMÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿTTTÿ}}}ÿ¦¦¦ÿÂÂÂÿÈÈÈÿÄÄÄÿÆÆÆÿÇÇÇÿÆÆÆÿÃÃÃÿÆÆÆÿÌÌÌÿÒÒÒÿÖÖÖÿÚÚÚÿâââÿéééÿîîîÿñññÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿçççÿÒÒÒÿÂÂÂÿ»»»ÿ¸¸¸ÿ¸¸¸ÿµµµÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ¶¶¶ÿ¸¸¸ÿ­­­ÿ›››ÿÿcccÿOOOÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿOOOÿaaaÿgggÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿhhhÿ888ÿ888ÿ888ÿ888ÿ<<<ÿÕÕÕÿôôôÿôôôÿóóóÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿíííÿÚÚÚÿÉÉÉÿÆÆÆÿ´´´ÿ???ÿ888ÿ888ÿ888ÿ888ÿfffÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿiiiÿhhhÿfffÿQQQÿ"""ÿ"""ÿ"""ÿ"""í""""""""""""""""""ÿÿÿ"""""""""""""""""""""í"""ÿ"""ÿ"""ÿRRRÿgggÿiiiÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿcccÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿWWWÿ‚‚‚ÿ¬¬¬ÿÇÇÇÿÉÉÉÿÅÅÅÿÇÇÇÿÉÉÉÿÇÇÇÿÆÆÆÿÊÊÊÿÑÑÑÿÖÖÖÿÚÚÚÿâââÿéééÿîîîÿñññÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿïïïÿæææÿÝÝÝÿÕÕÕÿÏÏÏÿÈÈÈÿÁÁÁÿ»»»ÿ¸¸¸ÿ¸¸¸ÿ···ÿµµµÿ···ÿ¸¸¸ÿ···ÿ···ÿ···ÿ¥¥¥ÿÿnnnÿVVVÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿMMMÿ]]]ÿfffÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿCCCÿ888ÿ888ÿ888ÿ888ÿÀÀÀÿôôôÿôôôÿóóóÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿòòòÿñññÿàààÿÌÌÌÿÄÄÄÿÆÆÆÿWWWÿ888ÿ888ÿ888ÿ888ÿ___ÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿjjjÿiiiÿfffÿAAAÿ"""ÿ"""ÿ"""ÿ"""–""""""""""""""""""ÿÿÿ"""""""""""""""""""""™"""ÿ"""ÿ"""ÿAAAÿgggÿjjjÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿbbbÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿSSSÿ€€€ÿ­­­ÿÊÊÊÿËËËÿÇÇÇÿÉÉÉÿËËËÿÉÉÉÿÈÈÈÿÍÍÍÿÕÕÕÿÚÚÚÿáááÿéééÿïïïÿòòòÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿðððÿìììÿäääÿÝÝÝÿ×××ÿÒÒÒÿÍÍÍÿÆÆÆÿ¿¿¿ÿ»»»ÿ¹¹¹ÿ¸¸¸ÿ¶¶¶ÿ¹¹¹ÿ¹¹¹ÿ¸¸¸ÿ¸¸¸ÿ¹¹¹ÿ«««ÿ˜˜˜ÿzzzÿ```ÿKKKÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿLLLÿZZZÿcccÿiiiÿkkkÿkkkÿkkkÿkkkÿLLLÿ888ÿ888ÿ888ÿ888ÿ«««ÿõõõÿõõõÿôôôÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿæææÿÓÓÓÿÇÇÇÿÇÇÇÿzzzÿ888ÿ888ÿ888ÿ888ÿMMMÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿkkkÿiiiÿfffÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""B""""""""""""""""""ÿÿÿ"""""""""""""""""""""E"""ÿ"""ÿ"""ÿ,,,ÿgggÿjjjÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿeeeÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿJJJÿwwwÿ¦¦¦ÿÆÆÆÿÌÌÌÿÇÇÇÿÊÊÊÿÌÌÌÿÊÊÊÿÈÈÈÿÏÏÏÿ×××ÿÛÛÛÿäääÿëëëÿñññÿòòòÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿñññÿíííÿéééÿâââÿÛÛÛÿÕÕÕÿÏÏÏÿÈÈÈÿÀÀÀÿ¼¼¼ÿ¹¹¹ÿ¸¸¸ÿ¸¸¸ÿ···ÿ¹¹¹ÿ¹¹¹ÿ¸¸¸ÿ¹¹¹ÿ´´´ÿ¥¥¥ÿ‡‡‡ÿlllÿUUUÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿOOOÿ```ÿgggÿRRRÿ888ÿ888ÿ888ÿ888ÿ–––ÿõõõÿõõõÿôôôÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿêêêÿ×××ÿÈÈÈÿÈÈÈÿ›››ÿ888ÿ888ÿ888ÿ888ÿAAAÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿlllÿkkkÿiiiÿQQQÿ"""ÿ"""ÿ"""ÿ"""ê"""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""ê"""ÿ"""ÿ"""ÿSSSÿjjjÿlllÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿNNNÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿeeeÿ———ÿÂÂÂÿËËËÿÇÇÇÿÊÊÊÿÌÌÌÿÊÊÊÿÉÉÉÿÐÐÐÿ×××ÿÜÜÜÿåååÿíííÿòòòÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿòòòÿïïïÿëëëÿäääÿÜÜÜÿÖÖÖÿÑÑÑÿËËËÿÅÅÅÿ¿¿¿ÿ»»»ÿ¹¹¹ÿ¸¸¸ÿ¶¶¶ÿ¹¹¹ÿ¹¹¹ÿ¸¸¸ÿ¸¸¸ÿ¹¹¹ÿ®®®ÿ“““ÿwwwÿ___ÿFFFÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿÿõõõÿõõõÿôôôÿôôôÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿîîîÿÛÛÛÿÊÊÊÿÇÇÇÿ¸¸¸ÿ@@@ÿ888ÿ888ÿ888ÿ888ÿjjjÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿmmmÿkkkÿiiiÿAAAÿ"""ÿ"""ÿ"""ÿ"""–"""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""–"""ÿ"""ÿ"""ÿBBBÿjjjÿlllÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿfffÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿKKKÿÿ´´´ÿËËËÿÉÉÉÿÌÌÌÿÍÍÍÿÊÊÊÿÊÊÊÿÐÐÐÿ×××ÿÜÜÜÿåååÿîîîÿòòòÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿòòòÿðððÿìììÿèèèÿáááÿÙÙÙÿÔÔÔÿÎÎÎÿÇÇÇÿÀÀÀÿ¼¼¼ÿ¹¹¹ÿ¹¹¹ÿ¶¶¶ÿ¹¹¹ÿºººÿ¸¸¸ÿ¸¸¸ÿ¸¸¸ÿ´´´ÿ   ÿ‚‚‚ÿjjjÿNNNÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿlllÿõõõÿõõõÿôôôÿôôôÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿóóóÿòòòÿáááÿÍÍÍÿÅÅÅÿÇÇÇÿ\\\ÿ888ÿ888ÿ888ÿ888ÿaaaÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿnnnÿmmmÿkkkÿfffÿ+++ÿ"""ÿ"""ÿ"""ÿ"""<"""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""?"""ÿ"""ÿ"""ÿ(((ÿdddÿlllÿnnnÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿiiiÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿWWWÿ”””ÿÅÅÅÿÎÎÎÿÎÎÎÿÎÎÎÿÌÌÌÿÌÌÌÿÑÑÑÿ×××ÿÜÜÜÿæææÿîîîÿóóóÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿóóóÿðððÿêêêÿäääÿÜÜÜÿÖÖÖÿÑÑÑÿËËËÿÂÂÂÿ½½½ÿ»»»ÿ¹¹¹ÿºººÿ¸¸¸ÿ¹¹¹ÿºººÿ¹¹¹ÿ¹¹¹ÿ¹¹¹ÿ«««ÿŽŽŽÿuuuÿYYYÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿVVVÿòòòÿöööÿõõõÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿçççÿÓÓÓÿÈÈÈÿÈÈÈÿ}}}ÿ888ÿ888ÿ888ÿ888ÿNNNÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿoooÿnnnÿlllÿLLLÿ"""ÿ"""ÿ"""ÿ"""ä""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""Û"""ÿ"""ÿ"""ÿIIIÿlllÿoooÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿVVVÿœœœÿÍÍÍÿÐÐÐÿÐÐÐÿÐÐÐÿÐÐÐÿÐÐÐÿÕÕÕÿÜÜÜÿåååÿíííÿóóóÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿóóóÿñññÿíííÿæææÿÞÞÞÿÙÙÙÿÓÓÓÿÎÎÎÿÈÈÈÿÀÀÀÿ½½½ÿºººÿºººÿ···ÿºººÿºººÿ¹¹¹ÿ¹¹¹ÿ¹¹¹ÿ°°°ÿ™™™ÿ€€€ÿdddÿKKKÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿEEEÿèèèÿõõõÿõõõÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿìììÿØØØÿÉÉÉÿÉÉÉÿžžžÿ888ÿ888ÿ888ÿ888ÿAAAÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿpppÿnnnÿkkkÿ111ÿ"""ÿ"""ÿ"""ÿ"""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""u"""ÿ"""ÿ"""ÿ111ÿlllÿoooÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿhhhÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿÿÍÍÍÿÑÑÑÿÒÒÒÿÑÑÑÿÒÒÒÿÓÓÓÿÙÙÙÿâââÿëëëÿñññÿóóóÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿóóóÿïïïÿêêêÿäääÿÜÜÜÿÖÖÖÿÑÑÑÿÊÊÊÿÁÁÁÿ½½½ÿºººÿ¹¹¹ÿºººÿ···ÿ¹¹¹ÿºººÿ¹¹¹ÿ¹¹¹ÿ¹¹¹ÿ¦¦¦ÿÿpppÿUUUÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿÔÔÔÿõõõÿõõõÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿðððÿÝÝÝÿËËËÿÈÈÈÿ»»»ÿBBBÿ888ÿ888ÿ888ÿ888ÿmmmÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿpppÿnnnÿ\\\ÿ"""ÿ"""ÿ"""ÿ"""ü"""!""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""ù"""ÿ"""ÿ"""ÿWWWÿnnnÿpppÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿ[[[ÿ888ÿ888ÿ888ÿ888ÿWWWÿÀÀÀÿÛÛÛÿÜÜÜÿÞÞÞÿÝÝÝÿßßßÿßßßÿæææÿîîîÿóóóÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿóóóÿðððÿìììÿåååÿÞÞÞÿØØØÿÓÓÓÿÍÍÍÿÇÇÇÿ¿¿¿ÿ¼¼¼ÿºººÿºººÿ···ÿ¹¹¹ÿºººÿºººÿ¹¹¹ÿºººÿ¬¬¬ÿ———ÿ|||ÿ___ÿKKKÿ999ÿ888ÿ½½½ÿõõõÿõõõÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿóóóÿâââÿÎÎÎÿÆÆÆÿÈÈÈÿ^^^ÿ888ÿ888ÿ888ÿ888ÿbbbÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿqqqÿoooÿmmmÿEEEÿ"""ÿ"""ÿ"""ÿ"""º"""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""«"""ÿ"""ÿ"""ÿDDDÿnnnÿpppÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿMMMÿ888ÿ888ÿ888ÿ888ÿ¯¯¯ÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿòòòÿîîîÿéééÿãããÿÜÜÜÿÕÕÕÿÐÐÐÿÉÉÉÿÁÁÁÿ½½½ÿºººÿºººÿ¹¹¹ÿ···ÿ¹¹¹ÿºººÿ¹¹¹ÿ¹¹¹ÿ¶¶¶ÿ£££ÿ‘‘‘ÿÄÄÄÿôôôÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿèèèÿÓÓÓÿÈÈÈÿÈÈÈÿÿ888ÿ888ÿ888ÿ888ÿNNNÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿrrrÿqqqÿoooÿkkkÿ---ÿ"""ÿ"""ÿ"""ÿ"""T"""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""E"""ÿ"""ÿ"""ÿ'''ÿeeeÿpppÿrrrÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿ\\\ÿ888ÿ888ÿ888ÿ888ÿNNNÿ´´´ÿöööÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿôôôÿñññÿìììÿæææÿßßßÿØØØÿÒÒÒÿÍÍÍÿÆÆÆÿÀÀÀÿ»»»ÿºººÿ¹¹¹ÿ¸¸¸ÿºººÿºººÿ¿¿¿ÿÑÑÑÿäääÿõõõÿõõõÿôôôÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿíííÿÚÚÚÿÊÊÊÿÊÊÊÿ£££ÿ888ÿ888ÿ888ÿ888ÿAAAÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿsssÿrrrÿoooÿKKKÿ"""ÿ"""ÿ"""ÿ"""ê""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""Û"""ÿ"""ÿ"""ÿGGGÿpppÿrrrÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿkkkÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿfffÿºººÿóóóÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿòòòÿîîîÿéééÿãããÿÛÛÛÿÕÕÕÿÐÐÐÿÉÉÉÿÁÁÁÿ½½½ÿºººÿ¿¿¿ÿÉÉÉÿÕÕÕÿéééÿóóóÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿñññÿßßßÿÌÌÌÿÉÉÉÿ¾¾¾ÿDDDÿ888ÿ888ÿ888ÿ888ÿoooÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿtttÿsssÿrrrÿoooÿ111ÿ"""ÿ"""ÿ"""ÿ"""„""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""l"""ÿ"""ÿ"""ÿ---ÿnnnÿrrrÿtttÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿNNNÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ^^^ÿ¨¨¨ÿäääÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿôôôÿñññÿëëëÿåååÿÝÝÝÿ×××ÿÑÑÑÿÓÓÓÿÚÚÚÿäääÿîîîÿôôôÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿôôôÿãããÿÏÏÏÿÆÆÆÿÈÈÈÿbbbÿ888ÿ888ÿ888ÿ888ÿcccÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿuuuÿtttÿqqqÿRRRÿ"""ÿ"""ÿ"""ÿ"""ù"""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""" """ê"""ÿ"""ÿ"""ÿMMMÿrrrÿuuuÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿsssÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿÿ½½½ÿíííÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿôôôÿòòòÿîîîÿîîîÿòòòÿôôôÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿéééÿÔÔÔÿÉÉÉÿÉÉÉÿƒƒƒÿ888ÿ888ÿ888ÿ888ÿOOOÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿuuuÿtttÿqqqÿ===ÿ"""ÿ"""ÿ"""ÿ"""Ÿ"""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""„"""ÿ"""ÿ"""ÿ111ÿpppÿtttÿuuuÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿsssÿNNNÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿPPPÿ„„„ÿ½½½ÿìììÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿîîîÿÛÛÛÿËËËÿËËËÿ¨¨¨ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿvvvÿuuuÿsssÿZZZÿ"""ÿ"""ÿ"""ÿ"""ÿ"""*"""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""ö"""ÿ"""ÿ"""ÿQQQÿsssÿvvvÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿlllÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿNNNÿ€€€ÿºººÿèèèÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿòòòÿàààÿÍÍÍÿÊÊÊÿÀÀÀÿGGGÿ888ÿ888ÿ888ÿ888ÿpppÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿwwwÿvvvÿuuuÿrrrÿBBBÿ"""ÿ"""ÿ"""ÿ"""·""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""™"""ÿ"""ÿ"""ÿ222ÿrrrÿvvvÿwwwÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿjjjÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿLLLÿyyyÿ°°°ÿàààÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿõõõÿäääÿÏÏÏÿÇÇÇÿÉÉÉÿdddÿ888ÿ888ÿ888ÿ888ÿVVVÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿwwwÿuuuÿ\\\ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""B""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""!"""ù"""ÿ"""ÿ"""ÿPPPÿtttÿwwwÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿqqqÿQQQÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿiiiÿžžžÿÓÓÓÿóóóÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿéééÿÕÕÕÿÊÊÊÿÊÊÊÿ†††ÿ888ÿ888ÿ888ÿ888ÿGGGÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿxxxÿwwwÿvvvÿsssÿ@@@ÿ"""ÿ"""ÿ"""ÿ"""Æ"""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""œ"""ÿ"""ÿ"""ÿ222ÿsssÿwwwÿxxxÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿjjjÿNNNÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿ[[[ÿ†††ÿ¼¼¼ÿâââÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿïïïÿÛÛÛÿÌÌÌÿÌÌÌÿ­­­ÿ999ÿ888ÿ888ÿ888ÿ888ÿxxxÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿyyyÿxxxÿvvvÿ[[[ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""B"""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""ù"""ÿ"""ÿ"""ÿPPPÿvvvÿxxxÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿkkkÿPPPÿ>>>ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿIIIÿlllÿ¡¡¡ÿÎÎÎÿòòòÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿóóóÿâââÿÎÎÎÿÊÊÊÿÄÄÄÿKKKÿ888ÿ888ÿ888ÿ888ÿqqqÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿzzzÿyyyÿxxxÿuuuÿ???ÿ"""ÿ"""ÿ"""ÿ"""½""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""“"""ÿ"""ÿ"""ÿ000ÿuuuÿxxxÿzzzÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿmmmÿQQQÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ:::ÿSSSÿ~~~ÿ°°°ÿØØØÿøøøÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿöööÿåååÿÐÐÐÿÈÈÈÿÉÉÉÿfffÿ888ÿ888ÿ888ÿ888ÿWWWÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿ{{{ÿzzzÿwwwÿZZZÿ"""ÿ"""ÿ"""ÿ"""ÿ"""?""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""ö"""ÿ"""ÿ"""ÿMMMÿxxxÿzzzÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿqqqÿUUUÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿ[[[ÿ‹‹‹ÿ···ÿàààÿøøøÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿêêêÿÖÖÖÿÊÊÊÿÊÊÊÿŠŠŠÿ888ÿ888ÿ888ÿ888ÿGGGÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ|||ÿ{{{ÿzzzÿvvvÿ:::ÿ"""ÿ"""ÿ"""ÿ"""º"""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""‡"""ÿ"""ÿ"""ÿ'''ÿmmmÿzzzÿ|||ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿyyyÿXXXÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿBBBÿ___ÿŽŽŽÿ···ÿâââÿ÷÷÷ÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿðððÿÝÝÝÿÍÍÍÿÍÍÍÿ­­­ÿ999ÿ888ÿ888ÿ888ÿ888ÿ|||ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ}}}ÿ{{{ÿyyyÿRRRÿ"""ÿ"""ÿ"""ÿ"""ÿ"""6"""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""í"""ÿ"""ÿ"""ÿHHHÿyyyÿ|||ÿ}}}ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿoooÿSSSÿBBBÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿbbbÿŒŒŒÿ¶¶¶ÿàààÿõõõÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿôôôÿâââÿÏÏÏÿËËËÿÄÄÄÿJJJÿ888ÿ888ÿ888ÿ888ÿuuuÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ~~~ÿ}}}ÿ{{{ÿsssÿ,,,ÿ"""ÿ"""ÿ"""ÿ"""¥""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""i"""ÿ"""ÿ"""ÿ"""ÿ___ÿ|||ÿ~~~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿyyyÿYYYÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿ___ÿ‡‡‡ÿ²²²ÿÜÜÜÿòòòÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿæææÿÑÑÑÿÈÈÈÿËËËÿdddÿ888ÿ888ÿ888ÿ888ÿYYYÿÿÿÿÿÿÿÿ~~~ÿ}}}ÿzzzÿLLLÿ"""ÿ"""ÿ"""ÿ"""ó"""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""Ï"""ÿ"""ÿ"""ÿ:::ÿzzzÿ}}}ÿÿÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿÿuuuÿWWWÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ;;;ÿYYYÿ}}}ÿ¨¨¨ÿÓÓÓÿíííÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿëëëÿ×××ÿËËËÿËËËÿˆˆˆÿ888ÿ888ÿ888ÿ888ÿHHHÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿ€€€ÿÿ~~~ÿ|||ÿiiiÿ"""ÿ"""ÿ"""ÿ"""ÿ"""x"""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""<"""ÿ"""ÿ"""ÿ"""ÿTTTÿ|||ÿÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ~~~ÿsssÿWWWÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿQQQÿrrrÿœœœÿÉÉÉÿæææÿúúúÿúúúÿúúúÿúúúÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿñññÿÞÞÞÿÎÎÎÿÎÎÎÿªªªÿ999ÿ888ÿ888ÿ888ÿ888ÿ€€€ÿÿÿÿÿ€€€ÿ€€€ÿ~~~ÿ{{{ÿ???ÿ"""ÿ"""ÿ"""ÿ"""Û""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""¥"""ÿ"""ÿ"""ÿ&&&ÿoooÿ}}}ÿÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ}}}ÿrrrÿWWWÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿIIIÿgggÿÿ¿¿¿ÿôôôÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿôôôÿãããÿÑÑÑÿÑÑÑÿÄÄÄÿJJJÿ888ÿ888ÿ888ÿ888ÿxxxÿÿÿÿÿ€€€ÿÿ}}}ÿTTTÿ"""ÿ"""ÿ"""ÿ"""ÿ"""W""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ð"""ÿ"""ÿ"""ÿCCCÿ}}}ÿÿÿÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿÿrrrÿYYYÿGGGÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿÐÐÐÿùùùÿùùùÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿ÷÷÷ÿæææÿÒÒÒÿÍÍÍÿÏÏÏÿ\\\ÿ888ÿ888ÿ888ÿ888ÿqqqÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿÿ€€€ÿÿrrrÿ(((ÿ"""ÿ"""ÿ"""ÿ"""´"""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""f"""ÿ"""ÿ"""ÿ"""ÿXXXÿÿÿ‚‚‚ÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿƒƒƒÿ€€€ÿsssÿZZZÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ­­­ÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿíííÿßßßÿØØØÿqqqÿ888ÿ888ÿ888ÿ888ÿhhhÿƒƒƒÿƒƒƒÿ‚‚‚ÿ‚‚‚ÿ€€€ÿ~~~ÿEEEÿ"""ÿ"""ÿ"""ÿ"""ó""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""Ã"""ÿ"""ÿ"""ÿ---ÿyyyÿÿ‚‚‚ÿƒƒƒÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿ„„„ÿƒƒƒÿwwwÿiiiÿRRRÿ@@@ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ‹‹‹ÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿùùùÿùùùÿòòòÿ~~~ÿ888ÿ888ÿ888ÿ888ÿfffÿ„„„ÿ„„„ÿƒƒƒÿ‚‚‚ÿ€€€ÿZZZÿ"""ÿ"""ÿ"""ÿ"""ÿ"""l""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""$"""ù"""ÿ"""ÿ"""ÿCCCÿÿ‚‚‚ÿ„„„ÿ„„„ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ{{{ÿnnnÿUUUÿCCCÿ888ÿ888ÿ888ÿ888ÿiiiÿùùùÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿ¥¥¥ÿBBBÿ888ÿ888ÿ888ÿ888ÿxxxÿ………ÿ„„„ÿƒƒƒÿÿxxxÿ+++ÿ"""ÿ"""ÿ"""ÿ"""Ã"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""{"""ÿ"""ÿ"""ÿ"""ÿYYYÿ€€€ÿƒƒƒÿ„„„ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ………ÿ~~~ÿ888ÿ888ÿ888ÿ888ÿIIIÿïïïÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿÊÊÊÿpppÿ888ÿ888ÿ888ÿ888ÿ888ÿ===ÿ………ÿ„„„ÿ„„„ÿ‚‚‚ÿÿAAAÿ"""ÿ"""ÿ"""ÿ"""ù"""*ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""É"""ÿ"""ÿ"""ÿ$$$ÿqqqÿ‚‚‚ÿ„„„ÿ………ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ………ÿ888ÿ888ÿ888ÿ888ÿ:::ÿÓÓÓÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿùùùÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿôôôÿ»»»ÿnnnÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿYYYÿ†††ÿ………ÿ„„„ÿÿUUUÿ"""ÿ"""ÿ"""ÿ"""ÿ"""uÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""!"""ó"""ÿ"""ÿ"""ÿ>>>ÿÿ„„„ÿ………ÿ†††ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿJJJÿ888ÿ888ÿ888ÿ888ÿ²²²ÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿôôôÿóóóÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿßßßÿžžžÿ[[[ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿGGGÿ‡‡‡ÿ†††ÿ………ÿƒƒƒÿlllÿ"""ÿ"""ÿ"""ÿ"""ÿ"""½ÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""`"""ÿ"""ÿ"""ÿ"""ÿLLLÿ‚‚‚ÿ„„„ÿ†††ÿ†††ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ‡‡‡ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿ÷÷÷ÿêêêÿãããÿíííÿ­­­ÿ¢¢¢ÿÐÐÐÿéééÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿãããÿ±±±ÿsssÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿYYYÿ‡‡‡ÿ†††ÿ………ÿ„„„ÿÿ888ÿ"""ÿ"""ÿ"""ÿ"""í"""ÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""¨"""ÿ"""ÿ"""ÿ"""ÿ\\\ÿ„„„ÿ†††ÿ‡‡‡ÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿˆˆˆÿrrrÿ888ÿ888ÿ888ÿ888ÿmmmÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿóóóÿáááÿÕÕÕÿÕÕÕÿ“““ÿ888ÿ888ÿLLLÿjjjÿÿ½½½ÿÜÜÜÿöööÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿáááÿ¯¯¯ÿqqqÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿGGGÿvvvÿˆˆˆÿ‡‡‡ÿ‡‡‡ÿ………ÿ‚‚‚ÿDDDÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Qÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ""""""á"""ÿ"""ÿ"""ÿ'''ÿtttÿ………ÿ‡‡‡ÿˆˆˆÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ€€€ÿ888ÿ888ÿ888ÿ888ÿMMMÿóóóÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿóóóÿßßßÿÎÎÎÿÎÎÎÿ²²²ÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿZZZÿzzzÿ¨¨¨ÿÌÌÌÿéééÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿÜÜÜÿ«««ÿnnnÿDDDÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿrrrÿ‰‰‰ÿ‰‰‰ÿˆˆˆÿˆˆˆÿ†††ÿ„„„ÿSSSÿ"""ÿ"""ÿ"""ÿ"""ÿ"""™ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿ"""9"""ù"""ÿ"""ÿ"""ÿ;;;ÿƒƒƒÿ†††ÿ‡‡‡ÿˆˆˆÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‡‡‡ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿ×××ÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿ÷÷÷ÿäääÿÑÑÑÿÍÍÍÿËËËÿTTTÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿLLLÿfffÿ‘‘‘ÿ¹¹¹ÿÙÙÙÿ÷÷÷ÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿùùùÿÑÑÑÿŸŸŸÿeeeÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿPPPÿxxxÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿˆˆˆÿ‡‡‡ÿ………ÿ^^^ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï""" ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿ"""i"""ÿ"""ÿ"""ÿ"""ÿDDDÿ„„„ÿ‡‡‡ÿˆˆˆÿ‰‰‰ÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿJJJÿ888ÿ888ÿ888ÿ888ÿ¸¸¸ÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿèèèÿÓÓÓÿÈÈÈÿÊÊÊÿzzzÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ@@@ÿUUUÿ{{{ÿ¥¥¥ÿÈÈÈÿëëëÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿúúúÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿòòòÿÆÆÆÿ’’’ÿZZZÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿ[[[ÿ†††ÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿ‰‰‰ÿˆˆˆÿ†††ÿ}}}ÿ///ÿ"""ÿ"""ÿ"""ÿ"""í"""!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿ"""Ÿ"""ÿ"""ÿ"""ÿ"""ÿMMMÿ………ÿ‡‡‡ÿ‰‰‰ÿ‰‰‰ÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿ]]]ÿ888ÿ888ÿ888ÿ888ÿ———ÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿïïïÿÜÜÜÿÎÎÎÿÎÎÎÿ¤¤¤ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿeeeÿÿµµµÿÜÜÜÿ÷÷÷ÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿûûûÿâââÿ²²²ÿÿOOOÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿYYYÿ~~~ÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿŠŠŠÿ‰‰‰ÿˆˆˆÿ‡‡‡ÿ„„„ÿ<<<ÿ"""ÿ"""ÿ"""ÿ"""ü"""Hÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿ""" """Ì"""ÿ"""ÿ"""ÿ"""ÿZZZÿ†††ÿˆˆˆÿŠŠŠÿŠŠŠÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿsssÿ888ÿ888ÿ888ÿ888ÿrrrÿüüüÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿ÷÷÷ÿâââÿÑÑÑÿÎÎÎÿÀÀÀÿDDDÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿTTTÿ{{{ÿ¡¡¡ÿÈÈÈÿëëëÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿîîîÿÄÄÄÿ•••ÿeeeÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿZZZÿ€€€ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿŠŠŠÿ‰‰‰ÿˆˆˆÿ………ÿCCCÿ"""ÿ"""ÿ"""ÿ"""ÿ"""~ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""ê"""ÿ"""ÿ"""ÿ"""ÿgggÿ‡‡‡ÿ‰‰‰ÿŠŠŠÿŠŠŠÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ€€€ÿ888ÿ888ÿ888ÿ888ÿQQQÿ÷÷÷ÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿúúúÿèèèÿÔÔÔÿÎÎÎÿÏÏÏÿ]]]ÿ888ÿ888ÿ888ÿ888ÿwwwÿ|||ÿoooÿWWWÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿDDDÿhhhÿÿ»»»ÿãããÿ÷÷÷ÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿïïïÿÍÍÍÿžžžÿsssÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿ[[[ÿÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿ‹‹‹ÿŠŠŠÿŠŠŠÿˆˆˆÿ†††ÿIIIÿ"""ÿ"""ÿ"""ÿ"""ÿ"""±ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""6"""ö"""ÿ"""ÿ"""ÿ---ÿsssÿˆˆˆÿŠŠŠÿ‹‹‹ÿ‹‹‹ÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿ‰‰‰ÿ888ÿ888ÿ888ÿ888ÿ???ÿÛÛÛÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿîîîÿÛÛÛÿÑÑÑÿÑÑÑÿxxxÿ888ÿ888ÿ888ÿ888ÿhhhÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿƒƒƒÿxxxÿ\\\ÿHHHÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ???ÿ^^^ÿƒƒƒÿ­­­ÿ×××ÿíííÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿéééÿÉÉÉÿšššÿuuuÿLLLÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿKKKÿ___ÿ‡‡‡ÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿ‹‹‹ÿ‹‹‹ÿ‰‰‰ÿ‡‡‡ÿRRRÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ò""" ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""W"""ÿ"""ÿ"""ÿ"""ÿ888ÿ~~~ÿˆˆˆÿŠŠŠÿ‹‹‹ÿ‹‹‹ÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿIIIÿ888ÿ888ÿ888ÿ888ÿ¾¾¾ÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿòòòÿßßßÿÓÓÓÿÓÓÓÿ”””ÿ888ÿ888ÿ888ÿ888ÿZZZÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿˆˆˆÿÿoooÿYYYÿEEEÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿLLLÿhhhÿ|||ÿÿœœœÿÿkkkÿFFFÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿAAAÿVVVÿvvvÿ‹‹‹ÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿ‹‹‹ÿ‹‹‹ÿ‰‰‰ÿ‡‡‡ÿ^^^ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ä"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""x"""ÿ"""ÿ"""ÿ"""ÿ;;;ÿƒƒƒÿ‰‰‰ÿ‹‹‹ÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ]]]ÿ888ÿ888ÿ888ÿ888ÿÿüüüÿüüüÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿûûûÿôôôÿâââÿÖÖÖÿÖÖÖÿ¦¦¦ÿ888ÿ888ÿ888ÿ888ÿLLLÿÿÿÿÿÿÿÿÿÿÿÿÿÿ†††ÿyyyÿ]]]ÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿ\\\ÿ€€€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒŒŒÿŒŒŒÿ‹‹‹ÿˆˆˆÿdddÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ð"""-ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""–"""ÿ"""ÿ"""ÿ"""ÿ<<<ÿ†††ÿ‰‰‰ÿ‹‹‹ÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿnnnÿ888ÿ888ÿ888ÿ888ÿÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿøøøÿæææÿØØØÿØØØÿ²²²ÿ999ÿ888ÿ888ÿ888ÿ888ÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŠŠŠÿÿpppÿ[[[ÿGGGÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿEEEÿYYYÿxxxÿ‰‰‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒŒŒÿŒŒŒÿ‹‹‹ÿ‰‰‰ÿhhhÿ)))ÿ"""ÿ"""ÿ"""ÿ"""ü"""Bÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""±"""ÿ"""ÿ"""ÿ"""ÿ???ÿ†††ÿ‰‰‰ÿ‹‹‹ÿŒŒŒÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿvvvÿ888ÿ888ÿ888ÿ888ÿmmmÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿøøøÿèèèÿÚÚÚÿÚÚÚÿºººÿ:::ÿ888ÿ888ÿ888ÿ888ÿŒŒŒÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡‡‡ÿxxxÿ]]]ÿIIIÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿWWWÿsssÿ………ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŒŒŒÿŒŒŒÿ‹‹‹ÿ‰‰‰ÿmmmÿ---ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""`888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿ""""""º"""ÿ"""ÿ"""ÿ"""ÿAAAÿ‡‡‡ÿŠŠŠÿŒŒŒÿÿÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿzzzÿ888ÿ888ÿ888ÿ888ÿfffÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿùùùÿéééÿÜÜÜÿÜÜÜÿºººÿ999ÿ888ÿ888ÿ888ÿ888ÿÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿ‹‹‹ÿ}}}ÿmmmÿXXXÿCCCÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿZZZÿsssÿ………ÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿÿÿŒŒŒÿŠŠŠÿrrrÿ222ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""l888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿ""" """Æ"""ÿ"""ÿ"""ÿ"""ÿ@@@ÿ‡‡‡ÿŠŠŠÿŒŒŒÿÿÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿxxxÿ888ÿ888ÿ888ÿ888ÿjjjÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿùùùÿéééÿÞÞÞÿÞÞÞÿ³³³ÿ888ÿ888ÿ888ÿ888ÿLLLÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿ………ÿyyyÿpppÿgggÿbbbÿhhhÿxxxÿˆˆˆÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿÿÿŒŒŒÿŠŠŠÿpppÿ444ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""x888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿ""""""Ì"""ÿ"""ÿ"""ÿ"""ÿ===ÿ€€€ÿŠŠŠÿŒŒŒÿÿÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿrrrÿ888ÿ888ÿ888ÿ888ÿwwwÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿøøøÿéééÿáááÿáááÿ£££ÿ888ÿ888ÿ888ÿ888ÿWWWÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿÿÿ‹‹‹ÿŠŠŠÿjjjÿ...ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""„888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿ""""""Ï"""ÿ"""ÿ"""ÿ"""ÿ:::ÿxxxÿ‹‹‹ÿÿŽŽŽÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ```ÿ888ÿ888ÿ888ÿ888ÿÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿüüüÿøøøÿêêêÿæææÿäääÿ‹‹‹ÿ888ÿ888ÿ888ÿ888ÿeeeÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿŽŽŽÿŒŒŒÿŠŠŠÿfffÿ***ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""Ã"""ÿ"""ÿ"""ÿ"""ÿ444ÿqqqÿ‹‹‹ÿŒŒŒÿŽŽŽÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿKKKÿ888ÿ888ÿ888ÿ888ÿ¯¯¯ÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿ÷÷÷ÿíííÿíííÿêêêÿiiiÿ888ÿ888ÿ888ÿ888ÿwwwÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿÿŒŒŒÿŠŠŠÿbbbÿ'''ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Š888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""" """±"""ÿ"""ÿ"""ÿ"""ÿ***ÿfffÿŠŠŠÿŒŒŒÿŽŽŽÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿ888ÿ888ÿ888ÿ888ÿ:::ÿÓÓÓÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿöööÿóóóÿðððÿÜÜÜÿEEEÿ888ÿ888ÿ888ÿ888ÿ‰‰‰ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿÿŒŒŒÿ‰‰‰ÿ[[[ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""{888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""œ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ^^^ÿŠŠŠÿÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿaaaÿ888ÿ888ÿ888ÿ888ÿTTTÿ÷÷÷ÿýýýÿýýýÿýýýÿüüüÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿüüüÿöööÿ÷÷÷ÿôôôÿ¤¤¤ÿ888ÿ888ÿ888ÿ888ÿFFFÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿÿŠŠŠÿOOOÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""o888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""Š"""ÿ"""ÿ"""ÿ"""ÿ"""ÿUUUÿŠŠŠÿÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿLLLÿ888ÿ888ÿ888ÿ888ÿ‹‹‹ÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿüüüÿùùùÿùùùÿðððÿWWWÿ888ÿ888ÿ888ÿ888ÿ[[[ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿŒŒŒÿ‰‰‰ÿFFFÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ü"""f888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""o"""ÿ"""ÿ"""ÿ"""ÿ"""ÿOOOÿŠŠŠÿŒŒŒÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ„„„ÿ888ÿ888ÿ888ÿ888ÿ:::ÿÐÐÐÿýýýÿýýýÿýýýÿüüüÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿûûûÿ§§§ÿ888ÿ888ÿ888ÿ888ÿ888ÿ‡‡‡ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿ‹‹‹ÿˆˆˆÿAAAÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""T888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""Z"""ù"""ÿ"""ÿ"""ÿ"""ÿEEEÿ‰‰‰ÿ‹‹‹ÿŽŽŽÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿVVVÿ888ÿ888ÿ888ÿ888ÿdddÿûûûÿýýýÿýýýÿýýýÿüüüÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿãããÿIIIÿ888ÿ888ÿ888ÿ888ÿSSSÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿŽŽŽÿÿ‹‹‹ÿtttÿ777ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ç"""6888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""E"""ó"""ÿ"""ÿ"""ÿ"""ÿ333ÿqqqÿŒŒŒÿŽŽŽÿÿÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‡‡‡ÿ888ÿ888ÿ888ÿ888ÿ888ÿ°°°ÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿøøøÿlllÿ888ÿ888ÿ888ÿ888ÿ888ÿˆˆˆÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿÿÿÿŽŽŽÿ‹‹‹ÿ^^^ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Õ"""888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""6"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿXXXÿ‹‹‹ÿŽŽŽÿÿÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿRRRÿ888ÿ888ÿ888ÿ888ÿQQQÿóóóÿýýýÿýýýÿýýýÿûûûÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿýýýÿüüüÿ„„„ÿ888ÿ888ÿ888ÿ888ÿ888ÿZZZÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿÿÿÿÿŠŠŠÿLLLÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""À"""888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""Æ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿEEEÿ‰‰‰ÿŒŒŒÿÿÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ}}}ÿ888ÿ888ÿ888ÿ888ÿ888ÿ«««ÿþþþÿýýýÿþþþÿþþþÿýýýÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿúúúÿ†††ÿ888ÿ888ÿ888ÿ888ÿ888ÿFFFÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿÿÿŽŽŽÿŒŒŒÿ~~~ÿ@@@ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ""""""888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""‡"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ222ÿnnnÿŒŒŒÿŽŽŽÿÿÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿHHHÿ888ÿ888ÿ888ÿ888ÿ[[[ÿøøøÿþþþÿþþþÿþþþÿüüüÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿçççÿqqqÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ~~~ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿÿÿÿŽŽŽÿ‹‹‹ÿ```ÿ)))ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""Z888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþ"""K"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿVVVÿŒŒŒÿŽŽŽÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ___ÿ888ÿ888ÿ888ÿ888ÿ888ÿ¿¿¿ÿýýýÿüüüÿþþþÿþþþÿýýýÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿ¹¹¹ÿPPPÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿuuuÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿÿ‹‹‹ÿJJJÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Û"""-þþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþ""""""Ã"""ÿ"""ÿ"""ÿ"""ÿ"""ÿEEEÿƒƒƒÿÿÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿŒŒŒÿ999ÿ888ÿ888ÿ888ÿ888ÿoooÿúúúÿýýýÿþþþÿþþþÿüüüÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿÎÎÎÿsssÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿsssÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿÿŒŒŒÿlllÿ666ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""´"""þþþþþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþ""""""„"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ,,,ÿ___ÿŒŒŒÿŽŽŽÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿWWWÿ888ÿ888ÿ888ÿ888ÿ???ÿÔÔÔÿõõõÿøøøÿþþþÿþþþÿýýýÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿôôôÿÁÁÁÿqqqÿ999ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿtttÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿÿÿ………ÿTTTÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""lþþþþþþþþþþþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþþþþþþþ"""E"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿCCCÿrrrÿŒŒŒÿÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿƒƒƒÿ888ÿ888ÿ888ÿ888ÿ888ÿ“““ÿûûûÿ÷÷÷ÿ÷÷÷ÿúúúÿüüüÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿûûûÿÒÒÒÿ”””ÿVVVÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿCCCÿ~~~ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿŽŽŽÿŒŒŒÿ```ÿ...ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ò"""'þþþþþþþþþþþþþþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþþþþþþþþþþ""""""À"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿVVVÿ‰‰‰ÿÿÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿLLLÿ888ÿ888ÿ888ÿ888ÿQQQÿìììÿüüüÿýýýÿýýýÿúúúÿýýýÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿüüüÿÒÒÒÿ˜˜˜ÿ[[[ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿWWWÿÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿÿÿsssÿDDDÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ""""""þþþþþþþþþþþþþþþþþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþþþþþþþþþþþþþþþþþþþþþþþþ""""""u"""ó"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ111ÿbbbÿÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿaaaÿ888ÿ888ÿ888ÿ888ÿ888ÿ²²²ÿýýýÿúúúÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿþþþÿÕÕÕÿ›››ÿ^^^ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿ~~~ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿ‹‹‹ÿVVVÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ø"""?þþþþþþþþþþþþþþþþþþþþþþþþ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""®"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿHHHÿvvvÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿLLLÿ888ÿ888ÿ888ÿ888ÿdddÿùùùÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿÕÕÕÿÿaaaÿ<<<ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿHHHÿbbbÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿcccÿ222ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""x"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""K"""Û"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿWWWÿŒŒŒÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ‘‘‘ÿ888ÿ888ÿ888ÿ888ÿ<<<ÿÊÊÊÿüüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿúúúÿÓÓÓÿ™™™ÿ___ÿ;;;ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ999ÿUUUÿÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿwwwÿIIIÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""±"""!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""" """"""ö"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ555ÿ[[[ÿŒŒŒÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ~~~ÿ888ÿ888ÿ888ÿ888ÿfffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷÷÷ÿÐÐÐÿ•••ÿ\\\ÿ:::ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿSSSÿwwwÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿ{{{ÿSSSÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Þ"""Nÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""$"""º"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ444ÿYYYÿ‡‡‡ÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿqqqÿ888ÿ888ÿ888ÿ888ÿ‚‚‚ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿõõõÿãããÿ¹¹¹ÿ‰‰‰ÿYYYÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿbbbÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿxxxÿRRRÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ù"""„""" ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""T"""ä"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ///ÿWWWÿƒƒƒÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ‡‡‡ÿ888ÿ888ÿ888ÿ888ÿGGGÿ¡¡¡ÿ¶¶¶ÿ¤¤¤ÿŒŒŒÿsssÿVVVÿ===ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿLLLÿaaaÿ‘‘‘ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿÿqqqÿQQQÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""½"""'ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888""" """Š"""ö"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ+++ÿVVVÿÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿAAAÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿLLLÿaaaÿÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿÿmmmÿMMMÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Þ"""W888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888""""""Š"""ð"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ)))ÿTTTÿ|||ÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿMMMÿcccÿ’’’ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿkkkÿKKKÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ø"""`"""888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888888""""""{"""í"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ$$$ÿRRRÿqqqÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿ“““ÿ”””ÿ”””ÿ“““ÿGGGÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ>>>ÿTTTÿyyyÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿÿÿcccÿGGGÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï"""Z888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888888888888""" """r"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ999ÿ\\\ÿ………ÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿ“““ÿ“““ÿ\\\ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿ888ÿKKKÿaaaÿ„„„ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿwwwÿUUUÿ111ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Æ"""H888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888888888888888888""" """l"""ä"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ***ÿRRRÿmmmÿÿÿÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿŽŽŽÿyyyÿcccÿZZZÿbbbÿmmmÿyyyÿ†††ÿ‘‘‘ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿ‡‡‡ÿ^^^ÿDDDÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""À"""?888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888888888888888888888888""""""`"""Ø"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ111ÿRRRÿqqqÿÿŽŽŽÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿˆˆˆÿbbbÿHHHÿ'''ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""·"""9888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888"""H"""«"""ü"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ222ÿRRRÿpppÿŒŒŒÿŽŽŽÿÿÿ‘‘‘ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ‘‘‘ÿÿŽŽŽÿÿ„„„ÿcccÿIIIÿ)))ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ê"""Š"""'888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""u"""Ø"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ###ÿ@@@ÿYYYÿtttÿÿÿÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ‘‘‘ÿ‘‘‘ÿÿŽŽŽÿˆˆˆÿlllÿRRRÿ555ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""º"""T"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""?"""–"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ(((ÿBBBÿ\\\ÿwwwÿÿÿÿ‘‘‘ÿ‘‘‘ÿ“““ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ’’’ÿ‘‘‘ÿÿÿŽŽŽÿŠŠŠÿoooÿTTTÿ888ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ï"""{""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""H"""œ"""í"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ+++ÿDDDÿ\\\ÿoooÿ„„„ÿŽŽŽÿÿÿÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ“““ÿ“““ÿ“““ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ•••ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ”””ÿ“““ÿ“““ÿ“““ÿ“““ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿÿÿŽŽŽÿÿ{{{ÿgggÿTTTÿ999ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Õ"""~"""$""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""H"""‡"""Æ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ+++ÿ===ÿPPPÿ```ÿpppÿzzzÿÿŽŽŽÿÿÿÿÿÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ’’’ÿ’’’ÿ’’’ÿ“““ÿ“““ÿ“““ÿ’’’ÿ’’’ÿ’’’ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿÿÿÿÿÿŽŽŽÿ‰‰‰ÿvvvÿiiiÿ^^^ÿKKKÿ666ÿ$$$ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ó"""´"""u"""'"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""N""""""Ò"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ...ÿ666ÿKKKÿKKKÿWWWÿ```ÿaaaÿuuuÿvvvÿvvvÿwwwÿ€€€ÿÿÿÿÿÿÿÿzzzÿwwwÿvvvÿvvvÿoooÿaaaÿ```ÿQQQÿKKKÿDDDÿ666ÿ(((ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ù"""º"""{"""<"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""T"""“"""Û"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ü"""À""""""B"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""W""""""½"""ê"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ø"""«"""~"""H""" """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""E"""r"""™"""½"""Õ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ö"""Ì"""®"""“"""f"""3""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""3"""f"""f"""„"""™"""™"""É"""Ì"""Ì"""Ì"""á"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""Ò"""Ì"""Ì"""Ì"""º"""™"""™"""u"""f"""W"""3"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿü?ÿÿÿÿÿÿøÿÿÿÿÿÿøÿÿÿÿÿÿðÿÿÿÿÿÿàÿÿÿÿÿÿÀÿÿÿÿÿÿÀÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿþÿÿÿÿþ?ÿÿÿÿüÿÿÿÿøÿÿÿÿøÿÿÿÿðÿÿÿÿàÿÿÿÿàÿÿÿÿÀÿÿÿÿÀÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿÿÿþÿÿþ?ÿÿü?ÿÿüÿÿøÿÿøÿÿðÿÿðÿÿàÿÿàÿÿàÿÿÀÿÿÀÿÿ€ÿÿ€ÿÿ€ÿÿÿÿÿþþ?þ?ü?üüøøøøððððàààààÀÀÀÀÀÀ€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€€ÀÀÀÀÀÀàààààðððððøøøüüüü?þ?þ?þÿÿÿ€ÿÿ€ÿÿ€ÿÿÀÿÿÀÿÿÀÿÿàÿÿàÿÿðÿÿðÿÿøÿÿøÿÿüÿÿü?ÿÿþ?ÿÿþÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿÀÿÿÿÿàÿÿÿÿàÿÿÿÿðÿÿÿÿøÿÿÿÿøÿÿÿÿüÿÿÿÿü?ÿÿÿÿþÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿ€ÿÿÿÿÿÿÀÿÿÿÿÿÿàÿÿÿÿÿÿðÿÿÿÿÿÿðÿÿÿÿÿÿøÿÿÿÿÿÿüÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿü?ÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿàÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿüÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿðÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ(0` """""""""""""""""""""""""""""""""""""""""""""""""""""""""2"""Y"""Œ!!!º!!!Ð!!!Û!!!Ú!!!Î!!!µ"""†"""S""".""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""M"""–!!!ä!!!ÿ!!!ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ ÿ!!!ý!!!Þ"""Œ"""D"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" """m"""Í###ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ###ÿ$$$ÿ$$$ÿ$$$ÿ###ÿ"""Ã"""^""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""!!!S###Ö&&&ÿ)))ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ111ÿ...ÿ***ÿ***ÿ***ÿ***ÿ)))ÿ&&&ÿ"""É!!!B""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""!!!"""¥'''ÿ...ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ///ÿ,,,ÿlllÿcccÿ---ÿ///ÿ///ÿ///ÿ///ÿ///ÿ---ÿ'''ÿ!!!’""" """""""""""""""""""""""""""""""""""""""""""""""""""!!!6$$$×///ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ222ÿ>>>ÿÃÃÃÿ···ÿKKKÿ000ÿ555ÿ555ÿ555ÿ555ÿ555ÿ555ÿ...ÿ"""Ç!!!&""""""""""""""""""""""""""""""""""""""""""""" L'''ð888ÿ<<<ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ000ÿ}}}ÿßßßÿØØØÿŸŸŸÿ===ÿ777ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ;;;ÿ<<<ÿ666ÿ%%%ä!!!8""""""""""""""""""""""""""""""""""""""" L***ø@@@ÿBBBÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ>>>ÿ>>>ÿÄÄÄÿÛÛÛÿÙÙÙÿÔÔÔÿ‚‚‚ÿ555ÿ@@@ÿAAAÿAAAÿAAAÿAAAÿAAAÿBBBÿ===ÿ&&&í 7"""""""""""""""""""""""""""""""""!!!6'''ó;;;ÿ;;;ÿDDDÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿ777ÿÿêêêÿßßßÿ×××ÿÛÛÛÿÈÈÈÿ]]]ÿ888ÿGGGÿFFFÿFFFÿFFFÿFFFÿFFFÿGGGÿAAAÿ&&&æ!!!$"""""""""""""""""""""""""""!!!'''Þ;;;ÿVVVÿkkkÿ999ÿ???ÿKKKÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿ888ÿÿ™™™ÿ›››ÿãããÿØØØÿÜÜÜÿ¬¬¬ÿ@@@ÿDDDÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿLLLÿBBBÿ$$$É!!! """"""""""""""""""""""""!!!±BBBÿMMMÿ===ÿ“““ÿ¦¦¦ÿJJJÿ888ÿJJJÿNNNÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿDDDÿ@@@ÿ777ÿ999ÿ»»»ÿâââÿÚÚÚÿ×××ÿ~~~ÿ777ÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿOOOÿ===ÿ “"""""""""""""""""""""\666ÿPPPÿOOOÿJJJÿ888ÿ­­­ÿÈÈÈÿkkkÿ888ÿFFFÿPPPÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿOOOÿMMMÿPPPÿ???ÿ\\\ÿÞÞÞÿÛÛÛÿÝÝÝÿÂÂÂÿOOOÿCCCÿOOOÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿPPPÿ111ÿ?"""""""""""""""!!!(((ãLLLÿOOOÿNNNÿOOOÿFFFÿAAAÿÉÉÉÿÚÚÚÿ‹‹‹ÿ>>>ÿBBBÿOOOÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿ666ÿ¯¯¯ÿäääÿÜÜÜÿÞÞÞÿ‘‘‘ÿ888ÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿOOOÿIIIÿ%%%Ì!!!"""""""""""" x<<<ÿOOOÿMMMÿMMMÿMMMÿNNNÿ===ÿZZZÿâââÿÝÝÝÿ   ÿEEEÿ>>>ÿNNNÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿNNNÿ>>>ÿfffÿäääÿÜÜÜÿßßßÿËËËÿUUUÿ@@@ÿNNNÿMMMÿMMMÿMMMÿMMMÿMMMÿMMMÿOOOÿ777ÿ ["""""""""!!! (((äIIIÿKKKÿJJJÿJJJÿJJJÿJJJÿLLLÿ555ÿÿëëëÿÞÞÞÿ«««ÿHHHÿ>>>ÿLLLÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿGGGÿCCCÿÎÎÎÿâââÿÞÞÞÿßßßÿ’’’ÿ888ÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿJJJÿLLLÿGGGÿ%%%Ç!!!"""""" `666ÿIIIÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿEEEÿ888ÿ»»»ÿéééÿàààÿ©©©ÿBBBÿ???ÿHHHÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿHHHÿ444ÿ™™™ÿéééÿÞÞÞÿâââÿÈÈÈÿLLLÿ???ÿHHHÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿIIIÿ222ÿ A""""""###°>>>ÿDDDÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿDDDÿ777ÿ___ÿäääÿãããÿàààÿ™™™ÿ999ÿ@@@ÿDDDÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿDDDÿ666ÿsssÿêêêÿßßßÿáááÿàààÿ‚‚‚ÿ666ÿDDDÿCCCÿCCCÿCCCÿCCCÿCCCÿCCCÿEEEÿ;;;ÿ!!!Ž"""!!!)))î???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ000ÿ§§§ÿìììÿãããÿÜÜÜÿ}}}ÿ333ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ999ÿSSSÿßßßÿãããÿáááÿåååÿ³³³ÿ;;;ÿ<<<ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ???ÿ>>>ÿ'''á!!! !!!Q...ÿ;;;ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ333ÿSSSÿáááÿäääÿæææÿËËËÿUUUÿ333ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ999ÿ666ÿÀÀÀÿéééÿâââÿåååÿÖÖÖÿ___ÿ333ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ:::ÿ;;;ÿ+++þ!!!,!!!u...ÿ555ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ333ÿ---ÿ©©©ÿíííÿäääÿçççÿ   ÿ666ÿ333ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ333ÿ---ÿ¦¦¦ÿîîîÿãããÿäääÿæææÿ‰‰‰ÿ...ÿ333ÿ444ÿ444ÿ444ÿ444ÿ444ÿ444ÿ555ÿ,,,ÿ!!!U"""£,,,ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ)))ÿ[[[ÿéééÿåååÿèèèÿØØØÿ^^^ÿ***ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ---ÿ'''ÿ“““ÿðððÿäääÿåååÿéééÿ®®®ÿ555ÿ---ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ...ÿ***ÿ""""""Ë)))ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ(((ÿ333ÿÀÀÀÿîîîÿçççÿéééÿ™™™ÿ///ÿ(((ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ***ÿ)))ÿ%%%ÿuuuÿïïïÿæææÿæææÿéééÿÓÓÓÿQQQÿ&&&ÿ)))ÿ***ÿ***ÿ***ÿ***ÿ***ÿ)))ÿ(((ÿ"""¸àÿÿÿÿÿÿÿÿÿÿÿÿÿÿ‡‡‡ÿôôôÿçççÿëëëÿÊÊÊÿDDDÿÿÿÿÿÿÿÿÿÿÿVVVÿèèèÿéééÿçççÿéééÿâââÿmmmÿÿÿÿÿÿÿÿÿÿ Ï###ë%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ$$$ÿ"""ÿ]]]ÿëëëÿêêêÿëëëÿáááÿhhhÿ"""ÿ$$$ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ###ÿEEEÿÝÝÝÿìììÿèèèÿéééÿìììÿ‘‘‘ÿ'''ÿ$$$ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ%%%ÿ"""Ú)))ì@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ>>>ÿ888ÿÈÈÈÿðððÿêêêÿîîîÿžžžÿ666ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ===ÿ???ÿÓÓÓÿïïïÿêêêÿêêêÿîîîÿ½½½ÿ???ÿ>>>ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ>>>ÿ%%%Ú(((áDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿEEEÿ333ÿ¦¦¦ÿöööÿëëëÿïïïÿÄÄÄÿAAAÿ@@@ÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿDDDÿCCCÿ;;;ÿÉÉÉÿòòòÿëëëÿëëëÿïïïÿÕÕÕÿOOOÿ>>>ÿEEEÿDDDÿDDDÿDDDÿDDDÿEEEÿBBBÿ%%%Ñ'''ÍIIIÿLLLÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿLLLÿ:::ÿ~~~ÿ÷÷÷ÿìììÿïïïÿäääÿjjjÿ===ÿLLLÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿLLLÿ999ÿ»»»ÿõõõÿìììÿìììÿîîîÿèèèÿnnnÿ;;;ÿLLLÿKKKÿKKKÿKKKÿKKKÿLLLÿGGGÿ%%%»###¨KKKÿTTTÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿSSSÿIIIÿPPPÿèèèÿðððÿïïïÿñññÿ”””ÿ999ÿSSSÿRRRÿRRRÿRRRÿRRRÿRRRÿRRRÿTTTÿ999ÿ   ÿùùùÿîîîÿîîîÿîîîÿòòòÿžžžÿ<<<ÿSSSÿRRRÿRRRÿRRRÿRRRÿTTTÿHHHÿ"""“ yIIIÿ\\\ÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿYYYÿZZZÿ[[[ÿ[[[ÿXXXÿ@@@ÿÒÒÒÿõõõÿïïïÿóóóÿÄÄÄÿDDDÿTTTÿZZZÿYYYÿYYYÿYYYÿYYYÿYYYÿ[[[ÿ@@@ÿ‚‚‚ÿúúúÿïïïÿïïïÿïïïÿôôôÿ¼¼¼ÿ@@@ÿWWWÿYYYÿYYYÿYYYÿYYYÿ\\\ÿBBBÿ\WDDDÿcccÿ___ÿ___ÿ___ÿ___ÿ___ÿ___ÿaaaÿbbbÿ___ÿVVVÿGGGÿ???ÿAAAÿ:::ÿ½½½ÿùùùÿðððÿóóóÿçççÿbbbÿFFFÿ^^^ÿaaaÿbbbÿaaaÿ```ÿ___ÿaaaÿLLLÿiiiÿ÷÷÷ÿñññÿðððÿðððÿôôôÿÜÜÜÿRRRÿQQQÿaaaÿ___ÿ___ÿ___ÿbbbÿ:::ÿ0555ógggÿfffÿeeeÿeeeÿeeeÿhhhÿgggÿZZZÿFFFÿ@@@ÿMMMÿgggÿ–––ÿ±±±ÿÀÀÀÿÜÜÜÿóóóÿñññÿòòòÿóóóÿ–––ÿFFFÿCCCÿ???ÿLLLÿVVVÿ___ÿgggÿjjjÿ[[[ÿ^^^ÿòòòÿóóóÿñññÿñññÿóóóÿñññÿzzzÿKKKÿhhhÿeeeÿeeeÿfffÿeeeÿ000ç !!!%%%¸cccÿmmmÿkkkÿkkkÿmmmÿZZZÿ>>>ÿOOOÿwwwÿ±±±ÿØØØÿìììÿöööÿ÷÷÷ÿøøøÿôôôÿòòòÿòòòÿóóóÿôôôÿæææÿÐÐÐÿ¹¹¹ÿÿkkkÿXXXÿCCCÿ@@@ÿKKKÿLLLÿRRRÿíííÿõõõÿòòòÿòòòÿóóóÿ÷÷÷ÿ™™™ÿCCCÿnnnÿkkkÿkkkÿnnnÿ[[[ÿ!!!˜!!!"""kPPPÿuuuÿpppÿsssÿZZZÿUUUÿ¬¬¬ÿàààÿôôôÿùùùÿøøøÿöööÿõõõÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿôôôÿöööÿøøøÿùùùÿöööÿìììÿßßßÿÃÃÃÿœœœÿ€€€ÿ___ÿOOOÿãããÿøøøÿôôôÿôôôÿôôôÿùùùÿÄÄÄÿCCCÿkkkÿqqqÿpppÿuuuÿGGGÿJ""""""111êuuuÿuuuÿwwwÿ\\\ÿSSSÿ¸¸¸ÿèèèÿÿÿÿÿÿÿÿÿüüüÿöööÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿõõõÿöööÿøøøÿùùùÿúúúÿùùùÿôôôÿçççÿÌÌÌÿæææÿ÷÷÷ÿõõõÿõõõÿõõõÿøøøÿæææÿ\\\ÿcccÿwwwÿvvvÿoooÿ***Ò """""""""‰\\\ÿ~~~ÿxxxÿzzzÿdddÿHHHÿJJJÿwwwÿ«««ÿÖÖÖÿúúúÿÿÿÿÿÿÿÿÿûûûÿøøøÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿöööÿ÷÷÷ÿøøøÿùùùÿûûûÿ÷÷÷ÿöööÿöööÿöööÿöööÿøøøÿôôôÿuuuÿTTTÿ|||ÿ~~~ÿRRRÿj""""""""""""333î|||ÿÿ}}}ÿ€€€ÿÿuuuÿ^^^ÿMMMÿEEEÿeeeÿ………ÿ²²²ÿãããÿ÷÷÷ÿÿÿÿÿøøøÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿøøøÿüüüÿ¡¡¡ÿEEEÿÿwwwÿ***Ú"""""""""""""""nUUUÿˆˆˆÿ‚‚‚ÿ‚‚‚ÿ‚‚‚ÿƒƒƒÿ†††ÿ†††ÿ}}}ÿnnnÿ\\\ÿHHHÿOOOÿYYYÿ’’’ÿüüüÿùùùÿøøøÿøøøÿøøøÿùùùÿüüüÿùùùÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿøøøÿúúúÿÿÿÿÿÛÛÛÿKKKÿ‚‚‚ÿJJJÿP""""""""""""""""""!!!$$$Âsssÿ‹‹‹ÿ†††ÿ†††ÿ†††ÿ†††ÿ†††ÿ‡‡‡ÿˆˆˆÿŠŠŠÿ‰‰‰ÿƒƒƒÿnnnÿBBBÿåååÿþþþÿùùùÿùùùÿúúúÿóóóÿèèèÿþþþÿÿÿÿÿÿÿÿÿÿÿÿÿüüüÿúúúÿùùùÿùùùÿúúúÿüüüÿÿÿÿÿÿÿÿÿùùùÿÉÉÉÿzzzÿUUUÿiiiÿ!!!¨!!!"""""""""""""""""""""&333ë„„„ÿÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿ‰‰‰ÿŠŠŠÿÿIIIÿÂÂÂÿÿÿÿÿúúúÿúúúÿýýýÿêêêÿiiiÿcccÿ‚‚‚ÿ§§§ÿÚÚÚÿôôôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿòòòÿÇÇÇÿ‰‰‰ÿ```ÿEEEÿdddÿ{{{ÿ---Ù"""""""""""""""""""""""""""KAAAüÿÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿŒŒŒÿ‘‘‘ÿXXXÿ§§§ÿÿÿÿÿûûûÿûûûÿýýýÿúúúÿuuuÿOOOÿiiiÿNNNÿMMMÿZZZÿkkkÿœœœÿ   ÿlllÿWWWÿJJJÿ\\\ÿwwwÿÿ‹‹‹ÿ888ñ3"""""""""""""""""""""""""""""""""bGGGÿŽŽŽÿ‘‘‘ÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿŽŽŽÿ“““ÿ```ÿšššÿÿÿÿÿüüüÿüüüÿýýýÿÿÿÿÿƒƒƒÿ^^^ÿ˜˜˜ÿ’’’ÿŒŒŒÿƒƒƒÿnnnÿWWWÿUUUÿlllÿ„„„ÿÿ“““ÿ•••ÿ‰‰‰ÿ;;;øJ"""""""""""""""""""""""""""""""""""""""]@@@úŠŠŠÿ•••ÿÿÿÿÿÿÿ”””ÿMMMÿ²²²ÿÿÿÿÿýýýÿýýýÿÿÿÿÿÿÿÿÿkkkÿqqqÿ”””ÿÿÿ‘‘‘ÿ”””ÿ•••ÿ•••ÿ”””ÿ‘‘‘ÿÿ–––ÿ………ÿ888òL"""""""""""""""""""""""""""""""""""""""""""""K333èzzzÿ˜˜˜ÿ“““ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ•••ÿ{{{ÿRRRÿðððÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿžžžÿJJJÿÿ’’’ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ‘‘‘ÿ”””ÿ———ÿuuuÿ...Ü8"""""""""""""""""""""""""""""""""""""""""""""""""""&$$$½]]]ÿÿ™™™ÿ”””ÿ“““ÿ“““ÿMMMÿ´´´ÿÿÿÿÿÿÿÿÿçççÿ®®®ÿiiiÿJJJÿ‰‰‰ÿ•••ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ’’’ÿ”””ÿšššÿŒŒŒÿTTTÿ!!!®""""""""""""""""""""""""""""""""""""""""""""""""""""""""" j666élllÿ“““ÿœœœÿˆˆˆÿTTTÿÕÕÕÿ¨¨¨ÿuuuÿLLLÿQQQÿpppÿ”””ÿ–––ÿ“““ÿ“““ÿ“““ÿ“““ÿ”””ÿ———ÿšššÿÿeeeÿ000ÝY!!!""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""‚666äfffÿ†††ÿbbbÿRRRÿWWWÿtttÿ‹‹‹ÿšššÿ™™™ÿ•••ÿ•••ÿ–––ÿ˜˜˜ÿšššÿ›››ÿ–––ÿ„„„ÿ```ÿ111Ür""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""l'''´DDDð```ÿwwwÿˆˆˆÿ‘‘‘ÿ”””ÿ———ÿ———ÿ”””ÿŽŽŽÿ‚‚‚ÿpppÿ^^^ÿ>>>é$$$¬` """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Sx%%%§///Ë888à>>>ì===ë777Þ...É$$$¡tM!!!""""""""""""""""""""""""""""""""""""""""""""""""ÿÿ€ÿÿÿüÿÿðÿÿàÿÿ€ÿÿÿþü?øðààÀÀ€€€€€€ÀÀààðøü?þÿÿÿ€ÿÿÀÿÿðÿÿü?ÿÿÿ€ÿÿ( @ """""""""""""""""""""""""""""""""""":"""u!!! !!!Ã!!!Ú!!!×!!!¾!!!™"""k"""1"""""""""""""""""""""""""""""""""""""""""""""""""""""""""!!!"""{###Õ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ###ÿ!!!ÿ"""ÿ###ý###É"""i!!! """"""""""""""""""""""""""""""""""""""""""!!!"""m&&&å***ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ+++ÿ999ÿ+++ÿ***ÿ+++ÿ)))ÿ&&&×"""V!!!"""""""""""""""""""""""""""""""""!!!%%%´000ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ333ÿ111ÿ<<<ÿ©©©ÿWWWÿ---ÿ444ÿ333ÿ444ÿ...ÿ###™!!! """""""""""""""""""""""""""!!!%+++Ö;;;ÿ===ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ<<<ÿ===ÿ222ÿwwwÿåååÿ³³³ÿAAAÿ999ÿ===ÿ<<<ÿ===ÿ999ÿ'''¼ """"""""""""""""""""" +++Ù<<<ÿBBBÿFFFÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿEEEÿBBBÿDDDÿÈÈÈÿäääÿÝÝÝÿ‘‘‘ÿ999ÿEEEÿEEEÿEEEÿFFFÿBBBÿ)))½ """"""""""""""" ***¿AAAÿmmmÿOOOÿ???ÿLLLÿKKKÿKKKÿKKKÿKKKÿKKKÿKKKÿFFFÿVVVÿƒƒƒÿ¦¦¦ÿäääÿÕÕÕÿdddÿ@@@ÿLLLÿKKKÿKKKÿLLLÿFFFÿ&&&›!!!"""""""""!!!###{GGGÿKKKÿZZZÿ°°°ÿiiiÿ===ÿMMMÿNNNÿNNNÿNNNÿNNNÿNNNÿNNNÿIIIÿ>>>ÿIIIÿËËËÿåååÿµµµÿDDDÿLLLÿNNNÿNNNÿNNNÿQQQÿ@@@ÿ!!!R""""""""" !888óQQQÿOOOÿBBBÿkkkÿÚÚÚÿ†††ÿ???ÿKKKÿOOOÿNNNÿNNNÿNNNÿNNNÿOOOÿPPPÿ???ÿ†††ÿçççÿàààÿyyyÿ@@@ÿOOOÿNNNÿNNNÿNNNÿPPPÿ111Ü """!!!&&&“IIIÿLLLÿLLLÿMMMÿ;;;ÿŽŽŽÿìììÿ•••ÿ???ÿIIIÿLLLÿLLLÿLLLÿLLLÿLLLÿLLLÿGGGÿQQQÿ×××ÿåååÿ¿¿¿ÿFFFÿIIIÿLLLÿLLLÿLLLÿMMMÿDDDÿ###h""" 333ëIIIÿGGGÿGGGÿGGGÿEEEÿ>>>ÿÁÁÁÿìììÿ“““ÿ;;;ÿFFFÿGGGÿGGGÿGGGÿGGGÿGGGÿGGGÿ;;;ÿ´´´ÿçççÿãããÿtttÿ<<<ÿGGGÿGGGÿGGGÿGGGÿHHHÿ---Í """U:::ÿBBBÿAAAÿAAAÿAAAÿAAAÿ777ÿgggÿêêêÿäääÿzzzÿ666ÿAAAÿAAAÿAAAÿAAAÿAAAÿAAAÿ555ÿ‹‹‹ÿëëëÿèèèÿ°°°ÿ:::ÿ@@@ÿAAAÿAAAÿAAAÿBBBÿ555ý 0%%%—888ÿ999ÿ999ÿ999ÿ999ÿ999ÿ999ÿ222ÿ´´´ÿðððÿÓÓÓÿQQQÿ333ÿ999ÿ999ÿ999ÿ999ÿ999ÿ111ÿfffÿèèèÿæææÿÙÙÙÿRRRÿ333ÿ999ÿ999ÿ999ÿ:::ÿ666ÿ###m&&&Å111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ111ÿ(((ÿgggÿìììÿíííÿŸŸŸÿ---ÿ///ÿ111ÿ111ÿ111ÿ111ÿ,,,ÿJJJÿÞÞÞÿçççÿêêêÿ}}}ÿ(((ÿ000ÿ111ÿ111ÿ111ÿ000ÿ$$$ž"""ß"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""ÿÿ---ÿËËËÿðððÿÙÙÙÿJJJÿÿ"""ÿ"""ÿ"""ÿ"""ÿÿ222ÿÏÏÏÿëëëÿîîîÿ©©©ÿ&&&ÿ!!!ÿ"""ÿ"""ÿ"""ÿ"""ÿ"""À ìÿÿÿÿÿÿÿÿÿ˜˜˜ÿôôôÿïïïÿ|||ÿÿÿÿÿÿÿ$$$ÿ¿¿¿ÿðððÿíííÿÎÎÎÿ666ÿÿÿÿÿÿ Ø111íBBBÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿAAAÿ777ÿiiiÿðððÿòòòÿ±±±ÿ999ÿ@@@ÿ@@@ÿ@@@ÿ@@@ÿAAAÿ555ÿ«««ÿôôôÿìììÿæææÿ___ÿ999ÿ@@@ÿ@@@ÿ@@@ÿBBBÿ,,,Ú333áKKKÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿIIIÿGGGÿLLLÿÞÞÞÿóóóÿÛÛÛÿPPPÿEEEÿIIIÿIIIÿIIIÿJJJÿ;;;ÿ———ÿ÷÷÷ÿíííÿóóóÿ………ÿ===ÿJJJÿIIIÿIIIÿJJJÿ,,,Ä222ËVVVÿSSSÿTTTÿTTTÿTTTÿTTTÿTTTÿUUUÿVVVÿDDDÿ¾¾¾ÿ÷÷÷ÿòòòÿzzzÿFFFÿUUUÿTTTÿTTTÿUUUÿFFFÿ„„„ÿøøøÿîîîÿöööÿ¯¯¯ÿCCCÿUUUÿTTTÿTTTÿSSSÿ+++¤,,,¡\\\ÿ^^^ÿ]]]ÿ]]]ÿ___ÿ```ÿZZZÿOOOÿHHHÿ;;;ÿÿûûûÿøøøÿ®®®ÿ@@@ÿZZZÿ^^^ÿ```ÿ___ÿTTTÿrrrÿöööÿðððÿõõõÿÔÔÔÿPPPÿ\\\ÿ]]]ÿ___ÿWWWÿ%%%v###`[[[ÿiiiÿgggÿiiiÿYYYÿKKKÿXXXÿzzzÿ¢¢¢ÿÀÀÀÿÝÝÝÿôôôÿõõõÿßßßÿwwwÿXXXÿMMMÿMMMÿUUUÿYYYÿcccÿïïïÿóóóÿóóóÿîîîÿhhhÿ]]]ÿgggÿkkkÿQQQÿ9IIIótttÿpppÿXXXÿ{{{ÿ¼¼¼ÿåååÿøøøÿüüüÿûûûÿ÷÷÷ÿóóóÿóóóÿõõõÿôôôÿãããÿÇÇÇÿ¤¤¤ÿÿbbbÿXXXÿæææÿ÷÷÷ÿôôôÿûûûÿŒŒŒÿZZZÿqqqÿsssÿ===Ù...¥sssÿxxxÿZZZÿ‡‡‡ÿÎÎÎÿñññÿÿÿÿÿÿÿÿÿüüüÿ÷÷÷ÿöööÿõõõÿöööÿ÷÷÷ÿùùùÿüüüÿþþþÿúúúÿíííÿØØØÿîîîÿ÷÷÷ÿõõõÿýýýÿ¶¶¶ÿVVVÿ{{{ÿkkkÿ%%%z!!!"""1VVVû‚‚‚ÿ}}}ÿjjjÿ\\\ÿeeeÿ„„„ÿ®®®ÿÕÕÕÿòòòÿýýýÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿ÷÷÷ÿøøøÿúúúÿüüüÿøøøÿ÷÷÷ÿ÷÷÷ÿüüüÿÞÞÞÿ\\\ÿ€€€ÿHHHê"""""" ***•zzzÿ†††ÿ………ÿ†††ÿ€€€ÿrrrÿdddÿaaaÿbbbÿ¯¯¯ÿþþþÿùùùÿøøøÿûûûÿÿÿÿÿÿÿÿÿþþþÿúúúÿùùùÿùùùÿûûûÿÿÿÿÿÿÿÿÿðððÿkkkÿfffÿ###k!!!"""""""""@@@׋‹‹ÿ‹‹‹ÿˆˆˆÿ‰‰‰ÿŠŠŠÿŒŒŒÿŒŒŒÿxxxÿuuuÿþþþÿûûûÿýýýÿÌÌÌÿÿ¹¹¹ÿÜÜÜÿõõõÿÿÿÿÿÿÿÿÿòòòÿÎÎÎÿšššÿpppÿkkkÿ444¶"""""""""""""""/PPPí’’’ÿŽŽŽÿŒŒŒÿŒŒŒÿŒŒŒÿÿ‹‹‹ÿjjjÿðððÿþþþÿÿÿÿÿÑÑÑÿNNNÿjjjÿeeeÿoooÿˆˆˆÿ‡‡‡ÿlllÿdddÿtttÿ†††ÿEEE×""""""""""""""""""""">PPPê’’’ÿ”””ÿÿÿÿÿiiiÿóóóÿÿÿÿÿÿÿÿÿÓÓÓÿdddÿ———ÿ’’’ÿ‹‹‹ÿ~~~ÿ~~~ÿŒŒŒÿ˜˜˜ÿÿCCCØ%""""""""""""""""""""""""!!!0AAAц††ÿ™™™ÿ“““ÿ•••ÿrrrÿŸŸŸÿÿÿÿÿÿÿÿÿÛÛÛÿsssÿÿ”””ÿ’’’ÿ’’’ÿ“““ÿ–––ÿ™™™ÿ~~~ÿ777¼"""""""""""""""""""""""""""""""""***‘cccùÿšššÿfffÿËËËÿ°°°ÿ|||ÿdddÿ€€€ÿ———ÿ“““ÿ•••ÿ˜˜˜ÿšššÿŠŠŠÿXXXï$$$y"""""""""""""""""""""""""""""""""""""""3111¡^^^ðdddÿgggÿuuuÿŒŒŒÿžžžÿžžžÿšššÿ˜˜˜ÿÿ{{{ÿUUUæ+++Ž# """"""""""""""""""""""""""""""""""""""""""""""""%%%^777JJJÉUUUàZZZìYYYëRRRÝCCCÃ000“ S"""""""""""""""""""""""""""ÿÀÿÿÿüøðàÀÀ€€€€ÀÀàðøü?ÿÿÿÀÿ(0 """""""""""""""""""""!!!!!!e!!!¥!!!Ë á á È!!!¢!!!_!!!""""""""""""""""""""""""""""""""""""!!!###‘&&&í&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ&&&ÿ$$$ÿ&&&ÿ&&&ê###ˆ!!!""""""""""""""""""""""""!!!!!!O+++ã111ÿ222ÿ222ÿ222ÿ222ÿ222ÿ+++ÿjjjÿjjjÿ***ÿ222ÿ111ÿ***Ü!!!E!!!"""""""""""""""!!!###e555ü???ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ>>>ÿ<<<ÿ===ÿÃÃÃÿÍÍÍÿNNNÿ888ÿ>>>ÿ???ÿ666ø###Y!!!"""""""""!!!"""O===þQQQÿ???ÿHHHÿHHHÿHHHÿHHHÿHHHÿBBBÿdddÿ½½½ÿãããÿ§§§ÿ???ÿGGGÿHHHÿJJJÿ<<<ù!!!B""""""""" :::êKKKÿÿxxxÿ@@@ÿLLLÿNNNÿNNNÿNNNÿMMMÿKKKÿAAAÿ±±±ÿæææÿtttÿCCCÿNNNÿNNNÿPPPÿ777à""" ***šNNNÿMMMÿIIIÿµµµÿ˜˜˜ÿBBBÿKKKÿNNNÿMMMÿMMMÿNNNÿEEEÿhhhÿçççÿÁÁÁÿIIIÿLLLÿMMMÿNNNÿLLLÿ(((‰ ;;;óKKKÿIIIÿCCCÿVVVÿÝÝÝÿžžžÿ@@@ÿGGGÿIIIÿIIIÿIIIÿHHHÿFFFÿÅÅÅÿêêêÿ{{{ÿ???ÿIIIÿIIIÿKKKÿ999î %%%r???ÿBBBÿAAAÿAAAÿ444ÿ‰‰‰ÿóóóÿ‰‰‰ÿ777ÿAAAÿAAAÿAAAÿAAAÿ666ÿœœœÿñññÿ¸¸¸ÿ<<<ÿ@@@ÿAAAÿBBBÿ>>>ÿ$$$`)))«888ÿ777ÿ777ÿ777ÿ444ÿ>>>ÿÓÓÓÿäääÿ[[[ÿ///ÿ777ÿ777ÿ777ÿ---ÿ{{{ÿîîîÿßßßÿYYYÿ111ÿ777ÿ777ÿ888ÿ'''¥%%%Ô)))ÿ)))ÿ)))ÿ)))ÿ)))ÿÿÿúúúÿ©©©ÿ'''ÿ'''ÿ)))ÿ)))ÿ ÿZZZÿêêêÿïïïÿ‚‚‚ÿ ÿ(((ÿ)))ÿ)))ÿ$$$Ì!!!ëÿÿÿÿ ÿÿMMMÿëëëÿßßßÿAAAÿÿÿÿÿ>>>ÿàààÿóóóÿ®®®ÿÿÿÿÿ!!!â333ëCCCÿAAAÿAAAÿAAAÿAAAÿ???ÿ@@@ÿÐÐÐÿ÷÷÷ÿyyyÿ777ÿAAAÿAAAÿ???ÿCCCÿÒÒÒÿôôôÿÔÔÔÿIIIÿ>>>ÿAAAÿCCCÿ222â888ÖRRRÿOOOÿOOOÿOOOÿOOOÿPPPÿCCCÿ§§§ÿÿÿÿÿ°°°ÿCCCÿOOOÿOOOÿOOOÿEEEÿÅÅÅÿöööÿíííÿfffÿGGGÿOOOÿQQQÿ666Î666®___ÿ\\\ÿ]]]ÿ\\\ÿUUUÿMMMÿAAAÿƒƒƒÿüüüÿÝÝÝÿPPPÿPPPÿYYYÿ^^^ÿNNNÿ···ÿùùùÿøøøÿÿOOOÿ]]]ÿ]]]ÿ111¨***veeeÿjjjÿcccÿ\\\ÿtttÿ¡¡¡ÿÂÂÂÿÝÝÝÿôôôÿóóóÿªªªÿwwwÿ^^^ÿVVVÿHHHÿ¡¡¡ÿýýýÿúúúÿ¹¹¹ÿWWWÿkkkÿcccÿ'''e$YYYövvvÿ]]]ÿ¯¯¯ÿøøøÿÿÿÿÿÿÿÿÿûûûÿôôôÿôôôÿûûûÿ÷÷÷ÿçççÿÍÍÍÿ¤¤¤ÿ¼¼¼ÿúúúÿùùùÿÜÜÜÿ___ÿwwwÿTTTò666£}}}ÿvvvÿjjjÿ€€€ÿ   ÿÄÄÄÿéééÿùùùÿ÷÷÷ÿ÷÷÷ÿúúúÿüüüÿýýýÿÿÿÿÿûûûÿöööÿùùùÿúúúÿ{{{ÿsssÿ222‘""" [[[Œÿ………ÿ}}}ÿqqqÿlllÿjjjÿ²²²ÿþþþÿùùùÿïïïÿñññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿýýýÿæææÿˆˆˆÿLLLæ"""""" %%%Ytttÿ’’’ÿ‹‹‹ÿŒŒŒÿŽŽŽÿ€€€ÿÿÿÿÿÿÿÿÿÿ±±±ÿdddÿ’’’ÿ³³³ÿÆÆÆÿ²²²ÿ‡‡‡ÿvvvÿaaaý$$$L """""""""***pwwwÿ———ÿÿÿ‡‡‡ÿŠŠŠÿÿÿÿÿÿÿÿÿ´´´ÿoooÿ‡‡‡ÿzzzÿqqqÿ{{{ÿÿsssþ(((d"""""""""""""""$$$Xcccë“““ÿ›››ÿxxxÿÀÀÀÿðððÿÀÀÀÿxxxÿÿ“““ÿ–––ÿ›››ÿ”””ÿ___æ"""Q""""""""""""""""""""" ":::Ÿqqqôuuuÿ’’’ÿ}}}ÿ€€€ÿ–––ÿœœœÿ™™™ÿÿlllð777— """"""""""""""""""""""""""""""'---s@@@«ZZZÔfffêcccéZZZÒHHH¨---o"""""""""""""""""""þøðàÀ€€€€Ààðøþ(  """""""""!!!!!!+"""„"""Â"""ã á¿"""~!!!%!!!""""""""""""""" &&&ˆ---ö...ÿ...ÿ---ÿ777ÿVVVÿ***ÿ---ñ&&&{ """"""""" ...¯<<<ÿAAAÿAAAÿAAAÿ999ÿvvvÿØØØÿVVVÿ;;;ÿ@@@ÿ---Ÿ """ ,,,WWWÿnnnÿEEEÿLLLÿMMMÿJJJÿXXXÿ²²²ÿ¶¶¶ÿFFFÿMMMÿLLLÿ+++{ ###1FFFüIIIÿˆˆˆÿ•••ÿDDDÿLLLÿMMMÿEEEÿ^^^ÿàààÿvvvÿEEEÿPPPÿCCCõ""""---•FFFÿCCCÿ@@@ÿÁÁÁÿ‘‘‘ÿ;;;ÿDDDÿCCCÿ???ÿÄÄÄÿ¼¼¼ÿ???ÿCCCÿEEEÿ***,,,Ò555ÿ444ÿ***ÿfffÿèèèÿZZZÿ---ÿ444ÿ***ÿœœœÿëëëÿQQQÿ...ÿ555ÿ+++Ã"""ë!!!ÿ!!!ÿÿ%%%ÿÓÓÓÿ¦¦¦ÿÿ!!!ÿÿ{{{ÿüüüÿwwwÿÿ!!!ÿ"""ã999ìCCCÿBBBÿBBBÿ777ÿ£££ÿÞÞÞÿFFFÿ@@@ÿ:::ÿrrrÿûûûÿ«««ÿ888ÿCCCÿ777åCCCÕ]]]ÿYYYÿOOOÿEEEÿ€€€ÿùùùÿrrrÿGGGÿOOOÿkkkÿóóóÿÕÕÕÿUUUÿ[[[ÿ???Æ;;;œmmmÿgggÿšššÿÇÇÇÿáááÿùùùÿÏÏÏÿ•••ÿxxxÿlllÿéééÿòòòÿtttÿjjjÿ666†&&&9lllÿxxxÿ§§§ÿÏÏÏÿéééÿ÷÷÷ÿüüüÿÿÿÿÿýýýÿèèèÿõõõÿÿÿÿÿ   ÿ^^^ù###)CCCŠŠŠÿ}}}ÿwwwÿtttÿÇÇÇÿþþþÿÖÖÖÿÚÚÚÿöööÿòòòÿØØØÿ’’’ÿ333ˆ""" SSS¾“““ÿ•••ÿ€€€ÿµµµÿÿÿÿÿ–––ÿuuuÿŠŠŠÿÿÿEEE¯""""""!!! FFFš………þ‡‡‡ÿÂÂÂÿºººÿÿšššÿ–––ÿ~~~úBBBŽ"""""""""""""""''';CCC˜XXXÑhhhéuuuéfffÎGGG’$$$3"""""""""ðÀ€€€€Àðdarkplaces/progs.h0000664000175000017500000001055213067716222013473 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef PROGS_H #define PROGS_H #include "pr_comp.h" // defs shared with qcc #define ENTITYGRIDAREAS 16 #define MAX_ENTITYCLUSTERS 16 #define GEOMTYPE_NONE -1 #define GEOMTYPE_SOLID 0 #define GEOMTYPE_BOX 1 #define GEOMTYPE_SPHERE 2 #define GEOMTYPE_CAPSULE 3 #define GEOMTYPE_TRIMESH 4 #define GEOMTYPE_CYLINDER 5 #define GEOMTYPE_CAPSULE_X 6 #define GEOMTYPE_CAPSULE_Y 7 #define GEOMTYPE_CAPSULE_Z 8 #define GEOMTYPE_CYLINDER_X 9 #define GEOMTYPE_CYLINDER_Y 10 #define GEOMTYPE_CYLINDER_Z 11 #define JOINTTYPE_NONE 0 #define JOINTTYPE_POINT 1 #define JOINTTYPE_HINGE 2 #define JOINTTYPE_SLIDER 3 #define JOINTTYPE_UNIVERSAL 4 #define JOINTTYPE_HINGE2 5 #define JOINTTYPE_FIXED -1 #define FORCETYPE_NONE 0 #define FORCETYPE_FORCE 1 #define FORCETYPE_FORCEATPOS 2 #define FORCETYPE_TORQUE 3 #define ODEFUNC_ENABLE 1 #define ODEFUNC_DISABLE 2 #define ODEFUNC_FORCE 3 #define ODEFUNC_TORQUE 4 typedef struct edict_odefunc_s { int type; vec3_t v1; vec3_t v2; struct edict_odefunc_s *next; }edict_odefunc_t; typedef struct edict_engineprivate_s { // true if this edict is unused qboolean free; // sv.time when the object was freed (to prevent early reuse which could // mess up client interpolation or obscure severe QuakeC bugs) float freetime; // mark for the leak detector int mark; // place in the code where it was allocated (for the leak detector) const char *allocation_origin; // initially false to prevent projectiles from moving on their first frame // (even if they were spawned by an synchronous client think) qboolean move; // cached cluster links for quick stationary object visibility checking vec3_t cullmins, cullmaxs; int pvs_numclusters; int pvs_clusterlist[MAX_ENTITYCLUSTERS]; // physics grid areas this edict is linked into link_t areagrid[ENTITYGRIDAREAS]; // since the areagrid can have multiple references to one entity, // we should avoid extensive checking on entities already encountered int areagridmarknumber; // mins/maxs passed to World_LinkEdict vec3_t areamins, areamaxs; // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE, PROTOCOL_QUAKEWORLD // baseline values entity_state_t baseline; // LordHavoc: gross hack to make floating items still work int suspendedinairflag; // cached position to avoid redundant SV_CheckWaterTransition calls on monsters qboolean waterposition_forceupdate; // force an update on this entity (set by SV_PushMove code for moving water entities) vec3_t waterposition_origin; // updates whenever this changes // used by PushMove to keep track of where objects were before they were // moved, in case they need to be moved back vec3_t moved_from; vec3_t moved_fromangles; framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS]; frameblend_t frameblend[MAX_FRAMEBLENDS]; skeleton_t skeleton; // physics parameters qboolean ode_physics; void *ode_body; void *ode_geom; void *ode_joint; float *ode_vertex3f; int *ode_element3i; int ode_numvertices; int ode_numtriangles; edict_odefunc_t *ode_func; vec3_t ode_mins; vec3_t ode_maxs; vec3_t ode_scale; vec_t ode_mass; float ode_friction; vec3_t ode_origin; vec3_t ode_velocity; vec3_t ode_angles; vec3_t ode_avelocity; qboolean ode_gravity; int ode_modelindex; vec_t ode_movelimit; // smallest component of (maxs[]-mins[]) matrix4x4_t ode_offsetmatrix; matrix4x4_t ode_offsetimatrix; int ode_joint_type; int ode_joint_enemy; int ode_joint_aiment; vec3_t ode_joint_origin; // joint anchor vec3_t ode_joint_angles; // joint axis vec3_t ode_joint_velocity; // second joint axis vec3_t ode_joint_movedir; // parameters void *ode_massbuf; } edict_engineprivate_t; #endif darkplaces/lhfont.h0000664000175000017500000020356513067716220013641 0ustar kalevkalev0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x08,0x08,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF, 0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF, 0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00, 0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF, 0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF, 0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF, 0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF, 0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF, 0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF, 0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00, 0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF, 0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00, 0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00, 0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, 0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, 0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF, 0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00, 0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00, 0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00, 0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00, 0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF, 0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00,0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF, 0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF, 0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF, 0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF, 0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF, 0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00, 0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF, 0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF, 0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF, 0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF, 0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x81,0x00,0xAB,0xFF, 0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00, 0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00, 0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x82,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF, 0xA7,0x00,0x83,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x81,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xB1,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xB2,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00,0xFF,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x99,0x00,0x83,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x8B,0x00, 0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8B,0xFF,0x89,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x9D,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x8A,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x84,0xFF,0x88,0x00,0x8B,0xFF,0x88,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x9B,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00, 0x89,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x99,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF, 0x87,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8A,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x96,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x87,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x94,0x00,0x8B,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x84,0xFF,0x9F,0x00,0x84,0xFF,0x95,0x00,0x84,0xFF,0x87,0x00, 0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x8D,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x82,0xFF,0x8D,0x00,0x83,0xFF,0x8D,0x00,0x82,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x99,0x00,0x84,0xFF,0x85,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF, 0x8B,0x00,0x83,0xFF,0x9B,0x00,0x83,0xFF,0x85,0x00,0xFF,0x00,0xB6,0x00,0x86,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x86,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x93,0x00,0xFF,0x00,0xB7,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x92,0x00,0xFF,0x00,0xE0,0x00,0x86,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xFF,0x00,0xE1,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x91,0x00,0xF3,0x00,0x85,0xFF,0xFF,0x00,0x85,0x00,0xF3,0x00,0x86,0xFF,0xFF,0x00,0x84,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x86,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x84,0x00,0x89,0xFF,0x82,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x92,0x00,0x8A,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x93,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x00,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x84,0xFF,0x90,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x87,0x00,0x8D,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x84,0x00,0x84,0xFF,0x89,0x00,0x88,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x89,0xFF,0x84,0x00,0x83,0xFF,0x8A,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0xA4,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x85,0x00, 0x83,0xFF,0x88,0x00,0x8B,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x82,0x00,0x85,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0xA5,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x83,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF,0x94,0x00,0x86,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0x85,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0xA3,0x00,0x83,0xFF, 0x95,0x00,0x85,0xFF,0x97,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x97,0x00,0x85,0xFF,0xB7,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x97,0x00,0x85,0xFF,0xFF,0x00,0xE1,0x00,0x96,0x00,0x86,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8C,0x00,0x88,0xFF, 0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x8E,0x00,0x84,0xFF,0x83,0x00,0x87,0xFF,0x93,0x00,0x8D,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x91,0x00, 0x84,0xFF,0x88,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8C,0x00,0x82,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x81,0x00, 0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0x8F,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x8A,0x00,0x83,0xFF,0x8E,0x00,0x84,0xFF,0x8B,0x00,0x83,0xFF,0xA3,0x00,0x81,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x83,0x00,0x8A,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x85,0xFF,0x8A,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x91,0x00,0x81,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF, 0x86,0x00,0x85,0xFF,0x89,0x00,0x85,0xFF,0x8A,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x91,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x92,0x00, 0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x8A,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x93,0x00,0x81,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x85,0x00,0x8A,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00, 0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x81,0x00,0x84,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x84,0xFF,0x8C,0x00,0x87,0xFF,0x86,0x00,0x87,0xFF,0x94,0x00,0x81,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x89,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x87,0xFF, 0x87,0x00,0x85,0xFF,0x95,0x00,0xFF,0x00,0xE4,0x00,0x83,0xFF,0x96,0x00,0xFF,0x00,0xE5,0x00,0x81,0xFF,0x97,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x83,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x85,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0x82,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x83,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x8A,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x87,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF, 0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x81,0x00,0x81,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x00,0x00,0x86,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00, 0x85,0xFF,0x84,0x00,0x82,0xFF,0x83,0x00,0x82,0xFF,0x84,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x85,0x00,0x81,0xFF,0x83,0x00,0x81,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8D,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF, 0x8B,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x8D,0xFF,0x83,0x00,0x86,0xFF,0x00,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x81,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x81,0x00,0x85,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x82,0x00,0x89,0xFF,0x87,0x00,0x85,0xFF,0x86,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x84,0x00, 0x8A,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x82,0x00,0x89,0xFF,0x86,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x83,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x84,0x00,0x89,0xFF,0x82,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x87,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF, 0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xB3,0x00,0x83,0xFF,0xC7,0x00,0xFF,0x00,0xB3,0x00,0x84,0xFF,0xC6,0x00,0x83,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x82,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF, 0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x8C,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x95,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF, 0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x94,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x85,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x83,0xFF,0x9F,0x00,0x83,0xFF,0x89,0x00,0x85,0xFF,0x83,0x00,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF, 0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0xA5,0x00,0x84,0xFF,0x9D,0x00,0x84,0xFF,0x89,0x00,0x86,0xFF,0x82,0x00,0x82,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x87,0x00,0x8B,0xFF,0x87,0x00,0x84,0xFF,0x8E,0x00,0x83,0xFF,0x81,0x00,0x83,0x00,0x87,0xFF, 0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x86,0x00,0x8B,0xFF,0x86,0x00,0x84,0xFF,0x8F,0x00,0x83,0xFF,0x81,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x88,0x00,0x8A,0xFF,0x82,0x00,0xFF,0x00,0xA5,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x97,0x00,0x83,0xFF,0x89,0x00,0x89,0xFF,0x83,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC5,0x00,0x83,0xFF,0xB5,0x00,0xFF,0x00, 0xC5,0x00,0x84,0xFF,0xB4,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x85,0xFF,0x81,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8C,0x00,0x84,0xFF,0x95,0x00,0x83,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x84,0x00,0x84,0xFF,0x84,0x00,0x83,0xFF,0x82,0x00,0x8C,0xFF,0x97,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0x88,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF, 0x89,0x00,0x84,0xFF,0x8A,0x00,0xB0,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x97,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0x89,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8A,0x00,0x84,0xFF,0x89,0x00,0xB0,0x00,0x8D,0xFF,0x87,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x85,0x00,0x84,0xFF,0x82,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x97,0x00,0x84,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x8D,0x00,0x83,0xFF,0x95,0x00,0x83,0xFF,0x8B,0x00,0x84,0xFF,0x88,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x87,0x00, 0x81,0xFF,0x81,0x00,0x81,0xFF,0x86,0x00,0x84,0xFF,0x87,0x00,0x83,0xFF,0x81,0x00,0x85,0xFF,0x96,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x94,0x00,0x84,0xFF,0x87,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x87,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x81,0x00,0x86,0xFF,0x95,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x83,0x00,0x8F,0xFF,0x81,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x95,0x00,0x84,0xFF,0x86,0x00,0x95,0x00,0x83,0xFF,0x98,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x88,0xFF,0x89,0x00,0x84,0xFF,0x86,0x00,0x88,0xFF,0x00,0x00,0x82,0xFF,0x88,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBA,0x00,0x84,0xFF,0x85,0x00,0x95,0x00,0x83,0xFF,0x96,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x8D,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x82,0x00,0x81,0xFF,0x88,0x00,0x84,0xFF,0x86,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0xBB,0x00,0x84,0xFF,0x84,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x81,0xFF,0x81,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x82,0x00,0x84,0xFF,0x86,0x00,0x86,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x85,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBC,0x00,0x84,0xFF,0x83,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x83,0x00,0x8D,0xFF,0x83,0x00,0x89,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x84,0xFF,0x85,0x00,0x87,0xFF,0x8D,0x00,0x83,0xFF,0x85,0x00,0x84,0xFF,0x8D,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xBD,0x00,0x84,0xFF,0x82,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x86,0x00,0x88,0xFF,0x83,0x00,0x83,0xFF,0x84,0x00,0x84,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x86,0x00,0x84,0xFF,0x8B,0x00, 0x84,0xFF,0xDD,0x00,0x84,0xFF,0x81,0x00,0x95,0x00,0x83,0xFF,0x88,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x89,0x00,0x81,0xFF,0x87,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x8C,0x00,0x83,0xFF,0x87,0x00,0x84,0xFF,0x89,0x00,0x84,0xFF,0xDF,0x00,0x83,0xFF,0x81,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB6,0x00,0x87,0xFF,0x9A,0x00,0x84,0xFF,0x87,0x00,0x84,0xFF,0xE6,0x00,0xA2,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0xB7,0x00,0x85,0xFF,0x9C,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0xE7,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00, 0x8B,0xFF,0xFF,0x00,0xE1,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x89,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x89,0x00,0x83,0xFF,0x89,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0xC3,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x87,0x00,0x8B,0xFF,0x83,0x00,0x8A,0xFF,0x8A,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x85,0x00,0x89,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x88,0xFF,0x99,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x96,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x93,0x00,0x83,0xFF,0x83,0x00,0x8B,0xFF,0x8B,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x94,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00, 0x83,0xFF,0x87,0x00,0x83,0xFF,0x88,0x00,0x88,0xFF,0x89,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x88,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x89,0x00,0x88,0xFF,0x88,0x00,0x86,0xFF,0x84,0x00,0x83,0xFF,0x81,0x00,0x83,0xFF,0x85,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x84,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x8A,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00, 0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x90,0x00,0x84,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x8F,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x91,0x00,0x83,0xFF,0x85,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x83,0x00,0x83,0xFF,0x87,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF, 0x81,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x84,0x00,0x89,0xFF,0x86,0x00,0x85,0xFF,0x87,0x00,0x8A,0xFF,0x84,0x00,0x8A,0xFF,0x87,0x00,0x86,0xFF,0x85,0x00,0x8B,0xFF,0x84,0x00,0x88,0xFF,0x85,0x00,0x89,0xFF,0x86,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x95,0x00,0xA9,0xFF,0x82,0x00,0x81,0x00,0x85,0xFF,0x8F,0x00,0x85,0xFF,0x85,0x00,0x87,0xFF,0x87,0x00,0x85,0xFF,0x87,0x00,0x89,0xFF,0x85,0x00,0x89,0xFF,0x89,0x00,0x85,0xFF,0x85,0x00,0x8B,0xFF,0x85,0x00,0x87,0xFF,0x85,0x00,0x89,0xFF,0x87,0x00,0x87,0xFF,0x87,0x00,0x87,0xFF,0x98,0x00,0xA5,0xFF,0x84,0x00,0x81,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xB8,0x00,0xA1,0xFF,0x86,0x00,0x81,0x00, 0x8B,0xFF,0x83,0x00,0x8B,0xFF,0xFF,0x00,0xE1,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00, 0x85,0xFF,0xA7,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF, 0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x87,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x85,0x00,0x83,0xFF,0x87,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x86,0x00,0x83,0xFF,0x88,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x89,0xFF,0x89,0x00,0x83,0xFF,0x8B,0x00,0x83,0xFF,0x85,0x00,0x91,0x00,0xAB,0xFF,0x82,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00, 0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x87,0xFF,0xA5,0x00,0x92,0x00,0xA9,0xFF,0x83,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x85,0xFF,0xA7,0x00,0x93,0x00,0xA7,0xFF,0x84,0x00,0x8D,0xFF,0x93,0x00,0x89,0xFF,0x84,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x83,0xFF,0xA9,0x00,0xC1,0x00,0x8B,0xFF,0x95,0x00,0x87,0xFF,0x85,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x83,0x00,0x8B,0xFF,0x93,0x00,0x8B,0xFF,0x93,0x00,0x81,0xFF,0xAB,0x00,0xC2,0x00,0x89,0xFF,0x97,0x00,0x85,0xFF,0xFF,0x00,0x94,0x00,0xFF,0x00, 0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0x52,0x55,0x45,0x56,0x49,0x53,0x49,0x4F,0x4E,0x2D,0x58,0x46,0x49,0x4C,0x45,0x2E,0x00 darkplaces/darkplaces-dedicated-vs2010.vcxproj0000664000175000017500000003132713067716216020561 0ustar kalevkalev Debug Win32 Release Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9} darkplacesdedicated Win32Proj Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(ProjectName) $(ProjectName) Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) true Console MachineX86 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true Console true true MachineX86 darkplaces/sound.h0000664000175000017500000001050713067716222013471 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SOUND_H #define SOUND_H #include "matrixlib.h" // ==================================================================== // Constants // ==================================================================== #define DEFAULT_SOUND_PACKET_VOLUME 255 #define DEFAULT_SOUND_PACKET_ATTENUATION 1.0 // Channel flags // These channel flags can be used for sound() builtins, with SOUNDFLAG_* names #define CHANNELFLAG_NONE 0 #define CHANNELFLAG_RELIABLE (1 << 0) // send as reliable message (only used on server) #define CHANNELFLAG_FORCELOOP (1 << 1) // force looping even if the sound is not looped #define CHANNELFLAG_LOCALSOUND (1 << 2) // INTERNAL USE. Not settable by S_SetChannelFlag #define CHANNELFLAG_PAUSED (1 << 3) // pause status #define CHANNELFLAG_FULLVOLUME (1 << 4) // isn't affected by the general volume // ==================================================================== // Types and variables // ==================================================================== typedef struct sfx_s sfx_t; extern cvar_t mastervolume; extern cvar_t bgmvolume; extern cvar_t volume; extern cvar_t snd_initialized; extern cvar_t snd_staticvolume; extern cvar_t snd_mutewhenidle; // ==================================================================== // Functions // ==================================================================== void S_Init (void); void S_Terminate (void); void S_Startup (void); void S_Shutdown (void); void S_UnloadAllSounds_f (void); void S_Update(const matrix4x4_t *listenermatrix); void S_ExtraUpdate (void); sfx_t *S_PrecacheSound (const char *sample, qboolean complain, qboolean levelsound); float S_SoundLength(const char *name); void S_ClearUsed (void); void S_PurgeUnused (void); qboolean S_IsSoundPrecached (const sfx_t *sfx); sfx_t *S_FindName(const char *name); // these define the "engine" channel namespace #define CHAN_MIN_AUTO -128 #define CHAN_MAX_AUTO 0 #define CHAN_MIN_SINGLE 1 #define CHAN_MAX_SINGLE 127 #define IS_CHAN_AUTO(n) ((n) >= CHAN_MIN_AUTO && (n) <= CHAN_MAX_AUTO) #define IS_CHAN_SINGLE(n) ((n) >= CHAN_MIN_SINGLE && (n) <= CHAN_MAX_SINGLE) #define IS_CHAN(n) (IS_CHAN_AUTO(n) || IS_CHAN_SINGLE(n)) // engine channel == network channel #define CHAN_ENGINE2NET(c) (c) #define CHAN_NET2ENGINE(c) (c) // engine view of channel encodes the auto flag into the channel number (see CHAN_ constants below) // user view uses the flags bitmask for it #define CHAN_USER2ENGINE(c) (c) #define CHAN_ENGINE2USER(c) (c) #define CHAN_ENGINE2CVAR(c) (abs(c)) // S_StartSound returns the channel index, or -1 if an error occurred int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation); int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed); qboolean S_LocalSound (const char *s); void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation); void S_StopSound (int entnum, int entchannel); void S_StopAllSounds (void); void S_PauseGameSounds (qboolean toggle); void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx); qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value); void S_SetChannelVolume (unsigned int ch_ind, float fvol); void S_SetChannelSpeed (unsigned int ch_ind, float fspeed); float S_GetChannelPosition (unsigned int ch_ind); float S_GetEntChannelPosition(int entnum, int entchannel); void S_BlockSound (void); void S_UnblockSound (void); int S_GetSoundRate (void); int S_GetSoundChannels (void); #endif darkplaces/darkplaces72x72.png0000664000175000017500000001560313067716220015521 0ustar kalevkalev‰PNG  IHDRHHUí³GJIDATxœå|gt×yösgg{°(‹Ee ‘Ã"ŠM•¢ò)´#ÉJ¢Æ¨Yq71Q¾Ï±-;‰cÅV"[²%G²Ƕ,Y²œ8’%K2-“bA¤ØDÝ°‹²evg§Þü¸3ÀhVžó½çÌaÁî;Ï}Þþ¦IAàfÎœ¹°¤¤dy__ßû*€$€,½¹¹™N×^ÎG¸i¼—Ãn·_;þü‡ øÀ*uüxAÈ4î圅ŸÆ{ÙSC‹u]Ÿ¡iÚF€“IÓ¸Ÿs’icP8ö(yɽêª%—ËÕà*óŠðâ e‘m:n" ƒõ-‡Ž|.qª½2­èD’$ À€€‹Å®([4] "”R¾Á?r (¥v0öD8¦q?ç,Óµ!Ž·Ùš(ߚ̛7Ϥ@9 ºÒÔlºâ¡«Õ-½ý¡|ÝbȲ̹\.Ìö„¸¦q?ç,Óµ!'¡4´ üeX³f åyž¨ŒA@Wƒ¦ËÍ;SýB@•¶Ãäž/ýƒòÒK/QŠÁ@r›û¨@Ýl·QÖž Xh  è¥8/;@æCº æÂ~€l¸å°—””¡¡!Œ=%|ì`vˆpÔÖÖÖrçç8®ÌápT-L “L:™´¹\#ªªÄb±>°ˆ< '‚@`\ °¦ƒA¤ºº:¢µÅõ.Ðhë>R*¬çÁN`Þ¬,¢vàfTUU—„Ãk\‹ôÉTqU9øëzú»¼­îáîåöµ´ g‘ }F 2J]¾ªÍqHÕÖx<Þ  e‚¥á"˜5q„ÒÆ|oOÙ{y?'ÅX BÜn7'I’ã9pÑhg4Ú ÀŽ‘‘y¡P(Ô•QZ ØS9{AÉN»-ï çg}ÎÃ]ůg0sWÝ®ŒŒ:kjvôööîp @/€aYA´ææfã|7ÙÅH$âðú|7¾}¸ý&äɧž"%%%´íøqc``À–Ëå4CNè³Cv°ä$IB.—ã$IÒ%EåÒ’Ì)Eüv£’Û›uèÛzGÉÌÁ>ÏÝëæ¾|×5s¥í«9›=âp8ø\.GÀX¤E"#‰ÐX,vÎûŸ€œA¿¹ÑÞ~ýÀ]¿þZdGGÈ7ž~n·[’$‰0 à؉§Àlˆ–§‰æeÙ™€œ¢(ª¤p—–:JƒA­]ƒ6?¶ßA]Aþ¡M+=]¤¤6OÌu:î\.gt€n‚tN*7¹Š‚þkŽ´µoÈäá$«¯Ý@3éNê4L€R`u{  ,€C` ‹šÆ¤EU}Á »Á/llô¶÷Dçó<Ìf³Ô\W D"ã\@º¬qéÁxPpö_¤qÁ| `ÀT‰€g7NO7t°\m,_ëРÀ~àWþGÅ÷3¹üÑc¡Fî=wƒÜ–‡¶ó+ƒG¹öÒwýåååC¡Ð'Ü ,zw ‚ðŸÿ’é10˜¡uVTT8 !n›ÍVC+UÌ8EÎ+5 †a 6Ç\’‚d©‡Å&˜áíÐà€§RiQ¨­«]ZÁë/;e+ùÚ7ÈßÝ°ÈöÃþüñǶ´´¸FGG]æÀ òÙŒ÷E$‚€«¦¦z&¡zÃHbxem¤bq0=~Üíø+l¼Ó'¶ €D|.€%¯…ô¶˜4I¸ejÞÇ(ø¬ Æ.L%‡ÀX6˜L¦’¾²ªõõ’JvuÙgl¾Ÿô¤ïÆ“O>éÛ¼yóBÃ0ŒT*e©¯ `Teª0à‚m ö¥K—ËËŠVÕ:ÔÇÚ÷¶~aô`ËŸíîÌãiØùÞïʹl ™Jy“ƒqÅ62âpäoÝÊóxùÇ?¢#QÏçóL:Ì+ ‹ÅN;ÕX,f]4‹ÑH$b€1Ló~#2¯ºxnFG4‡bðKË8Ì\±žPJõÖÖÖÒ|>o3Ác¥6•w;o€A°-]º4PQZ=‡ |Åw¼å ë¼mWfƒ/'a/--UyŽ5óçç‹Š‹ÝÇ¡¸¤ØÙÓÝ š¢Ý{š©§´ƒƒº$IÄ|°v0u™ ‰b‰D,U*ŸÏç‹JB®“Ÿs‡kØö¿|Ÿ<ðØçèÂ¥Ëù;vÐl6[¤(Š ¦ª#`lÔ'3Úçe¤ApÕÔT5–:ñµ`Û®zZ÷þq¹8âÿþ»ÀÉ2‹æÍ¥‹šš¸âª%•ÉøLÏ¡Û8NõÈ‚õ×âëÏ?G‚%Ū®ëL…Œ‚뼤¹¹™š6DcQ€–ááámÁpåÑ2EÊ.ø»Ïý5€^xAçy¾Àb°Šf-Xšc›¬ÔrN ‚@AðT…K¯Þ»kÏOûË7þrùÂÙÕ ÷~ƾ'ÃSïò¦|ÿªµÞ’p¥šEg&“ñ€h@Ü \²@@ï» PQÕd2i%™VÂyA Y@a¼¶°ßæp½WYZªñÞüÞ;F;]³fÍ€Ç㉘ ÍP†)Ê- UßìHù£Íž .ÜÙ÷¢¼²BQ¯ZK“ÞR>••dQ]&0¢¹ÉcØx{4abÞD×uXza©‡ûX¬º2z†††öFêjk€Z žo>x‹ÈÊ•WSžçÝf‚¹}«.~‹Î…A®¦¹ ›WJÑoÝäŠ7ÜçŽÇ@÷ìÚ#þí·ÿCÎêÔE‘K:%0½nð!€ßø=Ç‘Þ€v4=4(2M[ëòæe±è‚¥¤Q6—gÇú¦Yz/@ïýêG1ݲe _^^®(0@=XuÓŽ õ¨³$‚£¶¶v~÷æ/þ÷îƒ~qÞb²õí_ãdÖ²µŽ@YÄk‚£ZÀ!;¼еØl\Â\’|çù燓TÕÔ¥ *t»Ì  s½ÁÁÁÁá9Mƒ»u Y[ %àv»y—ËåP `XnLp\S$©®®®ýèÀ¾¯ýîXG=导ð mZ¿ [{LÑuÝ!Šbì´r0#€ívØà8€>ÞfëøZ f ˆq¶è&8úÅ‚S €4€N¯ÛPï1F’€Ü²Àºuë(!ÄVI¨³CnLP³) ‡Ãn]ÊlÐ2¹Õ¥€ãñý2ì6¡G2±þ~G<—Àô6 à €=šÐ¦n"‘çPrx÷nàtÓ ¿hcª¦JJÊ®ÃCùÙEÀ?ý €-[¶dÝn·f*ÀäcÐØþ&¤AàÇœö?ü¼<<ìÿ› >lú‹'[·nå¢Ñ¨ †v,n9Ƙƒ:Áôß*VÙx‡c¨·®.oëîö:9 §­ £Ãñ&·7—²djÈCO¶¶çhÂd÷GqûÇqœ Œ$E` ò`BÙw*¹9%{sõh|N òôû€Þôôôx³Ù¬L%’`Á] €ÃÏƳ0K ŒîîîŽ!J•@ò›wðþž=´8Tž>Ëý/¥(ÄfKȨüómó­ðRjð`Œñ‚©šfÙ×úòD"‘ð‰[î[…mUC˜Ì^¼ ðoÏ~'›H$ó£˜Ý9 ¦R]`àä0^D·ðÔTEæ†ÁUäïÿüF¨}$ÖÛÃ9NÃ܃ͼ¬\ì’t6,5Ó š|dmƒ¶¢p¦‡¬µ‰Â¢x,òD,;4Ù :J¾6ÌõÔn©v¼õ€t:®®.¯Ù‰PÀØs ¬Ñ fo,µâx*++kaÙr¢ç“}}}å‘"*Øeüé·Ó%‹ÝuuuªÇã±N0ˆ‚²ë%l Rjü÷··sŸGr.Æ %NÝnuN0õ²:+S3€§ïÄÑÛæ/^茻|À¬•^|ñEmhhH3?“+7œcNŒ9–›æÜО۷oß+G[÷ßý©ÿjU+Ö/ï?®ûGß~ åNÐb‡A#‘H€Y`a“Ä##váçr„%|õPÀQ›ƒ­o+Ù1ÁÍŸf¤A ~Ÿ/Ôänò7ÿ}tE#¹gƒ@o¼áH2O”t:mmÚŠ”»ÁŒt­AHYí¬]þòªjUUF¿ýò«ÜÕk×:{d]éuÔØ®»®ŒÊpR‚Ê2ñðºh‹hÛÁX™  ‚`©ê”rÝ ›¡i••9£©¸mËÝ $“ÉQu ŠqÕ>MÅ'z1¢iZÝKG}ÏÅAÞ|à/ðüŠeäco¥ªËg‘S/«ô9 sƧp³==͇݇¶\§O’$Ç„ ð@’ÁÆz°ÈÖfèÕI˜š÷“1ÞËcêÖŽ#52¼ò'à È<÷SòØß/½ôSƒRÃú¼fÞËÀ„™]ϧgÕf2äþU KÝ}Ä»h+}wÏ~ãÎ;ï´:u ÷^q0»“+X¼P†ìódsÀܨLß­Xƒš›Ëz\Ï@,¶™s8o½ùæMú«¯¾JÐùóæÁérC–‘ââbBA6›M¿þúëß«µ™÷²‚Ö1„ÃaŸØ×UÚÐïß^MVÜu-=ñÊ¿áÁ¬nè†fb`U+­a®1ÎÈHç~=ñ™“1›§J¥ Kšàˆæ¿'v1U0vYù°:°ÌcX6ÆHq{ý®ú™³—ô÷t6þäé§]`i5Ÿ·D‘ð¸°_ÉÎZ´ÇÛÚ5Îf³¸À/Ì’¨‚ LÜçt:gfFÝÓ®÷¢å¿^#‡ÔFÒ9{N’òæ>ræ>ʼn‡= gJ3Êv·æpdˆm{_¨xw?ð5k7Vë$m^–KŸÈjþ,jÞ´ã Œ»t«D‡‡‡KdjfÏõ¤e­.ÜÝmoK›>ý8þßOfü¡Œ[?qRñ˜ßñƒ•*N˜÷É`<¼{¯–ÛP™²•Ô£ZNÛÏ¿°7}ÍÝ0×(l+Yj=%ƒ8•ãôgXÞôú#KÉ Ÿ|”Çq–!SÌ…$˜ì™°LC­aœ¶Iœ^”Ÿè=9°@MŠÇãþHyYÍîînzô¨F:nÍ»÷µ¶"‘HøÍûkæÉWÙÓÖ4Õ«ôàþ–Mj ÞÁaÐÞØFÖ.¨§×®˜ç³²¹Ÿ<Æ+‹Ysí1&NÜ()rñɹ€X÷Äa‚B8ŽÆOÜʾ­®ÃƱ ì`š3èV‘½ðJ€yÄì&²×šÈ Ý(îžÛè3”¸H76VZv‚RJ-ÏSØŽ9l}F?Kðgµ‡œÕ3hÔ´SÁ` F&“±XÙ–ÿµc\½Æ ´ ¶êêêRËÞ-ˆ­í vôÙßÛÕ}\XëOfí`ŒÎ›àôž+@*Ç‘ÇC–•‹¤Ñï´6Ϲ\.ë­Ô༹³Ä,ÔyÉÕ ¢ ^ö3òàƒ’7ß|ÓºŸ• wƒÙ¶!ó{º¹À¯ˆéÏîíîozæ¡ÛÉ_¿ð~ñ=Om£î°CìéSæ=(ð„…ûœ¨bŠÍf§ ¯’É‚¶=@¡Y1Íùý~+Oâ1u^²d³@%e€£hžýˆ€ac9”ù )ó²æt‡Ã¾U«V}êÞþøwòcG¿§”~ý‰/â½÷ÞU;¹"ŒŒŒšË"V¦±Œ| ´Ólê*–ÎfcÅ9øJ7Š×tgˆø…­D<Þ‚\†].—/ŸÏÛÁâ7.1H&F0è3ü8 (Pq+Á´la¡u»œö­¼ûégžy&ŸÞµ“|ü¦MøùÏ_Ñ’iÑ?΃1æXº4ˆIÔ “<œ ]QUÛR ä?°tî\úÈg>1Úé+..Á\õi‰%.@ÝÎ& µÅ]㬠rZ‰Æò†… ùêkªn5zÛþñÛßxªr4llš!}þ¯’8é–$‰5V·:fþ9©zY7+@Úær©Ÿ=ï×O*ÖÝŽmG{91•ôx<'Ødªø]jáĉ­ÆÏ‘*¯Ëzxtª-Y%g8Ï®?Üu¨å©E³# Ç[ñÄWŸ%Ù«n;|L㥶4ÆÛBÝŽ€•„c(È%'îe²£Ñè!¾¸¨g €•·ß‡TâÄ=¸zI“Áqœ,m¨+Sz0EÓíB$ó±h¿çŒWF˜’¥{z¬A‡Â};ËÊÊ‚5‘È­ã£W¢ÿõÆ7{OöT5ïo# Šüêþß½£?ô·ï?ÒéÊf³V×EóXGÀBŠn°°ãŒ\rR€ÌäôùËÂ;RÕÕé5ý˜wuW-Ã?}ë){MU¥ÔÍ+vaB å"„ØíöòÞþþ(àÏnº‘6ÕÕAˉc{öûýÞÒ’b!?”øÒö;t}¦{õ²2Üv¤Tí‰Wß±IžttõHÕÏ´ ÈA°òp;˜íÉP§šð8£7oöº91›ãëËC‹G»{]oÅà :¸ç6"õÇt£¨Ü©j üúÁ<€l%]0:‘H„ƒÁ™½½½÷d³Yç'6ߊlV$ûº{P‰P°ÈðH^Î×´ïܵ4×Ó»¤O’\Ç2 Ë–—k#%u$TSKEYU3™Œåi%s`¥áf0u[ûëËOº§ÉN‚±vø‚ïr ügnm2–«]øåþfºqžÓQL3ðûý ̳Gg´L.@¥ÔA ?øÖ¿âWïï ï¿ñ¶ïÚwÞyG»ãÿÜjì޾ݖv¹j}½º¢±A©_±XÙCu>XžOf²¼Ùá•ÁÔ§lèj'Xu¡ã€v¶šÒ] 3’DãñøÞªY3–,X2s‰;ìýË›‰vªk«èGî*®·/:?“Éô±ÈÊ„ó¸ðÎ`+#7ß}q×Í’E\Í56õõ·MW¯[祔 ”PJ‘—eäE‘‚¸„ñÉ´˜o«(t©Uê\À¦¶VëöXthä½?ÚדÓÒE?{XpËíüMov¸ÝîÊ@ ° ìíÁY`áç4Ú6%B„8Fd™Þ°j%ýæ—¾tów¢¢®Ù|^Åbl.gä$IËçó²,˘g²rªS`vf'€ßØ`7˜Qî1Ÿ+³ØB™´/f²Èjîëëë ÿ,3|{”Î*Y‹“œãµ'¹»þågöDlüûv5ò<¯ŒŒŒXÝŽ6Ã& ÏwÚà*Ký$~ªŒ²à™€Ýn˜'R1^¿Éšk4j‚K!b`i‰•©[íísÞ×ÙFð X¶ø`0%ÊgÔÝ {'NRiº.°ìºÕŽ_þö=ýÎ;çy·¦i®V¯9–xæA8ïvEt×¾@×ÿÍWÆêʼnDBs*2ÆÖk"X,“4Á+= 0ç51å„Y,ƒ9æ¦ÂÌw¤¼Lt_IénÅŠSª<>nϵ|È=úàÚö£½%„¹¼ÃéËår…*¦E"‰D‰Dp6/‰DlÁ`p®¢j·ÛìNÃéÆ-û€—_~™Š¢È™@tØàX_®ã–>0YãuçÍšB9ëgss³aÅ£`îQK¥R¹@¨ôêÅ%ų~¦FdíÍpמûìÝ~îßžý^ñ¯Çnimm]–J¥>Ðu}O<ïcSŒQVÁk¬d2±\›ê×v~tÂÊUcÿi¦ÖÃ(˜›.Œe´‚u­µ'[ÿ¼äºdÓmÛÀº•`ŠUæ5/àõ=š¤w%RvÇ訾K¨›¶~Ñ;”×2Ç3b¢?zJ–äât~H)à8.Íqœ¤ªj4S¼ù06Knûó_>É£O}Ÿ Ï<óŒñÖ[oÑD"AÀÔk€7ÀâšxÁ÷dž.ÕkQp Ø4Ø:Æ{a2ÍÒÙ\c¨, ‡9„önc¶òâkoINª8ò²V,%’Kô€å¡Ðâòy v»=5<<|”RzÌÛXá¾H>SþÕ¿ÿ€<úÏßN"˲.I’aî×jÑXSó*˜»¾¨Á«©äœæ¤ @Ê©KŒæQ0ÝoÌårÖ’Çãñð2uâ"Ä^T; ‰çï?xÐ*W‚1×Á˜ÈÞÝ1oF Éítù$Jin4N9Ž³š|²¹³–|/•œó°i´ ŠZcsƃ` ÅÍ‹ªªæ4M“UUUÌ+¯(ŠÎpÁg‡0î…¬n‚3XT\ÿúSW«Šâ dS䣓¼(ÉY–­vÒQ0O9S½.&Å9›œ×¤½UcÁ¹•ÁB~0ƒYÆŽJ°’HXìb¼ÕpÀ8¨N·2á¸áP(dt‹"Vl¼™ùù/Hº«0›Œæ=­Fá%y³p*¹ WLïf±IÅ83úÀŒyмJ2ÀÀHƒ=` d h ©··÷øœŠ`úEW_»&óä³ÏyÁØn‰¢¹Ö¥Ù›T.ø] l²ŠY0ƒë€ùR‹ùwë>ÖçÆ^KBAÙ´ÀÖ%uÞÿæª*þñëvQ­½5{mut¯\€,)híæÃ)`ôç .k`a²Éú‰±Š é/©Ü=-‹\<1Ìžg®kEÇÖÛe•KúÎê° ëÅ…NøÎÄŸåg“V׃8tÄz¯Õ0°zè–×»²4•L|é<%Ïñönïâës zóù¼õΘP“´h.‡\q¿ Â*ûöôôtéíÏ9.ë=K½Nš¸Ü¿¹êŠÈÀ¨Óîh²\z ,n²š…—Ý@Óû¨ÎGtÃ¥áð¡ŒL`åÜ°ÀÐJKþ¿ˆOņ·išæ±Ùl!ŒgðÖTÛ´4-¿ê|ÅLkƒã8«r«÷D1^p¿l)†%W*ƒæ¥ÁìäXÀ™3Ò*.Ãoz™Lþ¼¥$Ȧ ­²IEND®B`‚darkplaces/darkplaces-dedicated-vs2015.vcxproj0000664000175000017500000004405613067716220020564 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {389AE334-D907-4069-90B3-F0551B3EFDE9} darkplacesdedicated Win32Proj darkplaces-dedicated-vs2015 Application v140 MultiByte true Application v140 MultiByte Application v140 MultiByte true Application v140 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console MachineX86 true X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console MachineX64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX86 true X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Console true true MachineX64 /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/svn-eol-style-from-gitattributes.sh0000664000175000017500000000052613067716222021076 0ustar kalevkalevfor F in *; do exec 3<.gitattributes while read <&3 -r L S; do if [ -z "${F##$L}" ]; then s=$S fi done case "$s" in '-diff -crlf') svn propdel svn:eol-style "$F" ;; '-crlf') svn propdel svn:eol-style "$F" ;; 'crlf=input') svn propset svn:eol-style native "$F" ;; *) echo "UNKNOWN: $s" ;; esac done darkplaces/collision.h0000664000175000017500000002406113067716216014337 0ustar kalevkalev #ifndef COLLISION_H #define COLLISION_H typedef union plane_s { struct { vec3_t normal; vec_t dist; }; vec4_t normal_and_dist; } plane_t; struct texture_s; typedef struct trace_s { // if true, the entire trace was in solid (see hitsupercontentsmask) int allsolid; // if true, the initial point was in solid (see hitsupercontentsmask) int startsolid; // this is set to true in world.c if startsolid was set in a trace against world int worldstartsolid; // this is set to true in world.c if startsolid was set in a trace against a SOLID_BSP entity, in other words this is true if the entity is stuck in a door or wall, but not if stuck in another normal entity int bmodelstartsolid; // if true, the trace passed through empty somewhere // (set only by Q1BSP tracing) int inopen; // if true, the trace passed through water/slime/lava somewhere // (set only by Q1BSP tracing) int inwater; // fraction of the total distance that was traveled before impact // in case of impact this is actually nudged a bit off the surface // (1.0 = did not hit anything) double fraction; // final position of the trace (simply a point between start and end) double endpos[3]; // surface normal at impact (not really correct for edge collisions) plane_t plane; // entity the surface is on // (not set by trace functions, only by physics) void *ent; // which SUPERCONTENTS bits to collide with, I.E. to consider solid // (this also affects startsolid/allsolid) int hitsupercontentsmask; // deliberately skip surfaces matching this mask (e.g. SUPERCONTENTS_SKY allows you to bypass sky surfaces in q1bsp/q2bsp which are SUPERCONTENTS_SKY | SUPERCONTENTS_SOLID) int skipsupercontentsmask; // the supercontents mask at the start point int startsupercontents; // the supercontents of the impacted surface int hitsupercontents; // the q3 surfaceflags of the impacted surface int hitq3surfaceflags; // the texture of the impacted surface const struct texture_s *hittexture; // initially false, set when the start leaf is found // (set only by Q1BSP tracing and entity box tracing) int startfound; // if startsolid, contains the minimum penetration depth found in the // trace, and the normal needed to push it out of that solid double startdepth; double startdepthnormal[3]; } trace_t; void Collision_Init(void); void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture); void Collision_Cache_Reset(qboolean resetlimits); void Collision_Cache_Init(mempool_t *mempool); void Collision_Cache_NewFrame(void); typedef struct colpointf_s { vec3_t v; } colpointf_t; typedef struct colplanef_s { const struct texture_s *texture; int q3surfaceflags; union { struct { vec3_t normal; vec_t dist; }; vec4_t normal_and_dist; }; } colplanef_t; typedef struct colbrushf_s { // culling box vec3_t mins; vec3_t maxs; // used to avoid tracing against the same brush more than once per sweep int markframe; // the content flags of this brush int supercontents; // bounding planes (face planes) of this brush int numplanes; colplanef_t *planes; // edge directions (normals) of this brush int numedgedirs; colpointf_t *edgedirs; // points (corners) of this brush int numpoints; colpointf_t *points; // renderable triangles representing this brush, using the points int numtriangles; int *elements; // texture data for cases where an edgedir is used const struct texture_s *texture; int q3surfaceflags; // optimized collisions for common cases int isaabb; // indicates this is an axis aligned box int hasaabbplanes; // indicates this has precomputed planes for AABB collisions } colbrushf_t; typedef struct colboxbrushf_s { colpointf_t points[8]; colpointf_t edgedirs[6]; colplanef_t planes[6]; colbrushf_t brush; } colboxbrushf_t; void Collision_CalcPlanesForTriangleBrushFloat(colbrushf_t *brush); colbrushf_t *Collision_AllocBrushFromPermanentPolygonFloat(mempool_t *mempool, int numpoints, float *points, int supercontents, int q3surfaceflags, const texture_t *texture); colbrushf_t *Collision_NewBrushFromPlanes(mempool_t *mempool, int numoriginalplanes, const colplanef_t *originalplanes, int supercontents, int q3surfaceflags, const texture_t *texture, int hasaabbplanes); void Collision_TraceBrushBrushFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); void Collision_TraceBrushTriangleMeshFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); void Collision_TraceLineBrushFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const colbrushf_t *thatbrush_start, const colbrushf_t *thatbrush_end); void Collision_TraceLineTriangleMeshFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, int numtriangles, const int *element3i, const float *vertex3f, int stride, float *bbox6f, int supercontents, int q3surfaceflags, const texture_t *texture, const vec3_t segmentmins, const vec3_t segmentmaxs); void Collision_TracePointBrushFloat(trace_t *trace, const vec3_t point, const colbrushf_t *thatbrush); qboolean Collision_PointInsideBrushFloat(const vec3_t point, const colbrushf_t *brush); void Collision_BrushForBox(colboxbrushf_t *boxbrush, const vec3_t mins, const vec3_t maxs, int supercontents, int q3surfaceflags, const texture_t *texture); void Collision_BoundingBoxOfBrushTraceSegment(const colbrushf_t *start, const colbrushf_t *end, vec3_t mins, vec3_t maxs, float startfrac, float endfrac); float Collision_ClipTrace_Line_Sphere(double *linestart, double *lineend, double *sphereorigin, double sphereradius, double *impactpoint, double *impactnormal); void Collision_TraceLineTriangleFloat(trace_t *trace, const vec3_t linestart, const vec3_t lineend, const float *point0, const float *point1, const float *point2, int supercontents, int q3surfaceflags, const texture_t *texture); void Collision_TraceBrushTriangleFloat(trace_t *trace, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, const float *v0, const float *v1, const float *v2, int supercontents, int q3surfaceflags, const texture_t *texture); // traces a box move against a single entity // mins and maxs are relative // // if the entire move stays in a single solid brush, trace.allsolid will be set // // if the starting point is in a solid, it will be allowed to move out to an // open area, and trace.startsolid will be set // // type is one of the MOVE_ values such as MOVE_NOMONSTERS which skips box // entities, only colliding with SOLID_BSP entities (doors, lifts) // // passedict is excluded from clipping checks struct frameblend_s; struct skeleton_s; void Collision_ClipToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, float extend); void Collision_ClipLineToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitsurfaces); void Collision_ClipPointToGenericEntity(trace_t *trace, dp_model_t *model, const struct frameblend_s *frameblend, const struct skeleton_s *skeleton, const vec3_t bodymins, const vec3_t bodymaxs, int bodysupercontents, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask); // like above but does not do a transform and does nothing if model is NULL void Collision_ClipToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, float extend); void Collision_ClipLineToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, float extend, qboolean hitsurfaces); void Collision_ClipPointToWorld(trace_t *trace, dp_model_t *model, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask); // caching surface trace for renderer (NOT THREAD SAFE) void Collision_Cache_ClipLineToGenericEntitySurfaces(trace_t *trace, dp_model_t *model, matrix4x4_t *matrix, matrix4x4_t *inversematrix, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); void Collision_Cache_ClipLineToWorldSurfaces(trace_t *trace, dp_model_t *model, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); // combines data from two traces: // merges contents flags, startsolid, allsolid, inwater // updates fraction, endpos, plane and surface info if new fraction is shorter void Collision_CombineTraces(trace_t *cliptrace, const trace_t *trace, void *touch, qboolean isbmodel); // this enables rather large debugging spew! // settings: // 0 = no spew // 1 = spew trace calls if something odd is happening // 2 = spew trace calls always // 3 = spew detailed trace flow (bsp tree recursion info) #define COLLISIONPARANOID 0 extern cvar_t collision_impactnudge; extern cvar_t collision_extendtracelinelength; extern cvar_t collision_extendtraceboxlength; extern cvar_t collision_extendmovelength; #endif darkplaces/.travis-before_install-xonotic.sh0000775000175000017500000000346213067716216020571 0ustar kalevkalev#!/bin/sh set -ex export USRLOCAL="$PWD"/usrlocal mkdir "$USRLOCAL" for os in "$@"; do git archive --format=tar --remote=git://de.git.xonotic.org/xonotic/xonotic.git \ --prefix=".deps/${os}/" master:"misc/builddeps/${os}" | tar xvf - case "$os" in linux32) wget https://www.libsdl.org/release/SDL2-2.0.5.tar.gz tar xf SDL2-2.0.5.tar.gz ( cd SDL2-2.0.5 export CC="gcc -m32" i386 ./configure --enable-static --disable-shared --prefix="$USRLOCAL" || cat config.log i386 make i386 make install ) ;; linux64) wget https://www.libsdl.org/release/SDL2-2.0.5.tar.gz tar xf SDL2-2.0.5.tar.gz ( cd SDL2-2.0.5 ./configure --enable-static --disable-shared --prefix="$USRLOCAL" make make install ) ;; win32) git archive --format=tar --remote=git://de.git.xonotic.org/xonotic/xonotic.git \ --prefix=".icons/" master:"misc/logos/icons_ico" | tar xvf - mv .icons/xonotic.ico darkplaces.ico wget -qO- http://beta.xonotic.org/win-builds.org/cross_toolchain_32.tar.xz | tar xaJvf - -C"$USRLOCAL" opt/cross_toolchain_32 ;; win64) git archive --format=tar --remote=git://de.git.xonotic.org/xonotic/xonotic.git \ --prefix=".icons/" master:"misc/logos/icons_ico" | tar xvf - mv .icons/xonotic.ico darkplaces.ico wget -qO- http://beta.xonotic.org/win-builds.org/cross_toolchain_64.tar.xz | tar xvJf - -C"$USRLOCAL" opt/cross_toolchain_64 ;; osx) git archive --format=tar --remote=git://de.git.xonotic.org/xonotic/xonotic.git \ --prefix=SDL2.framework/ master:misc/buildfiles/osx/Xonotic.app/Contents/Frameworks/SDL2.framework | tar xvf - ;; esac done for X in .deps/*; do rsync --remove-source-files -aL "$X"/*/ "$X"/ || true done darkplaces/polygon.h0000664000175000017500000000265213067716222014032 0ustar kalevkalev #ifndef POLYGON_H #define POLYGON_H /* Polygon clipping routines written by Forest Hale and placed into public domain. */ void PolygonF_QuadForPlane(float *outpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float quadsize); void PolygonD_QuadForPlane(double *outpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double quadsize); int PolygonF_Clip(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints); int PolygonD_Clip(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints); void PolygonF_Divide(int innumpoints, const float *inpoints, float planenormalx, float planenormaly, float planenormalz, float planedist, float epsilon, int outfrontmaxpoints, float *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, float *outbackpoints, int *neededbackpoints, int *oncountpointer); void PolygonD_Divide(int innumpoints, const double *inpoints, double planenormalx, double planenormaly, double planenormalz, double planedist, double epsilon, int outfrontmaxpoints, double *outfrontpoints, int *neededfrontpoints, int outbackmaxpoints, double *outbackpoints, int *neededbackpoints, int *oncountpointer); #endif darkplaces/vs2010_sdl2_win64.props0000664000175000017500000000130413067716222016156 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL2-2.0\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x64;C:\dev\SDL2-2.0\lib\x64;$(LibraryPath) <_PropertySheetDisplayName>vs2010_win64 darkplaces/cl_input.c0000664000175000017500000024357213067716216014166 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl.input.c -- builds an intended movement command to send to the server // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All // rights reserved. #include "quakedef.h" #include "csprogs.h" #include "thread.h" /* =============================================================================== KEY BUTTONS Continuous button event tracking is complicated by the fact that two different input sources (say, mouse button 1 and the control key) can both press the same button, but the button should only be released when both of the pressing key have been released. When a key event issues a button command (+forward, +attack, etc), it appends its key number as a parameter to the command so it can be matched up with the release. state bit 0 is the current state of the key state bit 1 is edge triggered on the up to down transition state bit 2 is edge triggered on the down to up transition =============================================================================== */ kbutton_t in_mlook, in_klook; kbutton_t in_left, in_right, in_forward, in_back; kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; kbutton_t in_strafe, in_speed, in_jump, in_attack, in_use; kbutton_t in_up, in_down; // LordHavoc: added 6 new buttons kbutton_t in_button3, in_button4, in_button5, in_button6, in_button7, in_button8; //even more kbutton_t in_button9, in_button10, in_button11, in_button12, in_button13, in_button14, in_button15, in_button16; int in_impulse; static void KeyDown (kbutton_t *b) { int k; const char *c; c = Cmd_Argv(1); if (c[0]) k = atoi(c); else k = -1; // typed manually at the console for continuous down if (k == b->down[0] || k == b->down[1]) return; // repeating key if (!b->down[0]) b->down[0] = k; else if (!b->down[1]) b->down[1] = k; else { Con_Print("Three keys down for a button!\n"); return; } if (b->state & 1) return; // still down b->state |= 1 + 2; // down + impulse down } static void KeyUp (kbutton_t *b) { int k; const char *c; c = Cmd_Argv(1); if (c[0]) k = atoi(c); else { // typed manually at the console, assume for unsticking, so clear all b->down[0] = b->down[1] = 0; b->state = 4; // impulse up return; } if (b->down[0] == k) b->down[0] = 0; else if (b->down[1] == k) b->down[1] = 0; else return; // key up without coresponding down (menu pass through) if (b->down[0] || b->down[1]) return; // some other key is still holding it down if (!(b->state & 1)) return; // still up (this should not happen) b->state &= ~1; // now up b->state |= 4; // impulse up } static void IN_KLookDown (void) {KeyDown(&in_klook);} static void IN_KLookUp (void) {KeyUp(&in_klook);} static void IN_MLookDown (void) {KeyDown(&in_mlook);} static void IN_MLookUp (void) { KeyUp(&in_mlook); if ( !(in_mlook.state&1) && lookspring.value) V_StartPitchDrift(); } static void IN_UpDown(void) {KeyDown(&in_up);} static void IN_UpUp(void) {KeyUp(&in_up);} static void IN_DownDown(void) {KeyDown(&in_down);} static void IN_DownUp(void) {KeyUp(&in_down);} static void IN_LeftDown(void) {KeyDown(&in_left);} static void IN_LeftUp(void) {KeyUp(&in_left);} static void IN_RightDown(void) {KeyDown(&in_right);} static void IN_RightUp(void) {KeyUp(&in_right);} static void IN_ForwardDown(void) {KeyDown(&in_forward);} static void IN_ForwardUp(void) {KeyUp(&in_forward);} static void IN_BackDown(void) {KeyDown(&in_back);} static void IN_BackUp(void) {KeyUp(&in_back);} static void IN_LookupDown(void) {KeyDown(&in_lookup);} static void IN_LookupUp(void) {KeyUp(&in_lookup);} static void IN_LookdownDown(void) {KeyDown(&in_lookdown);} static void IN_LookdownUp(void) {KeyUp(&in_lookdown);} static void IN_MoveleftDown(void) {KeyDown(&in_moveleft);} static void IN_MoveleftUp(void) {KeyUp(&in_moveleft);} static void IN_MoverightDown(void) {KeyDown(&in_moveright);} static void IN_MoverightUp(void) {KeyUp(&in_moveright);} static void IN_SpeedDown(void) {KeyDown(&in_speed);} static void IN_SpeedUp(void) {KeyUp(&in_speed);} static void IN_StrafeDown(void) {KeyDown(&in_strafe);} static void IN_StrafeUp(void) {KeyUp(&in_strafe);} static void IN_AttackDown(void) {KeyDown(&in_attack);} static void IN_AttackUp(void) {KeyUp(&in_attack);} static void IN_UseDown(void) {KeyDown(&in_use);} static void IN_UseUp(void) {KeyUp(&in_use);} // LordHavoc: added 6 new buttons static void IN_Button3Down(void) {KeyDown(&in_button3);} static void IN_Button3Up(void) {KeyUp(&in_button3);} static void IN_Button4Down(void) {KeyDown(&in_button4);} static void IN_Button4Up(void) {KeyUp(&in_button4);} static void IN_Button5Down(void) {KeyDown(&in_button5);} static void IN_Button5Up(void) {KeyUp(&in_button5);} static void IN_Button6Down(void) {KeyDown(&in_button6);} static void IN_Button6Up(void) {KeyUp(&in_button6);} static void IN_Button7Down(void) {KeyDown(&in_button7);} static void IN_Button7Up(void) {KeyUp(&in_button7);} static void IN_Button8Down(void) {KeyDown(&in_button8);} static void IN_Button8Up(void) {KeyUp(&in_button8);} static void IN_Button9Down(void) {KeyDown(&in_button9);} static void IN_Button9Up(void) {KeyUp(&in_button9);} static void IN_Button10Down(void) {KeyDown(&in_button10);} static void IN_Button10Up(void) {KeyUp(&in_button10);} static void IN_Button11Down(void) {KeyDown(&in_button11);} static void IN_Button11Up(void) {KeyUp(&in_button11);} static void IN_Button12Down(void) {KeyDown(&in_button12);} static void IN_Button12Up(void) {KeyUp(&in_button12);} static void IN_Button13Down(void) {KeyDown(&in_button13);} static void IN_Button13Up(void) {KeyUp(&in_button13);} static void IN_Button14Down(void) {KeyDown(&in_button14);} static void IN_Button14Up(void) {KeyUp(&in_button14);} static void IN_Button15Down(void) {KeyDown(&in_button15);} static void IN_Button15Up(void) {KeyUp(&in_button15);} static void IN_Button16Down(void) {KeyDown(&in_button16);} static void IN_Button16Up(void) {KeyUp(&in_button16);} static void IN_JumpDown (void) {KeyDown(&in_jump);} static void IN_JumpUp (void) {KeyUp(&in_jump);} static void IN_Impulse (void) {in_impulse=atoi(Cmd_Argv(1));} in_bestweapon_info_t in_bestweapon_info[IN_BESTWEAPON_MAX]; static void IN_BestWeapon_Register(const char *name, int impulse, int weaponbit, int activeweaponcode, int ammostat, int ammomin) { int i; for(i = 0; i < IN_BESTWEAPON_MAX && in_bestweapon_info[i].impulse; ++i) if(in_bestweapon_info[i].impulse == impulse) break; if(i >= IN_BESTWEAPON_MAX) { Con_Printf("no slot left for weapon definition; increase IN_BESTWEAPON_MAX\n"); return; // sorry } strlcpy(in_bestweapon_info[i].name, name, sizeof(in_bestweapon_info[i].name)); in_bestweapon_info[i].impulse = impulse; if(weaponbit != -1) in_bestweapon_info[i].weaponbit = weaponbit; if(activeweaponcode != -1) in_bestweapon_info[i].activeweaponcode = activeweaponcode; if(ammostat != -1) in_bestweapon_info[i].ammostat = ammostat; if(ammomin != -1) in_bestweapon_info[i].ammomin = ammomin; } void IN_BestWeapon_ResetData (void) { memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); IN_BestWeapon_Register("1", 1, IT_AXE, IT_AXE, STAT_SHELLS, 0); IN_BestWeapon_Register("2", 2, IT_SHOTGUN, IT_SHOTGUN, STAT_SHELLS, 1); IN_BestWeapon_Register("3", 3, IT_SUPER_SHOTGUN, IT_SUPER_SHOTGUN, STAT_SHELLS, 1); IN_BestWeapon_Register("4", 4, IT_NAILGUN, IT_NAILGUN, STAT_NAILS, 1); IN_BestWeapon_Register("5", 5, IT_SUPER_NAILGUN, IT_SUPER_NAILGUN, STAT_NAILS, 1); IN_BestWeapon_Register("6", 6, IT_GRENADE_LAUNCHER, IT_GRENADE_LAUNCHER, STAT_ROCKETS, 1); IN_BestWeapon_Register("7", 7, IT_ROCKET_LAUNCHER, IT_ROCKET_LAUNCHER, STAT_ROCKETS, 1); IN_BestWeapon_Register("8", 8, IT_LIGHTNING, IT_LIGHTNING, STAT_CELLS, 1); IN_BestWeapon_Register("9", 9, 128, 128, STAT_CELLS, 1); // generic energy weapon for mods IN_BestWeapon_Register("p", 209, 128, 128, STAT_CELLS, 1); // dpmod plasma gun IN_BestWeapon_Register("w", 210, 8388608, 8388608, STAT_CELLS, 1); // dpmod plasma wave cannon IN_BestWeapon_Register("l", 225, HIT_LASER_CANNON, HIT_LASER_CANNON, STAT_CELLS, 1); // hipnotic laser cannon IN_BestWeapon_Register("h", 226, HIT_MJOLNIR, HIT_MJOLNIR, STAT_CELLS, 0); // hipnotic mjolnir hammer } static void IN_BestWeapon_Register_f (void) { if(Cmd_Argc() == 7) { IN_BestWeapon_Register( Cmd_Argv(1), atoi(Cmd_Argv(2)), atoi(Cmd_Argv(3)), atoi(Cmd_Argv(4)), atoi(Cmd_Argv(5)), atoi(Cmd_Argv(6)) ); } else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "clear")) { memset(in_bestweapon_info, 0, sizeof(in_bestweapon_info)); } else if(Cmd_Argc() == 2 && !strcmp(Cmd_Argv(1), "quake")) { IN_BestWeapon_ResetData(); } else { Con_Printf("Usage: %s weaponshortname impulse itemcode activeweaponcode ammostat ammomin; %s clear; %s quake\n", Cmd_Argv(0), Cmd_Argv(0), Cmd_Argv(0)); } } static void IN_BestWeapon (void) { int i, n; const char *t; if (Cmd_Argc() < 2) { Con_Printf("bestweapon requires 1 or more parameters\n"); return; } for (i = 1;i < Cmd_Argc();i++) { t = Cmd_Argv(i); // figure out which weapon this character refers to for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) { if (!strcmp(in_bestweapon_info[n].name, t)) { // we found out what weapon this character refers to // check if the inventory contains the weapon and enough ammo if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) { // we found one of the weapons the player wanted // send an impulse to switch to it in_impulse = in_bestweapon_info[n].impulse; return; } break; } } // if we couldn't identify the weapon we just ignore it and continue checking for other weapons } // if we couldn't find any of the weapons, there's nothing more we can do... } #if 0 void IN_CycleWeapon (void) { int i, n; int first = -1; qboolean found = false; const char *t; if (Cmd_Argc() < 2) { Con_Printf("bestweapon requires 1 or more parameters\n"); return; } for (i = 1;i < Cmd_Argc();i++) { t = Cmd_Argv(i); // figure out which weapon this character refers to for (n = 0;n < IN_BESTWEAPON_MAX && in_bestweapon_info[n].impulse;n++) { if (!strcmp(in_bestweapon_info[n].name, t)) { // we found out what weapon this character refers to // check if the inventory contains the weapon and enough ammo if ((cl.stats[STAT_ITEMS] & in_bestweapon_info[n].weaponbit) && (cl.stats[in_bestweapon_info[n].ammostat] >= in_bestweapon_info[n].ammomin)) { // we found one of the weapons the player wanted if(first == -1) first = n; if(found) { in_impulse = in_bestweapon_info[n].impulse; return; } if(cl.stats[STAT_ACTIVEWEAPON] == in_bestweapon_info[n].activeweaponcode) found = true; } break; } } // if we couldn't identify the weapon we just ignore it and continue checking for other weapons } if(first != -1) { in_impulse = in_bestweapon_info[first].impulse; return; } // if we couldn't find any of the weapons, there's nothing more we can do... } #endif /* =============== CL_KeyState Returns 0.25 if a key was pressed and released during the frame, 0.5 if it was pressed and held 0 if held then released, and 1.0 if held for the entire time =============== */ float CL_KeyState (kbutton_t *key) { float val; qboolean impulsedown, impulseup, down; impulsedown = (key->state & 2) != 0; impulseup = (key->state & 4) != 0; down = (key->state & 1) != 0; val = 0; if (impulsedown && !impulseup) { if (down) val = 0.5; // pressed and held this frame else val = 0; // I_Error (); } if (impulseup && !impulsedown) { if (down) val = 0; // I_Error (); else val = 0; // released this frame } if (!impulsedown && !impulseup) { if (down) val = 1.0; // held the entire frame else val = 0; // up the entire frame } if (impulsedown && impulseup) { if (down) val = 0.75; // released and re-pressed this frame else val = 0.25; // pressed and released this frame } key->state &= 1; // clear impulses return val; } //========================================================================== cvar_t cl_upspeed = {CVAR_SAVE, "cl_upspeed","400","vertical movement speed (while swimming or flying)"}; cvar_t cl_forwardspeed = {CVAR_SAVE, "cl_forwardspeed","400","forward movement speed"}; cvar_t cl_backspeed = {CVAR_SAVE, "cl_backspeed","400","backward movement speed"}; cvar_t cl_sidespeed = {CVAR_SAVE, "cl_sidespeed","350","strafe movement speed"}; cvar_t cl_movespeedkey = {CVAR_SAVE, "cl_movespeedkey","2.0","how much +speed multiplies keyboard movement speed"}; cvar_t cl_movecliptokeyboard = {0, "cl_movecliptokeyboard", "0", "if set to 1, any move is clipped to the nine keyboard states; if set to 2, only the direction is clipped, not the amount"}; cvar_t cl_yawspeed = {CVAR_SAVE, "cl_yawspeed","140","keyboard yaw turning speed"}; cvar_t cl_pitchspeed = {CVAR_SAVE, "cl_pitchspeed","150","keyboard pitch turning speed"}; cvar_t cl_anglespeedkey = {CVAR_SAVE, "cl_anglespeedkey","1.5","how much +speed multiplies keyboard turning speed"}; cvar_t cl_movement = {CVAR_SAVE, "cl_movement", "0", "enables clientside prediction of your player movement on DP servers (use cl_nopred for QWSV servers)"}; cvar_t cl_movement_replay = {0, "cl_movement_replay", "1", "use engine prediction"}; cvar_t cl_movement_nettimeout = {CVAR_SAVE, "cl_movement_nettimeout", "0.3", "stops predicting moves when server is lagging badly (avoids major performance problems), timeout in seconds"}; cvar_t cl_movement_minping = {CVAR_SAVE, "cl_movement_minping", "0", "whether to use prediction when ping is lower than this value in milliseconds"}; cvar_t cl_movement_track_canjump = {CVAR_SAVE, "cl_movement_track_canjump", "1", "track if the player released the jump key between two jumps to decide if he is able to jump or not; when off, this causes some \"sliding\" slightly above the floor when the jump key is held too long; if the mod allows repeated jumping by holding space all the time, this has to be set to zero too"}; cvar_t cl_movement_maxspeed = {0, "cl_movement_maxspeed", "320", "how fast you can move (should match sv_maxspeed)"}; cvar_t cl_movement_maxairspeed = {0, "cl_movement_maxairspeed", "30", "how fast you can move while in the air (should match sv_maxairspeed)"}; cvar_t cl_movement_stopspeed = {0, "cl_movement_stopspeed", "100", "speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed)"}; cvar_t cl_movement_friction = {0, "cl_movement_friction", "4", "how fast you slow down (should match sv_friction)"}; cvar_t cl_movement_wallfriction = {0, "cl_movement_wallfriction", "1", "how fast you slow down while sliding along a wall (should match sv_wallfriction)"}; cvar_t cl_movement_waterfriction = {0, "cl_movement_waterfriction", "-1", "how fast you slow down (should match sv_waterfriction), if less than 0 the cl_movement_friction variable is used instead"}; cvar_t cl_movement_edgefriction = {0, "cl_movement_edgefriction", "1", "how much to slow down when you may be about to fall off a ledge (should match edgefriction)"}; cvar_t cl_movement_stepheight = {0, "cl_movement_stepheight", "18", "how tall a step you can step in one instant (should match sv_stepheight)"}; cvar_t cl_movement_accelerate = {0, "cl_movement_accelerate", "10", "how fast you accelerate (should match sv_accelerate)"}; cvar_t cl_movement_airaccelerate = {0, "cl_movement_airaccelerate", "-1", "how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; cvar_t cl_movement_wateraccelerate = {0, "cl_movement_wateraccelerate", "-1", "how fast you accelerate while in water (should match sv_wateraccelerate), if less than 0 the cl_movement_accelerate variable is used instead"}; cvar_t cl_movement_jumpvelocity = {0, "cl_movement_jumpvelocity", "270", "how fast you move upward when you begin a jump (should match the quakec code)"}; cvar_t cl_movement_airaccel_qw = {0, "cl_movement_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration (reduces speed gain when zigzagging) (should match sv_airaccel_qw); when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; cvar_t cl_movement_airaccel_sideways_friction = {0, "cl_movement_airaccel_sideways_friction", "0", "anti-sideways movement stabilization (should match sv_airaccel_sideways_friction); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; cvar_t cl_nopred = {CVAR_SAVE, "cl_nopred", "0", "(QWSV only) disables player movement prediction when playing on QWSV servers (this setting is separate from cl_movement because player expectations are different when playing on DP vs QW servers)"}; cvar_t in_pitch_min = {0, "in_pitch_min", "-90", "how far you can aim upward (quake used -70)"}; cvar_t in_pitch_max = {0, "in_pitch_max", "90", "how far you can aim downward (quake used 80)"}; cvar_t m_filter = {CVAR_SAVE, "m_filter","0", "smoothes mouse movement, less responsive but smoother aiming"}; cvar_t m_accelerate = {CVAR_SAVE, "m_accelerate","1", "mouse acceleration factor (try 2)"}; cvar_t m_accelerate_minspeed = {CVAR_SAVE, "m_accelerate_minspeed","5000", "below this speed, no acceleration is done"}; cvar_t m_accelerate_maxspeed = {CVAR_SAVE, "m_accelerate_maxspeed","10000", "above this speed, full acceleration is done"}; cvar_t m_accelerate_filter = {CVAR_SAVE, "m_accelerate_filter","0.1", "mouse acceleration factor filtering"}; cvar_t cl_netfps = {CVAR_SAVE, "cl_netfps","72", "how many input packets to send to server each second"}; cvar_t cl_netrepeatinput = {CVAR_SAVE, "cl_netrepeatinput", "1", "how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server), only affects DP7 and later servers (Quake uses 0, QuakeWorld uses 2, and just for comparison Quake3 uses 1)"}; cvar_t cl_netimmediatebuttons = {CVAR_SAVE, "cl_netimmediatebuttons", "1", "sends extra packets whenever your buttons change or an impulse is used (basically: whenever you click fire or change weapon)"}; cvar_t cl_nodelta = {0, "cl_nodelta", "0", "disables delta compression of non-player entities in QW network protocol"}; cvar_t cl_csqc_generatemousemoveevents = {0, "cl_csqc_generatemousemoveevents", "1", "enables calls to CSQC_InputEvent with type 2, for compliance with EXT_CSQC spec"}; extern cvar_t v_flipped; /* ================ CL_AdjustAngles Moves the local angle positions ================ */ static void CL_AdjustAngles (void) { float speed; float up, down; if (in_speed.state & 1) speed = cl.realframetime * cl_anglespeedkey.value; else speed = cl.realframetime; if (!(in_strafe.state & 1)) { cl.viewangles[YAW] -= speed*cl_yawspeed.value*CL_KeyState (&in_right); cl.viewangles[YAW] += speed*cl_yawspeed.value*CL_KeyState (&in_left); } if (in_klook.state & 1) { V_StopPitchDrift (); cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * CL_KeyState (&in_forward); cl.viewangles[PITCH] += speed*cl_pitchspeed.value * CL_KeyState (&in_back); } up = CL_KeyState (&in_lookup); down = CL_KeyState(&in_lookdown); cl.viewangles[PITCH] -= speed*cl_pitchspeed.value * up; cl.viewangles[PITCH] += speed*cl_pitchspeed.value * down; if (up || down) V_StopPitchDrift (); cl.viewangles[YAW] = ANGLEMOD(cl.viewangles[YAW]); cl.viewangles[PITCH] = ANGLEMOD(cl.viewangles[PITCH]); if (cl.viewangles[YAW] >= 180) cl.viewangles[YAW] -= 360; if (cl.viewangles[PITCH] >= 180) cl.viewangles[PITCH] -= 360; // TODO: honor serverinfo minpitch and maxpitch values in PROTOCOL_QUAKEWORLD // TODO: honor proquake pq_fullpitch cvar when playing on proquake server (server stuffcmd's this to 0 usually) cl.viewangles[PITCH] = bound(in_pitch_min.value, cl.viewangles[PITCH], in_pitch_max.value); cl.viewangles[ROLL] = bound(-180, cl.viewangles[ROLL], 180); } int cl_ignoremousemoves = 2; /* ================ CL_Input Send the intended movement message to the server ================ */ void CL_Input (void) { float mx, my; static float old_mouse_x = 0, old_mouse_y = 0; // clamp before the move to prevent starting with bad angles CL_AdjustAngles (); if(v_flipped.integer) cl.viewangles[YAW] = -cl.viewangles[YAW]; // reset some of the command fields cl.cmd.forwardmove = 0; cl.cmd.sidemove = 0; cl.cmd.upmove = 0; // get basic movement from keyboard if (in_strafe.state & 1) { cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_right); cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_left); } cl.cmd.sidemove += cl_sidespeed.value * CL_KeyState (&in_moveright); cl.cmd.sidemove -= cl_sidespeed.value * CL_KeyState (&in_moveleft); cl.cmd.upmove += cl_upspeed.value * CL_KeyState (&in_up); cl.cmd.upmove -= cl_upspeed.value * CL_KeyState (&in_down); if (! (in_klook.state & 1) ) { cl.cmd.forwardmove += cl_forwardspeed.value * CL_KeyState (&in_forward); cl.cmd.forwardmove -= cl_backspeed.value * CL_KeyState (&in_back); } // adjust for speed key if (in_speed.state & 1) { cl.cmd.forwardmove *= cl_movespeedkey.value; cl.cmd.sidemove *= cl_movespeedkey.value; cl.cmd.upmove *= cl_movespeedkey.value; } // allow mice or other external controllers to add to the move IN_Move (); // send mouse move to csqc if (cl.csqc_loaded && cl_csqc_generatemousemoveevents.integer) { if (cl.csqc_wantsmousemove) { // event type 3 is a DP_CSQC thing static int oldwindowmouse[2]; if (oldwindowmouse[0] != in_windowmouse_x || oldwindowmouse[1] != in_windowmouse_y) { CL_VM_InputEvent(3, in_windowmouse_x * vid_conwidth.value / vid.width, in_windowmouse_y * vid_conheight.value / vid.height); oldwindowmouse[0] = in_windowmouse_x; oldwindowmouse[1] = in_windowmouse_y; } } else { if (in_mouse_x || in_mouse_y) CL_VM_InputEvent(2, in_mouse_x, in_mouse_y); } } // apply m_accelerate if it is on if(m_accelerate.value > 1) { static float averagespeed = 0; float speed, f, mi, ma; speed = sqrt(in_mouse_x * in_mouse_x + in_mouse_y * in_mouse_y) / cl.realframetime; if(m_accelerate_filter.value > 0) f = bound(0, cl.realframetime / m_accelerate_filter.value, 1); else f = 1; averagespeed = speed * f + averagespeed * (1 - f); mi = max(1, m_accelerate_minspeed.value); ma = max(m_accelerate_minspeed.value + 1, m_accelerate_maxspeed.value); if(averagespeed <= mi) { f = 1; } else if(averagespeed >= ma) { f = m_accelerate.value; } else { f = averagespeed; f = (f - mi) / (ma - mi) * (m_accelerate.value - 1) + 1; } in_mouse_x *= f; in_mouse_y *= f; } // apply m_filter if it is on mx = in_mouse_x; my = in_mouse_y; if (m_filter.integer) { in_mouse_x = (mx + old_mouse_x) * 0.5; in_mouse_y = (my + old_mouse_y) * 0.5; } old_mouse_x = mx; old_mouse_y = my; // ignore a mouse move if mouse was activated/deactivated this frame if (cl_ignoremousemoves) { cl_ignoremousemoves--; in_mouse_x = old_mouse_x = 0; in_mouse_y = old_mouse_y = 0; } // if not in menu, apply mouse move to viewangles/movement if (!key_consoleactive && key_dest == key_game && !cl.csqc_wantsmousemove && cl_prydoncursor.integer <= 0) { float modulatedsensitivity = sensitivity.value * cl.sensitivityscale; if (in_strafe.state & 1) { // strafing mode, all looking is movement V_StopPitchDrift(); cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; if (noclip_anglehack) cl.cmd.upmove -= m_forward.value * in_mouse_y * modulatedsensitivity; else cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; } else if ((in_mlook.state & 1) || freelook.integer) { // mouselook, lookstrafe causes turning to become strafing V_StopPitchDrift(); if (lookstrafe.integer) cl.cmd.sidemove += m_side.value * in_mouse_x * modulatedsensitivity; else cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; cl.viewangles[PITCH] += m_pitch.value * in_mouse_y * modulatedsensitivity * cl.viewzoom; } else { // non-mouselook, yaw turning and forward/back movement cl.viewangles[YAW] -= m_yaw.value * in_mouse_x * modulatedsensitivity * cl.viewzoom; cl.cmd.forwardmove -= m_forward.value * in_mouse_y * modulatedsensitivity; } } else // don't pitch drift when csqc is controlling the mouse { // mouse interacting with the scene, mostly stationary view V_StopPitchDrift(); // update prydon cursor cl.cmd.cursor_screen[0] = in_windowmouse_x * 2.0 / vid.width - 1.0; cl.cmd.cursor_screen[1] = in_windowmouse_y * 2.0 / vid.height - 1.0; } if(v_flipped.integer) { cl.viewangles[YAW] = -cl.viewangles[YAW]; cl.cmd.sidemove = -cl.cmd.sidemove; } // clamp after the move to prevent rendering with bad angles CL_AdjustAngles (); if(cl_movecliptokeyboard.integer) { vec_t f = 1; if (in_speed.state & 1) f *= cl_movespeedkey.value; if(cl_movecliptokeyboard.integer == 2) { // digital direction, analog amount vec_t wishvel_x, wishvel_y; wishvel_x = fabs(cl.cmd.forwardmove); wishvel_y = fabs(cl.cmd.sidemove); if(wishvel_x != 0 && wishvel_y != 0 && wishvel_x != wishvel_y) { vec_t wishspeed = sqrt(wishvel_x * wishvel_x + wishvel_y * wishvel_y); if(wishvel_x >= 2 * wishvel_y) { // pure X motion if(cl.cmd.forwardmove > 0) cl.cmd.forwardmove = wishspeed; else cl.cmd.forwardmove = -wishspeed; cl.cmd.sidemove = 0; } else if(wishvel_y >= 2 * wishvel_x) { // pure Y motion cl.cmd.forwardmove = 0; if(cl.cmd.sidemove > 0) cl.cmd.sidemove = wishspeed; else cl.cmd.sidemove = -wishspeed; } else { // diagonal if(cl.cmd.forwardmove > 0) cl.cmd.forwardmove = 0.70710678118654752440 * wishspeed; else cl.cmd.forwardmove = -0.70710678118654752440 * wishspeed; if(cl.cmd.sidemove > 0) cl.cmd.sidemove = 0.70710678118654752440 * wishspeed; else cl.cmd.sidemove = -0.70710678118654752440 * wishspeed; } } } else if(cl_movecliptokeyboard.integer) { // digital direction, digital amount if(cl.cmd.sidemove >= cl_sidespeed.value * f * 0.5) cl.cmd.sidemove = cl_sidespeed.value * f; else if(cl.cmd.sidemove <= -cl_sidespeed.value * f * 0.5) cl.cmd.sidemove = -cl_sidespeed.value * f; else cl.cmd.sidemove = 0; if(cl.cmd.forwardmove >= cl_forwardspeed.value * f * 0.5) cl.cmd.forwardmove = cl_forwardspeed.value * f; else if(cl.cmd.forwardmove <= -cl_backspeed.value * f * 0.5) cl.cmd.forwardmove = -cl_backspeed.value * f; else cl.cmd.forwardmove = 0; } } } #include "cl_collision.h" static void CL_UpdatePrydonCursor(void) { vec3_t temp; if (cl_prydoncursor.integer <= 0) VectorClear(cl.cmd.cursor_screen); /* if (cl.cmd.cursor_screen[0] < -1) { cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - -1) * vid.width * sensitivity.value * cl.viewzoom; cl.cmd.cursor_screen[0] = -1; } if (cl.cmd.cursor_screen[0] > 1) { cl.viewangles[YAW] -= m_yaw.value * (cl.cmd.cursor_screen[0] - 1) * vid.width * sensitivity.value * cl.viewzoom; cl.cmd.cursor_screen[0] = 1; } if (cl.cmd.cursor_screen[1] < -1) { cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - -1) * vid.height * sensitivity.value * cl.viewzoom; cl.cmd.cursor_screen[1] = -1; } if (cl.cmd.cursor_screen[1] > 1) { cl.viewangles[PITCH] += m_pitch.value * (cl.cmd.cursor_screen[1] - 1) * vid.height * sensitivity.value * cl.viewzoom; cl.cmd.cursor_screen[1] = 1; } */ cl.cmd.cursor_screen[0] = bound(-1, cl.cmd.cursor_screen[0], 1); cl.cmd.cursor_screen[1] = bound(-1, cl.cmd.cursor_screen[1], 1); cl.cmd.cursor_screen[2] = 1; // calculate current view matrix Matrix4x4_OriginFromMatrix(&r_refdef.view.matrix, cl.cmd.cursor_start); // calculate direction vector of cursor in viewspace by using frustum slopes VectorSet(temp, cl.cmd.cursor_screen[2] * 1000000, (v_flipped.integer ? -1 : 1) * cl.cmd.cursor_screen[0] * -r_refdef.view.frustum_x * 1000000, cl.cmd.cursor_screen[1] * -r_refdef.view.frustum_y * 1000000); Matrix4x4_Transform(&r_refdef.view.matrix, temp, cl.cmd.cursor_end); // trace from view origin to the cursor if (cl_prydoncursor_notrace.integer) { cl.cmd.cursor_fraction = 1.0f; VectorCopy(cl.cmd.cursor_end, cl.cmd.cursor_impact); VectorClear(cl.cmd.cursor_normal); cl.cmd.cursor_entitynumber = 0; } else cl.cmd.cursor_fraction = CL_SelectTraceLine(cl.cmd.cursor_start, cl.cmd.cursor_end, cl.cmd.cursor_impact, cl.cmd.cursor_normal, &cl.cmd.cursor_entitynumber, (chase_active.integer || cl.intermission) ? &cl.entities[cl.playerentity].render : NULL); } #define NUMOFFSETS 27 static vec3_t offsets[NUMOFFSETS] = { // 1 no nudge (just return the original if this test passes) { 0.000, 0.000, 0.000}, // 6 simple nudges { 0.000, 0.000, 0.125}, { 0.000, 0.000, -0.125}, {-0.125, 0.000, 0.000}, { 0.125, 0.000, 0.000}, { 0.000, -0.125, 0.000}, { 0.000, 0.125, 0.000}, // 4 diagonal flat nudges {-0.125, -0.125, 0.000}, { 0.125, -0.125, 0.000}, {-0.125, 0.125, 0.000}, { 0.125, 0.125, 0.000}, // 8 diagonal upward nudges {-0.125, 0.000, 0.125}, { 0.125, 0.000, 0.125}, { 0.000, -0.125, 0.125}, { 0.000, 0.125, 0.125}, {-0.125, -0.125, 0.125}, { 0.125, -0.125, 0.125}, {-0.125, 0.125, 0.125}, { 0.125, 0.125, 0.125}, // 8 diagonal downward nudges {-0.125, 0.000, -0.125}, { 0.125, 0.000, -0.125}, { 0.000, -0.125, -0.125}, { 0.000, 0.125, -0.125}, {-0.125, -0.125, -0.125}, { 0.125, -0.125, -0.125}, {-0.125, 0.125, -0.125}, { 0.125, 0.125, -0.125}, }; static qboolean CL_ClientMovement_Unstick(cl_clientmovement_state_t *s) { int i; vec3_t neworigin; for (i = 0;i < NUMOFFSETS;i++) { VectorAdd(offsets[i], s->origin, neworigin); if (!CL_TraceBox(neworigin, cl.playercrouchmins, cl.playercrouchmaxs, neworigin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true).startsolid) { VectorCopy(neworigin, s->origin); return true; } } // if all offsets failed, give up return false; } static void CL_ClientMovement_UpdateStatus(cl_clientmovement_state_t *s) { vec_t f; vec3_t origin1, origin2; trace_t trace; // make sure player is not stuck CL_ClientMovement_Unstick(s); // set crouched if (s->cmd.crouch) { // wants to crouch, this always works.. if (!s->crouched) s->crouched = true; } else { // wants to stand, if currently crouching we need to check for a // low ceiling first if (s->crouched) { trace = CL_TraceBox(s->origin, cl.playerstandmins, cl.playerstandmaxs, s->origin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); if (!trace.startsolid) s->crouched = false; } } if (s->crouched) { VectorCopy(cl.playercrouchmins, s->mins); VectorCopy(cl.playercrouchmaxs, s->maxs); } else { VectorCopy(cl.playerstandmins, s->mins); VectorCopy(cl.playerstandmaxs, s->maxs); } // set onground VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + 1); VectorSet(origin2, s->origin[0], s->origin[1], s->origin[2] - 1); // -2 causes clientside doublejump bug at above 150fps, raising that to 300fps :) trace = CL_TraceBox(origin1, s->mins, s->maxs, origin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); if(trace.fraction < 1 && trace.plane.normal[2] > 0.7) { s->onground = true; // this code actually "predicts" an impact; so let's clip velocity first f = DotProduct(s->velocity, trace.plane.normal); if(f < 0) // only if moving downwards actually VectorMA(s->velocity, -f, trace.plane.normal, s->velocity); } else s->onground = false; // set watertype/waterlevel VectorSet(origin1, s->origin[0], s->origin[1], s->origin[2] + s->mins[2] + 1); s->waterlevel = WATERLEVEL_NONE; s->watertype = CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK; if (s->watertype) { s->waterlevel = WATERLEVEL_WETFEET; origin1[2] = s->origin[2] + (s->mins[2] + s->maxs[2]) * 0.5f; if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) { s->waterlevel = WATERLEVEL_SWIMMING; origin1[2] = s->origin[2] + 22; if (CL_TracePoint(origin1, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsupercontents & SUPERCONTENTS_LIQUIDSMASK) s->waterlevel = WATERLEVEL_SUBMERGED; } } // water jump prediction if (s->onground || s->velocity[2] <= 0 || s->waterjumptime <= 0) s->waterjumptime = 0; } static void CL_ClientMovement_Move(cl_clientmovement_state_t *s) { int bump; double t; vec_t f; vec3_t neworigin; vec3_t currentorigin2; vec3_t neworigin2; vec3_t primalvelocity; trace_t trace; trace_t trace2; trace_t trace3; CL_ClientMovement_UpdateStatus(s); VectorCopy(s->velocity, primalvelocity); for (bump = 0, t = s->cmd.frametime;bump < 8 && VectorLength2(s->velocity) > 0;bump++) { VectorMA(s->origin, t, s->velocity, neworigin); trace = CL_TraceBox(s->origin, s->mins, s->maxs, neworigin, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); if (trace.fraction < 1 && trace.plane.normal[2] == 0) { // may be a step or wall, try stepping up // first move forward at a higher level VectorSet(currentorigin2, s->origin[0], s->origin[1], s->origin[2] + cl.movevars_stepheight); VectorSet(neworigin2, neworigin[0], neworigin[1], s->origin[2] + cl.movevars_stepheight); trace2 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); if (!trace2.startsolid) { // then move down from there VectorCopy(trace2.endpos, currentorigin2); VectorSet(neworigin2, trace2.endpos[0], trace2.endpos[1], s->origin[2]); trace3 = CL_TraceBox(currentorigin2, s->mins, s->maxs, neworigin2, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); //Con_Printf("%f %f %f %f : %f %f %f %f : %f %f %f %f\n", trace.fraction, trace.endpos[0], trace.endpos[1], trace.endpos[2], trace2.fraction, trace2.endpos[0], trace2.endpos[1], trace2.endpos[2], trace3.fraction, trace3.endpos[0], trace3.endpos[1], trace3.endpos[2]); // accept the new trace if it made some progress if (fabs(trace3.endpos[0] - trace.endpos[0]) >= 0.03125 || fabs(trace3.endpos[1] - trace.endpos[1]) >= 0.03125) { trace = trace2; VectorCopy(trace3.endpos, trace.endpos); } } } // check if it moved at all if (trace.fraction >= 0.001) VectorCopy(trace.endpos, s->origin); // check if it moved all the way if (trace.fraction == 1) break; // this is only really needed for nogravityonground combined with gravityunaffectedbyticrate // I'm pretty sure I commented it out solely because it seemed redundant // this got commented out in a change that supposedly makes the code match QW better // so if this is broken, maybe put it in an if(cls.protocol != PROTOCOL_QUAKEWORLD) block if (trace.plane.normal[2] > 0.7) s->onground = true; t -= t * trace.fraction; f = DotProduct(s->velocity, trace.plane.normal); VectorMA(s->velocity, -f, trace.plane.normal, s->velocity); } if (s->waterjumptime > 0) VectorCopy(primalvelocity, s->velocity); } static void CL_ClientMovement_Physics_Swim(cl_clientmovement_state_t *s) { vec_t wishspeed; vec_t f; vec3_t wishvel; vec3_t wishdir; // water jump only in certain situations // this mimics quakeworld code if (s->cmd.jump && s->waterlevel == 2 && s->velocity[2] >= -180) { vec3_t forward; vec3_t yawangles; vec3_t spot; VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); AngleVectors(yawangles, forward, NULL, NULL); VectorMA(s->origin, 24, forward, spot); spot[2] += 8; if (CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsolid) { spot[2] += 24; if (!CL_TracePoint(spot, MOVE_NOMONSTERS, s->self, 0, 0, true, false, NULL, false).startsolid) { VectorScale(forward, 50, s->velocity); s->velocity[2] = 310; s->waterjumptime = 2; s->onground = false; s->cmd.canjump = false; } } } if (!(s->cmd.forwardmove*s->cmd.forwardmove + s->cmd.sidemove*s->cmd.sidemove + s->cmd.upmove*s->cmd.upmove)) { // drift towards bottom VectorSet(wishvel, 0, 0, -60); } else { // swim vec3_t forward; vec3_t right; vec3_t up; // calculate movement vector AngleVectors(s->cmd.viewangles, forward, right, up); VectorSet(up, 0, 0, 1); VectorMAMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, s->cmd.upmove, up, wishvel); } // split wishvel into wishspeed and wishdir wishspeed = VectorLength(wishvel); if (wishspeed) VectorScale(wishvel, 1 / wishspeed, wishdir); else VectorSet( wishdir, 0.0, 0.0, 0.0 ); wishspeed = min(wishspeed, cl.movevars_maxspeed) * 0.7; if (s->crouched) wishspeed *= 0.5; if (s->waterjumptime <= 0) { // water friction f = 1 - s->cmd.frametime * cl.movevars_waterfriction * (cls.protocol == PROTOCOL_QUAKEWORLD ? s->waterlevel : 1); f = bound(0, f, 1); VectorScale(s->velocity, f, s->velocity); // water acceleration f = wishspeed - DotProduct(s->velocity, wishdir); if (f > 0) { f = min(cl.movevars_wateraccelerate * s->cmd.frametime * wishspeed, f); VectorMA(s->velocity, f, wishdir, s->velocity); } // holding jump button swims upward slowly if (s->cmd.jump) { if (s->watertype & SUPERCONTENTS_LAVA) s->velocity[2] = 50; else if (s->watertype & SUPERCONTENTS_SLIME) s->velocity[2] = 80; else { if (IS_NEXUIZ_DERIVED(gamemode)) s->velocity[2] = 200; else s->velocity[2] = 100; } } } CL_ClientMovement_Move(s); } static vec_t CL_IsMoveInDirection(vec_t forward, vec_t side, vec_t angle) { if(forward == 0 && side == 0) return 0; // avoid division by zero angle -= RAD2DEG(atan2(side, forward)); angle = (ANGLEMOD(angle + 180) - 180) / 45; if(angle > 1) return 0; if(angle < -1) return 0; return 1 - fabs(angle); } static vec_t CL_GeomLerp(vec_t a, vec_t lerp, vec_t b) { if(a == 0) { if(lerp < 1) return 0; else return b; } if(b == 0) { if(lerp > 0) return 0; else return a; } return a * pow(fabs(b / a), lerp); } static void CL_ClientMovement_Physics_CPM_PM_Aircontrol(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) { vec_t zspeed, speed, dot, k; #if 0 // this doesn't play well with analog input if(s->cmd.forwardmove == 0 || s->cmd.sidemove != 0) return; k = 32; #else k = 32 * (2 * CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, 0) - 1); if(k <= 0) return; #endif k *= bound(0, wishspeed / cl.movevars_maxairspeed, 1); zspeed = s->velocity[2]; s->velocity[2] = 0; speed = VectorNormalizeLength(s->velocity); dot = DotProduct(s->velocity, wishdir); if(dot > 0) { // we can't change direction while slowing down k *= pow(dot, cl.movevars_aircontrol_power)*s->cmd.frametime; speed = max(0, speed - cl.movevars_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32); k *= cl.movevars_aircontrol; VectorMAM(speed, s->velocity, k, wishdir, s->velocity); VectorNormalize(s->velocity); } VectorScale(s->velocity, speed, s->velocity); s->velocity[2] = zspeed; } static float CL_ClientMovement_Physics_AdjustAirAccelQW(float accelqw, float factor) { return (accelqw < 0 ? -1 : +1) * bound(0.000001, 1 - (1 - fabs(accelqw)) * factor, 1); } static void CL_ClientMovement_Physics_PM_Accelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed, vec_t wishspeed0, vec_t accel, vec_t accelqw, vec_t stretchfactor, vec_t sidefric, vec_t speedlimit) { vec_t vel_straight; vec_t vel_z; vec3_t vel_perpend; vec_t step; vec3_t vel_xy; vec_t vel_xy_current; vec_t vel_xy_backward, vel_xy_forward; vec_t speedclamp; if(stretchfactor > 0) speedclamp = stretchfactor; else if(accelqw < 0) speedclamp = 1; else speedclamp = -1; // no clamping if(accelqw < 0) accelqw = -accelqw; if(cl.moveflags & MOVEFLAG_Q2AIRACCELERATE) wishspeed0 = wishspeed; // don't need to emulate this Q1 bug vel_straight = DotProduct(s->velocity, wishdir); vel_z = s->velocity[2]; VectorCopy(s->velocity, vel_xy); vel_xy[2] -= vel_z; VectorMA(vel_xy, -vel_straight, wishdir, vel_perpend); step = accel * s->cmd.frametime * wishspeed0; vel_xy_current = VectorLength(vel_xy); if(speedlimit > 0) accelqw = CL_ClientMovement_Physics_AdjustAirAccelQW(accelqw, (speedlimit - bound(wishspeed, vel_xy_current, speedlimit)) / max(1, speedlimit - wishspeed)); vel_xy_forward = vel_xy_current + bound(0, wishspeed - vel_xy_current, step) * accelqw + step * (1 - accelqw); vel_xy_backward = vel_xy_current - bound(0, wishspeed + vel_xy_current, step) * accelqw - step * (1 - accelqw); if(vel_xy_backward < 0) vel_xy_backward = 0; // not that it REALLY occurs that this would cause wrong behaviour afterwards vel_straight = vel_straight + bound(0, wishspeed - vel_straight, step) * accelqw + step * (1 - accelqw); if(sidefric < 0 && VectorLength2(vel_perpend)) // negative: only apply so much sideways friction to stay below the speed you could get by "braking" { vec_t f, fmin; f = max(0, 1 + s->cmd.frametime * wishspeed * sidefric); fmin = (vel_xy_backward*vel_xy_backward - vel_straight*vel_straight) / VectorLength2(vel_perpend); // assume: fmin > 1 // vel_xy_backward*vel_xy_backward - vel_straight*vel_straight > vel_perpend*vel_perpend // vel_xy_backward*vel_xy_backward > vel_straight*vel_straight + vel_perpend*vel_perpend // vel_xy_backward*vel_xy_backward > vel_xy * vel_xy // obviously, this cannot be if(fmin <= 0) VectorScale(vel_perpend, f, vel_perpend); else { fmin = sqrt(fmin); VectorScale(vel_perpend, max(fmin, f), vel_perpend); } } else VectorScale(vel_perpend, max(0, 1 - s->cmd.frametime * wishspeed * sidefric), vel_perpend); VectorMA(vel_perpend, vel_straight, wishdir, s->velocity); if(speedclamp >= 0) { vec_t vel_xy_preclamp; vel_xy_preclamp = VectorLength(s->velocity); if(vel_xy_preclamp > 0) // prevent division by zero { vel_xy_current += (vel_xy_forward - vel_xy_current) * speedclamp; if(vel_xy_current < vel_xy_preclamp) VectorScale(s->velocity, (vel_xy_current / vel_xy_preclamp), s->velocity); } } s->velocity[2] += vel_z; } static void CL_ClientMovement_Physics_PM_AirAccelerate(cl_clientmovement_state_t *s, vec3_t wishdir, vec_t wishspeed) { vec3_t curvel, wishvel, acceldir, curdir; float addspeed, accelspeed, curspeed; float dot; float airforwardaccel = cl.movevars_warsowbunny_airforwardaccel; float bunnyaccel = cl.movevars_warsowbunny_accel; float bunnytopspeed = cl.movevars_warsowbunny_topspeed; float turnaccel = cl.movevars_warsowbunny_turnaccel; float backtosideratio = cl.movevars_warsowbunny_backtosideratio; if( !wishspeed ) return; VectorCopy( s->velocity, curvel ); curvel[2] = 0; curspeed = VectorLength( curvel ); if( wishspeed > curspeed * 1.01f ) { float faccelspeed = curspeed + airforwardaccel * cl.movevars_maxairspeed * s->cmd.frametime; if( faccelspeed < wishspeed ) wishspeed = faccelspeed; } else { float f = ( bunnytopspeed - curspeed ) / ( bunnytopspeed - cl.movevars_maxairspeed ); if( f < 0 ) f = 0; wishspeed = max( curspeed, cl.movevars_maxairspeed ) + bunnyaccel * f * cl.movevars_maxairspeed * s->cmd.frametime; } VectorScale( wishdir, wishspeed, wishvel ); VectorSubtract( wishvel, curvel, acceldir ); addspeed = VectorNormalizeLength( acceldir ); accelspeed = turnaccel * cl.movevars_maxairspeed /* wishspeed */ * s->cmd.frametime; if( accelspeed > addspeed ) accelspeed = addspeed; if( backtosideratio < 1.0f ) { VectorNormalize2( curvel, curdir ); dot = DotProduct( acceldir, curdir ); if( dot < 0 ) VectorMA( acceldir, -( 1.0f - backtosideratio ) * dot, curdir, acceldir ); } VectorMA( s->velocity, accelspeed, acceldir, s->velocity ); } static void CL_ClientMovement_Physics_Walk(cl_clientmovement_state_t *s) { vec_t friction; vec_t wishspeed; vec_t addspeed; vec_t accelspeed; vec_t f; vec_t gravity; vec3_t forward; vec3_t right; vec3_t up; vec3_t wishvel; vec3_t wishdir; vec3_t yawangles; trace_t trace; // jump if on ground with jump button pressed but only if it has been // released at least once since the last jump if (s->cmd.jump) { if (s->onground && (s->cmd.canjump || !cl_movement_track_canjump.integer)) { s->velocity[2] += cl.movevars_jumpvelocity; s->onground = false; s->cmd.canjump = false; } } else s->cmd.canjump = true; // calculate movement vector VectorSet(yawangles, 0, s->cmd.viewangles[1], 0); AngleVectors(yawangles, forward, right, up); VectorMAM(s->cmd.forwardmove, forward, s->cmd.sidemove, right, wishvel); // split wishvel into wishspeed and wishdir wishspeed = VectorLength(wishvel); if (wishspeed) VectorScale(wishvel, 1 / wishspeed, wishdir); else VectorSet( wishdir, 0.0, 0.0, 0.0 ); // check if onground if (s->onground) { wishspeed = min(wishspeed, cl.movevars_maxspeed); if (s->crouched) wishspeed *= 0.5; // apply edge friction f = sqrt(s->velocity[0] * s->velocity[0] + s->velocity[1] * s->velocity[1]); if (f > 0) { friction = cl.movevars_friction; if (cl.movevars_edgefriction != 1) { vec3_t neworigin2; vec3_t neworigin3; // note: QW uses the full player box for the trace, and yet still // uses s->origin[2] + s->mins[2], which is clearly an bug, but // this mimics it for compatibility VectorSet(neworigin2, s->origin[0] + s->velocity[0]*(16/f), s->origin[1] + s->velocity[1]*(16/f), s->origin[2] + s->mins[2]); VectorSet(neworigin3, neworigin2[0], neworigin2[1], neworigin2[2] - 34); if (cls.protocol == PROTOCOL_QUAKEWORLD) trace = CL_TraceBox(neworigin2, s->mins, s->maxs, neworigin3, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true); else trace = CL_TraceLine(neworigin2, neworigin3, MOVE_NORMAL, s->self, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP, 0, collision_extendmovelength.value, true, true, NULL, true, false); if (trace.fraction == 1 && !trace.startsolid) friction *= cl.movevars_edgefriction; } // apply ground friction f = 1 - s->cmd.frametime * friction * ((f < cl.movevars_stopspeed) ? (cl.movevars_stopspeed / f) : 1); f = max(f, 0); VectorScale(s->velocity, f, s->velocity); } addspeed = wishspeed - DotProduct(s->velocity, wishdir); if (addspeed > 0) { accelspeed = min(cl.movevars_accelerate * s->cmd.frametime * wishspeed, addspeed); VectorMA(s->velocity, accelspeed, wishdir, s->velocity); } gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND)) { if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) s->velocity[2] -= gravity * 0.5f; else s->velocity[2] -= gravity; } if (cls.protocol == PROTOCOL_QUAKEWORLD) s->velocity[2] = 0; if (VectorLength2(s->velocity)) CL_ClientMovement_Move(s); if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !s->onground) { if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) s->velocity[2] -= gravity * 0.5f; } } else { if (s->waterjumptime <= 0) { // apply air speed limit vec_t accel, wishspeed0, wishspeed2, accelqw, strafity; qboolean accelerating; accelqw = cl.movevars_airaccel_qw; wishspeed0 = wishspeed; wishspeed = min(wishspeed, cl.movevars_maxairspeed); if (s->crouched) wishspeed *= 0.5; accel = cl.movevars_airaccelerate; accelerating = (DotProduct(s->velocity, wishdir) > 0); wishspeed2 = wishspeed; // CPM: air control if(cl.movevars_airstopaccelerate != 0) { vec3_t curdir; curdir[0] = s->velocity[0]; curdir[1] = s->velocity[1]; curdir[2] = 0; VectorNormalize(curdir); accel = accel + (cl.movevars_airstopaccelerate - accel) * max(0, -DotProduct(curdir, wishdir)); } strafity = CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, -90) + CL_IsMoveInDirection(s->cmd.forwardmove, s->cmd.sidemove, +90); // if one is nonzero, other is always zero if(cl.movevars_maxairstrafespeed) wishspeed = min(wishspeed, CL_GeomLerp(cl.movevars_maxairspeed, strafity, cl.movevars_maxairstrafespeed)); if(cl.movevars_airstrafeaccelerate) accel = CL_GeomLerp(cl.movevars_airaccelerate, strafity, cl.movevars_airstrafeaccelerate); if(cl.movevars_airstrafeaccel_qw) accelqw = (((strafity > 0.5 ? cl.movevars_airstrafeaccel_qw : cl.movevars_airaccel_qw) >= 0) ? +1 : -1) * (1 - CL_GeomLerp(1 - fabs(cl.movevars_airaccel_qw), strafity, 1 - fabs(cl.movevars_airstrafeaccel_qw))); // !CPM if(cl.movevars_warsowbunny_turnaccel && accelerating && s->cmd.sidemove == 0 && s->cmd.forwardmove != 0) CL_ClientMovement_Physics_PM_AirAccelerate(s, wishdir, wishspeed2); else CL_ClientMovement_Physics_PM_Accelerate(s, wishdir, wishspeed, wishspeed0, accel, accelqw, cl.movevars_airaccel_qw_stretchfactor, cl.movevars_airaccel_sideways_friction / cl.movevars_maxairspeed, cl.movevars_airspeedlimit_nonqw); if(cl.movevars_aircontrol) CL_ClientMovement_Physics_CPM_PM_Aircontrol(s, wishdir, wishspeed2); } gravity = cl.movevars_gravity * cl.movevars_entgravity * s->cmd.frametime; if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) s->velocity[2] -= gravity * 0.5f; else s->velocity[2] -= gravity; CL_ClientMovement_Move(s); if(!(cl.moveflags & MOVEFLAG_NOGRAVITYONGROUND) || !s->onground) { if(cl.moveflags & MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE) s->velocity[2] -= gravity * 0.5f; } } } static void CL_ClientMovement_PlayerMove(cl_clientmovement_state_t *s) { //Con_Printf(" %f", frametime); if (!s->cmd.jump) s->cmd.canjump = true; s->waterjumptime -= s->cmd.frametime; CL_ClientMovement_UpdateStatus(s); if (s->waterlevel >= WATERLEVEL_SWIMMING) CL_ClientMovement_Physics_Swim(s); else CL_ClientMovement_Physics_Walk(s); } extern cvar_t slowmo; void CL_UpdateMoveVars(void) { if (cls.protocol == PROTOCOL_QUAKEWORLD) { cl.moveflags = 0; } else if (cl.stats[STAT_MOVEVARS_TICRATE]) { cl.moveflags = cl.stats[STAT_MOVEFLAGS]; cl.movevars_ticrate = cl.statsf[STAT_MOVEVARS_TICRATE]; cl.movevars_timescale = cl.statsf[STAT_MOVEVARS_TIMESCALE]; cl.movevars_gravity = cl.statsf[STAT_MOVEVARS_GRAVITY]; cl.movevars_stopspeed = cl.statsf[STAT_MOVEVARS_STOPSPEED] ; cl.movevars_maxspeed = cl.statsf[STAT_MOVEVARS_MAXSPEED]; cl.movevars_spectatormaxspeed = cl.statsf[STAT_MOVEVARS_SPECTATORMAXSPEED]; cl.movevars_accelerate = cl.statsf[STAT_MOVEVARS_ACCELERATE]; cl.movevars_airaccelerate = cl.statsf[STAT_MOVEVARS_AIRACCELERATE]; cl.movevars_wateraccelerate = cl.statsf[STAT_MOVEVARS_WATERACCELERATE]; cl.movevars_entgravity = cl.statsf[STAT_MOVEVARS_ENTGRAVITY]; cl.movevars_jumpvelocity = cl.statsf[STAT_MOVEVARS_JUMPVELOCITY]; cl.movevars_edgefriction = cl.statsf[STAT_MOVEVARS_EDGEFRICTION]; cl.movevars_maxairspeed = cl.statsf[STAT_MOVEVARS_MAXAIRSPEED]; cl.movevars_stepheight = cl.statsf[STAT_MOVEVARS_STEPHEIGHT]; cl.movevars_airaccel_qw = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW]; cl.movevars_airaccel_qw_stretchfactor = cl.statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR]; cl.movevars_airaccel_sideways_friction = cl.statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION]; cl.movevars_friction = cl.statsf[STAT_MOVEVARS_FRICTION]; cl.movevars_wallfriction = cl.statsf[STAT_MOVEVARS_WALLFRICTION]; cl.movevars_waterfriction = cl.statsf[STAT_MOVEVARS_WATERFRICTION]; cl.movevars_airstopaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTOPACCELERATE]; cl.movevars_airstrafeaccelerate = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE]; cl.movevars_maxairstrafespeed = cl.statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED]; cl.movevars_airstrafeaccel_qw = cl.statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW]; cl.movevars_aircontrol = cl.statsf[STAT_MOVEVARS_AIRCONTROL]; cl.movevars_aircontrol_power = cl.statsf[STAT_MOVEVARS_AIRCONTROL_POWER]; cl.movevars_aircontrol_penalty = cl.statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY]; cl.movevars_warsowbunny_airforwardaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL]; cl.movevars_warsowbunny_accel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL]; cl.movevars_warsowbunny_topspeed = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED]; cl.movevars_warsowbunny_turnaccel = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL]; cl.movevars_warsowbunny_backtosideratio = cl.statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO]; cl.movevars_airspeedlimit_nonqw = cl.statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW]; } else { cl.moveflags = 0; cl.movevars_ticrate = (cls.demoplayback ? 1.0f : slowmo.value) / bound(1.0f, cl_netfps.value, 1000.0f); cl.movevars_timescale = (cls.demoplayback ? 1.0f : slowmo.value); cl.movevars_gravity = sv_gravity.value; cl.movevars_stopspeed = cl_movement_stopspeed.value; cl.movevars_maxspeed = cl_movement_maxspeed.value; cl.movevars_spectatormaxspeed = cl_movement_maxspeed.value; cl.movevars_accelerate = cl_movement_accelerate.value; cl.movevars_airaccelerate = cl_movement_airaccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_airaccelerate.value; cl.movevars_wateraccelerate = cl_movement_wateraccelerate.value < 0 ? cl_movement_accelerate.value : cl_movement_wateraccelerate.value; cl.movevars_friction = cl_movement_friction.value; cl.movevars_wallfriction = cl_movement_wallfriction.value; cl.movevars_waterfriction = cl_movement_waterfriction.value < 0 ? cl_movement_friction.value : cl_movement_waterfriction.value; cl.movevars_entgravity = 1; cl.movevars_jumpvelocity = cl_movement_jumpvelocity.value; cl.movevars_edgefriction = cl_movement_edgefriction.value; cl.movevars_maxairspeed = cl_movement_maxairspeed.value; cl.movevars_stepheight = cl_movement_stepheight.value; cl.movevars_airaccel_qw = cl_movement_airaccel_qw.value; cl.movevars_airaccel_qw_stretchfactor = 0; cl.movevars_airaccel_sideways_friction = cl_movement_airaccel_sideways_friction.value; cl.movevars_airstopaccelerate = 0; cl.movevars_airstrafeaccelerate = 0; cl.movevars_maxairstrafespeed = 0; cl.movevars_airstrafeaccel_qw = 0; cl.movevars_aircontrol = 0; cl.movevars_aircontrol_power = 2; cl.movevars_aircontrol_penalty = 0; cl.movevars_warsowbunny_airforwardaccel = 0; cl.movevars_warsowbunny_accel = 0; cl.movevars_warsowbunny_topspeed = 0; cl.movevars_warsowbunny_turnaccel = 0; cl.movevars_warsowbunny_backtosideratio = 0; cl.movevars_airspeedlimit_nonqw = 0; } if(!(cl.moveflags & MOVEFLAG_VALID)) { if(gamemode == GAME_NEXUIZ) // Legacy hack to work with old servers of Nexuiz. cl.moveflags = MOVEFLAG_Q2AIRACCELERATE; } if(cl.movevars_aircontrol_power <= 0) cl.movevars_aircontrol_power = 2; // CPMA default } void CL_ClientMovement_PlayerMove_Frame(cl_clientmovement_state_t *s) { // if a move is more than 50ms, do it as two moves (matching qwsv) //Con_Printf("%i ", s.cmd.msec); if(s->cmd.frametime > 0.0005) { if (s->cmd.frametime > 0.05) { s->cmd.frametime /= 2; CL_ClientMovement_PlayerMove(s); } CL_ClientMovement_PlayerMove(s); } else { // we REALLY need this handling to happen, even if the move is not executed if (!s->cmd.jump) s->cmd.canjump = true; } } void CL_ClientMovement_Replay(void) { int i; double totalmovemsec; cl_clientmovement_state_t s; VectorCopy(cl.mvelocity[0], cl.movement_velocity); if (cl.movement_predicted && !cl.movement_replay) return; if (!cl_movement_replay.integer) return; // set up starting state for the series of moves memset(&s, 0, sizeof(s)); VectorCopy(cl.entities[cl.playerentity].state_current.origin, s.origin); VectorCopy(cl.mvelocity[0], s.velocity); s.crouched = true; // will be updated on first move //Con_Printf("movement replay starting org %f %f %f vel %f %f %f\n", s.origin[0], s.origin[1], s.origin[2], s.velocity[0], s.velocity[1], s.velocity[2]); totalmovemsec = 0; for (i = 0;i < CL_MAX_USERCMDS;i++) if (cl.movecmd[i].sequence > cls.servermovesequence) totalmovemsec += cl.movecmd[i].msec; cl.movement_predicted = totalmovemsec >= cl_movement_minping.value && cls.servermovesequence && (cl_movement.integer && !cls.demoplayback && cls.signon == SIGNONS && cl.stats[STAT_HEALTH] > 0 && !cl.intermission); //Con_Printf("%i = %.0f >= %.0f && %u && (%i && %i && %i == %i && %i > 0 && %i\n", cl.movement_predicted, totalmovemsec, cl_movement_minping.value, cls.servermovesequence, cl_movement.integer, !cls.demoplayback, cls.signon, SIGNONS, cl.stats[STAT_HEALTH], !cl.intermission); if (cl.movement_predicted) { //Con_Printf("%ims\n", cl.movecmd[0].msec); // replay the input queue to predict current location // note: this relies on the fact there's always one queue item at the end // find how many are still valid for (i = 0;i < CL_MAX_USERCMDS;i++) if (cl.movecmd[i].sequence <= cls.servermovesequence) break; // now walk them in oldest to newest order for (i--;i >= 0;i--) { s.cmd = cl.movecmd[i]; if (i < CL_MAX_USERCMDS - 1) s.cmd.canjump = cl.movecmd[i+1].canjump; CL_ClientMovement_PlayerMove_Frame(&s); cl.movecmd[i].canjump = s.cmd.canjump; } //Con_Printf("\n"); CL_ClientMovement_UpdateStatus(&s); } else { // get the first movement queue entry to know whether to crouch and such s.cmd = cl.movecmd[0]; } if (!cls.demoplayback) // for bob, speedometer { cl.movement_replay = false; // update the interpolation target position and velocity VectorCopy(s.origin, cl.movement_origin); VectorCopy(s.velocity, cl.movement_velocity); } // update the onground flag if appropriate if (cl.movement_predicted) { // when predicted we simply set the flag according to the UpdateStatus cl.onground = s.onground; } else { // when not predicted, cl.onground is cleared by cl_parse.c each time // an update packet is received, but can be forced on here to hide // server inconsistencies in the onground flag // (which mostly occur when stepping up stairs at very high framerates // where after the step up the move continues forward and not // downward so the ground is not detected) // // such onground inconsistencies can cause jittery gun bobbing and // stair smoothing, so we set onground if UpdateStatus says so if (s.onground) cl.onground = true; } } static void QW_MSG_WriteDeltaUsercmd(sizebuf_t *buf, usercmd_t *from, usercmd_t *to) { int bits; bits = 0; if (to->viewangles[0] != from->viewangles[0]) bits |= QW_CM_ANGLE1; if (to->viewangles[1] != from->viewangles[1]) bits |= QW_CM_ANGLE2; if (to->viewangles[2] != from->viewangles[2]) bits |= QW_CM_ANGLE3; if (to->forwardmove != from->forwardmove) bits |= QW_CM_FORWARD; if (to->sidemove != from->sidemove) bits |= QW_CM_SIDE; if (to->upmove != from->upmove) bits |= QW_CM_UP; if (to->buttons != from->buttons) bits |= QW_CM_BUTTONS; if (to->impulse != from->impulse) bits |= QW_CM_IMPULSE; MSG_WriteByte(buf, bits); if (bits & QW_CM_ANGLE1) MSG_WriteAngle16i(buf, to->viewangles[0]); if (bits & QW_CM_ANGLE2) MSG_WriteAngle16i(buf, to->viewangles[1]); if (bits & QW_CM_ANGLE3) MSG_WriteAngle16i(buf, to->viewangles[2]); if (bits & QW_CM_FORWARD) MSG_WriteShort(buf, (short) to->forwardmove); if (bits & QW_CM_SIDE) MSG_WriteShort(buf, (short) to->sidemove); if (bits & QW_CM_UP) MSG_WriteShort(buf, (short) to->upmove); if (bits & QW_CM_BUTTONS) MSG_WriteByte(buf, to->buttons); if (bits & QW_CM_IMPULSE) MSG_WriteByte(buf, to->impulse); MSG_WriteByte(buf, to->msec); } void CL_NewFrameReceived(int num) { if (developer_networkentities.integer >= 10) Con_Printf("recv: svc_entities %i\n", num); cl.latestframenums[cl.latestframenumsposition] = num; cl.latestsendnums[cl.latestframenumsposition] = cl.cmd.sequence; cl.latestframenumsposition = (cl.latestframenumsposition + 1) % LATESTFRAMENUMS; } void CL_RotateMoves(const matrix4x4_t *m) { // rotate viewangles in all previous moves vec3_t v; vec3_t f, r, u; int i; for (i = 0;i < CL_MAX_USERCMDS;i++) { if (cl.movecmd[i].sequence > cls.servermovesequence) { usercmd_t *c = &cl.movecmd[i]; AngleVectors(c->viewangles, f, r, u); Matrix4x4_Transform(m, f, v); VectorCopy(v, f); Matrix4x4_Transform(m, u, v); VectorCopy(v, u); AnglesFromVectors(c->viewangles, f, u, false); } } } /* ============== CL_SendMove ============== */ usercmd_t nullcmd; // for delta compression of qw moves void CL_SendMove(void) { int i, j, packetloss; int checksumindex; int bits; int maxusercmds; usercmd_t *cmd; sizebuf_t buf; unsigned char data[1024]; double packettime; int msecdelta; qboolean quemove; qboolean important; // if playing a demo, do nothing if (!cls.netcon) return; // we don't que moves during a lag spike (potential network timeout) quemove = realtime - cl.last_received_message < cl_movement_nettimeout.value; // we build up cl.cmd and then decide whether to send or not // we store this into cl.movecmd[0] for prediction each frame even if we // do not send, to make sure that prediction is instant cl.cmd.time = cl.time; cl.cmd.sequence = cls.netcon->outgoing_unreliable_sequence; // set button bits // LordHavoc: added 6 new buttons and use and chat buttons, and prydon cursor active button bits = 0; if (in_attack.state & 3) bits |= 1; if (in_jump.state & 3) bits |= 2; if (in_button3.state & 3) bits |= 4; if (in_button4.state & 3) bits |= 8; if (in_button5.state & 3) bits |= 16; if (in_button6.state & 3) bits |= 32; if (in_button7.state & 3) bits |= 64; if (in_button8.state & 3) bits |= 128; if (in_use.state & 3) bits |= 256; if (key_dest != key_game || key_consoleactive) bits |= 512; if (cl_prydoncursor.integer > 0) bits |= 1024; if (in_button9.state & 3) bits |= 2048; if (in_button10.state & 3) bits |= 4096; if (in_button11.state & 3) bits |= 8192; if (in_button12.state & 3) bits |= 16384; if (in_button13.state & 3) bits |= 32768; if (in_button14.state & 3) bits |= 65536; if (in_button15.state & 3) bits |= 131072; if (in_button16.state & 3) bits |= 262144; // button bits 19-31 unused currently // rotate/zoom view serverside if PRYDON_CLIENTCURSOR cursor is at edge of screen if(cl_prydoncursor.integer > 0) { if (cl.cmd.cursor_screen[0] <= -1) bits |= 8; if (cl.cmd.cursor_screen[0] >= 1) bits |= 16; if (cl.cmd.cursor_screen[1] <= -1) bits |= 32; if (cl.cmd.cursor_screen[1] >= 1) bits |= 64; } // set buttons and impulse cl.cmd.buttons = bits; cl.cmd.impulse = in_impulse; // set viewangles VectorCopy(cl.viewangles, cl.cmd.viewangles); msecdelta = (int)(floor(cl.cmd.time * 1000) - floor(cl.movecmd[1].time * 1000)); cl.cmd.msec = (unsigned char)bound(0, msecdelta, 255); // ridiculous value rejection (matches qw) if (cl.cmd.msec > 250) cl.cmd.msec = 100; cl.cmd.frametime = cl.cmd.msec * (1.0 / 1000.0); switch(cls.protocol) { case PROTOCOL_QUAKEWORLD: // quakeworld uses a different cvar with opposite meaning, for compatibility cl.cmd.predicted = cl_nopred.integer == 0; break; case PROTOCOL_DARKPLACES6: case PROTOCOL_DARKPLACES7: cl.cmd.predicted = cl_movement.integer != 0; break; default: cl.cmd.predicted = false; break; } // movement is set by input code (forwardmove/sidemove/upmove) // always dump the first two moves, because they may contain leftover inputs from the last level if (cl.cmd.sequence <= 2) cl.cmd.forwardmove = cl.cmd.sidemove = cl.cmd.upmove = cl.cmd.impulse = cl.cmd.buttons = 0; cl.cmd.jump = (cl.cmd.buttons & 2) != 0; cl.cmd.crouch = 0; switch (cls.protocol) { case PROTOCOL_QUAKEWORLD: case PROTOCOL_QUAKE: case PROTOCOL_QUAKEDP: case PROTOCOL_NEHAHRAMOVIE: case PROTOCOL_NEHAHRABJP: case PROTOCOL_NEHAHRABJP2: case PROTOCOL_NEHAHRABJP3: case PROTOCOL_DARKPLACES1: case PROTOCOL_DARKPLACES2: case PROTOCOL_DARKPLACES3: case PROTOCOL_DARKPLACES4: case PROTOCOL_DARKPLACES5: break; case PROTOCOL_DARKPLACES6: case PROTOCOL_DARKPLACES7: // FIXME: cl.cmd.buttons & 16 is +button5, Nexuiz/Xonotic specific cl.cmd.crouch = (cl.cmd.buttons & 16) != 0; break; case PROTOCOL_UNKNOWN: break; } if (quemove) cl.movecmd[0] = cl.cmd; // don't predict more than 200fps if (realtime >= cl.lastpackettime + 0.005) cl.movement_replay = true; // redo the prediction // now decide whether to actually send this move // (otherwise it is only for prediction) // don't send too often or else network connections can get clogged by a // high renderer framerate packettime = 1.0 / bound(1, cl_netfps.value, 1000); if (cl.movevars_timescale && cl.movevars_ticrate) { float maxtic = cl.movevars_ticrate / cl.movevars_timescale; packettime = min(packettime, maxtic); } // do not send 0ms packets because they mess up physics if(cl.cmd.msec == 0 && cl.time > cl.oldtime && (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS)) return; // always send if buttons changed or an impulse is pending // even if it violates the rate limit! important = (cl.cmd.impulse || (cl_netimmediatebuttons.integer && cl.cmd.buttons != cl.movecmd[1].buttons)); // don't send too often (cl_netfps) if (!important && realtime < cl.lastpackettime + packettime) return; // don't choke the connection with packets (obey rate limit) // it is important that this check be last, because it adds a new // frame to the shownetgraph output and any cancelation after this // will produce a nasty spike-like look to the netgraph // we also still send if it is important if (!NetConn_CanSend(cls.netcon) && !important) return; // try to round off the lastpackettime to a multiple of the packet interval // (this causes it to emit packets at a steady beat) if (packettime > 0) cl.lastpackettime = floor(realtime / packettime) * packettime; else cl.lastpackettime = realtime; buf.maxsize = sizeof(data); buf.cursize = 0; buf.data = data; // send the movement message // PROTOCOL_QUAKE clc_move = 16 bytes total // PROTOCOL_QUAKEDP clc_move = 16 bytes total // PROTOCOL_NEHAHRAMOVIE clc_move = 16 bytes total // PROTOCOL_DARKPLACES1 clc_move = 19 bytes total // PROTOCOL_DARKPLACES2 clc_move = 25 bytes total // PROTOCOL_DARKPLACES3 clc_move = 25 bytes total // PROTOCOL_DARKPLACES4 clc_move = 19 bytes total // PROTOCOL_DARKPLACES5 clc_move = 19 bytes total // PROTOCOL_DARKPLACES6 clc_move = 52 bytes total // PROTOCOL_DARKPLACES7 clc_move = 56 bytes total per move (can be up to 16 moves) // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll) // set prydon cursor info CL_UpdatePrydonCursor(); if (cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon == SIGNONS) { switch (cls.protocol) { case PROTOCOL_QUAKEWORLD: MSG_WriteByte(&buf, qw_clc_move); // save the position for a checksum byte checksumindex = buf.cursize; MSG_WriteByte(&buf, 0); // packet loss percentage for (j = 0, packetloss = 0;j < NETGRAPH_PACKETS;j++) if (cls.netcon->incoming_netgraph[j].unreliablebytes == NETGRAPH_LOSTPACKET) packetloss++; packetloss = packetloss * 100 / NETGRAPH_PACKETS; MSG_WriteByte(&buf, packetloss); // write most recent 3 moves QW_MSG_WriteDeltaUsercmd(&buf, &nullcmd, &cl.movecmd[2]); QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[2], &cl.movecmd[1]); QW_MSG_WriteDeltaUsercmd(&buf, &cl.movecmd[1], &cl.cmd); // calculate the checksum buf.data[checksumindex] = COM_BlockSequenceCRCByteQW(buf.data + checksumindex + 1, buf.cursize - checksumindex - 1, cls.netcon->outgoing_unreliable_sequence); // if delta compression history overflows, request no delta if (cls.netcon->outgoing_unreliable_sequence - cl.qw_validsequence >= QW_UPDATE_BACKUP-1) cl.qw_validsequence = 0; // request delta compression if appropriate if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording) { cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = cl.qw_validsequence; MSG_WriteByte(&buf, qw_clc_delta); MSG_WriteByte(&buf, cl.qw_validsequence & 255); } else cl.qw_deltasequence[cls.netcon->outgoing_unreliable_sequence & QW_UPDATE_MASK] = -1; break; case PROTOCOL_QUAKE: case PROTOCOL_QUAKEDP: case PROTOCOL_NEHAHRAMOVIE: case PROTOCOL_NEHAHRABJP: case PROTOCOL_NEHAHRABJP2: case PROTOCOL_NEHAHRABJP3: // 5 bytes MSG_WriteByte (&buf, clc_move); MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time // 3 bytes (6 bytes in proquake) if (cls.proquake_servermod == 1) // MOD_PROQUAKE { for (i = 0;i < 3;i++) MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); } else { for (i = 0;i < 3;i++) MSG_WriteAngle8i (&buf, cl.cmd.viewangles[i]); } // 6 bytes MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); MSG_WriteCoord16i (&buf, cl.cmd.sidemove); MSG_WriteCoord16i (&buf, cl.cmd.upmove); // 2 bytes MSG_WriteByte (&buf, cl.cmd.buttons); MSG_WriteByte (&buf, cl.cmd.impulse); break; case PROTOCOL_DARKPLACES2: case PROTOCOL_DARKPLACES3: // 5 bytes MSG_WriteByte (&buf, clc_move); MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time // 12 bytes for (i = 0;i < 3;i++) MSG_WriteAngle32f (&buf, cl.cmd.viewangles[i]); // 6 bytes MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); MSG_WriteCoord16i (&buf, cl.cmd.sidemove); MSG_WriteCoord16i (&buf, cl.cmd.upmove); // 2 bytes MSG_WriteByte (&buf, cl.cmd.buttons); MSG_WriteByte (&buf, cl.cmd.impulse); break; case PROTOCOL_DARKPLACES1: case PROTOCOL_DARKPLACES4: case PROTOCOL_DARKPLACES5: // 5 bytes MSG_WriteByte (&buf, clc_move); MSG_WriteFloat (&buf, cl.cmd.time); // last server packet time // 6 bytes for (i = 0;i < 3;i++) MSG_WriteAngle16i (&buf, cl.cmd.viewangles[i]); // 6 bytes MSG_WriteCoord16i (&buf, cl.cmd.forwardmove); MSG_WriteCoord16i (&buf, cl.cmd.sidemove); MSG_WriteCoord16i (&buf, cl.cmd.upmove); // 2 bytes MSG_WriteByte (&buf, cl.cmd.buttons); MSG_WriteByte (&buf, cl.cmd.impulse); case PROTOCOL_DARKPLACES6: case PROTOCOL_DARKPLACES7: // set the maxusercmds variable to limit how many should be sent maxusercmds = bound(1, cl_netrepeatinput.integer + 1, min(3, CL_MAX_USERCMDS)); // when movement prediction is off, there's not much point in repeating old input as it will just be ignored if (!cl.cmd.predicted) maxusercmds = 1; // send the latest moves in order, the old ones will be // ignored by the server harmlessly, however if the previous // packets were lost these moves will be used // // this reduces packet loss impact on gameplay. for (j = 0, cmd = &cl.movecmd[maxusercmds-1];j < maxusercmds;j++, cmd--) { // don't repeat any stale moves if (cmd->sequence && cmd->sequence < cls.servermovesequence) continue; // 5/9 bytes MSG_WriteByte (&buf, clc_move); if (cls.protocol != PROTOCOL_DARKPLACES6) MSG_WriteLong (&buf, cmd->predicted ? cmd->sequence : 0); MSG_WriteFloat (&buf, cmd->time); // last server packet time // 6 bytes for (i = 0;i < 3;i++) MSG_WriteAngle16i (&buf, cmd->viewangles[i]); // 6 bytes MSG_WriteCoord16i (&buf, cmd->forwardmove); MSG_WriteCoord16i (&buf, cmd->sidemove); MSG_WriteCoord16i (&buf, cmd->upmove); // 5 bytes MSG_WriteLong (&buf, cmd->buttons); MSG_WriteByte (&buf, cmd->impulse); // PRYDON_CLIENTCURSOR // 30 bytes MSG_WriteShort (&buf, (short)(cmd->cursor_screen[0] * 32767.0f)); MSG_WriteShort (&buf, (short)(cmd->cursor_screen[1] * 32767.0f)); MSG_WriteFloat (&buf, cmd->cursor_start[0]); MSG_WriteFloat (&buf, cmd->cursor_start[1]); MSG_WriteFloat (&buf, cmd->cursor_start[2]); MSG_WriteFloat (&buf, cmd->cursor_impact[0]); MSG_WriteFloat (&buf, cmd->cursor_impact[1]); MSG_WriteFloat (&buf, cmd->cursor_impact[2]); MSG_WriteShort (&buf, cmd->cursor_entitynumber); } break; case PROTOCOL_UNKNOWN: break; } } if (cls.protocol != PROTOCOL_QUAKEWORLD && buf.cursize) { // ack entity frame numbers received since the last input was sent // (redundent to improve handling of client->server packet loss) // if cl_netrepeatinput is 1 and client framerate matches server // framerate, this is 10 bytes, if client framerate is lower this // will be more... unsigned int oldsequence = cl.cmd.sequence; unsigned int delta = bound(1, cl_netrepeatinput.integer + 1, 3); if (oldsequence > delta) oldsequence = oldsequence - delta; else oldsequence = 1; for (i = 0;i < LATESTFRAMENUMS;i++) { j = (cl.latestframenumsposition + i) % LATESTFRAMENUMS; if (cl.latestsendnums[j] >= oldsequence) { if (developer_networkentities.integer >= 10) Con_Printf("send clc_ackframe %i\n", cl.latestframenums[j]); MSG_WriteByte(&buf, clc_ackframe); MSG_WriteLong(&buf, cl.latestframenums[j]); } } } // PROTOCOL_DARKPLACES6 = 67 bytes per packet // PROTOCOL_DARKPLACES7 = 71 bytes per packet // acknowledge any recently received data blocks for (i = 0;i < CL_MAX_DOWNLOADACKS && (cls.dp_downloadack[i].start || cls.dp_downloadack[i].size);i++) { MSG_WriteByte(&buf, clc_ackdownloaddata); MSG_WriteLong(&buf, cls.dp_downloadack[i].start); MSG_WriteShort(&buf, cls.dp_downloadack[i].size); cls.dp_downloadack[i].start = 0; cls.dp_downloadack[i].size = 0; } // send the reliable message (forwarded commands) if there is one if (buf.cursize || cls.netcon->message.cursize) NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol, max(20*(buf.cursize+40), cl_rate.integer), cl_rate_burstsize.integer, false); if (quemove) { // update the cl.movecmd array which holds the most recent moves, // because we now need a new slot for the next input for (i = CL_MAX_USERCMDS - 1;i >= 1;i--) cl.movecmd[i] = cl.movecmd[i-1]; cl.movecmd[0].msec = 0; cl.movecmd[0].frametime = 0; } // clear button 'click' states in_attack.state &= ~2; in_jump.state &= ~2; in_button3.state &= ~2; in_button4.state &= ~2; in_button5.state &= ~2; in_button6.state &= ~2; in_button7.state &= ~2; in_button8.state &= ~2; in_use.state &= ~2; in_button9.state &= ~2; in_button10.state &= ~2; in_button11.state &= ~2; in_button12.state &= ~2; in_button13.state &= ~2; in_button14.state &= ~2; in_button15.state &= ~2; in_button16.state &= ~2; // clear impulse in_impulse = 0; if (cls.netcon->message.overflowed) { Con_Print("CL_SendMove: lost server connection\n"); CL_Disconnect(); SV_LockThreadMutex(); Host_ShutdownServer(); SV_UnlockThreadMutex(); } } /* ============ CL_InitInput ============ */ void CL_InitInput (void) { Cmd_AddCommand ("+moveup",IN_UpDown, "swim upward"); Cmd_AddCommand ("-moveup",IN_UpUp, "stop swimming upward"); Cmd_AddCommand ("+movedown",IN_DownDown, "swim downward"); Cmd_AddCommand ("-movedown",IN_DownUp, "stop swimming downward"); Cmd_AddCommand ("+left",IN_LeftDown, "turn left"); Cmd_AddCommand ("-left",IN_LeftUp, "stop turning left"); Cmd_AddCommand ("+right",IN_RightDown, "turn right"); Cmd_AddCommand ("-right",IN_RightUp, "stop turning right"); Cmd_AddCommand ("+forward",IN_ForwardDown, "move forward"); Cmd_AddCommand ("-forward",IN_ForwardUp, "stop moving forward"); Cmd_AddCommand ("+back",IN_BackDown, "move backward"); Cmd_AddCommand ("-back",IN_BackUp, "stop moving backward"); Cmd_AddCommand ("+lookup", IN_LookupDown, "look upward"); Cmd_AddCommand ("-lookup", IN_LookupUp, "stop looking upward"); Cmd_AddCommand ("+lookdown", IN_LookdownDown, "look downward"); Cmd_AddCommand ("-lookdown", IN_LookdownUp, "stop looking downward"); Cmd_AddCommand ("+strafe", IN_StrafeDown, "activate strafing mode (move instead of turn)"); Cmd_AddCommand ("-strafe", IN_StrafeUp, "deactivate strafing mode"); Cmd_AddCommand ("+moveleft", IN_MoveleftDown, "strafe left"); Cmd_AddCommand ("-moveleft", IN_MoveleftUp, "stop strafing left"); Cmd_AddCommand ("+moveright", IN_MoverightDown, "strafe right"); Cmd_AddCommand ("-moveright", IN_MoverightUp, "stop strafing right"); Cmd_AddCommand ("+speed", IN_SpeedDown, "activate run mode (faster movement and turning)"); Cmd_AddCommand ("-speed", IN_SpeedUp, "deactivate run mode"); Cmd_AddCommand ("+attack", IN_AttackDown, "begin firing"); Cmd_AddCommand ("-attack", IN_AttackUp, "stop firing"); Cmd_AddCommand ("+jump", IN_JumpDown, "jump"); Cmd_AddCommand ("-jump", IN_JumpUp, "end jump (so you can jump again)"); Cmd_AddCommand ("impulse", IN_Impulse, "send an impulse number to server (select weapon, use item, etc)"); Cmd_AddCommand ("+klook", IN_KLookDown, "activate keyboard looking mode, do not recenter view"); Cmd_AddCommand ("-klook", IN_KLookUp, "deactivate keyboard looking mode"); Cmd_AddCommand ("+mlook", IN_MLookDown, "activate mouse looking mode, do not recenter view"); Cmd_AddCommand ("-mlook", IN_MLookUp, "deactivate mouse looking mode"); // LordHavoc: added use button Cmd_AddCommand ("+use", IN_UseDown, "use something (may be used by some mods)"); Cmd_AddCommand ("-use", IN_UseUp, "stop using something"); // LordHavoc: added 6 new buttons Cmd_AddCommand ("+button3", IN_Button3Down, "activate button3 (behavior depends on mod)"); Cmd_AddCommand ("-button3", IN_Button3Up, "deactivate button3"); Cmd_AddCommand ("+button4", IN_Button4Down, "activate button4 (behavior depends on mod)"); Cmd_AddCommand ("-button4", IN_Button4Up, "deactivate button4"); Cmd_AddCommand ("+button5", IN_Button5Down, "activate button5 (behavior depends on mod)"); Cmd_AddCommand ("-button5", IN_Button5Up, "deactivate button5"); Cmd_AddCommand ("+button6", IN_Button6Down, "activate button6 (behavior depends on mod)"); Cmd_AddCommand ("-button6", IN_Button6Up, "deactivate button6"); Cmd_AddCommand ("+button7", IN_Button7Down, "activate button7 (behavior depends on mod)"); Cmd_AddCommand ("-button7", IN_Button7Up, "deactivate button7"); Cmd_AddCommand ("+button8", IN_Button8Down, "activate button8 (behavior depends on mod)"); Cmd_AddCommand ("-button8", IN_Button8Up, "deactivate button8"); Cmd_AddCommand ("+button9", IN_Button9Down, "activate button9 (behavior depends on mod)"); Cmd_AddCommand ("-button9", IN_Button9Up, "deactivate button9"); Cmd_AddCommand ("+button10", IN_Button10Down, "activate button10 (behavior depends on mod)"); Cmd_AddCommand ("-button10", IN_Button10Up, "deactivate button10"); Cmd_AddCommand ("+button11", IN_Button11Down, "activate button11 (behavior depends on mod)"); Cmd_AddCommand ("-button11", IN_Button11Up, "deactivate button11"); Cmd_AddCommand ("+button12", IN_Button12Down, "activate button12 (behavior depends on mod)"); Cmd_AddCommand ("-button12", IN_Button12Up, "deactivate button12"); Cmd_AddCommand ("+button13", IN_Button13Down, "activate button13 (behavior depends on mod)"); Cmd_AddCommand ("-button13", IN_Button13Up, "deactivate button13"); Cmd_AddCommand ("+button14", IN_Button14Down, "activate button14 (behavior depends on mod)"); Cmd_AddCommand ("-button14", IN_Button14Up, "deactivate button14"); Cmd_AddCommand ("+button15", IN_Button15Down, "activate button15 (behavior depends on mod)"); Cmd_AddCommand ("-button15", IN_Button15Up, "deactivate button15"); Cmd_AddCommand ("+button16", IN_Button16Down, "activate button16 (behavior depends on mod)"); Cmd_AddCommand ("-button16", IN_Button16Up, "deactivate button16"); // LordHavoc: added bestweapon command Cmd_AddCommand ("bestweapon", IN_BestWeapon, "send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1)"); #if 0 Cmd_AddCommand ("cycleweapon", IN_CycleWeapon, "send an impulse number to server to select the next usable weapon out of several (example: 9 4 8) if you are holding one of these, and choose the first one if you are holding none of these"); #endif Cmd_AddCommand ("register_bestweapon", IN_BestWeapon_Register_f, "(for QC usage only) change weapon parameters to be used by bestweapon; stuffcmd this in ClientConnect"); Cvar_RegisterVariable(&cl_movecliptokeyboard); Cvar_RegisterVariable(&cl_movement); Cvar_RegisterVariable(&cl_movement_replay); Cvar_RegisterVariable(&cl_movement_nettimeout); Cvar_RegisterVariable(&cl_movement_minping); Cvar_RegisterVariable(&cl_movement_track_canjump); Cvar_RegisterVariable(&cl_movement_maxspeed); Cvar_RegisterVariable(&cl_movement_maxairspeed); Cvar_RegisterVariable(&cl_movement_stopspeed); Cvar_RegisterVariable(&cl_movement_friction); Cvar_RegisterVariable(&cl_movement_wallfriction); Cvar_RegisterVariable(&cl_movement_waterfriction); Cvar_RegisterVariable(&cl_movement_edgefriction); Cvar_RegisterVariable(&cl_movement_stepheight); Cvar_RegisterVariable(&cl_movement_accelerate); Cvar_RegisterVariable(&cl_movement_airaccelerate); Cvar_RegisterVariable(&cl_movement_wateraccelerate); Cvar_RegisterVariable(&cl_movement_jumpvelocity); Cvar_RegisterVariable(&cl_movement_airaccel_qw); Cvar_RegisterVariable(&cl_movement_airaccel_sideways_friction); Cvar_RegisterVariable(&cl_nopred); Cvar_RegisterVariable(&in_pitch_min); Cvar_RegisterVariable(&in_pitch_max); Cvar_RegisterVariable(&m_filter); Cvar_RegisterVariable(&m_accelerate); Cvar_RegisterVariable(&m_accelerate_minspeed); Cvar_RegisterVariable(&m_accelerate_maxspeed); Cvar_RegisterVariable(&m_accelerate_filter); Cvar_RegisterVariable(&cl_netfps); Cvar_RegisterVariable(&cl_netrepeatinput); Cvar_RegisterVariable(&cl_netimmediatebuttons); Cvar_RegisterVariable(&cl_nodelta); Cvar_RegisterVariable(&cl_csqc_generatemousemoveevents); } darkplaces/model_brush.c0000664000175000017500000126322413067716220014644 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "image.h" #include "r_shadow.h" #include "polygon.h" #include "curves.h" #include "wad.h" //cvar_t r_subdivide_size = {CVAR_SAVE, "r_subdivide_size", "128", "how large water polygons should be (smaller values produce more polygons which give better warping effects)"}; cvar_t mod_bsp_portalize = {0, "mod_bsp_portalize", "1", "enables portal generation from BSP tree (may take several seconds per map), used by r_drawportals, r_useportalculling, r_shadow_realtime_world_compileportalculling, sv_cullentities_portal"}; cvar_t r_novis = {0, "r_novis", "0", "draws whole level, see also sv_cullentities_pvs 0"}; cvar_t r_nosurftextures = {0, "r_nosurftextures", "0", "pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case)"}; cvar_t r_subdivisions_tolerance = {0, "r_subdivisions_tolerance", "4", "maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality)"}; cvar_t r_subdivisions_mintess = {0, "r_subdivisions_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; cvar_t r_subdivisions_maxtess = {0, "r_subdivisions_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; cvar_t r_subdivisions_maxvertices = {0, "r_subdivisions_maxvertices", "65536", "maximum vertices allowed per subdivided curve"}; cvar_t r_subdivisions_collision_tolerance = {0, "r_subdivisions_collision_tolerance", "15", "maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering)"}; cvar_t r_subdivisions_collision_mintess = {0, "r_subdivisions_collision_mintess", "0", "minimum number of subdivisions (values above 0 will smooth curves that don't need it)"}; cvar_t r_subdivisions_collision_maxtess = {0, "r_subdivisions_collision_maxtess", "1024", "maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing)"}; cvar_t r_subdivisions_collision_maxvertices = {0, "r_subdivisions_collision_maxvertices", "4225", "maximum vertices allowed per subdivided curve"}; cvar_t r_trippy = {0, "r_trippy", "0", "easter egg"}; cvar_t r_fxaa = {CVAR_SAVE, "r_fxaa", "0", "fast approximate anti aliasing"}; cvar_t mod_noshader_default_offsetmapping = {CVAR_SAVE, "mod_noshader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are not using q3 shader files"}; cvar_t mod_obj_orientation = {0, "mod_obj_orientation", "1", "fix orientation of OBJ models to the usual conventions (if zero, use coordinates as is)"}; cvar_t mod_q2bsp_littransparentsurfaces = {0, "mod_q2bsp_littransparentsurfaces", "0", "allows lighting on rain in 3v3gloom3 and other cases of transparent surfaces that have lightmaps that were ignored by quake2"}; cvar_t mod_q3bsp_curves_collisions = {0, "mod_q3bsp_curves_collisions", "1", "enables collisions with curves (SLOW)"}; cvar_t mod_q3bsp_curves_collisions_stride = {0, "mod_q3bsp_curves_collisions_stride", "16", "collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; cvar_t mod_q3bsp_curves_stride = {0, "mod_q3bsp_curves_stride", "16", "particle effect collisions against curves: optimize performance by doing a combined collision check for this triangle amount first (-1 avoids any box tests)"}; cvar_t mod_q3bsp_optimizedtraceline = {0, "mod_q3bsp_optimizedtraceline", "1", "whether to use optimized traceline code for line traces (as opposed to tracebox code)"}; cvar_t mod_q3bsp_debugtracebrush = {0, "mod_q3bsp_debugtracebrush", "0", "selects different tracebrush bsp recursion algorithms (for debugging purposes only)"}; cvar_t mod_q3bsp_lightmapmergepower = {CVAR_SAVE, "mod_q3bsp_lightmapmergepower", "4", "merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ..."}; cvar_t mod_q3bsp_nolightmaps = {CVAR_SAVE, "mod_q3bsp_nolightmaps", "0", "do not load lightmaps in Q3BSP maps (to save video RAM, but be warned: it looks ugly)"}; cvar_t mod_q3bsp_tracelineofsight_brushes = {0, "mod_q3bsp_tracelineofsight_brushes", "0", "enables culling of entities behind detail brushes, curves, etc"}; cvar_t mod_q3bsp_sRGBlightmaps = {0, "mod_q3bsp_sRGBlightmaps", "0", "treat lightmaps from Q3 maps as sRGB when vid_sRGB is active"}; cvar_t mod_q3shader_default_offsetmapping = {CVAR_SAVE, "mod_q3shader_default_offsetmapping", "1", "use offsetmapping by default on all surfaces that are using q3 shader files"}; cvar_t mod_q3shader_default_offsetmapping_scale = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_scale", "1", "default scale used for offsetmapping"}; cvar_t mod_q3shader_default_offsetmapping_bias = {CVAR_SAVE, "mod_q3shader_default_offsetmapping_bias", "0", "default bias used for offsetmapping"}; cvar_t mod_q3shader_default_polygonfactor = {0, "mod_q3shader_default_polygonfactor", "0", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; cvar_t mod_q3shader_default_polygonoffset = {0, "mod_q3shader_default_polygonoffset", "-2", "biases depth values of 'polygonoffset' shaders to prevent z-fighting artifacts"}; cvar_t mod_q3shader_force_addalpha = {0, "mod_q3shader_force_addalpha", "0", "treat GL_ONE GL_ONE (or add) blendfunc as GL_SRC_ALPHA GL_ONE for compatibility with older DarkPlaces releases"}; cvar_t mod_q3shader_force_terrain_alphaflag = {0, "mod_q3shader_force_terrain_alphaflag", "0", "for multilayered terrain shaders force TEXF_ALPHA flag on both layers"}; cvar_t mod_q1bsp_polygoncollisions = {0, "mod_q1bsp_polygoncollisions", "0", "disables use of precomputed cliphulls and instead collides with polygons (uses Bounding Interval Hierarchy optimizations)"}; cvar_t mod_collision_bih = {0, "mod_collision_bih", "1", "enables use of generated Bounding Interval Hierarchy tree instead of compiled bsp tree in collision code"}; cvar_t mod_recalculatenodeboxes = {0, "mod_recalculatenodeboxes", "1", "enables use of generated node bounding boxes based on BSP tree portal reconstruction, rather than the node boxes supplied by the map compiler"}; static texture_t mod_q1bsp_texture_solid; static texture_t mod_q1bsp_texture_sky; static texture_t mod_q1bsp_texture_lava; static texture_t mod_q1bsp_texture_slime; static texture_t mod_q1bsp_texture_water; static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end); void Mod_BrushInit(void) { // Cvar_RegisterVariable(&r_subdivide_size); Cvar_RegisterVariable(&mod_bsp_portalize); Cvar_RegisterVariable(&r_novis); Cvar_RegisterVariable(&r_nosurftextures); Cvar_RegisterVariable(&r_subdivisions_tolerance); Cvar_RegisterVariable(&r_subdivisions_mintess); Cvar_RegisterVariable(&r_subdivisions_maxtess); Cvar_RegisterVariable(&r_subdivisions_maxvertices); Cvar_RegisterVariable(&r_subdivisions_collision_tolerance); Cvar_RegisterVariable(&r_subdivisions_collision_mintess); Cvar_RegisterVariable(&r_subdivisions_collision_maxtess); Cvar_RegisterVariable(&r_subdivisions_collision_maxvertices); Cvar_RegisterVariable(&r_trippy); Cvar_RegisterVariable(&r_fxaa); Cvar_RegisterVariable(&mod_noshader_default_offsetmapping); Cvar_RegisterVariable(&mod_obj_orientation); Cvar_RegisterVariable(&mod_q2bsp_littransparentsurfaces); Cvar_RegisterVariable(&mod_q3bsp_curves_collisions); Cvar_RegisterVariable(&mod_q3bsp_curves_collisions_stride); Cvar_RegisterVariable(&mod_q3bsp_curves_stride); Cvar_RegisterVariable(&mod_q3bsp_optimizedtraceline); Cvar_RegisterVariable(&mod_q3bsp_debugtracebrush); Cvar_RegisterVariable(&mod_q3bsp_lightmapmergepower); Cvar_RegisterVariable(&mod_q3bsp_nolightmaps); Cvar_RegisterVariable(&mod_q3bsp_sRGBlightmaps); Cvar_RegisterVariable(&mod_q3bsp_tracelineofsight_brushes); Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping); Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_scale); Cvar_RegisterVariable(&mod_q3shader_default_offsetmapping_bias); Cvar_RegisterVariable(&mod_q3shader_default_polygonfactor); Cvar_RegisterVariable(&mod_q3shader_default_polygonoffset); Cvar_RegisterVariable(&mod_q3shader_force_addalpha); Cvar_RegisterVariable(&mod_q3shader_force_terrain_alphaflag); Cvar_RegisterVariable(&mod_q1bsp_polygoncollisions); Cvar_RegisterVariable(&mod_collision_bih); Cvar_RegisterVariable(&mod_recalculatenodeboxes); // these games were made for older DP engines and are no longer // maintained; use this hack to show their textures properly if(gamemode == GAME_NEXUIZ) Cvar_SetQuick(&mod_q3shader_force_addalpha, "1"); memset(&mod_q1bsp_texture_solid, 0, sizeof(mod_q1bsp_texture_solid)); strlcpy(mod_q1bsp_texture_solid.name, "solid" , sizeof(mod_q1bsp_texture_solid.name)); mod_q1bsp_texture_solid.surfaceflags = 0; mod_q1bsp_texture_solid.supercontents = SUPERCONTENTS_SOLID; mod_q1bsp_texture_sky = mod_q1bsp_texture_solid; strlcpy(mod_q1bsp_texture_sky.name, "sky", sizeof(mod_q1bsp_texture_sky.name)); mod_q1bsp_texture_sky.surfaceflags = Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT | Q3SURFACEFLAG_NOMARKS | Q3SURFACEFLAG_NODLIGHT | Q3SURFACEFLAG_NOLIGHTMAP; mod_q1bsp_texture_sky.supercontents = SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP; mod_q1bsp_texture_lava = mod_q1bsp_texture_solid; strlcpy(mod_q1bsp_texture_lava.name, "*lava", sizeof(mod_q1bsp_texture_lava.name)); mod_q1bsp_texture_lava.surfaceflags = Q3SURFACEFLAG_NOMARKS; mod_q1bsp_texture_lava.supercontents = SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; mod_q1bsp_texture_slime = mod_q1bsp_texture_solid; strlcpy(mod_q1bsp_texture_slime.name, "*slime", sizeof(mod_q1bsp_texture_slime.name)); mod_q1bsp_texture_slime.surfaceflags = Q3SURFACEFLAG_NOMARKS; mod_q1bsp_texture_slime.supercontents = SUPERCONTENTS_SLIME; mod_q1bsp_texture_water = mod_q1bsp_texture_solid; strlcpy(mod_q1bsp_texture_water.name, "*water", sizeof(mod_q1bsp_texture_water.name)); mod_q1bsp_texture_water.surfaceflags = Q3SURFACEFLAG_NOMARKS; mod_q1bsp_texture_water.supercontents = SUPERCONTENTS_WATER; } static mleaf_t *Mod_Q1BSP_PointInLeaf(dp_model_t *model, const vec3_t p) { mnode_t *node; if (model == NULL) return NULL; // LordHavoc: modified to start at first clip node, // in other words: first node of the (sub)model node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; while (node->plane) node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; return (mleaf_t *)node; } static void Mod_Q1BSP_AmbientSoundLevelsForPoint(dp_model_t *model, const vec3_t p, unsigned char *out, int outsize) { int i; mleaf_t *leaf; leaf = Mod_Q1BSP_PointInLeaf(model, p); if (leaf) { i = min(outsize, (int)sizeof(leaf->ambient_sound_level)); if (i) { memcpy(out, leaf->ambient_sound_level, i); out += i; outsize -= i; } } if (outsize) memset(out, 0, outsize); } static int Mod_Q1BSP_FindBoxClusters(dp_model_t *model, const vec3_t mins, const vec3_t maxs, int maxclusters, int *clusterlist) { int numclusters = 0; int nodestackindex = 0; mnode_t *node, *nodestack[1024]; if (!model->brush.num_pvsclusters) return -1; node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; for (;;) { #if 1 if (node->plane) { // node - recurse down the BSP tree int sides = BoxOnPlaneSide(mins, maxs, node->plane); if (sides < 3) { if (sides == 0) return -1; // ERROR: NAN bounding box! // box is on one side of plane, take that path node = node->children[sides-1]; } else { // box crosses plane, take one path and remember the other if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; } continue; } else { // leaf - add clusterindex to list if (numclusters < maxclusters) clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; numclusters++; } #else if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) { if (node->plane) { if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; continue; } else { // leaf - add clusterindex to list if (numclusters < maxclusters) clusterlist[numclusters] = ((mleaf_t *)node)->clusterindex; numclusters++; } } #endif // try another path we didn't take earlier if (nodestackindex == 0) break; node = nodestack[--nodestackindex]; } // return number of clusters found (even if more than the maxclusters) return numclusters; } static int Mod_Q1BSP_BoxTouchingPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) { int nodestackindex = 0; mnode_t *node, *nodestack[1024]; if (!model->brush.num_pvsclusters) return true; node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; for (;;) { #if 1 if (node->plane) { // node - recurse down the BSP tree int sides = BoxOnPlaneSide(mins, maxs, node->plane); if (sides < 3) { if (sides == 0) return -1; // ERROR: NAN bounding box! // box is on one side of plane, take that path node = node->children[sides-1]; } else { // box crosses plane, take one path and remember the other if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; } continue; } else { // leaf - check cluster bit int clusterindex = ((mleaf_t *)node)->clusterindex; if (CHECKPVSBIT(pvs, clusterindex)) { // it is visible, return immediately with the news return true; } } #else if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) { if (node->plane) { if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; continue; } else { // leaf - check cluster bit int clusterindex = ((mleaf_t *)node)->clusterindex; if (CHECKPVSBIT(pvs, clusterindex)) { // it is visible, return immediately with the news return true; } } } #endif // nothing to see here, try another path we didn't take earlier if (nodestackindex == 0) break; node = nodestack[--nodestackindex]; } // it is not visible return false; } static int Mod_Q1BSP_BoxTouchingLeafPVS(dp_model_t *model, const unsigned char *pvs, const vec3_t mins, const vec3_t maxs) { int nodestackindex = 0; mnode_t *node, *nodestack[1024]; if (!model->brush.num_leafs) return true; node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; for (;;) { #if 1 if (node->plane) { // node - recurse down the BSP tree int sides = BoxOnPlaneSide(mins, maxs, node->plane); if (sides < 3) { if (sides == 0) return -1; // ERROR: NAN bounding box! // box is on one side of plane, take that path node = node->children[sides-1]; } else { // box crosses plane, take one path and remember the other if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; } continue; } else { // leaf - check cluster bit int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; if (CHECKPVSBIT(pvs, clusterindex)) { // it is visible, return immediately with the news return true; } } #else if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) { if (node->plane) { if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; continue; } else { // leaf - check cluster bit int clusterindex = ((mleaf_t *)node) - model->brush.data_leafs; if (CHECKPVSBIT(pvs, clusterindex)) { // it is visible, return immediately with the news return true; } } } #endif // nothing to see here, try another path we didn't take earlier if (nodestackindex == 0) break; node = nodestack[--nodestackindex]; } // it is not visible return false; } static int Mod_Q1BSP_BoxTouchingVisibleLeafs(dp_model_t *model, const unsigned char *visibleleafs, const vec3_t mins, const vec3_t maxs) { int nodestackindex = 0; mnode_t *node, *nodestack[1024]; if (!model->brush.num_leafs) return true; node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; for (;;) { #if 1 if (node->plane) { // node - recurse down the BSP tree int sides = BoxOnPlaneSide(mins, maxs, node->plane); if (sides < 3) { if (sides == 0) return -1; // ERROR: NAN bounding box! // box is on one side of plane, take that path node = node->children[sides-1]; } else { // box crosses plane, take one path and remember the other if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; } continue; } else { // leaf - check if it is visible if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) { // it is visible, return immediately with the news return true; } } #else if (BoxesOverlap(mins, maxs, node->mins, node->maxs)) { if (node->plane) { if (nodestackindex < 1024) nodestack[nodestackindex++] = node->children[0]; node = node->children[1]; continue; } else { // leaf - check if it is visible if (visibleleafs[(mleaf_t *)node - model->brush.data_leafs]) { // it is visible, return immediately with the news return true; } } } #endif // nothing to see here, try another path we didn't take earlier if (nodestackindex == 0) break; node = nodestack[--nodestackindex]; } // it is not visible return false; } typedef struct findnonsolidlocationinfo_s { vec3_t center; vec3_t absmin, absmax; vec_t radius; vec3_t nudge; vec_t bestdist; dp_model_t *model; } findnonsolidlocationinfo_t; static void Mod_Q1BSP_FindNonSolidLocation_r_Triangle(findnonsolidlocationinfo_t *info, msurface_t *surface, int k) { int i, *tri; float dist, f, vert[3][3], edge[3][3], facenormal[3], edgenormal[3][3], point[3]; tri = (info->model->surfmesh.data_element3i + 3 * surface->num_firsttriangle) + k * 3; VectorCopy((info->model->surfmesh.data_vertex3f + tri[0] * 3), vert[0]); VectorCopy((info->model->surfmesh.data_vertex3f + tri[1] * 3), vert[1]); VectorCopy((info->model->surfmesh.data_vertex3f + tri[2] * 3), vert[2]); VectorSubtract(vert[1], vert[0], edge[0]); VectorSubtract(vert[2], vert[1], edge[1]); CrossProduct(edge[1], edge[0], facenormal); if (facenormal[0] || facenormal[1] || facenormal[2]) { VectorNormalize(facenormal); f = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); if (f <= info->bestdist && f >= -info->bestdist) { VectorSubtract(vert[0], vert[2], edge[2]); VectorNormalize(edge[0]); VectorNormalize(edge[1]); VectorNormalize(edge[2]); CrossProduct(facenormal, edge[0], edgenormal[0]); CrossProduct(facenormal, edge[1], edgenormal[1]); CrossProduct(facenormal, edge[2], edgenormal[2]); // face distance if (DotProduct(info->center, edgenormal[0]) < DotProduct(vert[0], edgenormal[0]) && DotProduct(info->center, edgenormal[1]) < DotProduct(vert[1], edgenormal[1]) && DotProduct(info->center, edgenormal[2]) < DotProduct(vert[2], edgenormal[2])) { // we got lucky, the center is within the face dist = DotProduct(info->center, facenormal) - DotProduct(vert[0], facenormal); if (dist < 0) { dist = -dist; if (info->bestdist > dist) { info->bestdist = dist; VectorScale(facenormal, (info->radius - -dist), info->nudge); } } else { if (info->bestdist > dist) { info->bestdist = dist; VectorScale(facenormal, (info->radius - dist), info->nudge); } } } else { // check which edge or vertex the center is nearest for (i = 0;i < 3;i++) { f = DotProduct(info->center, edge[i]); if (f >= DotProduct(vert[0], edge[i]) && f <= DotProduct(vert[1], edge[i])) { // on edge VectorMA(info->center, -f, edge[i], point); dist = sqrt(DotProduct(point, point)); if (info->bestdist > dist) { info->bestdist = dist; VectorScale(point, (info->radius / dist), info->nudge); } // skip both vertex checks // (both are further away than this edge) i++; } else { // not on edge, check first vertex of edge VectorSubtract(info->center, vert[i], point); dist = sqrt(DotProduct(point, point)); if (info->bestdist > dist) { info->bestdist = dist; VectorScale(point, (info->radius / dist), info->nudge); } } } } } } } static void Mod_Q1BSP_FindNonSolidLocation_r_Leaf(findnonsolidlocationinfo_t *info, mleaf_t *leaf) { int surfacenum, k, *mark; msurface_t *surface; for (surfacenum = 0, mark = leaf->firstleafsurface;surfacenum < leaf->numleafsurfaces;surfacenum++, mark++) { surface = info->model->data_surfaces + *mark; if (surface->texture->supercontents & SUPERCONTENTS_SOLID) { if(surface->deprecatedq3num_bboxstride > 0) { int i, cnt, tri; cnt = (surface->num_triangles + surface->deprecatedq3num_bboxstride - 1) / surface->deprecatedq3num_bboxstride; for(i = 0; i < cnt; ++i) { if(BoxesOverlap(surface->deprecatedq3data_bbox6f + i * 6, surface->deprecatedq3data_bbox6f + i * 6 + 3, info->absmin, info->absmax)) { for(k = 0; k < surface->deprecatedq3num_bboxstride; ++k) { tri = i * surface->deprecatedq3num_bboxstride + k; if(tri >= surface->num_triangles) break; Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, tri); } } } } else { for (k = 0;k < surface->num_triangles;k++) { Mod_Q1BSP_FindNonSolidLocation_r_Triangle(info, surface, k); } } } } } static void Mod_Q1BSP_FindNonSolidLocation_r(findnonsolidlocationinfo_t *info, mnode_t *node) { if (node->plane) { float f = PlaneDiff(info->center, node->plane); if (f >= -info->bestdist) Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[0]); if (f <= info->bestdist) Mod_Q1BSP_FindNonSolidLocation_r(info, node->children[1]); } else { if (((mleaf_t *)node)->numleafsurfaces) Mod_Q1BSP_FindNonSolidLocation_r_Leaf(info, (mleaf_t *)node); } } static void Mod_Q1BSP_FindNonSolidLocation(dp_model_t *model, const vec3_t in, vec3_t out, float radius) { int i; findnonsolidlocationinfo_t info; if (model == NULL) { VectorCopy(in, out); return; } VectorCopy(in, info.center); info.radius = radius; info.model = model; i = 0; do { VectorClear(info.nudge); info.bestdist = radius; VectorCopy(info.center, info.absmin); VectorCopy(info.center, info.absmax); info.absmin[0] -= info.radius + 1; info.absmin[1] -= info.radius + 1; info.absmin[2] -= info.radius + 1; info.absmax[0] += info.radius + 1; info.absmax[1] += info.radius + 1; info.absmax[2] += info.radius + 1; Mod_Q1BSP_FindNonSolidLocation_r(&info, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); VectorAdd(info.center, info.nudge, info.center); } while (info.bestdist < radius && ++i < 10); VectorCopy(info.center, out); } int Mod_Q1BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) { switch(nativecontents) { case CONTENTS_EMPTY: return 0; case CONTENTS_SOLID: return SUPERCONTENTS_SOLID | SUPERCONTENTS_OPAQUE; case CONTENTS_WATER: return SUPERCONTENTS_WATER; case CONTENTS_SLIME: return SUPERCONTENTS_SLIME; case CONTENTS_LAVA: return SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP; case CONTENTS_SKY: return SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; // to match behaviour of Q3 maps, let sky count as opaque } return 0; } int Mod_Q1BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) { if (supercontents & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY)) return CONTENTS_SOLID; if (supercontents & SUPERCONTENTS_SKY) return CONTENTS_SKY; if (supercontents & SUPERCONTENTS_LAVA) return CONTENTS_LAVA; if (supercontents & SUPERCONTENTS_SLIME) return CONTENTS_SLIME; if (supercontents & SUPERCONTENTS_WATER) return CONTENTS_WATER; return CONTENTS_EMPTY; } typedef struct RecursiveHullCheckTraceInfo_s { // the hull we're tracing through const hull_t *hull; // the trace structure to fill in trace_t *trace; // start, end, and end - start (in model space) double start[3]; double end[3]; double dist[3]; } RecursiveHullCheckTraceInfo_t; // 1/32 epsilon to keep floating point happy #define DIST_EPSILON (0.03125) #define HULLCHECKSTATE_EMPTY 0 #define HULLCHECKSTATE_SOLID 1 #define HULLCHECKSTATE_DONE 2 static int Mod_Q1BSP_RecursiveHullCheck(RecursiveHullCheckTraceInfo_t *t, int num, double p1f, double p2f, double p1[3], double p2[3]) { // status variables, these don't need to be saved on the stack when // recursing... but are because this should be thread-safe // (note: tracing against a bbox is not thread-safe, yet) int ret; mplane_t *plane; double t1, t2; // variables that need to be stored on the stack when recursing mclipnode_t *node; int p1side, p2side; double midf, mid[3]; // keep looping until we hit a leaf while (num >= 0) { // find the point distances node = t->hull->clipnodes + num; plane = t->hull->planes + node->planenum; // axial planes can be calculated more quickly without the DotProduct if (plane->type < 3) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; } // negative plane distances indicate children[1] (behind plane) p1side = t1 < 0; p2side = t2 < 0; // if the line starts and ends on the same side of the plane, recurse // into that child instantly if (p1side == p2side) { #if COLLISIONPARANOID >= 3 if (p1side) Con_Print("<"); else Con_Print(">"); #endif // loop back and process the start child num = node->children[p1side]; } else { // find the midpoint where the line crosses the plane, use the // original line for best accuracy #if COLLISIONPARANOID >= 3 Con_Print("M"); #endif if (plane->type < 3) { t1 = t->start[plane->type] - plane->dist; t2 = t->end[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, t->start) - plane->dist; t2 = DotProduct (plane->normal, t->end) - plane->dist; } midf = t1 / (t1 - t2); midf = bound(p1f, midf, p2f); VectorMA(t->start, midf, t->dist, mid); // we now have a mid point, essentially splitting the line into // the segments in the near child and the far child, we can now // recurse those in order and get their results // recurse both sides, front side first ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[p1side], p1f, midf, p1, mid); // if this side is not empty, return what it is (solid or done) if (ret != HULLCHECKSTATE_EMPTY) return ret; ret = Mod_Q1BSP_RecursiveHullCheck(t, node->children[p2side], midf, p2f, mid, p2); // if other side is not solid, return what it is (empty or done) if (ret != HULLCHECKSTATE_SOLID) return ret; // front is air and back is solid, this is the impact point... // copy the plane information, flipping it if needed if (p1side) { t->trace->plane.dist = -plane->dist; VectorNegate (plane->normal, t->trace->plane.normal); } else { t->trace->plane.dist = plane->dist; VectorCopy (plane->normal, t->trace->plane.normal); } // calculate the return fraction which is nudged off the surface a bit t1 = DotProduct(t->trace->plane.normal, t->start) - t->trace->plane.dist; t2 = DotProduct(t->trace->plane.normal, t->end) - t->trace->plane.dist; midf = (t1 - collision_impactnudge.value) / (t1 - t2); t->trace->fraction = bound(0, midf, 1); #if COLLISIONPARANOID >= 3 Con_Print("D"); #endif return HULLCHECKSTATE_DONE; } } // we reached a leaf contents // check for empty num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); if (!t->trace->startfound) { t->trace->startfound = true; t->trace->startsupercontents |= num; } if (num & SUPERCONTENTS_LIQUIDSMASK) t->trace->inwater = true; if (num == 0) t->trace->inopen = true; if (num & SUPERCONTENTS_SOLID) t->trace->hittexture = &mod_q1bsp_texture_solid; else if (num & SUPERCONTENTS_SKY) t->trace->hittexture = &mod_q1bsp_texture_sky; else if (num & SUPERCONTENTS_LAVA) t->trace->hittexture = &mod_q1bsp_texture_lava; else if (num & SUPERCONTENTS_SLIME) t->trace->hittexture = &mod_q1bsp_texture_slime; else t->trace->hittexture = &mod_q1bsp_texture_water; t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; t->trace->hitsupercontents = num; if (num & t->trace->hitsupercontentsmask) { // if the first leaf is solid, set startsolid if (t->trace->allsolid) t->trace->startsolid = true; #if COLLISIONPARANOID >= 3 Con_Print("S"); #endif return HULLCHECKSTATE_SOLID; } else { t->trace->allsolid = false; #if COLLISIONPARANOID >= 3 Con_Print("E"); #endif return HULLCHECKSTATE_EMPTY; } } //#if COLLISIONPARANOID < 2 static int Mod_Q1BSP_RecursiveHullCheckPoint(RecursiveHullCheckTraceInfo_t *t, int num) { mplane_t *plane; mclipnode_t *nodes = t->hull->clipnodes; mplane_t *planes = t->hull->planes; vec3_t point; VectorCopy(t->start, point); while (num >= 0) { plane = planes + nodes[num].planenum; num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; } num = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); t->trace->startsupercontents |= num; if (num & SUPERCONTENTS_LIQUIDSMASK) t->trace->inwater = true; if (num == 0) t->trace->inopen = true; if (num & t->trace->hitsupercontentsmask) { t->trace->allsolid = t->trace->startsolid = true; return HULLCHECKSTATE_SOLID; } else { t->trace->allsolid = t->trace->startsolid = false; return HULLCHECKSTATE_EMPTY; } } //#endif static void Mod_Q1BSP_TracePoint(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { RecursiveHullCheckTraceInfo_t rhc; memset(&rhc, 0, sizeof(rhc)); memset(trace, 0, sizeof(trace_t)); rhc.trace = trace; rhc.trace->fraction = 1; rhc.trace->allsolid = true; rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 VectorCopy(start, rhc.start); VectorCopy(start, rhc.end); Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); } static void Mod_Q1BSP_TraceLineAgainstSurfaces(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); static void Mod_Q1BSP_TraceLine(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { RecursiveHullCheckTraceInfo_t rhc; if (VectorCompare(start, end)) { Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); return; } // sometimes we want to traceline against polygons so we can report the texture that was hit rather than merely a contents, but using this method breaks one of negke's maps so it must be a cvar check... if (sv_gameplayfix_q1bsptracelinereportstexture.integer) { Mod_Q1BSP_TraceLineAgainstSurfaces(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); return; } memset(&rhc, 0, sizeof(rhc)); memset(trace, 0, sizeof(trace_t)); rhc.trace = trace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 VectorCopy(start, rhc.start); VectorCopy(end, rhc.end); VectorSubtract(rhc.end, rhc.start, rhc.dist); #if COLLISIONPARANOID >= 2 Con_Printf("t(%f %f %f,%f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2]); Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); { double test[3]; trace_t testtrace; VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); memset(&testtrace, 0, sizeof(trace_t)); rhc.trace = &testtrace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; VectorCopy(test, rhc.start); VectorCopy(test, rhc.end); VectorClear(rhc.dist); Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); if (!trace->startsolid && testtrace.startsolid) Con_Printf(" - ended in solid!\n"); } Con_Print("\n"); #else if (VectorLength2(rhc.dist)) Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); else Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); #endif } static void Mod_Q1BSP_TraceBox(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { // this function currently only supports same size start and end double boxsize[3]; RecursiveHullCheckTraceInfo_t rhc; if (VectorCompare(boxmins, boxmaxs)) { if (VectorCompare(start, end)) Mod_Q1BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); else Mod_Q1BSP_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); return; } memset(&rhc, 0, sizeof(rhc)); memset(trace, 0, sizeof(trace_t)); rhc.trace = trace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; VectorSubtract(boxmaxs, boxmins, boxsize); if (boxsize[0] < 3) rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 else if (model->brush.ishlbsp) { // LordHavoc: this has to have a minor tolerance (the .1) because of // minor float precision errors from the box being transformed around if (boxsize[0] < 32.1) { if (boxsize[2] < 54) // pick the nearest of 36 or 72 rhc.hull = &model->brushq1.hulls[3]; // 32x32x36 else rhc.hull = &model->brushq1.hulls[1]; // 32x32x72 } else rhc.hull = &model->brushq1.hulls[2]; // 64x64x64 } else { // LordHavoc: this has to have a minor tolerance (the .1) because of // minor float precision errors from the box being transformed around if (boxsize[0] < 32.1) rhc.hull = &model->brushq1.hulls[1]; // 32x32x56 else rhc.hull = &model->brushq1.hulls[2]; // 64x64x88 } VectorMAMAM(1, start, 1, boxmins, -1, rhc.hull->clip_mins, rhc.start); VectorMAMAM(1, end, 1, boxmins, -1, rhc.hull->clip_mins, rhc.end); VectorSubtract(rhc.end, rhc.start, rhc.dist); #if COLLISIONPARANOID >= 2 Con_Printf("t(%f %f %f,%f %f %f,%i %f %f %f)", rhc.start[0], rhc.start[1], rhc.start[2], rhc.end[0], rhc.end[1], rhc.end[2], rhc.hull - model->brushq1.hulls, rhc.hull->clip_mins[0], rhc.hull->clip_mins[1], rhc.hull->clip_mins[2]); Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); { double test[3]; trace_t testtrace; VectorLerp(rhc.start, rhc.trace->fraction, rhc.end, test); memset(&testtrace, 0, sizeof(trace_t)); rhc.trace = &testtrace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; VectorCopy(test, rhc.start); VectorCopy(test, rhc.end); VectorClear(rhc.dist); Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); //Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, test, test); if (!trace->startsolid && testtrace.startsolid) Con_Printf(" - ended in solid!\n"); } Con_Print("\n"); #else if (VectorLength2(rhc.dist)) Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); else Mod_Q1BSP_RecursiveHullCheckPoint(&rhc, rhc.hull->firstclipnode); #endif } static int Mod_Q1BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) { int num = model->brushq1.hulls[0].firstclipnode; mplane_t *plane; mclipnode_t *nodes = model->brushq1.hulls[0].clipnodes; mplane_t *planes = model->brushq1.hulls[0].planes; while (num >= 0) { plane = planes + nodes[num].planenum; num = nodes[num].children[(plane->type < 3 ? point[plane->type] : DotProduct(plane->normal, point)) < plane->dist]; } return Mod_Q1BSP_SuperContentsFromNativeContents(NULL, num); } void Collision_ClipTrace_Box(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) { #if 1 colbrushf_t cbox; colplanef_t cbox_planes[6]; cbox.isaabb = true; cbox.hasaabbplanes = true; cbox.supercontents = boxsupercontents; cbox.numplanes = 6; cbox.numpoints = 0; cbox.numtriangles = 0; cbox.planes = cbox_planes; cbox.points = NULL; cbox.elements = NULL; cbox.markframe = 0; cbox.mins[0] = 0; cbox.mins[1] = 0; cbox.mins[2] = 0; cbox.maxs[0] = 0; cbox.maxs[1] = 0; cbox.maxs[2] = 0; cbox_planes[0].normal[0] = 1;cbox_planes[0].normal[1] = 0;cbox_planes[0].normal[2] = 0;cbox_planes[0].dist = cmaxs[0] - mins[0]; cbox_planes[1].normal[0] = -1;cbox_planes[1].normal[1] = 0;cbox_planes[1].normal[2] = 0;cbox_planes[1].dist = maxs[0] - cmins[0]; cbox_planes[2].normal[0] = 0;cbox_planes[2].normal[1] = 1;cbox_planes[2].normal[2] = 0;cbox_planes[2].dist = cmaxs[1] - mins[1]; cbox_planes[3].normal[0] = 0;cbox_planes[3].normal[1] = -1;cbox_planes[3].normal[2] = 0;cbox_planes[3].dist = maxs[1] - cmins[1]; cbox_planes[4].normal[0] = 0;cbox_planes[4].normal[1] = 0;cbox_planes[4].normal[2] = 1;cbox_planes[4].dist = cmaxs[2] - mins[2]; cbox_planes[5].normal[0] = 0;cbox_planes[5].normal[1] = 0;cbox_planes[5].normal[2] = -1;cbox_planes[5].dist = maxs[2] - cmins[2]; cbox_planes[0].q3surfaceflags = boxq3surfaceflags;cbox_planes[0].texture = boxtexture; cbox_planes[1].q3surfaceflags = boxq3surfaceflags;cbox_planes[1].texture = boxtexture; cbox_planes[2].q3surfaceflags = boxq3surfaceflags;cbox_planes[2].texture = boxtexture; cbox_planes[3].q3surfaceflags = boxq3surfaceflags;cbox_planes[3].texture = boxtexture; cbox_planes[4].q3surfaceflags = boxq3surfaceflags;cbox_planes[4].texture = boxtexture; cbox_planes[5].q3surfaceflags = boxq3surfaceflags;cbox_planes[5].texture = boxtexture; memset(trace, 0, sizeof(trace_t)); trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; trace->fraction = 1; Collision_TraceLineBrushFloat(trace, start, end, &cbox, &cbox); #else RecursiveHullCheckTraceInfo_t rhc; static hull_t box_hull; static mclipnode_t box_clipnodes[6]; static mplane_t box_planes[6]; // fill in a default trace memset(&rhc, 0, sizeof(rhc)); memset(trace, 0, sizeof(trace_t)); //To keep everything totally uniform, bounding boxes are turned into small //BSP trees instead of being compared directly. // create a temp hull from bounding box sizes box_planes[0].dist = cmaxs[0] - mins[0]; box_planes[1].dist = cmins[0] - maxs[0]; box_planes[2].dist = cmaxs[1] - mins[1]; box_planes[3].dist = cmins[1] - maxs[1]; box_planes[4].dist = cmaxs[2] - mins[2]; box_planes[5].dist = cmins[2] - maxs[2]; #if COLLISIONPARANOID >= 3 Con_Printf("box_planes %f:%f %f:%f %f:%f\ncbox %f %f %f:%f %f %f\nbox %f %f %f:%f %f %f\n", box_planes[0].dist, box_planes[1].dist, box_planes[2].dist, box_planes[3].dist, box_planes[4].dist, box_planes[5].dist, cmins[0], cmins[1], cmins[2], cmaxs[0], cmaxs[1], cmaxs[2], mins[0], mins[1], mins[2], maxs[0], maxs[1], maxs[2]); #endif if (box_hull.clipnodes == NULL) { int i, side; //Set up the planes and clipnodes so that the six floats of a bounding box //can just be stored out and get a proper hull_t structure. box_hull.clipnodes = box_clipnodes; box_hull.planes = box_planes; box_hull.firstclipnode = 0; box_hull.lastclipnode = 5; for (i = 0;i < 6;i++) { box_clipnodes[i].planenum = i; side = i&1; box_clipnodes[i].children[side] = CONTENTS_EMPTY; if (i != 5) box_clipnodes[i].children[side^1] = i + 1; else box_clipnodes[i].children[side^1] = CONTENTS_SOLID; box_planes[i].type = i>>1; box_planes[i].normal[i>>1] = 1; } } // trace a line through the generated clipping hull //rhc.boxsupercontents = boxsupercontents; rhc.hull = &box_hull; rhc.trace = trace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; VectorCopy(start, rhc.start); VectorCopy(end, rhc.end); VectorSubtract(rhc.end, rhc.start, rhc.dist); Mod_Q1BSP_RecursiveHullCheck(&rhc, rhc.hull->firstclipnode, 0, 1, rhc.start, rhc.end); //VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); if (rhc.trace->startsupercontents) rhc.trace->startsupercontents = boxsupercontents; #endif } void Collision_ClipTrace_Point(trace_t *trace, const vec3_t cmins, const vec3_t cmaxs, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask, int boxsupercontents, int boxq3surfaceflags, const texture_t *boxtexture) { memset(trace, 0, sizeof(trace_t)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; if (BoxesOverlap(start, start, cmins, cmaxs)) { trace->startsupercontents |= boxsupercontents; if ((hitsupercontentsmask & boxsupercontents) && !(skipsupercontentsmask & boxsupercontents)) { trace->startsolid = true; trace->allsolid = true; } } } static qboolean Mod_Q1BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) { trace_t trace; Mod_Q1BSP_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0); return trace.fraction == 1; } static int Mod_Q1BSP_LightPoint_RecursiveBSPNode(dp_model_t *model, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal, const mnode_t *node, float x, float y, float startz, float endz) { int side; float front, back; float mid, distz = endz - startz; while (node->plane) { switch (node->plane->type) { case PLANE_X: node = node->children[x < node->plane->dist]; continue; // loop back and process the new node case PLANE_Y: node = node->children[y < node->plane->dist]; continue; // loop back and process the new node case PLANE_Z: side = startz < node->plane->dist; if ((endz < node->plane->dist) == side) { node = node->children[side]; continue; // loop back and process the new node } // found an intersection mid = node->plane->dist; break; default: back = front = x * node->plane->normal[0] + y * node->plane->normal[1]; front += startz * node->plane->normal[2]; back += endz * node->plane->normal[2]; side = front < node->plane->dist; if ((back < node->plane->dist) == side) { node = node->children[side]; continue; // loop back and process the new node } // found an intersection mid = startz + distz * (front - node->plane->dist) / (front - back); break; } // go down front side if (node->children[side]->plane && Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, node->children[side], x, y, startz, mid)) return true; // hit something // check for impact on this node if (node->numsurfaces) { unsigned int i; int dsi, dti, lmwidth, lmheight; float ds, dt; msurface_t *surface; unsigned char *lightmap; int maps, line3, size3; float dsfrac; float dtfrac; float scale, w, w00, w01, w10, w11; surface = model->data_surfaces + node->firstsurface; for (i = 0;i < node->numsurfaces;i++, surface++) { if (!(surface->texture->basematerialflags & MATERIALFLAG_WALL) || !surface->lightmapinfo || !surface->lightmapinfo->samples) continue; // no lightmaps // location we want to sample in the lightmap ds = ((x * surface->lightmapinfo->texinfo->vecs[0][0] + y * surface->lightmapinfo->texinfo->vecs[0][1] + mid * surface->lightmapinfo->texinfo->vecs[0][2] + surface->lightmapinfo->texinfo->vecs[0][3]) - surface->lightmapinfo->texturemins[0]) * 0.0625f; dt = ((x * surface->lightmapinfo->texinfo->vecs[1][0] + y * surface->lightmapinfo->texinfo->vecs[1][1] + mid * surface->lightmapinfo->texinfo->vecs[1][2] + surface->lightmapinfo->texinfo->vecs[1][3]) - surface->lightmapinfo->texturemins[1]) * 0.0625f; // check the bounds // thanks to jitspoe for pointing out that this int cast was // rounding toward zero, so we floor it dsi = (int)floor(ds); dti = (int)floor(dt); lmwidth = ((surface->lightmapinfo->extents[0]>>4)+1); lmheight = ((surface->lightmapinfo->extents[1]>>4)+1); // is it in bounds? // we have to tolerate a position of lmwidth-1 for some rare // cases - in which case the sampling position still gets // clamped but the color gets interpolated to that edge. if (dsi >= 0 && dsi < lmwidth && dti >= 0 && dti < lmheight) { // in the rare cases where we're sampling slightly off // the polygon, clamp the sampling position (we can still // interpolate outside it, where it becomes extrapolation) if (dsi < 0) dsi = 0; if (dti < 0) dti = 0; if (dsi > lmwidth-2) dsi = lmwidth-2; if (dti > lmheight-2) dti = lmheight-2; // calculate bilinear interpolation factors // and also multiply by fixedpoint conversion factors to // compensate for lightmaps being 0-255 (as 0-2), we use // r_refdef.scene.rtlightstylevalue here which is already // 0.000-2.148 range // (if we used r_refdef.scene.lightstylevalue this // divisor would be 32768 rather than 128) dsfrac = ds - dsi; dtfrac = dt - dti; w00 = (1 - dsfrac) * (1 - dtfrac) * (1.0f / 128.0f); w01 = ( dsfrac) * (1 - dtfrac) * (1.0f / 128.0f); w10 = (1 - dsfrac) * ( dtfrac) * (1.0f / 128.0f); w11 = ( dsfrac) * ( dtfrac) * (1.0f / 128.0f); // values for pointer math line3 = lmwidth * 3; // LordHavoc: *3 for colored lighting size3 = lmwidth * lmheight * 3; // LordHavoc: *3 for colored lighting // look up the pixel lightmap = surface->lightmapinfo->samples + dti * line3 + dsi*3; // LordHavoc: *3 for colored lighting // bilinear filter each lightmap style, and sum them for (maps = 0;maps < MAXLIGHTMAPS && surface->lightmapinfo->styles[maps] != 255;maps++) { scale = r_refdef.scene.rtlightstylevalue[surface->lightmapinfo->styles[maps]]; w = w00 * scale;VectorMA(ambientcolor, w, lightmap , ambientcolor); w = w01 * scale;VectorMA(ambientcolor, w, lightmap + 3 , ambientcolor); w = w10 * scale;VectorMA(ambientcolor, w, lightmap + line3 , ambientcolor); w = w11 * scale;VectorMA(ambientcolor, w, lightmap + line3 + 3, ambientcolor); lightmap += size3; } return true; // success } } } // go down back side node = node->children[side ^ 1]; startz = mid; distz = endz - startz; // loop back and process the new node } // did not hit anything return false; } static void Mod_Q1BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) { // pretend lighting is coming down from above (due to lack of a lightgrid to know primary lighting direction) VectorSet(diffusenormal, 0, 0, 1); if (!model->brushq1.lightdata) { VectorSet(ambientcolor, 1, 1, 1); VectorSet(diffusecolor, 0, 0, 0); return; } Mod_Q1BSP_LightPoint_RecursiveBSPNode(model, ambientcolor, diffusecolor, diffusenormal, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode, p[0], p[1], p[2] + 0.125, p[2] - 65536); } static const texture_t *Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, double mid[3]) { unsigned int i; int j; int k; const msurface_t *surface; float normal[3]; float v0[3]; float v1[3]; float edgedir[3]; float edgenormal[3]; float p[4]; float midf; float t1; float t2; VectorCopy(mid, p); p[3] = 1; surface = model->data_surfaces + node->firstsurface; for (i = 0;i < node->numsurfaces;i++, surface++) { // skip surfaces whose bounding box does not include the point // if (!BoxesOverlap(mid, mid, surface->mins, surface->maxs)) // continue; // skip faces with contents we don't care about if (!(t->trace->hitsupercontentsmask & surface->texture->supercontents)) continue; // ignore surfaces matching the skipsupercontentsmask (this is rare) if (t->trace->skipsupercontentsmask & surface->texture->supercontents) continue; // get the surface normal - since it is flat we know any vertex normal will suffice VectorCopy(model->surfmesh.data_normal3f + 3 * surface->num_firstvertex, normal); // skip backfaces if (DotProduct(t->dist, normal) > 0) continue; // iterate edges and see if the point is outside one of them for (j = 0, k = surface->num_vertices - 1;j < surface->num_vertices;k = j, j++) { VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + k), v0); VectorCopy(model->surfmesh.data_vertex3f + 3 * (surface->num_firstvertex + j), v1); VectorSubtract(v0, v1, edgedir); CrossProduct(edgedir, normal, edgenormal); if (DotProduct(edgenormal, p) > DotProduct(edgenormal, v0)) break; } // if the point is outside one of the edges, it is not within the surface if (j < surface->num_vertices) continue; // we hit a surface, this is the impact point... VectorCopy(normal, t->trace->plane.normal); t->trace->plane.dist = DotProduct(normal, p); // calculate the return fraction which is nudged off the surface a bit t1 = DotProduct(t->start, t->trace->plane.normal) - t->trace->plane.dist; t2 = DotProduct(t->end, t->trace->plane.normal) - t->trace->plane.dist; midf = (t1 - collision_impactnudge.value) / (t1 - t2); t->trace->fraction = bound(0, midf, 1); t->trace->hittexture = surface->texture->currentframe; t->trace->hitq3surfaceflags = t->trace->hittexture->surfaceflags; t->trace->hitsupercontents = t->trace->hittexture->supercontents; return surface->texture->currentframe; } return NULL; } static int Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(RecursiveHullCheckTraceInfo_t *t, const dp_model_t *model, const mnode_t *node, const double p1[3], const double p2[3]) { const mplane_t *plane; double t1, t2; int side; double midf, mid[3]; const mleaf_t *leaf; while (node->plane) { plane = node->plane; if (plane->type < 3) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; } if (t1 < 0) { if (t2 < 0) { node = node->children[1]; continue; } side = 1; } else { if (t2 >= 0) { node = node->children[0]; continue; } side = 0; } // the line intersects, find intersection point // LordHavoc: this uses the original trace for maximum accuracy if (plane->type < 3) { t1 = t->start[plane->type] - plane->dist; t2 = t->end[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, t->start) - plane->dist; t2 = DotProduct (plane->normal, t->end) - plane->dist; } midf = t1 / (t1 - t2); VectorMA(t->start, midf, t->dist, mid); // recurse both sides, front side first, return if we hit a surface if (Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side], p1, mid) == HULLCHECKSTATE_DONE) return HULLCHECKSTATE_DONE; // test each surface on the node Mod_Q1BSP_TraceLineAgainstSurfacesFindTextureOnNode(t, model, node, mid); if (t->trace->hittexture) return HULLCHECKSTATE_DONE; // recurse back side return Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(t, model, node->children[side ^ 1], mid, p2); } leaf = (const mleaf_t *)node; side = Mod_Q1BSP_SuperContentsFromNativeContents(NULL, leaf->contents); if (!t->trace->startfound) { t->trace->startfound = true; t->trace->startsupercontents |= side; } if (side & SUPERCONTENTS_LIQUIDSMASK) t->trace->inwater = true; if (side == 0) t->trace->inopen = true; if (side & t->trace->hitsupercontentsmask) { // if the first leaf is solid, set startsolid if (t->trace->allsolid) t->trace->startsolid = true; return HULLCHECKSTATE_SOLID; } else { t->trace->allsolid = false; return HULLCHECKSTATE_EMPTY; } } static void Mod_Q1BSP_TraceLineAgainstSurfaces(struct model_s *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { RecursiveHullCheckTraceInfo_t rhc; memset(&rhc, 0, sizeof(rhc)); memset(trace, 0, sizeof(trace_t)); rhc.trace = trace; rhc.trace->hitsupercontentsmask = hitsupercontentsmask; rhc.trace->skipsupercontentsmask = skipsupercontentsmask; rhc.trace->fraction = 1; rhc.trace->allsolid = true; rhc.hull = &model->brushq1.hulls[0]; // 0x0x0 VectorCopy(start, rhc.start); VectorCopy(end, rhc.end); VectorSubtract(rhc.end, rhc.start, rhc.dist); Mod_Q1BSP_TraceLineAgainstSurfacesRecursiveBSPNode(&rhc, model, model->brush.data_nodes + rhc.hull->firstclipnode, rhc.start, rhc.end); VectorMA(rhc.start, rhc.trace->fraction, rhc.dist, rhc.trace->endpos); } static void Mod_Q1BSP_DecompressVis(const unsigned char *in, const unsigned char *inend, unsigned char *out, unsigned char *outend) { int c; unsigned char *outstart = out; while (out < outend) { if (in == inend) { Con_Printf("Mod_Q1BSP_DecompressVis: input underrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); return; } c = *in++; if (c) *out++ = c; else { if (in == inend) { Con_Printf("Mod_Q1BSP_DecompressVis: input underrun (during zero-run) on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); return; } for (c = *in++;c > 0;c--) { if (out == outend) { Con_Printf("Mod_Q1BSP_DecompressVis: output overrun on model \"%s\" (decompressed %i of %i output bytes)\n", loadmodel->name, (int)(out - outstart), (int)(outend - outstart)); return; } *out++ = 0; } } } } /* ============= R_Q1BSP_LoadSplitSky A sky texture is 256*128, with the right side being a masked overlay ============== */ static void R_Q1BSP_LoadSplitSky (unsigned char *src, int width, int height, int bytesperpixel) { int x, y; int w = width/2; int h = height; unsigned int *solidpixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); unsigned int *alphapixels = (unsigned int *)Mem_Alloc(tempmempool, w*h*sizeof(unsigned char[4])); // allocate a texture pool if we need it if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) loadmodel->texturepool = R_AllocTexturePool(); if (bytesperpixel == 4) { for (y = 0;y < h;y++) { for (x = 0;x < w;x++) { solidpixels[y*w+x] = ((unsigned *)src)[y*width+x+w]; alphapixels[y*w+x] = ((unsigned *)src)[y*width+x]; } } } else { // make an average value for the back to avoid // a fringe on the top level int p, r, g, b; union { unsigned int i; unsigned char b[4]; } bgra; r = g = b = 0; for (y = 0;y < h;y++) { for (x = 0;x < w;x++) { p = src[x*width+y+w]; r += palette_rgb[p][0]; g += palette_rgb[p][1]; b += palette_rgb[p][2]; } } bgra.b[2] = r/(w*h); bgra.b[1] = g/(w*h); bgra.b[0] = b/(w*h); bgra.b[3] = 0; for (y = 0;y < h;y++) { for (x = 0;x < w;x++) { solidpixels[y*w+x] = palette_bgra_complete[src[y*width+x+w]]; p = src[y*width+x]; alphapixels[y*w+x] = p ? palette_bgra_complete[p] : bgra.i; } } } loadmodel->brush.solidskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_solidtexture", 0 , (unsigned char *) solidpixels, w, h, vid.sRGB3D); loadmodel->brush.alphaskyskinframe = R_SkinFrame_LoadInternalBGRA("sky_alphatexture", TEXF_ALPHA, (unsigned char *) alphapixels, w, h, vid.sRGB3D); Mem_Free(solidpixels); Mem_Free(alphapixels); } static void Mod_Q1BSP_LoadTextures(sizebuf_t *sb) { int i, j, k, num, max, altmax, mtwidth, mtheight, doffset, incomplete, nummiptex = 0; skinframe_t *skinframe; texture_t *tx, *tx2, *anims[10], *altanims[10]; texture_t backuptex; unsigned char *data, *mtdata; const char *s; char mapname[MAX_QPATH], name[MAX_QPATH]; unsigned char zeroopaque[4], zerotrans[4]; sizebuf_t miptexsb; char vabuf[1024]; Vector4Set(zeroopaque, 0, 0, 0, 255); Vector4Set(zerotrans, 0, 0, 0, 128); loadmodel->data_textures = NULL; // add two slots for notexture walls and notexture liquids if (sb->cursize) { nummiptex = MSG_ReadLittleLong(sb); loadmodel->num_textures = nummiptex + 2; loadmodel->num_texturesperskin = loadmodel->num_textures; } else { loadmodel->num_textures = 2; loadmodel->num_texturesperskin = loadmodel->num_textures; } loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_textures * sizeof(texture_t)); // fill out all slots with notexture if (cls.state != ca_dedicated) skinframe = R_SkinFrame_LoadMissing(); else skinframe = NULL; for (i = 0, tx = loadmodel->data_textures;i < loadmodel->num_textures;i++, tx++) { strlcpy(tx->name, "NO TEXTURE FOUND", sizeof(tx->name)); tx->width = 16; tx->height = 16; tx->basealpha = 1.0f; if (cls.state != ca_dedicated) { tx->numskinframes = 1; tx->skinframerate = 1; tx->skinframes[0] = skinframe; tx->currentskinframe = tx->skinframes[0]; } tx->basematerialflags = MATERIALFLAG_WALL; if (i == loadmodel->num_textures - 1) { tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; tx->supercontents = mod_q1bsp_texture_water.supercontents; tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; } else { tx->supercontents = mod_q1bsp_texture_solid.supercontents; tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; } tx->currentframe = tx; // clear water settings tx->reflectmin = 0; tx->reflectmax = 1; tx->refractfactor = 1; Vector4Set(tx->refractcolor4f, 1, 1, 1, 1); tx->reflectfactor = 1; Vector4Set(tx->reflectcolor4f, 1, 1, 1, 1); tx->r_water_wateralpha = 1; tx->offsetmapping = OFFSETMAPPING_DEFAULT; tx->offsetscale = 1; tx->offsetbias = 0; tx->specularscalemod = 1; tx->specularpowermod = 1; tx->transparentsort = TRANSPARENTSORT_DISTANCE; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". } if (!sb->cursize) { Con_Printf("%s: no miptex lump to load textures from\n", loadmodel->name); return; } s = loadmodel->name; if (!strncasecmp(s, "maps/", 5)) s += 5; FS_StripExtension(s, mapname, sizeof(mapname)); // just to work around bounds checking when debugging with it (array index out of bounds error thing) // LordHavoc: mostly rewritten map texture loader for (i = 0;i < nummiptex;i++) { doffset = MSG_ReadLittleLong(sb); if (r_nosurftextures.integer) continue; if (doffset == -1) { Con_DPrintf("%s: miptex #%i missing\n", loadmodel->name, i); continue; } MSG_InitReadBuffer(&miptexsb, sb->data + doffset, sb->cursize - doffset); // copy name, but only up to 16 characters // (the output buffer can hold more than this, but the input buffer is // only 16) for (j = 0;j < 16;j++) name[j] = MSG_ReadByte(&miptexsb); name[j] = 0; // pretty up the buffer (replacing any trailing garbage with 0) for (j = (int)strlen(name);j < 16;j++) name[j] = 0; if (!name[0]) { dpsnprintf(name, sizeof(name), "unnamed%i", i); Con_DPrintf("%s: warning: renaming unnamed texture to %s\n", loadmodel->name, name); } mtwidth = MSG_ReadLittleLong(&miptexsb); mtheight = MSG_ReadLittleLong(&miptexsb); mtdata = NULL; j = MSG_ReadLittleLong(&miptexsb); if (j) { // texture included if (j < 40 || j + mtwidth * mtheight > miptexsb.cursize) { Con_Printf("%s: Texture \"%s\" is corrupt or incomplete\n", loadmodel->name, name); continue; } mtdata = miptexsb.data + j; } if ((mtwidth & 15) || (mtheight & 15)) Con_DPrintf("%s: warning: texture \"%s\" is not 16 aligned\n", loadmodel->name, name); // LordHavoc: force all names to lowercase for (j = 0;name[j];j++) if (name[j] >= 'A' && name[j] <= 'Z') name[j] += 'a' - 'A'; // LordHavoc: backup the texture_t because q3 shader loading overwrites it backuptex = loadmodel->data_textures[i]; if (name[0] && Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i, name, false, false, 0)) continue; loadmodel->data_textures[i] = backuptex; tx = loadmodel->data_textures + i; strlcpy(tx->name, name, sizeof(tx->name)); tx->width = mtwidth; tx->height = mtheight; tx->basealpha = 1.0f; if (tx->name[0] == '*') { if (!strncmp(tx->name, "*lava", 5)) { tx->supercontents = mod_q1bsp_texture_lava.supercontents; tx->surfaceflags = mod_q1bsp_texture_lava.surfaceflags; } else if (!strncmp(tx->name, "*slime", 6)) { tx->supercontents = mod_q1bsp_texture_slime.supercontents; tx->surfaceflags = mod_q1bsp_texture_slime.surfaceflags; } else { tx->supercontents = mod_q1bsp_texture_water.supercontents; tx->surfaceflags = mod_q1bsp_texture_water.surfaceflags; } } else if (!strncmp(tx->name, "sky", 3)) { tx->supercontents = mod_q1bsp_texture_sky.supercontents; tx->surfaceflags = mod_q1bsp_texture_sky.surfaceflags; // for the surface traceline we need to hit this surface as a solid... tx->supercontents |= SUPERCONTENTS_SOLID; } else { tx->supercontents = mod_q1bsp_texture_solid.supercontents; tx->surfaceflags = mod_q1bsp_texture_solid.surfaceflags; } if (cls.state != ca_dedicated) { // LordHavoc: HL sky textures are entirely different than quake if (!loadmodel->brush.ishlbsp && !strncmp(tx->name, "sky", 3) && mtwidth == mtheight * 2) { data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s/%s", mapname, tx->name), false, false, false, NULL); if (!data) data = loadimagepixelsbgra(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s", tx->name), false, false, false, NULL); if (data && image_width == image_height * 2) { R_Q1BSP_LoadSplitSky(data, image_width, image_height, 4); Mem_Free(data); } else if (mtdata != NULL) R_Q1BSP_LoadSplitSky(mtdata, mtwidth, mtheight, 1); } else { skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s/%s", mapname, tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); if (!skinframe) skinframe = R_SkinFrame_LoadExternal(gamemode == GAME_TENEBRAE ? tx->name : va(vabuf, sizeof(vabuf), "textures/%s", tx->name), TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS, false); if (skinframe) tx->offsetmapping = OFFSETMAPPING_DEFAULT; // allow offsetmapping on external textures without a q3 shader if (!skinframe) { // did not find external texture, load it from the bsp or wad3 if (loadmodel->brush.ishlbsp) { // internal texture overrides wad unsigned char *pixels, *freepixels; pixels = freepixels = NULL; if (mtdata) pixels = W_ConvertWAD3TextureBGRA(&miptexsb); if (pixels == NULL) pixels = freepixels = W_GetTextureBGRA(tx->name); if (pixels != NULL) { tx->width = image_width; tx->height = image_height; skinframe = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, pixels, image_width, image_height, true); } if (freepixels) Mem_Free(freepixels); } else if (mtdata) // texture included skinframe = R_SkinFrame_LoadInternalQuake(tx->name, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP, false, r_fullbrights.integer, mtdata, tx->width, tx->height); } // if skinframe is still NULL the "missing" texture will be used if (skinframe) tx->skinframes[0] = skinframe; } // LordHavoc: some Tenebrae textures get replaced by black if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, TEXF_MIPMAP | TEXF_ALPHA, zerotrans, 1, 1, false); else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae tx->skinframes[0] = R_SkinFrame_LoadInternalBGRA(tx->name, 0, zeroopaque, 1, 1, false); } tx->basematerialflags = MATERIALFLAG_WALL; if (tx->name[0] == '*') { // LordHavoc: some turbulent textures should not be affected by wateralpha if (!strncmp(tx->name, "*glassmirror", 12)) // Tenebrae tx->basematerialflags |= MATERIALFLAG_NOSHADOW | MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_REFLECTION; else if (!strncmp(tx->name,"*lava",5) || !strncmp(tx->name,"*teleport",9) || !strncmp(tx->name,"*rift",5)) // Scourge of Armagon texture tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW; else tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_LIGHTBOTHSIDES | MATERIALFLAG_NOSHADOW | MATERIALFLAG_WATERALPHA | MATERIALFLAG_WATERSHADER; if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; } else if (tx->name[0] == '{') // fence textures { tx->basematerialflags |= MATERIALFLAG_ALPHATEST | MATERIALFLAG_NOSHADOW; } else if (!strncmp(tx->name, "mirror", 6)) // Tenebrae { // replace the texture with black tx->basematerialflags |= MATERIALFLAG_REFLECTION; } else if (!strncmp(tx->name, "sky", 3)) tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; else if (!strcmp(tx->name, "caulk")) tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; else if (tx->skinframes[0] && tx->skinframes[0]->hasalpha) tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; // start out with no animation tx->currentframe = tx; tx->currentskinframe = tx->skinframes[0]; tx->currentmaterialflags = tx->basematerialflags; } // sequence the animations for (i = 0;i < nummiptex;i++) { tx = loadmodel->data_textures + i; if (!tx || tx->name[0] != '+' || tx->name[1] == 0 || tx->name[2] == 0) continue; num = tx->name[1]; if ((num < '0' || num > '9') && (num < 'a' || num > 'j')) { Con_Printf("Bad animating texture %s\n", tx->name); continue; } if (tx->anim_total[0] || tx->anim_total[1]) continue; // already sequenced // find the number of frames in the animation memset(anims, 0, sizeof(anims)); memset(altanims, 0, sizeof(altanims)); for (j = i;j < nummiptex;j++) { tx2 = loadmodel->data_textures + j; if (!tx2 || tx2->name[0] != '+' || strcmp(tx2->name+2, tx->name+2)) continue; num = tx2->name[1]; if (num >= '0' && num <= '9') anims[num - '0'] = tx2; else if (num >= 'a' && num <= 'j') altanims[num - 'a'] = tx2; // No need to warn otherwise - we already did above. } max = altmax = 0; for (j = 0;j < 10;j++) { if (anims[j]) max = j + 1; if (altanims[j]) altmax = j + 1; } //Con_Printf("linking animation %s (%i:%i frames)\n\n", tx->name, max, altmax); incomplete = false; for (j = 0;j < max;j++) { if (!anims[j]) { Con_Printf("Missing frame %i of %s\n", j, tx->name); incomplete = true; } } for (j = 0;j < altmax;j++) { if (!altanims[j]) { Con_Printf("Missing altframe %i of %s\n", j, tx->name); incomplete = true; } } if (incomplete) continue; // If we have exactly one frame, something's wrong. if (max + altmax <= 1) { Con_Printf("Texture %s is animated (leading +) but has only one frame\n", tx->name); } if (altmax < 1) { // if there is no alternate animation, duplicate the primary // animation into the alternate altmax = max; for (k = 0;k < 10;k++) altanims[k] = anims[k]; } if (max < 1) { // Warn. Con_Printf("Missing frame 0 of %s\n", tx->name); // however, we can handle this by duplicating the alternate animation into the primary max = altmax; for (k = 0;k < 10;k++) anims[k] = altanims[k]; } // link together the primary animation for (j = 0;j < max;j++) { tx2 = anims[j]; tx2->animated = 1; // q1bsp tx2->anim_total[0] = max; tx2->anim_total[1] = altmax; for (k = 0;k < 10;k++) { tx2->anim_frames[0][k] = anims[k]; tx2->anim_frames[1][k] = altanims[k]; } } // if there really is an alternate anim... if (anims[0] != altanims[0]) { // link together the alternate animation for (j = 0;j < altmax;j++) { tx2 = altanims[j]; tx2->animated = 1; // q1bsp // the primary/alternate are reversed here tx2->anim_total[0] = altmax; tx2->anim_total[1] = max; for (k = 0;k < 10;k++) { tx2->anim_frames[0][k] = altanims[k]; tx2->anim_frames[1][k] = anims[k]; } } } } } static void Mod_Q1BSP_LoadLighting(sizebuf_t *sb) { int i; unsigned char *in, *out, *data, d; char litfilename[MAX_QPATH]; char dlitfilename[MAX_QPATH]; fs_offset_t filesize; if (loadmodel->brush.ishlbsp) // LordHavoc: load the colored lighting data straight { loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize); for (i = 0;i < sb->cursize;i++) loadmodel->brushq1.lightdata[i] = sb->data[i] >>= 1; } else // LordHavoc: bsp version 29 (normal white lighting) { // LordHavoc: hope is not lost yet, check for a .lit file to load strlcpy (litfilename, loadmodel->name, sizeof (litfilename)); FS_StripExtension (litfilename, litfilename, sizeof (litfilename)); strlcpy (dlitfilename, litfilename, sizeof (dlitfilename)); strlcat (litfilename, ".lit", sizeof (litfilename)); strlcat (dlitfilename, ".dlit", sizeof (dlitfilename)); data = (unsigned char*) FS_LoadFile(litfilename, tempmempool, false, &filesize); if (data) { if (filesize == (fs_offset_t)(8 + sb->cursize * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') { i = LittleLong(((int *)data)[1]); if (i == 1) { if (developer_loading.integer) Con_Printf("loaded %s\n", litfilename); loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); memcpy(loadmodel->brushq1.lightdata, data + 8, filesize - 8); Mem_Free(data); data = (unsigned char*) FS_LoadFile(dlitfilename, tempmempool, false, &filesize); if (data) { if (filesize == (fs_offset_t)(8 + sb->cursize * 3) && data[0] == 'Q' && data[1] == 'L' && data[2] == 'I' && data[3] == 'T') { i = LittleLong(((int *)data)[1]); if (i == 1) { if (developer_loading.integer) Con_Printf("loaded %s\n", dlitfilename); loadmodel->brushq1.nmaplightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, filesize - 8); memcpy(loadmodel->brushq1.nmaplightdata, data + 8, filesize - 8); loadmodel->brushq3.deluxemapping_modelspace = false; loadmodel->brushq3.deluxemapping = true; } } Mem_Free(data); data = NULL; } return; } else Con_Printf("Unknown .lit file version (%d)\n", i); } else if (filesize == 8) Con_Print("Empty .lit file, ignoring\n"); else Con_Printf("Corrupt .lit file (file size %i bytes, should be %i bytes), ignoring\n", (int) filesize, (int) (8 + sb->cursize * 3)); if (data) { Mem_Free(data); data = NULL; } } // LordHavoc: oh well, expand the white lighting data if (!sb->cursize) return; loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize*3); in = sb->data; out = loadmodel->brushq1.lightdata; for (i = 0;i < sb->cursize;i++) { d = *in++; *out++ = d; *out++ = d; *out++ = d; } } } static void Mod_Q1BSP_LoadVisibility(sizebuf_t *sb) { loadmodel->brushq1.num_compressedpvs = 0; loadmodel->brushq1.data_compressedpvs = NULL; if (!sb->cursize) return; loadmodel->brushq1.num_compressedpvs = sb->cursize; loadmodel->brushq1.data_compressedpvs = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize); MSG_ReadBytes(sb, sb->cursize, loadmodel->brushq1.data_compressedpvs); } // used only for HalfLife maps static void Mod_Q1BSP_ParseWadsFromEntityLump(const char *data) { char key[128], value[4096]; int i, j, k; if (!data) return; if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] != '{') return; // error while (1) { if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] == '}') break; // end of worldspawn if (com_token[0] == '_') strlcpy(key, com_token + 1, sizeof(key)); else strlcpy(key, com_token, sizeof(key)); while (key[strlen(key)-1] == ' ') // remove trailing spaces key[strlen(key)-1] = 0; if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error dpsnprintf(value, sizeof(value), "%s", com_token); if (!strcmp("wad", key)) // for HalfLife maps { if (loadmodel->brush.ishlbsp) { j = 0; for (i = 0;i < (int)sizeof(value);i++) if (value[i] != ';' && value[i] != '\\' && value[i] != '/' && value[i] != ':') break; if (i < (int)sizeof(value) && value[i]) { for (;i < (int)sizeof(value);i++) { // ignore path - the \\ check is for HalfLife... stupid windoze 'programmers'... if (value[i] == '\\' || value[i] == '/' || value[i] == ':') j = i+1; else if (value[i] == ';' || value[i] == 0) { k = value[i]; value[i] = 0; W_LoadTextureWadFile(&value[j], false); j = i+1; if (!k) break; } } } } } } } static void Mod_Q1BSP_LoadEntities(sizebuf_t *sb) { loadmodel->brush.entities = NULL; if (!sb->cursize) return; loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, sb->cursize + 1); MSG_ReadBytes(sb, sb->cursize, (unsigned char *)loadmodel->brush.entities); loadmodel->brush.entities[sb->cursize] = 0; if (loadmodel->brush.ishlbsp) Mod_Q1BSP_ParseWadsFromEntityLump(loadmodel->brush.entities); } static void Mod_Q1BSP_LoadVertexes(sizebuf_t *sb) { mvertex_t *out; int i, count; int structsize = 12; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadVertexes: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mvertex_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brushq1.vertexes = out; loadmodel->brushq1.numvertexes = count; for ( i=0 ; iposition[0] = MSG_ReadLittleFloat(sb); out->position[1] = MSG_ReadLittleFloat(sb); out->position[2] = MSG_ReadLittleFloat(sb); } } static void Mod_Q1BSP_LoadSubmodels(sizebuf_t *sb, hullinfo_t *hullinfo) { mmodel_t *out; int i, j, count; int structsize = (48+4*hullinfo->filehulls); if (sb->cursize % structsize) Host_Error ("Mod_Q1BSP_LoadSubmodels: funny lump size in %s", loadmodel->name); count = sb->cursize / structsize; out = (mmodel_t *)Mem_Alloc (loadmodel->mempool, count*sizeof(*out)); loadmodel->brushq1.submodels = out; loadmodel->brush.numsubmodels = count; for (i = 0; i < count; i++, out++) { // spread out the mins / maxs by a pixel out->mins[0] = MSG_ReadLittleFloat(sb) - 1; out->mins[1] = MSG_ReadLittleFloat(sb) - 1; out->mins[2] = MSG_ReadLittleFloat(sb) - 1; out->maxs[0] = MSG_ReadLittleFloat(sb) + 1; out->maxs[1] = MSG_ReadLittleFloat(sb) + 1; out->maxs[2] = MSG_ReadLittleFloat(sb) + 1; out->origin[0] = MSG_ReadLittleFloat(sb); out->origin[1] = MSG_ReadLittleFloat(sb); out->origin[2] = MSG_ReadLittleFloat(sb); for (j = 0; j < hullinfo->filehulls; j++) out->headnode[j] = MSG_ReadLittleLong(sb); out->visleafs = MSG_ReadLittleLong(sb); out->firstface = MSG_ReadLittleLong(sb); out->numfaces = MSG_ReadLittleLong(sb); } } static void Mod_Q1BSP_LoadEdges(sizebuf_t *sb) { medge_t *out; int i, count; int structsize = loadmodel->brush.isbsp2 ? 8 : 4; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadEdges: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (medge_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq1.edges = out; loadmodel->brushq1.numedges = count; for ( i=0 ; ibrush.isbsp2) { out->v[0] = (unsigned int)MSG_ReadLittleLong(sb); out->v[1] = (unsigned int)MSG_ReadLittleLong(sb); } else { out->v[0] = (unsigned short)MSG_ReadLittleShort(sb); out->v[1] = (unsigned short)MSG_ReadLittleShort(sb); } if ((int)out->v[0] >= loadmodel->brushq1.numvertexes || (int)out->v[1] >= loadmodel->brushq1.numvertexes) { Con_Printf("Mod_Q1BSP_LoadEdges: %s has invalid vertex indices in edge %i (vertices %i %i >= numvertices %i)\n", loadmodel->name, i, out->v[0], out->v[1], loadmodel->brushq1.numvertexes); if(!loadmodel->brushq1.numvertexes) Host_Error("Mod_Q1BSP_LoadEdges: %s has edges but no vertexes, cannot fix\n", loadmodel->name); out->v[0] = 0; out->v[1] = 0; } } } static void Mod_Q1BSP_LoadTexinfo(sizebuf_t *sb) { mtexinfo_t *out; int i, j, k, count, miptex; int structsize = 40; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadTexinfo: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mtexinfo_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq1.texinfo = out; loadmodel->brushq1.numtexinfo = count; for (i = 0;i < count;i++, out++) { for (k = 0;k < 2;k++) for (j = 0;j < 4;j++) out->vecs[k][j] = MSG_ReadLittleFloat(sb); miptex = MSG_ReadLittleLong(sb); out->q1flags = MSG_ReadLittleLong(sb); if (out->q1flags & TEX_SPECIAL) { // if texture chosen is NULL or the shader needs a lightmap, // force to notexture water shader out->textureindex = loadmodel->num_textures - 1; } else { // if texture chosen is NULL, force to notexture out->textureindex = loadmodel->num_textures - 2; } // see if the specified miptex is valid and try to use it instead if (loadmodel->data_textures) { if ((unsigned int) miptex >= (unsigned int) loadmodel->num_textures) Con_Printf("error in model \"%s\": invalid miptex index %i(of %i)\n", loadmodel->name, miptex, loadmodel->num_textures); else out->textureindex = miptex; } } } #if 0 void BoundPoly(int numverts, float *verts, vec3_t mins, vec3_t maxs) { int i, j; float *v; mins[0] = mins[1] = mins[2] = 9999; maxs[0] = maxs[1] = maxs[2] = -9999; v = verts; for (i = 0;i < numverts;i++) { for (j = 0;j < 3;j++, v++) { if (*v < mins[j]) mins[j] = *v; if (*v > maxs[j]) maxs[j] = *v; } } } #define MAX_SUBDIVPOLYTRIANGLES 4096 #define MAX_SUBDIVPOLYVERTS(MAX_SUBDIVPOLYTRIANGLES * 3) static int subdivpolyverts, subdivpolytriangles; static int subdivpolyindex[MAX_SUBDIVPOLYTRIANGLES][3]; static float subdivpolyvert[MAX_SUBDIVPOLYVERTS][3]; static int subdivpolylookupvert(vec3_t v) { int i; for (i = 0;i < subdivpolyverts;i++) if (subdivpolyvert[i][0] == v[0] && subdivpolyvert[i][1] == v[1] && subdivpolyvert[i][2] == v[2]) return i; if (subdivpolyverts >= MAX_SUBDIVPOLYVERTS) Host_Error("SubDividePolygon: ran out of vertices in buffer, please increase your r_subdivide_size"); VectorCopy(v, subdivpolyvert[subdivpolyverts]); return subdivpolyverts++; } static void SubdividePolygon(int numverts, float *verts) { int i, i1, i2, i3, f, b, c, p; vec3_t mins, maxs, front[256], back[256]; float m, *pv, *cv, dist[256], frac; if (numverts > 250) Host_Error("SubdividePolygon: ran out of verts in buffer"); BoundPoly(numverts, verts, mins, maxs); for (i = 0;i < 3;i++) { m = (mins[i] + maxs[i]) * 0.5; m = r_subdivide_size.value * floor(m/r_subdivide_size.value + 0.5); if (maxs[i] - m < 8) continue; if (m - mins[i] < 8) continue; // cut it for (cv = verts, c = 0;c < numverts;c++, cv += 3) dist[c] = cv[i] - m; f = b = 0; for (p = numverts - 1, c = 0, pv = verts + p * 3, cv = verts;c < numverts;p = c, c++, pv = cv, cv += 3) { if (dist[p] >= 0) { VectorCopy(pv, front[f]); f++; } if (dist[p] <= 0) { VectorCopy(pv, back[b]); b++; } if (dist[p] == 0 || dist[c] == 0) continue; if ((dist[p] > 0) != (dist[c] > 0) ) { // clip point frac = dist[p] / (dist[p] - dist[c]); front[f][0] = back[b][0] = pv[0] + frac * (cv[0] - pv[0]); front[f][1] = back[b][1] = pv[1] + frac * (cv[1] - pv[1]); front[f][2] = back[b][2] = pv[2] + frac * (cv[2] - pv[2]); f++; b++; } } SubdividePolygon(f, front[0]); SubdividePolygon(b, back[0]); return; } i1 = subdivpolylookupvert(verts); i2 = subdivpolylookupvert(verts + 3); for (i = 2;i < numverts;i++) { if (subdivpolytriangles >= MAX_SUBDIVPOLYTRIANGLES) { Con_Print("SubdividePolygon: ran out of triangles in buffer, please increase your r_subdivide_size\n"); return; } i3 = subdivpolylookupvert(verts + i * 3); subdivpolyindex[subdivpolytriangles][0] = i1; subdivpolyindex[subdivpolytriangles][1] = i2; subdivpolyindex[subdivpolytriangles][2] = i3; i2 = i3; subdivpolytriangles++; } } //Breaks a polygon up along axial 64 unit //boundaries so that turbulent and sky warps //can be done reasonably. static void Mod_Q1BSP_GenerateWarpMesh(msurface_t *surface) { int i, j; surfvertex_t *v; surfmesh_t *mesh; subdivpolytriangles = 0; subdivpolyverts = 0; SubdividePolygon(surface->num_vertices, (surface->mesh->data_vertex3f + 3 * surface->num_firstvertex)); if (subdivpolytriangles < 1) Host_Error("Mod_Q1BSP_GenerateWarpMesh: no triangles?"); surface->mesh = mesh = Mem_Alloc(loadmodel->mempool, sizeof(surfmesh_t) + subdivpolytriangles * sizeof(int[3]) + subdivpolyverts * sizeof(surfvertex_t)); mesh->num_vertices = subdivpolyverts; mesh->num_triangles = subdivpolytriangles; mesh->vertex = (surfvertex_t *)(mesh + 1); mesh->index = (int *)(mesh->vertex + mesh->num_vertices); memset(mesh->vertex, 0, mesh->num_vertices * sizeof(surfvertex_t)); for (i = 0;i < mesh->num_triangles;i++) for (j = 0;j < 3;j++) mesh->index[i*3+j] = subdivpolyindex[i][j]; for (i = 0, v = mesh->vertex;i < subdivpolyverts;i++, v++) { VectorCopy(subdivpolyvert[i], v->v); v->st[0] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[0]); v->st[1] = DotProduct(v->v, surface->lightmapinfo->texinfo->vecs[1]); } } #endif extern cvar_t gl_max_lightmapsize; static void Mod_Q1BSP_LoadFaces(sizebuf_t *sb) { msurface_t *surface; int i, j, count, surfacenum, planenum, smax, tmax, ssize, tsize, firstedge, numedges, totalverts, totaltris, lightmapnumber, lightmapsize, totallightmapsamples, lightmapoffset, texinfoindex; float texmins[2], texmaxs[2], val; rtexture_t *lightmaptexture, *deluxemaptexture; char vabuf[1024]; int structsize = loadmodel->brush.isbsp2 ? 28 : 20; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadFaces: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; loadmodel->data_surfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_t)); loadmodel->data_surfaces_lightmapinfo = (msurface_lightmapinfo_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(msurface_lightmapinfo_t)); loadmodel->num_surfaces = count; loadmodel->brushq1.firstrender = true; loadmodel->brushq1.lightmapupdateflags = (unsigned char *)Mem_Alloc(loadmodel->mempool, count*sizeof(unsigned char)); totalverts = 0; totaltris = 0; for (surfacenum = 0;surfacenum < count;surfacenum++) { if (loadmodel->brush.isbsp2) numedges = BuffLittleLong(sb->data + structsize * surfacenum + 12); else numedges = BuffLittleShort(sb->data + structsize * surfacenum + 8); totalverts += numedges; totaltris += numedges - 2; } Mod_AllocSurfMesh(loadmodel->mempool, totalverts, totaltris, true, false, false); lightmaptexture = NULL; deluxemaptexture = r_texture_blanknormalmap; lightmapnumber = 0; lightmapsize = bound(256, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d); totallightmapsamples = 0; totalverts = 0; totaltris = 0; for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) { surface->lightmapinfo = loadmodel->data_surfaces_lightmapinfo + surfacenum; // the struct on disk is the same in BSP29 (Q1), BSP30 (HL1), and IBSP38 (Q2) planenum = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); /*side = */loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); firstedge = MSG_ReadLittleLong(sb); numedges = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); texinfoindex = loadmodel->brush.isbsp2 ? MSG_ReadLittleLong(sb) : (unsigned short)MSG_ReadLittleShort(sb); for (i = 0;i < MAXLIGHTMAPS;i++) surface->lightmapinfo->styles[i] = MSG_ReadByte(sb); lightmapoffset = MSG_ReadLittleLong(sb); // FIXME: validate edges, texinfo, etc? if ((unsigned int) firstedge > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges || (unsigned int) firstedge + (unsigned int) numedges > (unsigned int) loadmodel->brushq1.numsurfedges) Host_Error("Mod_Q1BSP_LoadFaces: invalid edge range (firstedge %i, numedges %i, model edges %i)", firstedge, numedges, loadmodel->brushq1.numsurfedges); if ((unsigned int) texinfoindex >= (unsigned int) loadmodel->brushq1.numtexinfo) Host_Error("Mod_Q1BSP_LoadFaces: invalid texinfo index %i(model has %i texinfos)", texinfoindex, loadmodel->brushq1.numtexinfo); if ((unsigned int) planenum >= (unsigned int) loadmodel->brush.num_planes) Host_Error("Mod_Q1BSP_LoadFaces: invalid plane index %i (model has %i planes)", planenum, loadmodel->brush.num_planes); surface->lightmapinfo->texinfo = loadmodel->brushq1.texinfo + texinfoindex; surface->texture = loadmodel->data_textures + surface->lightmapinfo->texinfo->textureindex; // Q2BSP doesn't use lightmaps on sky or warped surfaces (water), but still has a lightofs of 0 if (lightmapoffset == 0 && (surface->texture->q2flags & (Q2SURF_SKY | Q2SURF_WARP))) lightmapoffset = -1; //surface->flags = surface->texture->flags; //if (LittleShort(in->side)) // surface->flags |= SURF_PLANEBACK; //surface->plane = loadmodel->brush.data_planes + planenum; surface->num_firstvertex = totalverts; surface->num_vertices = numedges; surface->num_firsttriangle = totaltris; surface->num_triangles = numedges - 2; totalverts += numedges; totaltris += numedges - 2; // convert edges back to a normal polygon for (i = 0;i < surface->num_vertices;i++) { int lindex = loadmodel->brushq1.surfedges[firstedge + i]; float s, t; // note: the q1bsp format does not allow a 0 surfedge (it would have no negative counterpart) if (lindex >= 0) VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[lindex].v[0]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); else VectorCopy(loadmodel->brushq1.vertexes[loadmodel->brushq1.edges[-lindex].v[1]].position, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3); s = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; t = DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 0] = s / surface->texture->width; (loadmodel->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[i * 2 + 1] = t / surface->texture->height; (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = 0; (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = 0; (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = 0; } for (i = 0;i < surface->num_triangles;i++) { (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 0] = 0 + surface->num_firstvertex; (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 1] = i + 1 + surface->num_firstvertex; (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[i * 3 + 2] = i + 2 + surface->num_firstvertex; } // compile additional data about the surface geometry Mod_BuildNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); Mod_BuildTextureVectorsFromNormals(surface->num_firstvertex, surface->num_vertices, surface->num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, (loadmodel->surfmesh.data_element3i + 3 * surface->num_firsttriangle), loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); BoxFromPoints(surface->mins, surface->maxs, surface->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)); // generate surface extents information texmins[0] = texmaxs[0] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]; texmins[1] = texmaxs[1] = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]; for (i = 1;i < surface->num_vertices;i++) { for (j = 0;j < 2;j++) { val = DotProduct((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3, surface->lightmapinfo->texinfo->vecs[j]) + surface->lightmapinfo->texinfo->vecs[j][3]; texmins[j] = min(texmins[j], val); texmaxs[j] = max(texmaxs[j], val); } } for (i = 0;i < 2;i++) { surface->lightmapinfo->texturemins[i] = (int) floor(texmins[i] / 16.0) * 16; surface->lightmapinfo->extents[i] = (int) ceil(texmaxs[i] / 16.0) * 16 - surface->lightmapinfo->texturemins[i]; } smax = surface->lightmapinfo->extents[0] >> 4; tmax = surface->lightmapinfo->extents[1] >> 4; ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; // lighting info surface->lightmaptexture = NULL; surface->deluxemaptexture = r_texture_blanknormalmap; if (lightmapoffset == -1) { surface->lightmapinfo->samples = NULL; #if 1 // give non-lightmapped water a 1x white lightmap if (!loadmodel->brush.isq2bsp && surface->texture->name[0] == '*' && (surface->lightmapinfo->texinfo->q1flags & TEX_SPECIAL) && ssize <= 256 && tsize <= 256) { surface->lightmapinfo->samples = (unsigned char *)Mem_Alloc(loadmodel->mempool, ssize * tsize * 3); surface->lightmapinfo->styles[0] = 0; memset(surface->lightmapinfo->samples, 128, ssize * tsize * 3); } #endif } else if (loadmodel->brush.ishlbsp || loadmodel->brush.isq2bsp) // LordHavoc: HalfLife map (bsp version 30) surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + lightmapoffset; else // LordHavoc: white lighting (bsp version 29) { surface->lightmapinfo->samples = loadmodel->brushq1.lightdata + (lightmapoffset * 3); if (loadmodel->brushq1.nmaplightdata) surface->lightmapinfo->nmapsamples = loadmodel->brushq1.nmaplightdata + (lightmapoffset * 3); } // check if we should apply a lightmap to this if (!(surface->lightmapinfo->texinfo->q1flags & TEX_SPECIAL) || surface->lightmapinfo->samples) { if (ssize > 256 || tsize > 256) Host_Error("Bad surface extents"); if (lightmapsize < ssize) lightmapsize = ssize; if (lightmapsize < tsize) lightmapsize = tsize; totallightmapsamples += ssize*tsize; // force lightmap upload on first time seeing the surface // // additionally this is used by the later code to see if a // lightmap is needed on this surface (rather than duplicating the // logic above) loadmodel->brushq1.lightmapupdateflags[surfacenum] = true; loadmodel->lit = true; } } // small maps (such as ammo boxes especially) don't need big lightmap // textures, so this code tries to guess a good size based on // totallightmapsamples (size of the lightmaps lump basically), as well as // trying to max out the size if there is a lot of lightmap data to store // additionally, never choose a lightmapsize that is smaller than the // largest surface encountered (as it would fail) i = lightmapsize; for (lightmapsize = 64; (lightmapsize < i) && (lightmapsize < bound(128, gl_max_lightmapsize.integer, (int)vid.maxtexturesize_2d)) && (totallightmapsamples > lightmapsize*lightmapsize); lightmapsize*=2) ; // now that we've decided the lightmap texture size, we can do the rest if (cls.state != ca_dedicated) { int stainmapsize = 0; mod_alloclightmap_state_t allocState; Mod_AllocLightmap_Init(&allocState, lightmapsize, lightmapsize); for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) { int iu, iv, lightmapx = 0, lightmapy = 0; float u, v, ubase, vbase, uscale, vscale; if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) continue; smax = surface->lightmapinfo->extents[0] >> 4; tmax = surface->lightmapinfo->extents[1] >> 4; ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; stainmapsize += ssize * tsize * 3; if (!lightmaptexture || !Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy)) { // allocate a texture pool if we need it if (loadmodel->texturepool == NULL) loadmodel->texturepool = R_AllocTexturePool(); // could not find room, make a new lightmap loadmodel->brushq3.num_mergedlightmaps = lightmapnumber + 1; loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_lightmaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_lightmaps[0])); loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Realloc(loadmodel->mempool, loadmodel->brushq3.data_deluxemaps, loadmodel->brushq3.num_mergedlightmaps * sizeof(loadmodel->brushq3.data_deluxemaps[0])); loadmodel->brushq3.data_lightmaps[lightmapnumber] = lightmaptexture = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); if (loadmodel->brushq1.nmaplightdata) loadmodel->brushq3.data_deluxemaps[lightmapnumber] = deluxemaptexture = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%i", lightmapnumber), lightmapsize, lightmapsize, NULL, TEXTYPE_BGRA, TEXF_FORCELINEAR | TEXF_ALLOWUPDATES, -1, NULL); lightmapnumber++; Mod_AllocLightmap_Reset(&allocState); Mod_AllocLightmap_Block(&allocState, ssize, tsize, &lightmapx, &lightmapy); } surface->lightmaptexture = lightmaptexture; surface->deluxemaptexture = deluxemaptexture; surface->lightmapinfo->lightmaporigin[0] = lightmapx; surface->lightmapinfo->lightmaporigin[1] = lightmapy; uscale = 1.0f / (float)lightmapsize; vscale = 1.0f / (float)lightmapsize; ubase = lightmapx * uscale; vbase = lightmapy * vscale; for (i = 0;i < surface->num_vertices;i++) { u = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[0]) + surface->lightmapinfo->texinfo->vecs[0][3]) + 8 - surface->lightmapinfo->texturemins[0]) * (1.0 / 16.0); v = ((DotProduct(((loadmodel->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + i * 3), surface->lightmapinfo->texinfo->vecs[1]) + surface->lightmapinfo->texinfo->vecs[1][3]) + 8 - surface->lightmapinfo->texturemins[1]) * (1.0 / 16.0); (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 0] = u * uscale + ubase; (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[i * 2 + 1] = v * vscale + vbase; // LordHavoc: calc lightmap data offset for vertex lighting to use iu = (int) u; iv = (int) v; (loadmodel->surfmesh.data_lightmapoffsets + surface->num_firstvertex)[i] = (bound(0, iv, tmax) * ssize + bound(0, iu, smax)) * 3; } } if (cl_stainmaps.integer) { // allocate stainmaps for permanent marks on walls and clear white unsigned char *stainsamples = NULL; stainsamples = (unsigned char *)Mem_Alloc(loadmodel->mempool, stainmapsize); memset(stainsamples, 255, stainmapsize); // assign pointers for (surfacenum = 0, surface = loadmodel->data_surfaces;surfacenum < count;surfacenum++, surface++) { if (!loadmodel->brushq1.lightmapupdateflags[surfacenum]) continue; ssize = (surface->lightmapinfo->extents[0] >> 4) + 1; tsize = (surface->lightmapinfo->extents[1] >> 4) + 1; surface->lightmapinfo->stainsamples = stainsamples; stainsamples += ssize * tsize * 3; } } } // generate ushort elements array if possible if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; } static void Mod_Q1BSP_LoadNodes_RecursiveSetParent(mnode_t *node, mnode_t *parent) { //if (node->parent) // Host_Error("Mod_Q1BSP_LoadNodes_RecursiveSetParent: runaway recursion"); node->parent = parent; if (node->plane) { // this is a node, recurse to children Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[0], node); Mod_Q1BSP_LoadNodes_RecursiveSetParent(node->children[1], node); // combine supercontents of children node->combinedsupercontents = node->children[0]->combinedsupercontents | node->children[1]->combinedsupercontents; } else { int j; mleaf_t *leaf = (mleaf_t *)node; // if this is a leaf, calculate supercontents mask from all collidable // primitives in the leaf (brushes and collision surfaces) // also flag if the leaf contains any collision surfaces leaf->combinedsupercontents = 0; // combine the supercontents values of all brushes in this leaf for (j = 0;j < leaf->numleafbrushes;j++) leaf->combinedsupercontents |= loadmodel->brush.data_brushes[leaf->firstleafbrush[j]].texture->supercontents; // check if this leaf contains any collision surfaces (q3 patches) for (j = 0;j < leaf->numleafsurfaces;j++) { msurface_t *surface = loadmodel->data_surfaces + leaf->firstleafsurface[j]; if (surface->num_collisiontriangles) { leaf->containscollisionsurfaces = true; leaf->combinedsupercontents |= surface->texture->supercontents; } } } } static void Mod_Q1BSP_LoadNodes(sizebuf_t *sb) { int i, j, count, p, child[2]; mnode_t *out; int structsize = loadmodel->brush.isbsp2rmqe ? 32 : (loadmodel->brush.isbsp2 ? 44 : 24); if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadNodes: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; if (count == 0) Host_Error("Mod_Q1BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brush.data_nodes = out; loadmodel->brush.num_nodes = count; for ( i=0 ; iplane = loadmodel->brush.data_planes + p; if (loadmodel->brush.isbsp2rmqe) { child[0] = MSG_ReadLittleLong(sb); child[1] = MSG_ReadLittleLong(sb); out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); out->firstsurface = MSG_ReadLittleLong(sb); out->numsurfaces = MSG_ReadLittleLong(sb); } else if (loadmodel->brush.isbsp2) { child[0] = MSG_ReadLittleLong(sb); child[1] = MSG_ReadLittleLong(sb); out->mins[0] = MSG_ReadLittleFloat(sb); out->mins[1] = MSG_ReadLittleFloat(sb); out->mins[2] = MSG_ReadLittleFloat(sb); out->maxs[0] = MSG_ReadLittleFloat(sb); out->maxs[1] = MSG_ReadLittleFloat(sb); out->maxs[2] = MSG_ReadLittleFloat(sb); out->firstsurface = MSG_ReadLittleLong(sb); out->numsurfaces = MSG_ReadLittleLong(sb); } else { child[0] = (unsigned short)MSG_ReadLittleShort(sb); child[1] = (unsigned short)MSG_ReadLittleShort(sb); if (child[0] >= count) child[0] -= 65536; if (child[1] >= count) child[1] -= 65536; out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); out->firstsurface = (unsigned short)MSG_ReadLittleShort(sb); out->numsurfaces = (unsigned short)MSG_ReadLittleShort(sb); } for (j=0 ; j<2 ; j++) { // LordHavoc: this code supports broken bsp files produced by // arguire qbsp which can produce more than 32768 nodes, any value // below count is assumed to be a node number, any other value is // assumed to be a leaf number p = child[j]; if (p >= 0) { if (p < loadmodel->brush.num_nodes) out->children[j] = loadmodel->brush.data_nodes + p; else { Con_Printf("Mod_Q1BSP_LoadNodes: invalid node index %i (file has only %i nodes)\n", p, loadmodel->brush.num_nodes); // map it to the solid leaf out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; } } else { // get leaf index as a positive value starting at 0 (-1 becomes 0, -2 becomes 1, etc) p = -(p+1); if (p < loadmodel->brush.num_leafs) out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + p); else { Con_Printf("Mod_Q1BSP_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->brush.num_leafs); // map it to the solid leaf out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; } } } } Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); // sets nodes and leafs } static void Mod_Q1BSP_LoadLeafs(sizebuf_t *sb) { mleaf_t *out; int i, j, count, p, firstmarksurface, nummarksurfaces; int structsize = loadmodel->brush.isbsp2rmqe ? 32 : (loadmodel->brush.isbsp2 ? 44 : 28); if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadLeafs: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brush.data_leafs = out; loadmodel->brush.num_leafs = count; // get visleafs from the submodel data loadmodel->brush.num_pvsclusters = loadmodel->brushq1.submodels[0].visleafs; loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters+7)>>3; loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); memset(loadmodel->brush.data_pvsclusters, 0xFF, loadmodel->brush.num_pvsclusters * loadmodel->brush.num_pvsclusterbytes); // FIXME: this function could really benefit from some error checking for ( i=0 ; icontents = MSG_ReadLittleLong(sb); out->clusterindex = i - 1; if (out->clusterindex >= loadmodel->brush.num_pvsclusters) out->clusterindex = -1; p = MSG_ReadLittleLong(sb); // ignore visofs errors on leaf 0 (solid) if (p >= 0 && out->clusterindex >= 0) { if (p >= loadmodel->brushq1.num_compressedpvs) Con_Print("Mod_Q1BSP_LoadLeafs: invalid visofs\n"); else Mod_Q1BSP_DecompressVis(loadmodel->brushq1.data_compressedpvs + p, loadmodel->brushq1.data_compressedpvs + loadmodel->brushq1.num_compressedpvs, loadmodel->brush.data_pvsclusters + out->clusterindex * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (out->clusterindex + 1) * loadmodel->brush.num_pvsclusterbytes); } if (loadmodel->brush.isbsp2rmqe) { out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); firstmarksurface = MSG_ReadLittleLong(sb); nummarksurfaces = MSG_ReadLittleLong(sb); } else if (loadmodel->brush.isbsp2) { out->mins[0] = MSG_ReadLittleFloat(sb); out->mins[1] = MSG_ReadLittleFloat(sb); out->mins[2] = MSG_ReadLittleFloat(sb); out->maxs[0] = MSG_ReadLittleFloat(sb); out->maxs[1] = MSG_ReadLittleFloat(sb); out->maxs[2] = MSG_ReadLittleFloat(sb); firstmarksurface = MSG_ReadLittleLong(sb); nummarksurfaces = MSG_ReadLittleLong(sb); } else { out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); firstmarksurface = (unsigned short)MSG_ReadLittleShort(sb); nummarksurfaces = (unsigned short)MSG_ReadLittleShort(sb); } if (firstmarksurface >= 0 && firstmarksurface + nummarksurfaces <= loadmodel->brush.num_leafsurfaces) { out->firstleafsurface = loadmodel->brush.data_leafsurfaces + firstmarksurface; out->numleafsurfaces = nummarksurfaces; } else { Con_Printf("Mod_Q1BSP_LoadLeafs: invalid leafsurface range %i:%i outside range %i:%i\n", firstmarksurface, firstmarksurface+nummarksurfaces, 0, loadmodel->brush.num_leafsurfaces); out->firstleafsurface = NULL; out->numleafsurfaces = 0; } for (j = 0;j < 4;j++) out->ambient_sound_level[j] = MSG_ReadByte(sb); } } static qboolean Mod_Q1BSP_CheckWaterAlphaSupport(void) { int i, j; mleaf_t *leaf; const unsigned char *pvs; // if there's no vis data, assume supported (because everything is visible all the time) if (!loadmodel->brush.data_pvsclusters) return true; // check all liquid leafs to see if they can see into empty leafs, if any // can we can assume this map supports r_wateralpha for (i = 0, leaf = loadmodel->brush.data_leafs;i < loadmodel->brush.num_leafs;i++, leaf++) { if ((leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME) && leaf->clusterindex >= 0) { pvs = loadmodel->brush.data_pvsclusters + leaf->clusterindex * loadmodel->brush.num_pvsclusterbytes; for (j = 0;j < loadmodel->brush.num_leafs;j++) if (CHECKPVSBIT(pvs, loadmodel->brush.data_leafs[j].clusterindex) && loadmodel->brush.data_leafs[j].contents == CONTENTS_EMPTY) return true; } } return false; } static void Mod_Q1BSP_LoadClipnodes(sizebuf_t *sb, hullinfo_t *hullinfo) { mclipnode_t *out; int i, count; hull_t *hull; int structsize = loadmodel->brush.isbsp2 ? 12 : 8; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadClipnodes: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brushq1.clipnodes = out; loadmodel->brushq1.numclipnodes = count; for (i = 1; i < MAX_MAP_HULLS; i++) { hull = &loadmodel->brushq1.hulls[i]; hull->clipnodes = out; hull->firstclipnode = 0; hull->lastclipnode = count-1; hull->planes = loadmodel->brush.data_planes; hull->clip_mins[0] = hullinfo->hullsizes[i][0][0]; hull->clip_mins[1] = hullinfo->hullsizes[i][0][1]; hull->clip_mins[2] = hullinfo->hullsizes[i][0][2]; hull->clip_maxs[0] = hullinfo->hullsizes[i][1][0]; hull->clip_maxs[1] = hullinfo->hullsizes[i][1][1]; hull->clip_maxs[2] = hullinfo->hullsizes[i][1][2]; VectorSubtract(hull->clip_maxs, hull->clip_mins, hull->clip_size); } for (i=0 ; iplanenum = MSG_ReadLittleLong(sb); if (out->planenum < 0 || out->planenum >= loadmodel->brush.num_planes) Host_Error("%s: Corrupt clipping hull(out of range planenum)", loadmodel->name); if (loadmodel->brush.isbsp2) { out->children[0] = MSG_ReadLittleLong(sb); out->children[1] = MSG_ReadLittleLong(sb); if (out->children[0] >= count) Host_Error("%s: Corrupt clipping hull (invalid child index)", loadmodel->name); if (out->children[1] >= count) Host_Error("%s: Corrupt clipping hull (invalid child index)", loadmodel->name); } else { // LordHavoc: this code supports arguire qbsp's broken clipnodes indices (more than 32768 clipnodes), values above count are assumed to be contents values out->children[0] = (unsigned short)MSG_ReadLittleShort(sb); out->children[1] = (unsigned short)MSG_ReadLittleShort(sb); if (out->children[0] >= count) out->children[0] -= 65536; if (out->children[1] >= count) out->children[1] -= 65536; } } } //Duplicate the drawing hull structure as a clipping hull static void Mod_Q1BSP_MakeHull0(void) { mnode_t *in; mclipnode_t *out; int i; hull_t *hull; hull = &loadmodel->brushq1.hulls[0]; in = loadmodel->brush.data_nodes; out = (mclipnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_nodes * sizeof(*out)); hull->clipnodes = out; hull->firstclipnode = 0; hull->lastclipnode = loadmodel->brush.num_nodes - 1; hull->planes = loadmodel->brush.data_planes; for (i = 0;i < loadmodel->brush.num_nodes;i++, out++, in++) { out->planenum = in->plane - loadmodel->brush.data_planes; out->children[0] = in->children[0]->plane ? in->children[0] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[0])->contents; out->children[1] = in->children[1]->plane ? in->children[1] - loadmodel->brush.data_nodes : ((mleaf_t *)in->children[1])->contents; } } static void Mod_Q1BSP_LoadLeaffaces(sizebuf_t *sb) { int i, j; int structsize = loadmodel->brush.isbsp2 ? 4 : 2; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadLeaffaces: funny lump size in %s",loadmodel->name); loadmodel->brush.num_leafsurfaces = sb->cursize / structsize; loadmodel->brush.data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafsurfaces * sizeof(int)); if (loadmodel->brush.isbsp2) { for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++) { j = MSG_ReadLittleLong(sb); if (j < 0 || j >= loadmodel->num_surfaces) Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number"); loadmodel->brush.data_leafsurfaces[i] = j; } } else { for (i = 0;i < loadmodel->brush.num_leafsurfaces;i++) { j = (unsigned short) MSG_ReadLittleShort(sb); if (j >= loadmodel->num_surfaces) Host_Error("Mod_Q1BSP_LoadLeaffaces: bad surface number"); loadmodel->brush.data_leafsurfaces[i] = j; } } } static void Mod_Q1BSP_LoadSurfedges(sizebuf_t *sb) { int i; int structsize = 4; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadSurfedges: funny lump size in %s",loadmodel->name); loadmodel->brushq1.numsurfedges = sb->cursize / structsize; loadmodel->brushq1.surfedges = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brushq1.numsurfedges * sizeof(int)); for (i = 0;i < loadmodel->brushq1.numsurfedges;i++) loadmodel->brushq1.surfedges[i] = MSG_ReadLittleLong(sb); } static void Mod_Q1BSP_LoadPlanes(sizebuf_t *sb) { int i; mplane_t *out; int structsize = 20; if (sb->cursize % structsize) Host_Error("Mod_Q1BSP_LoadPlanes: funny lump size in %s", loadmodel->name); loadmodel->brush.num_planes = sb->cursize / structsize; loadmodel->brush.data_planes = out = (mplane_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_planes * sizeof(*out)); for (i = 0;i < loadmodel->brush.num_planes;i++, out++) { out->normal[0] = MSG_ReadLittleFloat(sb); out->normal[1] = MSG_ReadLittleFloat(sb); out->normal[2] = MSG_ReadLittleFloat(sb); out->dist = MSG_ReadLittleFloat(sb); MSG_ReadLittleLong(sb); // type is not used, we use PlaneClassify PlaneClassify(out); } } static void Mod_Q1BSP_LoadMapBrushes(void) { #if 0 // unfinished int submodel, numbrushes; qboolean firstbrush; char *text, *maptext; char mapfilename[MAX_QPATH]; FS_StripExtension (loadmodel->name, mapfilename, sizeof (mapfilename)); strlcat (mapfilename, ".map", sizeof (mapfilename)); maptext = (unsigned char*) FS_LoadFile(mapfilename, tempmempool, false, NULL); if (!maptext) return; text = maptext; if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error submodel = 0; for (;;) { if (!COM_ParseToken_Simple(&data, false, false, true)) break; if (com_token[0] != '{') return; // error // entity firstbrush = true; numbrushes = 0; maxbrushes = 256; brushes = Mem_Alloc(loadmodel->mempool, maxbrushes * sizeof(mbrush_t)); for (;;) { if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] == '}') break; // end of entity if (com_token[0] == '{') { // brush if (firstbrush) { if (submodel) { if (submodel > loadmodel->brush.numsubmodels) { Con_Printf("Mod_Q1BSP_LoadMapBrushes: .map has more submodels than .bsp!\n"); model = NULL; } else model = loadmodel->brush.submodels[submodel]; } else model = loadmodel; } for (;;) { if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] == '}') break; // end of brush // each brush face should be this format: // ( x y z ) ( x y z ) ( x y z ) texture scroll_s scroll_t rotateangle scale_s scale_t // FIXME: support hl .map format for (pointnum = 0;pointnum < 3;pointnum++) { COM_ParseToken_Simple(&data, false, false, true); for (componentnum = 0;componentnum < 3;componentnum++) { COM_ParseToken_Simple(&data, false, false, true); point[pointnum][componentnum] = atof(com_token); } COM_ParseToken_Simple(&data, false, false, true); } COM_ParseToken_Simple(&data, false, false, true); strlcpy(facetexture, com_token, sizeof(facetexture)); COM_ParseToken_Simple(&data, false, false, true); //scroll_s = atof(com_token); COM_ParseToken_Simple(&data, false, false, true); //scroll_t = atof(com_token); COM_ParseToken_Simple(&data, false, false, true); //rotate = atof(com_token); COM_ParseToken_Simple(&data, false, false, true); //scale_s = atof(com_token); COM_ParseToken_Simple(&data, false, false, true); //scale_t = atof(com_token); TriangleNormal(point[0], point[1], point[2], planenormal); VectorNormalizeDouble(planenormal); planedist = DotProduct(point[0], planenormal); //ChooseTexturePlane(planenormal, texturevector[0], texturevector[1]); } continue; } } } #endif } #define MAX_PORTALPOINTS 64 typedef struct portal_s { mplane_t plane; mnode_t *nodes[2]; // [0] = front side of plane struct portal_s *next[2]; int numpoints; double points[3*MAX_PORTALPOINTS]; struct portal_s *chain; // all portals are linked into a list } portal_t; static memexpandablearray_t portalarray; static void Mod_Q1BSP_RecursiveRecalcNodeBBox(mnode_t *node) { // process only nodes (leafs already had their box calculated) if (!node->plane) return; // calculate children first Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[0]); Mod_Q1BSP_RecursiveRecalcNodeBBox(node->children[1]); // make combined bounding box from children node->mins[0] = min(node->children[0]->mins[0], node->children[1]->mins[0]); node->mins[1] = min(node->children[0]->mins[1], node->children[1]->mins[1]); node->mins[2] = min(node->children[0]->mins[2], node->children[1]->mins[2]); node->maxs[0] = max(node->children[0]->maxs[0], node->children[1]->maxs[0]); node->maxs[1] = max(node->children[0]->maxs[1], node->children[1]->maxs[1]); node->maxs[2] = max(node->children[0]->maxs[2], node->children[1]->maxs[2]); } static void Mod_Q1BSP_FinalizePortals(void) { int i, j, numportals, numpoints, portalindex, portalrange = (int)Mem_ExpandableArray_IndexRange(&portalarray); portal_t *p; mportal_t *portal; mvertex_t *point; mleaf_t *leaf, *endleaf; // tally up portal and point counts and recalculate bounding boxes for all // leafs (because qbsp is very sloppy) leaf = loadmodel->brush.data_leafs; endleaf = leaf + loadmodel->brush.num_leafs; if (mod_recalculatenodeboxes.integer) { for (;leaf < endleaf;leaf++) { VectorSet(leaf->mins, 2000000000, 2000000000, 2000000000); VectorSet(leaf->maxs, -2000000000, -2000000000, -2000000000); } } numportals = 0; numpoints = 0; for (portalindex = 0;portalindex < portalrange;portalindex++) { p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); if (!p) continue; // note: this check must match the one below or it will usually corrupt memory // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1] && ((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) { numportals += 2; numpoints += p->numpoints * 2; } } loadmodel->brush.data_portals = (mportal_t *)Mem_Alloc(loadmodel->mempool, numportals * sizeof(mportal_t) + numpoints * sizeof(mvertex_t)); loadmodel->brush.num_portals = numportals; loadmodel->brush.data_portalpoints = (mvertex_t *)((unsigned char *) loadmodel->brush.data_portals + numportals * sizeof(mportal_t)); loadmodel->brush.num_portalpoints = numpoints; // clear all leaf portal chains for (i = 0;i < loadmodel->brush.num_leafs;i++) loadmodel->brush.data_leafs[i].portals = NULL; // process all portals in the global portal chain, while freeing them portal = loadmodel->brush.data_portals; point = loadmodel->brush.data_portalpoints; for (portalindex = 0;portalindex < portalrange;portalindex++) { p = (portal_t*)Mem_ExpandableArray_RecordAtIndex(&portalarray, portalindex); if (!p) continue; if (p->numpoints >= 3 && p->nodes[0] != p->nodes[1]) { // note: this check must match the one above or it will usually corrupt memory // the nodes[0] != nodes[1] check is because leaf 0 is the shared solid leaf, it can have many portals inside with leaf 0 on both sides if (((mleaf_t *)p->nodes[0])->clusterindex >= 0 && ((mleaf_t *)p->nodes[1])->clusterindex >= 0) { // first make the back to front portal(forward portal) portal->points = point; portal->numpoints = p->numpoints; portal->plane.dist = p->plane.dist; VectorCopy(p->plane.normal, portal->plane.normal); portal->here = (mleaf_t *)p->nodes[1]; portal->past = (mleaf_t *)p->nodes[0]; // copy points for (j = 0;j < portal->numpoints;j++) { VectorCopy(p->points + j*3, point->position); point++; } BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); PlaneClassify(&portal->plane); // link into leaf's portal chain portal->next = portal->here->portals; portal->here->portals = portal; // advance to next portal portal++; // then make the front to back portal(backward portal) portal->points = point; portal->numpoints = p->numpoints; portal->plane.dist = -p->plane.dist; VectorNegate(p->plane.normal, portal->plane.normal); portal->here = (mleaf_t *)p->nodes[0]; portal->past = (mleaf_t *)p->nodes[1]; // copy points for (j = portal->numpoints - 1;j >= 0;j--) { VectorCopy(p->points + j*3, point->position); point++; } BoxFromPoints(portal->mins, portal->maxs, portal->numpoints, portal->points->position); PlaneClassify(&portal->plane); // link into leaf's portal chain portal->next = portal->here->portals; portal->here->portals = portal; // advance to next portal portal++; } // add the portal's polygon points to the leaf bounding boxes if (mod_recalculatenodeboxes.integer) { for (i = 0;i < 2;i++) { leaf = (mleaf_t *)p->nodes[i]; for (j = 0;j < p->numpoints;j++) { if (leaf->mins[0] > p->points[j*3+0]) leaf->mins[0] = p->points[j*3+0]; if (leaf->mins[1] > p->points[j*3+1]) leaf->mins[1] = p->points[j*3+1]; if (leaf->mins[2] > p->points[j*3+2]) leaf->mins[2] = p->points[j*3+2]; if (leaf->maxs[0] < p->points[j*3+0]) leaf->maxs[0] = p->points[j*3+0]; if (leaf->maxs[1] < p->points[j*3+1]) leaf->maxs[1] = p->points[j*3+1]; if (leaf->maxs[2] < p->points[j*3+2]) leaf->maxs[2] = p->points[j*3+2]; } } } } } // now recalculate the node bounding boxes from the leafs if (mod_recalculatenodeboxes.integer) Mod_Q1BSP_RecursiveRecalcNodeBBox(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); } /* ============= AddPortalToNodes ============= */ static void AddPortalToNodes(portal_t *p, mnode_t *front, mnode_t *back) { if (!front) Host_Error("AddPortalToNodes: NULL front node"); if (!back) Host_Error("AddPortalToNodes: NULL back node"); if (p->nodes[0] || p->nodes[1]) Host_Error("AddPortalToNodes: already included"); // note: front == back is handled gracefully, because leaf 0 is the shared solid leaf, it can often have portals with the same leaf on both sides p->nodes[0] = front; p->next[0] = (portal_t *)front->portals; front->portals = (mportal_t *)p; p->nodes[1] = back; p->next[1] = (portal_t *)back->portals; back->portals = (mportal_t *)p; } /* ============= RemovePortalFromNode ============= */ static void RemovePortalFromNodes(portal_t *portal) { int i; mnode_t *node; void **portalpointer; portal_t *t; for (i = 0;i < 2;i++) { node = portal->nodes[i]; portalpointer = (void **) &node->portals; while (1) { t = (portal_t *)*portalpointer; if (!t) Host_Error("RemovePortalFromNodes: portal not in leaf"); if (t == portal) { if (portal->nodes[0] == node) { *portalpointer = portal->next[0]; portal->nodes[0] = NULL; } else if (portal->nodes[1] == node) { *portalpointer = portal->next[1]; portal->nodes[1] = NULL; } else Host_Error("RemovePortalFromNodes: portal not bounding leaf"); break; } if (t->nodes[0] == node) portalpointer = (void **) &t->next[0]; else if (t->nodes[1] == node) portalpointer = (void **) &t->next[1]; else Host_Error("RemovePortalFromNodes: portal not bounding leaf"); } } } #define PORTAL_DIST_EPSILON (1.0 / 32.0) static double *portalpointsbuffer; static int portalpointsbufferoffset; static int portalpointsbuffersize; static void Mod_Q1BSP_RecursiveNodePortals(mnode_t *node) { int i, side; mnode_t *front, *back, *other_node; mplane_t clipplane, *plane; portal_t *portal, *nextportal, *nodeportal, *splitportal, *temp; int numfrontpoints, numbackpoints; double *frontpoints, *backpoints; // if a leaf, we're done if (!node->plane) return; // get some space for our clipping operations to use if (portalpointsbuffersize < portalpointsbufferoffset + 6*MAX_PORTALPOINTS) { portalpointsbuffersize = portalpointsbufferoffset * 2; portalpointsbuffer = (double *)Mem_Realloc(loadmodel->mempool, portalpointsbuffer, portalpointsbuffersize * sizeof(*portalpointsbuffer)); } frontpoints = portalpointsbuffer + portalpointsbufferoffset; portalpointsbufferoffset += 3*MAX_PORTALPOINTS; backpoints = portalpointsbuffer + portalpointsbufferoffset; portalpointsbufferoffset += 3*MAX_PORTALPOINTS; plane = node->plane; front = node->children[0]; back = node->children[1]; if (front == back) Host_Error("Mod_Q1BSP_RecursiveNodePortals: corrupt node hierarchy"); // create the new portal by generating a polygon for the node plane, // and clipping it by all of the other portals(which came from nodes above this one) nodeportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); nodeportal->plane = *plane; // TODO: calculate node bounding boxes during recursion and calculate a maximum plane size accordingly to improve precision (as most maps do not need 1 billion unit plane polygons) PolygonD_QuadForPlane(nodeportal->points, nodeportal->plane.normal[0], nodeportal->plane.normal[1], nodeportal->plane.normal[2], nodeportal->plane.dist, 1024.0*1024.0*1024.0); nodeportal->numpoints = 4; // side = 0; // shut up compiler warning -> should be no longer needed, Host_Error is declared noreturn now for (portal = (portal_t *)node->portals;portal;portal = portal->next[side]) { clipplane = portal->plane; if (portal->nodes[0] == portal->nodes[1]) Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(1)"); if (portal->nodes[0] == node) side = 0; else if (portal->nodes[1] == node) { clipplane.dist = -clipplane.dist; VectorNegate(clipplane.normal, clipplane.normal); side = 1; } else { Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); side = 0; // hush warning } for (i = 0;i < nodeportal->numpoints*3;i++) frontpoints[i] = nodeportal->points[i]; PolygonD_Divide(nodeportal->numpoints, frontpoints, clipplane.normal[0], clipplane.normal[1], clipplane.normal[2], clipplane.dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, nodeportal->points, &nodeportal->numpoints, 0, NULL, NULL, NULL); if (nodeportal->numpoints <= 0 || nodeportal->numpoints >= MAX_PORTALPOINTS) break; } if (nodeportal->numpoints < 3) { Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal was clipped away\n"); nodeportal->numpoints = 0; } else if (nodeportal->numpoints >= MAX_PORTALPOINTS) { Con_Print("Mod_Q1BSP_RecursiveNodePortals: WARNING: new portal has too many points\n"); nodeportal->numpoints = 0; } AddPortalToNodes(nodeportal, front, back); // split the portals of this node along this node's plane and assign them to the children of this node // (migrating the portals downward through the tree) for (portal = (portal_t *)node->portals;portal;portal = nextportal) { if (portal->nodes[0] == portal->nodes[1]) Host_Error("Mod_Q1BSP_RecursiveNodePortals: portal has same node on both sides(2)"); if (portal->nodes[0] == node) side = 0; else if (portal->nodes[1] == node) side = 1; else { Host_Error("Mod_Q1BSP_RecursiveNodePortals: mislinked portal"); side = 0; // hush warning } nextportal = portal->next[side]; if (!portal->numpoints) continue; other_node = portal->nodes[!side]; RemovePortalFromNodes(portal); // cut the portal into two portals, one on each side of the node plane PolygonD_Divide(portal->numpoints, portal->points, plane->normal[0], plane->normal[1], plane->normal[2], plane->dist, PORTAL_DIST_EPSILON, MAX_PORTALPOINTS, frontpoints, &numfrontpoints, MAX_PORTALPOINTS, backpoints, &numbackpoints, NULL); if (!numfrontpoints) { if (side == 0) AddPortalToNodes(portal, back, other_node); else AddPortalToNodes(portal, other_node, back); continue; } if (!numbackpoints) { if (side == 0) AddPortalToNodes(portal, front, other_node); else AddPortalToNodes(portal, other_node, front); continue; } // the portal is split splitportal = (portal_t *)Mem_ExpandableArray_AllocRecord(&portalarray); temp = splitportal->chain; *splitportal = *portal; splitportal->chain = temp; for (i = 0;i < numbackpoints*3;i++) splitportal->points[i] = backpoints[i]; splitportal->numpoints = numbackpoints; for (i = 0;i < numfrontpoints*3;i++) portal->points[i] = frontpoints[i]; portal->numpoints = numfrontpoints; if (side == 0) { AddPortalToNodes(portal, front, other_node); AddPortalToNodes(splitportal, back, other_node); } else { AddPortalToNodes(portal, other_node, front); AddPortalToNodes(splitportal, other_node, back); } } Mod_Q1BSP_RecursiveNodePortals(front); Mod_Q1BSP_RecursiveNodePortals(back); portalpointsbufferoffset -= 6*MAX_PORTALPOINTS; } static void Mod_Q1BSP_MakePortals(void) { Mem_ExpandableArray_NewArray(&portalarray, loadmodel->mempool, sizeof(portal_t), 1020*1024/sizeof(portal_t)); portalpointsbufferoffset = 0; portalpointsbuffersize = 6*MAX_PORTALPOINTS*128; portalpointsbuffer = (double *)Mem_Alloc(loadmodel->mempool, portalpointsbuffersize * sizeof(*portalpointsbuffer)); Mod_Q1BSP_RecursiveNodePortals(loadmodel->brush.data_nodes + loadmodel->brushq1.hulls[0].firstclipnode); Mem_Free(portalpointsbuffer); portalpointsbuffer = NULL; portalpointsbufferoffset = 0; portalpointsbuffersize = 0; Mod_Q1BSP_FinalizePortals(); Mem_ExpandableArray_FreeArray(&portalarray); } //Returns PVS data for a given point //(note: can return NULL) static unsigned char *Mod_Q1BSP_GetPVS(dp_model_t *model, const vec3_t p) { mnode_t *node; node = model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode; while (node->plane) node = node->children[(node->plane->type < 3 ? p[node->plane->type] : DotProduct(p,node->plane->normal)) < node->plane->dist]; if (((mleaf_t *)node)->clusterindex >= 0) return model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; else return NULL; } static void Mod_Q1BSP_FatPVS_RecursiveBSPNode(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbytes, mnode_t *node) { while (node->plane) { float d = PlaneDiff(org, node->plane); if (d > radius) node = node->children[0]; else if (d < -radius) node = node->children[1]; else { // go down both sides Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, pvsbytes, node->children[0]); node = node->children[1]; } } // if this leaf is in a cluster, accumulate the pvs bits if (((mleaf_t *)node)->clusterindex >= 0) { int i; unsigned char *pvs = model->brush.data_pvsclusters + ((mleaf_t *)node)->clusterindex * model->brush.num_pvsclusterbytes; for (i = 0;i < pvsbytes;i++) pvsbuffer[i] |= pvs[i]; } } //Calculates a PVS that is the inclusive or of all leafs within radius pixels //of the given point. static int Mod_Q1BSP_FatPVS(dp_model_t *model, const vec3_t org, vec_t radius, unsigned char *pvsbuffer, int pvsbufferlength, qboolean merge) { int bytes = model->brush.num_pvsclusterbytes; bytes = min(bytes, pvsbufferlength); if (r_novis.integer || r_trippy.integer || !model->brush.num_pvsclusters || !Mod_Q1BSP_GetPVS(model, org)) { memset(pvsbuffer, 0xFF, bytes); return bytes; } if (!merge) memset(pvsbuffer, 0, bytes); Mod_Q1BSP_FatPVS_RecursiveBSPNode(model, org, radius, pvsbuffer, bytes, model->brush.data_nodes + model->brushq1.hulls[0].firstclipnode); return bytes; } static void Mod_Q1BSP_RoundUpToHullSize(dp_model_t *cmodel, const vec3_t inmins, const vec3_t inmaxs, vec3_t outmins, vec3_t outmaxs) { vec3_t size; const hull_t *hull; VectorSubtract(inmaxs, inmins, size); if (cmodel->brush.ishlbsp) { if (size[0] < 3) hull = &cmodel->brushq1.hulls[0]; // 0x0x0 else if (size[0] <= 32) { if (size[2] < 54) // pick the nearest of 36 or 72 hull = &cmodel->brushq1.hulls[3]; // 32x32x36 else hull = &cmodel->brushq1.hulls[1]; // 32x32x72 } else hull = &cmodel->brushq1.hulls[2]; // 64x64x64 } else { if (size[0] < 3) hull = &cmodel->brushq1.hulls[0]; // 0x0x0 else if (size[0] <= 32) hull = &cmodel->brushq1.hulls[1]; // 32x32x56 else hull = &cmodel->brushq1.hulls[2]; // 64x64x88 } VectorCopy(inmins, outmins); VectorAdd(inmins, hull->clip_size, outmaxs); } static int Mod_Q1BSP_CreateShadowMesh(dp_model_t *mod) { int j; int numshadowmeshtriangles = 0; msurface_t *surface; if (cls.state == ca_dedicated) return 0; // make a single combined shadow mesh to allow optimized shadow volume creation for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) { surface->num_firstshadowmeshtriangle = numshadowmeshtriangles; numshadowmeshtriangles += surface->num_triangles; } mod->brush.shadowmesh = Mod_ShadowMesh_Begin(mod->mempool, numshadowmeshtriangles * 3, numshadowmeshtriangles, NULL, NULL, NULL, false, false, true); for (j = 0, surface = mod->data_surfaces;j < mod->num_surfaces;j++, surface++) if (surface->num_triangles > 0) Mod_ShadowMesh_AddMesh(mod->mempool, mod->brush.shadowmesh, NULL, NULL, NULL, mod->surfmesh.data_vertex3f, NULL, NULL, NULL, NULL, surface->num_triangles, (mod->surfmesh.data_element3i + 3 * surface->num_firsttriangle)); mod->brush.shadowmesh = Mod_ShadowMesh_Finish(mod->mempool, mod->brush.shadowmesh, false, r_enableshadowvolumes.integer != 0, false); if (mod->brush.shadowmesh && mod->brush.shadowmesh->neighbor3i) Mod_BuildTriangleNeighbors(mod->brush.shadowmesh->neighbor3i, mod->brush.shadowmesh->element3i, mod->brush.shadowmesh->numtriangles); return numshadowmeshtriangles; } void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask); void Mod_Q1BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, k; sizebuf_t lumpsb[HEADER_LUMPS]; mmodel_t *bm; float dist, modelyawradius, modelradius; msurface_t *surface; hullinfo_t hullinfo; int totalstylesurfaces, totalstyles, stylecounts[256], remapstyles[256]; model_brush_lightstyleinfo_t styleinfo[256]; unsigned char *datapointer; sizebuf_t sb; MSG_InitReadBuffer(&sb, (unsigned char *)buffer, (unsigned char *)bufferend - (unsigned char *)buffer); mod->type = mod_brushq1; mod->brush.ishlbsp = false; mod->brush.isbsp2rmqe = false; mod->brush.isbsp2 = false; mod->brush.isq2bsp = false; mod->brush.isq3bsp = false; mod->brush.skymasking = true; i = MSG_ReadLittleLong(&sb); switch(i) { case BSPVERSION: mod->modeldatatypestring = "Q1BSP"; break; case 30: mod->brush.ishlbsp = true; mod->modeldatatypestring = "HLBSP"; break; case ('2' + 'P' * 256 + 'S' * 65536 + 'B' * 16777216): mod->brush.isbsp2 = true; mod->brush.isbsp2rmqe = true; // like bsp2 except leaf/node bounds are 16bit (unexpanded) mod->modeldatatypestring = "Q1BSP2rmqe"; break; case ('B' + 'S' * 256 + 'P' * 65536 + '2' * 16777216): mod->brush.isbsp2 = true; mod->modeldatatypestring = "Q1BSP2"; break; default: mod->modeldatatypestring = "Unknown BSP"; Host_Error("Mod_Q1BSP_Load: %s has wrong version number %i: supported versions are 29 (Quake), 30 (Half-Life), \"BSP2\" or \"2PSB\" (rmqe)", mod->name, i); return; } // fill in hull info VectorClear (hullinfo.hullsizes[0][0]); VectorClear (hullinfo.hullsizes[0][1]); if (mod->brush.ishlbsp) { hullinfo.filehulls = 4; VectorSet (hullinfo.hullsizes[1][0], -16, -16, -36); VectorSet (hullinfo.hullsizes[1][1], 16, 16, 36); VectorSet (hullinfo.hullsizes[2][0], -32, -32, -32); VectorSet (hullinfo.hullsizes[2][1], 32, 32, 32); VectorSet (hullinfo.hullsizes[3][0], -16, -16, -18); VectorSet (hullinfo.hullsizes[3][1], 16, 16, 18); } else { hullinfo.filehulls = 4; VectorSet (hullinfo.hullsizes[1][0], -16, -16, -24); VectorSet (hullinfo.hullsizes[1][1], 16, 16, 32); VectorSet (hullinfo.hullsizes[2][0], -32, -32, -24); VectorSet (hullinfo.hullsizes[2][1], 32, 32, 64); } // read lumps for (i = 0; i < HEADER_LUMPS; i++) { int offset = MSG_ReadLittleLong(&sb); int size = MSG_ReadLittleLong(&sb); if (offset < 0 || offset + size > sb.cursize) Host_Error("Mod_Q1BSP_Load: %s has invalid lump %i (offset %i, size %i, file size %i)\n", mod->name, i, offset, size, (int)sb.cursize); MSG_InitReadBuffer(&lumpsb[i], sb.data + offset, size); } mod->soundfromcenter = true; mod->TraceBox = Mod_Q1BSP_TraceBox; mod->TraceLine = Mod_Q1BSP_TraceLine; mod->TracePoint = Mod_Q1BSP_TracePoint; mod->PointSuperContents = Mod_Q1BSP_PointSuperContents; mod->TraceLineAgainstSurfaces = Mod_Q1BSP_TraceLineAgainstSurfaces; mod->brush.TraceLineOfSight = Mod_Q1BSP_TraceLineOfSight; mod->brush.SuperContentsFromNativeContents = Mod_Q1BSP_SuperContentsFromNativeContents; mod->brush.NativeContentsFromSuperContents = Mod_Q1BSP_NativeContentsFromSuperContents; mod->brush.GetPVS = Mod_Q1BSP_GetPVS; mod->brush.FatPVS = Mod_Q1BSP_FatPVS; mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; mod->brush.LightPoint = Mod_Q1BSP_LightPoint; mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; mod->brush.AmbientSoundLevelsForPoint = Mod_Q1BSP_AmbientSoundLevelsForPoint; mod->brush.RoundUpToHullSize = Mod_Q1BSP_RoundUpToHullSize; mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; mod->Draw = R_Q1BSP_Draw; mod->DrawDepth = R_Q1BSP_DrawDepth; mod->DrawDebug = R_Q1BSP_DrawDebug; mod->DrawPrepass = R_Q1BSP_DrawPrepass; mod->GetLightInfo = R_Q1BSP_GetLightInfo; mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; mod->DrawLight = R_Q1BSP_DrawLight; // load into heap mod->brush.qw_md4sum = 0; mod->brush.qw_md4sum2 = 0; for (i = 0;i < HEADER_LUMPS;i++) { int temp; if (i == LUMP_ENTITIES) continue; temp = Com_BlockChecksum(lumpsb[i].data, lumpsb[i].cursize); mod->brush.qw_md4sum ^= LittleLong(temp); if (i == LUMP_VISIBILITY || i == LUMP_LEAFS || i == LUMP_NODES) continue; mod->brush.qw_md4sum2 ^= LittleLong(temp); } Mod_Q1BSP_LoadEntities(&lumpsb[LUMP_ENTITIES]); Mod_Q1BSP_LoadVertexes(&lumpsb[LUMP_VERTEXES]); Mod_Q1BSP_LoadEdges(&lumpsb[LUMP_EDGES]); Mod_Q1BSP_LoadSurfedges(&lumpsb[LUMP_SURFEDGES]); Mod_Q1BSP_LoadTextures(&lumpsb[LUMP_TEXTURES]); Mod_Q1BSP_LoadLighting(&lumpsb[LUMP_LIGHTING]); Mod_Q1BSP_LoadPlanes(&lumpsb[LUMP_PLANES]); Mod_Q1BSP_LoadTexinfo(&lumpsb[LUMP_TEXINFO]); Mod_Q1BSP_LoadFaces(&lumpsb[LUMP_FACES]); Mod_Q1BSP_LoadLeaffaces(&lumpsb[LUMP_MARKSURFACES]); Mod_Q1BSP_LoadVisibility(&lumpsb[LUMP_VISIBILITY]); // load submodels before leafs because they contain the number of vis leafs Mod_Q1BSP_LoadSubmodels(&lumpsb[LUMP_MODELS], &hullinfo); Mod_Q1BSP_LoadLeafs(&lumpsb[LUMP_LEAFS]); Mod_Q1BSP_LoadNodes(&lumpsb[LUMP_NODES]); Mod_Q1BSP_LoadClipnodes(&lumpsb[LUMP_CLIPNODES], &hullinfo); for (i = 0; i < HEADER_LUMPS; i++) if (lumpsb[i].readcount != lumpsb[i].cursize && i != LUMP_TEXTURES && i != LUMP_LIGHTING) Host_Error("Lump %i incorrectly loaded (readcount %i, size %i)\n", i, lumpsb[i].readcount, lumpsb[i].cursize); // check if the map supports transparent water rendering loadmodel->brush.supportwateralpha = Mod_Q1BSP_CheckWaterAlphaSupport(); // we don't need the compressed pvs data anymore if (mod->brushq1.data_compressedpvs) Mem_Free(mod->brushq1.data_compressedpvs); mod->brushq1.data_compressedpvs = NULL; mod->brushq1.num_compressedpvs = 0; Mod_Q1BSP_MakeHull0(); if (mod_bsp_portalize.integer) Mod_Q1BSP_MakePortals(); mod->numframes = 2; // regular and alternate animation mod->numskins = 1; // make a single combined shadow mesh to allow optimized shadow volume creation Mod_Q1BSP_CreateShadowMesh(loadmodel); if (loadmodel->brush.numsubmodels) loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); // LordHavoc: to clear the fog around the original quake submodel code, I // will explain: // first of all, some background info on the submodels: // model 0 is the map model (the world, named maps/e1m1.bsp for example) // model 1 and higher are submodels (doors and the like, named *1, *2, etc) // now the weird for loop itself: // the loop functions in an odd way, on each iteration it sets up the // current 'mod' model (which despite the confusing code IS the model of // the number i), at the end of the loop it duplicates the model to become // the next submodel, and loops back to set up the new submodel. // LordHavoc: now the explanation of my sane way (which works identically): // set up the world model, then on each submodel copy from the world model // and set up the submodel with the respective model info. totalstylesurfaces = 0; totalstyles = 0; for (i = 0;i < mod->brush.numsubmodels;i++) { memset(stylecounts, 0, sizeof(stylecounts)); for (k = 0;k < mod->brushq1.submodels[i].numfaces;k++) { surface = mod->data_surfaces + mod->brushq1.submodels[i].firstface + k; for (j = 0;j < MAXLIGHTMAPS;j++) stylecounts[surface->lightmapinfo->styles[j]]++; } for (k = 0;k < 255;k++) { totalstyles++; if (stylecounts[k]) totalstylesurfaces += stylecounts[k]; } } datapointer = (unsigned char *)Mem_Alloc(mod->mempool, mod->num_surfaces * sizeof(int) + totalstyles * sizeof(model_brush_lightstyleinfo_t) + totalstylesurfaces * sizeof(int *)); for (i = 0;i < mod->brush.numsubmodels;i++) { // LordHavoc: this code was originally at the end of this loop, but // has been transformed to something more readable at the start here. if (i > 0) { char name[10]; // duplicate the basic information dpsnprintf(name, sizeof(name), "*%i", i); mod = Mod_FindName(name, loadmodel->name); // copy the base model to this one *mod = *loadmodel; // rename the clone back to its proper name strlcpy(mod->name, name, sizeof(mod->name)); mod->brush.parentmodel = loadmodel; // textures and memory belong to the main model mod->texturepool = NULL; mod->mempool = NULL; mod->brush.GetPVS = NULL; mod->brush.FatPVS = NULL; mod->brush.BoxTouchingPVS = NULL; mod->brush.BoxTouchingLeafPVS = NULL; mod->brush.BoxTouchingVisibleLeafs = NULL; mod->brush.FindBoxClusters = NULL; mod->brush.LightPoint = NULL; mod->brush.AmbientSoundLevelsForPoint = NULL; } mod->brush.submodel = i; if (loadmodel->brush.submodels) loadmodel->brush.submodels[i] = mod; bm = &mod->brushq1.submodels[i]; mod->brushq1.hulls[0].firstclipnode = bm->headnode[0]; for (j=1 ; jbrushq1.hulls[j].firstclipnode = bm->headnode[j]; mod->brushq1.hulls[j].lastclipnode = mod->brushq1.numclipnodes - 1; } mod->firstmodelsurface = bm->firstface; mod->nummodelsurfaces = bm->numfaces; // set node/leaf parents for this submodel Mod_Q1BSP_LoadNodes_RecursiveSetParent(mod->brush.data_nodes + mod->brushq1.hulls[0].firstclipnode, NULL); // make the model surface list (used by shadowing/lighting) mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int); Mod_MakeSortedSurfaces(mod); // copy the submodel bounds, then enlarge the yaw and rotated bounds according to radius // (previously this code measured the radius of the vertices of surfaces in the submodel, but that broke submodels that contain only CLIP brushes, which do not produce surfaces) VectorCopy(bm->mins, mod->normalmins); VectorCopy(bm->maxs, mod->normalmaxs); dist = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); modelyawradius = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); modelyawradius = dist*dist+modelyawradius*modelyawradius; modelradius = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); modelradius = modelyawradius + modelradius * modelradius; modelyawradius = sqrt(modelyawradius); modelradius = sqrt(modelradius); mod->yawmins[0] = mod->yawmins[1] = -modelyawradius; mod->yawmins[2] = mod->normalmins[2]; mod->yawmaxs[0] = mod->yawmaxs[1] = modelyawradius; mod->yawmaxs[2] = mod->normalmaxs[2]; mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; mod->radius = modelradius; mod->radius2 = modelradius * modelradius; // this gets altered below if sky or water is used mod->DrawSky = NULL; mod->DrawAddWaterPlanes = NULL; // scan surfaces for sky and water and flag the submodel as possessing these features or not // build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker if (mod->nummodelsurfaces) { for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) if (surface->texture->basematerialflags & MATERIALFLAG_SKY) break; if (j < mod->nummodelsurfaces) mod->DrawSky = R_Q1BSP_DrawSky; for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) break; if (j < mod->nummodelsurfaces) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; // build lightstyle update chains // (used to rapidly mark lightmapupdateflags on many surfaces // when d_lightstylevalue changes) memset(stylecounts, 0, sizeof(stylecounts)); for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; for (j = 0;j < MAXLIGHTMAPS;j++) stylecounts[surface->lightmapinfo->styles[j]]++; } mod->brushq1.num_lightstyles = 0; for (k = 0;k < 255;k++) { if (stylecounts[k]) { styleinfo[mod->brushq1.num_lightstyles].style = k; styleinfo[mod->brushq1.num_lightstyles].value = 0; styleinfo[mod->brushq1.num_lightstyles].numsurfaces = 0; styleinfo[mod->brushq1.num_lightstyles].surfacelist = (int *)datapointer;datapointer += stylecounts[k] * sizeof(int); remapstyles[k] = mod->brushq1.num_lightstyles; mod->brushq1.num_lightstyles++; } } for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; for (j = 0;j < MAXLIGHTMAPS;j++) { if (surface->lightmapinfo->styles[j] != 255) { int r = remapstyles[surface->lightmapinfo->styles[j]]; styleinfo[r].surfacelist[styleinfo[r].numsurfaces++] = mod->firstmodelsurface + k; } } } mod->brushq1.data_lightstyleinfo = (model_brush_lightstyleinfo_t *)datapointer;datapointer += mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t); memcpy(mod->brushq1.data_lightstyleinfo, styleinfo, mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t)); } else { // LordHavoc: empty submodel(lacrima.bsp has such a glitch) Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadmodel->name); } //mod->brushq1.num_visleafs = bm->visleafs; // build a Bounding Interval Hierarchy for culling triangles in light rendering Mod_MakeCollisionBIH(mod, true, &mod->render_bih); if (mod_q1bsp_polygoncollisions.integer) { mod->collision_bih = mod->render_bih; // point traces and contents checks still use the bsp tree mod->TraceLine = Mod_CollisionBIH_TraceLine; mod->TraceBox = Mod_CollisionBIH_TraceBox; mod->TraceBrush = Mod_CollisionBIH_TraceBrush; mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLineAgainstSurfaces; } // generate VBOs and other shared data before cloning submodels if (i == 0) { Mod_BuildVBOs(); Mod_Q1BSP_LoadMapBrushes(); //Mod_Q1BSP_ProcessLightList(); } } Con_DPrintf("Stats for q1bsp model \"%s\": %i faces, %i nodes, %i leafs, %i visleafs, %i visleafportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); } int Mod_Q2BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) { int supercontents = 0; if (nativecontents & CONTENTSQ2_SOLID) supercontents |= SUPERCONTENTS_SOLID; if (nativecontents & CONTENTSQ2_WATER) supercontents |= SUPERCONTENTS_WATER; if (nativecontents & CONTENTSQ2_SLIME) supercontents |= SUPERCONTENTS_SLIME; if (nativecontents & CONTENTSQ2_LAVA) supercontents |= SUPERCONTENTS_LAVA; if (nativecontents & CONTENTSQ2_MONSTER) supercontents |= SUPERCONTENTS_BODY; if (nativecontents & CONTENTSQ2_DEADMONSTER) supercontents |= SUPERCONTENTS_CORPSE; if (nativecontents & CONTENTSQ2_PLAYERCLIP) supercontents |= SUPERCONTENTS_PLAYERCLIP; if (nativecontents & CONTENTSQ2_MONSTERCLIP) supercontents |= SUPERCONTENTS_MONSTERCLIP; if (!(nativecontents & CONTENTSQ2_TRANSLUCENT)) supercontents |= SUPERCONTENTS_OPAQUE; return supercontents; } int Mod_Q2BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) { int nativecontents = 0; if (supercontents & SUPERCONTENTS_SOLID) nativecontents |= CONTENTSQ2_SOLID; if (supercontents & SUPERCONTENTS_WATER) nativecontents |= CONTENTSQ2_WATER; if (supercontents & SUPERCONTENTS_SLIME) nativecontents |= CONTENTSQ2_SLIME; if (supercontents & SUPERCONTENTS_LAVA) nativecontents |= CONTENTSQ2_LAVA; if (supercontents & SUPERCONTENTS_BODY) nativecontents |= CONTENTSQ2_MONSTER; if (supercontents & SUPERCONTENTS_CORPSE) nativecontents |= CONTENTSQ2_DEADMONSTER; if (supercontents & SUPERCONTENTS_PLAYERCLIP) nativecontents |= CONTENTSQ2_PLAYERCLIP; if (supercontents & SUPERCONTENTS_MONSTERCLIP) nativecontents |= CONTENTSQ2_MONSTERCLIP; if (!(supercontents & SUPERCONTENTS_OPAQUE)) nativecontents |= CONTENTSQ2_TRANSLUCENT; return nativecontents; } static void Mod_Q2BSP_LoadVisibility(sizebuf_t *sb) { int i, count; loadmodel->brushq1.num_compressedpvs = 0; loadmodel->brushq1.data_compressedpvs = NULL; loadmodel->brush.num_pvsclusters = 0; loadmodel->brush.num_pvsclusterbytes = 0; loadmodel->brush.data_pvsclusters = NULL; if (!sb->cursize) return; count = MSG_ReadLittleLong(sb); loadmodel->brush.num_pvsclusters = count; loadmodel->brush.num_pvsclusterbytes = (count+7)>>3; loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, count*loadmodel->brush.num_pvsclusterbytes); for (i = 0;i < count;i++) { int pvsofs = MSG_ReadLittleLong(sb); /*int phsofs = */MSG_ReadLittleLong(sb); // decompress the vis data for this cluster // (note this accesses the underlying data store of sb, which is kind of evil) Mod_Q1BSP_DecompressVis(sb->data + pvsofs, sb->data + sb->cursize, loadmodel->brush.data_pvsclusters + i * loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.data_pvsclusters + (i+1) * loadmodel->brush.num_pvsclusterbytes); } // hush the loading error check later - we had to do random access on this lump, so we didn't read to the end sb->readcount = sb->cursize; } static void Mod_Q2BSP_LoadNodes(sizebuf_t *sb) { int i, j, count, p, child[2]; mnode_t *out; int structsize = 28; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadNodes: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; if (count == 0) Host_Error("Mod_Q2BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brush.data_nodes = out; loadmodel->brush.num_nodes = count; for ( i=0 ; iplane = loadmodel->brush.data_planes + p; child[0] = MSG_ReadLittleLong(sb); child[1] = MSG_ReadLittleLong(sb); out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); out->firstsurface = (unsigned short)MSG_ReadLittleShort(sb); out->numsurfaces = (unsigned short)MSG_ReadLittleShort(sb); if (out->firstsurface + out->numsurfaces > (unsigned int)loadmodel->num_surfaces) { Con_Printf("Mod_Q2BSP_LoadNodes: invalid surface index range %i+%i (file has only %i surfaces)\n", out->firstsurface, out->numsurfaces, loadmodel->num_surfaces); out->firstsurface = 0; out->numsurfaces = 0; } for (j=0 ; j<2 ; j++) { p = child[j]; if (p >= 0) { if (p < loadmodel->brush.num_nodes) out->children[j] = loadmodel->brush.data_nodes + p; else { Con_Printf("Mod_Q2BSP_LoadNodes: invalid node index %i (file has only %i nodes)\n", p, loadmodel->brush.num_nodes); // map it to the solid leaf out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; } } else { // get leaf index as a positive value starting at 0 (-1 becomes 0, -2 becomes 1, etc) p = -(p+1); if (p < loadmodel->brush.num_leafs) out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + p); else { Con_Printf("Mod_Q2BSP_LoadNodes: invalid leaf index %i (file has only %i leafs)\n", p, loadmodel->brush.num_leafs); // map it to the solid leaf out->children[j] = (mnode_t *)loadmodel->brush.data_leafs; } } } } Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); // sets nodes and leafs } static void Mod_Q2BSP_LoadTexinfo(sizebuf_t *sb) { mtexinfo_t *out; int i, l, count; int structsize = 76; int maxtextures = 1024; // hardcoded limit of quake2 engine, so we may as well use it as an upper bound char filename[MAX_QPATH]; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadTexinfo: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mtexinfo_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq1.texinfo = out; loadmodel->brushq1.numtexinfo = count; loadmodel->num_texturesperskin = 0; loadmodel->data_textures = (texture_t*)Mem_Alloc(loadmodel->mempool, maxtextures * sizeof(texture_t)); for (i = 0;i < count;i++, out++) { int j, k; for (k = 0;k < 2;k++) for (j = 0;j < 4;j++) out->vecs[k][j] = MSG_ReadLittleFloat(sb); out->q2flags = MSG_ReadLittleLong(sb); out->q2value = MSG_ReadLittleLong(sb); MSG_ReadBytes(sb, 32, (unsigned char*)out->q2texture); out->q2texture[31] = 0; // make absolutely sure it is terminated out->q2nexttexinfo = MSG_ReadLittleLong(sb); // find an existing match for the texture if possible dpsnprintf(filename, sizeof(filename), "textures/%s.wal", out->q2texture); for (j = 0;j < loadmodel->num_texturesperskin;j++) if (!strcmp(filename, loadmodel->data_textures[j].name) && out->q2flags == loadmodel->data_textures[j].q2flags && out->q2value == loadmodel->data_textures[j].q2value) break; // if we don't find the texture, store the new texture if (j == loadmodel->num_texturesperskin) { if (loadmodel->num_texturesperskin < maxtextures) { texture_t *tx = loadmodel->data_textures + j; int q2flags = out->q2flags; unsigned char *walfile = NULL; fs_offset_t walfilesize = 0; Mod_LoadTextureFromQ3Shader(tx, filename, true, true, TEXF_ALPHA | TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS); // now read the .wal file to get metadata (even if a .tga was overriding it, we still need the wal data) walfile = FS_LoadFile(filename, tempmempool, true, &walfilesize); if (walfile) { int w, h; LoadWAL_GetMetadata(walfile, (int)walfilesize, &w, &h, NULL, NULL, &tx->q2contents, NULL); tx->width = w; tx->height = h; Mem_Free(walfile); } else { tx->width = 16; tx->height = 16; } tx->q2flags = out->q2flags; tx->q2value = out->q2value; // also modify the texture to have the correct contents and such based on flags // note that we create multiple texture_t structures if q2flags differs if (q2flags & Q2SURF_LIGHT) { // doesn't mean anything to us } if (q2flags & Q2SURF_SLICK) { // would be nice to support... } if (q2flags & Q2SURF_SKY) { // sky is a rather specific thing q2flags &= ~Q2SURF_NODRAW; // quake2 had a slightly different meaning than we have in mind here... tx->basematerialflags = MATERIALFLAG_SKY | MATERIALFLAG_NOSHADOW; tx->supercontents = SUPERCONTENTS_SKY | SUPERCONTENTS_NODROP | SUPERCONTENTS_OPAQUE; tx->surfaceflags = Q3SURFACEFLAG_SKY | Q3SURFACEFLAG_NOIMPACT | Q3SURFACEFLAG_NOMARKS | Q3SURFACEFLAG_NODLIGHT | Q3SURFACEFLAG_NOLIGHTMAP; } if (q2flags & Q2SURF_WARP) { // we use a scroll instead of a warp tx->basematerialflags |= MATERIALFLAG_WATERSCROLL | MATERIALFLAG_FULLBRIGHT; // if it's also transparent, we can enable the WATERSHADER // but we do not set the WATERALPHA flag because we don't // want to honor r_wateralpha in q2bsp // (it would go against the artistic intent) if (q2flags & (Q2SURF_TRANS33 | Q2SURF_TRANS66)) tx->basematerialflags |= MATERIALFLAG_WATERSHADER; } if (q2flags & Q2SURF_TRANS33) { tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED; tx->basealpha = 1.0f / 3.0f; tx->supercontents &= ~SUPERCONTENTS_OPAQUE; if (tx->q2contents & Q2CONTENTS_SOLID) tx->q2contents = (tx->q2contents & ~Q2CONTENTS_SOLID) | Q2CONTENTS_WINDOW; } if (q2flags & Q2SURF_TRANS66) { tx->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED; tx->basealpha = 2.0f / 3.0f; tx->supercontents &= ~SUPERCONTENTS_OPAQUE; if (tx->q2contents & Q2CONTENTS_SOLID) tx->q2contents = (tx->q2contents & ~Q2CONTENTS_SOLID) | Q2CONTENTS_WINDOW; } if (q2flags & Q2SURF_FLOWING) { tx->tcmods[0].tcmod = Q3TCMOD_SCROLL; if (q2flags & Q2SURF_WARP) tx->tcmods[0].parms[0] = -0.5f; else tx->tcmods[0].parms[0] = -1.6f; tx->tcmods[0].parms[1] = 0.0f; } if (q2flags & Q2SURF_ALPHATEST) { // KMQUAKE2 and other modded engines added this flag for lit alpha tested surfaces tx->basematerialflags |= MATERIALFLAG_ALPHATEST | MATERIALFLAG_NOSHADOW; } else if (q2flags & (Q2SURF_TRANS33 | Q2SURF_TRANS66 | Q2SURF_WARP)) { if (!mod_q2bsp_littransparentsurfaces.integer) tx->basematerialflags |= MATERIALFLAG_FULLBRIGHT; } if (q2flags & Q2SURF_NODRAW) { tx->basematerialflags = MATERIALFLAG_NODRAW | MATERIALFLAG_NOSHADOW; } if (tx->q2contents & (Q2CONTENTS_TRANSLUCENT | Q2CONTENTS_MONSTERCLIP | Q2CONTENTS_PLAYERCLIP)) tx->q2contents |= Q2CONTENTS_DETAIL; if (!(tx->q2contents & (Q2CONTENTS_SOLID | Q2CONTENTS_WINDOW | Q2CONTENTS_AUX | Q2CONTENTS_LAVA | Q2CONTENTS_SLIME | Q2CONTENTS_WATER | Q2CONTENTS_MIST | Q2CONTENTS_PLAYERCLIP | Q2CONTENTS_MONSTERCLIP | Q2CONTENTS_MIST))) tx->q2contents |= Q2CONTENTS_SOLID; if (tx->q2flags & (Q2SURF_HINT | Q2SURF_SKIP)) tx->q2contents = 0; tx->supercontents = Mod_Q2BSP_SuperContentsFromNativeContents(loadmodel, tx->q2contents); // set the current values to the base values tx->currentframe = tx; tx->currentskinframe = tx->skinframes[0]; tx->currentmaterialflags = tx->basematerialflags; loadmodel->num_texturesperskin++; loadmodel->num_textures = loadmodel->num_texturesperskin; } else { Con_Printf("Mod_Q2BSP_LoadTexinfo: max textures reached (%i)\n", maxtextures); j = 0; // use first texture and give up } } // store the index we found for this texture out->textureindex = j; } // realloc the textures array now that we know how many we actually need loadmodel->data_textures = (texture_t*)Mem_Realloc(loadmodel->mempool, loadmodel->data_textures, loadmodel->num_texturesperskin * sizeof(texture_t)); // now assemble the texture chains // if we encounter the textures out of order, the later ones won't mark the earlier ones in a sequence, so the earlier for (i = 0, out = loadmodel->brushq1.texinfo;i < count;i++, out++) { int j, k; texture_t *t = loadmodel->data_textures + out->textureindex; t->currentframe = t; // fix the reallocated pointer // if this is not animated, skip it // if this is already processed, skip it (part of an existing sequence) if (out->q2nexttexinfo == 0 || t->animated) continue; // store the array of frames to use t->animated = 2; // q2bsp animation t->anim_total[0] = 0; t->anim_total[1] = 0; // gather up to 10 frames (we don't support more) for (j = i;j >= 0 && t->anim_total[0] < (int)(sizeof(t->anim_frames[0])/sizeof(t->anim_frames[0][0]));j = loadmodel->brushq1.texinfo[j].q2nexttexinfo) { // detect looping and stop there if (t->anim_total[0] && loadmodel->brushq1.texinfo[j].textureindex == out->textureindex) break; t->anim_frames[0][t->anim_total[0]++] = &loadmodel->data_textures[loadmodel->brushq1.texinfo[j].textureindex]; } // we could look for the +a sequence here if this is the +0 sequence, // but it seems that quake2 did not implement that (even though the // files exist in the baseq2 content) // write the frame sequence to all the textures involved (just like // in the q1bsp loader) // // note that this can overwrite the rest of the sequence - so if the // start of a sequence is found later than the other parts of the // sequence, it will go back and rewrite them correctly. for (k = 0;k < t->anim_total[0];k++) { texture_t *txk = t->anim_frames[0][k]; txk->animated = t->animated; txk->anim_total[0] = t->anim_total[0]; for (l = 0;l < t->anim_total[0];l++) txk->anim_frames[0][l] = t->anim_frames[0][l]; } } } static void Mod_Q2BSP_LoadLighting(sizebuf_t *sb) { // LordHavoc: this fits exactly the same format that we use in .lit files loadmodel->brushq1.lightdata = (unsigned char *)Mem_Alloc(loadmodel->mempool, sb->cursize); MSG_ReadBytes(sb, sb->cursize, loadmodel->brushq1.lightdata); } static void Mod_Q2BSP_LoadLeafs(sizebuf_t *sb) { mleaf_t *out; int i, j, count, firstmarksurface, nummarksurfaces, firstmarkbrush, nummarkbrushes; int structsize = 28; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadLeafs: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count*sizeof(*out)); loadmodel->brush.data_leafs = out; loadmodel->brush.num_leafs = count; // FIXME: this function could really benefit from some error checking for ( i=0 ; icontents = MSG_ReadLittleLong(sb); out->clusterindex = MSG_ReadLittleShort(sb); out->areaindex = MSG_ReadLittleShort(sb); out->mins[0] = MSG_ReadLittleShort(sb); out->mins[1] = MSG_ReadLittleShort(sb); out->mins[2] = MSG_ReadLittleShort(sb); out->maxs[0] = MSG_ReadLittleShort(sb); out->maxs[1] = MSG_ReadLittleShort(sb); out->maxs[2] = MSG_ReadLittleShort(sb); firstmarksurface = (unsigned short)MSG_ReadLittleShort(sb); nummarksurfaces = (unsigned short)MSG_ReadLittleShort(sb); firstmarkbrush = (unsigned short)MSG_ReadLittleShort(sb); nummarkbrushes = (unsigned short)MSG_ReadLittleShort(sb); for (j = 0;j < 4;j++) out->ambient_sound_level[j] = 0; if (out->clusterindex >= loadmodel->brush.num_pvsclusters) { Con_Print("Mod_Q2BSP_LoadLeafs: invalid clusterindex\n"); out->clusterindex = -1; } if (firstmarksurface >= 0 && firstmarksurface + nummarksurfaces <= loadmodel->brush.num_leafsurfaces) { out->firstleafsurface = loadmodel->brush.data_leafsurfaces + firstmarksurface; out->numleafsurfaces = nummarksurfaces; } else { Con_Printf("Mod_Q2BSP_LoadLeafs: invalid leafsurface range %i:%i outside range %i:%i\n", firstmarksurface, firstmarksurface+nummarksurfaces, 0, loadmodel->brush.num_leafsurfaces); out->firstleafsurface = NULL; out->numleafsurfaces = 0; } if (firstmarkbrush >= 0 && firstmarkbrush + nummarkbrushes <= loadmodel->brush.num_leafbrushes) { out->firstleafbrush = loadmodel->brush.data_leafbrushes + firstmarkbrush; out->numleafbrushes = nummarkbrushes; } else { Con_Printf("Mod_Q2BSP_LoadLeafs: invalid leafbrush range %i:%i outside range %i:%i\n", firstmarkbrush, firstmarkbrush+nummarkbrushes, 0, loadmodel->brush.num_leafbrushes); out->firstleafbrush = NULL; out->numleafbrushes = 0; } } } static void Mod_Q2BSP_LoadLeafBrushes(sizebuf_t *sb) { int i, j; int structsize = 2; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); loadmodel->brush.num_leafbrushes = sb->cursize / structsize; loadmodel->brush.data_leafbrushes = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafbrushes * sizeof(int)); for (i = 0;i < loadmodel->brush.num_leafbrushes;i++) { j = (unsigned short) MSG_ReadLittleShort(sb); if (j >= loadmodel->brush.num_brushes) Host_Error("Mod_Q1BSP_LoadLeafBrushes: bad brush number"); loadmodel->brush.data_leafbrushes[i] = j; } } static void Mod_Q2BSP_LoadBrushSides(sizebuf_t *sb) { q3mbrushside_t *out; int i, n, count; int structsize = 4; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_brushsides = out; loadmodel->brush.num_brushsides = count; for (i = 0;i < count;i++, out++) { n = (unsigned short)MSG_ReadLittleShort(sb); if (n < 0 || n >= loadmodel->brush.num_planes) Host_Error("Mod_Q2BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); out->plane = loadmodel->brush.data_planes + n; n = MSG_ReadLittleShort(sb); if (n >= 0) { if (n >= loadmodel->brushq1.numtexinfo) Host_Error("Mod_Q2BSP_LoadBrushSides: invalid texinfo index %i (%i texinfos)", n, loadmodel->brushq1.numtexinfo); out->texture = loadmodel->data_textures + loadmodel->brushq1.texinfo[n].textureindex; } else { //Con_Printf("Mod_Q2BSP_LoadBrushSides: brushside %i has texinfo index %i < 0, changing to generic texture!\n", i, n); out->texture = &mod_q1bsp_texture_solid; } } } static void Mod_Q2BSP_LoadBrushes(sizebuf_t *sb) { q3mbrush_t *out; int i, j, firstside, numsides, contents, count, maxplanes, q3surfaceflags, supercontents; colplanef_t *planes; int structsize = 12; qboolean brushmissingtextures; int numbrushesmissingtextures = 0; int numcreatedtextures = 0; if (sb->cursize % structsize) Host_Error("Mod_Q2BSP_LoadBrushes: funny lump size in %s",loadmodel->name); count = sb->cursize / structsize; out = (q3mbrush_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_brushes = out; loadmodel->brush.num_brushes = count; maxplanes = 0; planes = NULL; for (i = 0; i < count; i++, out++) { firstside = MSG_ReadLittleLong(sb); numsides = MSG_ReadLittleLong(sb); contents = MSG_ReadLittleLong(sb); if (firstside < 0 || firstside + numsides > loadmodel->brush.num_brushsides) Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)", firstside, firstside + numsides, loadmodel->brush.num_brushsides); out->firstbrushside = loadmodel->brush.data_brushsides + firstside; out->numbrushsides = numsides; // convert the contents to our values supercontents = Mod_Q2BSP_SuperContentsFromNativeContents(loadmodel, contents); // problem: q2bsp brushes have contents but not a texture // problem: q2bsp brushsides *may* have a texture or may not // problem: all brushsides and brushes must have a texture for trace_hittexture functionality to work, and the collision code is engineered around this assumption // solution: nasty hacks brushmissingtextures = false; out->texture = NULL; for (j = 0; j < out->numbrushsides; j++) { if (out->firstbrushside[j].texture == &mod_q1bsp_texture_solid) brushmissingtextures = true; else { // if we can find a matching texture on a brush side we can use it instead of creating one if (out->firstbrushside[j].texture->supercontents == supercontents) out->texture = out->firstbrushside[j].texture; } } if (brushmissingtextures || out->texture == NULL) { numbrushesmissingtextures++; // if we didn't find any appropriate texture (matching contents), we'll have to create one // we could search earlier ones for a matching one but that can be slow if (out->texture == NULL) { texture_t *validtexture; validtexture = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t)); dpsnprintf(validtexture->name, sizeof(validtexture->name), "brushcollision%i", numcreatedtextures); validtexture->surfaceflags = 0; validtexture->supercontents = supercontents; numcreatedtextures++; out->texture = validtexture; } // out->texture now contains a texture with appropriate contents, copy onto any missing sides for (j = 0; j < out->numbrushsides; j++) if (out->firstbrushside[j].texture == &mod_q1bsp_texture_solid) out->firstbrushside[j].texture = out->texture; } // make a colbrush from the brush q3surfaceflags = 0; // make a list of mplane_t structs to construct a colbrush from if (maxplanes < out->numbrushsides) { maxplanes = out->numbrushsides; if (planes) Mem_Free(planes); planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes); } for (j = 0;j < out->numbrushsides;j++) { VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal); planes[j].dist = out->firstbrushside[j].plane->dist; planes[j].q3surfaceflags = out->firstbrushside[j].texture->surfaceflags; planes[j].texture = out->firstbrushside[j].texture; q3surfaceflags |= planes[j].q3surfaceflags; } out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, q3surfaceflags, out->texture, true); // this whole loop can take a while (e.g. on redstarrepublic4) CL_KeepaliveMessage(false); } if (planes) Mem_Free(planes); if (numcreatedtextures) Con_DPrintf("Mod_Q2BSP_LoadBrushes: %i brushes own sides that lack textures or have differing contents from the brush, %i textures have been created to describe these contents.\n", numbrushesmissingtextures, numcreatedtextures); } static void Mod_Q2BSP_LoadPOP(sizebuf_t *sb) { // this is probably a "proof of purchase" lump of some sort, it seems to be 0 size in most bsp files (but not q2dm1.bsp for instance) sb->readcount = sb->cursize; } static void Mod_Q2BSP_LoadAreas(sizebuf_t *sb) { // we currently don't use areas, they represent closable doors as vis blockers sb->readcount = sb->cursize; } static void Mod_Q2BSP_LoadAreaPortals(sizebuf_t *sb) { // we currently don't use areas, they represent closable doors as vis blockers sb->readcount = sb->cursize; } static void Mod_Q2BSP_LoadSubmodels(sizebuf_t *sb) { mmodel_t *out; int i, count; int structsize = 48; if (sb->cursize % structsize) Host_Error ("Mod_Q2BSP_LoadSubmodels: funny lump size in %s", loadmodel->name); count = sb->cursize / structsize; out = (mmodel_t *)Mem_Alloc (loadmodel->mempool, count*sizeof(*out)); loadmodel->brushq1.submodels = out; loadmodel->brush.numsubmodels = count; // this is identical to the q1 submodel structure except for having 1 hull for (i = 0; i < count; i++, out++) { // spread out the mins / maxs by a pixel out->mins[0] = MSG_ReadLittleFloat(sb) - 1; out->mins[1] = MSG_ReadLittleFloat(sb) - 1; out->mins[2] = MSG_ReadLittleFloat(sb) - 1; out->maxs[0] = MSG_ReadLittleFloat(sb) + 1; out->maxs[1] = MSG_ReadLittleFloat(sb) + 1; out->maxs[2] = MSG_ReadLittleFloat(sb) + 1; out->origin[0] = MSG_ReadLittleFloat(sb); out->origin[1] = MSG_ReadLittleFloat(sb); out->origin[2] = MSG_ReadLittleFloat(sb); out->headnode[0] = MSG_ReadLittleLong(sb); out->firstface = MSG_ReadLittleLong(sb); out->numfaces = MSG_ReadLittleLong(sb); } } static void Mod_Q2BSP_FindSubmodelBrushRange_r(dp_model_t *mod, mnode_t *node, int *first, int *last) { int i; mleaf_t *leaf; while (node->plane) { Mod_Q2BSP_FindSubmodelBrushRange_r(mod, node->children[0], first, last); node = node->children[1]; } leaf = (mleaf_t*)node; for (i = 0;i < leaf->numleafbrushes;i++) { int brushnum = leaf->firstleafbrush[i]; if (*first > brushnum) *first = brushnum; if (*last < brushnum) *last = brushnum; } } static void Mod_Q2BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, k; sizebuf_t lumpsb[Q2HEADER_LUMPS]; mmodel_t *bm; float dist, modelyawradius, modelradius; msurface_t *surface; int totalstylesurfaces, totalstyles, stylecounts[256], remapstyles[256]; model_brush_lightstyleinfo_t styleinfo[256]; unsigned char *datapointer; sizebuf_t sb; MSG_InitReadBuffer(&sb, (unsigned char *)buffer, (unsigned char *)bufferend - (unsigned char *)buffer); mod->type = mod_brushq2; mod->brush.ishlbsp = false; mod->brush.isbsp2rmqe = false; mod->brush.isbsp2 = false; mod->brush.isq2bsp = true; // q1bsp loaders mostly work but we need a few tweaks mod->brush.isq3bsp = false; mod->brush.skymasking = true; mod->modeldatatypestring = "Q2BSP"; i = MSG_ReadLittleLong(&sb); if (i != Q2BSPMAGIC) Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION); i = MSG_ReadLittleLong(&sb); if (i != Q2BSPVERSION) Host_Error("Mod_Q2BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q2BSPVERSION); // read lumps for (i = 0; i < Q2HEADER_LUMPS; i++) { int offset = MSG_ReadLittleLong(&sb); int size = MSG_ReadLittleLong(&sb); if (offset < 0 || offset + size > sb.cursize) Host_Error("Mod_Q2BSP_Load: %s has invalid lump %i (offset %i, size %i, file size %i)\n", mod->name, i, offset, size, (int)sb.cursize); MSG_InitReadBuffer(&lumpsb[i], sb.data + offset, size); } mod->soundfromcenter = true; mod->TracePoint = Mod_CollisionBIH_TracePoint; mod->TraceLine = Mod_CollisionBIH_TraceLine; mod->TraceBox = Mod_CollisionBIH_TraceBox; mod->TraceBrush = Mod_CollisionBIH_TraceBrush; mod->PointSuperContents = Mod_CollisionBIH_PointSuperContents; mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight; mod->brush.SuperContentsFromNativeContents = Mod_Q2BSP_SuperContentsFromNativeContents; mod->brush.NativeContentsFromSuperContents = Mod_Q2BSP_NativeContentsFromSuperContents; mod->brush.GetPVS = Mod_Q1BSP_GetPVS; mod->brush.FatPVS = Mod_Q1BSP_FatPVS; mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; mod->brush.LightPoint = Mod_Q1BSP_LightPoint; mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; mod->brush.AmbientSoundLevelsForPoint = NULL; mod->brush.RoundUpToHullSize = NULL; mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; mod->Draw = R_Q1BSP_Draw; mod->DrawDepth = R_Q1BSP_DrawDepth; mod->DrawDebug = R_Q1BSP_DrawDebug; mod->DrawPrepass = R_Q1BSP_DrawPrepass; mod->GetLightInfo = R_Q1BSP_GetLightInfo; mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; mod->DrawLight = R_Q1BSP_DrawLight; // load into heap mod->brush.qw_md4sum = 0; mod->brush.qw_md4sum2 = 0; for (i = 0;i < Q2HEADER_LUMPS;i++) { int temp; if (i == Q2LUMP_ENTITIES) continue; temp = Com_BlockChecksum(lumpsb[i].data, lumpsb[i].cursize); mod->brush.qw_md4sum ^= LittleLong(temp); if (i == Q2LUMP_VISIBILITY || i == Q2LUMP_LEAFS || i == Q2LUMP_NODES) continue; mod->brush.qw_md4sum2 ^= LittleLong(temp); } // many of these functions are identical to Q1 loaders, so we use those where possible Mod_Q1BSP_LoadEntities(&lumpsb[Q2LUMP_ENTITIES]); Mod_Q1BSP_LoadVertexes(&lumpsb[Q2LUMP_VERTEXES]); Mod_Q1BSP_LoadEdges(&lumpsb[Q2LUMP_EDGES]); Mod_Q1BSP_LoadSurfedges(&lumpsb[Q2LUMP_SURFEDGES]); Mod_Q2BSP_LoadLighting(&lumpsb[Q2LUMP_LIGHTING]); Mod_Q1BSP_LoadPlanes(&lumpsb[Q2LUMP_PLANES]); Mod_Q2BSP_LoadTexinfo(&lumpsb[Q2LUMP_TEXINFO]); Mod_Q2BSP_LoadBrushSides(&lumpsb[Q2LUMP_BRUSHSIDES]); Mod_Q2BSP_LoadBrushes(&lumpsb[Q2LUMP_BRUSHES]); Mod_Q1BSP_LoadFaces(&lumpsb[Q2LUMP_FACES]); Mod_Q1BSP_LoadLeaffaces(&lumpsb[Q2LUMP_LEAFFACES]); Mod_Q2BSP_LoadLeafBrushes(&lumpsb[Q2LUMP_LEAFBRUSHES]); Mod_Q2BSP_LoadVisibility(&lumpsb[Q2LUMP_VISIBILITY]); Mod_Q2BSP_LoadPOP(&lumpsb[Q2LUMP_POP]); Mod_Q2BSP_LoadAreas(&lumpsb[Q2LUMP_AREAS]); Mod_Q2BSP_LoadAreaPortals(&lumpsb[Q2LUMP_AREAPORTALS]); Mod_Q2BSP_LoadLeafs(&lumpsb[Q2LUMP_LEAFS]); Mod_Q2BSP_LoadNodes(&lumpsb[Q2LUMP_NODES]); Mod_Q2BSP_LoadSubmodels(&lumpsb[Q2LUMP_MODELS]); for (i = 0; i < Q2HEADER_LUMPS; i++) if (lumpsb[i].readcount != lumpsb[i].cursize) Host_Error("Lump %i incorrectly loaded (readcount %i, size %i)\n", i, lumpsb[i].readcount, lumpsb[i].cursize); // we don't actually set MATERIALFLAG_WATERALPHA on anything, so this // doesn't enable the cvar, just indicates that transparent water is OK loadmodel->brush.supportwateralpha = true; // we don't need the compressed pvs data anymore if (mod->brushq1.data_compressedpvs) Mem_Free(mod->brushq1.data_compressedpvs); mod->brushq1.data_compressedpvs = NULL; mod->brushq1.num_compressedpvs = 0; // the MakePortals code works fine on the q2bsp data as well if (mod_bsp_portalize.integer) Mod_Q1BSP_MakePortals(); mod->numframes = 0; // q2bsp animations are kind of special, frame is unbounded... mod->numskins = 1; // make a single combined shadow mesh to allow optimized shadow volume creation Mod_Q1BSP_CreateShadowMesh(loadmodel); if (loadmodel->brush.numsubmodels) loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); totalstylesurfaces = 0; totalstyles = 0; for (i = 0;i < mod->brush.numsubmodels;i++) { memset(stylecounts, 0, sizeof(stylecounts)); for (k = 0;k < mod->brushq1.submodels[i].numfaces;k++) { surface = mod->data_surfaces + mod->brushq1.submodels[i].firstface + k; for (j = 0;j < MAXLIGHTMAPS;j++) stylecounts[surface->lightmapinfo->styles[j]]++; } for (k = 0;k < 255;k++) { totalstyles++; if (stylecounts[k]) totalstylesurfaces += stylecounts[k]; } } datapointer = (unsigned char *)Mem_Alloc(mod->mempool, mod->num_surfaces * sizeof(int) + totalstyles * sizeof(model_brush_lightstyleinfo_t) + totalstylesurfaces * sizeof(int *)); // set up the world model, then on each submodel copy from the world model // and set up the submodel with the respective model info. mod = loadmodel; for (i = 0;i < loadmodel->brush.numsubmodels;i++) { mnode_t *rootnode = NULL; int firstbrush = loadmodel->brush.num_brushes, lastbrush = 0; if (i > 0) { char name[10]; // duplicate the basic information dpsnprintf(name, sizeof(name), "*%i", i); mod = Mod_FindName(name, loadmodel->name); // copy the base model to this one *mod = *loadmodel; // rename the clone back to its proper name strlcpy(mod->name, name, sizeof(mod->name)); mod->brush.parentmodel = loadmodel; // textures and memory belong to the main model mod->texturepool = NULL; mod->mempool = NULL; mod->brush.GetPVS = NULL; mod->brush.FatPVS = NULL; mod->brush.BoxTouchingPVS = NULL; mod->brush.BoxTouchingLeafPVS = NULL; mod->brush.BoxTouchingVisibleLeafs = NULL; mod->brush.FindBoxClusters = NULL; mod->brush.LightPoint = NULL; mod->brush.AmbientSoundLevelsForPoint = NULL; } mod->brush.submodel = i; if (loadmodel->brush.submodels) loadmodel->brush.submodels[i] = mod; bm = &mod->brushq1.submodels[i]; // we store the headnode (there's only one in Q2BSP) as if it were the first hull mod->brushq1.hulls[0].firstclipnode = bm->headnode[0]; mod->firstmodelsurface = bm->firstface; mod->nummodelsurfaces = bm->numfaces; // set node/leaf parents for this submodel // note: if the root of this submodel is a leaf (headnode[0] < 0) then there is nothing to do... // (this happens in base3.bsp) if (bm->headnode[0] >= 0) rootnode = mod->brush.data_nodes + bm->headnode[0]; else rootnode = (mnode_t*)(mod->brush.data_leafs + -1 - bm->headnode[0]); Mod_Q1BSP_LoadNodes_RecursiveSetParent(rootnode, NULL); // make the model surface list (used by shadowing/lighting) mod->sortedmodelsurfaces = (int *)datapointer;datapointer += mod->nummodelsurfaces * sizeof(int); Mod_Q2BSP_FindSubmodelBrushRange_r(mod, rootnode, &firstbrush, &lastbrush); if (firstbrush <= lastbrush) { mod->firstmodelbrush = firstbrush; mod->nummodelbrushes = lastbrush + 1 - firstbrush; } else { mod->firstmodelbrush = 0; mod->nummodelbrushes = 0; } Mod_MakeSortedSurfaces(mod); VectorCopy(bm->mins, mod->normalmins); VectorCopy(bm->maxs, mod->normalmaxs); dist = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); modelyawradius = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); modelyawradius = dist*dist+modelyawradius*modelyawradius; modelradius = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); modelradius = modelyawradius + modelradius * modelradius; modelyawradius = sqrt(modelyawradius); modelradius = sqrt(modelradius); mod->yawmins[0] = mod->yawmins[1] = -modelyawradius; mod->yawmins[2] = mod->normalmins[2]; mod->yawmaxs[0] = mod->yawmaxs[1] = modelyawradius; mod->yawmaxs[2] = mod->normalmaxs[2]; mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; mod->radius = modelradius; mod->radius2 = modelradius * modelradius; // this gets altered below if sky or water is used mod->DrawSky = NULL; mod->DrawAddWaterPlanes = NULL; // scan surfaces for sky and water and flag the submodel as possessing these features or not // build lightstyle lists for quick marking of dirty lightmaps when lightstyles flicker if (mod->nummodelsurfaces) { for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) if (surface->texture->basematerialflags & MATERIALFLAG_SKY) break; if (j < mod->nummodelsurfaces) mod->DrawSky = R_Q1BSP_DrawSky; for (j = 0, surface = &mod->data_surfaces[mod->firstmodelsurface];j < mod->nummodelsurfaces;j++, surface++) if (surface->texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) break; if (j < mod->nummodelsurfaces) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; // build lightstyle update chains // (used to rapidly mark lightmapupdateflags on many surfaces // when d_lightstylevalue changes) memset(stylecounts, 0, sizeof(stylecounts)); for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; for (j = 0;j < MAXLIGHTMAPS;j++) stylecounts[surface->lightmapinfo->styles[j]]++; } mod->brushq1.num_lightstyles = 0; for (k = 0;k < 255;k++) { if (stylecounts[k]) { styleinfo[mod->brushq1.num_lightstyles].style = k; styleinfo[mod->brushq1.num_lightstyles].value = 0; styleinfo[mod->brushq1.num_lightstyles].numsurfaces = 0; styleinfo[mod->brushq1.num_lightstyles].surfacelist = (int *)datapointer;datapointer += stylecounts[k] * sizeof(int); remapstyles[k] = mod->brushq1.num_lightstyles; mod->brushq1.num_lightstyles++; } } for (k = 0;k < mod->nummodelsurfaces;k++) { surface = mod->data_surfaces + mod->firstmodelsurface + k; for (j = 0;j < MAXLIGHTMAPS;j++) { if (surface->lightmapinfo->styles[j] != 255) { int r = remapstyles[surface->lightmapinfo->styles[j]]; styleinfo[r].surfacelist[styleinfo[r].numsurfaces++] = mod->firstmodelsurface + k; } } } mod->brushq1.data_lightstyleinfo = (model_brush_lightstyleinfo_t *)datapointer;datapointer += mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t); memcpy(mod->brushq1.data_lightstyleinfo, styleinfo, mod->brushq1.num_lightstyles * sizeof(model_brush_lightstyleinfo_t)); } else { Con_Printf("warning: empty submodel *%i in %s\n", i+1, loadmodel->name); } //mod->brushq1.num_visleafs = bm->visleafs; // build a Bounding Interval Hierarchy for culling triangles in light rendering Mod_MakeCollisionBIH(mod, false, &mod->collision_bih); // build a Bounding Interval Hierarchy for culling brushes in collision detection Mod_MakeCollisionBIH(mod, true, &mod->render_bih); // generate VBOs and other shared data before cloning submodels if (i == 0) Mod_BuildVBOs(); } mod = loadmodel; Con_DPrintf("Stats for q2bsp model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); } static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents); static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents); static void Mod_Q3BSP_LoadEntities(lump_t *l) { const char *data; char key[128], value[MAX_INPUTLINE]; float v[3]; loadmodel->brushq3.num_lightgrid_cellsize[0] = 64; loadmodel->brushq3.num_lightgrid_cellsize[1] = 64; loadmodel->brushq3.num_lightgrid_cellsize[2] = 128; if (!l->filelen) return; loadmodel->brush.entities = (char *)Mem_Alloc(loadmodel->mempool, l->filelen + 1); memcpy(loadmodel->brush.entities, mod_base + l->fileofs, l->filelen); loadmodel->brush.entities[l->filelen] = 0; data = loadmodel->brush.entities; // some Q3 maps override the lightgrid_cellsize with a worldspawn key // VorteX: q3map2 FS-R generates tangentspace deluxemaps for q3bsp and sets 'deluxeMaps' key loadmodel->brushq3.deluxemapping = false; if (data && COM_ParseToken_Simple(&data, false, false, true) && com_token[0] == '{') { while (1) { if (!COM_ParseToken_Simple(&data, false, false, true)) break; // error if (com_token[0] == '}') break; // end of worldspawn if (com_token[0] == '_') strlcpy(key, com_token + 1, sizeof(key)); else strlcpy(key, com_token, sizeof(key)); while (key[strlen(key)-1] == ' ') // remove trailing spaces key[strlen(key)-1] = 0; if (!COM_ParseToken_Simple(&data, false, false, true)) break; // error strlcpy(value, com_token, sizeof(value)); if (!strcasecmp("gridsize", key)) // this one is case insensitive to 100% match q3map2 { #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif #if 0 if (sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) == 3 && v[0] != 0 && v[1] != 0 && v[2] != 0) VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); #else VectorSet(v, 64, 64, 128); if(sscanf(value, "%f %f %f", &v[0], &v[1], &v[2]) != 3) Con_Printf("Mod_Q3BSP_LoadEntities: funny gridsize \"%s\" in %s, interpreting as \"%f %f %f\" to match q3map2's parsing\n", value, loadmodel->name, v[0], v[1], v[2]); if (v[0] != 0 && v[1] != 0 && v[2] != 0) VectorCopy(v, loadmodel->brushq3.num_lightgrid_cellsize); #endif } else if (!strcmp("deluxeMaps", key)) { if (!strcmp(com_token, "1")) { loadmodel->brushq3.deluxemapping = true; loadmodel->brushq3.deluxemapping_modelspace = true; } else if (!strcmp(com_token, "2")) { loadmodel->brushq3.deluxemapping = true; loadmodel->brushq3.deluxemapping_modelspace = false; } } } } } static void Mod_Q3BSP_LoadTextures(lump_t *l) { q3dtexture_t *in; texture_t *out; int i, count; in = (q3dtexture_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadTextures: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (texture_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->data_textures = out; loadmodel->num_textures = count; loadmodel->num_texturesperskin = loadmodel->num_textures; for (i = 0;i < count;i++) { out[i].surfaceflags = LittleLong(in[i].surfaceflags); out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); Mod_LoadTextureFromQ3Shader(out + i, in[i].name, true, true, TEXF_MIPMAP | TEXF_ISWORLD | TEXF_PICMIP | TEXF_COMPRESS); // restore the surfaceflags and supercontents out[i].surfaceflags = LittleLong(in[i].surfaceflags); out[i].supercontents = Mod_Q3BSP_SuperContentsFromNativeContents(loadmodel, LittleLong(in[i].contents)); } } static void Mod_Q3BSP_LoadPlanes(lump_t *l) { q3dplane_t *in; mplane_t *out; int i, count; in = (q3dplane_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadPlanes: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (mplane_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_planes = out; loadmodel->brush.num_planes = count; for (i = 0;i < count;i++, in++, out++) { out->normal[0] = LittleFloat(in->normal[0]); out->normal[1] = LittleFloat(in->normal[1]); out->normal[2] = LittleFloat(in->normal[2]); out->dist = LittleFloat(in->dist); PlaneClassify(out); } } static void Mod_Q3BSP_LoadBrushSides(lump_t *l) { q3dbrushside_t *in; q3mbrushside_t *out; int i, n, count; in = (q3dbrushside_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_brushsides = out; loadmodel->brush.num_brushsides = count; for (i = 0;i < count;i++, in++, out++) { n = LittleLong(in->planeindex); if (n < 0 || n >= loadmodel->brush.num_planes) Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); out->plane = loadmodel->brush.data_planes + n; n = LittleLong(in->textureindex); if (n < 0 || n >= loadmodel->num_textures) Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); out->texture = loadmodel->data_textures + n; } } static void Mod_Q3BSP_LoadBrushSides_IG(lump_t *l) { q3dbrushside_ig_t *in; q3mbrushside_t *out; int i, n, count; in = (q3dbrushside_ig_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadBrushSides: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (q3mbrushside_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_brushsides = out; loadmodel->brush.num_brushsides = count; for (i = 0;i < count;i++, in++, out++) { n = LittleLong(in->planeindex); if (n < 0 || n >= loadmodel->brush.num_planes) Host_Error("Mod_Q3BSP_LoadBrushSides: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); out->plane = loadmodel->brush.data_planes + n; n = LittleLong(in->textureindex); if (n < 0 || n >= loadmodel->num_textures) Host_Error("Mod_Q3BSP_LoadBrushSides: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); out->texture = loadmodel->data_textures + n; } } static void Mod_Q3BSP_LoadBrushes(lump_t *l) { q3dbrush_t *in; q3mbrush_t *out; int i, j, n, c, count, maxplanes, q3surfaceflags; colplanef_t *planes; in = (q3dbrush_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadBrushes: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (q3mbrush_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_brushes = out; loadmodel->brush.num_brushes = count; maxplanes = 0; planes = NULL; for (i = 0;i < count;i++, in++, out++) { n = LittleLong(in->firstbrushside); c = LittleLong(in->numbrushsides); if (n < 0 || n + c > loadmodel->brush.num_brushsides) Host_Error("Mod_Q3BSP_LoadBrushes: invalid brushside range %i : %i (%i brushsides)", n, n + c, loadmodel->brush.num_brushsides); out->firstbrushside = loadmodel->brush.data_brushsides + n; out->numbrushsides = c; n = LittleLong(in->textureindex); if (n < 0 || n >= loadmodel->num_textures) Host_Error("Mod_Q3BSP_LoadBrushes: invalid textureindex %i (%i textures)", n, loadmodel->num_textures); out->texture = loadmodel->data_textures + n; // make a list of mplane_t structs to construct a colbrush from if (maxplanes < out->numbrushsides) { maxplanes = out->numbrushsides; if (planes) Mem_Free(planes); planes = (colplanef_t *)Mem_Alloc(tempmempool, sizeof(colplanef_t) * maxplanes); } q3surfaceflags = 0; for (j = 0;j < out->numbrushsides;j++) { VectorCopy(out->firstbrushside[j].plane->normal, planes[j].normal); planes[j].dist = out->firstbrushside[j].plane->dist; planes[j].q3surfaceflags = out->firstbrushside[j].texture->surfaceflags; planes[j].texture = out->firstbrushside[j].texture; q3surfaceflags |= planes[j].q3surfaceflags; } // make the colbrush from the planes out->colbrushf = Collision_NewBrushFromPlanes(loadmodel->mempool, out->numbrushsides, planes, out->texture->supercontents, q3surfaceflags, out->texture, true); // this whole loop can take a while (e.g. on redstarrepublic4) CL_KeepaliveMessage(false); } if (planes) Mem_Free(planes); } static void Mod_Q3BSP_LoadEffects(lump_t *l) { q3deffect_t *in; q3deffect_t *out; int i, n, count; in = (q3deffect_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadEffects: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (q3deffect_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq3.data_effects = out; loadmodel->brushq3.num_effects = count; for (i = 0;i < count;i++, in++, out++) { strlcpy (out->shadername, in->shadername, sizeof (out->shadername)); n = LittleLong(in->brushindex); if (n >= loadmodel->brush.num_brushes) { Con_Printf("Mod_Q3BSP_LoadEffects: invalid brushindex %i (%i brushes), setting to -1\n", n, loadmodel->brush.num_brushes); n = -1; } out->brushindex = n; out->unknown = LittleLong(in->unknown); } } static void Mod_Q3BSP_LoadVertices(lump_t *l) { q3dvertex_t *in; int i, count; in = (q3dvertex_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadVertices: funny lump size in %s",loadmodel->name); loadmodel->brushq3.num_vertices = count = l->filelen / sizeof(*in); loadmodel->brushq3.data_vertex3f = (float *)Mem_Alloc(loadmodel->mempool, count * (sizeof(float) * (3 + 3 + 2 + 2 + 4))); loadmodel->brushq3.data_normal3f = loadmodel->brushq3.data_vertex3f + count * 3; loadmodel->brushq3.data_texcoordtexture2f = loadmodel->brushq3.data_normal3f + count * 3; loadmodel->brushq3.data_texcoordlightmap2f = loadmodel->brushq3.data_texcoordtexture2f + count * 2; loadmodel->brushq3.data_color4f = loadmodel->brushq3.data_texcoordlightmap2f + count * 2; for (i = 0;i < count;i++, in++) { loadmodel->brushq3.data_vertex3f[i * 3 + 0] = LittleFloat(in->origin3f[0]); loadmodel->brushq3.data_vertex3f[i * 3 + 1] = LittleFloat(in->origin3f[1]); loadmodel->brushq3.data_vertex3f[i * 3 + 2] = LittleFloat(in->origin3f[2]); loadmodel->brushq3.data_normal3f[i * 3 + 0] = LittleFloat(in->normal3f[0]); loadmodel->brushq3.data_normal3f[i * 3 + 1] = LittleFloat(in->normal3f[1]); loadmodel->brushq3.data_normal3f[i * 3 + 2] = LittleFloat(in->normal3f[2]); loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 0] = LittleFloat(in->texcoord2f[0]); loadmodel->brushq3.data_texcoordtexture2f[i * 2 + 1] = LittleFloat(in->texcoord2f[1]); loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 0] = LittleFloat(in->lightmap2f[0]); loadmodel->brushq3.data_texcoordlightmap2f[i * 2 + 1] = LittleFloat(in->lightmap2f[1]); // svector/tvector are calculated later in face loading if(mod_q3bsp_sRGBlightmaps.integer) { // if lightmaps are sRGB, vertex colors are sRGB too, so we need to linearize them // note: when this is in use, lightmap color 128 is no longer neutral, but "sRGB half power" is // working like this may be odd, but matches q3map2 -gamma 2.2 if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f); loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f); loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f); // we fix the brightness consistently via lightmapscale } else { loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_LinearFloatFromsRGB(in->color4ub[0]); loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_LinearFloatFromsRGB(in->color4ub[1]); loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_LinearFloatFromsRGB(in->color4ub[2]); } } else { if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { loadmodel->brushq3.data_color4f[i * 4 + 0] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[0]); loadmodel->brushq3.data_color4f[i * 4 + 1] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[1]); loadmodel->brushq3.data_color4f[i * 4 + 2] = Image_sRGBFloatFromLinear_Lightmap(in->color4ub[2]); } else { loadmodel->brushq3.data_color4f[i * 4 + 0] = in->color4ub[0] * (1.0f / 255.0f); loadmodel->brushq3.data_color4f[i * 4 + 1] = in->color4ub[1] * (1.0f / 255.0f); loadmodel->brushq3.data_color4f[i * 4 + 2] = in->color4ub[2] * (1.0f / 255.0f); } } loadmodel->brushq3.data_color4f[i * 4 + 3] = in->color4ub[3] * (1.0f / 255.0f); if(in->color4ub[0] != 255 || in->color4ub[1] != 255 || in->color4ub[2] != 255) loadmodel->lit = true; } } static void Mod_Q3BSP_LoadTriangles(lump_t *l) { int *in; int *out; int i, count; in = (int *)(mod_base + l->fileofs); if (l->filelen % sizeof(int[3])) Host_Error("Mod_Q3BSP_LoadTriangles: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); if(!loadmodel->brushq3.num_vertices) { if (count) Con_Printf("Mod_Q3BSP_LoadTriangles: %s has triangles but no vertexes, broken compiler, ignoring problem\n", loadmodel->name); loadmodel->brushq3.num_triangles = 0; return; } out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq3.num_triangles = count / 3; loadmodel->brushq3.data_element3i = out; for (i = 0;i < count;i++, in++, out++) { *out = LittleLong(*in); if (*out < 0 || *out >= loadmodel->brushq3.num_vertices) { Con_Printf("Mod_Q3BSP_LoadTriangles: invalid vertexindex %i (%i vertices), setting to 0\n", *out, loadmodel->brushq3.num_vertices); *out = 0; } } } static void Mod_Q3BSP_LoadLightmaps(lump_t *l, lump_t *faceslump) { q3dlightmap_t *input_pointer; int i; int j; int k; int count; int powerx; int powery; int powerxy; int powerdxy; int endlightmap; int mergegoal; int lightmapindex; int realcount; int realindex; int mergedwidth; int mergedheight; int mergedcolumns; int mergedrows; int mergedrowsxcolumns; int size; int bytesperpixel; int rgbmap[3]; unsigned char *c; unsigned char *mergedpixels; unsigned char *mergeddeluxepixels; unsigned char *mergebuf; char mapname[MAX_QPATH]; qboolean external; unsigned char *inpixels[10000]; // max count q3map2 can output (it uses 4 digits) char vabuf[1024]; // defaults for q3bsp size = 128; bytesperpixel = 3; rgbmap[0] = 2; rgbmap[1] = 1; rgbmap[2] = 0; external = false; loadmodel->brushq3.lightmapsize = 128; if (cls.state == ca_dedicated) return; if(mod_q3bsp_nolightmaps.integer) { return; } else if(l->filelen) { // prefer internal LMs for compatibility (a BSP contains no info on whether external LMs exist) if (developer_loading.integer) Con_Printf("Using internal lightmaps\n"); input_pointer = (q3dlightmap_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*input_pointer)) Host_Error("Mod_Q3BSP_LoadLightmaps: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*input_pointer); for(i = 0; i < count; ++i) inpixels[i] = input_pointer[i].rgb; } else { // no internal lightmaps // try external lightmaps if (developer_loading.integer) Con_Printf("Using external lightmaps\n"); FS_StripExtension(loadmodel->name, mapname, sizeof(mapname)); inpixels[0] = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s/lm_%04d", mapname, 0), false, false, false, NULL); if(!inpixels[0]) return; // using EXTERNAL lightmaps instead if(image_width != (int) CeilPowerOf2(image_width) || image_width != image_height) { Mem_Free(inpixels[0]); Host_Error("Mod_Q3BSP_LoadLightmaps: invalid external lightmap size in %s",loadmodel->name); } size = image_width; bytesperpixel = 4; rgbmap[0] = 0; rgbmap[1] = 1; rgbmap[2] = 2; external = true; for(count = 1; ; ++count) { inpixels[count] = loadimagepixelsbgra(va(vabuf, sizeof(vabuf), "%s/lm_%04d", mapname, count), false, false, false, NULL); if(!inpixels[count]) break; // we got all of them if(image_width != size || image_height != size) { Mem_Free(inpixels[count]); inpixels[count] = NULL; Con_Printf("Mod_Q3BSP_LoadLightmaps: mismatched lightmap size in %s - external lightmap %s/lm_%04d does not match earlier ones\n", loadmodel->name, mapname, count); break; } } } loadmodel->brushq3.lightmapsize = size; loadmodel->brushq3.num_originallightmaps = count; // now check the surfaces to see if any of them index an odd numbered // lightmap, if so this is not a deluxemapped bsp file // // also check what lightmaps are actually used, because q3map2 sometimes // (always?) makes an unused one at the end, which // q3map2 sometimes (or always?) makes a second blank lightmap for no // reason when only one lightmap is used, which can throw off the // deluxemapping detection method, so check 2-lightmap bsp's specifically // to see if the second lightmap is blank, if so it is not deluxemapped. // VorteX: autodetect only if previous attempt to find "deluxeMaps" key // in Mod_Q3BSP_LoadEntities was failed if (!loadmodel->brushq3.deluxemapping) { loadmodel->brushq3.deluxemapping = !(count & 1); loadmodel->brushq3.deluxemapping_modelspace = true; endlightmap = 0; if (loadmodel->brushq3.deluxemapping) { int facecount = faceslump->filelen / sizeof(q3dface_t); q3dface_t *faces = (q3dface_t *)(mod_base + faceslump->fileofs); for (i = 0;i < facecount;i++) { j = LittleLong(faces[i].lightmapindex); if (j >= 0) { endlightmap = max(endlightmap, j + 1); if ((j & 1) || j + 1 >= count) { loadmodel->brushq3.deluxemapping = false; break; } } } } // q3map2 sometimes (or always?) makes a second blank lightmap for no // reason when only one lightmap is used, which can throw off the // deluxemapping detection method, so check 2-lightmap bsp's specifically // to see if the second lightmap is blank, if so it is not deluxemapped. // // further research has shown q3map2 sometimes creates a deluxemap and two // blank lightmaps, which must be handled properly as well if (endlightmap == 1 && count > 1) { c = inpixels[1]; for (i = 0;i < size*size;i++) { if (c[bytesperpixel*i + rgbmap[0]]) break; if (c[bytesperpixel*i + rgbmap[1]]) break; if (c[bytesperpixel*i + rgbmap[2]]) break; } if (i == size*size) { // all pixels in the unused lightmap were black... loadmodel->brushq3.deluxemapping = false; } } } Con_DPrintf("%s is %sdeluxemapped\n", loadmodel->name, loadmodel->brushq3.deluxemapping ? "" : "not "); // figure out what the most reasonable merge power is within limits // find the appropriate NxN dimensions to merge to, to avoid wasted space realcount = count >> (int)loadmodel->brushq3.deluxemapping; // figure out how big the merged texture has to be mergegoal = 128< size && mergegoal * mergegoal / 4 >= size * size * realcount) mergegoal /= 2; mergedwidth = mergegoal; mergedheight = mergegoal; // choose non-square size (2x1 aspect) if only half the space is used; // this really only happens when the entire set fits in one texture, if // there are multiple textures, we don't worry about shrinking the last // one to fit, because the driver prefers the same texture size on // consecutive draw calls... if (mergedwidth * mergedheight / 2 >= size*size*realcount) mergedheight /= 2; loadmodel->brushq3.num_lightmapmergedwidthpower = 0; loadmodel->brushq3.num_lightmapmergedheightpower = 0; while (mergedwidth > size<brushq3.num_lightmapmergedwidthpower) loadmodel->brushq3.num_lightmapmergedwidthpower++; while (mergedheight > size<brushq3.num_lightmapmergedheightpower) loadmodel->brushq3.num_lightmapmergedheightpower++; loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower = loadmodel->brushq3.num_lightmapmergedwidthpower + loadmodel->brushq3.num_lightmapmergedheightpower + (loadmodel->brushq3.deluxemapping ? 1 : 0); powerx = loadmodel->brushq3.num_lightmapmergedwidthpower; powery = loadmodel->brushq3.num_lightmapmergedheightpower; powerxy = powerx+powery; powerdxy = loadmodel->brushq3.deluxemapping + powerxy; mergedcolumns = 1 << powerx; mergedrows = 1 << powery; mergedrowsxcolumns = 1 << powerxy; loadmodel->brushq3.num_mergedlightmaps = (realcount + (1 << powerxy) - 1) >> powerxy; loadmodel->brushq3.data_lightmaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); if (loadmodel->brushq3.deluxemapping) loadmodel->brushq3.data_deluxemaps = (rtexture_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brushq3.num_mergedlightmaps * sizeof(rtexture_t *)); // allocate a texture pool if we need it if (loadmodel->texturepool == NULL && cls.state != ca_dedicated) loadmodel->texturepool = R_AllocTexturePool(); mergedpixels = (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4); mergeddeluxepixels = loadmodel->brushq3.deluxemapping ? (unsigned char *) Mem_Alloc(tempmempool, mergedwidth * mergedheight * 4) : NULL; for (i = 0;i < count;i++) { // figure out which merged lightmap texture this fits into realindex = i >> (int)loadmodel->brushq3.deluxemapping; lightmapindex = i >> powerdxy; // choose the destination address mergebuf = (loadmodel->brushq3.deluxemapping && (i & 1)) ? mergeddeluxepixels : mergedpixels; mergebuf += 4 * (realindex & (mergedcolumns-1))*size + 4 * ((realindex >> powerx) & (mergedrows-1))*mergedwidth*size; if ((i & 1) == 0 || !loadmodel->brushq3.deluxemapping) Con_DPrintf("copying original lightmap %i (%ix%i) to %i (at %i,%i)\n", i, size, size, lightmapindex, (realindex & (mergedcolumns-1))*size, ((realindex >> powerx) & (mergedrows-1))*size); // convert pixels from RGB or BGRA while copying them into the destination rectangle for (j = 0;j < size;j++) for (k = 0;k < size;k++) { mergebuf[(j*mergedwidth+k)*4+0] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[0]]; mergebuf[(j*mergedwidth+k)*4+1] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[1]]; mergebuf[(j*mergedwidth+k)*4+2] = inpixels[i][(j*size+k)*bytesperpixel+rgbmap[2]]; mergebuf[(j*mergedwidth+k)*4+3] = 255; } // upload texture if this was the last tile being written to the texture if (((realindex + 1) & (mergedrowsxcolumns - 1)) == 0 || (realindex + 1) == realcount) { if (loadmodel->brushq3.deluxemapping && (i & 1)) loadmodel->brushq3.data_deluxemaps[lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "deluxemap%04i", lightmapindex), mergedwidth, mergedheight, mergeddeluxepixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bspdeluxemaps.integer ? TEXF_COMPRESS : 0), -1, NULL); else { if(mod_q3bsp_sRGBlightmaps.integer) { textype_t t; if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { t = TEXTYPE_BGRA; // in stupid fallback mode, we upload lightmaps in sRGB form and just fix their brightness // we fix the brightness consistently via lightmapscale } else t = TEXTYPE_SRGB_BGRA; // normally, we upload lightmaps in sRGB form (possibly downconverted to linear) loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, t, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL); } else { if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) Image_MakesRGBColorsFromLinear_Lightmap(mergedpixels, mergedpixels, mergedwidth * mergedheight); loadmodel->brushq3.data_lightmaps [lightmapindex] = R_LoadTexture2D(loadmodel->texturepool, va(vabuf, sizeof(vabuf), "lightmap%04i", lightmapindex), mergedwidth, mergedheight, mergedpixels, TEXTYPE_BGRA, TEXF_FORCELINEAR | (gl_texturecompression_q3bsplightmaps.integer ? TEXF_COMPRESS : 0), -1, NULL); } } } } if (mergeddeluxepixels) Mem_Free(mergeddeluxepixels); Mem_Free(mergedpixels); if(external) { for(i = 0; i < count; ++i) Mem_Free(inpixels[i]); } } static void Mod_Q3BSP_BuildBBoxes(const int *element3i, int num_triangles, const float *vertex3f, float **collisionbbox6f, int *collisionstride, int stride) { int j, k, cnt, tri; float *mins, *maxs; const float *vert; *collisionstride = stride; if(stride > 0) { cnt = (num_triangles + stride - 1) / stride; *collisionbbox6f = (float *) Mem_Alloc(loadmodel->mempool, sizeof(float[6]) * cnt); for(j = 0; j < cnt; ++j) { mins = &((*collisionbbox6f)[6 * j + 0]); maxs = &((*collisionbbox6f)[6 * j + 3]); for(k = 0; k < stride; ++k) { tri = j * stride + k; if(tri >= num_triangles) break; vert = &(vertex3f[element3i[3 * tri + 0] * 3]); if(!k || vert[0] < mins[0]) mins[0] = vert[0]; if(!k || vert[1] < mins[1]) mins[1] = vert[1]; if(!k || vert[2] < mins[2]) mins[2] = vert[2]; if(!k || vert[0] > maxs[0]) maxs[0] = vert[0]; if(!k || vert[1] > maxs[1]) maxs[1] = vert[1]; if(!k || vert[2] > maxs[2]) maxs[2] = vert[2]; vert = &(vertex3f[element3i[3 * tri + 1] * 3]); if(vert[0] < mins[0]) mins[0] = vert[0]; if(vert[1] < mins[1]) mins[1] = vert[1]; if(vert[2] < mins[2]) mins[2] = vert[2]; if(vert[0] > maxs[0]) maxs[0] = vert[0]; if(vert[1] > maxs[1]) maxs[1] = vert[1]; if(vert[2] > maxs[2]) maxs[2] = vert[2]; vert = &(vertex3f[element3i[3 * tri + 2] * 3]); if(vert[0] < mins[0]) mins[0] = vert[0]; if(vert[1] < mins[1]) mins[1] = vert[1]; if(vert[2] < mins[2]) mins[2] = vert[2]; if(vert[0] > maxs[0]) maxs[0] = vert[0]; if(vert[1] > maxs[1]) maxs[1] = vert[1]; if(vert[2] > maxs[2]) maxs[2] = vert[2]; } } } else *collisionbbox6f = NULL; } typedef struct patchtess_s { patchinfo_t info; // Auxiliary data used only by patch loading code in Mod_Q3BSP_LoadFaces int surface_id; float lodgroup[6]; float *originalvertex3f; } patchtess_t; #define PATCHTESS_SAME_LODGROUP(a,b) \ ( \ (a).lodgroup[0] == (b).lodgroup[0] && \ (a).lodgroup[1] == (b).lodgroup[1] && \ (a).lodgroup[2] == (b).lodgroup[2] && \ (a).lodgroup[3] == (b).lodgroup[3] && \ (a).lodgroup[4] == (b).lodgroup[4] && \ (a).lodgroup[5] == (b).lodgroup[5] \ ) static void Mod_Q3BSP_LoadFaces(lump_t *l) { q3dface_t *in, *oldin; msurface_t *out, *oldout; int i, oldi, j, n, count, invalidelements, patchsize[2], finalwidth, finalheight, xtess, ytess, finalvertices, finaltriangles, firstvertex, firstelement, type, oldnumtriangles, oldnumtriangles2, meshvertices, meshtriangles, collisionvertices, collisiontriangles, numvertices, numtriangles, cxtess, cytess; float lightmaptcbase[2], lightmaptcscale[2]; //int *originalelement3i; //int *originalneighbor3i; float *originalvertex3f; //float *originalsvector3f; //float *originaltvector3f; float *originalnormal3f; float *originalcolor4f; float *originaltexcoordtexture2f; float *originaltexcoordlightmap2f; float *surfacecollisionvertex3f; int *surfacecollisionelement3i; float *v; patchtess_t *patchtess = NULL; int patchtesscount = 0; qboolean again; in = (q3dface_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadFaces: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (msurface_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->data_surfaces = out; loadmodel->num_surfaces = count; if(count > 0) patchtess = (patchtess_t*) Mem_Alloc(tempmempool, count * sizeof(*patchtess)); i = 0; oldi = i; oldin = in; oldout = out; meshvertices = 0; meshtriangles = 0; for (;i < count;i++, in++, out++) { // check face type first type = LittleLong(in->type); if (type != Q3FACETYPE_FLAT && type != Q3FACETYPE_PATCH && type != Q3FACETYPE_MESH && type != Q3FACETYPE_FLARE) { Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: unknown face type %i\n", i, type); continue; } n = LittleLong(in->textureindex); if (n < 0 || n >= loadmodel->num_textures) { Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i: invalid textureindex %i (%i textures)\n", i, n, loadmodel->num_textures); continue; } out->texture = loadmodel->data_textures + n; n = LittleLong(in->effectindex); if (n < -1 || n >= loadmodel->brushq3.num_effects) { if (developer_extra.integer) Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid effectindex %i (%i effects)\n", i, out->texture->name, n, loadmodel->brushq3.num_effects); n = -1; } if (n == -1) out->effect = NULL; else out->effect = loadmodel->brushq3.data_effects + n; if (cls.state != ca_dedicated) { out->lightmaptexture = NULL; out->deluxemaptexture = r_texture_blanknormalmap; n = LittleLong(in->lightmapindex); if (n < 0) n = -1; else if (n >= loadmodel->brushq3.num_originallightmaps) { if(loadmodel->brushq3.num_originallightmaps != 0) Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid lightmapindex %i (%i lightmaps)\n", i, out->texture->name, n, loadmodel->brushq3.num_originallightmaps); n = -1; } else { out->lightmaptexture = loadmodel->brushq3.data_lightmaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; if (loadmodel->brushq3.deluxemapping) out->deluxemaptexture = loadmodel->brushq3.data_deluxemaps[n >> loadmodel->brushq3.num_lightmapmergedwidthheightdeluxepower]; loadmodel->lit = true; } } firstvertex = LittleLong(in->firstvertex); numvertices = LittleLong(in->numvertices); firstelement = LittleLong(in->firstelement); numtriangles = LittleLong(in->numelements) / 3; if (numtriangles * 3 != LittleLong(in->numelements)) { Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): numelements %i is not a multiple of 3\n", i, out->texture->name, LittleLong(in->numelements)); continue; } if (firstvertex < 0 || firstvertex + numvertices > loadmodel->brushq3.num_vertices) { Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid vertex range %i : %i (%i vertices)\n", i, out->texture->name, firstvertex, firstvertex + numvertices, loadmodel->brushq3.num_vertices); continue; } if (firstelement < 0 || firstelement + numtriangles * 3 > loadmodel->brushq3.num_triangles * 3) { Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid element range %i : %i (%i elements)\n", i, out->texture->name, firstelement, firstelement + numtriangles * 3, loadmodel->brushq3.num_triangles * 3); continue; } switch(type) { case Q3FACETYPE_FLAT: case Q3FACETYPE_MESH: // no processing necessary break; case Q3FACETYPE_PATCH: patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); if (numvertices != (patchsize[0] * patchsize[1]) || patchsize[0] < 3 || patchsize[1] < 3 || !(patchsize[0] & 1) || !(patchsize[1] & 1) || patchsize[0] * patchsize[1] >= min(r_subdivisions_maxvertices.integer, r_subdivisions_collision_maxvertices.integer)) { Con_Printf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): invalid patchsize %ix%i\n", i, out->texture->name, patchsize[0], patchsize[1]); continue; } originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; // convert patch to Q3FACETYPE_MESH xtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); ytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_tolerance.value); // bound to user settings xtess = bound(r_subdivisions_mintess.integer, xtess, r_subdivisions_maxtess.integer); ytess = bound(r_subdivisions_mintess.integer, ytess, r_subdivisions_maxtess.integer); // bound to sanity settings xtess = bound(0, xtess, 1024); ytess = bound(0, ytess, 1024); // lower quality collision patches! Same procedure as before, but different cvars // convert patch to Q3FACETYPE_MESH cxtess = Q3PatchTesselationOnX(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); cytess = Q3PatchTesselationOnY(patchsize[0], patchsize[1], 3, originalvertex3f, r_subdivisions_collision_tolerance.value); // bound to user settings cxtess = bound(r_subdivisions_collision_mintess.integer, cxtess, r_subdivisions_collision_maxtess.integer); cytess = bound(r_subdivisions_collision_mintess.integer, cytess, r_subdivisions_collision_maxtess.integer); // bound to sanity settings cxtess = bound(0, cxtess, 1024); cytess = bound(0, cytess, 1024); // store it for the LOD grouping step patchtess[patchtesscount].info.xsize = patchsize[0]; patchtess[patchtesscount].info.ysize = patchsize[1]; patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].xtess = xtess; patchtess[patchtesscount].info.lods[PATCH_LOD_VISUAL].ytess = ytess; patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].xtess = cxtess; patchtess[patchtesscount].info.lods[PATCH_LOD_COLLISION].ytess = cytess; patchtess[patchtesscount].surface_id = i; patchtess[patchtesscount].lodgroup[0] = LittleFloat(in->specific.patch.mins[0]); patchtess[patchtesscount].lodgroup[1] = LittleFloat(in->specific.patch.mins[1]); patchtess[patchtesscount].lodgroup[2] = LittleFloat(in->specific.patch.mins[2]); patchtess[patchtesscount].lodgroup[3] = LittleFloat(in->specific.patch.maxs[0]); patchtess[patchtesscount].lodgroup[4] = LittleFloat(in->specific.patch.maxs[1]); patchtess[patchtesscount].lodgroup[5] = LittleFloat(in->specific.patch.maxs[2]); patchtess[patchtesscount].originalvertex3f = originalvertex3f; ++patchtesscount; break; case Q3FACETYPE_FLARE: if (developer_extra.integer) Con_DPrintf("Mod_Q3BSP_LoadFaces: face #%i (texture \"%s\"): Q3FACETYPE_FLARE not supported (yet)\n", i, out->texture->name); // don't render it continue; } out->num_vertices = numvertices; out->num_triangles = numtriangles; meshvertices += out->num_vertices; meshtriangles += out->num_triangles; } // Fix patches tesselations so that they make no seams do { again = false; for(i = 0; i < patchtesscount; ++i) { for(j = i+1; j < patchtesscount; ++j) { if (!PATCHTESS_SAME_LODGROUP(patchtess[i], patchtess[j])) continue; if (Q3PatchAdjustTesselation(3, &patchtess[i].info, patchtess[i].originalvertex3f, &patchtess[j].info, patchtess[j].originalvertex3f) ) again = true; } } } while (again); // Calculate resulting number of triangles collisionvertices = 0; collisiontriangles = 0; for(i = 0; i < patchtesscount; ++i) { finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_VISUAL].xtess); finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_VISUAL].ytess); numvertices = finalwidth * finalheight; numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; oldout[patchtess[i].surface_id].num_vertices = numvertices; oldout[patchtess[i].surface_id].num_triangles = numtriangles; meshvertices += oldout[patchtess[i].surface_id].num_vertices; meshtriangles += oldout[patchtess[i].surface_id].num_triangles; finalwidth = Q3PatchDimForTess(patchtess[i].info.xsize, patchtess[i].info.lods[PATCH_LOD_COLLISION].xtess); finalheight = Q3PatchDimForTess(patchtess[i].info.ysize,patchtess[i].info.lods[PATCH_LOD_COLLISION].ytess); numvertices = finalwidth * finalheight; numtriangles = (finalwidth - 1) * (finalheight - 1) * 2; oldout[patchtess[i].surface_id].num_collisionvertices = numvertices; oldout[patchtess[i].surface_id].num_collisiontriangles = numtriangles; collisionvertices += oldout[patchtess[i].surface_id].num_collisionvertices; collisiontriangles += oldout[patchtess[i].surface_id].num_collisiontriangles; } i = oldi; in = oldin; out = oldout; Mod_AllocSurfMesh(loadmodel->mempool, meshvertices, meshtriangles, false, true, false); if (collisiontriangles) { loadmodel->brush.data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, collisionvertices * sizeof(float[3])); loadmodel->brush.data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, collisiontriangles * sizeof(int[3])); } meshvertices = 0; meshtriangles = 0; collisionvertices = 0; collisiontriangles = 0; for (;i < count && meshvertices + out->num_vertices <= loadmodel->surfmesh.num_vertices;i++, in++, out++) { if (out->num_vertices < 3 || out->num_triangles < 1) continue; type = LittleLong(in->type); firstvertex = LittleLong(in->firstvertex); firstelement = LittleLong(in->firstelement); out->num_firstvertex = meshvertices; out->num_firsttriangle = meshtriangles; out->num_firstcollisiontriangle = collisiontriangles; switch(type) { case Q3FACETYPE_FLAT: case Q3FACETYPE_MESH: // no processing necessary, except for lightmap merging for (j = 0;j < out->num_vertices;j++) { (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 0]; (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 1]; (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_vertex3f[(firstvertex + j) * 3 + 2]; (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 0] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 0]; (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 1] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 1]; (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex)[j * 3 + 2] = loadmodel->brushq3.data_normal3f[(firstvertex + j) * 3 + 2]; (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 0]; (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordtexture2f[(firstvertex + j) * 2 + 1]; (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 0] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 0]; (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex)[j * 2 + 1] = loadmodel->brushq3.data_texcoordlightmap2f[(firstvertex + j) * 2 + 1]; (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 0] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 0]; (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 1] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 1]; (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 2] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 2]; (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex)[j * 4 + 3] = loadmodel->brushq3.data_color4f[(firstvertex + j) * 4 + 3]; } for (j = 0;j < out->num_triangles*3;j++) (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = loadmodel->brushq3.data_element3i[firstelement + j] + out->num_firstvertex; break; case Q3FACETYPE_PATCH: patchsize[0] = LittleLong(in->specific.patch.patchsize[0]); patchsize[1] = LittleLong(in->specific.patch.patchsize[1]); originalvertex3f = loadmodel->brushq3.data_vertex3f + firstvertex * 3; originalnormal3f = loadmodel->brushq3.data_normal3f + firstvertex * 3; originaltexcoordtexture2f = loadmodel->brushq3.data_texcoordtexture2f + firstvertex * 2; originaltexcoordlightmap2f = loadmodel->brushq3.data_texcoordlightmap2f + firstvertex * 2; originalcolor4f = loadmodel->brushq3.data_color4f + firstvertex * 4; xtess = ytess = cxtess = cytess = -1; for(j = 0; j < patchtesscount; ++j) if(patchtess[j].surface_id == i) { xtess = patchtess[j].info.lods[PATCH_LOD_VISUAL].xtess; ytess = patchtess[j].info.lods[PATCH_LOD_VISUAL].ytess; cxtess = patchtess[j].info.lods[PATCH_LOD_COLLISION].xtess; cytess = patchtess[j].info.lods[PATCH_LOD_COLLISION].ytess; break; } if(xtess == -1) { Con_Printf("ERROR: patch %d isn't preprocessed?!?\n", i); xtess = ytess = cxtess = cytess = 0; } finalwidth = Q3PatchDimForTess(patchsize[0],xtess); //((patchsize[0] - 1) * xtess) + 1; finalheight = Q3PatchDimForTess(patchsize[1],ytess); //((patchsize[1] - 1) * ytess) + 1; finalvertices = finalwidth * finalheight; oldnumtriangles = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; type = Q3FACETYPE_MESH; // generate geometry // (note: normals are skipped because they get recalculated) Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, xtess, ytess); Q3PatchTesselateFloat(3, sizeof(float[3]), (loadmodel->surfmesh.data_normal3f + 3 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[3]), originalnormal3f, xtess, ytess); Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordtexture2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordtexture2f, xtess, ytess); Q3PatchTesselateFloat(2, sizeof(float[2]), (loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[2]), originaltexcoordlightmap2f, xtess, ytess); Q3PatchTesselateFloat(4, sizeof(float[4]), (loadmodel->surfmesh.data_lightmapcolor4f + 4 * out->num_firstvertex), patchsize[0], patchsize[1], sizeof(float[4]), originalcolor4f, xtess, ytess); Q3PatchTriangleElements((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), finalwidth, finalheight, out->num_firstvertex); out->num_triangles = Mod_RemoveDegenerateTriangles(out->num_triangles, (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle), loadmodel->surfmesh.data_vertex3f); if (developer_extra.integer) { if (out->num_triangles < finaltriangles) Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles, %i degenerate triangles removed (leaving %i)\n", patchsize[0], patchsize[1], out->num_vertices, finaltriangles, finaltriangles - out->num_triangles, out->num_triangles); else Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve subdivided to %i vertices / %i triangles\n", patchsize[0], patchsize[1], out->num_vertices, out->num_triangles); } // q3map does not put in collision brushes for curves... ugh // build the lower quality collision geometry finalwidth = Q3PatchDimForTess(patchsize[0],cxtess); //((patchsize[0] - 1) * cxtess) + 1; finalheight = Q3PatchDimForTess(patchsize[1],cytess); //((patchsize[1] - 1) * cytess) + 1; finalvertices = finalwidth * finalheight; oldnumtriangles2 = finaltriangles = (finalwidth - 1) * (finalheight - 1) * 2; // legacy collision geometry implementation out->deprecatedq3data_collisionvertex3f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[3]) * finalvertices); out->deprecatedq3data_collisionelement3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * finaltriangles); out->num_collisionvertices = finalvertices; out->num_collisiontriangles = finaltriangles; Q3PatchTesselateFloat(3, sizeof(float[3]), out->deprecatedq3data_collisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); Q3PatchTriangleElements(out->deprecatedq3data_collisionelement3i, finalwidth, finalheight, 0); //Mod_SnapVertices(3, out->num_vertices, (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), 0.25); Mod_SnapVertices(3, finalvertices, out->deprecatedq3data_collisionvertex3f, 1); out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionelement3i, out->deprecatedq3data_collisionvertex3f); // now optimize the collision mesh by finding triangle bboxes... Mod_Q3BSP_BuildBBoxes(out->deprecatedq3data_collisionelement3i, out->num_collisiontriangles, out->deprecatedq3data_collisionvertex3f, &out->deprecatedq3data_collisionbbox6f, &out->deprecatedq3num_collisionbboxstride, mod_q3bsp_curves_collisions_stride.integer); Mod_Q3BSP_BuildBBoxes(loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle, out->num_triangles, loadmodel->surfmesh.data_vertex3f, &out->deprecatedq3data_bbox6f, &out->deprecatedq3num_bboxstride, mod_q3bsp_curves_stride.integer); // store collision geometry for BIH collision tree surfacecollisionvertex3f = loadmodel->brush.data_collisionvertex3f + collisionvertices * 3; surfacecollisionelement3i = loadmodel->brush.data_collisionelement3i + collisiontriangles * 3; Q3PatchTesselateFloat(3, sizeof(float[3]), surfacecollisionvertex3f, patchsize[0], patchsize[1], sizeof(float[3]), originalvertex3f, cxtess, cytess); Q3PatchTriangleElements(surfacecollisionelement3i, finalwidth, finalheight, collisionvertices); Mod_SnapVertices(3, finalvertices, surfacecollisionvertex3f, 1); #if 1 // remove this once the legacy code is removed { int nc = out->num_collisiontriangles; #endif out->num_collisiontriangles = Mod_RemoveDegenerateTriangles(finaltriangles, surfacecollisionelement3i, surfacecollisionelement3i, loadmodel->brush.data_collisionvertex3f); #if 1 if(nc != out->num_collisiontriangles) { Con_Printf("number of collision triangles differs between BIH and BSP. FAIL.\n"); } } #endif if (developer_extra.integer) Con_DPrintf("Mod_Q3BSP_LoadFaces: %ix%i curve became %i:%i vertices / %i:%i triangles (%i:%i degenerate)\n", patchsize[0], patchsize[1], out->num_vertices, out->num_collisionvertices, oldnumtriangles, oldnumtriangles2, oldnumtriangles - out->num_triangles, oldnumtriangles2 - out->num_collisiontriangles); collisionvertices += finalvertices; collisiontriangles += out->num_collisiontriangles; break; default: break; } meshvertices += out->num_vertices; meshtriangles += out->num_triangles; for (j = 0, invalidelements = 0;j < out->num_triangles * 3;j++) if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) invalidelements++; if (invalidelements) { Con_Printf("Mod_Q3BSP_LoadFaces: Warning: face #%i has %i invalid elements, type = %i, texture->name = \"%s\", texture->surfaceflags = %i, firstvertex = %i, numvertices = %i, firstelement = %i, numelements = %i, elements list:\n", i, invalidelements, type, out->texture->name, out->texture->surfaceflags, firstvertex, out->num_vertices, firstelement, out->num_triangles * 3); for (j = 0;j < out->num_triangles * 3;j++) { Con_Printf(" %i", (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] - out->num_firstvertex); if ((loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] < out->num_firstvertex || (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] >= out->num_firstvertex + out->num_vertices) (loadmodel->surfmesh.data_element3i + 3 * out->num_firsttriangle)[j] = out->num_firstvertex; } Con_Print("\n"); } // calculate a bounding box VectorClear(out->mins); VectorClear(out->maxs); if (out->num_vertices) { if (cls.state != ca_dedicated && out->lightmaptexture) { // figure out which part of the merged lightmap this fits into int lightmapindex = LittleLong(in->lightmapindex) >> (loadmodel->brushq3.deluxemapping ? 1 : 0); int mergewidth = R_TextureWidth(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; int mergeheight = R_TextureHeight(out->lightmaptexture) / loadmodel->brushq3.lightmapsize; lightmapindex &= mergewidth * mergeheight - 1; lightmaptcscale[0] = 1.0f / mergewidth; lightmaptcscale[1] = 1.0f / mergeheight; lightmaptcbase[0] = (lightmapindex % mergewidth) * lightmaptcscale[0]; lightmaptcbase[1] = (lightmapindex / mergewidth) * lightmaptcscale[1]; // modify the lightmap texcoords to match this region of the merged lightmap for (j = 0, v = loadmodel->surfmesh.data_texcoordlightmap2f + 2 * out->num_firstvertex;j < out->num_vertices;j++, v += 2) { v[0] = v[0] * lightmaptcscale[0] + lightmaptcbase[0]; v[1] = v[1] * lightmaptcscale[1] + lightmaptcbase[1]; } } VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->mins); VectorCopy((loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex), out->maxs); for (j = 1, v = (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex) + 3;j < out->num_vertices;j++, v += 3) { out->mins[0] = min(out->mins[0], v[0]); out->maxs[0] = max(out->maxs[0], v[0]); out->mins[1] = min(out->mins[1], v[1]); out->maxs[1] = max(out->maxs[1], v[1]); out->mins[2] = min(out->mins[2], v[2]); out->maxs[2] = max(out->maxs[2], v[2]); } out->mins[0] -= 1.0f; out->mins[1] -= 1.0f; out->mins[2] -= 1.0f; out->maxs[0] += 1.0f; out->maxs[1] += 1.0f; out->maxs[2] += 1.0f; } // set lightmap styles for consistency with q1bsp //out->lightmapinfo->styles[0] = 0; //out->lightmapinfo->styles[1] = 255; //out->lightmapinfo->styles[2] = 255; //out->lightmapinfo->styles[3] = 255; } i = oldi; out = oldout; for (;i < count;i++, out++) { if(out->num_vertices && out->num_triangles) continue; if(out->num_vertices == 0) { Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no vertices, ignoring\n", i, out->texture ? out->texture->name : "(none)"); if(out->num_triangles == 0) Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)"); } else if(out->num_triangles == 0) Con_Printf("Mod_Q3BSP_LoadFaces: surface %d (texture %s, near %f %f %f) has no triangles, ignoring\n", i, out->texture ? out->texture->name : "(none)", (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[0 * 3 + 0], (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[1 * 3 + 0], (loadmodel->surfmesh.data_vertex3f + 3 * out->num_firstvertex)[2 * 3 + 0]); } // for per pixel lighting Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); // generate ushort elements array if possible if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; // free the no longer needed vertex data loadmodel->brushq3.num_vertices = 0; if (loadmodel->brushq3.data_vertex3f) Mem_Free(loadmodel->brushq3.data_vertex3f); loadmodel->brushq3.data_vertex3f = NULL; loadmodel->brushq3.data_normal3f = NULL; loadmodel->brushq3.data_texcoordtexture2f = NULL; loadmodel->brushq3.data_texcoordlightmap2f = NULL; loadmodel->brushq3.data_color4f = NULL; // free the no longer needed triangle data loadmodel->brushq3.num_triangles = 0; if (loadmodel->brushq3.data_element3i) Mem_Free(loadmodel->brushq3.data_element3i); loadmodel->brushq3.data_element3i = NULL; if(patchtess) Mem_Free(patchtess); } static void Mod_Q3BSP_LoadModels(lump_t *l) { q3dmodel_t *in; q3dmodel_t *out; int i, j, n, c, count; in = (q3dmodel_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadModels: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (q3dmodel_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq3.data_models = out; loadmodel->brushq3.num_models = count; for (i = 0;i < count;i++, in++, out++) { for (j = 0;j < 3;j++) { out->mins[j] = LittleFloat(in->mins[j]); out->maxs[j] = LittleFloat(in->maxs[j]); } n = LittleLong(in->firstface); c = LittleLong(in->numfaces); if (n < 0 || n + c > loadmodel->num_surfaces) Host_Error("Mod_Q3BSP_LoadModels: invalid face range %i : %i (%i faces)", n, n + c, loadmodel->num_surfaces); out->firstface = n; out->numfaces = c; n = LittleLong(in->firstbrush); c = LittleLong(in->numbrushes); if (n < 0 || n + c > loadmodel->brush.num_brushes) Host_Error("Mod_Q3BSP_LoadModels: invalid brush range %i : %i (%i brushes)", n, n + c, loadmodel->brush.num_brushes); out->firstbrush = n; out->numbrushes = c; } } static void Mod_Q3BSP_LoadLeafBrushes(lump_t *l) { int *in; int *out; int i, n, count; in = (int *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadLeafBrushes: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_leafbrushes = out; loadmodel->brush.num_leafbrushes = count; for (i = 0;i < count;i++, in++, out++) { n = LittleLong(*in); if (n < 0 || n >= loadmodel->brush.num_brushes) Host_Error("Mod_Q3BSP_LoadLeafBrushes: invalid brush index %i (%i brushes)", n, loadmodel->brush.num_brushes); *out = n; } } static void Mod_Q3BSP_LoadLeafFaces(lump_t *l) { int *in; int *out; int i, n, count; in = (int *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadLeafFaces: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (int *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_leafsurfaces = out; loadmodel->brush.num_leafsurfaces = count; for (i = 0;i < count;i++, in++, out++) { n = LittleLong(*in); if (n < 0 || n >= loadmodel->num_surfaces) Host_Error("Mod_Q3BSP_LoadLeafFaces: invalid face index %i (%i faces)", n, loadmodel->num_surfaces); *out = n; } } static void Mod_Q3BSP_LoadLeafs(lump_t *l) { q3dleaf_t *in; mleaf_t *out; int i, j, n, c, count; in = (q3dleaf_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadLeafs: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); out = (mleaf_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_leafs = out; loadmodel->brush.num_leafs = count; for (i = 0;i < count;i++, in++, out++) { out->parent = NULL; out->plane = NULL; out->clusterindex = LittleLong(in->clusterindex); out->areaindex = LittleLong(in->areaindex); for (j = 0;j < 3;j++) { // yes the mins/maxs are ints out->mins[j] = LittleLong(in->mins[j]) - 1; out->maxs[j] = LittleLong(in->maxs[j]) + 1; } n = LittleLong(in->firstleafface); c = LittleLong(in->numleaffaces); if (n < 0 || n + c > loadmodel->brush.num_leafsurfaces) Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafsurface range %i : %i (%i leafsurfaces)", n, n + c, loadmodel->brush.num_leafsurfaces); out->firstleafsurface = loadmodel->brush.data_leafsurfaces + n; out->numleafsurfaces = c; n = LittleLong(in->firstleafbrush); c = LittleLong(in->numleafbrushes); if (n < 0 || n + c > loadmodel->brush.num_leafbrushes) Host_Error("Mod_Q3BSP_LoadLeafs: invalid leafbrush range %i : %i (%i leafbrushes)", n, n + c, loadmodel->brush.num_leafbrushes); out->firstleafbrush = loadmodel->brush.data_leafbrushes + n; out->numleafbrushes = c; } } static void Mod_Q3BSP_LoadNodes(lump_t *l) { q3dnode_t *in; mnode_t *out; int i, j, n, count; in = (q3dnode_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadNodes: funny lump size in %s",loadmodel->name); count = l->filelen / sizeof(*in); if (count == 0) Host_Error("Mod_Q3BSP_LoadNodes: missing BSP tree in %s",loadmodel->name); out = (mnode_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brush.data_nodes = out; loadmodel->brush.num_nodes = count; for (i = 0;i < count;i++, in++, out++) { out->parent = NULL; n = LittleLong(in->planeindex); if (n < 0 || n >= loadmodel->brush.num_planes) Host_Error("Mod_Q3BSP_LoadNodes: invalid planeindex %i (%i planes)", n, loadmodel->brush.num_planes); out->plane = loadmodel->brush.data_planes + n; for (j = 0;j < 2;j++) { n = LittleLong(in->childrenindex[j]); if (n >= 0) { if (n >= loadmodel->brush.num_nodes) Host_Error("Mod_Q3BSP_LoadNodes: invalid child node index %i (%i nodes)", n, loadmodel->brush.num_nodes); out->children[j] = loadmodel->brush.data_nodes + n; } else { n = -1 - n; if (n >= loadmodel->brush.num_leafs) Host_Error("Mod_Q3BSP_LoadNodes: invalid child leaf index %i (%i leafs)", n, loadmodel->brush.num_leafs); out->children[j] = (mnode_t *)(loadmodel->brush.data_leafs + n); } } for (j = 0;j < 3;j++) { // yes the mins/maxs are ints out->mins[j] = LittleLong(in->mins[j]) - 1; out->maxs[j] = LittleLong(in->maxs[j]) + 1; } } // set the parent pointers Mod_Q1BSP_LoadNodes_RecursiveSetParent(loadmodel->brush.data_nodes, NULL); } static void Mod_Q3BSP_LoadLightGrid(lump_t *l) { q3dlightgrid_t *in; q3dlightgrid_t *out; int count; int i; in = (q3dlightgrid_t *)(mod_base + l->fileofs); if (l->filelen % sizeof(*in)) Host_Error("Mod_Q3BSP_LoadLightGrid: funny lump size in %s",loadmodel->name); loadmodel->brushq3.num_lightgrid_scale[0] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[0]; loadmodel->brushq3.num_lightgrid_scale[1] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[1]; loadmodel->brushq3.num_lightgrid_scale[2] = 1.0f / loadmodel->brushq3.num_lightgrid_cellsize[2]; loadmodel->brushq3.num_lightgrid_imins[0] = (int)ceil(loadmodel->brushq3.data_models->mins[0] * loadmodel->brushq3.num_lightgrid_scale[0]); loadmodel->brushq3.num_lightgrid_imins[1] = (int)ceil(loadmodel->brushq3.data_models->mins[1] * loadmodel->brushq3.num_lightgrid_scale[1]); loadmodel->brushq3.num_lightgrid_imins[2] = (int)ceil(loadmodel->brushq3.data_models->mins[2] * loadmodel->brushq3.num_lightgrid_scale[2]); loadmodel->brushq3.num_lightgrid_imaxs[0] = (int)floor(loadmodel->brushq3.data_models->maxs[0] * loadmodel->brushq3.num_lightgrid_scale[0]); loadmodel->brushq3.num_lightgrid_imaxs[1] = (int)floor(loadmodel->brushq3.data_models->maxs[1] * loadmodel->brushq3.num_lightgrid_scale[1]); loadmodel->brushq3.num_lightgrid_imaxs[2] = (int)floor(loadmodel->brushq3.data_models->maxs[2] * loadmodel->brushq3.num_lightgrid_scale[2]); loadmodel->brushq3.num_lightgrid_isize[0] = loadmodel->brushq3.num_lightgrid_imaxs[0] - loadmodel->brushq3.num_lightgrid_imins[0] + 1; loadmodel->brushq3.num_lightgrid_isize[1] = loadmodel->brushq3.num_lightgrid_imaxs[1] - loadmodel->brushq3.num_lightgrid_imins[1] + 1; loadmodel->brushq3.num_lightgrid_isize[2] = loadmodel->brushq3.num_lightgrid_imaxs[2] - loadmodel->brushq3.num_lightgrid_imins[2] + 1; count = loadmodel->brushq3.num_lightgrid_isize[0] * loadmodel->brushq3.num_lightgrid_isize[1] * loadmodel->brushq3.num_lightgrid_isize[2]; Matrix4x4_CreateScale3(&loadmodel->brushq3.num_lightgrid_indexfromworld, loadmodel->brushq3.num_lightgrid_scale[0], loadmodel->brushq3.num_lightgrid_scale[1], loadmodel->brushq3.num_lightgrid_scale[2]); Matrix4x4_ConcatTranslate(&loadmodel->brushq3.num_lightgrid_indexfromworld, -loadmodel->brushq3.num_lightgrid_imins[0] * loadmodel->brushq3.num_lightgrid_cellsize[0], -loadmodel->brushq3.num_lightgrid_imins[1] * loadmodel->brushq3.num_lightgrid_cellsize[1], -loadmodel->brushq3.num_lightgrid_imins[2] * loadmodel->brushq3.num_lightgrid_cellsize[2]); // if lump is empty there is nothing to load, we can deal with that in the LightPoint code if (l->filelen) { if (l->filelen < count * (int)sizeof(*in)) { Con_Printf("Mod_Q3BSP_LoadLightGrid: invalid lightgrid lump size %i bytes, should be %i bytes (%ix%ix%i)", l->filelen, (int)(count * sizeof(*in)), loadmodel->brushq3.num_lightgrid_isize[0], loadmodel->brushq3.num_lightgrid_isize[1], loadmodel->brushq3.num_lightgrid_isize[2]); return; // ignore the grid if we cannot understand it } if (l->filelen != count * (int)sizeof(*in)) Con_Printf("Mod_Q3BSP_LoadLightGrid: Warning: calculated lightgrid size %i bytes does not match lump size %i\n", (int)(count * sizeof(*in)), l->filelen); out = (q3dlightgrid_t *)Mem_Alloc(loadmodel->mempool, count * sizeof(*out)); loadmodel->brushq3.data_lightgrid = out; loadmodel->brushq3.num_lightgrid = count; // no swapping or validation necessary memcpy(out, in, count * (int)sizeof(*out)); if(mod_q3bsp_sRGBlightmaps.integer) { if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { // we fix the brightness consistently via lightmapscale } else { for(i = 0; i < count; ++i) { out[i].ambientrgb[0] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[0]) * 255.0f + 0.5f); out[i].ambientrgb[1] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[1]) * 255.0f + 0.5f); out[i].ambientrgb[2] = floor(Image_LinearFloatFromsRGB(out[i].ambientrgb[2]) * 255.0f + 0.5f); out[i].diffusergb[0] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[0]) * 255.0f + 0.5f); out[i].diffusergb[1] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[1]) * 255.0f + 0.5f); out[i].diffusergb[2] = floor(Image_LinearFloatFromsRGB(out[i].diffusergb[2]) * 255.0f + 0.5f); } } } else { if(vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { for(i = 0; i < count; ++i) { out[i].ambientrgb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[0]) * 255.0f + 0.5f); out[i].ambientrgb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[1]) * 255.0f + 0.5f); out[i].ambientrgb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].ambientrgb[2]) * 255.0f + 0.5f); out[i].diffusergb[0] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[0]) * 255.0f + 0.5f); out[i].diffusergb[1] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[1]) * 255.0f + 0.5f); out[i].diffusergb[2] = floor(Image_sRGBFloatFromLinear_Lightmap(out[i].diffusergb[2]) * 255.0f + 0.5f); } } else { // all is good } } } } static void Mod_Q3BSP_LoadPVS(lump_t *l) { q3dpvs_t *in; int totalchains; if (l->filelen == 0) { int i; // unvised maps often have cluster indices even without pvs, so check // leafs to find real number of clusters loadmodel->brush.num_pvsclusters = 1; for (i = 0;i < loadmodel->brush.num_leafs;i++) loadmodel->brush.num_pvsclusters = max(loadmodel->brush.num_pvsclusters, loadmodel->brush.data_leafs[i].clusterindex + 1); // create clusters loadmodel->brush.num_pvsclusterbytes = (loadmodel->brush.num_pvsclusters + 7) / 8; totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); memset(loadmodel->brush.data_pvsclusters, 0xFF, totalchains); return; } in = (q3dpvs_t *)(mod_base + l->fileofs); if (l->filelen < 9) Host_Error("Mod_Q3BSP_LoadPVS: funny lump size in %s",loadmodel->name); loadmodel->brush.num_pvsclusters = LittleLong(in->numclusters); loadmodel->brush.num_pvsclusterbytes = LittleLong(in->chainlength); if (loadmodel->brush.num_pvsclusterbytes < ((loadmodel->brush.num_pvsclusters + 7) / 8)) Host_Error("Mod_Q3BSP_LoadPVS: (chainlength = %i) < ((numclusters = %i) + 7) / 8", loadmodel->brush.num_pvsclusterbytes, loadmodel->brush.num_pvsclusters); totalchains = loadmodel->brush.num_pvsclusterbytes * loadmodel->brush.num_pvsclusters; if (l->filelen < totalchains + (int)sizeof(*in)) Host_Error("Mod_Q3BSP_LoadPVS: lump too small ((numclusters = %i) * (chainlength = %i) + sizeof(q3dpvs_t) == %i bytes, lump is %i bytes)", loadmodel->brush.num_pvsclusters, loadmodel->brush.num_pvsclusterbytes, (int)(totalchains + sizeof(*in)), l->filelen); loadmodel->brush.data_pvsclusters = (unsigned char *)Mem_Alloc(loadmodel->mempool, totalchains); memcpy(loadmodel->brush.data_pvsclusters, (unsigned char *)(in + 1), totalchains); } static void Mod_Q3BSP_LightPoint(dp_model_t *model, const vec3_t p, vec3_t ambientcolor, vec3_t diffusecolor, vec3_t diffusenormal) { int i, j, k, index[3]; float transformed[3], blend1, blend2, blend, stylescale = 1; q3dlightgrid_t *a, *s; // scale lighting by lightstyle[0] so that darkmode in dpmod works properly switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: // LordHavoc: FIXME: is this true? stylescale = 1; // added while render break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: stylescale = r_refdef.scene.rtlightstylevalue[0]; break; } if (!model->brushq3.num_lightgrid) { ambientcolor[0] = stylescale; ambientcolor[1] = stylescale; ambientcolor[2] = stylescale; return; } Matrix4x4_Transform(&model->brushq3.num_lightgrid_indexfromworld, p, transformed); //Matrix4x4_Print(&model->brushq3.num_lightgrid_indexfromworld); //Con_Printf("%f %f %f transformed %f %f %f clamped ", p[0], p[1], p[2], transformed[0], transformed[1], transformed[2]); transformed[0] = bound(0, transformed[0], model->brushq3.num_lightgrid_isize[0] - 1); transformed[1] = bound(0, transformed[1], model->brushq3.num_lightgrid_isize[1] - 1); transformed[2] = bound(0, transformed[2], model->brushq3.num_lightgrid_isize[2] - 1); index[0] = (int)floor(transformed[0]); index[1] = (int)floor(transformed[1]); index[2] = (int)floor(transformed[2]); //Con_Printf("%f %f %f index %i %i %i:\n", transformed[0], transformed[1], transformed[2], index[0], index[1], index[2]); // now lerp the values VectorClear(diffusenormal); a = &model->brushq3.data_lightgrid[(index[2] * model->brushq3.num_lightgrid_isize[1] + index[1]) * model->brushq3.num_lightgrid_isize[0] + index[0]]; for (k = 0;k < 2;k++) { blend1 = (k ? (transformed[2] - index[2]) : (1 - (transformed[2] - index[2]))); if (blend1 < 0.001f || index[2] + k >= model->brushq3.num_lightgrid_isize[2]) continue; for (j = 0;j < 2;j++) { blend2 = blend1 * (j ? (transformed[1] - index[1]) : (1 - (transformed[1] - index[1]))); if (blend2 < 0.001f || index[1] + j >= model->brushq3.num_lightgrid_isize[1]) continue; for (i = 0;i < 2;i++) { blend = blend2 * (i ? (transformed[0] - index[0]) : (1 - (transformed[0] - index[0]))) * stylescale; if (blend < 0.001f || index[0] + i >= model->brushq3.num_lightgrid_isize[0]) continue; s = a + (k * model->brushq3.num_lightgrid_isize[1] + j) * model->brushq3.num_lightgrid_isize[0] + i; VectorMA(ambientcolor, blend * (1.0f / 128.0f), s->ambientrgb, ambientcolor); VectorMA(diffusecolor, blend * (1.0f / 128.0f), s->diffusergb, diffusecolor); // this uses the mod_md3_sin table because the values are // already in the 0-255 range, the 64+ bias fetches a cosine // instead of a sine value diffusenormal[0] += blend * (mod_md3_sin[64 + s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); diffusenormal[1] += blend * (mod_md3_sin[ s->diffuseyaw] * mod_md3_sin[s->diffusepitch]); diffusenormal[2] += blend * (mod_md3_sin[64 + s->diffusepitch]); //Con_Printf("blend %f: ambient %i %i %i, diffuse %i %i %i, diffusepitch %i diffuseyaw %i (%f %f, normal %f %f %f)\n", blend, s->ambientrgb[0], s->ambientrgb[1], s->ambientrgb[2], s->diffusergb[0], s->diffusergb[1], s->diffusergb[2], s->diffusepitch, s->diffuseyaw, pitch, yaw, (cos(yaw) * cospitch), (sin(yaw) * cospitch), (-sin(pitch))); } } } // normalize the light direction before turning VectorNormalize(diffusenormal); //Con_Printf("result: ambient %f %f %f diffuse %f %f %f diffusenormal %f %f %f\n", ambientcolor[0], ambientcolor[1], ambientcolor[2], diffusecolor[0], diffusecolor[1], diffusecolor[2], diffusenormal[0], diffusenormal[1], diffusenormal[2]); } static int Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(mnode_t *node, double p1[3], double p2[3]) { double t1, t2; double midf, mid[3]; int ret, side; // check for empty while (node->plane) { // find the point distances mplane_t *plane = node->plane; if (plane->type < 3) { t1 = p1[plane->type] - plane->dist; t2 = p2[plane->type] - plane->dist; } else { t1 = DotProduct (plane->normal, p1) - plane->dist; t2 = DotProduct (plane->normal, p2) - plane->dist; } if (t1 < 0) { if (t2 < 0) { node = node->children[1]; continue; } side = 1; } else { if (t2 >= 0) { node = node->children[0]; continue; } side = 0; } midf = t1 / (t1 - t2); VectorLerp(p1, midf, p2, mid); // recurse both sides, front side first // return 2 if empty is followed by solid (hit something) // do not return 2 if both are solid or both empty, // or if start is solid and end is empty // as these degenerate cases usually indicate the eye is in solid and // should see the target point anyway ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ], p1, mid); if (ret != 0) return ret; ret = Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(node->children[side ^ 1], mid, p2); if (ret != 1) return ret; return 2; } return ((mleaf_t *)node)->clusterindex < 0; } static qboolean Mod_Q3BSP_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) { if (model->brush.submodel || mod_q3bsp_tracelineofsight_brushes.integer) { trace_t trace; model->TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0); return trace.fraction == 1; } else { double tracestart[3], traceend[3]; VectorCopy(start, tracestart); VectorCopy(end, traceend); return !Mod_Q3BSP_TraceLineOfSight_RecursiveNodeCheck(model->brush.data_nodes, tracestart, traceend); } } void Mod_CollisionBIH_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { const bih_t *bih; const bih_leaf_t *leaf; const bih_node_t *node; const colbrushf_t *brush; int axis; int nodenum; int nodestackpos = 0; int nodestack[1024]; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; bih = &model->collision_bih; if(!bih->nodes) return; nodenum = bih->rootnode; nodestack[nodestackpos++] = nodenum; while (nodestackpos) { nodenum = nodestack[--nodestackpos]; node = bih->nodes + nodenum; #if 1 if (!BoxesOverlap(start, start, node->mins, node->maxs)) continue; #endif if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) { axis = node->type - BIH_SPLITX; if (start[axis] >= node->frontmin) nodestack[nodestackpos++] = node->front; if (start[axis] <= node->backmax) nodestack[nodestackpos++] = node->back; } else if (node->type == BIH_UNORDERED) { for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) { leaf = bih->leafs + node->children[axis]; #if 1 if (!BoxesOverlap(start, start, leaf->mins, leaf->maxs)) continue; #endif switch(leaf->type) { case BIH_BRUSH: brush = model->brush.data_brushes[leaf->itemindex].colbrushf; Collision_TracePointBrushFloat(trace, start, brush); break; case BIH_COLLISIONTRIANGLE: // collision triangle - skipped because they have no volume break; case BIH_RENDERTRIANGLE: // render triangle - skipped because they have no volume break; } } } } } static void Mod_CollisionBIH_TraceLineShared(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask, const bih_t *bih) { const bih_leaf_t *leaf; const bih_node_t *node; const colbrushf_t *brush; const int *e; const texture_t *texture; vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; int axis, nodenum, nodestackpos = 0, nodestack[1024]; if(!bih->nodes) return; if (VectorCompare(start, end)) { Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); return; } nodenum = bih->rootnode; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; // push first node nodestackline[nodestackpos][0] = start[0]; nodestackline[nodestackpos][1] = start[1]; nodestackline[nodestackpos][2] = start[2]; nodestackline[nodestackpos][3] = end[0]; nodestackline[nodestackpos][4] = end[1]; nodestackline[nodestackpos][5] = end[2]; nodestack[nodestackpos++] = nodenum; while (nodestackpos) { nodenum = nodestack[--nodestackpos]; node = bih->nodes + nodenum; VectorCopy(nodestackline[nodestackpos], nodestart); VectorCopy(nodestackline[nodestackpos] + 3, nodeend); sweepnodemins[0] = min(nodestart[0], nodeend[0]) - 1; sweepnodemins[1] = min(nodestart[1], nodeend[1]) - 1; sweepnodemins[2] = min(nodestart[2], nodeend[2]) - 1; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + 1; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + 1; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + 1; if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) continue; if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) { // recurse children of the split axis = node->type - BIH_SPLITX; d1 = node->backmax - nodestart[axis]; d2 = node->backmax - nodeend[axis]; d3 = nodestart[axis] - node->frontmin; d4 = nodeend[axis] - node->frontmin; switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) { case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 15: /* <<<< */ break; } } else if (node->type == BIH_UNORDERED) { // calculate sweep bounds for this node // copy node bounds into local variables VectorCopy(node->mins, nodebigmins); VectorCopy(node->maxs, nodebigmaxs); // clip line to this node bounds axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } // some of the line intersected the enlarged node box // calculate sweep bounds for this node sweepnodemins[0] = min(nodestart[0], nodeend[0]) - 1; sweepnodemins[1] = min(nodestart[1], nodeend[1]) - 1; sweepnodemins[2] = min(nodestart[2], nodeend[2]) - 1; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + 1; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + 1; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + 1; for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) { leaf = bih->leafs + node->children[axis]; if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) continue; switch(leaf->type) { case BIH_BRUSH: brush = model->brush.data_brushes[leaf->itemindex].colbrushf; Collision_TraceLineBrushFloat(trace, start, end, brush, brush); break; case BIH_COLLISIONTRIANGLE: if (!mod_q3bsp_curves_collisions.integer) continue; e = model->brush.data_collisionelement3i + 3*leaf->itemindex; texture = model->data_textures + leaf->textureindex; Collision_TraceLineTriangleFloat(trace, start, end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); break; case BIH_RENDERTRIANGLE: e = model->surfmesh.data_element3i + 3*leaf->itemindex; texture = model->data_textures + leaf->textureindex; Collision_TraceLineTriangleFloat(trace, start, end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); break; } } } } } void Mod_CollisionBIH_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { if (VectorCompare(start, end)) { Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); return; } Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask, &model->collision_bih); } void Mod_CollisionBIH_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *thisbrush_start, colbrushf_t *thisbrush_end, int hitsupercontentsmask, int skipsupercontentsmask) { const bih_t *bih; const bih_leaf_t *leaf; const bih_node_t *node; const colbrushf_t *brush; const int *e; const texture_t *texture; vec3_t start, end, startmins, startmaxs, endmins, endmaxs, mins, maxs; vec3_t nodebigmins, nodebigmaxs, nodestart, nodeend, sweepnodemins, sweepnodemaxs; vec_t d1, d2, d3, d4, f, nodestackline[1024][6]; int axis, nodenum, nodestackpos = 0, nodestack[1024]; if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(thisbrush_start->mins, thisbrush_start->maxs) && VectorCompare(thisbrush_end->mins, thisbrush_end->maxs)) { if (VectorCompare(thisbrush_start->mins, thisbrush_end->mins)) Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, thisbrush_start->mins, hitsupercontentsmask, skipsupercontentsmask); else Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, thisbrush_start->mins, thisbrush_end->mins, hitsupercontentsmask, skipsupercontentsmask); return; } bih = &model->collision_bih; if(!bih->nodes) return; nodenum = bih->rootnode; // box trace, performed as brush trace memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; // calculate tracebox-like parameters for efficient culling VectorMAM(0.5f, thisbrush_start->mins, 0.5f, thisbrush_start->maxs, start); VectorMAM(0.5f, thisbrush_end->mins, 0.5f, thisbrush_end->maxs, end); VectorSubtract(thisbrush_start->mins, start, startmins); VectorSubtract(thisbrush_start->maxs, start, startmaxs); VectorSubtract(thisbrush_end->mins, end, endmins); VectorSubtract(thisbrush_end->maxs, end, endmaxs); mins[0] = min(startmins[0], endmins[0]); mins[1] = min(startmins[1], endmins[1]); mins[2] = min(startmins[2], endmins[2]); maxs[0] = max(startmaxs[0], endmaxs[0]); maxs[1] = max(startmaxs[1], endmaxs[1]); maxs[2] = max(startmaxs[2], endmaxs[2]); // push first node nodestackline[nodestackpos][0] = start[0]; nodestackline[nodestackpos][1] = start[1]; nodestackline[nodestackpos][2] = start[2]; nodestackline[nodestackpos][3] = end[0]; nodestackline[nodestackpos][4] = end[1]; nodestackline[nodestackpos][5] = end[2]; nodestack[nodestackpos++] = nodenum; while (nodestackpos) { nodenum = nodestack[--nodestackpos]; node = bih->nodes + nodenum; VectorCopy(nodestackline[nodestackpos], nodestart); VectorCopy(nodestackline[nodestackpos] + 3, nodeend); sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0] - 1; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1] - 1; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2] - 1; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0] + 1; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1] + 1; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2] + 1; if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, node->mins, node->maxs)) continue; if (node->type <= BIH_SPLITZ && nodestackpos+2 <= 1024) { // recurse children of the split axis = node->type - BIH_SPLITX; d1 = node->backmax - nodestart[axis] - mins[axis]; d2 = node->backmax - nodeend[axis] - mins[axis]; d3 = nodestart[axis] - node->frontmin + maxs[axis]; d4 = nodeend[axis] - node->frontmin + maxs[axis]; switch((d1 < 0) | ((d2 < 0) << 1) | ((d3 < 0) << 2) | ((d4 < 0) << 3)) { case 0: /* >>>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 1: /* <>>> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 2: /* ><>> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 3: /* <<>> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 4: /* >><> */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 5: /* <><> */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 6: /* ><<> */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 7: /* <<<> */ f = d3 / (d3 - d4); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 8: /* >>>< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 9: /* <>>< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 10: /* ><>< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 11: /* <<>< */ f = d3 / (d3 - d4); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->front; break; case 12: /* >><< */ VectorCopy(nodestart, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 13: /* <><< */ f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos]); VectorCopy( nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 14: /* ><<< */ f = d1 / (d1 - d2); VectorCopy(nodestart, nodestackline[nodestackpos]); VectorLerp(nodestart, f, nodeend, nodestackline[nodestackpos] + 3); nodestack[nodestackpos++] = node->back; break; case 15: /* <<<< */ break; } } else if (node->type == BIH_UNORDERED) { // calculate sweep bounds for this node // copy node bounds into local variables and expand to get Minkowski Sum of the two shapes VectorSubtract(node->mins, maxs, nodebigmins); VectorSubtract(node->maxs, mins, nodebigmaxs); // clip line to this node bounds axis = 0; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } axis = 1; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } axis = 2; d1 = nodestart[axis] - nodebigmins[axis]; d2 = nodeend[axis] - nodebigmins[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } d1 = nodebigmaxs[axis] - nodestart[axis]; d2 = nodebigmaxs[axis] - nodeend[axis]; if (d1 < 0) { if (d2 < 0) continue; f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodestart); } else if (d2 < 0) { f = d1 / (d1 - d2); VectorLerp(nodestart, f, nodeend, nodeend); } // some of the line intersected the enlarged node box // calculate sweep bounds for this node sweepnodemins[0] = min(nodestart[0], nodeend[0]) + mins[0] - 1; sweepnodemins[1] = min(nodestart[1], nodeend[1]) + mins[1] - 1; sweepnodemins[2] = min(nodestart[2], nodeend[2]) + mins[2] - 1; sweepnodemaxs[0] = max(nodestart[0], nodeend[0]) + maxs[0] + 1; sweepnodemaxs[1] = max(nodestart[1], nodeend[1]) + maxs[1] + 1; sweepnodemaxs[2] = max(nodestart[2], nodeend[2]) + maxs[2] + 1; for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++) { leaf = bih->leafs + node->children[axis]; if (!BoxesOverlap(sweepnodemins, sweepnodemaxs, leaf->mins, leaf->maxs)) continue; switch(leaf->type) { case BIH_BRUSH: brush = model->brush.data_brushes[leaf->itemindex].colbrushf; Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); break; case BIH_COLLISIONTRIANGLE: if (!mod_q3bsp_curves_collisions.integer) continue; e = model->brush.data_collisionelement3i + 3*leaf->itemindex; texture = model->data_textures + leaf->textureindex; Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->brush.data_collisionvertex3f + e[0] * 3, model->brush.data_collisionvertex3f + e[1] * 3, model->brush.data_collisionvertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); break; case BIH_RENDERTRIANGLE: e = model->surfmesh.data_element3i + 3*leaf->itemindex; texture = model->data_textures + leaf->textureindex; Collision_TraceBrushTriangleFloat(trace, thisbrush_start, thisbrush_end, model->surfmesh.data_vertex3f + e[0] * 3, model->surfmesh.data_vertex3f + e[1] * 3, model->surfmesh.data_vertex3f + e[2] * 3, texture->supercontents, texture->surfaceflags, texture); break; } } } } } void Mod_CollisionBIH_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { colboxbrushf_t thisbrush_start, thisbrush_end; vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; // box trace, performed as brush trace VectorAdd(start, boxmins, boxstartmins); VectorAdd(start, boxmaxs, boxstartmaxs); VectorAdd(end, boxmins, boxendmins); VectorAdd(end, boxmaxs, boxendmaxs); Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask, skipsupercontentsmask); } int Mod_CollisionBIH_PointSuperContents(struct model_s *model, int frame, const vec3_t point) { trace_t trace; Mod_CollisionBIH_TracePoint(model, NULL, NULL, &trace, point, 0, 0); return trace.startsupercontents; } qboolean Mod_CollisionBIH_TraceLineOfSight(struct model_s *model, const vec3_t start, const vec3_t end) { trace_t trace; Mod_CollisionBIH_TraceLine(model, NULL, NULL, &trace, start, end, SUPERCONTENTS_VISBLOCKERMASK, 0); return trace.fraction == 1; } void Mod_CollisionBIH_TracePoint_Mesh(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { #if 0 // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid vec3_t end; int hitsupercontents; VectorSet(end, start[0], start[1], model->normalmins[2]); #endif memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; #if 0 Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); hitsupercontents = trace->hitsupercontents; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; trace->startsupercontents = hitsupercontents; #endif } int Mod_CollisionBIH_PointSuperContents_Mesh(struct model_s *model, int frame, const vec3_t start) { #if 0 // broken - needs to be modified to count front faces and backfaces to figure out if it is in solid trace_t trace; vec3_t end; VectorSet(end, start[0], start[1], model->normalmins[2]); memset(&trace, 0, sizeof(trace)); trace.fraction = 1; trace.hitsupercontentsmask = hitsupercontentsmask; trace.skipsupercontentsmask = skipsupercontentsmask; Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); return trace.hitsupercontents; #else return 0; #endif } static void Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t point, int markframe) { int i; mleaf_t *leaf; colbrushf_t *brush; // find which leaf the point is in while (node->plane) node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; // point trace the brushes leaf = (mleaf_t *)node; for (i = 0;i < leaf->numleafbrushes;i++) { brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; if (brush && brush->markframe != markframe && BoxesOverlap(point, point, brush->mins, brush->maxs)) { brush->markframe = markframe; Collision_TracePointBrushFloat(trace, point, brush); } } // can't do point traces on curves (they have no thickness) } static void Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const vec3_t start, const vec3_t end, vec_t startfrac, vec_t endfrac, const vec3_t linestart, const vec3_t lineend, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) { int i, startside, endside; float dist1, dist2, midfrac, mid[3], nodesegmentmins[3], nodesegmentmaxs[3]; mleaf_t *leaf; msurface_t *surface; mplane_t *plane; colbrushf_t *brush; // walk the tree until we hit a leaf, recursing for any split cases while (node->plane) { #if 0 if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) return; Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[0], start, end, startfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); node = node->children[1]; #else // abort if this part of the bsp tree can not be hit by this trace // if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) // return; plane = node->plane; // axial planes are much more common than non-axial, so an optimized // axial case pays off here if (plane->type < 3) { dist1 = start[plane->type] - plane->dist; dist2 = end[plane->type] - plane->dist; } else { dist1 = DotProduct(start, plane->normal) - plane->dist; dist2 = DotProduct(end, plane->normal) - plane->dist; } startside = dist1 < 0; endside = dist2 < 0; if (startside == endside) { // most of the time the line fragment is on one side of the plane node = node->children[startside]; } else { // line crosses node plane, split the line dist1 = PlaneDiff(linestart, plane); dist2 = PlaneDiff(lineend, plane); midfrac = dist1 / (dist1 - dist2); VectorLerp(linestart, midfrac, lineend, mid); // take the near side first Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[startside], start, mid, startfrac, midfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); // if we found an impact on the front side, don't waste time // exploring the far side if (midfrac <= trace->fraction) Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, node->children[endside], mid, end, midfrac, endfrac, linestart, lineend, markframe, segmentmins, segmentmaxs); return; } #endif } // abort if this part of the bsp tree can not be hit by this trace // if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) // return; // hit a leaf nodesegmentmins[0] = min(start[0], end[0]) - 1; nodesegmentmins[1] = min(start[1], end[1]) - 1; nodesegmentmins[2] = min(start[2], end[2]) - 1; nodesegmentmaxs[0] = max(start[0], end[0]) + 1; nodesegmentmaxs[1] = max(start[1], end[1]) + 1; nodesegmentmaxs[2] = max(start[2], end[2]) + 1; // line trace the brushes leaf = (mleaf_t *)node; #if 0 if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) return; #endif for (i = 0;i < leaf->numleafbrushes;i++) { brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) { brush->markframe = markframe; Collision_TraceLineBrushFloat(trace, linestart, lineend, brush, brush); } } // can't do point traces on curves (they have no thickness) if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer && !VectorCompare(start, end)) { // line trace the curves for (i = 0;i < leaf->numleafsurfaces;i++) { surface = model->data_surfaces + leaf->firstleafsurface[i]; if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) { surface->deprecatedq3collisionmarkframe = markframe; Collision_TraceLineTriangleMeshFloat(trace, linestart, lineend, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); } } } } static void Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace_t *trace, dp_model_t *model, mnode_t *node, const colbrushf_t *thisbrush_start, const colbrushf_t *thisbrush_end, int markframe, const vec3_t segmentmins, const vec3_t segmentmaxs) { int i; int sides; mleaf_t *leaf; colbrushf_t *brush; msurface_t *surface; mplane_t *plane; float nodesegmentmins[3], nodesegmentmaxs[3]; // walk the tree until we hit a leaf, recursing for any split cases while (node->plane) { #if 0 if (!BoxesOverlap(segmentmins, segmentmaxs, node->mins, node->maxs)) return; Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); node = node->children[1]; #else // abort if this part of the bsp tree can not be hit by this trace // if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) // return; plane = node->plane; // axial planes are much more common than non-axial, so an optimized // axial case pays off here if (plane->type < 3) { // this is an axial plane, compare bounding box directly to it and // recurse sides accordingly // recurse down node sides // use an inlined axial BoxOnPlaneSide to slightly reduce overhead //sides = BoxOnPlaneSide(nodesegmentmins, nodesegmentmaxs, plane); //sides = ((segmentmaxs[plane->type] >= plane->dist) | ((segmentmins[plane->type] < plane->dist) << 1)); sides = ((segmentmaxs[plane->type] >= plane->dist) + ((segmentmins[plane->type] < plane->dist) * 2)); } else { // this is a non-axial plane, so check if the start and end boxes // are both on one side of the plane to handle 'diagonal' cases sides = BoxOnPlaneSide(thisbrush_start->mins, thisbrush_start->maxs, plane) | BoxOnPlaneSide(thisbrush_end->mins, thisbrush_end->maxs, plane); } if (sides == 3) { // segment crosses plane Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, node->children[0], thisbrush_start, thisbrush_end, markframe, segmentmins, segmentmaxs); sides = 2; } // if sides == 0 then the trace itself is bogus (Not A Number values), // in this case we simply pretend the trace hit nothing if (sides == 0) return; // ERROR: NAN bounding box! // take whichever side the segment box is on node = node->children[sides - 1]; #endif } // abort if this part of the bsp tree can not be hit by this trace // if (!(node->combinedsupercontents & trace->hitsupercontentsmask)) // return; nodesegmentmins[0] = max(segmentmins[0], node->mins[0] - 1); nodesegmentmins[1] = max(segmentmins[1], node->mins[1] - 1); nodesegmentmins[2] = max(segmentmins[2], node->mins[2] - 1); nodesegmentmaxs[0] = min(segmentmaxs[0], node->maxs[0] + 1); nodesegmentmaxs[1] = min(segmentmaxs[1], node->maxs[1] + 1); nodesegmentmaxs[2] = min(segmentmaxs[2], node->maxs[2] + 1); // hit a leaf leaf = (mleaf_t *)node; #if 0 if (!BoxesOverlap(segmentmins, segmentmaxs, leaf->mins, leaf->maxs)) return; #endif for (i = 0;i < leaf->numleafbrushes;i++) { brush = model->brush.data_brushes[leaf->firstleafbrush[i]].colbrushf; if (brush && brush->markframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, brush->mins, brush->maxs)) { brush->markframe = markframe; Collision_TraceBrushBrushFloat(trace, thisbrush_start, thisbrush_end, brush, brush); } } if (leaf->containscollisionsurfaces && mod_q3bsp_curves_collisions.integer) { for (i = 0;i < leaf->numleafsurfaces;i++) { surface = model->data_surfaces + leaf->firstleafsurface[i]; if (surface->num_collisiontriangles && surface->deprecatedq3collisionmarkframe != markframe && BoxesOverlap(nodesegmentmins, nodesegmentmaxs, surface->mins, surface->maxs)) { surface->deprecatedq3collisionmarkframe = markframe; Collision_TraceBrushTriangleMeshFloat(trace, thisbrush_start, thisbrush_end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); } } } } static int markframe = 0; static void Mod_Q3BSP_TracePoint(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, int hitsupercontentsmask, int skipsupercontentsmask) { int i; q3mbrush_t *brush; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; if (mod_collision_bih.integer) Mod_CollisionBIH_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); else if (model->brush.submodel) { for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) if (brush->colbrushf) Collision_TracePointBrushFloat(trace, start, brush->colbrushf); } else Mod_Q3BSP_TracePoint_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, ++markframe); } static void Mod_Q3BSP_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { int i; float segmentmins[3], segmentmaxs[3]; msurface_t *surface; q3mbrush_t *brush; if (VectorCompare(start, end)) { Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start, hitsupercontentsmask, skipsupercontentsmask); return; } memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; segmentmins[0] = min(start[0], end[0]) - 1; segmentmins[1] = min(start[1], end[1]) - 1; segmentmins[2] = min(start[2], end[2]) - 1; segmentmaxs[0] = max(start[0], end[0]) + 1; segmentmaxs[1] = max(start[1], end[1]) + 1; segmentmaxs[2] = max(start[2], end[2]) + 1; if (mod_collision_bih.integer) Mod_CollisionBIH_TraceLine(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); else if (model->brush.submodel) { for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) Collision_TraceLineBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); if (mod_q3bsp_curves_collisions.integer) for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) Collision_TraceLineTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); } else Mod_Q3BSP_TraceLine_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, 0, 1, start, end, ++markframe, segmentmins, segmentmaxs); } static void Mod_Q3BSP_TraceBrush(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, colbrushf_t *start, colbrushf_t *end, int hitsupercontentsmask, int skipsupercontentsmask) { float segmentmins[3], segmentmaxs[3]; int i; msurface_t *surface; q3mbrush_t *brush; if (mod_q3bsp_optimizedtraceline.integer && VectorCompare(start->mins, start->maxs) && VectorCompare(end->mins, end->maxs)) { if (VectorCompare(start->mins, end->mins)) Mod_Q3BSP_TracePoint(model, frameblend, skeleton, trace, start->mins, hitsupercontentsmask, skipsupercontentsmask); else Mod_Q3BSP_TraceLine(model, frameblend, skeleton, trace, start->mins, end->mins, hitsupercontentsmask, skipsupercontentsmask); return; } // box trace, performed as brush trace memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; segmentmins[0] = min(start->mins[0], end->mins[0]) - 1; segmentmins[1] = min(start->mins[1], end->mins[1]) - 1; segmentmins[2] = min(start->mins[2], end->mins[2]) - 1; segmentmaxs[0] = max(start->maxs[0], end->maxs[0]) + 1; segmentmaxs[1] = max(start->maxs[1], end->maxs[1]) + 1; segmentmaxs[2] = max(start->maxs[2], end->maxs[2]) + 1; if (mod_collision_bih.integer) Mod_CollisionBIH_TraceBrush(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask); else if (model->brush.submodel) { for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) if (brush->colbrushf && BoxesOverlap(segmentmins, segmentmaxs, brush->colbrushf->mins, brush->colbrushf->maxs)) Collision_TraceBrushBrushFloat(trace, start, end, brush->colbrushf, brush->colbrushf); if (mod_q3bsp_curves_collisions.integer) for (i = 0, surface = model->data_surfaces + model->firstmodelsurface;i < model->nummodelsurfaces;i++, surface++) if (surface->num_collisiontriangles && BoxesOverlap(segmentmins, segmentmaxs, surface->mins, surface->maxs)) Collision_TraceBrushTriangleMeshFloat(trace, start, end, surface->num_collisiontriangles, surface->deprecatedq3data_collisionelement3i, surface->deprecatedq3data_collisionvertex3f, surface->deprecatedq3num_collisionbboxstride, surface->deprecatedq3data_collisionbbox6f, surface->texture->supercontents, surface->texture->surfaceflags, surface->texture, segmentmins, segmentmaxs); } else Mod_Q3BSP_TraceBrush_RecursiveBSPNode(trace, model, model->brush.data_nodes, start, end, ++markframe, segmentmins, segmentmaxs); } static void Mod_Q3BSP_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { colboxbrushf_t thisbrush_start, thisbrush_end; vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; // box trace, performed as brush trace VectorAdd(start, boxmins, boxstartmins); VectorAdd(start, boxmaxs, boxstartmaxs); VectorAdd(end, boxmins, boxendmins); VectorAdd(end, boxmaxs, boxendmaxs); Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); Mod_Q3BSP_TraceBrush(model, frameblend, skeleton, trace, &thisbrush_start.brush, &thisbrush_end.brush, hitsupercontentsmask, skipsupercontentsmask); } static int Mod_Q3BSP_PointSuperContents(struct model_s *model, int frame, const vec3_t point) { int i; int supercontents = 0; q3mbrush_t *brush; if (mod_collision_bih.integer) { supercontents = Mod_CollisionBIH_PointSuperContents(model, frame, point); } // test if the point is inside each brush else if (model->brush.submodel) { // submodels are effectively one leaf for (i = 0, brush = model->brush.data_brushes + model->firstmodelbrush;i < model->nummodelbrushes;i++, brush++) if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) supercontents |= brush->colbrushf->supercontents; } else { mnode_t *node = model->brush.data_nodes; mleaf_t *leaf; // find which leaf the point is in while (node->plane) node = node->children[(node->plane->type < 3 ? point[node->plane->type] : DotProduct(point, node->plane->normal)) < node->plane->dist]; leaf = (mleaf_t *)node; // now check the brushes in the leaf for (i = 0;i < leaf->numleafbrushes;i++) { brush = model->brush.data_brushes + leaf->firstleafbrush[i]; if (brush->colbrushf && Collision_PointInsideBrushFloat(point, brush->colbrushf)) supercontents |= brush->colbrushf->supercontents; } } return supercontents; } void Mod_CollisionBIH_TraceLineAgainstSurfaces(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { Mod_CollisionBIH_TraceLineShared(model, frameblend, skeleton, trace, start, end, hitsupercontentsmask, skipsupercontentsmask, &model->render_bih); } bih_t *Mod_MakeCollisionBIH(dp_model_t *model, qboolean userendersurfaces, bih_t *out) { int j; int bihnumleafs; int bihmaxnodes; int brushindex; int triangleindex; int bihleafindex; int nummodelbrushes = model->nummodelbrushes; int nummodelsurfaces = model->nummodelsurfaces; const int *e; const int *collisionelement3i; const float *collisionvertex3f; const int *renderelement3i; const float *rendervertex3f; bih_leaf_t *bihleafs; bih_node_t *bihnodes; int *temp_leafsort; int *temp_leafsortscratch; const msurface_t *surface; const q3mbrush_t *brush; // find out how many BIH leaf nodes we need bihnumleafs = 0; if (userendersurfaces) { for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) bihnumleafs += surface->num_triangles; } else { for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) if (brush->colbrushf) bihnumleafs++; for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) { if (surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS) bihnumleafs += surface->num_triangles + surface->num_collisiontriangles; else bihnumleafs += surface->num_collisiontriangles; } } if (!bihnumleafs) return NULL; // allocate the memory for the BIH leaf nodes bihleafs = (bih_leaf_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_leaf_t) * bihnumleafs); // now populate the BIH leaf nodes bihleafindex = 0; // add render surfaces renderelement3i = model->surfmesh.data_element3i; rendervertex3f = model->surfmesh.data_vertex3f; for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) { for (triangleindex = 0, e = renderelement3i + 3*surface->num_firsttriangle;triangleindex < surface->num_triangles;triangleindex++, e += 3) { if (!userendersurfaces && !(surface->texture->basematerialflags & MATERIALFLAG_MESHCOLLISIONS)) continue; bihleafs[bihleafindex].type = BIH_RENDERTRIANGLE; bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firsttriangle; bihleafs[bihleafindex].mins[0] = min(rendervertex3f[3*e[0]+0], min(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) - 1; bihleafs[bihleafindex].mins[1] = min(rendervertex3f[3*e[0]+1], min(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) - 1; bihleafs[bihleafindex].mins[2] = min(rendervertex3f[3*e[0]+2], min(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) - 1; bihleafs[bihleafindex].maxs[0] = max(rendervertex3f[3*e[0]+0], max(rendervertex3f[3*e[1]+0], rendervertex3f[3*e[2]+0])) + 1; bihleafs[bihleafindex].maxs[1] = max(rendervertex3f[3*e[0]+1], max(rendervertex3f[3*e[1]+1], rendervertex3f[3*e[2]+1])) + 1; bihleafs[bihleafindex].maxs[2] = max(rendervertex3f[3*e[0]+2], max(rendervertex3f[3*e[1]+2], rendervertex3f[3*e[2]+2])) + 1; bihleafindex++; } } if (!userendersurfaces) { // add collision brushes for (brushindex = 0, brush = model->brush.data_brushes + brushindex+model->firstmodelbrush;brushindex < nummodelbrushes;brushindex++, brush++) { if (!brush->colbrushf) continue; bihleafs[bihleafindex].type = BIH_BRUSH; bihleafs[bihleafindex].textureindex = brush->texture - model->data_textures; bihleafs[bihleafindex].surfaceindex = -1; bihleafs[bihleafindex].itemindex = brushindex+model->firstmodelbrush; VectorCopy(brush->colbrushf->mins, bihleafs[bihleafindex].mins); VectorCopy(brush->colbrushf->maxs, bihleafs[bihleafindex].maxs); bihleafindex++; } // add collision surfaces collisionelement3i = model->brush.data_collisionelement3i; collisionvertex3f = model->brush.data_collisionvertex3f; for (j = 0, surface = model->data_surfaces + model->firstmodelsurface;j < nummodelsurfaces;j++, surface++) { for (triangleindex = 0, e = collisionelement3i + 3*surface->num_firstcollisiontriangle;triangleindex < surface->num_collisiontriangles;triangleindex++, e += 3) { bihleafs[bihleafindex].type = BIH_COLLISIONTRIANGLE; bihleafs[bihleafindex].textureindex = surface->texture - model->data_textures; bihleafs[bihleafindex].surfaceindex = surface - model->data_surfaces; bihleafs[bihleafindex].itemindex = triangleindex+surface->num_firstcollisiontriangle; bihleafs[bihleafindex].mins[0] = min(collisionvertex3f[3*e[0]+0], min(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) - 1; bihleafs[bihleafindex].mins[1] = min(collisionvertex3f[3*e[0]+1], min(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) - 1; bihleafs[bihleafindex].mins[2] = min(collisionvertex3f[3*e[0]+2], min(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) - 1; bihleafs[bihleafindex].maxs[0] = max(collisionvertex3f[3*e[0]+0], max(collisionvertex3f[3*e[1]+0], collisionvertex3f[3*e[2]+0])) + 1; bihleafs[bihleafindex].maxs[1] = max(collisionvertex3f[3*e[0]+1], max(collisionvertex3f[3*e[1]+1], collisionvertex3f[3*e[2]+1])) + 1; bihleafs[bihleafindex].maxs[2] = max(collisionvertex3f[3*e[0]+2], max(collisionvertex3f[3*e[1]+2], collisionvertex3f[3*e[2]+2])) + 1; bihleafindex++; } } } // allocate buffers for the produced and temporary data bihmaxnodes = bihnumleafs + 1; bihnodes = (bih_node_t *)Mem_Alloc(loadmodel->mempool, sizeof(bih_node_t) * bihmaxnodes); temp_leafsort = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * bihnumleafs * 2); temp_leafsortscratch = temp_leafsort + bihnumleafs; // now build it BIH_Build(out, bihnumleafs, bihleafs, bihmaxnodes, bihnodes, temp_leafsort, temp_leafsortscratch); // we're done with the temporary data Mem_Free(temp_leafsort); // resize the BIH nodes array if it over-allocated if (out->maxnodes > out->numnodes) { out->maxnodes = out->numnodes; out->nodes = (bih_node_t *)Mem_Realloc(loadmodel->mempool, out->nodes, out->numnodes * sizeof(bih_node_t)); } return out; } static int Mod_Q3BSP_SuperContentsFromNativeContents(dp_model_t *model, int nativecontents) { int supercontents = 0; if (nativecontents & CONTENTSQ3_SOLID) supercontents |= SUPERCONTENTS_SOLID; if (nativecontents & CONTENTSQ3_WATER) supercontents |= SUPERCONTENTS_WATER; if (nativecontents & CONTENTSQ3_SLIME) supercontents |= SUPERCONTENTS_SLIME; if (nativecontents & CONTENTSQ3_LAVA) supercontents |= SUPERCONTENTS_LAVA; if (nativecontents & CONTENTSQ3_BODY) supercontents |= SUPERCONTENTS_BODY; if (nativecontents & CONTENTSQ3_CORPSE) supercontents |= SUPERCONTENTS_CORPSE; if (nativecontents & CONTENTSQ3_NODROP) supercontents |= SUPERCONTENTS_NODROP; if (nativecontents & CONTENTSQ3_PLAYERCLIP) supercontents |= SUPERCONTENTS_PLAYERCLIP; if (nativecontents & CONTENTSQ3_MONSTERCLIP) supercontents |= SUPERCONTENTS_MONSTERCLIP; if (nativecontents & CONTENTSQ3_DONOTENTER) supercontents |= SUPERCONTENTS_DONOTENTER; if (nativecontents & CONTENTSQ3_BOTCLIP) supercontents |= SUPERCONTENTS_BOTCLIP; if (!(nativecontents & CONTENTSQ3_TRANSLUCENT)) supercontents |= SUPERCONTENTS_OPAQUE; return supercontents; } static int Mod_Q3BSP_NativeContentsFromSuperContents(dp_model_t *model, int supercontents) { int nativecontents = 0; if (supercontents & SUPERCONTENTS_SOLID) nativecontents |= CONTENTSQ3_SOLID; if (supercontents & SUPERCONTENTS_WATER) nativecontents |= CONTENTSQ3_WATER; if (supercontents & SUPERCONTENTS_SLIME) nativecontents |= CONTENTSQ3_SLIME; if (supercontents & SUPERCONTENTS_LAVA) nativecontents |= CONTENTSQ3_LAVA; if (supercontents & SUPERCONTENTS_BODY) nativecontents |= CONTENTSQ3_BODY; if (supercontents & SUPERCONTENTS_CORPSE) nativecontents |= CONTENTSQ3_CORPSE; if (supercontents & SUPERCONTENTS_NODROP) nativecontents |= CONTENTSQ3_NODROP; if (supercontents & SUPERCONTENTS_PLAYERCLIP) nativecontents |= CONTENTSQ3_PLAYERCLIP; if (supercontents & SUPERCONTENTS_MONSTERCLIP) nativecontents |= CONTENTSQ3_MONSTERCLIP; if (supercontents & SUPERCONTENTS_DONOTENTER) nativecontents |= CONTENTSQ3_DONOTENTER; if (supercontents & SUPERCONTENTS_BOTCLIP) nativecontents |= CONTENTSQ3_BOTCLIP; if (!(supercontents & SUPERCONTENTS_OPAQUE)) nativecontents |= CONTENTSQ3_TRANSLUCENT; return nativecontents; } static void Mod_Q3BSP_RecursiveFindNumLeafs(mnode_t *node) { int numleafs; while (node->plane) { Mod_Q3BSP_RecursiveFindNumLeafs(node->children[0]); node = node->children[1]; } numleafs = ((mleaf_t *)node - loadmodel->brush.data_leafs) + 1; if (loadmodel->brush.num_leafs < numleafs) loadmodel->brush.num_leafs = numleafs; } static void Mod_Q3BSP_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, lumps; q3dheader_t *header; float corner[3], yawradius, modelradius; mod->modeldatatypestring = "Q3BSP"; mod->type = mod_brushq3; mod->brush.ishlbsp = false; mod->brush.isbsp2rmqe = false; mod->brush.isbsp2 = false; mod->brush.isq2bsp = false; mod->brush.isq3bsp = true; mod->numframes = 2; // although alternate textures are not supported it is annoying to complain about no such frame 1 mod->numskins = 1; header = (q3dheader_t *)buffer; if((char *) bufferend < (char *) buffer + sizeof(q3dheader_t)) Host_Error("Mod_Q3BSP_Load: %s is smaller than its header", mod->name); i = LittleLong(header->version); if (i != Q3BSPVERSION && i != Q3BSPVERSION_IG && i != Q3BSPVERSION_LIVE) Host_Error("Mod_Q3BSP_Load: %s has wrong version number (%i, should be %i)", mod->name, i, Q3BSPVERSION); mod->soundfromcenter = true; mod->TraceBox = Mod_Q3BSP_TraceBox; mod->TraceBrush = Mod_Q3BSP_TraceBrush; mod->TraceLine = Mod_Q3BSP_TraceLine; mod->TracePoint = Mod_Q3BSP_TracePoint; mod->PointSuperContents = Mod_Q3BSP_PointSuperContents; mod->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; mod->brush.TraceLineOfSight = Mod_Q3BSP_TraceLineOfSight; mod->brush.SuperContentsFromNativeContents = Mod_Q3BSP_SuperContentsFromNativeContents; mod->brush.NativeContentsFromSuperContents = Mod_Q3BSP_NativeContentsFromSuperContents; mod->brush.GetPVS = Mod_Q1BSP_GetPVS; mod->brush.FatPVS = Mod_Q1BSP_FatPVS; mod->brush.BoxTouchingPVS = Mod_Q1BSP_BoxTouchingPVS; mod->brush.BoxTouchingLeafPVS = Mod_Q1BSP_BoxTouchingLeafPVS; mod->brush.BoxTouchingVisibleLeafs = Mod_Q1BSP_BoxTouchingVisibleLeafs; mod->brush.FindBoxClusters = Mod_Q1BSP_FindBoxClusters; mod->brush.LightPoint = Mod_Q3BSP_LightPoint; mod->brush.FindNonSolidLocation = Mod_Q1BSP_FindNonSolidLocation; mod->brush.AmbientSoundLevelsForPoint = NULL; mod->brush.RoundUpToHullSize = NULL; mod->brush.PointInLeaf = Mod_Q1BSP_PointInLeaf; mod->Draw = R_Q1BSP_Draw; mod->DrawDepth = R_Q1BSP_DrawDepth; mod->DrawDebug = R_Q1BSP_DrawDebug; mod->DrawPrepass = R_Q1BSP_DrawPrepass; mod->GetLightInfo = R_Q1BSP_GetLightInfo; mod->CompileShadowMap = R_Q1BSP_CompileShadowMap; mod->DrawShadowMap = R_Q1BSP_DrawShadowMap; mod->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; mod->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; mod->DrawLight = R_Q1BSP_DrawLight; mod_base = (unsigned char *)header; // swap all the lumps header->ident = LittleLong(header->ident); header->version = LittleLong(header->version); lumps = (header->version == Q3BSPVERSION_LIVE) ? Q3HEADER_LUMPS_LIVE : Q3HEADER_LUMPS; for (i = 0;i < lumps;i++) { j = (header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs)); if((char *) bufferend < (char *) buffer + j) Host_Error("Mod_Q3BSP_Load: %s has a lump that starts outside the file!", mod->name); j += (header->lumps[i].filelen = LittleLong(header->lumps[i].filelen)); if((char *) bufferend < (char *) buffer + j) Host_Error("Mod_Q3BSP_Load: %s has a lump that ends outside the file!", mod->name); } /* * NO, do NOT clear them! * they contain actual data referenced by other stuff. * Instead, before using the advertisements lump, check header->versio * again! * Sorry, but otherwise it breaks memory of the first lump. for (i = lumps;i < Q3HEADER_LUMPS_MAX;i++) { header->lumps[i].fileofs = 0; header->lumps[i].filelen = 0; } */ mod->brush.qw_md4sum = 0; mod->brush.qw_md4sum2 = 0; for (i = 0;i < lumps;i++) { if (i == Q3LUMP_ENTITIES) continue; mod->brush.qw_md4sum ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); if (i == Q3LUMP_PVS || i == Q3LUMP_LEAFS || i == Q3LUMP_NODES) continue; mod->brush.qw_md4sum2 ^= Com_BlockChecksum(mod_base + header->lumps[i].fileofs, header->lumps[i].filelen); // all this checksumming can take a while, so let's send keepalives here too CL_KeepaliveMessage(false); } Mod_Q3BSP_LoadEntities(&header->lumps[Q3LUMP_ENTITIES]); Mod_Q3BSP_LoadTextures(&header->lumps[Q3LUMP_TEXTURES]); Mod_Q3BSP_LoadPlanes(&header->lumps[Q3LUMP_PLANES]); if (header->version == Q3BSPVERSION_IG) Mod_Q3BSP_LoadBrushSides_IG(&header->lumps[Q3LUMP_BRUSHSIDES]); else Mod_Q3BSP_LoadBrushSides(&header->lumps[Q3LUMP_BRUSHSIDES]); Mod_Q3BSP_LoadBrushes(&header->lumps[Q3LUMP_BRUSHES]); Mod_Q3BSP_LoadEffects(&header->lumps[Q3LUMP_EFFECTS]); Mod_Q3BSP_LoadVertices(&header->lumps[Q3LUMP_VERTICES]); Mod_Q3BSP_LoadTriangles(&header->lumps[Q3LUMP_TRIANGLES]); Mod_Q3BSP_LoadLightmaps(&header->lumps[Q3LUMP_LIGHTMAPS], &header->lumps[Q3LUMP_FACES]); Mod_Q3BSP_LoadFaces(&header->lumps[Q3LUMP_FACES]); Mod_Q3BSP_LoadModels(&header->lumps[Q3LUMP_MODELS]); Mod_Q3BSP_LoadLeafBrushes(&header->lumps[Q3LUMP_LEAFBRUSHES]); Mod_Q3BSP_LoadLeafFaces(&header->lumps[Q3LUMP_LEAFFACES]); Mod_Q3BSP_LoadLeafs(&header->lumps[Q3LUMP_LEAFS]); Mod_Q3BSP_LoadNodes(&header->lumps[Q3LUMP_NODES]); Mod_Q3BSP_LoadLightGrid(&header->lumps[Q3LUMP_LIGHTGRID]); Mod_Q3BSP_LoadPVS(&header->lumps[Q3LUMP_PVS]); loadmodel->brush.numsubmodels = loadmodel->brushq3.num_models; // the MakePortals code works fine on the q3bsp data as well if (mod_bsp_portalize.integer) Mod_Q1BSP_MakePortals(); // FIXME: shader alpha should replace r_wateralpha support in q3bsp loadmodel->brush.supportwateralpha = true; // make a single combined shadow mesh to allow optimized shadow volume creation Mod_Q1BSP_CreateShadowMesh(loadmodel); loadmodel->brush.num_leafs = 0; Mod_Q3BSP_RecursiveFindNumLeafs(loadmodel->brush.data_nodes); if (loadmodel->brush.numsubmodels) loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); mod = loadmodel; for (i = 0;i < loadmodel->brush.numsubmodels;i++) { if (i > 0) { char name[10]; // duplicate the basic information dpsnprintf(name, sizeof(name), "*%i", i); mod = Mod_FindName(name, loadmodel->name); // copy the base model to this one *mod = *loadmodel; // rename the clone back to its proper name strlcpy(mod->name, name, sizeof(mod->name)); mod->brush.parentmodel = loadmodel; // textures and memory belong to the main model mod->texturepool = NULL; mod->mempool = NULL; mod->brush.GetPVS = NULL; mod->brush.FatPVS = NULL; mod->brush.BoxTouchingPVS = NULL; mod->brush.BoxTouchingLeafPVS = NULL; mod->brush.BoxTouchingVisibleLeafs = NULL; mod->brush.FindBoxClusters = NULL; mod->brush.LightPoint = NULL; mod->brush.AmbientSoundLevelsForPoint = NULL; } mod->brush.submodel = i; if (loadmodel->brush.submodels) loadmodel->brush.submodels[i] = mod; // make the model surface list (used by shadowing/lighting) mod->firstmodelsurface = mod->brushq3.data_models[i].firstface; mod->nummodelsurfaces = mod->brushq3.data_models[i].numfaces; mod->firstmodelbrush = mod->brushq3.data_models[i].firstbrush; mod->nummodelbrushes = mod->brushq3.data_models[i].numbrushes; mod->sortedmodelsurfaces = (int *)Mem_Alloc(loadmodel->mempool, mod->nummodelsurfaces * sizeof(*mod->sortedmodelsurfaces)); Mod_MakeSortedSurfaces(mod); VectorCopy(mod->brushq3.data_models[i].mins, mod->normalmins); VectorCopy(mod->brushq3.data_models[i].maxs, mod->normalmaxs); // enlarge the bounding box to enclose all geometry of this model, // because q3map2 sometimes lies (mostly to affect the lightgrid), // which can in turn mess up the farclip (as well as culling when // outside the level - an unimportant concern) //printf("Editing model %d... BEFORE re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); for (j = 0;j < mod->nummodelsurfaces;j++) { const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; const float *v = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; int k; if (!surface->num_vertices) continue; for (k = 0;k < surface->num_vertices;k++, v += 3) { mod->normalmins[0] = min(mod->normalmins[0], v[0]); mod->normalmins[1] = min(mod->normalmins[1], v[1]); mod->normalmins[2] = min(mod->normalmins[2], v[2]); mod->normalmaxs[0] = max(mod->normalmaxs[0], v[0]); mod->normalmaxs[1] = max(mod->normalmaxs[1], v[1]); mod->normalmaxs[2] = max(mod->normalmaxs[2], v[2]); } } //printf("Editing model %d... AFTER re-bounding: %f %f %f - %f %f %f\n", i, mod->normalmins[0], mod->normalmins[1], mod->normalmins[2], mod->normalmaxs[0], mod->normalmaxs[1], mod->normalmaxs[2]); corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; mod->yawmins[0] = mod->yawmins[1] = -yawradius; mod->yawmins[2] = mod->normalmins[2]; mod->yawmaxs[2] = mod->normalmaxs[2]; mod->radius = modelradius; mod->radius2 = modelradius * modelradius; // this gets altered below if sky or water is used mod->DrawSky = NULL; mod->DrawAddWaterPlanes = NULL; for (j = 0;j < mod->nummodelsurfaces;j++) if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) break; if (j < mod->nummodelsurfaces) mod->DrawSky = R_Q1BSP_DrawSky; for (j = 0;j < mod->nummodelsurfaces;j++) if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) break; if (j < mod->nummodelsurfaces) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; Mod_MakeCollisionBIH(mod, false, &mod->collision_bih); Mod_MakeCollisionBIH(mod, true, &mod->render_bih); // generate VBOs and other shared data before cloning submodels if (i == 0) Mod_BuildVBOs(); } if (mod_q3bsp_sRGBlightmaps.integer) { if (vid_sRGB.integer && vid_sRGB_fallback.integer && !vid.sRGB3D) { // actually we do in sRGB fallback with sRGB lightmaps: Image_sRGBFloatFromLinear_Lightmap(Image_LinearFloatFromsRGBFloat(x)) // neutral point is at Image_sRGBFloatFromLinearFloat(0.5) // so we need to map Image_sRGBFloatFromLinearFloat(0.5) to 0.5 // factor is 0.5 / Image_sRGBFloatFromLinearFloat(0.5) //loadmodel->lightmapscale *= 0.679942f; // fixes neutral level } else // if this is NOT set, regular rendering looks right by this requirement anyway { /* // we want color 1 to do the same as without sRGB // so, we want to map 1 to Image_LinearFloatFromsRGBFloat(2) instead of to 2 loadmodel->lightmapscale *= 2.476923f; // fixes max level */ // neutral level 0.5 gets uploaded as sRGB and becomes Image_LinearFloatFromsRGBFloat(0.5) // we need to undo that loadmodel->lightmapscale *= 2.336f; // fixes neutral level } } Con_DPrintf("Stats for q3bsp model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); } void Mod_IBSP_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i = LittleLong(((int *)buffer)[1]); if (i == Q3BSPVERSION || i == Q3BSPVERSION_IG || i == Q3BSPVERSION_LIVE) Mod_Q3BSP_Load(mod,buffer, bufferend); else if (i == Q2BSPVERSION) Mod_Q2BSP_Load(mod,buffer, bufferend); else Host_Error("Mod_IBSP_Load: unknown/unsupported version %i", i); } void Mod_MAP_Load(dp_model_t *mod, void *buffer, void *bufferend) { Host_Error("Mod_MAP_Load: not yet implemented"); } typedef struct objvertex_s { int nextindex; int submodelindex; int textureindex; float v[3]; float vt[2]; float vn[3]; } objvertex_t; static unsigned char nobsp_pvs[1] = {1}; void Mod_OBJ_Load(dp_model_t *mod, void *buffer, void *bufferend) { const char *textbase = (char *)buffer, *text = textbase; char *s; char *argv[512]; char line[1024]; char materialname[MAX_QPATH]; int i, j, l, numvertices, firstvertex, firsttriangle, elementindex, vertexindex, surfacevertices, surfacetriangles, surfaceelements, submodelindex = 0; int index1, index2, index3; objvertex_t vfirst, vprev, vcurrent; int argc; int linelen; int numtriangles = 0; int maxtriangles = 0; objvertex_t *vertices = NULL; int linenumber = 0; int maxtextures = 0, numtextures = 0, textureindex = 0; int maxv = 0, numv = 1; int maxvt = 0, numvt = 1; int maxvn = 0, numvn = 1; char *texturenames = NULL; float dist, modelradius, modelyawradius, yawradius; float *obj_v = NULL; float *obj_vt = NULL; float *obj_vn = NULL; float mins[3]; float maxs[3]; float corner[3]; objvertex_t *thisvertex = NULL; int vertexhashindex; int *vertexhashtable = NULL; objvertex_t *vertexhashdata = NULL; objvertex_t *vdata = NULL; int vertexhashsize = 0; int vertexhashcount = 0; skinfile_t *skinfiles = NULL; unsigned char *data = NULL; int *submodelfirstsurface; msurface_t *tempsurface; msurface_t *tempsurfaces; memset(&vfirst, 0, sizeof(vfirst)); memset(&vprev, 0, sizeof(vprev)); memset(&vcurrent, 0, sizeof(vcurrent)); dpsnprintf(materialname, sizeof(materialname), "%s", loadmodel->name); loadmodel->modeldatatypestring = "OBJ"; loadmodel->type = mod_obj; loadmodel->soundfromcenter = true; loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->TraceLineAgainstSurfaces = Mod_CollisionBIH_TraceLine; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; loadmodel->brush.TraceLineOfSight = NULL; loadmodel->brush.SuperContentsFromNativeContents = NULL; loadmodel->brush.NativeContentsFromSuperContents = NULL; loadmodel->brush.GetPVS = NULL; loadmodel->brush.FatPVS = NULL; loadmodel->brush.BoxTouchingPVS = NULL; loadmodel->brush.BoxTouchingLeafPVS = NULL; loadmodel->brush.BoxTouchingVisibleLeafs = NULL; loadmodel->brush.FindBoxClusters = NULL; loadmodel->brush.LightPoint = NULL; loadmodel->brush.FindNonSolidLocation = NULL; loadmodel->brush.AmbientSoundLevelsForPoint = NULL; loadmodel->brush.RoundUpToHullSize = NULL; loadmodel->brush.PointInLeaf = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->GetLightInfo = R_Q1BSP_GetLightInfo; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; // make skinscenes for the skins (no groups) loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } VectorClear(mins); VectorClear(maxs); // we always have model 0, i.e. the first "submodel" loadmodel->brush.numsubmodels = 1; // parse the OBJ text now for(;;) { static char emptyarg[1] = ""; if (!*text) break; linenumber++; linelen = 0; for (linelen = 0;text[linelen] && text[linelen] != '\r' && text[linelen] != '\n';linelen++) line[linelen] = text[linelen]; line[linelen] = 0; for (argc = 0;argc < 4;argc++) argv[argc] = emptyarg; argc = 0; s = line; while (*s == ' ' || *s == '\t') s++; while (*s) { argv[argc++] = s; while (*s > ' ') s++; if (!*s) break; *s++ = 0; while (*s == ' ' || *s == '\t') s++; } text += linelen; if (*text == '\r') text++; if (*text == '\n') text++; if (!argc) continue; if (argv[0][0] == '#') continue; if (!strcmp(argv[0], "v")) { if (maxv <= numv) { maxv = max(maxv * 2, 1024); obj_v = (float *)Mem_Realloc(tempmempool, obj_v, maxv * sizeof(float[3])); } if(mod_obj_orientation.integer) { obj_v[numv*3+0] = atof(argv[1]); obj_v[numv*3+2] = atof(argv[2]); obj_v[numv*3+1] = atof(argv[3]); } else { obj_v[numv*3+0] = atof(argv[1]); obj_v[numv*3+1] = atof(argv[2]); obj_v[numv*3+2] = atof(argv[3]); } numv++; } else if (!strcmp(argv[0], "vt")) { if (maxvt <= numvt) { maxvt = max(maxvt * 2, 1024); obj_vt = (float *)Mem_Realloc(tempmempool, obj_vt, maxvt * sizeof(float[2])); } obj_vt[numvt*2+0] = atof(argv[1]); obj_vt[numvt*2+1] = 1-atof(argv[2]); numvt++; } else if (!strcmp(argv[0], "vn")) { if (maxvn <= numvn) { maxvn = max(maxvn * 2, 1024); obj_vn = (float *)Mem_Realloc(tempmempool, obj_vn, maxvn * sizeof(float[3])); } if(mod_obj_orientation.integer) { obj_vn[numvn*3+0] = atof(argv[1]); obj_vn[numvn*3+2] = atof(argv[2]); obj_vn[numvn*3+1] = atof(argv[3]); } else { obj_vn[numvn*3+0] = atof(argv[1]); obj_vn[numvn*3+1] = atof(argv[2]); obj_vn[numvn*3+2] = atof(argv[3]); } numvn++; } else if (!strcmp(argv[0], "f")) { if (!numtextures) { if (maxtextures <= numtextures) { maxtextures = max(maxtextures * 2, 256); texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); } textureindex = numtextures++; strlcpy(texturenames + textureindex*MAX_QPATH, loadmodel->name, MAX_QPATH); } for (j = 1;j < argc;j++) { index1 = atoi(argv[j]); while(argv[j][0] && argv[j][0] != '/') argv[j]++; if (argv[j][0]) argv[j]++; index2 = atoi(argv[j]); while(argv[j][0] && argv[j][0] != '/') argv[j]++; if (argv[j][0]) argv[j]++; index3 = atoi(argv[j]); // negative refers to a recent vertex // zero means not specified // positive means an absolute vertex index if (index1 < 0) index1 = numv - index1; if (index2 < 0) index2 = numvt - index2; if (index3 < 0) index3 = numvn - index3; vcurrent.nextindex = -1; vcurrent.textureindex = textureindex; vcurrent.submodelindex = submodelindex; if (obj_v && index1 >= 0 && index1 < numv) VectorCopy(obj_v + 3*index1, vcurrent.v); if (obj_vt && index2 >= 0 && index2 < numvt) Vector2Copy(obj_vt + 2*index2, vcurrent.vt); if (obj_vn && index3 >= 0 && index3 < numvn) VectorCopy(obj_vn + 3*index3, vcurrent.vn); if (numtriangles == 0) { VectorCopy(vcurrent.v, mins); VectorCopy(vcurrent.v, maxs); } else { mins[0] = min(mins[0], vcurrent.v[0]); mins[1] = min(mins[1], vcurrent.v[1]); mins[2] = min(mins[2], vcurrent.v[2]); maxs[0] = max(maxs[0], vcurrent.v[0]); maxs[1] = max(maxs[1], vcurrent.v[1]); maxs[2] = max(maxs[2], vcurrent.v[2]); } if (j == 1) vfirst = vcurrent; else if (j >= 3) { if (maxtriangles <= numtriangles) { maxtriangles = max(maxtriangles * 2, 32768); vertices = (objvertex_t*)Mem_Realloc(loadmodel->mempool, vertices, maxtriangles * sizeof(objvertex_t[3])); } if(mod_obj_orientation.integer) { vertices[numtriangles*3+0] = vfirst; vertices[numtriangles*3+1] = vprev; vertices[numtriangles*3+2] = vcurrent; } else { vertices[numtriangles*3+0] = vfirst; vertices[numtriangles*3+2] = vprev; vertices[numtriangles*3+1] = vcurrent; } numtriangles++; } vprev = vcurrent; } } else if (!strcmp(argv[0], "o") || !strcmp(argv[0], "g")) { submodelindex = atof(argv[1]); loadmodel->brush.numsubmodels = max(submodelindex + 1, loadmodel->brush.numsubmodels); } else if (!strcmp(argv[0], "usemtl")) { for (i = 0;i < numtextures;i++) if (!strcmp(texturenames+i*MAX_QPATH, argv[1])) break; if (i < numtextures) textureindex = i; else { if (maxtextures <= numtextures) { maxtextures = max(maxtextures * 2, 256); texturenames = (char *)Mem_Realloc(loadmodel->mempool, texturenames, maxtextures * MAX_QPATH); } textureindex = numtextures++; strlcpy(texturenames + textureindex*MAX_QPATH, argv[1], MAX_QPATH); } } } // now that we have the OBJ data loaded as-is, we can convert it // copy the model bounds, then enlarge the yaw and rotated bounds according to radius VectorCopy(mins, loadmodel->normalmins); VectorCopy(maxs, loadmodel->normalmaxs); dist = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); modelyawradius = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); modelyawradius = dist*dist+modelyawradius*modelyawradius; modelradius = max(fabs(loadmodel->normalmins[2]), fabs(loadmodel->normalmaxs[2])); modelradius = modelyawradius + modelradius * modelradius; modelyawradius = sqrt(modelyawradius); modelradius = sqrt(modelradius); loadmodel->yawmins[0] = loadmodel->yawmins[1] = -modelyawradius; loadmodel->yawmins[2] = loadmodel->normalmins[2]; loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelyawradius; loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -modelradius; loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = modelradius; loadmodel->radius = modelradius; loadmodel->radius2 = modelradius * modelradius; // allocate storage for triangles loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, numtriangles * sizeof(int[3])); // allocate vertex hash structures to build an optimal vertex subset vertexhashsize = numtriangles*2; vertexhashtable = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int) * vertexhashsize); memset(vertexhashtable, 0xFF, sizeof(int) * vertexhashsize); vertexhashdata = (objvertex_t *)Mem_Alloc(loadmodel->mempool, sizeof(*vertexhashdata) * numtriangles*3); vertexhashcount = 0; // gather surface stats for assigning vertex/triangle ranges firstvertex = 0; firsttriangle = 0; elementindex = 0; loadmodel->num_surfaces = 0; // allocate storage for the worst case number of surfaces, later we resize tempsurfaces = (msurface_t *)Mem_Alloc(loadmodel->mempool, numtextures * loadmodel->brush.numsubmodels * sizeof(msurface_t)); submodelfirstsurface = (int *)Mem_Alloc(loadmodel->mempool, (loadmodel->brush.numsubmodels+1) * sizeof(int)); tempsurface = tempsurfaces; for (submodelindex = 0;submodelindex < loadmodel->brush.numsubmodels;submodelindex++) { submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; for (textureindex = 0;textureindex < numtextures;textureindex++) { for (vertexindex = 0;vertexindex < numtriangles*3;vertexindex++) { thisvertex = vertices + vertexindex; if (thisvertex->submodelindex == submodelindex && thisvertex->textureindex == textureindex) break; } // skip the surface creation if there are no triangles for it if (vertexindex == numtriangles*3) continue; // create a surface for these vertices surfacevertices = 0; surfaceelements = 0; // we hack in a texture index in the surface to be fixed up later... tempsurface->texture = (texture_t *)((size_t)textureindex); // calculate bounds as we go VectorCopy(thisvertex->v, tempsurface->mins); VectorCopy(thisvertex->v, tempsurface->maxs); for (;vertexindex < numtriangles*3;vertexindex++) { thisvertex = vertices + vertexindex; if (thisvertex->submodelindex != submodelindex) continue; if (thisvertex->textureindex != textureindex) continue; // add vertex to surface bounds tempsurface->mins[0] = min(tempsurface->mins[0], thisvertex->v[0]); tempsurface->mins[1] = min(tempsurface->mins[1], thisvertex->v[1]); tempsurface->mins[2] = min(tempsurface->mins[2], thisvertex->v[2]); tempsurface->maxs[0] = max(tempsurface->maxs[0], thisvertex->v[0]); tempsurface->maxs[1] = max(tempsurface->maxs[1], thisvertex->v[1]); tempsurface->maxs[2] = max(tempsurface->maxs[2], thisvertex->v[2]); // add the vertex if it is not found in the merged set, and // get its index (triangle element) for the surface vertexhashindex = (unsigned int)(thisvertex->v[0] * 3571 + thisvertex->v[0] * 1777 + thisvertex->v[0] * 457) % (unsigned int)vertexhashsize; for (i = vertexhashtable[vertexhashindex];i >= 0;i = vertexhashdata[i].nextindex) { vdata = vertexhashdata + i; if (vdata->submodelindex == thisvertex->submodelindex && vdata->textureindex == thisvertex->textureindex && VectorCompare(thisvertex->v, vdata->v) && VectorCompare(thisvertex->vn, vdata->vn) && Vector2Compare(thisvertex->vt, vdata->vt)) break; } if (i < 0) { i = vertexhashcount++; vdata = vertexhashdata + i; *vdata = *thisvertex; vdata->nextindex = vertexhashtable[vertexhashindex]; vertexhashtable[vertexhashindex] = i; surfacevertices++; } loadmodel->surfmesh.data_element3i[elementindex++] = i; surfaceelements++; } surfacetriangles = surfaceelements / 3; tempsurface->num_vertices = surfacevertices; tempsurface->num_triangles = surfacetriangles; tempsurface->num_firstvertex = firstvertex; tempsurface->num_firsttriangle = firsttriangle; firstvertex += tempsurface->num_vertices; firsttriangle += tempsurface->num_triangles; tempsurface++; loadmodel->num_surfaces++; } } submodelfirstsurface[submodelindex] = loadmodel->num_surfaces; numvertices = firstvertex; loadmodel->data_surfaces = (msurface_t *)Mem_Realloc(loadmodel->mempool, tempsurfaces, loadmodel->num_surfaces * sizeof(msurface_t)); tempsurfaces = NULL; // allocate storage for final mesh data loadmodel->num_textures = numtextures * loadmodel->numskins; loadmodel->num_texturesperskin = numtextures; data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + numtriangles * sizeof(int[3]) + (numvertices <= 65536 ? numtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? numtriangles * sizeof(int[3]) : 0) + numvertices * sizeof(float[14]) + loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); loadmodel->brush.submodels = (dp_model_t **)data;data += loadmodel->brush.numsubmodels * sizeof(dp_model_t *); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.num_vertices = numvertices; loadmodel->surfmesh.num_triangles = numtriangles; if (r_enableshadowvolumes.integer) loadmodel->surfmesh.data_neighbor3i = (int *)data;data += numtriangles * sizeof(int[3]); loadmodel->surfmesh.data_vertex3f = (float *)data;data += numvertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)data;data += numvertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)data;data += numvertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)data;data += numvertices * sizeof(float[3]); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += numvertices * sizeof(float[2]); if (loadmodel->surfmesh.num_vertices <= 65536) loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) { VectorCopy(vertexhashdata[j].v, loadmodel->surfmesh.data_vertex3f + 3*j); VectorCopy(vertexhashdata[j].vn, loadmodel->surfmesh.data_normal3f + 3*j); Vector2Copy(vertexhashdata[j].vt, loadmodel->surfmesh.data_texcoordtexture2f + 2*j); } // load the textures for (textureindex = 0;textureindex < numtextures;textureindex++) Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + textureindex, skinfiles, texturenames + textureindex*MAX_QPATH, texturenames + textureindex*MAX_QPATH); Mod_FreeSkinFiles(skinfiles); // set the surface textures to their real values now that we loaded them... for (i = 0;i < loadmodel->num_surfaces;i++) loadmodel->data_surfaces[i].texture = loadmodel->data_textures + (size_t)loadmodel->data_surfaces[i].texture; // free data Mem_Free(vertices); Mem_Free(texturenames); Mem_Free(obj_v); Mem_Free(obj_vt); Mem_Free(obj_vn); Mem_Free(vertexhashtable); Mem_Free(vertexhashdata); // make a single combined shadow mesh to allow optimized shadow volume creation Mod_Q1BSP_CreateShadowMesh(loadmodel); // compute all the mesh information that was not loaded from the file if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); // generate normals if the file did not have them if (!VectorLength2(loadmodel->surfmesh.data_normal3f)) Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); // if this is a worldmodel and has no BSP tree, create a fake one for the purpose loadmodel->brush.num_visleafs = 1; loadmodel->brush.num_leafs = 1; loadmodel->brush.num_nodes = 0; loadmodel->brush.num_leafsurfaces = loadmodel->num_surfaces; loadmodel->brush.data_leafs = (mleaf_t *)Mem_Alloc(loadmodel->mempool, loadmodel->brush.num_leafs * sizeof(mleaf_t)); loadmodel->brush.data_nodes = (mnode_t *)loadmodel->brush.data_leafs; loadmodel->brush.num_pvsclusters = 1; loadmodel->brush.num_pvsclusterbytes = 1; loadmodel->brush.data_pvsclusters = nobsp_pvs; //if (loadmodel->num_nodes) loadmodel->data_nodes = (mnode_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_nodes * sizeof(mnode_t)); //loadmodel->data_leafsurfaces = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->num_leafsurfaces * sizeof(int)); loadmodel->brush.data_leafsurfaces = loadmodel->sortedmodelsurfaces; VectorCopy(loadmodel->normalmins, loadmodel->brush.data_leafs->mins); VectorCopy(loadmodel->normalmaxs, loadmodel->brush.data_leafs->maxs); loadmodel->brush.data_leafs->combinedsupercontents = 0; // FIXME? loadmodel->brush.data_leafs->clusterindex = 0; loadmodel->brush.data_leafs->areaindex = 0; loadmodel->brush.data_leafs->numleafsurfaces = loadmodel->brush.num_leafsurfaces; loadmodel->brush.data_leafs->firstleafsurface = loadmodel->brush.data_leafsurfaces; loadmodel->brush.data_leafs->numleafbrushes = 0; loadmodel->brush.data_leafs->firstleafbrush = NULL; loadmodel->brush.supportwateralpha = true; if (loadmodel->brush.numsubmodels) loadmodel->brush.submodels = (dp_model_t **)Mem_Alloc(loadmodel->mempool, loadmodel->brush.numsubmodels * sizeof(dp_model_t *)); mod = loadmodel; for (i = 0;i < loadmodel->brush.numsubmodels;i++) { if (i > 0) { char name[10]; // duplicate the basic information dpsnprintf(name, sizeof(name), "*%i", i); mod = Mod_FindName(name, loadmodel->name); // copy the base model to this one *mod = *loadmodel; // rename the clone back to its proper name strlcpy(mod->name, name, sizeof(mod->name)); mod->brush.parentmodel = loadmodel; // textures and memory belong to the main model mod->texturepool = NULL; mod->mempool = NULL; mod->brush.GetPVS = NULL; mod->brush.FatPVS = NULL; mod->brush.BoxTouchingPVS = NULL; mod->brush.BoxTouchingLeafPVS = NULL; mod->brush.BoxTouchingVisibleLeafs = NULL; mod->brush.FindBoxClusters = NULL; mod->brush.LightPoint = NULL; mod->brush.AmbientSoundLevelsForPoint = NULL; } mod->brush.submodel = i; if (loadmodel->brush.submodels) loadmodel->brush.submodels[i] = mod; // make the model surface list (used by shadowing/lighting) mod->firstmodelsurface = submodelfirstsurface[i]; mod->nummodelsurfaces = submodelfirstsurface[i+1] - submodelfirstsurface[i]; mod->firstmodelbrush = 0; mod->nummodelbrushes = 0; mod->sortedmodelsurfaces = loadmodel->sortedmodelsurfaces + mod->firstmodelsurface; Mod_MakeSortedSurfaces(mod); VectorClear(mod->normalmins); VectorClear(mod->normalmaxs); l = false; for (j = 0;j < mod->nummodelsurfaces;j++) { const msurface_t *surface = mod->data_surfaces + j + mod->firstmodelsurface; const float *v3f = mod->surfmesh.data_vertex3f + 3 * surface->num_firstvertex; int k; if (!surface->num_vertices) continue; if (!l) { l = true; VectorCopy(v3f, mod->normalmins); VectorCopy(v3f, mod->normalmaxs); } for (k = 0;k < surface->num_vertices;k++, v3f += 3) { mod->normalmins[0] = min(mod->normalmins[0], v3f[0]); mod->normalmins[1] = min(mod->normalmins[1], v3f[1]); mod->normalmins[2] = min(mod->normalmins[2], v3f[2]); mod->normalmaxs[0] = max(mod->normalmaxs[0], v3f[0]); mod->normalmaxs[1] = max(mod->normalmaxs[1], v3f[1]); mod->normalmaxs[2] = max(mod->normalmaxs[2], v3f[2]); } } corner[0] = max(fabs(mod->normalmins[0]), fabs(mod->normalmaxs[0])); corner[1] = max(fabs(mod->normalmins[1]), fabs(mod->normalmaxs[1])); corner[2] = max(fabs(mod->normalmins[2]), fabs(mod->normalmaxs[2])); modelradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]+corner[2]*corner[2]); yawradius = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); mod->rotatedmins[0] = mod->rotatedmins[1] = mod->rotatedmins[2] = -modelradius; mod->rotatedmaxs[0] = mod->rotatedmaxs[1] = mod->rotatedmaxs[2] = modelradius; mod->yawmaxs[0] = mod->yawmaxs[1] = yawradius; mod->yawmins[0] = mod->yawmins[1] = -yawradius; mod->yawmins[2] = mod->normalmins[2]; mod->yawmaxs[2] = mod->normalmaxs[2]; mod->radius = modelradius; mod->radius2 = modelradius * modelradius; // this gets altered below if sky or water is used mod->DrawSky = NULL; mod->DrawAddWaterPlanes = NULL; for (j = 0;j < mod->nummodelsurfaces;j++) if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & MATERIALFLAG_SKY) break; if (j < mod->nummodelsurfaces) mod->DrawSky = R_Q1BSP_DrawSky; for (j = 0;j < mod->nummodelsurfaces;j++) if (mod->data_surfaces[j + mod->firstmodelsurface].texture->basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) break; if (j < mod->nummodelsurfaces) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; Mod_MakeCollisionBIH(mod, true, &mod->collision_bih); mod->render_bih = mod->collision_bih; // generate VBOs and other shared data before cloning submodels if (i == 0) Mod_BuildVBOs(); } mod = loadmodel; Mem_Free(submodelfirstsurface); Con_DPrintf("Stats for obj model \"%s\": %i faces, %i nodes, %i leafs, %i clusters, %i clusterportals, mesh: %i vertices, %i triangles, %i surfaces\n", loadmodel->name, loadmodel->num_surfaces, loadmodel->brush.num_nodes, loadmodel->brush.num_leafs, mod->brush.num_pvsclusters, loadmodel->brush.num_portals, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->num_surfaces); } darkplaces/gl_draw.c0000664000175000017500000020304213067716220013747 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "image.h" #include "wad.h" #include "cl_video.h" #include "cl_dyntexture.h" #include "ft2.h" #include "ft2_fontdefs.h" dp_fonts_t dp_fonts; static mempool_t *fonts_mempool = NULL; cvar_t r_textshadow = {CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"}; cvar_t r_textbrightness = {CVAR_SAVE, "r_textbrightness", "0", "additional brightness for text color codes (0 keeps colors as is, 1 makes them all white)"}; cvar_t r_textcontrast = {CVAR_SAVE, "r_textcontrast", "1", "additional contrast for text color codes (1 keeps colors as is, 0 makes them all black)"}; cvar_t r_font_postprocess_blur = {CVAR_SAVE, "r_font_postprocess_blur", "0", "font blur amount"}; cvar_t r_font_postprocess_outline = {CVAR_SAVE, "r_font_postprocess_outline", "0", "font outline amount"}; cvar_t r_font_postprocess_shadow_x = {CVAR_SAVE, "r_font_postprocess_shadow_x", "0", "font shadow X shift amount, applied during outlining"}; cvar_t r_font_postprocess_shadow_y = {CVAR_SAVE, "r_font_postprocess_shadow_y", "0", "font shadow Y shift amount, applied during outlining"}; cvar_t r_font_postprocess_shadow_z = {CVAR_SAVE, "r_font_postprocess_shadow_z", "0", "font shadow Z shift amount, applied during blurring"}; cvar_t r_font_hinting = {CVAR_SAVE, "r_font_hinting", "3", "0 = no hinting, 1 = light autohinting, 2 = full autohinting, 3 = full hinting"}; cvar_t r_font_antialias = {CVAR_SAVE, "r_font_antialias", "1", "0 = monochrome, 1 = grey" /* , 2 = rgb, 3 = bgr" */}; cvar_t r_nearest_2d = {CVAR_SAVE, "r_nearest_2d", "0", "use nearest filtering on all 2d textures (including conchars)"}; cvar_t r_nearest_conchars = {CVAR_SAVE, "r_nearest_conchars", "0", "use nearest filtering on conchars texture"}; extern cvar_t v_glslgamma; //============================================================================= /* Support Routines */ #define FONT_FILESIZE 13468 static cachepic_t *cachepichash[CACHEPICHASHSIZE]; static cachepic_t cachepics[MAX_CACHED_PICS]; static int numcachepics; rtexturepool_t *drawtexturepool; static const unsigned char concharimage[FONT_FILESIZE] = { #include "lhfont.h" }; static rtexture_t *draw_generateconchars(void) { int i; unsigned char *data; double random; rtexture_t *tex; data = LoadTGA_BGRA (concharimage, FONT_FILESIZE, NULL); // Gold numbers for (i = 0;i < 8192;i++) { random = lhrandom (0.0,1.0); data[i*4+3] = data[i*4+0]; data[i*4+2] = 83 + (unsigned char)(random * 64); data[i*4+1] = 71 + (unsigned char)(random * 32); data[i*4+0] = 23 + (unsigned char)(random * 16); } // White chars for (i = 8192;i < 32768;i++) { random = lhrandom (0.0,1.0); data[i*4+3] = data[i*4+0]; data[i*4+2] = 95 + (unsigned char)(random * 64); data[i*4+1] = 95 + (unsigned char)(random * 64); data[i*4+0] = 95 + (unsigned char)(random * 64); } // Gold numbers for (i = 32768;i < 40960;i++) { random = lhrandom (0.0,1.0); data[i*4+3] = data[i*4+0]; data[i*4+2] = 83 + (unsigned char)(random * 64); data[i*4+1] = 71 + (unsigned char)(random * 32); data[i*4+0] = 23 + (unsigned char)(random * 16); } // Red chars for (i = 40960;i < 65536;i++) { random = lhrandom (0.0,1.0); data[i*4+3] = data[i*4+0]; data[i*4+2] = 96 + (unsigned char)(random * 64); data[i*4+1] = 43 + (unsigned char)(random * 32); data[i*4+0] = 27 + (unsigned char)(random * 32); } #if 0 Image_WriteTGABGRA ("gfx/generated_conchars.tga", 256, 256, data); #endif tex = R_LoadTexture2D(drawtexturepool, "conchars", 256, 256, data, TEXTYPE_BGRA, TEXF_ALPHA | (r_nearest_conchars.integer ? TEXF_FORCENEAREST : 0), -1, NULL); Mem_Free(data); return tex; } static rtexture_t *draw_generateditherpattern(void) { int x, y; unsigned char pixels[8][8]; for (y = 0;y < 8;y++) for (x = 0;x < 8;x++) pixels[y][x] = ((x^y) & 4) ? 254 : 0; return R_LoadTexture2D(drawtexturepool, "ditherpattern", 8, 8, pixels[0], TEXTYPE_PALETTE, TEXF_FORCENEAREST, -1, palette_bgra_transparent); } typedef struct embeddedpic_s { const char *name; int width; int height; const char *pixels; } embeddedpic_t; static const embeddedpic_t embeddedpics[] = { { "gfx/prydoncursor001", 16, 16, "477777774......." "77.....6........" "7.....6........." "7....6.........." "7.....6........." "7..6...6........" "7.6.6...6......." "76...6...6......" "4.....6.6......." ".......6........" "................" "................" "................" "................" "................" "................" }, { "ui/mousepointer", 16, 16, "477777774......." "77.....6........" "7.....6........." "7....6.........." "7.....6........." "7..6...6........" "7.6.6...6......." "76...6...6......" "4.....6.6......." ".......6........" "................" "................" "................" "................" "................" "................" }, { "gfx/crosshair1", 16, 16, "................" "................" "................" "...33......33..." "...355....553..." "....577..775...." ".....77..77....." "................" "................" ".....77..77....." "....577..775...." "...355....553..." "...33......33..." "................" "................" "................" }, { "gfx/crosshair2", 16, 16, "................" "................" "................" "...3........3..." "....5......5...." ".....7....7....." "......7..7......" "................" "................" "......7..7......" ".....7....7....." "....5......5...." "...3........3..." "................" "................" "................" }, { "gfx/crosshair3", 16, 16, "................" ".......77......." ".......77......." "................" "................" ".......44......." ".......44......." ".77..44..44..77." ".77..44..44..77." ".......44......." ".......44......." "................" "................" ".......77......." ".......77......." "................" }, { "gfx/crosshair4", 16, 16, "................" "................" "................" "................" "................" "................" "................" "................" "........7777777." "........752....." "........72......" "........7......." "........7......." "........7......." "........7......." "................" }, { "gfx/crosshair5", 8, 8, "........" "........" "....7..." "........" "..7.7.7." "........" "....7..." "........" }, { "gfx/crosshair6", 2, 2, "77" "77" }, { "gfx/crosshair7", 16, 16, "................" ".3............3." "..5...2332...5.." "...7.3....3.7..." "....7......7...." "...3.7....7.3..." "..2...7..7...2.." "..3..........3.." "..3..........3.." "..2...7..7...2.." "...3.7....7.3..." "....7......7...." "...7.3....3.7..." "..5...2332...5.." ".3............3." "................" }, {NULL, 0, 0, NULL} }; static rtexture_t *draw_generatepic(const char *name, qboolean quiet) { const embeddedpic_t *p; for (p = embeddedpics;p->name;p++) if (!strcmp(name, p->name)) return R_LoadTexture2D(drawtexturepool, p->name, p->width, p->height, (const unsigned char *)p->pixels, TEXTYPE_PALETTE, TEXF_ALPHA, -1, palette_bgra_embeddedpic); if (!strcmp(name, "gfx/conchars")) return draw_generateconchars(); if (!strcmp(name, "gfx/colorcontrol/ditherpattern")) return draw_generateditherpattern(); if (!quiet) Con_DPrintf("Draw_CachePic: failed to load %s\n", name); return r_texture_notexture; } int draw_frame = 1; /* ================ Draw_CachePic ================ */ // FIXME: move this to client somehow cachepic_t *Draw_CachePic_Flags(const char *path, unsigned int cachepicflags) { int crc, hashkey; unsigned char *pixels = NULL; cachepic_t *pic; fs_offset_t lmpsize; unsigned char *lmpdata; char lmpname[MAX_QPATH]; int texflags; int j; qboolean ddshasalpha; float ddsavgcolor[4]; qboolean loaded = false; char vabuf[1024]; texflags = TEXF_ALPHA; if (!(cachepicflags & CACHEPICFLAG_NOCLAMP)) texflags |= TEXF_CLAMP; if (cachepicflags & CACHEPICFLAG_MIPMAP) texflags |= TEXF_MIPMAP; if (!(cachepicflags & CACHEPICFLAG_NOCOMPRESSION) && gl_texturecompression_2d.integer && gl_texturecompression.integer) texflags |= TEXF_COMPRESS; if ((cachepicflags & CACHEPICFLAG_NEAREST) || r_nearest_2d.integer) texflags |= TEXF_FORCENEAREST; // check whether the picture has already been cached crc = CRC_Block((unsigned char *)path, strlen(path)); hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; for (pic = cachepichash[hashkey];pic;pic = pic->chain) { if (!strcmp (path, pic->name)) { // if it was created (or replaced) by Draw_NewPic, just return it if(pic->flags & CACHEPICFLAG_NEWPIC) return pic; if (!((pic->texflags ^ texflags) & ~(TEXF_COMPRESS | TEXF_MIPMAP))) // ignore TEXF_COMPRESS when comparing, because fallback pics remove the flag, and ignore TEXF_MIPMAP because QC specifies that { if(!(cachepicflags & CACHEPICFLAG_NOTPERSISTENT)) { if(pic->tex) pic->autoload = false; // persist it else goto reload; // load it below, and then persist } return pic; } } } if (numcachepics == MAX_CACHED_PICS) { Con_Printf ("Draw_CachePic: numcachepics == MAX_CACHED_PICS\n"); // FIXME: support NULL in callers? return cachepics; // return the first one } pic = cachepics + (numcachepics++); memset(pic, 0, sizeof(*pic)); strlcpy (pic->name, path, sizeof(pic->name)); // link into list pic->chain = cachepichash[hashkey]; cachepichash[hashkey] = pic; reload: // TODO why does this crash? if(pic->allow_free_tex && pic->tex) R_PurgeTexture(pic->tex); // check whether it is an dynamic texture (if so, we can directly use its texture handler) pic->flags = cachepicflags; pic->tex = CL_GetDynTexture( path ); // if so, set the width/height, too if( pic->tex ) { pic->allow_free_tex = false; pic->width = R_TextureWidth(pic->tex); pic->height = R_TextureHeight(pic->tex); // we're done now (early-out) return pic; } pic->allow_free_tex = true; pic->hasalpha = true; // assume alpha unless we know it has none pic->texflags = texflags; pic->autoload = (cachepicflags & CACHEPICFLAG_NOTPERSISTENT); pic->lastusedframe = draw_frame; // load a high quality image from disk if possible if (!loaded && r_texture_dds_load.integer != 0 && (pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false))) { // note this loads even if autoload is true, otherwise we can't get the width/height loaded = true; pic->hasalpha = ddshasalpha; pic->width = R_TextureWidth(pic->tex); pic->height = R_TextureHeight(pic->tex); } if (!loaded && ((pixels = loadimagepixelsbgra(pic->name, false, true, false, NULL)) || (!strncmp(pic->name, "gfx/", 4) && (pixels = loadimagepixelsbgra(pic->name+4, false, true, false, NULL))))) { loaded = true; pic->hasalpha = false; if (pic->texflags & TEXF_ALPHA) { for (j = 3;j < image_width * image_height * 4;j += 4) { if (pixels[j] < 255) { pic->hasalpha = true; break; } } } pic->width = image_width; pic->height = image_height; if (!pic->autoload) { pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, image_width, image_height, pixels, vid.sRGB2D ? TEXTYPE_SRGB_BGRA : TEXTYPE_BGRA, pic->texflags & (pic->hasalpha ? ~0 : ~TEXF_ALPHA), -1, NULL); #ifndef USE_GLES2 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); #endif } } if (!loaded) { pic->autoload = false; // never compress the fallback images pic->texflags &= ~TEXF_COMPRESS; } // now read the low quality version (wad or lmp file), and take the pic // size from that even if we don't upload the texture, this way the pics // show up the right size in the menu even if they were replaced with // higher or lower resolution versions dpsnprintf(lmpname, sizeof(lmpname), "%s.lmp", pic->name); if ((!strncmp(pic->name, "gfx/", 4) || (gamemode == GAME_BLOODOMNICIDE && !strncmp(pic->name, "locale/", 6))) && (lmpdata = FS_LoadFile(lmpname, tempmempool, false, &lmpsize))) { if (developer_loading.integer) Con_Printf("loading lump \"%s\"\n", pic->name); if (lmpsize >= 9) { pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; // if no high quality replacement image was found, upload the original low quality texture if (!loaded) { loaded = true; pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); } } Mem_Free(lmpdata); } else if ((lmpdata = W_GetLumpName (pic->name + 4))) { if (developer_loading.integer) Con_Printf("loading gfx.wad lump \"%s\"\n", pic->name + 4); if (!strcmp(pic->name, "gfx/conchars")) { // conchars is a raw image and with color 0 as transparent instead of 255 pic->width = 128; pic->height = 128; // if no high quality replacement image was found, upload the original low quality texture if (!loaded) { loaded = true; pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, 128, 128, lmpdata, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_font); } } else { pic->width = lmpdata[0] + lmpdata[1] * 256 + lmpdata[2] * 65536 + lmpdata[3] * 16777216; pic->height = lmpdata[4] + lmpdata[5] * 256 + lmpdata[6] * 65536 + lmpdata[7] * 16777216; // if no high quality replacement image was found, upload the original low quality texture if (!loaded) { loaded = true; pic->tex = R_LoadTexture2D(drawtexturepool, pic->name, pic->width, pic->height, lmpdata + 8, vid.sRGB2D != 0 ? TEXTYPE_SRGB_PALETTE : TEXTYPE_PALETTE, pic->texflags, -1, palette_bgra_transparent); } } } if (pixels) { Mem_Free(pixels); pixels = NULL; } if (!loaded) { // if it's not found on disk, generate an image pic->tex = draw_generatepic(pic->name, (cachepicflags & CACHEPICFLAG_QUIET) != 0); pic->width = R_TextureWidth(pic->tex); pic->height = R_TextureHeight(pic->tex); pic->allow_free_tex = (pic->tex != r_texture_notexture); } return pic; } cachepic_t *Draw_CachePic (const char *path) { return Draw_CachePic_Flags (path, 0); // default to persistent! } rtexture_t *Draw_GetPicTexture(cachepic_t *pic) { char vabuf[1024]; if (pic->autoload && !pic->tex) { if (pic->tex == NULL && r_texture_dds_load.integer != 0) { qboolean ddshasalpha; float ddsavgcolor[4]; pic->tex = R_LoadTextureDDSFile(drawtexturepool, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), vid.sRGB2D, pic->texflags, &ddshasalpha, ddsavgcolor, 0, false); } if (pic->tex == NULL) { pic->tex = loadtextureimage(drawtexturepool, pic->name, false, pic->texflags, true, vid.sRGB2D); #ifndef USE_GLES2 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); #endif } if (pic->tex == NULL && !strncmp(pic->name, "gfx/", 4)) { pic->tex = loadtextureimage(drawtexturepool, pic->name+4, false, pic->texflags, true, vid.sRGB2D); #ifndef USE_GLES2 if (r_texture_dds_save.integer && qglGetCompressedTexImageARB && pic->tex) R_SaveTextureDDSFile(pic->tex, va(vabuf, sizeof(vabuf), "dds/%s.dds", pic->name), r_texture_dds_save.integer < 2, pic->hasalpha); #endif } if (pic->tex == NULL) pic->tex = draw_generatepic(pic->name, true); } pic->lastusedframe = draw_frame; return pic->tex; } void Draw_Frame(void) { int i; cachepic_t *pic; static double nextpurgetime; if (nextpurgetime > realtime) return; nextpurgetime = realtime + 0.05; for (i = 0, pic = cachepics;i < numcachepics;i++, pic++) { if (pic->autoload && pic->tex && pic->lastusedframe < draw_frame) { R_FreeTexture(pic->tex); pic->tex = NULL; } } draw_frame++; } cachepic_t *Draw_NewPic(const char *picname, int width, int height, int alpha, unsigned char *pixels_bgra) { int crc, hashkey; cachepic_t *pic; crc = CRC_Block((unsigned char *)picname, strlen(picname)); hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; for (pic = cachepichash[hashkey];pic;pic = pic->chain) if (!strcmp (picname, pic->name)) break; if (pic) { if (pic->flags == CACHEPICFLAG_NEWPIC && pic->tex && pic->width == width && pic->height == height) { R_UpdateTexture(pic->tex, pixels_bgra, 0, 0, 0, width, height, 1); return pic; } } else { if (numcachepics == MAX_CACHED_PICS) { Con_Printf ("Draw_NewPic: numcachepics == MAX_CACHED_PICS\n"); // FIXME: support NULL in callers? return cachepics; // return the first one } pic = cachepics + (numcachepics++); memset(pic, 0, sizeof(*pic)); strlcpy (pic->name, picname, sizeof(pic->name)); // link into list pic->chain = cachepichash[hashkey]; cachepichash[hashkey] = pic; } pic->flags = CACHEPICFLAG_NEWPIC; // disable texflags checks in Draw_CachePic pic->width = width; pic->height = height; if (pic->allow_free_tex && pic->tex) R_FreeTexture(pic->tex); pic->tex = R_LoadTexture2D(drawtexturepool, picname, width, height, pixels_bgra, TEXTYPE_BGRA, (alpha ? TEXF_ALPHA : 0), -1, NULL); return pic; } void Draw_FreePic(const char *picname) { int crc; int hashkey; cachepic_t *pic; // this doesn't really free the pic, but does free it's texture crc = CRC_Block((unsigned char *)picname, strlen(picname)); hashkey = ((crc >> 8) ^ crc) % CACHEPICHASHSIZE; for (pic = cachepichash[hashkey];pic;pic = pic->chain) { if (!strcmp (picname, pic->name) && pic->tex) { R_FreeTexture(pic->tex); pic->tex = NULL; pic->width = 0; pic->height = 0; return; } } } static float snap_to_pixel_x(float x, float roundUpAt); extern int con_linewidth; // to force rewrapping void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset) { int i, ch; float maxwidth; char widthfile[MAX_QPATH]; char *widthbuf; fs_offset_t widthbufsize; if(override || !fnt->texpath[0]) { strlcpy(fnt->texpath, name, sizeof(fnt->texpath)); // load the cvars when the font is FIRST loader fnt->settings.scale = scale; fnt->settings.voffset = voffset; fnt->settings.antialias = r_font_antialias.integer; fnt->settings.hinting = r_font_hinting.integer; fnt->settings.outline = r_font_postprocess_outline.value; fnt->settings.blur = r_font_postprocess_blur.value; fnt->settings.shadowx = r_font_postprocess_shadow_x.value; fnt->settings.shadowy = r_font_postprocess_shadow_y.value; fnt->settings.shadowz = r_font_postprocess_shadow_z.value; } // fix bad scale if (fnt->settings.scale <= 0) fnt->settings.scale = 1; if(drawtexturepool == NULL) return; // before gl_draw_start, so will be loaded later if(fnt->ft2) { // clear freetype font Font_UnloadFont(fnt->ft2); Mem_Free(fnt->ft2); fnt->ft2 = NULL; } if(fnt->req_face != -1) { if(!Font_LoadFont(fnt->texpath, fnt)) Con_DPrintf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath); } fnt->tex = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; if(fnt->tex == r_texture_notexture) { for (i = 0; i < MAX_FONT_FALLBACKS; ++i) { if (!fnt->fallbacks[i][0]) break; fnt->tex = Draw_CachePic_Flags(fnt->fallbacks[i], CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; if(fnt->tex != r_texture_notexture) break; } if(fnt->tex == r_texture_notexture) { fnt->tex = Draw_CachePic_Flags("gfx/conchars", CACHEPICFLAG_NOCOMPRESSION | (r_nearest_conchars.integer ? CACHEPICFLAG_NEAREST : 0))->tex; strlcpy(widthfile, "gfx/conchars.width", sizeof(widthfile)); } else dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->fallbacks[i]); } else dpsnprintf(widthfile, sizeof(widthfile), "%s.width", fnt->texpath); // unspecified width == 1 (base width) for(ch = 0; ch < 256; ++ch) fnt->width_of[ch] = 1; // FIXME load "name.width", if it fails, fill all with 1 if((widthbuf = (char *) FS_LoadFile(widthfile, tempmempool, true, &widthbufsize))) { float extraspacing = 0; const char *p = widthbuf; ch = 0; while(ch < 256) { if(!COM_ParseToken_Simple(&p, false, false, true)) return; switch(*com_token) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': case '.': fnt->width_of[ch] = atof(com_token) + extraspacing; ch++; break; default: if(!strcmp(com_token, "extraspacing")) { if(!COM_ParseToken_Simple(&p, false, false, true)) return; extraspacing = atof(com_token); } else if(!strcmp(com_token, "scale")) { if(!COM_ParseToken_Simple(&p, false, false, true)) return; fnt->settings.scale = atof(com_token); } else { Con_Printf("Warning: skipped unknown font property %s\n", com_token); if(!COM_ParseToken_Simple(&p, false, false, true)) return; } break; } } Mem_Free(widthbuf); } if(fnt->ft2) { for (i = 0; i < MAX_FONT_SIZES; ++i) { ft2_font_map_t *map = Font_MapForIndex(fnt->ft2, i); if (!map) break; for(ch = 0; ch < 256; ++ch) map->width_of[ch] = Font_SnapTo(fnt->width_of[ch], 1/map->size); } } maxwidth = fnt->width_of[0]; for(i = 1; i < 256; ++i) maxwidth = max(maxwidth, fnt->width_of[i]); fnt->maxwidth = maxwidth; // fix up maxwidth for overlap fnt->maxwidth *= fnt->settings.scale; if(fnt == FONT_CONSOLE) con_linewidth = -1; // rewrap console in next frame } extern cvar_t developer_font; dp_font_t *FindFont(const char *title, qboolean allocate_new) { int i, oldsize; // find font for(i = 0; i < dp_fonts.maxsize; ++i) if(!strcmp(dp_fonts.f[i].title, title)) return &dp_fonts.f[i]; // if not found - try allocate if (allocate_new) { // find any font with empty title for(i = 0; i < dp_fonts.maxsize; ++i) { if(!strcmp(dp_fonts.f[i].title, "")) { strlcpy(dp_fonts.f[i].title, title, sizeof(dp_fonts.f[i].title)); return &dp_fonts.f[i]; } } // if no any 'free' fonts - expand buffer oldsize = dp_fonts.maxsize; dp_fonts.maxsize = dp_fonts.maxsize + FONTS_EXPAND; if (developer_font.integer) Con_Printf("FindFont: enlarging fonts buffer (%i -> %i)\n", oldsize, dp_fonts.maxsize); dp_fonts.f = (dp_font_t *)Mem_Realloc(fonts_mempool, dp_fonts.f, sizeof(dp_font_t) * dp_fonts.maxsize); // relink ft2 structures for(i = 0; i < oldsize; ++i) if (dp_fonts.f[i].ft2) dp_fonts.f[i].ft2->settings = &dp_fonts.f[i].settings; // register a font in first expanded slot strlcpy(dp_fonts.f[oldsize].title, title, sizeof(dp_fonts.f[oldsize].title)); return &dp_fonts.f[oldsize]; } return NULL; } static float snap_to_pixel_x(float x, float roundUpAt) { float pixelpos = x * vid.width / vid_conwidth.value; int snap = (int) pixelpos; if (pixelpos - snap >= roundUpAt) ++snap; return ((float)snap * vid_conwidth.value / vid.width); /* x = (int)(x * vid.width / vid_conwidth.value); x = (x * vid_conwidth.value / vid.width); return x; */ } static float snap_to_pixel_y(float y, float roundUpAt) { float pixelpos = y * vid.height / vid_conheight.value; int snap = (int) pixelpos; if (pixelpos - snap > roundUpAt) ++snap; return ((float)snap * vid_conheight.value / vid.height); /* y = (int)(y * vid.height / vid_conheight.value); y = (y * vid_conheight.value / vid.height); return y; */ } static void LoadFont_f(void) { dp_font_t *f; int i, sizes; const char *filelist, *c, *cm; float sz, scale, voffset; char mainfont[MAX_QPATH]; if(Cmd_Argc() < 2) { Con_Printf("Available font commands:\n"); for(i = 0; i < dp_fonts.maxsize; ++i) if (dp_fonts.f[i].title[0]) Con_Printf(" loadfont %s gfx/tgafile[...] [custom switches] [sizes...]\n", dp_fonts.f[i].title); Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n" "can specify multiple fonts and faces\n" "Like this: gfx/vera-sans:2,gfx/fallback:1\n" "to load face 2 of the font gfx/vera-sans and use face 1\n" "of gfx/fallback as fallback font.\n" "You can also specify a list of font sizes to load, like this:\n" "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n" "In many cases, 8 12 16 24 32 should be a good choice.\n" "custom switches:\n" " scale x : scale all characters by this amount when rendering (doesnt change line height)\n" " voffset x : offset all chars vertical when rendering, this is multiplied to character height\n" ); return; } f = FindFont(Cmd_Argv(1), true); if(f == NULL) { Con_Printf("font function not found\n"); return; } if(Cmd_Argc() < 3) filelist = "gfx/conchars"; else filelist = Cmd_Argv(2); memset(f->fallbacks, 0, sizeof(f->fallbacks)); memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); // first font is handled "normally" c = strchr(filelist, ':'); cm = strchr(filelist, ','); if(c && (!cm || c < cm)) f->req_face = atoi(c+1); else { f->req_face = 0; c = cm; } if(!c || (c - filelist) > MAX_QPATH) strlcpy(mainfont, filelist, sizeof(mainfont)); else { memcpy(mainfont, filelist, c - filelist); mainfont[c - filelist] = 0; } for(i = 0; i < MAX_FONT_FALLBACKS; ++i) { c = strchr(filelist, ','); if(!c) break; filelist = c + 1; if(!*filelist) break; c = strchr(filelist, ':'); cm = strchr(filelist, ','); if(c && (!cm || c < cm)) f->fallback_faces[i] = atoi(c+1); else { f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index c = cm; } if(!c || (c-filelist) > MAX_QPATH) { strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); } else { memcpy(f->fallbacks[i], filelist, c - filelist); f->fallbacks[i][c - filelist] = 0; } } // for now: by default load only one size: the default size f->req_sizes[0] = 0; for(i = 1; i < MAX_FONT_SIZES; ++i) f->req_sizes[i] = -1; scale = 1; voffset = 0; if(Cmd_Argc() >= 4) { for(sizes = 0, i = 3; i < Cmd_Argc(); ++i) { // special switches if (!strcmp(Cmd_Argv(i), "scale")) { i++; if (i < Cmd_Argc()) scale = atof(Cmd_Argv(i)); continue; } if (!strcmp(Cmd_Argv(i), "voffset")) { i++; if (i < Cmd_Argc()) voffset = atof(Cmd_Argv(i)); continue; } if (sizes == -1) continue; // no slot for other sizes // parse one of sizes sz = atof(Cmd_Argv(i)); if (sz > 0.001f && sz < 1000.0f) // do not use crap sizes { // search for duplicated sizes int j; for (j=0; jreq_sizes[j] == sz) break; if (j != sizes) continue; // sz already in req_sizes, don't add it again if (sizes == MAX_FONT_SIZES) { Con_Printf("Warning: specified more than %i different font sizes, exceding ones are ignored\n", MAX_FONT_SIZES); sizes = -1; continue; } f->req_sizes[sizes] = sz; sizes++; } } } LoadFont(true, mainfont, f, scale, voffset); } /* =============== Draw_Init =============== */ static void gl_draw_start(void) { int i; char vabuf[1024]; drawtexturepool = R_AllocTexturePool(); numcachepics = 0; memset(cachepichash, 0, sizeof(cachepichash)); font_start(); // load default font textures for(i = 0; i < dp_fonts.maxsize; ++i) if (dp_fonts.f[i].title[0]) LoadFont(false, va(vabuf, sizeof(vabuf), "gfx/font_%s", dp_fonts.f[i].title), &dp_fonts.f[i], 1, 0); // draw the loading screen so people have something to see in the newly opened window SCR_UpdateLoadingScreen(true, true); } static void gl_draw_shutdown(void) { font_shutdown(); R_FreeTexturePool(&drawtexturepool); numcachepics = 0; memset(cachepichash, 0, sizeof(cachepichash)); } static void gl_draw_newmap(void) { font_newmap(); } void GL_Draw_Init (void) { int i, j; Cvar_RegisterVariable(&r_font_postprocess_blur); Cvar_RegisterVariable(&r_font_postprocess_outline); Cvar_RegisterVariable(&r_font_postprocess_shadow_x); Cvar_RegisterVariable(&r_font_postprocess_shadow_y); Cvar_RegisterVariable(&r_font_postprocess_shadow_z); Cvar_RegisterVariable(&r_font_hinting); Cvar_RegisterVariable(&r_font_antialias); Cvar_RegisterVariable(&r_textshadow); Cvar_RegisterVariable(&r_textbrightness); Cvar_RegisterVariable(&r_textcontrast); Cvar_RegisterVariable(&r_nearest_2d); Cvar_RegisterVariable(&r_nearest_conchars); // allocate fonts storage fonts_mempool = Mem_AllocPool("FONTS", 0, NULL); dp_fonts.maxsize = MAX_FONTS; dp_fonts.f = (dp_font_t *)Mem_Alloc(fonts_mempool, sizeof(dp_font_t) * dp_fonts.maxsize); memset(dp_fonts.f, 0, sizeof(dp_font_t) * dp_fonts.maxsize); // assign starting font names strlcpy(FONT_DEFAULT->title, "default", sizeof(FONT_DEFAULT->title)); strlcpy(FONT_DEFAULT->texpath, "gfx/conchars", sizeof(FONT_DEFAULT->texpath)); strlcpy(FONT_CONSOLE->title, "console", sizeof(FONT_CONSOLE->title)); strlcpy(FONT_SBAR->title, "sbar", sizeof(FONT_SBAR->title)); strlcpy(FONT_NOTIFY->title, "notify", sizeof(FONT_NOTIFY->title)); strlcpy(FONT_CHAT->title, "chat", sizeof(FONT_CHAT->title)); strlcpy(FONT_CENTERPRINT->title, "centerprint", sizeof(FONT_CENTERPRINT->title)); strlcpy(FONT_INFOBAR->title, "infobar", sizeof(FONT_INFOBAR->title)); strlcpy(FONT_MENU->title, "menu", sizeof(FONT_MENU->title)); for(i = 0, j = 0; i < MAX_USERFONTS; ++i) if(!FONT_USER(i)->title[0]) dpsnprintf(FONT_USER(i)->title, sizeof(FONT_USER(i)->title), "user%d", j++); Cmd_AddCommand ("loadfont",LoadFont_f, "loadfont function tganame loads a font; example: loadfont console gfx/veramono; loadfont without arguments lists the available functions"); R_RegisterModule("GL_Draw", gl_draw_start, gl_draw_shutdown, gl_draw_newmap, NULL, NULL); } static void _DrawQ_Setup(void) // see R_ResetViewRendering2D { if (r_refdef.draw2dstage == 1) return; r_refdef.draw2dstage = 1; R_ResetViewRendering2D_Common(0, NULL, NULL, vid_conwidth.integer, vid_conheight.integer); } qboolean r_draw2d_force = false; static void _DrawQ_SetupAndProcessDrawFlag(int flags, cachepic_t *pic, float alpha) { _DrawQ_Setup(); if(!r_draw2d.integer && !r_draw2d_force) return; DrawQ_ProcessDrawFlag(flags, (alpha < 1) || (pic && pic->hasalpha)); } void DrawQ_ProcessDrawFlag(int flags, qboolean alpha) { if(flags == DRAWFLAG_ADDITIVE) { GL_DepthMask(false); GL_BlendFunc(alpha ? GL_SRC_ALPHA : GL_ONE, GL_ONE); } else if(flags == DRAWFLAG_MODULATE) { GL_DepthMask(false); GL_BlendFunc(GL_DST_COLOR, GL_ZERO); } else if(flags == DRAWFLAG_2XMODULATE) { GL_DepthMask(false); GL_BlendFunc(GL_DST_COLOR, GL_SRC_COLOR); } else if(flags == DRAWFLAG_SCREEN) { GL_DepthMask(false); GL_BlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE); } else if(alpha) { GL_DepthMask(false); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { GL_DepthMask(true); GL_BlendFunc(GL_ONE, GL_ZERO); } } void DrawQ_Pic(float x, float y, cachepic_t *pic, float width, float height, float red, float green, float blue, float alpha, int flags) { float floats[36]; _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); if(!r_draw2d.integer && !r_draw2d_force) return; // R_Mesh_ResetTextureState(); floats[12] = 0.0f;floats[13] = 0.0f; floats[14] = 1.0f;floats[15] = 0.0f; floats[16] = 1.0f;floats[17] = 1.0f; floats[18] = 0.0f;floats[19] = 1.0f; floats[20] = floats[24] = floats[28] = floats[32] = red; floats[21] = floats[25] = floats[29] = floats[33] = green; floats[22] = floats[26] = floats[30] = floats[34] = blue; floats[23] = floats[27] = floats[31] = floats[35] = alpha; if (pic) { if (width == 0) width = pic->width; if (height == 0) height = pic->height; R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); #if 0 // AK07: lets be texel correct on the corners { float horz_offset = 0.5f / pic->width; float vert_offset = 0.5f / pic->height; floats[12] = 0.0f + horz_offset;floats[13] = 0.0f + vert_offset; floats[14] = 1.0f - horz_offset;floats[15] = 0.0f + vert_offset; floats[16] = 1.0f - horz_offset;floats[17] = 1.0f - vert_offset; floats[18] = 0.0f + horz_offset;floats[19] = 1.0f - vert_offset; } #endif } else R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); floats[2] = floats[5] = floats[8] = floats[11] = 0; floats[0] = floats[9] = x; floats[1] = floats[4] = y; floats[3] = floats[6] = x + width; floats[7] = floats[10] = y + height; R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } void DrawQ_RotPic(float x, float y, cachepic_t *pic, float width, float height, float org_x, float org_y, float angle, float red, float green, float blue, float alpha, int flags) { float floats[36]; float af = DEG2RAD(-angle); // forward float ar = DEG2RAD(-angle + 90); // right float sinaf = sin(af); float cosaf = cos(af); float sinar = sin(ar); float cosar = cos(ar); _DrawQ_SetupAndProcessDrawFlag(flags, pic, alpha); if(!r_draw2d.integer && !r_draw2d_force) return; // R_Mesh_ResetTextureState(); if (pic) { if (width == 0) width = pic->width; if (height == 0) height = pic->height; R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); } else R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); floats[2] = floats[5] = floats[8] = floats[11] = 0; // top left floats[0] = x - cosaf*org_x - cosar*org_y; floats[1] = y - sinaf*org_x - sinar*org_y; // top right floats[3] = x + cosaf*(width-org_x) - cosar*org_y; floats[4] = y + sinaf*(width-org_x) - sinar*org_y; // bottom right floats[6] = x + cosaf*(width-org_x) + cosar*(height-org_y); floats[7] = y + sinaf*(width-org_x) + sinar*(height-org_y); // bottom left floats[9] = x - cosaf*org_x + cosar*(height-org_y); floats[10] = y - sinaf*org_x + sinar*(height-org_y); floats[12] = 0.0f;floats[13] = 0.0f; floats[14] = 1.0f;floats[15] = 0.0f; floats[16] = 1.0f;floats[17] = 1.0f; floats[18] = 0.0f;floats[19] = 1.0f; floats[20] = floats[24] = floats[28] = floats[32] = red; floats[21] = floats[25] = floats[29] = floats[33] = green; floats[22] = floats[26] = floats[30] = floats[34] = blue; floats[23] = floats[27] = floats[31] = floats[35] = alpha; R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } void DrawQ_Fill(float x, float y, float width, float height, float red, float green, float blue, float alpha, int flags) { float floats[36]; _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); if(!r_draw2d.integer && !r_draw2d_force) return; // R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); floats[2] = floats[5] = floats[8] = floats[11] = 0; floats[0] = floats[9] = x; floats[1] = floats[4] = y; floats[3] = floats[6] = x + width; floats[7] = floats[10] = y + height; floats[12] = 0.0f;floats[13] = 0.0f; floats[14] = 1.0f;floats[15] = 0.0f; floats[16] = 1.0f;floats[17] = 1.0f; floats[18] = 0.0f;floats[19] = 1.0f; floats[20] = floats[24] = floats[28] = floats[32] = red; floats[21] = floats[25] = floats[29] = floats[33] = green; floats[22] = floats[26] = floats[30] = floats[34] = blue; floats[23] = floats[27] = floats[31] = floats[35] = alpha; R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } /// color tag printing static const vec4_t string_colors[] = { // Quake3 colors // LordHavoc: why on earth is cyan before magenta in Quake3? // LordHavoc: note: Doom3 uses white for [0] and [7] {0.0, 0.0, 0.0, 1.0}, // black {1.0, 0.0, 0.0, 1.0}, // red {0.0, 1.0, 0.0, 1.0}, // green {1.0, 1.0, 0.0, 1.0}, // yellow {0.0, 0.0, 1.0, 1.0}, // blue {0.0, 1.0, 1.0, 1.0}, // cyan {1.0, 0.0, 1.0, 1.0}, // magenta {1.0, 1.0, 1.0, 1.0}, // white // [515]'s BX_COLOREDTEXT extension {1.0, 1.0, 1.0, 0.5}, // half transparent {0.5, 0.5, 0.5, 1.0} // half brightness // Black's color table //{1.0, 1.0, 1.0, 1.0}, //{1.0, 0.0, 0.0, 1.0}, //{0.0, 1.0, 0.0, 1.0}, //{0.0, 0.0, 1.0, 1.0}, //{1.0, 1.0, 0.0, 1.0}, //{0.0, 1.0, 1.0, 1.0}, //{1.0, 0.0, 1.0, 1.0}, //{0.1, 0.1, 0.1, 1.0} }; #define STRING_COLORS_COUNT (sizeof(string_colors) / sizeof(vec4_t)) static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, float b, float a, qboolean shadow) { float C = r_textcontrast.value; float B = r_textbrightness.value; if (colorindex & 0x10000) // that bit means RGB color { color[0] = ((colorindex >> 12) & 0xf) / 15.0; color[1] = ((colorindex >> 8) & 0xf) / 15.0; color[2] = ((colorindex >> 4) & 0xf) / 15.0; color[3] = (colorindex & 0xf) / 15.0; } else Vector4Copy(string_colors[colorindex], color); Vector4Set(color, color[0] * r * C + B, color[1] * g * C + B, color[2] * b * C + B, color[3] * a); if (shadow) { float shadowalpha = (color[0]+color[1]+color[2]) * 0.8; Vector4Set(color, 0, 0, 0, color[3] * bound(0, shadowalpha, 1)); } } // NOTE: this function always draws exactly one character if maxwidth <= 0 float DrawQ_TextWidth_UntilWidth_TrackColors_Scale(const char *text, size_t *maxlen, float w, float h, float sw, float sh, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) { const char *text_start = text; int colorindex = STRING_COLOR_DEFAULT; size_t i; float x = 0; Uchar ch, mapch, nextch; Uchar prevch = 0; // used for kerning int tempcolorindex; float kx; int map_index = 0; size_t bytes_left; ft2_font_map_t *fontmap = NULL; ft2_font_map_t *map = NULL; //ft2_font_map_t *prevmap = NULL; ft2_font_t *ft2 = fnt->ft2; // float ftbase_x; qboolean snap = true; qboolean least_one = false; float dw; // display w //float dh; // display h const float *width_of; if (!h) h = w; if (!h) { w = h = 1; snap = false; } // do this in the end w *= fnt->settings.scale; h *= fnt->settings.scale; // find the most fitting size: if (ft2 != NULL) { if (snap) map_index = Font_IndexForSize(ft2, h, &w, &h); else map_index = Font_IndexForSize(ft2, h, NULL, NULL); fontmap = Font_MapForIndex(ft2, map_index); } dw = w * sw; //dh = h * sh; if (*maxlen < 1) *maxlen = 1<<30; if (!outcolor || *outcolor == -1) colorindex = STRING_COLOR_DEFAULT; else colorindex = *outcolor; // maxwidth /= fnt->scale; // w and h are multiplied by it already // ftbase_x = snap_to_pixel_x(0); if(maxwidth <= 0) { least_one = true; maxwidth = -maxwidth; } //if (snap) // x = snap_to_pixel_x(x, 0.4); // haha, it's 0 anyway if (fontmap) width_of = fontmap->width_of; else width_of = fnt->width_of; i = 0; while (((bytes_left = *maxlen - (text - text_start)) > 0) && *text) { size_t i0 = i; nextch = ch = u8_getnchar(text, &text, bytes_left); i = text - text_start; if (!ch) break; if (ch == ' ' && !fontmap) { if(!least_one || i0) // never skip the first character if(x + width_of[(int) ' '] * dw > maxwidth) { i = i0; break; // oops, can't draw this } x += width_of[(int) ' '] * dw; continue; } // i points to the char after ^ if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < *maxlen) { ch = *text; // colors are ascii, so no u8_ needed if (ch <= '9' && ch >= '0') // ^[0-9] found { colorindex = ch - '0'; ++text; ++i; continue; } // i points to the char after ^... // i+3 points to 3 in ^x123 // i+3 == *maxlen would mean that char is missing else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found { // building colorindex... ch = tolower(text[1]); tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; else tempcolorindex = 0; if (tempcolorindex) { ch = tolower(text[2]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; else tempcolorindex = 0; if (tempcolorindex) { ch = tolower(text[3]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; else tempcolorindex = 0; if (tempcolorindex) { colorindex = tempcolorindex | 0xf; // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) i+=4; text += 4; continue; } } } } else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second { i++; text++; } i--; } ch = nextch; if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) { if (ch > 0xE000) ch -= 0xE000; if (ch > 0xFF) continue; if (fontmap) map = ft2_oldstyle_map; prevch = 0; if(!least_one || i0) // never skip the first character if(x + width_of[ch] * dw > maxwidth) { i = i0; break; // oops, can't draw this } x += width_of[ch] * dw; } else { if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) { map = FontMap_FindForChar(fontmap, ch); if (!map) { if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) break; if (!map) break; } } mapch = ch - map->start; if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL)) x += kx * dw; x += map->glyphs[mapch].advance_x * dw; //prevmap = map; prevch = ch; } } *maxlen = i; if (outcolor) *outcolor = colorindex; return x; } float DrawQ_Color[4]; float DrawQ_String_Scale(float startx, float starty, const char *text, size_t maxlen, float w, float h, float sw, float sh, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) { int shadow, colorindex = STRING_COLOR_DEFAULT; size_t i; float x = startx, y, s, t, u, v, thisw; float *av, *at, *ac; int batchcount; static float vertex3f[QUADELEMENTS_MAXQUADS*4*3]; static float texcoord2f[QUADELEMENTS_MAXQUADS*4*2]; static float color4f[QUADELEMENTS_MAXQUADS*4*4]; Uchar ch, mapch, nextch; Uchar prevch = 0; // used for kerning int tempcolorindex; int map_index = 0; //ft2_font_map_t *prevmap = NULL; // the previous map ft2_font_map_t *map = NULL; // the currently used map ft2_font_map_t *fontmap = NULL; // the font map for the size float ftbase_y; const char *text_start = text; float kx, ky; ft2_font_t *ft2 = fnt->ft2; qboolean snap = true; float pix_x, pix_y; size_t bytes_left; float dw, dh; const float *width_of; int tw, th; tw = R_TextureWidth(fnt->tex); th = R_TextureHeight(fnt->tex); if (!h) h = w; if (!h) { h = w = 1; snap = false; } starty -= (fnt->settings.scale - 1) * h * 0.5 - fnt->settings.voffset*h; // center & offset w *= fnt->settings.scale; h *= fnt->settings.scale; if (ft2 != NULL) { if (snap) map_index = Font_IndexForSize(ft2, h, &w, &h); else map_index = Font_IndexForSize(ft2, h, NULL, NULL); fontmap = Font_MapForIndex(ft2, map_index); } dw = w * sw; dh = h * sh; // draw the font at its baseline when using freetype //ftbase_x = 0; ftbase_y = dh * (4.5/6.0); if (maxlen < 1) maxlen = 1<<30; _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 0); if(!r_draw2d.integer && !r_draw2d_force) return startx + DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, &maxlen, w, h, sw, sh, NULL, ignorecolorcodes, fnt, 1000000000); // R_Mesh_ResetTextureState(); if (!fontmap) R_Mesh_TexBind(0, fnt->tex); R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); ac = color4f; at = texcoord2f; av = vertex3f; batchcount = 0; //ftbase_x = snap_to_pixel_x(ftbase_x); if(snap) { startx = snap_to_pixel_x(startx, 0.4); starty = snap_to_pixel_y(starty, 0.4); ftbase_y = snap_to_pixel_y(ftbase_y, 0.3); } pix_x = vid.width / vid_conwidth.value; pix_y = vid.height / vid_conheight.value; if (fontmap) width_of = fontmap->width_of; else width_of = fnt->width_of; for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--) { prevch = 0; text = text_start; if (!outcolor || *outcolor == -1) colorindex = STRING_COLOR_DEFAULT; else colorindex = *outcolor; DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); x = startx; y = starty; /* if (shadow) { x += r_textshadow.value * vid.width / vid_conwidth.value; y += r_textshadow.value * vid.height / vid_conheight.value; } */ while (((bytes_left = maxlen - (text - text_start)) > 0) && *text) { nextch = ch = u8_getnchar(text, &text, bytes_left); i = text - text_start; if (!ch) break; if (ch == ' ' && !fontmap) { x += width_of[(int) ' '] * dw; continue; } if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i < maxlen) { ch = *text; // colors are ascii, so no u8_ needed if (ch <= '9' && ch >= '0') // ^[0-9] found { colorindex = ch - '0'; DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); ++text; ++i; continue; } else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found { // building colorindex... ch = tolower(text[1]); tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; else tempcolorindex = 0; if (tempcolorindex) { ch = tolower(text[2]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; else tempcolorindex = 0; if (tempcolorindex) { ch = tolower(text[3]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; else tempcolorindex = 0; if (tempcolorindex) { colorindex = tempcolorindex | 0xf; // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) //Con_Printf("^1colorindex:^7 %x\n", colorindex); DrawQ_GetTextColor(DrawQ_Color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); i+=4; text+=4; continue; } } } } else if (ch == STRING_COLOR_TAG) { i++; text++; } i--; } // get the backup ch = nextch; // using a value of -1 for the oldstyle map because NULL means uninitialized... // this way we don't need to rebind fnt->tex for every old-style character // E000..E0FF: emulate old-font characters (to still have smileys and such available) if (shadow) { x += 1.0/pix_x * r_textshadow.value; y += 1.0/pix_y * r_textshadow.value; } if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) { if (ch >= 0xE000) ch -= 0xE000; if (ch > 0xFF) goto out; if (fontmap) { if (map != ft2_oldstyle_map) { if (batchcount) { // switching from freetype to non-freetype rendering R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); batchcount = 0; ac = color4f; at = texcoord2f; av = vertex3f; } R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); map = ft2_oldstyle_map; } } prevch = 0; //num = (unsigned char) text[i]; //thisw = fnt->width_of[num]; thisw = fnt->width_of[ch]; // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering if (r_nearest_conchars.integer) { s = (ch & 15)*0.0625f; t = (ch >> 4)*0.0625f; u = 0.0625f * thisw; v = 0.0625f; } else { s = (ch & 15)*0.0625f + (0.5f / tw); t = (ch >> 4)*0.0625f + (0.5f / th); u = 0.0625f * thisw - (1.0f / tw); v = 0.0625f - (1.0f / th); } ac[ 0] = DrawQ_Color[0];ac[ 1] = DrawQ_Color[1];ac[ 2] = DrawQ_Color[2];ac[ 3] = DrawQ_Color[3]; ac[ 4] = DrawQ_Color[0];ac[ 5] = DrawQ_Color[1];ac[ 6] = DrawQ_Color[2];ac[ 7] = DrawQ_Color[3]; ac[ 8] = DrawQ_Color[0];ac[ 9] = DrawQ_Color[1];ac[10] = DrawQ_Color[2];ac[11] = DrawQ_Color[3]; ac[12] = DrawQ_Color[0];ac[13] = DrawQ_Color[1];ac[14] = DrawQ_Color[2];ac[15] = DrawQ_Color[3]; at[ 0] = s ; at[ 1] = t ; at[ 2] = s+u ; at[ 3] = t ; at[ 4] = s+u ; at[ 5] = t+v ; at[ 6] = s ; at[ 7] = t+v ; av[ 0] = x ; av[ 1] = y ; av[ 2] = 10; av[ 3] = x+dw*thisw ; av[ 4] = y ; av[ 5] = 10; av[ 6] = x+dw*thisw ; av[ 7] = y+dh ; av[ 8] = 10; av[ 9] = x ; av[10] = y+dh ; av[11] = 10; ac += 16; at += 8; av += 12; batchcount++; if (batchcount >= QUADELEMENTS_MAXQUADS) { R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); batchcount = 0; ac = color4f; at = texcoord2f; av = vertex3f; } x += width_of[ch] * dw; } else { if (!map || map == ft2_oldstyle_map || ch < map->start || ch >= map->start + FONT_CHARS_PER_MAP) { // new charmap - need to render if (batchcount) { // we need a different character map, render what we currently have: R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); batchcount = 0; ac = color4f; at = texcoord2f; av = vertex3f; } // find the new map map = FontMap_FindForChar(fontmap, ch); if (!map) { if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) { shadow = -1; break; } if (!map) { // this shouldn't happen shadow = -1; break; } } R_SetupShader_Generic(map->pic->tex, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); } mapch = ch - map->start; thisw = map->glyphs[mapch].advance_x; //x += ftbase_x; y += ftbase_y; if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky)) { x += kx * dw; y += ky * dh; } else kx = ky = 0; ac[ 0] = DrawQ_Color[0]; ac[ 1] = DrawQ_Color[1]; ac[ 2] = DrawQ_Color[2]; ac[ 3] = DrawQ_Color[3]; ac[ 4] = DrawQ_Color[0]; ac[ 5] = DrawQ_Color[1]; ac[ 6] = DrawQ_Color[2]; ac[ 7] = DrawQ_Color[3]; ac[ 8] = DrawQ_Color[0]; ac[ 9] = DrawQ_Color[1]; ac[10] = DrawQ_Color[2]; ac[11] = DrawQ_Color[3]; ac[12] = DrawQ_Color[0]; ac[13] = DrawQ_Color[1]; ac[14] = DrawQ_Color[2]; ac[15] = DrawQ_Color[3]; at[0] = map->glyphs[mapch].txmin; at[1] = map->glyphs[mapch].tymin; at[2] = map->glyphs[mapch].txmax; at[3] = map->glyphs[mapch].tymin; at[4] = map->glyphs[mapch].txmax; at[5] = map->glyphs[mapch].tymax; at[6] = map->glyphs[mapch].txmin; at[7] = map->glyphs[mapch].tymax; av[ 0] = x + dw * map->glyphs[mapch].vxmin; av[ 1] = y + dh * map->glyphs[mapch].vymin; av[ 2] = 10; av[ 3] = x + dw * map->glyphs[mapch].vxmax; av[ 4] = y + dh * map->glyphs[mapch].vymin; av[ 5] = 10; av[ 6] = x + dw * map->glyphs[mapch].vxmax; av[ 7] = y + dh * map->glyphs[mapch].vymax; av[ 8] = 10; av[ 9] = x + dw * map->glyphs[mapch].vxmin; av[10] = y + dh * map->glyphs[mapch].vymax; av[11] = 10; //x -= ftbase_x; y -= ftbase_y; x += thisw * dw; ac += 16; at += 8; av += 12; batchcount++; if (batchcount >= QUADELEMENTS_MAXQUADS) { R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); batchcount = 0; ac = color4f; at = texcoord2f; av = vertex3f; } //prevmap = map; prevch = ch; } out: if (shadow) { x -= 1.0/pix_x * r_textshadow.value; y -= 1.0/pix_y * r_textshadow.value; } } } if (batchcount > 0) { R_Mesh_PrepareVertices_Generic_Arrays(batchcount * 4, vertex3f, color4f, texcoord2f); R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, NULL, 0, quadelement3s, NULL, 0); } if (outcolor) *outcolor = colorindex; // note: this relies on the proper text (not shadow) being drawn last return x; } float DrawQ_String(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) { return DrawQ_String_Scale(startx, starty, text, maxlen, w, h, 1, 1, basered, basegreen, baseblue, basealpha, flags, outcolor, ignorecolorcodes, fnt); } float DrawQ_TextWidth_UntilWidth_TrackColors(const char *text, size_t *maxlen, float w, float h, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) { return DrawQ_TextWidth_UntilWidth_TrackColors_Scale(text, maxlen, w, h, 1, 1, outcolor, ignorecolorcodes, fnt, maxwidth); } float DrawQ_TextWidth(const char *text, size_t maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt) { return DrawQ_TextWidth_UntilWidth(text, &maxlen, w, h, ignorecolorcodes, fnt, 1000000000); } float DrawQ_TextWidth_UntilWidth(const char *text, size_t *maxlen, float w, float h, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) { return DrawQ_TextWidth_UntilWidth_TrackColors(text, maxlen, w, h, NULL, ignorecolorcodes, fnt, maxWidth); } #if 0 // not used // no ^xrgb management static int DrawQ_BuildColoredText(char *output2c, size_t maxoutchars, const char *text, int maxreadchars, qboolean ignorecolorcodes, int *outcolor) { int color, numchars = 0; char *outputend2c = output2c + maxoutchars - 2; if (!outcolor || *outcolor == -1) color = STRING_COLOR_DEFAULT; else color = *outcolor; if (!maxreadchars) maxreadchars = 1<<30; textend = text + maxreadchars; while (text != textend && *text) { if (*text == STRING_COLOR_TAG && !ignorecolorcodes && text + 1 != textend) { if (text[1] == STRING_COLOR_TAG) text++; else if (text[1] >= '0' && text[1] <= '9') { color = text[1] - '0'; text += 2; continue; } } if (output2c >= outputend2c) break; *output2c++ = *text++; *output2c++ = color; numchars++; } output2c[0] = output2c[1] = 0; if (outcolor) *outcolor = color; return numchars; } #endif void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags) { float floats[36]; _DrawQ_SetupAndProcessDrawFlag(flags, pic, a1*a2*a3*a4); if(!r_draw2d.integer && !r_draw2d_force) return; // R_Mesh_ResetTextureState(); if (pic) { if (width == 0) width = pic->width; if (height == 0) height = pic->height; R_SetupShader_Generic(Draw_GetPicTexture(pic), NULL, GL_MODULATE, 1, (flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true, false); } else R_SetupShader_Generic_NoTexture((flags & (DRAWFLAGS_BLEND | DRAWFLAG_NOGAMMA)) ? false : true, true); floats[2] = floats[5] = floats[8] = floats[11] = 0; floats[0] = floats[9] = x; floats[1] = floats[4] = y; floats[3] = floats[6] = x + width; floats[7] = floats[10] = y + height; floats[12] = s1;floats[13] = t1; floats[14] = s2;floats[15] = t2; floats[16] = s4;floats[17] = t4; floats[18] = s3;floats[19] = t3; floats[20] = r1;floats[21] = g1;floats[22] = b1;floats[23] = a1; floats[24] = r2;floats[25] = g2;floats[26] = b2;floats[27] = a2; floats[28] = r4;floats[29] = g4;floats[30] = b4;floats[31] = a4; floats[32] = r3;floats[33] = g3;floats[34] = b3;floats[35] = a3; R_Mesh_PrepareVertices_Generic_Arrays(4, floats, floats + 20, floats + 12); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } void DrawQ_Mesh (drawqueuemesh_t *mesh, int flags, qboolean hasalpha) { _DrawQ_Setup(); if(!r_draw2d.integer && !r_draw2d_force) return; DrawQ_ProcessDrawFlag(flags, hasalpha); // R_Mesh_ResetTextureState(); R_SetupShader_Generic(mesh->texture, NULL, GL_MODULATE, 1, (flags & DRAWFLAGS_BLEND) ? false : true, true, false); R_Mesh_PrepareVertices_Generic_Arrays(mesh->num_vertices, mesh->data_vertex3f, mesh->data_color4f, mesh->data_texcoord2f); R_Mesh_Draw(0, mesh->num_vertices, 0, mesh->num_triangles, mesh->data_element3i, NULL, 0, mesh->data_element3s, NULL, 0); } void DrawQ_LineLoop (drawqueuemesh_t *mesh, int flags) { _DrawQ_SetupAndProcessDrawFlag(flags, NULL, 1); if(!r_draw2d.integer && !r_draw2d_force) return; GL_Color(1,1,1,1); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: #ifndef USE_GLES2 { int num; CHECKGLERROR qglBegin(GL_LINE_LOOP); for (num = 0;num < mesh->num_vertices;num++) { if (mesh->data_color4f) GL_Color(mesh->data_color4f[num*4+0], mesh->data_color4f[num*4+1], mesh->data_color4f[num*4+2], mesh->data_color4f[num*4+3]); qglVertex2f(mesh->data_vertex3f[num*3+0], mesh->data_vertex3f[num*3+1]); } qglEnd(); CHECKGLERROR } #endif break; case RENDERPATH_D3D9: //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GLES1: case RENDERPATH_GLES2: //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; } } //[515]: this is old, delete void DrawQ_Line (float width, float x1, float y1, float x2, float y2, float r, float g, float b, float alpha, int flags) { _DrawQ_SetupAndProcessDrawFlag(flags, NULL, alpha); if(!r_draw2d.integer && !r_draw2d_force) return; R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: #ifndef USE_GLES2 CHECKGLERROR //qglLineWidth(width);CHECKGLERROR GL_Color(r,g,b,alpha); CHECKGLERROR qglBegin(GL_LINES); qglVertex2f(x1, y1); qglVertex2f(x2, y2); qglEnd(); CHECKGLERROR #endif break; case RENDERPATH_D3D9: //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GLES1: case RENDERPATH_GLES2: //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; } } void DrawQ_Lines (float width, int numlines, int flags, qboolean hasalpha) { _DrawQ_SetupAndProcessDrawFlag(flags, NULL, hasalpha ? 0.5f : 1.0f); if(!r_draw2d.integer && !r_draw2d_force) return; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: CHECKGLERROR R_SetupShader_Generic_NoTexture((flags & DRAWFLAGS_BLEND) ? false : true, true); //qglLineWidth(width);CHECKGLERROR CHECKGLERROR qglDrawArrays(GL_LINES, 0, numlines*2); CHECKGLERROR break; case RENDERPATH_D3D9: //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_GLES1: case RENDERPATH_GLES2: //Con_DPrintf("FIXME GLES2 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; } } void DrawQ_SetClipArea(float x, float y, float width, float height) { int ix, iy, iw, ih; _DrawQ_Setup(); // We have to convert the con coords into real coords // OGL uses top to bottom ix = (int)(0.5 + x * ((float)vid.width / vid_conwidth.integer)); iy = (int)(0.5 + y * ((float) vid.height / vid_conheight.integer)); iw = (int)(0.5 + (x+width) * ((float)vid.width / vid_conwidth.integer)) - ix; ih = (int)(0.5 + (y+height) * ((float) vid.height / vid_conheight.integer)) - iy; switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: case RENDERPATH_SOFT: GL_Scissor(ix, vid.height - iy - ih, iw, ih); break; case RENDERPATH_D3D9: GL_Scissor(ix, iy, iw, ih); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } GL_ScissorTest(true); } void DrawQ_ResetClipArea(void) { _DrawQ_Setup(); GL_ScissorTest(false); } void DrawQ_Finish(void) { r_refdef.draw2dstage = 0; } void DrawQ_RecalcView(void) { if(r_refdef.draw2dstage) r_refdef.draw2dstage = -1; // next draw call will set viewport etc. again } static float blendvertex3f[9] = {-5000, -5000, 10, 10000, -5000, 10, -5000, 10000, 10}; void R_DrawGamma(void) { float c[4]; switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_GLES2: if (vid_usinghwgamma || v_glslgamma.integer) return; break; case RENDERPATH_GL11: case RENDERPATH_GL13: if (vid_usinghwgamma) return; break; case RENDERPATH_GLES1: case RENDERPATH_SOFT: return; } // all the blends ignore depth // R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(true, true); GL_DepthMask(true); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(false); // interpretation of brightness and contrast: // color range := brightness .. (brightness + contrast) // i.e. "c *= contrast; c += brightness" // plausible values for brightness thus range from -contrast to 1 // apply pre-brightness (subtractive brightness, for where contrast was >= 1) if (vid.support.ext_blend_subtract) { if (v_color_enable.integer) { c[0] = -v_color_black_r.value / v_color_white_r.value; c[1] = -v_color_black_g.value / v_color_white_g.value; c[2] = -v_color_black_b.value / v_color_white_b.value; } else c[0] = c[1] = c[2] = -v_brightness.value / v_contrast.value; if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) { // need SUBTRACTIVE blending to do this! GL_BlendEquationSubtract(true); GL_BlendFunc(GL_ONE, GL_ONE); GL_Color(c[0], c[1], c[2], 1); R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); GL_BlendEquationSubtract(false); } } // apply contrast if (v_color_enable.integer) { c[0] = v_color_white_r.value; c[1] = v_color_white_g.value; c[2] = v_color_white_b.value; } else c[0] = c[1] = c[2] = v_contrast.value; if (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) { GL_BlendFunc(GL_DST_COLOR, GL_ONE); while (c[0] >= 1.003f || c[1] >= 1.003f || c[2] >= 1.003f) { float cc[4]; cc[0] = bound(0, c[0] - 1, 1); cc[1] = bound(0, c[1] - 1, 1); cc[2] = bound(0, c[2] - 1, 1); GL_Color(cc[0], cc[1], cc[2], 1); R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); c[0] /= 1 + cc[0]; c[1] /= 1 + cc[1]; c[2] /= 1 + cc[2]; } } if (c[0] <= 0.997f || c[1] <= 0.997f || c[2] <= 0.997f) { GL_BlendFunc(GL_DST_COLOR, GL_ZERO); GL_Color(c[0], c[1], c[2], 1); R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } // apply post-brightness (additive brightness, for where contrast was <= 1) if (v_color_enable.integer) { c[0] = v_color_black_r.value; c[1] = v_color_black_g.value; c[2] = v_color_black_b.value; } else c[0] = c[1] = c[2] = v_brightness.value; if (c[0] >= 0.01f || c[1] >= 0.01f || c[2] >= 0.01f) { GL_BlendFunc(GL_ONE, GL_ONE); GL_Color(c[0], c[1], c[2], 1); R_Mesh_PrepareVertices_Generic_Arrays(3, blendvertex3f, NULL, NULL); R_Mesh_Draw(0, 3, 0, 1, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); } } darkplaces/cl_particles.c0000664000175000017500000041660513067716216015014 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "cl_collision.h" #include "image.h" #include "r_shadow.h" // must match ptype_t values particletype_t particletype[pt_total] = { {PBLEND_INVALID, PARTICLE_INVALID, false}, //pt_dead (should never happen) {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_alphastatic {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_static {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_spark {PBLEND_ADD, PARTICLE_HBEAM, false}, //pt_beam {PBLEND_ADD, PARTICLE_SPARK, false}, //pt_rain {PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_raindecal {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_snow {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_bubble {PBLEND_INVMOD, PARTICLE_BILLBOARD, false}, //pt_blood {PBLEND_ADD, PARTICLE_BILLBOARD, false}, //pt_smoke {PBLEND_INVMOD, PARTICLE_ORIENTED_DOUBLESIDED, false}, //pt_decal {PBLEND_ALPHA, PARTICLE_BILLBOARD, false}, //pt_entityparticle }; #define PARTICLEEFFECT_UNDERWATER 1 #define PARTICLEEFFECT_NOTUNDERWATER 2 #define PARTICLEEFFECT_DEFINED 2147483648U typedef struct particleeffectinfo_s { int effectnameindex; // which effect this belongs to // PARTICLEEFFECT_* bits int flags; // blood effects may spawn very few particles, so proper fraction-overflow // handling is very important, this variable keeps track of the fraction double particleaccumulator; // the math is: countabsolute + requestedcount * countmultiplier * quality // absolute number of particles to spawn, often used for decals // (unaffected by quality and requestedcount) float countabsolute; // multiplier for the number of particles CL_ParticleEffect was told to // spawn, most effects do not really have a count and hence use 1, so // this is often the actual count to spawn, not merely a multiplier float countmultiplier; // if > 0 this causes the particle to spawn in an evenly spaced line from // originmins to originmaxs (causing them to describe a trail, not a box) float trailspacing; // type of particle to spawn (defines some aspects of behavior) ptype_t particletype; // blending mode used on this particle type pblend_t blendmode; // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) porientation_t orientation; // range of colors to choose from in hex RRGGBB (like HTML color tags), // randomly interpolated at spawn unsigned int color[2]; // a random texture is chosen in this range (note the second value is one // past the last choosable, so for example 8,16 chooses any from 8 up and // including 15) // if start and end of the range are the same, no randomization is done int tex[2]; // range of size values randomly chosen when spawning, plus size increase over time float size[3]; // range of alpha values randomly chosen when spawning, plus alpha fade float alpha[3]; // how long the particle should live (note it is also removed if alpha drops to 0) float time[2]; // how much gravity affects this particle (negative makes it fly up!) float gravity; // how much bounce the particle has when it hits a surface // if negative the particle is removed on impact float bounce; // if in air this friction is applied // if negative the particle accelerates float airfriction; // if in liquid (water/slime/lava) this friction is applied // if negative the particle accelerates float liquidfriction; // these offsets are added to the values given to particleeffect(), and // then an ellipsoid-shaped jitter is added as defined by these // (they are the 3 radii) float stretchfactor; // stretch velocity factor (used for sparks) float originoffset[3]; float relativeoriginoffset[3]; float velocityoffset[3]; float relativevelocityoffset[3]; float originjitter[3]; float velocityjitter[3]; float velocitymultiplier; // an effect can also spawn a dlight float lightradiusstart; float lightradiusfade; float lighttime; float lightcolor[3]; qboolean lightshadow; int lightcubemapnum; float lightcorona[2]; unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! int staintex[2]; float stainalpha[2]; float stainsize[2]; // other parameters float rotate[4]; // min/max base angle, min/max rotation over time } particleeffectinfo_t; char particleeffectname[MAX_PARTICLEEFFECTNAME][64]; int numparticleeffectinfo; particleeffectinfo_t particleeffectinfo[MAX_PARTICLEEFFECTINFO]; static int particlepalette[256]; /* 0x000000,0x0f0f0f,0x1f1f1f,0x2f2f2f,0x3f3f3f,0x4b4b4b,0x5b5b5b,0x6b6b6b, // 0-7 0x7b7b7b,0x8b8b8b,0x9b9b9b,0xababab,0xbbbbbb,0xcbcbcb,0xdbdbdb,0xebebeb, // 8-15 0x0f0b07,0x170f0b,0x1f170b,0x271b0f,0x2f2313,0x372b17,0x3f2f17,0x4b371b, // 16-23 0x533b1b,0x5b431f,0x634b1f,0x6b531f,0x73571f,0x7b5f23,0x836723,0x8f6f23, // 24-31 0x0b0b0f,0x13131b,0x1b1b27,0x272733,0x2f2f3f,0x37374b,0x3f3f57,0x474767, // 32-39 0x4f4f73,0x5b5b7f,0x63638b,0x6b6b97,0x7373a3,0x7b7baf,0x8383bb,0x8b8bcb, // 40-47 0x000000,0x070700,0x0b0b00,0x131300,0x1b1b00,0x232300,0x2b2b07,0x2f2f07, // 48-55 0x373707,0x3f3f07,0x474707,0x4b4b0b,0x53530b,0x5b5b0b,0x63630b,0x6b6b0f, // 56-63 0x070000,0x0f0000,0x170000,0x1f0000,0x270000,0x2f0000,0x370000,0x3f0000, // 64-71 0x470000,0x4f0000,0x570000,0x5f0000,0x670000,0x6f0000,0x770000,0x7f0000, // 72-79 0x131300,0x1b1b00,0x232300,0x2f2b00,0x372f00,0x433700,0x4b3b07,0x574307, // 80-87 0x5f4707,0x6b4b0b,0x77530f,0x835713,0x8b5b13,0x975f1b,0xa3631f,0xaf6723, // 88-95 0x231307,0x2f170b,0x3b1f0f,0x4b2313,0x572b17,0x632f1f,0x733723,0x7f3b2b, // 96-103 0x8f4333,0x9f4f33,0xaf632f,0xbf772f,0xcf8f2b,0xdfab27,0xefcb1f,0xfff31b, // 104-111 0x0b0700,0x1b1300,0x2b230f,0x372b13,0x47331b,0x533723,0x633f2b,0x6f4733, // 112-119 0x7f533f,0x8b5f47,0x9b6b53,0xa77b5f,0xb7876b,0xc3937b,0xd3a38b,0xe3b397, // 120-127 0xab8ba3,0x9f7f97,0x937387,0x8b677b,0x7f5b6f,0x775363,0x6b4b57,0x5f3f4b, // 128-135 0x573743,0x4b2f37,0x43272f,0x371f23,0x2b171b,0x231313,0x170b0b,0x0f0707, // 136-143 0xbb739f,0xaf6b8f,0xa35f83,0x975777,0x8b4f6b,0x7f4b5f,0x734353,0x6b3b4b, // 144-151 0x5f333f,0x532b37,0x47232b,0x3b1f23,0x2f171b,0x231313,0x170b0b,0x0f0707, // 152-159 0xdbc3bb,0xcbb3a7,0xbfa39b,0xaf978b,0xa3877b,0x977b6f,0x876f5f,0x7b6353, // 160-167 0x6b5747,0x5f4b3b,0x533f33,0x433327,0x372b1f,0x271f17,0x1b130f,0x0f0b07, // 168-175 0x6f837b,0x677b6f,0x5f7367,0x576b5f,0x4f6357,0x475b4f,0x3f5347,0x374b3f, // 176-183 0x2f4337,0x2b3b2f,0x233327,0x1f2b1f,0x172317,0x0f1b13,0x0b130b,0x070b07, // 184-191 0xfff31b,0xefdf17,0xdbcb13,0xcbb70f,0xbba70f,0xab970b,0x9b8307,0x8b7307, // 192-199 0x7b6307,0x6b5300,0x5b4700,0x4b3700,0x3b2b00,0x2b1f00,0x1b0f00,0x0b0700, // 200-207 0x0000ff,0x0b0bef,0x1313df,0x1b1bcf,0x2323bf,0x2b2baf,0x2f2f9f,0x2f2f8f, // 208-215 0x2f2f7f,0x2f2f6f,0x2f2f5f,0x2b2b4f,0x23233f,0x1b1b2f,0x13131f,0x0b0b0f, // 216-223 0x2b0000,0x3b0000,0x4b0700,0x5f0700,0x6f0f00,0x7f1707,0x931f07,0xa3270b, // 224-231 0xb7330f,0xc34b1b,0xcf632b,0xdb7f3b,0xe3974f,0xe7ab5f,0xefbf77,0xf7d38b, // 232-239 0xa77b3b,0xb79b37,0xc7c337,0xe7e357,0x7fbfff,0xabe7ff,0xd7ffff,0x670000, // 240-247 0x8b0000,0xb30000,0xd70000,0xff0000,0xfff393,0xfff7c7,0xffffff,0x9f5b53 // 248-255 */ int ramp1[8] = {0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61}; int ramp2[8] = {0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66}; int ramp3[8] = {0x6d, 0x6b, 6, 5, 4, 3}; //static int explosparkramp[8] = {0x4b0700, 0x6f0f00, 0x931f07, 0xb7330f, 0xcf632b, 0xe3974f, 0xffe7b5, 0xffffff}; // particletexture_t is a rectangle in the particlefonttexture typedef struct particletexture_s { rtexture_t *texture; float s1, t1, s2, t2; } particletexture_t; static rtexturepool_t *particletexturepool; static rtexture_t *particlefonttexture; static particletexture_t particletexture[MAX_PARTICLETEXTURES]; skinframe_t *decalskinframe; // texture numbers in particle font static const int tex_smoke[8] = {0, 1, 2, 3, 4, 5, 6, 7}; static const int tex_bulletdecal[8] = {8, 9, 10, 11, 12, 13, 14, 15}; static const int tex_blooddecal[8] = {16, 17, 18, 19, 20, 21, 22, 23}; static const int tex_bloodparticle[8] = {24, 25, 26, 27, 28, 29, 30, 31}; static const int tex_rainsplash = 32; static const int tex_particle = 63; static const int tex_bubble = 62; static const int tex_raindrop = 61; static const int tex_beam = 60; particleeffectinfo_t baselineparticleeffectinfo = { 0, //int effectnameindex; // which effect this belongs to // PARTICLEEFFECT_* bits 0, //int flags; // blood effects may spawn very few particles, so proper fraction-overflow // handling is very important, this variable keeps track of the fraction 0.0, //double particleaccumulator; // the math is: countabsolute + requestedcount * countmultiplier * quality // absolute number of particles to spawn, often used for decals // (unaffected by quality and requestedcount) 0.0f, //float countabsolute; // multiplier for the number of particles CL_ParticleEffect was told to // spawn, most effects do not really have a count and hence use 1, so // this is often the actual count to spawn, not merely a multiplier 0.0f, //float countmultiplier; // if > 0 this causes the particle to spawn in an evenly spaced line from // originmins to originmaxs (causing them to describe a trail, not a box) 0.0f, //float trailspacing; // type of particle to spawn (defines some aspects of behavior) pt_alphastatic, //ptype_t particletype; // blending mode used on this particle type PBLEND_ALPHA, //pblend_t blendmode; // orientation of this particle type (BILLBOARD, SPARK, BEAM, etc) PARTICLE_BILLBOARD, //porientation_t orientation; // range of colors to choose from in hex RRGGBB (like HTML color tags), // randomly interpolated at spawn {0xFFFFFF, 0xFFFFFF}, //unsigned int color[2]; // a random texture is chosen in this range (note the second value is one // past the last choosable, so for example 8,16 chooses any from 8 up and // including 15) // if start and end of the range are the same, no randomization is done {63, 63 /* tex_particle */}, //int tex[2]; // range of size values randomly chosen when spawning, plus size increase over time {1, 1, 0.0f}, //float size[3]; // range of alpha values randomly chosen when spawning, plus alpha fade {0.0f, 256.0f, 256.0f}, //float alpha[3]; // how long the particle should live (note it is also removed if alpha drops to 0) {16777216.0f, 16777216.0f}, //float time[2]; // how much gravity affects this particle (negative makes it fly up!) 0.0f, //float gravity; // how much bounce the particle has when it hits a surface // if negative the particle is removed on impact 0.0f, //float bounce; // if in air this friction is applied // if negative the particle accelerates 0.0f, //float airfriction; // if in liquid (water/slime/lava) this friction is applied // if negative the particle accelerates 0.0f, //float liquidfriction; // these offsets are added to the values given to particleeffect(), and // then an ellipsoid-shaped jitter is added as defined by these // (they are the 3 radii) 1.0f, //float stretchfactor; // stretch velocity factor (used for sparks) {0.0f, 0.0f, 0.0f}, //float originoffset[3]; {0.0f, 0.0f, 0.0f}, //float relativeoriginoffset[3]; {0.0f, 0.0f, 0.0f}, //float velocityoffset[3]; {0.0f, 0.0f, 0.0f}, //float relativevelocityoffset[3]; {0.0f, 0.0f, 0.0f}, //float originjitter[3]; {0.0f, 0.0f, 0.0f}, //float velocityjitter[3]; 0.0f, //float velocitymultiplier; // an effect can also spawn a dlight 0.0f, //float lightradiusstart; 0.0f, //float lightradiusfade; 16777216.0f, //float lighttime; {1.0f, 1.0f, 1.0f}, //float lightcolor[3]; true, //qboolean lightshadow; 0, //int lightcubemapnum; {1.0f, 0.25f}, //float lightcorona[2]; {(unsigned int)-1, (unsigned int)-1}, //unsigned int staincolor[2]; // note: 0x808080 = neutral (particle's own color), these are modding factors for the particle's original color! {-1, -1}, //int staintex[2]; {1.0f, 1.0f}, //float stainalpha[2]; {2.0f, 2.0f}, //float stainsize[2]; // other parameters {0.0f, 360.0f, 0.0f, 0.0f}, //float rotate[4]; // min/max base angle, min/max rotation over time }; cvar_t cl_particles = {CVAR_SAVE, "cl_particles", "1", "enables particle effects"}; cvar_t cl_particles_quality = {CVAR_SAVE, "cl_particles_quality", "1", "multiplies number of particles"}; cvar_t cl_particles_alpha = {CVAR_SAVE, "cl_particles_alpha", "1", "multiplies opacity of particles"}; cvar_t cl_particles_size = {CVAR_SAVE, "cl_particles_size", "1", "multiplies particle size"}; cvar_t cl_particles_quake = {CVAR_SAVE, "cl_particles_quake", "0", "makes particle effects look mostly like the ones in Quake"}; cvar_t cl_particles_blood = {CVAR_SAVE, "cl_particles_blood", "1", "enables blood effects"}; cvar_t cl_particles_blood_alpha = {CVAR_SAVE, "cl_particles_blood_alpha", "1", "opacity of blood, does not affect decals"}; cvar_t cl_particles_blood_decal_alpha = {CVAR_SAVE, "cl_particles_blood_decal_alpha", "1", "opacity of blood decal"}; cvar_t cl_particles_blood_decal_scalemin = {CVAR_SAVE, "cl_particles_blood_decal_scalemin", "1.5", "minimal random scale of decal"}; cvar_t cl_particles_blood_decal_scalemax = {CVAR_SAVE, "cl_particles_blood_decal_scalemax", "2", "maximal random scale of decal"}; cvar_t cl_particles_blood_bloodhack = {CVAR_SAVE, "cl_particles_blood_bloodhack", "1", "make certain quake particle() calls create blood effects instead"}; cvar_t cl_particles_bulletimpacts = {CVAR_SAVE, "cl_particles_bulletimpacts", "1", "enables bulletimpact effects"}; cvar_t cl_particles_explosions_sparks = {CVAR_SAVE, "cl_particles_explosions_sparks", "1", "enables sparks from explosions"}; cvar_t cl_particles_explosions_shell = {CVAR_SAVE, "cl_particles_explosions_shell", "0", "enables polygonal shell from explosions"}; cvar_t cl_particles_rain = {CVAR_SAVE, "cl_particles_rain", "1", "enables rain effects"}; cvar_t cl_particles_snow = {CVAR_SAVE, "cl_particles_snow", "1", "enables snow effects"}; cvar_t cl_particles_smoke = {CVAR_SAVE, "cl_particles_smoke", "1", "enables smoke (used by multiple effects)"}; cvar_t cl_particles_smoke_alpha = {CVAR_SAVE, "cl_particles_smoke_alpha", "0.5", "smoke brightness"}; cvar_t cl_particles_smoke_alphafade = {CVAR_SAVE, "cl_particles_smoke_alphafade", "0.55", "brightness fade per second"}; cvar_t cl_particles_sparks = {CVAR_SAVE, "cl_particles_sparks", "1", "enables sparks (used by multiple effects)"}; cvar_t cl_particles_bubbles = {CVAR_SAVE, "cl_particles_bubbles", "1", "enables bubbles (used by multiple effects)"}; cvar_t cl_particles_visculling = {CVAR_SAVE, "cl_particles_visculling", "0", "perform a costly check if each particle is visible before drawing"}; cvar_t cl_particles_collisions = {CVAR_SAVE, "cl_particles_collisions", "1", "allow costly collision detection on particles (sparks that bounce, particles not going through walls, blood hitting surfaces, etc)"}; cvar_t cl_particles_forcetraileffects = {0, "cl_particles_forcetraileffects", "0", "force trails to be displayed even if a non-trail draw primitive was used (debug/compat feature)"}; cvar_t cl_decals = {CVAR_SAVE, "cl_decals", "1", "enables decals (bullet holes, blood, etc)"}; cvar_t cl_decals_visculling = {CVAR_SAVE, "cl_decals_visculling", "1", "perform a very cheap check if each decal is visible before drawing"}; cvar_t cl_decals_time = {CVAR_SAVE, "cl_decals_time", "20", "how long before decals start to fade away"}; cvar_t cl_decals_fadetime = {CVAR_SAVE, "cl_decals_fadetime", "1", "how long decals take to fade away"}; cvar_t cl_decals_newsystem = {CVAR_SAVE, "cl_decals_newsystem", "1", "enables new advanced decal system"}; cvar_t cl_decals_newsystem_intensitymultiplier = {CVAR_SAVE, "cl_decals_newsystem_intensitymultiplier", "2", "boosts intensity of decals (because the distance fade can make them hard to see otherwise)"}; cvar_t cl_decals_newsystem_immediatebloodstain = {CVAR_SAVE, "cl_decals_newsystem_immediatebloodstain", "2", "0: no on-spawn blood stains; 1: on-spawn blood stains for pt_blood; 2: always use on-spawn blood stains"}; cvar_t cl_decals_newsystem_bloodsmears = {CVAR_SAVE, "cl_decals_newsystem_bloodsmears", "1", "enable use of particle velocity as decal projection direction rather than surface normal"}; cvar_t cl_decals_models = {CVAR_SAVE, "cl_decals_models", "0", "enables decals on animated models (if newsystem is also 1)"}; cvar_t cl_decals_bias = {CVAR_SAVE, "cl_decals_bias", "0.125", "distance to bias decals from surface to prevent depth fighting"}; cvar_t cl_decals_max = {CVAR_SAVE, "cl_decals_max", "4096", "maximum number of decals allowed to exist in the world at once"}; static void CL_Particles_ParseEffectInfo(const char *textstart, const char *textend, const char *filename) { int arrayindex; int argc; int i; int linenumber; particleeffectinfo_t *info = NULL; const char *text = textstart; char argv[16][1024]; for (linenumber = 1;;linenumber++) { argc = 0; for (arrayindex = 0;arrayindex < 16;arrayindex++) argv[arrayindex][0] = 0; for (;;) { if (!COM_ParseToken_Simple(&text, true, false, true)) return; if (!strcmp(com_token, "\n")) break; if (argc < 16) { strlcpy(argv[argc], com_token, sizeof(argv[argc])); argc++; } } if (argc < 1) continue; #define checkparms(n) if (argc != (n)) {Con_Printf("%s:%i: error while parsing: %s given %i parameters, should be %i parameters\n", filename, linenumber, argv[0], argc, (n));break;} #define readints(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = strtol(argv[1+arrayindex], NULL, 0) #define readfloats(array, n) checkparms(n+1);for (arrayindex = 0;arrayindex < argc - 1;arrayindex++) array[arrayindex] = atof(argv[1+arrayindex]) #define readint(var) checkparms(2);var = strtol(argv[1], NULL, 0) #define readfloat(var) checkparms(2);var = atof(argv[1]) #define readbool(var) checkparms(2);var = strtol(argv[1], NULL, 0) != 0 if (!strcmp(argv[0], "effect")) { int effectnameindex; checkparms(2); if (numparticleeffectinfo >= MAX_PARTICLEEFFECTINFO) { Con_Printf("%s:%i: too many effects!\n", filename, linenumber); break; } for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++) { if (particleeffectname[effectnameindex][0]) { if (!strcmp(particleeffectname[effectnameindex], argv[1])) break; } else { strlcpy(particleeffectname[effectnameindex], argv[1], sizeof(particleeffectname[effectnameindex])); break; } } // if we run out of names, abort if (effectnameindex == MAX_PARTICLEEFFECTNAME) { Con_Printf("%s:%i: too many effects!\n", filename, linenumber); break; } for(i = 0; i < numparticleeffectinfo; ++i) { info = particleeffectinfo + i; if(!(info->flags & PARTICLEEFFECT_DEFINED)) if(info->effectnameindex == effectnameindex) break; } if(i < numparticleeffectinfo) continue; info = particleeffectinfo + numparticleeffectinfo++; // copy entire info from baseline, then fix up the nameindex *info = baselineparticleeffectinfo; info->effectnameindex = effectnameindex; continue; } else if (info == NULL) { Con_Printf("%s:%i: command %s encountered before effect\n", filename, linenumber, argv[0]); break; } info->flags |= PARTICLEEFFECT_DEFINED; if (!strcmp(argv[0], "countabsolute")) {readfloat(info->countabsolute);} else if (!strcmp(argv[0], "count")) {readfloat(info->countmultiplier);} else if (!strcmp(argv[0], "type")) { checkparms(2); if (!strcmp(argv[1], "alphastatic")) info->particletype = pt_alphastatic; else if (!strcmp(argv[1], "static")) info->particletype = pt_static; else if (!strcmp(argv[1], "spark")) info->particletype = pt_spark; else if (!strcmp(argv[1], "beam")) info->particletype = pt_beam; else if (!strcmp(argv[1], "rain")) info->particletype = pt_rain; else if (!strcmp(argv[1], "raindecal")) info->particletype = pt_raindecal; else if (!strcmp(argv[1], "snow")) info->particletype = pt_snow; else if (!strcmp(argv[1], "bubble")) info->particletype = pt_bubble; else if (!strcmp(argv[1], "blood")) {info->particletype = pt_blood;info->gravity = 1;} else if (!strcmp(argv[1], "smoke")) info->particletype = pt_smoke; else if (!strcmp(argv[1], "decal")) info->particletype = pt_decal; else if (!strcmp(argv[1], "entityparticle")) info->particletype = pt_entityparticle; else Con_Printf("%s:%i: unrecognized particle type %s\n", filename, linenumber, argv[1]); info->blendmode = particletype[info->particletype].blendmode; info->orientation = particletype[info->particletype].orientation; } else if (!strcmp(argv[0], "blend")) { checkparms(2); if (!strcmp(argv[1], "alpha")) info->blendmode = PBLEND_ALPHA; else if (!strcmp(argv[1], "add")) info->blendmode = PBLEND_ADD; else if (!strcmp(argv[1], "invmod")) info->blendmode = PBLEND_INVMOD; else Con_Printf("%s:%i: unrecognized blendmode %s\n", filename, linenumber, argv[1]); } else if (!strcmp(argv[0], "orientation")) { checkparms(2); if (!strcmp(argv[1], "billboard")) info->orientation = PARTICLE_BILLBOARD; else if (!strcmp(argv[1], "spark")) info->orientation = PARTICLE_SPARK; else if (!strcmp(argv[1], "oriented")) info->orientation = PARTICLE_ORIENTED_DOUBLESIDED; else if (!strcmp(argv[1], "beam")) info->orientation = PARTICLE_HBEAM; else Con_Printf("%s:%i: unrecognized orientation %s\n", filename, linenumber, argv[1]); } else if (!strcmp(argv[0], "color")) {readints(info->color, 2);} else if (!strcmp(argv[0], "tex")) {readints(info->tex, 2);} else if (!strcmp(argv[0], "size")) {readfloats(info->size, 2);} else if (!strcmp(argv[0], "sizeincrease")) {readfloat(info->size[2]);} else if (!strcmp(argv[0], "alpha")) {readfloats(info->alpha, 3);} else if (!strcmp(argv[0], "time")) {readfloats(info->time, 2);} else if (!strcmp(argv[0], "gravity")) {readfloat(info->gravity);} else if (!strcmp(argv[0], "bounce")) {readfloat(info->bounce);} else if (!strcmp(argv[0], "airfriction")) {readfloat(info->airfriction);} else if (!strcmp(argv[0], "liquidfriction")) {readfloat(info->liquidfriction);} else if (!strcmp(argv[0], "originoffset")) {readfloats(info->originoffset, 3);} else if (!strcmp(argv[0], "relativeoriginoffset")) {readfloats(info->relativeoriginoffset, 3);} else if (!strcmp(argv[0], "velocityoffset")) {readfloats(info->velocityoffset, 3);} else if (!strcmp(argv[0], "relativevelocityoffset")) {readfloats(info->relativevelocityoffset, 3);} else if (!strcmp(argv[0], "originjitter")) {readfloats(info->originjitter, 3);} else if (!strcmp(argv[0], "velocityjitter")) {readfloats(info->velocityjitter, 3);} else if (!strcmp(argv[0], "velocitymultiplier")) {readfloat(info->velocitymultiplier);} else if (!strcmp(argv[0], "lightradius")) {readfloat(info->lightradiusstart);} else if (!strcmp(argv[0], "lightradiusfade")) {readfloat(info->lightradiusfade);} else if (!strcmp(argv[0], "lighttime")) {readfloat(info->lighttime);} else if (!strcmp(argv[0], "lightcolor")) {readfloats(info->lightcolor, 3);} else if (!strcmp(argv[0], "lightshadow")) {readbool(info->lightshadow);} else if (!strcmp(argv[0], "lightcubemapnum")) {readint(info->lightcubemapnum);} else if (!strcmp(argv[0], "lightcorona")) {readints(info->lightcorona, 2);} else if (!strcmp(argv[0], "underwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_UNDERWATER;} else if (!strcmp(argv[0], "notunderwater")) {checkparms(1);info->flags |= PARTICLEEFFECT_NOTUNDERWATER;} else if (!strcmp(argv[0], "trailspacing")) {readfloat(info->trailspacing);if (info->trailspacing > 0) info->countmultiplier = 1.0f / info->trailspacing;} else if (!strcmp(argv[0], "stretchfactor")) {readfloat(info->stretchfactor);} else if (!strcmp(argv[0], "staincolor")) {readints(info->staincolor, 2);} else if (!strcmp(argv[0], "stainalpha")) {readfloats(info->stainalpha, 2);} else if (!strcmp(argv[0], "stainsize")) {readfloats(info->stainsize, 2);} else if (!strcmp(argv[0], "staintex")) {readints(info->staintex, 2);} else if (!strcmp(argv[0], "stainless")) {info->staintex[0] = -2; info->staincolor[0] = (unsigned int)-1; info->staincolor[1] = (unsigned int)-1; info->stainalpha[0] = 1; info->stainalpha[1] = 1; info->stainsize[0] = 2; info->stainsize[1] = 2; } else if (!strcmp(argv[0], "rotate")) {readfloats(info->rotate, 4);} else Con_Printf("%s:%i: skipping unknown command %s\n", filename, linenumber, argv[0]); #undef checkparms #undef readints #undef readfloats #undef readint #undef readfloat } } int CL_ParticleEffectIndexForName(const char *name) { int i; for (i = 1;i < MAX_PARTICLEEFFECTNAME && particleeffectname[i][0];i++) if (!strcmp(particleeffectname[i], name)) return i; return 0; } const char *CL_ParticleEffectNameForIndex(int i) { if (i < 1 || i >= MAX_PARTICLEEFFECTNAME) return NULL; return particleeffectname[i]; } // MUST match effectnameindex_t in client.h static const char *standardeffectnames[EFFECT_TOTAL] = { "", "TE_GUNSHOT", "TE_GUNSHOTQUAD", "TE_SPIKE", "TE_SPIKEQUAD", "TE_SUPERSPIKE", "TE_SUPERSPIKEQUAD", "TE_WIZSPIKE", "TE_KNIGHTSPIKE", "TE_EXPLOSION", "TE_EXPLOSIONQUAD", "TE_TAREXPLOSION", "TE_TELEPORT", "TE_LAVASPLASH", "TE_SMALLFLASH", "TE_FLAMEJET", "EF_FLAME", "TE_BLOOD", "TE_SPARK", "TE_PLASMABURN", "TE_TEI_G3", "TE_TEI_SMOKE", "TE_TEI_BIGEXPLOSION", "TE_TEI_PLASMAHIT", "EF_STARDUST", "TR_ROCKET", "TR_GRENADE", "TR_BLOOD", "TR_WIZSPIKE", "TR_SLIGHTBLOOD", "TR_KNIGHTSPIKE", "TR_VORESPIKE", "TR_NEHAHRASMOKE", "TR_NEXUIZPLASMA", "TR_GLOWTRAIL", "SVC_PARTICLE" }; static void CL_Particles_LoadEffectInfo(const char *customfile) { int i; int filepass; unsigned char *filedata; fs_offset_t filesize; char filename[MAX_QPATH]; numparticleeffectinfo = 0; memset(particleeffectinfo, 0, sizeof(particleeffectinfo)); memset(particleeffectname, 0, sizeof(particleeffectname)); for (i = 0;i < EFFECT_TOTAL;i++) strlcpy(particleeffectname[i], standardeffectnames[i], sizeof(particleeffectname[i])); for (filepass = 0;;filepass++) { if (filepass == 0) { if (customfile) strlcpy(filename, customfile, sizeof(filename)); else strlcpy(filename, "effectinfo.txt", sizeof(filename)); } else if (filepass == 1) { if (!cl.worldbasename[0] || customfile) continue; dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", cl.worldnamenoextension); } else break; filedata = FS_LoadFile(filename, tempmempool, true, &filesize); if (!filedata) continue; CL_Particles_ParseEffectInfo((const char *)filedata, (const char *)filedata + filesize, filename); Mem_Free(filedata); } } static void CL_Particles_LoadEffectInfo_f(void) { CL_Particles_LoadEffectInfo(Cmd_Argc() > 1 ? Cmd_Argv(1) : NULL); } /* =============== CL_InitParticles =============== */ void CL_ReadPointFile_f (void); void CL_Particles_Init (void) { Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level)"); Cmd_AddCommand ("cl_particles_reloadeffects", CL_Particles_LoadEffectInfo_f, "reloads effectinfo.txt and maps/levelname_effectinfo.txt (where levelname is the current map) if parameter is given, loads from custom file (no levelname_effectinfo are loaded in this case)"); Cvar_RegisterVariable (&cl_particles); Cvar_RegisterVariable (&cl_particles_quality); Cvar_RegisterVariable (&cl_particles_alpha); Cvar_RegisterVariable (&cl_particles_size); Cvar_RegisterVariable (&cl_particles_quake); Cvar_RegisterVariable (&cl_particles_blood); Cvar_RegisterVariable (&cl_particles_blood_alpha); Cvar_RegisterVariable (&cl_particles_blood_decal_alpha); Cvar_RegisterVariable (&cl_particles_blood_decal_scalemin); Cvar_RegisterVariable (&cl_particles_blood_decal_scalemax); Cvar_RegisterVariable (&cl_particles_blood_bloodhack); Cvar_RegisterVariable (&cl_particles_explosions_sparks); Cvar_RegisterVariable (&cl_particles_explosions_shell); Cvar_RegisterVariable (&cl_particles_bulletimpacts); Cvar_RegisterVariable (&cl_particles_rain); Cvar_RegisterVariable (&cl_particles_snow); Cvar_RegisterVariable (&cl_particles_smoke); Cvar_RegisterVariable (&cl_particles_smoke_alpha); Cvar_RegisterVariable (&cl_particles_smoke_alphafade); Cvar_RegisterVariable (&cl_particles_sparks); Cvar_RegisterVariable (&cl_particles_bubbles); Cvar_RegisterVariable (&cl_particles_visculling); Cvar_RegisterVariable (&cl_particles_collisions); Cvar_RegisterVariable (&cl_particles_forcetraileffects); Cvar_RegisterVariable (&cl_decals); Cvar_RegisterVariable (&cl_decals_visculling); Cvar_RegisterVariable (&cl_decals_time); Cvar_RegisterVariable (&cl_decals_fadetime); Cvar_RegisterVariable (&cl_decals_newsystem); Cvar_RegisterVariable (&cl_decals_newsystem_intensitymultiplier); Cvar_RegisterVariable (&cl_decals_newsystem_immediatebloodstain); Cvar_RegisterVariable (&cl_decals_newsystem_bloodsmears); Cvar_RegisterVariable (&cl_decals_models); Cvar_RegisterVariable (&cl_decals_bias); Cvar_RegisterVariable (&cl_decals_max); } void CL_Particles_Shutdown (void) { } void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha); void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2); // list of all 26 parameters: // ptype - any of the pt_ enum values (pt_static, pt_blood, etc), see ptype_t near the top of this file // pcolor1,pcolor2 - minimum and maximum ranges of color, randomly interpolated to decide particle color // ptex - any of the tex_ values such as tex_smoke[rand()&7] or tex_particle // psize - size of particle (or thickness for PARTICLE_SPARK and PARTICLE_*BEAM) // palpha - opacity of particle as 0-255 (can be more than 255) // palphafade - rate of fade per second (so 256 would mean a 256 alpha particle would fade to nothing in 1 second) // ptime - how long the particle can live (note it is also removed if alpha drops to nothing) // pgravity - how much effect gravity has on the particle (0-1) // pbounce - how much bounce the particle has when it hits a surface (0-1), -1 makes a blood splat when it hits a surface, 0 does not even check for collisions // px,py,pz - starting origin of particle // pvx,pvy,pvz - starting velocity of particle // pfriction - how much the particle slows down per second (0-1 typically, can slowdown faster than 1) // blendmode - one of the PBLEND_ values // orientation - one of the PARTICLE_ values // staincolor1, staincolor2: minimum and maximum ranges of stain color, randomly interpolated to decide stain color (-1 to use none) // staintex: any of the tex_ values such as tex_smoke[rand()&7] or tex_particle (-1 to use none) // stainalpha: opacity of the stain as factor for alpha // stainsize: size of the stain as factor for palpha // angle: base rotation of the particle geometry around its center normal // spin: rotation speed of the particle geometry around its center normal particle_t *CL_NewParticle(const vec3_t sortorigin, unsigned short ptypeindex, int pcolor1, int pcolor2, int ptex, float psize, float psizeincrease, float palpha, float palphafade, float pgravity, float pbounce, float px, float py, float pz, float pvx, float pvy, float pvz, float pairfriction, float pliquidfriction, float originjitter, float velocityjitter, qboolean pqualityreduction, float lifetime, float stretch, pblend_t blendmode, porientation_t orientation, int staincolor1, int staincolor2, int staintex, float stainalpha, float stainsize, float angle, float spin, float tint[4]) { int l1, l2, r, g, b; particle_t *part; vec3_t v; if (!cl_particles.integer) return NULL; for (;cl.free_particle < cl.max_particles && cl.particles[cl.free_particle].typeindex;cl.free_particle++); if (cl.free_particle >= cl.max_particles) return NULL; if (!lifetime) lifetime = palpha / min(1, palphafade); part = &cl.particles[cl.free_particle++]; if (cl.num_particles < cl.free_particle) cl.num_particles = cl.free_particle; memset(part, 0, sizeof(*part)); VectorCopy(sortorigin, part->sortorigin); part->typeindex = ptypeindex; part->blendmode = blendmode; if(orientation == PARTICLE_HBEAM || orientation == PARTICLE_VBEAM) { particletexture_t *tex = &particletexture[ptex]; if(tex->t1 == 0 && tex->t2 == 1) // full height of texture? part->orientation = PARTICLE_VBEAM; else part->orientation = PARTICLE_HBEAM; } else part->orientation = orientation; l2 = (int)lhrandom(0.5, 256.5); l1 = 256 - l2; part->color[0] = ((((pcolor1 >> 16) & 0xFF) * l1 + ((pcolor2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; part->color[1] = ((((pcolor1 >> 8) & 0xFF) * l1 + ((pcolor2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; part->color[2] = ((((pcolor1 >> 0) & 0xFF) * l1 + ((pcolor2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; if (vid.sRGB3D) { part->color[0] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[0]) * 255.0f + 0.5f); part->color[1] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[1]) * 255.0f + 0.5f); part->color[2] = (unsigned char)floor(Image_LinearFloatFromsRGB(part->color[2]) * 255.0f + 0.5f); } part->alpha = palpha; part->alphafade = palphafade; part->staintexnum = staintex; if(staincolor1 >= 0 && staincolor2 >= 0) { l2 = (int)lhrandom(0.5, 256.5); l1 = 256 - l2; if(blendmode == PBLEND_INVMOD) { r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * (255 - part->color[0])) / 0x8000; // staincolor 0x808080 keeps color invariant g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * (255 - part->color[1])) / 0x8000; b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * (255 - part->color[2])) / 0x8000; } else { r = ((((staincolor1 >> 16) & 0xFF) * l1 + ((staincolor2 >> 16) & 0xFF) * l2) * part->color[0]) / 0x8000; // staincolor 0x808080 keeps color invariant g = ((((staincolor1 >> 8) & 0xFF) * l1 + ((staincolor2 >> 8) & 0xFF) * l2) * part->color[1]) / 0x8000; b = ((((staincolor1 >> 0) & 0xFF) * l1 + ((staincolor2 >> 0) & 0xFF) * l2) * part->color[2]) / 0x8000; } if(r > 0xFF) r = 0xFF; if(g > 0xFF) g = 0xFF; if(b > 0xFF) b = 0xFF; } else { r = part->color[0]; // -1 is shorthand for stain = particle color g = part->color[1]; b = part->color[2]; } part->staincolor[0] = r; part->staincolor[1] = g; part->staincolor[2] = b; part->stainalpha = palpha * stainalpha; part->stainsize = psize * stainsize; if(tint) { if(blendmode != PBLEND_INVMOD) // invmod is immune to tinting { part->color[0] *= tint[0]; part->color[1] *= tint[1]; part->color[2] *= tint[2]; } part->alpha *= tint[3]; part->alphafade *= tint[3]; part->stainalpha *= tint[3]; } part->texnum = ptex; part->size = psize; part->sizeincrease = psizeincrease; part->gravity = pgravity; part->bounce = pbounce; part->stretch = stretch; VectorRandom(v); part->org[0] = px + originjitter * v[0]; part->org[1] = py + originjitter * v[1]; part->org[2] = pz + originjitter * v[2]; part->vel[0] = pvx + velocityjitter * v[0]; part->vel[1] = pvy + velocityjitter * v[1]; part->vel[2] = pvz + velocityjitter * v[2]; part->time2 = 0; part->airfriction = pairfriction; part->liquidfriction = pliquidfriction; part->die = cl.time + lifetime; part->delayedspawn = cl.time; // part->delayedcollisions = 0; part->qualityreduction = pqualityreduction; part->angle = angle; part->spin = spin; // if it is rain or snow, trace ahead and shut off collisions until an actual collision event needs to occur to improve performance if (part->typeindex == pt_rain) { int i; particle_t *part2; vec3_t endvec; trace_t trace; // turn raindrop into simple spark and create delayedspawn splash effect part->typeindex = pt_spark; part->bounce = 0; VectorMA(part->org, lifetime, part->vel, endvec); trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK, 0, collision_extendmovelength.value, true, false, NULL, false, false); part->die = cl.time + lifetime * trace.fraction; part2 = CL_NewParticle(endvec, pt_raindecal, pcolor1, pcolor2, tex_rainsplash, part->size, part->size * 20, part->alpha, part->alpha / 0.4, 0, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0], trace.plane.normal[1], trace.plane.normal[2], 0, 0, 0, 0, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_ORIENTED_DOUBLESIDED, -1, -1, -1, 1, 1, 0, 0, NULL); if (part2) { part2->delayedspawn = part->die; part2->die += part->die - cl.time; for (i = rand() & 7;i < 10;i++) { part2 = CL_NewParticle(endvec, pt_spark, pcolor1, pcolor2, tex_particle, 0.25f, 0, part->alpha * 2, part->alpha * 4, 1, 0, trace.endpos[0] + trace.plane.normal[0], trace.endpos[1] + trace.plane.normal[1], trace.endpos[2] + trace.plane.normal[2], trace.plane.normal[0] * 16, trace.plane.normal[1] * 16, trace.plane.normal[2] * 16 + cl.movevars_gravity * 0.04, 0, 0, 0, 32, pqualityreduction, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); if (part2) { part2->delayedspawn = part->die; part2->die += part->die - cl.time; } } } } #if 0 else if (part->bounce != 0 && part->gravity == 0 && part->typeindex != pt_snow) { float lifetime = part->alpha / (part->alphafade ? part->alphafade : 1); vec3_t endvec; trace_t trace; VectorMA(part->org, lifetime, part->vel, endvec); trace = CL_TraceLine(part->org, endvec, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY, true, false, NULL, false); part->delayedcollisions = cl.time + lifetime * trace.fraction - 0.1; } #endif return part; } static void CL_ImmediateBloodStain(particle_t *part) { vec3_t v; int staintex; // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot if (part->staintexnum >= 0 && cl_decals_newsystem.integer && cl_decals.integer) { VectorCopy(part->vel, v); VectorNormalize(v); staintex = part->staintexnum; R_DecalSystem_SplatEntities(part->org, v, 1-part->staincolor[0]*(1.0f/255.0f), 1-part->staincolor[1]*(1.0f/255.0f), 1-part->staincolor[2]*(1.0f/255.0f), part->stainalpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->stainsize); } // blood creates a splash at spawn, not just at impact, this makes monsters bloody where they are shot if (part->typeindex == pt_blood && cl_decals_newsystem.integer && cl_decals.integer) { VectorCopy(part->vel, v); VectorNormalize(v); staintex = tex_blooddecal[rand()&7]; R_DecalSystem_SplatEntities(part->org, v, part->color[0]*(1.0f/255.0f), part->color[1]*(1.0f/255.0f), part->color[2]*(1.0f/255.0f), part->alpha*(1.0f/255.0f), particletexture[staintex].s1, particletexture[staintex].t1, particletexture[staintex].s2, particletexture[staintex].t2, part->size * 2); } } void CL_SpawnDecalParticleForSurface(int hitent, const vec3_t org, const vec3_t normal, int color1, int color2, int texnum, float size, float alpha) { int l1, l2; decal_t *decal; entity_render_t *ent = &cl.entities[hitent].render; unsigned char color[3]; if (!cl_decals.integer) return; if (!ent->allowdecals) return; l2 = (int)lhrandom(0.5, 256.5); l1 = 256 - l2; color[0] = ((((color1 >> 16) & 0xFF) * l1 + ((color2 >> 16) & 0xFF) * l2) >> 8) & 0xFF; color[1] = ((((color1 >> 8) & 0xFF) * l1 + ((color2 >> 8) & 0xFF) * l2) >> 8) & 0xFF; color[2] = ((((color1 >> 0) & 0xFF) * l1 + ((color2 >> 0) & 0xFF) * l2) >> 8) & 0xFF; if (cl_decals_newsystem.integer) { if (vid.sRGB3D) R_DecalSystem_SplatEntities(org, normal, Image_LinearFloatFromsRGB(color[0]), Image_LinearFloatFromsRGB(color[1]), Image_LinearFloatFromsRGB(color[2]), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); else R_DecalSystem_SplatEntities(org, normal, color[0]*(1.0f/255.0f), color[1]*(1.0f/255.0f), color[2]*(1.0f/255.0f), alpha*(1.0f/255.0f), particletexture[texnum].s1, particletexture[texnum].t1, particletexture[texnum].s2, particletexture[texnum].t2, size); return; } for (;cl.free_decal < cl.max_decals && cl.decals[cl.free_decal].typeindex;cl.free_decal++); if (cl.free_decal >= cl.max_decals) return; decal = &cl.decals[cl.free_decal++]; if (cl.num_decals < cl.free_decal) cl.num_decals = cl.free_decal; memset(decal, 0, sizeof(*decal)); decal->decalsequence = cl.decalsequence++; decal->typeindex = pt_decal; decal->texnum = texnum; VectorMA(org, cl_decals_bias.value, normal, decal->org); VectorCopy(normal, decal->normal); decal->size = size; decal->alpha = alpha; decal->time2 = cl.time; decal->color[0] = color[0]; decal->color[1] = color[1]; decal->color[2] = color[2]; if (vid.sRGB3D) { decal->color[0] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[0]) * 256.0f); decal->color[1] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[1]) * 256.0f); decal->color[2] = (unsigned char)(Image_LinearFloatFromsRGB(decal->color[2]) * 256.0f); } decal->owner = hitent; decal->clusterindex = -1000; // no vis culling unless we're sure if (hitent) { // these relative things are only used to regenerate p->org and p->vel if decal->owner is not world (0) decal->ownermodel = cl.entities[decal->owner].render.model; Matrix4x4_Transform(&cl.entities[decal->owner].render.inversematrix, org, decal->relativeorigin); Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.inversematrix, normal, decal->relativenormal); } else { if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) { mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, decal->org); if(leaf) decal->clusterindex = leaf->clusterindex; } } } void CL_SpawnDecalParticleForPoint(const vec3_t org, float maxdist, float size, float alpha, int texnum, int color1, int color2) { int i; vec_t bestfrac; vec3_t bestorg; vec3_t bestnormal; vec3_t org2; int besthitent = 0, hitent; trace_t trace; bestfrac = 10; for (i = 0;i < 32;i++) { VectorRandom(org2); VectorMA(org, maxdist, org2, org2); trace = CL_TraceLine(org, org2, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY, 0, collision_extendmovelength.value, true, false, &hitent, false, true); // take the closest trace result that doesn't end up hitting a NOMARKS // surface (sky for example) if (bestfrac > trace.fraction && !(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) { bestfrac = trace.fraction; besthitent = hitent; VectorCopy(trace.endpos, bestorg); VectorCopy(trace.plane.normal, bestnormal); } } if (bestfrac < 1) CL_SpawnDecalParticleForSurface(besthitent, bestorg, bestnormal, color1, color2, texnum, size, alpha); } static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount); static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount); static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail); static void CL_ParticleEffect_Fallback(int effectnameindex, float count, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, qboolean wanttrail) { vec3_t center; matrix4x4_t lightmatrix; particle_t *part; VectorLerp(originmins, 0.5, originmaxs, center); Matrix4x4_CreateTranslate(&lightmatrix, center[0], center[1], center[2]); if (effectnameindex == EFFECT_SVC_PARTICLE) { if (cl_particles.integer) { // bloodhack checks if this effect's color matches regular or lightning blood and if so spawns a blood effect instead if (count == 1024) CL_NewParticlesFromEffectinfo(EFFECT_TE_EXPLOSION, 1, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else if (cl_particles_blood_bloodhack.integer && !cl_particles_quake.integer && (palettecolor == 73 || palettecolor == 225)) CL_NewParticlesFromEffectinfo(EFFECT_TE_BLOOD, count / 2.0f, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else { count *= cl_particles_quality.value; for (;count > 0;count--) { int k = particlepalette[(palettecolor & ~7) + (rand()&7)]; CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 1.5, 0, 255, 0, 0.05, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 8, 0, true, lhrandom(0.1, 0.5), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } } } else if (effectnameindex == EFFECT_TE_WIZSPIKE) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 30*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 20, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else if (effectnameindex == EFFECT_TE_KNIGHTSPIKE) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 226, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else if (effectnameindex == EFFECT_TE_SPIKE) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) { if (cl_particles_smoke.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); } else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); } else if (effectnameindex == EFFECT_TE_SPIKEQUAD) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) { if (cl_particles_smoke.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 10*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); } else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 15*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_SUPERSPIKE) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) { if (cl_particles_smoke.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); } else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); } else if (effectnameindex == EFFECT_TE_SUPERSPIKEQUAD) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) { if (cl_particles_smoke.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); } else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 8*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 30*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_BLOOD) { if (!cl_particles_blood.integer) return; if (cl_particles_quake.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 2*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 73, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else { static double bloodaccumulator = 0; qboolean immediatebloodstain = (cl_decals_newsystem_immediatebloodstain.integer >= 1); //CL_NewParticle(center, pt_alphastatic, 0x4f0000,0x7f0000, tex_particle, 2.5, 0, 256, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 1, 4, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, NULL); bloodaccumulator += count * 0.333 * cl_particles_quality.value; for (;bloodaccumulator > 0;bloodaccumulator--) { part = CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, cl_particles_blood_alpha.value * 768, cl_particles_blood_alpha.value * 384, 1, -1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); if (immediatebloodstain && part) { immediatebloodstain = false; CL_ImmediateBloodStain(part); } } } } else if (effectnameindex == EFFECT_TE_SPARK) CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, count); else if (effectnameindex == EFFECT_TE_PLASMABURN) { // plasma scorch mark R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 6, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); CL_AllocLightFlash(NULL, &lightmatrix, 200, 1, 1, 1, 1000, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_GUNSHOT) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); } else if (effectnameindex == EFFECT_TE_GUNSHOTQUAD) { if (cl_particles_bulletimpacts.integer) { if (cl_particles_quake.integer) CL_NewParticlesFromEffectinfo(EFFECT_SVC_PARTICLE, 20*count, originmins, originmaxs, velocitymins, velocitymaxs, NULL, 0, spawndlight, spawnparticles, NULL, NULL, 1, wanttrail); else { CL_Smoke(originmins, originmaxs, velocitymins, velocitymaxs, 4*count); CL_Sparks(originmins, originmaxs, velocitymins, velocitymaxs, 20*count); CL_NewParticle(center, pt_static, 0x808080,0x808080, tex_particle, 3, 0, 256, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } // bullet hole R_Stain(center, 16, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 3, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); CL_AllocLightFlash(NULL, &lightmatrix, 100, 0.15f, 0.15f, 1.5f, 500, 0.2, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_EXPLOSION) { CL_ParticleExplosion(center); CL_AllocLightFlash(NULL, &lightmatrix, 350, 4.0f, 2.0f, 0.50f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_EXPLOSIONQUAD) { CL_ParticleExplosion(center); CL_AllocLightFlash(NULL, &lightmatrix, 350, 2.5f, 2.0f, 4.0f, 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_TAREXPLOSION) { if (cl_particles_quake.integer) { int i; for (i = 0;i < 1024 * cl_particles_quality.value;i++) { if (i & 1) CL_NewParticle(center, pt_alphastatic, particlepalette[66], particlepalette[71], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, 0, -4, -4, 16, 256, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); else CL_NewParticle(center, pt_alphastatic, particlepalette[150], particlepalette[155], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0], center[1], center[2], 0, 0, lhrandom(-256, 256), 0, 0, 16, 0, true, (rand() & 1) ? 1.4 : 1.0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else CL_ParticleExplosion(center); CL_AllocLightFlash(NULL, &lightmatrix, 600, 1.6f, 0.8f, 2.0f, 1200, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_SMALLFLASH) CL_AllocLightFlash(NULL, &lightmatrix, 200, 2, 2, 2, 1000, 0.2, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); else if (effectnameindex == EFFECT_TE_FLAMEJET) { count *= cl_particles_quality.value; while (count-- > 0) CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 1.1, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (effectnameindex == EFFECT_TE_LAVASPLASH) { float i, j, inc, vel; vec3_t dir, org; inc = 8 / cl_particles_quality.value; for (i = -128;i < 128;i += inc) { for (j = -128;j < 128;j += inc) { dir[0] = j + lhrandom(0, inc); dir[1] = i + lhrandom(0, inc); dir[2] = 256; org[0] = center[0] + dir[0]; org[1] = center[1] + dir[1]; org[2] = center[2] + lhrandom(0, 64); vel = lhrandom(50, 120) / VectorLength(dir); // normalize and scale CL_NewParticle(center, pt_alphastatic, particlepalette[224], particlepalette[231], tex_particle, 1.5f, 0, 255, 0, 0.05, 0, org[0], org[1], org[2], dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(2, 2.62), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } } else if (effectnameindex == EFFECT_TE_TELEPORT) { float i, j, k, inc, vel; vec3_t dir; if (cl_particles_quake.integer) inc = 4 / cl_particles_quality.value; else inc = 8 / cl_particles_quality.value; for (i = -16;i < 16;i += inc) { for (j = -16;j < 16;j += inc) { for (k = -24;k < 32;k += inc) { VectorSet(dir, i*8, j*8, k*8); VectorNormalize(dir); vel = lhrandom(50, 113); if (cl_particles_quake.integer) CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, 255, 0, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, lhrandom(0.2, 0.34), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); else CL_NewParticle(center, pt_alphastatic, particlepalette[7], particlepalette[14], tex_particle, 1.5f, 0, inc * lhrandom(37, 63), inc * 187, 0, 0, center[0] + i + lhrandom(0, inc), center[1] + j + lhrandom(0, inc), center[2] + k + lhrandom(0, inc), dir[0] * vel, dir[1] * vel, dir[2] * vel, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } } if (!cl_particles_quake.integer) CL_NewParticle(center, pt_static, 0xffffff, 0xffffff, tex_particle, 30, 0, 256, 512, 0, 0, center[0], center[1], center[2], 0, 0, 0, 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 2.0f, 2.0f, 400, 99.0f, 0, -1, true, 1, 0.25, 1, 0, 0, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_TEI_G3) CL_NewParticle(center, pt_beam, 0xFFFFFF, 0xFFFFFF, tex_beam, 8, 0, 256, 256, 0, 0, originmins[0], originmins[1], originmins[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, 0, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); else if (effectnameindex == EFFECT_TE_TEI_SMOKE) { if (cl_particles_smoke.integer) { count *= 0.25f * cl_particles_quality.value; while (count-- > 0) CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 1.5f, 6.0f, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TE_TEI_BIGEXPLOSION) { CL_ParticleExplosion(center); CL_AllocLightFlash(NULL, &lightmatrix, 500, 2.5f, 2.0f, 1.0f, 500, 9999, 0, -1, true, 1, 0.25, 0.5, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_TE_TEI_PLASMAHIT) { float f; R_Stain(center, 40, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(center, 6, 8, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); if (cl_particles_smoke.integer) for (f = 0;f < count;f += 4.0f / cl_particles_quality.value) CL_NewParticle(center, pt_smoke, 0x202020, 0x404040, tex_smoke[rand()&7], 5, 0, 255, 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 20, 155, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); if (cl_particles_sparks.integer) for (f = 0;f < count;f += 1.0f / cl_particles_quality.value) CL_NewParticle(center, pt_spark, 0x2030FF, 0x80C0FF, tex_particle, 2.0f, 0, lhrandom(64, 255), 512, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, 465, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); CL_AllocLightFlash(NULL, &lightmatrix, 500, 0.6f, 1.2f, 2.0f, 2000, 9999, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_EF_FLAME) { if (!spawnparticles) count = 0; count *= 300 * cl_particles_quality.value; while (count-- > 0) CL_NewParticle(center, pt_smoke, 0x6f0f00, 0xe3974f, tex_particle, 4, 0, lhrandom(64, 128), 384, -1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_AllocLightFlash(NULL, &lightmatrix, 200, 2.0f, 1.5f, 0.5f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (effectnameindex == EFFECT_EF_STARDUST) { if (!spawnparticles) count = 0; count *= 200 * cl_particles_quality.value; while (count-- > 0) CL_NewParticle(center, pt_static, 0x903010, 0xFFD030, tex_particle, 4, 0, lhrandom(64, 128), 128, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0.2, 0.8, 16, 128, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_AllocLightFlash(NULL, &lightmatrix, 200, 1.0f, 0.7f, 0.3f, 0, 0, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (!strncmp(particleeffectname[effectnameindex], "TR_", 3)) { vec3_t dir, pos; float len, dec, qd; int smoke, blood, bubbles, r, color, spawnedcount; if (spawndlight && r_refdef.scene.numlights < MAX_DLIGHTS) { vec4_t light; Vector4Set(light, 0, 0, 0, 0); if (effectnameindex == EFFECT_TR_ROCKET) Vector4Set(light, 3.0f, 1.5f, 0.5f, 200); else if (effectnameindex == EFFECT_TR_VORESPIKE) { if (gamemode == GAME_PRYDON && !cl_particles_quake.integer) Vector4Set(light, 0.3f, 0.6f, 1.2f, 100); else Vector4Set(light, 1.2f, 0.5f, 1.0f, 200); } else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) Vector4Set(light, 0.75f, 1.5f, 3.0f, 200); if (light[3]) { matrix4x4_t traillightmatrix; Matrix4x4_CreateFromQuakeEntity(&traillightmatrix, originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, light[3]); R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &traillightmatrix, light, -1, NULL, true, 1, 0.25, 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } } if (!spawnparticles) return; if (originmaxs[0] == originmins[0] && originmaxs[1] == originmins[1] && originmaxs[2] == originmins[2]) return; VectorSubtract(originmaxs, originmins, dir); len = VectorNormalizeLength(dir); if (ent) { dec = -ent->persistent.trail_time; ent->persistent.trail_time += len; if (ent->persistent.trail_time < 0.01f) return; // if we skip out, leave it reset ent->persistent.trail_time = 0.0f; } else dec = 0; // advance into this frame to reach the first puff location VectorMA(originmins, dec, dir, pos); len -= dec; smoke = cl_particles.integer && cl_particles_smoke.integer; blood = cl_particles.integer && cl_particles_blood.integer; bubbles = cl_particles.integer && cl_particles_bubbles.integer && !cl_particles_quake.integer && (CL_PointSuperContents(pos) & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)); qd = 1.0f / cl_particles_quality.value; spawnedcount = 0; while (len >= 0 && ++spawnedcount <= 16384) { dec = 3; if (blood) { if (effectnameindex == EFFECT_TR_BLOOD) { if (cl_particles_quake.integer) { color = particlepalette[67 + (rand()&3)]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { dec = 16; CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TR_SLIGHTBLOOD) { if (cl_particles_quake.integer) { dec = 6; color = particlepalette[67 + (rand()&3)]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 2, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { dec = 32; CL_NewParticle(center, pt_blood, 0xFFFFFF, 0xFFFFFF, tex_bloodparticle[rand()&7], 8, 0, qd * cl_particles_blood_alpha.value * 768.0f, qd * cl_particles_blood_alpha.value * 384.0f, 1, -1, pos[0], pos[1], pos[2], lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 1, 4, 0, 64, true, 0, 1, PBLEND_INVMOD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } } if (smoke) { if (effectnameindex == EFFECT_TR_ROCKET) { if (cl_particles_quake.integer) { r = rand()&3; color = particlepalette[ramp3[r]]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*62, cl_particles_smoke_alphafade.value*62, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_NewParticle(center, pt_static, 0x801010, 0xFFA020, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*288, cl_particles_smoke_alphafade.value*1400, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 20, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TR_GRENADE) { if (cl_particles_quake.integer) { r = 2 + (rand()%5); color = particlepalette[ramp3[r]]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, -0.05, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 3, 0, true, 0.1372549*(6-r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { CL_NewParticle(center, pt_smoke, 0x303030, 0x606060, tex_smoke[rand()&7], 3, 0, cl_particles_smoke_alpha.value*50, cl_particles_smoke_alphafade.value*75, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TR_WIZSPIKE) { if (cl_particles_quake.integer) { dec = 6; color = particlepalette[52 + (rand()&7)]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (gamemode == GAME_GOODVSBAD2) { dec = 6; CL_NewParticle(center, pt_static, 0x00002E, 0x000030, tex_particle, 6, 0, 128, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { color = particlepalette[20 + (rand()&7)]; CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TR_KNIGHTSPIKE) { if (cl_particles_quake.integer) { dec = 6; color = particlepalette[230 + (rand()&7)]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*dir[1], 30*-dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 30*-dir[1], 30*dir[0], 0, 0, 0, 0, 0, true, 0.5, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { color = particlepalette[226 + (rand()&7)]; CL_NewParticle(center, pt_static, color, color, tex_particle, 2, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } else if (effectnameindex == EFFECT_TR_VORESPIKE) { if (cl_particles_quake.integer) { color = particlepalette[152 + (rand()&3)]; CL_NewParticle(center, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 8, 0, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (gamemode == GAME_GOODVSBAD2) { dec = 6; CL_NewParticle(center, pt_alphastatic, particlepalette[0 + (rand()&255)], particlepalette[0 + (rand()&255)], tex_particle, 6, 0, 255, 384, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (gamemode == GAME_PRYDON) { dec = 6; CL_NewParticle(center, pt_static, 0x103040, 0x204050, tex_particle, 6, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else CL_NewParticle(center, pt_static, 0x502030, 0x502030, tex_particle, 3, 0, 64, 192, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (effectnameindex == EFFECT_TR_NEHAHRASMOKE) { dec = 7; CL_NewParticle(center, pt_alphastatic, 0x303030, 0x606060, tex_smoke[rand()&7], 7, 0, 64, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, lhrandom(4, 12), 0, 0, 0, 4, false, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (effectnameindex == EFFECT_TR_NEXUIZPLASMA) { dec = 4; CL_NewParticle(center, pt_static, 0x283880, 0x283880, tex_particle, 4, 0, 255, 1024, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else if (effectnameindex == EFFECT_TR_GLOWTRAIL) CL_NewParticle(center, pt_alphastatic, particlepalette[palettecolor], particlepalette[palettecolor], tex_particle, 5, 0, 128, 320, 0, 0, pos[0], pos[1], pos[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } if (bubbles) { if (effectnameindex == EFFECT_TR_ROCKET) CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); else if (effectnameindex == EFFECT_TR_GRENADE) CL_NewParticle(center, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 512), 512, -0.25, 1.5, pos[0], pos[1], pos[2], 0, 0, 0, 0.0625, 0.25, 0, 16, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } // advance to next time and position dec *= qd; len -= dec; VectorMA (pos, dec, dir, pos); } if (ent) ent->persistent.trail_time = len; } else Con_DPrintf("CL_ParticleEffect_Fallback: no fallback found for effect %s\n", particleeffectname[effectnameindex]); } // this is also called on point effects with spawndlight = true and // spawnparticles = true static void CL_NewParticlesFromEffectinfo(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade, qboolean wanttrail) { qboolean found = false; char vabuf[1024]; if (effectnameindex < 1 || effectnameindex >= MAX_PARTICLEEFFECTNAME || !particleeffectname[effectnameindex][0]) { Con_DPrintf("Unknown effect number %i received from server\n", effectnameindex); return; // no such effect } if (!cl_particles_quake.integer && particleeffectinfo[0].effectnameindex) { int effectinfoindex; int supercontents; int tex, staintex; particleeffectinfo_t *info; vec3_t center; vec3_t traildir; vec3_t trailpos; vec3_t rvec; vec3_t angles; vec3_t velocity; vec3_t forward; vec3_t right; vec3_t up; vec_t traillen; vec_t trailstep; qboolean underwater; qboolean immediatebloodstain; particle_t *part; float avgtint[4], tint[4], tintlerp; // note this runs multiple effects with the same name, each one spawns only one kind of particle, so some effects need more than one VectorLerp(originmins, 0.5, originmaxs, center); supercontents = CL_PointSuperContents(center); underwater = (supercontents & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME)) != 0; VectorSubtract(originmaxs, originmins, traildir); traillen = VectorLength(traildir); VectorNormalize(traildir); if(tintmins) { Vector4Lerp(tintmins, 0.5, tintmaxs, avgtint); } else { Vector4Set(avgtint, 1, 1, 1, 1); } for (effectinfoindex = 0, info = particleeffectinfo;effectinfoindex < MAX_PARTICLEEFFECTINFO && info->effectnameindex;effectinfoindex++, info++) { if ((info->effectnameindex == effectnameindex) && (info->flags & PARTICLEEFFECT_DEFINED)) { qboolean definedastrail = info->trailspacing > 0; qboolean drawastrail = wanttrail; if (cl_particles_forcetraileffects.integer) drawastrail = drawastrail || definedastrail; found = true; if ((info->flags & PARTICLEEFFECT_UNDERWATER) && !underwater) continue; if ((info->flags & PARTICLEEFFECT_NOTUNDERWATER) && underwater) continue; // spawn a dlight if requested if (info->lightradiusstart > 0 && spawndlight) { matrix4x4_t tempmatrix; if (drawastrail) Matrix4x4_CreateTranslate(&tempmatrix, originmaxs[0], originmaxs[1], originmaxs[2]); else Matrix4x4_CreateTranslate(&tempmatrix, center[0], center[1], center[2]); if (info->lighttime > 0 && info->lightradiusfade > 0) { // light flash (explosion, etc) // called when effect starts CL_AllocLightFlash(NULL, &tempmatrix, info->lightradiusstart, info->lightcolor[0]*avgtint[0]*avgtint[3], info->lightcolor[1]*avgtint[1]*avgtint[3], info->lightcolor[2]*avgtint[2]*avgtint[3], info->lightradiusfade, info->lighttime, info->lightcubemapnum, -1, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); } else if (r_refdef.scene.numlights < MAX_DLIGHTS) { // glowing entity // called by CL_LinkNetworkEntity Matrix4x4_Scale(&tempmatrix, info->lightradiusstart, 1); rvec[0] = info->lightcolor[0]*avgtint[0]*avgtint[3]; rvec[1] = info->lightcolor[1]*avgtint[1]*avgtint[3]; rvec[2] = info->lightcolor[2]*avgtint[2]*avgtint[3]; R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &tempmatrix, rvec, -1, info->lightcubemapnum > 0 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", info->lightcubemapnum) : NULL, info->lightshadow, info->lightcorona[0], info->lightcorona[1], 0, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights];r_refdef.scene.numlights++; } } if (!spawnparticles) continue; // spawn particles tex = info->tex[0]; if (info->tex[1] > info->tex[0]) { tex = (int)lhrandom(info->tex[0], info->tex[1]); tex = min(tex, info->tex[1] - 1); } if(info->staintex[0] < 0) staintex = info->staintex[0]; else { staintex = (int)lhrandom(info->staintex[0], info->staintex[1]); staintex = min(staintex, info->staintex[1] - 1); } if (info->particletype == pt_decal) { VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity); AnglesFromVectors(angles, velocity, NULL, false); AngleVectors(angles, forward, right, up); VectorMAMAMAM(1.0f, center, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); CL_SpawnDecalParticleForPoint(trailpos, info->originjitter[0], lhrandom(info->size[0], info->size[1]), lhrandom(info->alpha[0], info->alpha[1])*avgtint[3], tex, info->color[0], info->color[1]); } else if (info->orientation == PARTICLE_HBEAM) { if (!drawastrail) continue; AnglesFromVectors(angles, traildir, NULL, false); AngleVectors(angles, forward, right, up); VectorMAMAM(info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], 0, 0, originmins[0] + trailpos[0], originmins[1] + trailpos[1], originmins[2] + trailpos[2], originmaxs[0], originmaxs[1], originmaxs[2], 0, 0, 0, 0, false, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), 0, 0, tintmins ? avgtint : NULL); } else { float cnt; if (!cl_particles.integer) continue; switch (info->particletype) { case pt_smoke: if (!cl_particles_smoke.integer) continue;break; case pt_spark: if (!cl_particles_sparks.integer) continue;break; case pt_bubble: if (!cl_particles_bubbles.integer) continue;break; case pt_blood: if (!cl_particles_blood.integer) continue;break; case pt_rain: if (!cl_particles_rain.integer) continue;break; case pt_snow: if (!cl_particles_snow.integer) continue;break; default: break; } cnt = info->countabsolute; cnt += (pcount * info->countmultiplier) * cl_particles_quality.value; // if drawastrail is not set, we will // use the regular cnt-based random // particle spawning at the center; so // do NOT apply trailspacing then! if (drawastrail && definedastrail) cnt += (traillen / info->trailspacing) * cl_particles_quality.value; cnt *= fade; if (cnt == 0) continue; // nothing to draw info->particleaccumulator += cnt; if (drawastrail || definedastrail) immediatebloodstain = false; else immediatebloodstain = ((cl_decals_newsystem_immediatebloodstain.integer >= 1) && (info->particletype == pt_blood)) || ((cl_decals_newsystem_immediatebloodstain.integer >= 2) && staintex); if (drawastrail) { VectorCopy(originmins, trailpos); trailstep = traillen / cnt; } else { VectorCopy(center, trailpos); trailstep = 0; } if (trailstep == 0) { VectorMAM(0.5f, velocitymins, 0.5f, velocitymaxs, velocity); AnglesFromVectors(angles, velocity, NULL, false); } else AnglesFromVectors(angles, traildir, NULL, false); AngleVectors(angles, forward, right, up); VectorMAMAMAM(1.0f, trailpos, info->relativeoriginoffset[0], forward, info->relativeoriginoffset[1], right, info->relativeoriginoffset[2], up, trailpos); VectorMAMAM(info->relativevelocityoffset[0], forward, info->relativevelocityoffset[1], right, info->relativevelocityoffset[2], up, velocity); info->particleaccumulator = bound(0, info->particleaccumulator, 16384); for (;info->particleaccumulator >= 1;info->particleaccumulator--) { if (info->tex[1] > info->tex[0]) { tex = (int)lhrandom(info->tex[0], info->tex[1]); tex = min(tex, info->tex[1] - 1); } if (!(drawastrail || definedastrail)) { trailpos[0] = lhrandom(originmins[0], originmaxs[0]); trailpos[1] = lhrandom(originmins[1], originmaxs[1]); trailpos[2] = lhrandom(originmins[2], originmaxs[2]); } if(tintmins) { tintlerp = lhrandom(0, 1); Vector4Lerp(tintmins, tintlerp, tintmaxs, tint); } VectorRandom(rvec); part = CL_NewParticle(center, info->particletype, info->color[0], info->color[1], tex, lhrandom(info->size[0], info->size[1]), info->size[2], lhrandom(info->alpha[0], info->alpha[1]), info->alpha[2], info->gravity, info->bounce, trailpos[0] + info->originoffset[0] + info->originjitter[0] * rvec[0], trailpos[1] + info->originoffset[1] + info->originjitter[1] * rvec[1], trailpos[2] + info->originoffset[2] + info->originjitter[2] * rvec[2], lhrandom(velocitymins[0], velocitymaxs[0]) * info->velocitymultiplier + info->velocityoffset[0] + info->velocityjitter[0] * rvec[0] + velocity[0], lhrandom(velocitymins[1], velocitymaxs[1]) * info->velocitymultiplier + info->velocityoffset[1] + info->velocityjitter[1] * rvec[1] + velocity[1], lhrandom(velocitymins[2], velocitymaxs[2]) * info->velocitymultiplier + info->velocityoffset[2] + info->velocityjitter[2] * rvec[2] + velocity[2], info->airfriction, info->liquidfriction, 0, 0, info->countabsolute <= 0, lhrandom(info->time[0], info->time[1]), info->stretchfactor, info->blendmode, info->orientation, info->staincolor[0], info->staincolor[1], staintex, lhrandom(info->stainalpha[0], info->stainalpha[1]), lhrandom(info->stainsize[0], info->stainsize[1]), lhrandom(info->rotate[0], info->rotate[1]), lhrandom(info->rotate[2], info->rotate[3]), tintmins ? tint : NULL); if (immediatebloodstain && part) { immediatebloodstain = false; CL_ImmediateBloodStain(part); } if (trailstep) VectorMA(trailpos, trailstep, traildir, trailpos); } } } } } if (!found) CL_ParticleEffect_Fallback(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, wanttrail); } void CL_ParticleTrail(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade) { CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, true); } void CL_ParticleBox(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor, qboolean spawndlight, qboolean spawnparticles, float tintmins[4], float tintmaxs[4], float fade) { CL_NewParticlesFromEffectinfo(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, spawndlight, spawnparticles, tintmins, tintmaxs, fade, false); } // note: this one ONLY does boxes! void CL_ParticleEffect(int effectnameindex, float pcount, const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, entity_t *ent, int palettecolor) { CL_ParticleBox(effectnameindex, pcount, originmins, originmaxs, velocitymins, velocitymaxs, ent, palettecolor, true, true, NULL, NULL, 1); } /* =============== CL_EntityParticles =============== */ void CL_EntityParticles (const entity_t *ent) { int i, j; vec_t pitch, yaw, dist = 64, beamlength = 16; vec3_t org, v; static vec3_t avelocities[NUMVERTEXNORMALS]; if (!cl_particles.integer) return; if (cl.time <= cl.oldtime) return; // don't spawn new entity particles while paused Matrix4x4_OriginFromMatrix(&ent->render.matrix, org); if (!avelocities[0][0]) for (i = 0;i < NUMVERTEXNORMALS;i++) for (j = 0;j < 3;j++) avelocities[i][j] = lhrandom(0, 2.55); for (i = 0;i < NUMVERTEXNORMALS;i++) { yaw = cl.time * avelocities[i][0]; pitch = cl.time * avelocities[i][1]; v[0] = org[0] + m_bytenormals[i][0] * dist + (cos(pitch)*cos(yaw)) * beamlength; v[1] = org[1] + m_bytenormals[i][1] * dist + (cos(pitch)*sin(yaw)) * beamlength; v[2] = org[2] + m_bytenormals[i][2] * dist + (-sin(pitch)) * beamlength; CL_NewParticle(org, pt_entityparticle, particlepalette[0x6f], particlepalette[0x6f], tex_particle, 1, 0, 255, 0, 0, 0, v[0], v[1], v[2], 0, 0, 0, 0, 0, 0, 0, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } void CL_ReadPointFile_f (void) { double org[3], leakorg[3]; vec3_t vecorg; int r, c, s; char *pointfile = NULL, *pointfilepos, *t, tchar; char name[MAX_QPATH]; if (!cl.worldmodel) return; dpsnprintf(name, sizeof(name), "%s.pts", cl.worldnamenoextension); pointfile = (char *)FS_LoadFile(name, tempmempool, true, NULL); if (!pointfile) { Con_Printf("Could not open %s\n", name); return; } Con_Printf("Reading %s...\n", name); VectorClear(leakorg); c = 0; s = 0; pointfilepos = pointfile; while (*pointfilepos) { while (*pointfilepos == '\n' || *pointfilepos == '\r') pointfilepos++; if (!*pointfilepos) break; t = pointfilepos; while (*t && *t != '\n' && *t != '\r') t++; tchar = *t; *t = 0; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif r = sscanf (pointfilepos,"%lf %lf %lf", &org[0], &org[1], &org[2]); VectorCopy(org, vecorg); *t = tchar; pointfilepos = t; if (r != 3) break; if (c == 0) VectorCopy(org, leakorg); c++; if (cl.num_particles < cl.max_particles - 3) { s++; CL_NewParticle(vecorg, pt_alphastatic, particlepalette[(-c)&15], particlepalette[(-c)&15], tex_particle, 2, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 0, 0, 0, 0, true, 1<<30, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } Mem_Free(pointfile); VectorCopy(leakorg, vecorg); Con_Printf("%i points read (%i particles spawned)\nLeak at %f %f %f\n", c, s, leakorg[0], leakorg[1], leakorg[2]); if (c == 0) { return; } CL_NewParticle(vecorg, pt_beam, 0xFF0000, 0xFF0000, tex_beam, 64, 0, 255, 0, 0, 0, org[0] - 4096, org[1], org[2], org[0] + 4096, org[1], org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); CL_NewParticle(vecorg, pt_beam, 0x00FF00, 0x00FF00, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1] - 4096, org[2], org[0], org[1] + 4096, org[2], 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); CL_NewParticle(vecorg, pt_beam, 0x0000FF, 0x0000FF, tex_beam, 64, 0, 255, 0, 0, 0, org[0], org[1], org[2] - 4096, org[0], org[1], org[2] + 4096, 0, 0, 0, 0, false, 1<<30, 1, PBLEND_ADD, PARTICLE_HBEAM, -1, -1, -1, 1, 1, 0, 0, NULL); } /* =============== CL_ParseParticleEffect Parse an effect out of the server message =============== */ void CL_ParseParticleEffect (void) { vec3_t org, dir; int i, count, msgcount, color; MSG_ReadVector(&cl_message, org, cls.protocol); for (i=0 ; i<3 ; i++) dir[i] = MSG_ReadChar(&cl_message) * (1.0 / 16.0); msgcount = MSG_ReadByte(&cl_message); color = MSG_ReadByte(&cl_message); if (msgcount == 255) count = 1024; else count = msgcount; CL_ParticleEffect(EFFECT_SVC_PARTICLE, count, org, org, dir, dir, NULL, color); } /* =============== CL_ParticleExplosion =============== */ void CL_ParticleExplosion (const vec3_t org) { int i; trace_t trace; //vec3_t v; //vec3_t v2; R_Stain(org, 96, 40, 40, 40, 64, 88, 88, 88, 64); CL_SpawnDecalParticleForPoint(org, 40, 48, 255, tex_bulletdecal[rand()&7], 0xFFFFFF, 0xFFFFFF); if (cl_particles_quake.integer) { for (i = 0;i < 1024;i++) { int r, color; r = rand()&3; if (i & 1) { color = particlepalette[ramp1[r]]; CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.1006 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { color = particlepalette[ramp2[r]]; CL_NewParticle(org, pt_alphastatic, color, color, tex_particle, 1.5f, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, 1, 1, 16, 256, true, 0.0669 * (8 - r), 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } } else { i = CL_PointSuperContents(org); if (i & (SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER)) { if (cl_particles.integer && cl_particles_bubbles.integer) for (i = 0;i < 128 * cl_particles_quality.value;i++) CL_NewParticle(org, pt_bubble, 0x404040, 0x808080, tex_bubble, 2, 0, lhrandom(128, 255), 128, -0.125, 1.5, org[0], org[1], org[2], 0, 0, 0, 0.0625, 0.25, 16, 96, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } else { if (cl_particles.integer && cl_particles_sparks.integer && cl_particles_explosions_sparks.integer) { for (i = 0;i < 512 * cl_particles_quality.value;i++) { int k = 0; vec3_t v, v2; do { VectorRandom(v2); VectorMA(org, 128, v2, v); trace = CL_TraceLine(org, v, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, 0, collision_extendmovelength.value, true, false, NULL, false, false); } while (k < 16 && trace.fraction < 0.1f); VectorSubtract(trace.endpos, org, v2); VectorScale(v2, 2.0f, v2); CL_NewParticle(org, pt_spark, 0x903010, 0xFFD030, tex_particle, 1.0f, 0, lhrandom(0, 255), 512, 0, 0, org[0], org[1], org[2], v2[0], v2[1], v2[2], 0, 0, 0, 0, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); } } } } if (cl_particles_explosions_shell.integer) R_NewExplosion(org); } /* =============== CL_ParticleExplosion2 =============== */ void CL_ParticleExplosion2 (const vec3_t org, int colorStart, int colorLength) { int i, k; if (!cl_particles.integer) return; for (i = 0;i < 512 * cl_particles_quality.value;i++) { k = particlepalette[colorStart + (i % colorLength)]; if (cl_particles_quake.integer) CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, 1, 0, 255, 0, 0, 0, org[0], org[1], org[2], 0, 0, 0, -4, -4, 16, 256, true, 0.3, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); else CL_NewParticle(org, pt_alphastatic, k, k, tex_particle, lhrandom(0.5, 1.5), 0, 255, 512, 0, 0, org[0], org[1], org[2], 0, 0, 0, lhrandom(1.5, 3), lhrandom(1.5, 3), 8, 192, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } static void CL_Sparks(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float sparkcount) { vec3_t center; VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); if (cl_particles_sparks.integer) { sparkcount *= cl_particles_quality.value; while(sparkcount-- > 0) CL_NewParticle(center, pt_spark, particlepalette[0x68], particlepalette[0x6f], tex_particle, 0.5f, 0, lhrandom(64, 255), 512, 1, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]) + cl.movevars_gravity * 0.1f, 0, 0, 0, 64, true, 0, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); } } static void CL_Smoke(const vec3_t originmins, const vec3_t originmaxs, const vec3_t velocitymins, const vec3_t velocitymaxs, float smokecount) { vec3_t center; VectorMAM(0.5f, originmins, 0.5f, originmaxs, center); if (cl_particles_smoke.integer) { smokecount *= cl_particles_quality.value; while(smokecount-- > 0) CL_NewParticle(center, pt_smoke, 0x101010, 0x101010, tex_smoke[rand()&7], 2, 2, 255, 256, 0, 0, lhrandom(originmins[0], originmaxs[0]), lhrandom(originmins[1], originmaxs[1]), lhrandom(originmins[2], originmaxs[2]), lhrandom(velocitymins[0], velocitymaxs[0]), lhrandom(velocitymins[1], velocitymaxs[1]), lhrandom(velocitymins[2], velocitymaxs[2]), 0, 0, 0, smokecount > 0 ? 16 : 0, true, 0, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } void CL_ParticleCube (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, vec_t gravity, vec_t randomvel) { vec3_t center; int k; if (!cl_particles.integer) return; VectorMAM(0.5f, mins, 0.5f, maxs, center); count = (int)(count * cl_particles_quality.value); while (count--) { k = particlepalette[colorbase + (rand()&3)]; CL_NewParticle(center, pt_alphastatic, k, k, tex_particle, 2, 0, 255, 128, gravity, 0, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(mins[2], maxs[2]), dir[0], dir[1], dir[2], 0, 0, 0, randomvel, true, 0, 1, PBLEND_ALPHA, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } } void CL_ParticleRain (const vec3_t mins, const vec3_t maxs, const vec3_t dir, int count, int colorbase, int type) { int k; float minz, maxz, lifetime = 30; vec3_t org; if (!cl_particles.integer) return; if (dir[2] < 0) // falling { minz = maxs[2] + dir[2] * 0.1; maxz = maxs[2]; if (cl.worldmodel) lifetime = (maxz - cl.worldmodel->normalmins[2]) / max(1, -dir[2]); } else // rising?? { minz = mins[2]; maxz = maxs[2] + dir[2] * 0.1; if (cl.worldmodel) lifetime = (cl.worldmodel->normalmaxs[2] - minz) / max(1, dir[2]); } count = (int)(count * cl_particles_quality.value); switch(type) { case 0: if (!cl_particles_rain.integer) break; count *= 4; // ick, this should be in the mod or maps? while(count--) { k = particlepalette[colorbase + (rand()&3)]; VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); if (gamemode == GAME_GOODVSBAD2) CL_NewParticle(org, pt_rain, k, k, tex_particle, 20, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); else CL_NewParticle(org, pt_rain, k, k, tex_particle, 0.5, 0, lhrandom(32, 64), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_SPARK, -1, -1, -1, 1, 1, 0, 0, NULL); } break; case 1: if (!cl_particles_snow.integer) break; while(count--) { k = particlepalette[colorbase + (rand()&3)]; VectorSet(org, lhrandom(mins[0], maxs[0]), lhrandom(mins[1], maxs[1]), lhrandom(minz, maxz)); if (gamemode == GAME_GOODVSBAD2) CL_NewParticle(org, pt_snow, k, k, tex_particle, 20, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); else CL_NewParticle(org, pt_snow, k, k, tex_particle, 1, 0, lhrandom(64, 128), 0, 0, -1, org[0], org[1], org[2], dir[0], dir[1], dir[2], 0, 0, 0, 0, true, lifetime, 1, PBLEND_ADD, PARTICLE_BILLBOARD, -1, -1, -1, 1, 1, 0, 0, NULL); } break; default: Con_Printf ("CL_ParticleRain: unknown type %i (0 = rain, 1 = snow)\n", type); } } cvar_t r_drawparticles = {0, "r_drawparticles", "1", "enables drawing of particles"}; static cvar_t r_drawparticles_drawdistance = {CVAR_SAVE, "r_drawparticles_drawdistance", "2000", "particles further than drawdistance*size will not be drawn"}; static cvar_t r_drawparticles_nearclip_min = {CVAR_SAVE, "r_drawparticles_nearclip_min", "4", "particles closer than drawnearclip_min will not be drawn"}; static cvar_t r_drawparticles_nearclip_max = {CVAR_SAVE, "r_drawparticles_nearclip_max", "4", "particles closer than drawnearclip_min will be faded"}; cvar_t r_drawdecals = {0, "r_drawdecals", "1", "enables drawing of decals"}; static cvar_t r_drawdecals_drawdistance = {CVAR_SAVE, "r_drawdecals_drawdistance", "500", "decals further than drawdistance*size will not be drawn"}; #define PARTICLETEXTURESIZE 64 #define PARTICLEFONTSIZE (PARTICLETEXTURESIZE*8) static unsigned char shadebubble(float dx, float dy, vec3_t light) { float dz, f, dot; vec3_t normal; dz = 1 - (dx*dx+dy*dy); if (dz > 0) // it does hit the sphere { f = 0; // back side normal[0] = dx;normal[1] = dy;normal[2] = dz; VectorNormalize(normal); dot = DotProduct(normal, light); if (dot > 0.5) // interior reflection f += ((dot * 2) - 1); else if (dot < -0.5) // exterior reflection f += ((dot * -2) - 1); // front side normal[0] = dx;normal[1] = dy;normal[2] = -dz; VectorNormalize(normal); dot = DotProduct(normal, light); if (dot > 0.5) // interior reflection f += ((dot * 2) - 1); else if (dot < -0.5) // exterior reflection f += ((dot * -2) - 1); f *= 128; f += 16; // just to give it a haze so you can see the outline f = bound(0, f, 255); return (unsigned char) f; } else return 0; } int particlefontwidth, particlefontheight, particlefontcellwidth, particlefontcellheight, particlefontrows, particlefontcols; static void CL_Particle_PixelCoordsForTexnum(int texnum, int *basex, int *basey, int *width, int *height) { *basex = (texnum % particlefontcols) * particlefontcellwidth; *basey = ((texnum / particlefontcols) % particlefontrows) * particlefontcellheight; *width = particlefontcellwidth; *height = particlefontcellheight; } static void setuptex(int texnum, unsigned char *data, unsigned char *particletexturedata) { int basex, basey, w, h, y; CL_Particle_PixelCoordsForTexnum(texnum, &basex, &basey, &w, &h); if(w != PARTICLETEXTURESIZE || h != PARTICLETEXTURESIZE) Sys_Error("invalid particle texture size for autogenerating"); for (y = 0;y < PARTICLETEXTURESIZE;y++) memcpy(particletexturedata + ((basey + y) * PARTICLEFONTSIZE + basex) * 4, data + y * PARTICLETEXTURESIZE * 4, PARTICLETEXTURESIZE * 4); } static void particletextureblotch(unsigned char *data, float radius, float red, float green, float blue, float alpha) { int x, y; float cx, cy, dx, dy, f, iradius; unsigned char *d; cx = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; cy = (lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius) + lhrandom(radius + 1, PARTICLETEXTURESIZE - 2 - radius)) * 0.5f; iradius = 1.0f / radius; alpha *= (1.0f / 255.0f); for (y = 0;y < PARTICLETEXTURESIZE;y++) { for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - cx); dy = (y - cy); f = (1.0f - sqrt(dx * dx + dy * dy) * iradius) * alpha; if (f > 0) { if (f > 1) f = 1; d = data + (y * PARTICLETEXTURESIZE + x) * 4; d[0] += (int)(f * (blue - d[0])); d[1] += (int)(f * (green - d[1])); d[2] += (int)(f * (red - d[2])); } } } } #if 0 static void particletextureclamp(unsigned char *data, int minr, int ming, int minb, int maxr, int maxg, int maxb) { int i; for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) { data[0] = bound(minb, data[0], maxb); data[1] = bound(ming, data[1], maxg); data[2] = bound(minr, data[2], maxr); } } #endif static void particletextureinvert(unsigned char *data) { int i; for (i = 0;i < PARTICLETEXTURESIZE*PARTICLETEXTURESIZE;i++, data += 4) { data[0] = 255 - data[0]; data[1] = 255 - data[1]; data[2] = 255 - data[2]; } } // Those loops are in a separate function to work around an optimization bug in Mac OS X's GCC static void R_InitBloodTextures (unsigned char *particletexturedata) { int i, j, k, m; size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); // blood particles for (i = 0;i < 8;i++) { memset(data, 255, datasize); for (k = 0;k < 24;k++) particletextureblotch(data, PARTICLETEXTURESIZE/16, 96, 0, 0, 160); //particletextureclamp(data, 32, 32, 32, 255, 255, 255); particletextureinvert(data); setuptex(tex_bloodparticle[i], data, particletexturedata); } // blood decals for (i = 0;i < 8;i++) { memset(data, 255, datasize); m = 8; for (j = 1;j < 10;j++) for (k = min(j, m - 1);k < m;k++) particletextureblotch(data, (float)j*PARTICLETEXTURESIZE/64.0f, 96, 0, 0, 320 - j * 8); //particletextureclamp(data, 32, 32, 32, 255, 255, 255); particletextureinvert(data); setuptex(tex_blooddecal[i], data, particletexturedata); } Mem_Free(data); } //uncomment this to make engine save out particle font to a tga file when run //#define DUMPPARTICLEFONT static void R_InitParticleTexture (void) { int x, y, d, i, k, m; int basex, basey, w, h; float dx, dy, f, s1, t1, s2, t2; vec3_t light; char *buf; fs_offset_t filesize; char texturename[MAX_QPATH]; skinframe_t *sf; // a note: decals need to modulate (multiply) the background color to // properly darken it (stain), and they need to be able to alpha fade, // this is a very difficult challenge because it means fading to white // (no change to background) rather than black (darkening everything // behind the whole decal polygon), and to accomplish this the texture is // inverted (dark red blood on white background becomes brilliant cyan // and white on black background) so we can alpha fade it to black, then // we invert it again during the blendfunc to make it work... #ifndef DUMPPARTICLEFONT decalskinframe = R_SkinFrame_LoadExternal("particles/particlefont.tga", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, false); if (decalskinframe) { particlefonttexture = decalskinframe->base; // TODO maybe allow custom grid size? particlefontwidth = image_width; particlefontheight = image_height; particlefontcellwidth = image_width / 8; particlefontcellheight = image_height / 8; particlefontcols = 8; particlefontrows = 8; } else #endif { unsigned char *particletexturedata = (unsigned char *)Mem_Alloc(tempmempool, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); size_t datasize = PARTICLETEXTURESIZE*PARTICLETEXTURESIZE*4; unsigned char *data = (unsigned char *)Mem_Alloc(tempmempool, datasize); unsigned char *noise1 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); unsigned char *noise2 = (unsigned char *)Mem_Alloc(tempmempool, PARTICLETEXTURESIZE*2*PARTICLETEXTURESIZE*2); particlefontwidth = particlefontheight = PARTICLEFONTSIZE; particlefontcellwidth = particlefontcellheight = PARTICLETEXTURESIZE; particlefontcols = 8; particlefontrows = 8; memset(particletexturedata, 255, PARTICLEFONTSIZE*PARTICLEFONTSIZE*4); // smoke for (i = 0;i < 8;i++) { memset(data, 255, datasize); do { fractalnoise(noise1, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/8); fractalnoise(noise2, PARTICLETEXTURESIZE*2, PARTICLETEXTURESIZE/4); m = 0; for (y = 0;y < PARTICLETEXTURESIZE;y++) { dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); d = (noise2[y*PARTICLETEXTURESIZE*2+x] - 128) * 3 + 192; if (d > 0) d = (int)(d * (1-(dx*dx+dy*dy))); d = (d * noise1[y*PARTICLETEXTURESIZE*2+x]) >> 7; d = bound(0, d, 255); data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; if (m < d) m = d; } } } while (m < 224); setuptex(tex_smoke[i], data, particletexturedata); } // rain splash memset(data, 255, datasize); for (y = 0;y < PARTICLETEXTURESIZE;y++) { dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); f = 255.0f * (1.0 - 4.0f * fabs(10.0f - sqrt(dx*dx+dy*dy))); data[(y*PARTICLETEXTURESIZE+x)*4+3] = (int) (bound(0.0f, f, 255.0f)); } } setuptex(tex_rainsplash, data, particletexturedata); // normal particle memset(data, 255, datasize); for (y = 0;y < PARTICLETEXTURESIZE;y++) { dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); d = (int)(256 * (1 - (dx*dx+dy*dy))); d = bound(0, d, 255); data[(y*PARTICLETEXTURESIZE+x)*4+3] = (unsigned char) d; } } setuptex(tex_particle, data, particletexturedata); // rain memset(data, 255, datasize); light[0] = 1;light[1] = 1;light[2] = 1; VectorNormalize(light); for (y = 0;y < PARTICLETEXTURESIZE;y++) { dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); // stretch upper half of bubble by +50% and shrink lower half by -50% // (this gives an elongated teardrop shape) if (dy > 0.5f) dy = (dy - 0.5f) * 2.0f; else dy = (dy - 0.5f) / 1.5f; for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); // shrink bubble width to half dx *= 2.0f; data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); } } setuptex(tex_raindrop, data, particletexturedata); // bubble memset(data, 255, datasize); light[0] = 1;light[1] = 1;light[2] = 1; VectorNormalize(light); for (y = 0;y < PARTICLETEXTURESIZE;y++) { dy = (y - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); for (x = 0;x < PARTICLETEXTURESIZE;x++) { dx = (x - 0.5f*PARTICLETEXTURESIZE) / (PARTICLETEXTURESIZE*0.5f-1); data[(y*PARTICLETEXTURESIZE+x)*4+3] = shadebubble(dx, dy, light); } } setuptex(tex_bubble, data, particletexturedata); // Blood particles and blood decals R_InitBloodTextures (particletexturedata); // bullet decals for (i = 0;i < 8;i++) { memset(data, 255, datasize); for (k = 0;k < 12;k++) particletextureblotch(data, PARTICLETEXTURESIZE/16, 0, 0, 0, 128); for (k = 0;k < 3;k++) particletextureblotch(data, PARTICLETEXTURESIZE/2, 0, 0, 0, 160); //particletextureclamp(data, 64, 64, 64, 255, 255, 255); particletextureinvert(data); setuptex(tex_bulletdecal[i], data, particletexturedata); } #ifdef DUMPPARTICLEFONT Image_WriteTGABGRA ("particles/particlefont.tga", PARTICLEFONTSIZE, PARTICLEFONTSIZE, particletexturedata); #endif decalskinframe = R_SkinFrame_LoadInternalBGRA("particlefont", TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, particletexturedata, PARTICLEFONTSIZE, PARTICLEFONTSIZE, false); particlefonttexture = decalskinframe->base; Mem_Free(particletexturedata); Mem_Free(data); Mem_Free(noise1); Mem_Free(noise2); } for (i = 0;i < MAX_PARTICLETEXTURES;i++) { CL_Particle_PixelCoordsForTexnum(i, &basex, &basey, &w, &h); particletexture[i].texture = particlefonttexture; particletexture[i].s1 = (basex + 1) / (float)particlefontwidth; particletexture[i].t1 = (basey + 1) / (float)particlefontheight; particletexture[i].s2 = (basex + w - 1) / (float)particlefontwidth; particletexture[i].t2 = (basey + h - 1) / (float)particlefontheight; } #ifndef DUMPPARTICLEFONT particletexture[tex_beam].texture = loadtextureimage(particletexturepool, "particles/nexbeam.tga", false, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true, vid.sRGB3D); if (!particletexture[tex_beam].texture) #endif { unsigned char noise3[64][64], data2[64][16][4]; // nexbeam fractalnoise(&noise3[0][0], 64, 4); m = 0; for (y = 0;y < 64;y++) { dy = (y - 0.5f*64) / (64*0.5f-1); for (x = 0;x < 16;x++) { dx = (x - 0.5f*16) / (16*0.5f-2); d = (int)((1 - sqrt(fabs(dx))) * noise3[y][x]); data2[y][x][0] = data2[y][x][1] = data2[y][x][2] = (unsigned char) bound(0, d, 255); data2[y][x][3] = 255; } } #ifdef DUMPPARTICLEFONT Image_WriteTGABGRA ("particles/nexbeam.tga", 64, 64, &data2[0][0][0]); #endif particletexture[tex_beam].texture = R_LoadTexture2D(particletexturepool, "nexbeam", 16, 64, &data2[0][0][0], TEXTYPE_BGRA, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, -1, NULL); } particletexture[tex_beam].s1 = 0; particletexture[tex_beam].t1 = 0; particletexture[tex_beam].s2 = 1; particletexture[tex_beam].t2 = 1; // now load an texcoord/texture override file buf = (char *) FS_LoadFile("particles/particlefont.txt", tempmempool, false, &filesize); if(buf) { const char *bufptr; bufptr = buf; for(;;) { if(!COM_ParseToken_Simple(&bufptr, true, false, true)) break; if(!strcmp(com_token, "\n")) continue; // empty line i = atoi(com_token); texturename[0] = 0; s1 = 0; t1 = 0; s2 = 1; t2 = 1; if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) { strlcpy(texturename, com_token, sizeof(texturename)); s1 = atof(com_token); if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) { texturename[0] = 0; t1 = atof(com_token); if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) { s2 = atof(com_token); if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) { t2 = atof(com_token); strlcpy(texturename, "particles/particlefont.tga", sizeof(texturename)); if (COM_ParseToken_Simple(&bufptr, true, false, true) && strcmp(com_token, "\n")) strlcpy(texturename, com_token, sizeof(texturename)); } } } else s1 = 0; } if (!texturename[0]) { Con_Printf("particles/particlefont.txt: syntax should be texnum x1 y1 x2 y2 texturename or texnum x1 y1 x2 y2 or texnum texturename\n"); continue; } if (i < 0 || i >= MAX_PARTICLETEXTURES) { Con_Printf("particles/particlefont.txt: texnum %i outside valid range (0 to %i)\n", i, MAX_PARTICLETEXTURES); continue; } sf = R_SkinFrame_LoadExternal(texturename, TEXF_ALPHA | TEXF_FORCELINEAR | TEXF_RGBMULTIPLYBYALPHA, true); // note: this loads as sRGB if sRGB is active! if(!sf) { // R_SkinFrame_LoadExternal already complained continue; } particletexture[i].texture = sf->base; particletexture[i].s1 = s1; particletexture[i].t1 = t1; particletexture[i].s2 = s2; particletexture[i].t2 = t2; } Mem_Free(buf); } } static void r_part_start(void) { int i; // generate particlepalette for convenience from the main one for (i = 0;i < 256;i++) particlepalette[i] = palette_rgb[i][0] * 65536 + palette_rgb[i][1] * 256 + palette_rgb[i][2]; particletexturepool = R_AllocTexturePool(); R_InitParticleTexture (); CL_Particles_LoadEffectInfo(NULL); } static void r_part_shutdown(void) { R_FreeTexturePool(&particletexturepool); } static void r_part_newmap(void) { if (decalskinframe) R_SkinFrame_MarkUsed(decalskinframe); CL_Particles_LoadEffectInfo(NULL); } unsigned short particle_elements[MESHQUEUE_TRANSPARENT_BATCHSIZE*6]; float particle_vertex3f[MESHQUEUE_TRANSPARENT_BATCHSIZE*12], particle_texcoord2f[MESHQUEUE_TRANSPARENT_BATCHSIZE*8], particle_color4f[MESHQUEUE_TRANSPARENT_BATCHSIZE*16]; void R_Particles_Init (void) { int i; for (i = 0;i < MESHQUEUE_TRANSPARENT_BATCHSIZE;i++) { particle_elements[i*6+0] = i*4+0; particle_elements[i*6+1] = i*4+1; particle_elements[i*6+2] = i*4+2; particle_elements[i*6+3] = i*4+0; particle_elements[i*6+4] = i*4+2; particle_elements[i*6+5] = i*4+3; } Cvar_RegisterVariable(&r_drawparticles); Cvar_RegisterVariable(&r_drawparticles_drawdistance); Cvar_RegisterVariable(&r_drawparticles_nearclip_min); Cvar_RegisterVariable(&r_drawparticles_nearclip_max); Cvar_RegisterVariable(&r_drawdecals); Cvar_RegisterVariable(&r_drawdecals_drawdistance); R_RegisterModule("R_Particles", r_part_start, r_part_shutdown, r_part_newmap, NULL, NULL); } static void R_DrawDecal_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { int surfacelistindex; const decal_t *d; float *v3f, *t2f, *c4f; particletexture_t *tex; vec_t right[3], up[3], size, ca; float alphascale = (1.0f / 65536.0f) * cl_particles_alpha.value; RSurf_ActiveWorldEntity(); r_refdef.stats[r_stat_drawndecals] += numsurfaces; // R_Mesh_ResetTextureState(); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(true); GL_CullFace(GL_NONE); // generate all the vertices at once for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { d = cl.decals + surfacelist[surfacelistindex]; // calculate color c4f = particle_color4f + 16*surfacelistindex; ca = d->alpha * alphascale; // ensure alpha multiplier saturates properly if (ca > 1.0f / 256.0f) ca = 1.0f / 256.0f; if (r_refdef.fogenabled) ca *= RSurf_FogVertex(d->org); Vector4Set(c4f, d->color[0] * ca, d->color[1] * ca, d->color[2] * ca, 1); Vector4Copy(c4f, c4f + 4); Vector4Copy(c4f, c4f + 8); Vector4Copy(c4f, c4f + 12); // calculate vertex positions size = d->size * cl_particles_size.value; VectorVectors(d->normal, right, up); VectorScale(right, size, right); VectorScale(up, size, up); v3f = particle_vertex3f + 12*surfacelistindex; v3f[ 0] = d->org[0] - right[0] - up[0]; v3f[ 1] = d->org[1] - right[1] - up[1]; v3f[ 2] = d->org[2] - right[2] - up[2]; v3f[ 3] = d->org[0] - right[0] + up[0]; v3f[ 4] = d->org[1] - right[1] + up[1]; v3f[ 5] = d->org[2] - right[2] + up[2]; v3f[ 6] = d->org[0] + right[0] + up[0]; v3f[ 7] = d->org[1] + right[1] + up[1]; v3f[ 8] = d->org[2] + right[2] + up[2]; v3f[ 9] = d->org[0] + right[0] - up[0]; v3f[10] = d->org[1] + right[1] - up[1]; v3f[11] = d->org[2] + right[2] - up[2]; // calculate texcoords tex = &particletexture[d->texnum]; t2f = particle_texcoord2f + 8*surfacelistindex; t2f[0] = tex->s1;t2f[1] = tex->t2; t2f[2] = tex->s1;t2f[3] = tex->t1; t2f[4] = tex->s2;t2f[5] = tex->t1; t2f[6] = tex->s2;t2f[7] = tex->t2; } // now render the decals all at once // (this assumes they all use one particle font texture!) GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); R_SetupShader_Generic(particletexture[63].texture, NULL, GL_MODULATE, 1, false, false, true); R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); R_Mesh_Draw(0, numsurfaces * 4, 0, numsurfaces * 2, NULL, NULL, 0, particle_elements, NULL, 0); } void R_DrawDecals (void) { int i; int drawdecals = r_drawdecals.integer; decal_t *decal; float frametime; float decalfade; float drawdist2; unsigned int killsequence = cl.decalsequence - bound(0, (unsigned int) cl_decals_max.integer, cl.decalsequence); frametime = bound(0, cl.time - cl.decals_updatetime, 1); cl.decals_updatetime = bound(cl.time - 1, cl.decals_updatetime + frametime, cl.time + 1); // LordHavoc: early out conditions if (!cl.num_decals) return; decalfade = frametime * 256 / cl_decals_fadetime.value; drawdist2 = r_drawdecals_drawdistance.value * r_refdef.view.quality; drawdist2 = drawdist2*drawdist2; for (i = 0, decal = cl.decals;i < cl.num_decals;i++, decal++) { if (!decal->typeindex) continue; if (killsequence > decal->decalsequence) goto killdecal; if (cl.time > decal->time2 + cl_decals_time.value) { decal->alpha -= decalfade; if (decal->alpha <= 0) goto killdecal; } if (decal->owner) { if (cl.entities[decal->owner].render.model == decal->ownermodel) { Matrix4x4_Transform(&cl.entities[decal->owner].render.matrix, decal->relativeorigin, decal->org); Matrix4x4_Transform3x3(&cl.entities[decal->owner].render.matrix, decal->relativenormal, decal->normal); } else goto killdecal; } if(cl_decals_visculling.integer && decal->clusterindex > -1000 && !CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, decal->clusterindex)) continue; if (!drawdecals) continue; if (DotProduct(r_refdef.view.origin, decal->normal) > DotProduct(decal->org, decal->normal) && VectorDistance2(decal->org, r_refdef.view.origin) < drawdist2 * (decal->size * decal->size)) R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, decal->org, R_DrawDecal_TransparentCallback, NULL, i, NULL); continue; killdecal: decal->typeindex = 0; if (cl.free_decal > i) cl.free_decal = i; } // reduce cl.num_decals if possible while (cl.num_decals > 0 && cl.decals[cl.num_decals - 1].typeindex == 0) cl.num_decals--; if (cl.num_decals == cl.max_decals && cl.max_decals < MAX_DECALS) { decal_t *olddecals = cl.decals; cl.max_decals = min(cl.max_decals * 2, MAX_DECALS); cl.decals = (decal_t *) Mem_Alloc(cls.levelmempool, cl.max_decals * sizeof(decal_t)); memcpy(cl.decals, olddecals, cl.num_decals * sizeof(decal_t)); Mem_Free(olddecals); } r_refdef.stats[r_stat_totaldecals] = cl.num_decals; } static void R_DrawParticle_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { vec3_t vecorg, vecvel, baseright, baseup; int surfacelistindex; int batchstart, batchcount; const particle_t *p; pblend_t blendmode; rtexture_t *texture; float *v3f, *t2f, *c4f; particletexture_t *tex; float up2[3], v[3], right[3], up[3], fog, ifog, size, len, lenfactor, alpha; // float ambient[3], diffuse[3], diffusenormal[3]; float palpha, spintime, spinrad, spincos, spinsin, spinm1, spinm2, spinm3, spinm4; vec4_t colormultiplier; float minparticledist_start, minparticledist_end; qboolean dofade; RSurf_ActiveWorldEntity(); Vector4Set(colormultiplier, r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), r_refdef.view.colorscale * (1.0 / 256.0f), cl_particles_alpha.value * (1.0 / 256.0f)); r_refdef.stats[r_stat_particles] += numsurfaces; // R_Mesh_ResetTextureState(); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(true); GL_CullFace(GL_NONE); spintime = r_refdef.scene.time; minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; minparticledist_end = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_max.value; dofade = (minparticledist_start < minparticledist_end); // first generate all the vertices at once for (surfacelistindex = 0, v3f = particle_vertex3f, t2f = particle_texcoord2f, c4f = particle_color4f;surfacelistindex < numsurfaces;surfacelistindex++, v3f += 3*4, t2f += 2*4, c4f += 4*4) { p = cl.particles + surfacelist[surfacelistindex]; blendmode = (pblend_t)p->blendmode; palpha = p->alpha; if(dofade && p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM) palpha *= min(1, (DotProduct(p->org, r_refdef.view.forward) - minparticledist_start) / (minparticledist_end - minparticledist_start)); alpha = palpha * colormultiplier[3]; // ensure alpha multiplier saturates properly if (alpha > 1.0f) alpha = 1.0f; switch (blendmode) { case PBLEND_INVALID: case PBLEND_INVMOD: // additive and modulate can just fade out in fog (this is correct) if (r_refdef.fogenabled) alpha *= RSurf_FogVertex(p->org); // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) alpha *= 1.0f / 256.0f; c4f[0] = p->color[0] * alpha; c4f[1] = p->color[1] * alpha; c4f[2] = p->color[2] * alpha; c4f[3] = 0; break; case PBLEND_ADD: // additive and modulate can just fade out in fog (this is correct) if (r_refdef.fogenabled) alpha *= RSurf_FogVertex(p->org); // collapse alpha into color for these blends (so that the particlefont does not need alpha on most textures) c4f[0] = p->color[0] * colormultiplier[0] * alpha; c4f[1] = p->color[1] * colormultiplier[1] * alpha; c4f[2] = p->color[2] * colormultiplier[2] * alpha; c4f[3] = 0; break; case PBLEND_ALPHA: c4f[0] = p->color[0] * colormultiplier[0]; c4f[1] = p->color[1] * colormultiplier[1]; c4f[2] = p->color[2] * colormultiplier[2]; c4f[3] = alpha; // note: lighting is not cheap! if (particletype[p->typeindex].lighting) { vecorg[0] = p->org[0]; vecorg[1] = p->org[1]; vecorg[2] = p->org[2]; R_LightPoint(c4f, vecorg, LP_LIGHTMAP | LP_RTWORLD | LP_DYNLIGHT); } // mix in the fog color if (r_refdef.fogenabled) { fog = RSurf_FogVertex(p->org); ifog = 1 - fog; c4f[0] = c4f[0] * fog + r_refdef.fogcolor[0] * ifog; c4f[1] = c4f[1] * fog + r_refdef.fogcolor[1] * ifog; c4f[2] = c4f[2] * fog + r_refdef.fogcolor[2] * ifog; } // for premultiplied alpha we have to apply the alpha to the color (after fog of course) VectorScale(c4f, alpha, c4f); break; } // copy the color into the other three vertices Vector4Copy(c4f, c4f + 4); Vector4Copy(c4f, c4f + 8); Vector4Copy(c4f, c4f + 12); size = p->size * cl_particles_size.value; tex = &particletexture[p->texnum]; switch(p->orientation) { // case PARTICLE_INVALID: case PARTICLE_BILLBOARD: if (p->angle + p->spin) { spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); spinsin = sin(spinrad) * size; spincos = cos(spinrad) * size; spinm1 = -p->stretch * spincos; spinm2 = -spinsin; spinm3 = spinsin; spinm4 = -p->stretch * spincos; VectorMAM(spinm1, r_refdef.view.left, spinm2, r_refdef.view.up, right); VectorMAM(spinm3, r_refdef.view.left, spinm4, r_refdef.view.up, up); } else { VectorScale(r_refdef.view.left, -size * p->stretch, right); VectorScale(r_refdef.view.up, size, up); } v3f[ 0] = p->org[0] - right[0] - up[0]; v3f[ 1] = p->org[1] - right[1] - up[1]; v3f[ 2] = p->org[2] - right[2] - up[2]; v3f[ 3] = p->org[0] - right[0] + up[0]; v3f[ 4] = p->org[1] - right[1] + up[1]; v3f[ 5] = p->org[2] - right[2] + up[2]; v3f[ 6] = p->org[0] + right[0] + up[0]; v3f[ 7] = p->org[1] + right[1] + up[1]; v3f[ 8] = p->org[2] + right[2] + up[2]; v3f[ 9] = p->org[0] + right[0] - up[0]; v3f[10] = p->org[1] + right[1] - up[1]; v3f[11] = p->org[2] + right[2] - up[2]; t2f[0] = tex->s1;t2f[1] = tex->t2; t2f[2] = tex->s1;t2f[3] = tex->t1; t2f[4] = tex->s2;t2f[5] = tex->t1; t2f[6] = tex->s2;t2f[7] = tex->t2; break; case PARTICLE_ORIENTED_DOUBLESIDED: vecvel[0] = p->vel[0]; vecvel[1] = p->vel[1]; vecvel[2] = p->vel[2]; VectorVectors(vecvel, baseright, baseup); if (p->angle + p->spin) { spinrad = (p->angle + p->spin * (spintime - p->delayedspawn)) * (float)(M_PI / 180.0f); spinsin = sin(spinrad) * size; spincos = cos(spinrad) * size; spinm1 = p->stretch * spincos; spinm2 = -spinsin; spinm3 = spinsin; spinm4 = p->stretch * spincos; VectorMAM(spinm1, baseright, spinm2, baseup, right); VectorMAM(spinm3, baseright, spinm4, baseup, up); } else { VectorScale(baseright, size * p->stretch, right); VectorScale(baseup, size, up); } v3f[ 0] = p->org[0] - right[0] - up[0]; v3f[ 1] = p->org[1] - right[1] - up[1]; v3f[ 2] = p->org[2] - right[2] - up[2]; v3f[ 3] = p->org[0] - right[0] + up[0]; v3f[ 4] = p->org[1] - right[1] + up[1]; v3f[ 5] = p->org[2] - right[2] + up[2]; v3f[ 6] = p->org[0] + right[0] + up[0]; v3f[ 7] = p->org[1] + right[1] + up[1]; v3f[ 8] = p->org[2] + right[2] + up[2]; v3f[ 9] = p->org[0] + right[0] - up[0]; v3f[10] = p->org[1] + right[1] - up[1]; v3f[11] = p->org[2] + right[2] - up[2]; t2f[0] = tex->s1;t2f[1] = tex->t2; t2f[2] = tex->s1;t2f[3] = tex->t1; t2f[4] = tex->s2;t2f[5] = tex->t1; t2f[6] = tex->s2;t2f[7] = tex->t2; break; case PARTICLE_SPARK: len = VectorLength(p->vel); VectorNormalize2(p->vel, up); lenfactor = p->stretch * 0.04 * len; if(lenfactor < size * 0.5) lenfactor = size * 0.5; VectorMA(p->org, -lenfactor, up, v); VectorMA(p->org, lenfactor, up, up2); R_CalcBeam_Vertex3f(v3f, v, up2, size); t2f[0] = tex->s1;t2f[1] = tex->t2; t2f[2] = tex->s1;t2f[3] = tex->t1; t2f[4] = tex->s2;t2f[5] = tex->t1; t2f[6] = tex->s2;t2f[7] = tex->t2; break; case PARTICLE_VBEAM: R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); VectorSubtract(p->vel, p->org, up); VectorNormalize(up); v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; t2f[0] = tex->s2;t2f[1] = v[0]; t2f[2] = tex->s1;t2f[3] = v[0]; t2f[4] = tex->s1;t2f[5] = v[1]; t2f[6] = tex->s2;t2f[7] = v[1]; break; case PARTICLE_HBEAM: R_CalcBeam_Vertex3f(v3f, p->org, p->vel, size); VectorSubtract(p->vel, p->org, up); VectorNormalize(up); v[0] = DotProduct(p->org, up) * (1.0f / 64.0f) * p->stretch; v[1] = DotProduct(p->vel, up) * (1.0f / 64.0f) * p->stretch; t2f[0] = v[0];t2f[1] = tex->t1; t2f[2] = v[0];t2f[3] = tex->t2; t2f[4] = v[1];t2f[5] = tex->t2; t2f[6] = v[1];t2f[7] = tex->t1; break; } } // now render batches of particles based on blendmode and texture blendmode = PBLEND_INVALID; texture = NULL; batchstart = 0; batchcount = 0; R_Mesh_PrepareVertices_Generic_Arrays(numsurfaces * 4, particle_vertex3f, particle_color4f, particle_texcoord2f); for (surfacelistindex = 0;surfacelistindex < numsurfaces;) { p = cl.particles + surfacelist[surfacelistindex]; if (texture != particletexture[p->texnum].texture) { texture = particletexture[p->texnum].texture; R_SetupShader_Generic(texture, NULL, GL_MODULATE, 1, false, false, false); } if (p->blendmode == PBLEND_INVMOD) { // inverse modulate blend - group these GL_BlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); // iterate until we find a change in settings batchstart = surfacelistindex++; for (;surfacelistindex < numsurfaces;surfacelistindex++) { p = cl.particles + surfacelist[surfacelistindex]; if (p->blendmode != PBLEND_INVMOD || texture != particletexture[p->texnum].texture) break; } } else { // additive or alpha blend - group these // (we can group these because we premultiplied the texture alpha) GL_BlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // iterate until we find a change in settings batchstart = surfacelistindex++; for (;surfacelistindex < numsurfaces;surfacelistindex++) { p = cl.particles + surfacelist[surfacelistindex]; if (p->blendmode == PBLEND_INVMOD || texture != particletexture[p->texnum].texture) break; } } batchcount = surfacelistindex - batchstart; R_Mesh_Draw(batchstart * 4, batchcount * 4, batchstart * 2, batchcount * 2, NULL, NULL, 0, particle_elements, NULL, 0); } } void R_DrawParticles (void) { int i, a; int drawparticles = r_drawparticles.integer; float minparticledist_start; particle_t *p; float gravity, frametime, f, dist, oldorg[3], decaldir[3]; float drawdist2; int hitent; trace_t trace; qboolean update; frametime = bound(0, cl.time - cl.particles_updatetime, 1); cl.particles_updatetime = bound(cl.time - 1, cl.particles_updatetime + frametime, cl.time + 1); // LordHavoc: early out conditions if (!cl.num_particles) return; minparticledist_start = DotProduct(r_refdef.view.origin, r_refdef.view.forward) + r_drawparticles_nearclip_min.value; gravity = frametime * cl.movevars_gravity; update = frametime > 0; drawdist2 = r_drawparticles_drawdistance.value * r_refdef.view.quality; drawdist2 = drawdist2*drawdist2; for (i = 0, p = cl.particles;i < cl.num_particles;i++, p++) { if (!p->typeindex) { if (cl.free_particle > i) cl.free_particle = i; continue; } if (update) { if (p->delayedspawn > cl.time) continue; p->size += p->sizeincrease * frametime; p->alpha -= p->alphafade * frametime; if (p->alpha <= 0 || p->die <= cl.time) goto killparticle; if (p->orientation != PARTICLE_VBEAM && p->orientation != PARTICLE_HBEAM && frametime > 0) { if (p->liquidfriction && cl_particles_collisions.integer && (CL_PointSuperContents(p->org) & SUPERCONTENTS_LIQUIDSMASK)) { if (p->typeindex == pt_blood) p->size += frametime * 8; else p->vel[2] -= p->gravity * gravity; f = 1.0f - min(p->liquidfriction * frametime, 1); VectorScale(p->vel, f, p->vel); } else { p->vel[2] -= p->gravity * gravity; if (p->airfriction) { f = 1.0f - min(p->airfriction * frametime, 1); VectorScale(p->vel, f, p->vel); } } VectorCopy(p->org, oldorg); VectorMA(p->org, frametime, p->vel, p->org); // if (p->bounce && cl.time >= p->delayedcollisions) if (p->bounce && cl_particles_collisions.integer && VectorLength(p->vel)) { trace = CL_TraceLine(oldorg, p->org, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | ((p->typeindex == pt_rain || p->typeindex == pt_snow) ? SUPERCONTENTS_LIQUIDSMASK : 0), 0, collision_extendmovelength.value, true, false, &hitent, false, false); // if the trace started in or hit something of SUPERCONTENTS_NODROP // or if the trace hit something flagged as NOIMPACT // then remove the particle if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT || ((trace.startsupercontents | trace.hitsupercontents) & SUPERCONTENTS_NODROP) || (trace.startsupercontents & SUPERCONTENTS_SOLID)) goto killparticle; VectorCopy(trace.endpos, p->org); // react if the particle hit something if (trace.fraction < 1) { VectorCopy(trace.endpos, p->org); if (p->staintexnum >= 0) { // blood - splash on solid if (!(trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS)) { R_Stain(p->org, 16, p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f)), p->staincolor[0], p->staincolor[1], p->staincolor[2], (int)(p->stainalpha * p->stainsize * (1.0f / 160.0f))); if (cl_decals.integer) { // create a decal for the blood splat a = 0xFFFFFF ^ (p->staincolor[0]*65536+p->staincolor[1]*256+p->staincolor[2]); if (cl_decals_newsystem_bloodsmears.integer) { VectorCopy(p->vel, decaldir); VectorNormalize(decaldir); } else VectorCopy(trace.plane.normal, decaldir); CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, a, a, p->staintexnum, p->stainsize, p->stainalpha); // staincolor needs to be inverted for decals! } } } if (p->typeindex == pt_blood) { // blood - splash on solid if (trace.hitq3surfaceflags & Q3SURFACEFLAG_NOMARKS) goto killparticle; if(p->staintexnum == -1) // staintex < -1 means no stains at all { R_Stain(p->org, 16, 64, 16, 16, (int)(p->alpha * p->size * (1.0f / 80.0f)), 64, 32, 32, (int)(p->alpha * p->size * (1.0f / 80.0f))); if (cl_decals.integer) { // create a decal for the blood splat if (cl_decals_newsystem_bloodsmears.integer) { VectorCopy(p->vel, decaldir); VectorNormalize(decaldir); } else VectorCopy(trace.plane.normal, decaldir); CL_SpawnDecalParticleForSurface(hitent, p->org, decaldir, p->color[0] * 65536 + p->color[1] * 256 + p->color[2], p->color[0] * 65536 + p->color[1] * 256 + p->color[2], tex_blooddecal[rand()&7], p->size * lhrandom(cl_particles_blood_decal_scalemin.value, cl_particles_blood_decal_scalemax.value), cl_particles_blood_decal_alpha.value * 768); } } goto killparticle; } else if (p->bounce < 0) { // bounce -1 means remove on impact goto killparticle; } else { // anything else - bounce off solid dist = DotProduct(p->vel, trace.plane.normal) * -p->bounce; VectorMA(p->vel, dist, trace.plane.normal, p->vel); } } } if (VectorLength2(p->vel) < 0.03) { if(p->orientation == PARTICLE_SPARK) // sparks are virtually invisible if very slow, so rather let them go off goto killparticle; VectorClear(p->vel); } } if (p->typeindex != pt_static) { switch (p->typeindex) { case pt_entityparticle: // particle that removes itself after one rendered frame if (p->time2) goto killparticle; else p->time2 = 1; break; case pt_blood: a = CL_PointSuperContents(p->org); if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_LAVA | SUPERCONTENTS_NODROP)) goto killparticle; break; case pt_bubble: a = CL_PointSuperContents(p->org); if (!(a & (SUPERCONTENTS_WATER | SUPERCONTENTS_SLIME))) goto killparticle; break; case pt_rain: a = CL_PointSuperContents(p->org); if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) goto killparticle; break; case pt_snow: if (cl.time > p->time2) { // snow flutter p->time2 = cl.time + (rand() & 3) * 0.1; p->vel[0] = p->vel[0] * 0.9f + lhrandom(-32, 32); p->vel[1] = p->vel[0] * 0.9f + lhrandom(-32, 32); } a = CL_PointSuperContents(p->org); if (a & (SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_LIQUIDSMASK)) goto killparticle; break; default: break; } } } else if (p->delayedspawn > cl.time) continue; if (!drawparticles) continue; // don't render particles too close to the view (they chew fillrate) // also don't render particles behind the view (useless) // further checks to cull to the frustum would be too slow here switch(p->typeindex) { case pt_beam: // beams have no culling R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); break; default: if(cl_particles_visculling.integer) if (!r_refdef.viewcache.world_novis) if(r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.PointInLeaf) { mleaf_t *leaf = r_refdef.scene.worldmodel->brush.PointInLeaf(r_refdef.scene.worldmodel, p->org); if(leaf) if(!CHECKPVSBIT(r_refdef.viewcache.world_pvsbits, leaf->clusterindex)) continue; } // anything else just has to be in front of the viewer and visible at this distance if (DotProduct(p->org, r_refdef.view.forward) >= minparticledist_start && VectorDistance2(p->org, r_refdef.view.origin) < drawdist2 * (p->size * p->size)) R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, p->sortorigin, R_DrawParticle_TransparentCallback, NULL, i, NULL); break; } continue; killparticle: p->typeindex = 0; if (cl.free_particle > i) cl.free_particle = i; } // reduce cl.num_particles if possible while (cl.num_particles > 0 && cl.particles[cl.num_particles - 1].typeindex == 0) cl.num_particles--; if (cl.num_particles == cl.max_particles && cl.max_particles < MAX_PARTICLES) { particle_t *oldparticles = cl.particles; cl.max_particles = min(cl.max_particles * 2, MAX_PARTICLES); cl.particles = (particle_t *) Mem_Alloc(cls.levelmempool, cl.max_particles * sizeof(particle_t)); memcpy(cl.particles, oldparticles, cl.num_particles * sizeof(particle_t)); Mem_Free(oldparticles); } } darkplaces/r_shadow.h0000664000175000017500000001470213067716222014150 0ustar kalevkalev #ifndef R_SHADOW_H #define R_SHADOW_H #define R_SHADOW_SHADOWMAP_NUMCUBEMAPS 8 extern cvar_t r_shadow_bumpscale_basetexture; extern cvar_t r_shadow_bumpscale_bumpmap; extern cvar_t r_shadow_debuglight; extern cvar_t r_shadow_gloss; extern cvar_t r_shadow_gloss2intensity; extern cvar_t r_shadow_glossintensity; extern cvar_t r_shadow_glossexponent; extern cvar_t r_shadow_gloss2exponent; extern cvar_t r_shadow_glossexact; extern cvar_t r_shadow_lightattenuationpower; extern cvar_t r_shadow_lightattenuationscale; extern cvar_t r_shadow_lightintensityscale; extern cvar_t r_shadow_lightradiusscale; extern cvar_t r_shadow_projectdistance; extern cvar_t r_shadow_frontsidecasting; extern cvar_t r_shadow_realtime_dlight; extern cvar_t r_shadow_realtime_dlight_shadows; extern cvar_t r_shadow_realtime_dlight_svbspculling; extern cvar_t r_shadow_realtime_dlight_portalculling; extern cvar_t r_shadow_realtime_world; extern cvar_t r_shadow_realtime_world_lightmaps; extern cvar_t r_shadow_realtime_world_shadows; extern cvar_t r_shadow_realtime_world_compile; extern cvar_t r_shadow_realtime_world_compileshadow; extern cvar_t r_shadow_realtime_world_compilesvbsp; extern cvar_t r_shadow_realtime_world_compileportalculling; extern cvar_t r_shadow_scissor; extern cvar_t r_shadow_polygonfactor; extern cvar_t r_shadow_polygonoffset; extern cvar_t r_shadow_texture3d; extern cvar_t gl_ext_separatestencil; extern cvar_t gl_ext_stenciltwoside; // used by shader for bouncegrid feature typedef struct r_shadow_bouncegrid_settings_s { qboolean staticmode; qboolean bounceanglediffuse; qboolean directionalshading; qboolean includedirectlighting; qboolean blur; int floatcolors; float dlightparticlemultiplier; qboolean hitmodels; float lightradiusscale; int maxbounce; int lightpathsize; float particlebounceintensity; float particleintensity; int maxphotons; float energyperphoton; float spacing[3]; int stablerandom; } r_shadow_bouncegrid_settings_t; typedef struct r_shadow_bouncegrid_state_s { r_shadow_bouncegrid_settings_t settings; qboolean capable; qboolean allowdirectionalshading; qboolean directional; // copied from settings.directionalshading after createtexture is decided qboolean createtexture; // set to true to recreate the texture rather than updating it - happens when size changes or directional changes rtexture_t *texture; matrix4x4_t matrix; vec_t intensity; double lastupdatetime; int resolution[3]; int numpixels; int pixelbands; int pixelsperband; int bytesperband; float spacing[3]; float ispacing[3]; vec3_t mins; vec3_t maxs; vec3_t size; int maxsplatpaths; // per-frame data that is very temporary int numsplatpaths; struct r_shadow_bouncegrid_splatpath_s *splatpaths; float *highpixels; } r_shadow_bouncegrid_state_t; extern r_shadow_bouncegrid_state_t r_shadow_bouncegrid_state; void R_Shadow_Init(void); qboolean R_Shadow_ShadowMappingEnabled(void); void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs); void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris); void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs); int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias); int R_Shadow_CalcSphereSideMask(const vec3_t p1, float radius, float bias); int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals); void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist); void R_Shadow_RenderMode_Begin(void); void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight); void R_Shadow_RenderMode_Reset(void); void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass); void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping); void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping); void R_Shadow_RenderMode_VisibleShadowVolumes(void); void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent); void R_Shadow_RenderMode_End(void); void R_Shadow_ClearStencil(void); void R_Shadow_SetupEntityLight(const entity_render_t *ent); qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs); // these never change, they are used to create attenuation matrices extern matrix4x4_t matrix_attenuationxyz; extern matrix4x4_t matrix_attenuationz; void R_Shadow_UpdateWorldLightSelection(void); extern rtlight_t *r_shadow_compilingrtlight; void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags); void R_RTLight_Compile(rtlight_t *rtlight); void R_RTLight_Uncompile(rtlight_t *rtlight); void R_Shadow_PrepareLights(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_Shadow_DrawPrepass(void); void R_Shadow_DrawLights(void); void R_Shadow_DrawCoronas(void); extern int maxshadowmark; extern int numshadowmark; extern int *shadowmark; extern int *shadowmarklist; extern int shadowmarkcount; void R_Shadow_PrepareShadowMark(int numtris); extern int maxshadowsides; extern int numshadowsides; extern unsigned char *shadowsides; extern int *shadowsideslist; void R_Shadow_PrepareShadowSides(int numtris); void R_Shadow_PrepareModelShadows(void); #define LP_LIGHTMAP 1 #define LP_RTWORLD 2 #define LP_DYNLIGHT 4 void R_LightPoint(float *color, const vec3_t p, const int flags); void R_CompleteLightPoint(float *ambientcolor, float *diffusecolor, float *diffusenormal, const vec3_t p, const int flags); void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); #endif darkplaces/quakedef.h0000664000175000017500000005204413067716222014130 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // quakedef.h -- primary header for client #ifndef QUAKEDEF_H #define QUAKEDEF_H #ifdef __APPLE__ # include #endif #if defined(__GNUC__) && (__GNUC__ > 2) #define DP_FUNC_PRINTF(n) __attribute__ ((format (printf, n, n+1))) #define DP_FUNC_PURE __attribute__ ((pure)) #define DP_FUNC_NORETURN __attribute__ ((noreturn)) #else #define DP_FUNC_PRINTF(n) #define DP_FUNC_PURE #define DP_FUNC_NORETURN #endif #include #include #include #include #include #include #include #include #include #include "qtypes.h" extern const char *buildstring; extern char engineversion[128]; #define GAMENAME "id1" #define MAX_NUM_ARGVS 50 #ifdef DP_SMALLMEMORY #define MAX_INPUTLINE 1024 #define CON_TEXTSIZE 16384 #define CON_MAXLINES 256 #define HIST_TEXTSIZE 2048 #define HIST_MAXLINES 16 #define MAX_ALIAS_NAME 32 #define CMDBUFSIZE 131072 #define MAX_ARGS 80 #define NET_MAXMESSAGE 65536 #define MAX_PACKETFRAGMENT 1024 #define MAX_EDICTS 4096 #define MAX_MODELS 1024 #define MAX_SOUNDS 1024 #define MAX_LIGHTSTYLES 64 #define MAX_STYLESTRING 16 #define MAX_SCOREBOARD 32 #define MAX_SCOREBOARDNAME 128 #define MAX_USERINFO_STRING 196 #define MAX_SERVERINFO_STRING 512 #define MAX_LOCALINFO_STRING 1 // not actually used by DP servers #define CL_MAX_USERCMDS 32 #define CVAR_HASHSIZE 1024 #define M_MAX_EDICTS 4096 #define MAX_DEMOS 8 #define MAX_DEMONAME 16 #define MAX_SAVEGAMES 12 #define SAVEGAME_COMMENT_LENGTH 39 #define MAX_CLIENTNETWORKEYES 2 #define MAX_LEVELNETWORKEYES 0 // no portal support #define MAX_OCCLUSION_QUERIES 256 #define CRYPTO_HOSTKEY_HASHSIZE 256 #define MAX_NETWM_ICON 1026 // one 32x32 #define MAX_WATERPLANES 2 #define MAX_CUBEMAPS 1024 #define MAX_EXPLOSIONS 8 #define MAX_DLIGHTS 16 #define MAX_CACHED_PICS 1024 // this is 144 bytes each (or 152 on 64bit) #define CACHEPICHASHSIZE 256 #define MAX_PARTICLEEFFECTNAME 256 #define MAX_PARTICLEEFFECTINFO 1024 #define MAX_PARTICLETEXTURES 256 #define MAXCLVIDEOS 1 #define MAX_DYNAMIC_TEXTURE_COUNT 2 #define MAX_MAP_LEAFS 8192 #define MAXTRACKS 256 #define MAX_DYNAMIC_CHANNELS 64 #define MAX_CHANNELS 260 #define MODLIST_TOTALSIZE 32 #define MAX_FAVORITESERVERS 32 #define MAX_DECALSYSTEM_QUEUE 64 #define PAINTBUFFER_SIZE 512 #define MAX_BINDMAPS 8 #define MAX_PARTICLES_INITIAL 8192 #define MAX_PARTICLES 8192 #define MAX_DECALS_INITIAL 1024 #define MAX_DECALS 1024 #define MAX_ENITIES_INITIAL 256 #define MAX_STATICENTITIES 256 #define MAX_EFFECTS 16 #define MAX_BEAMS 16 #define MAX_TEMPENTITIES 256 #define SERVERLIST_TOTALSIZE 1024 #define SERVERLIST_ANDMASKCOUNT 5 #define SERVERLIST_ORMASKCOUNT 5 #else #define MAX_INPUTLINE 16384 ///< maximum length of console commandline, QuakeC strings, and many other text processing buffers #define CON_TEXTSIZE 1048576 ///< max scrollback buffer characters in console #define CON_MAXLINES 16384 ///< max scrollback buffer lines in console #define HIST_TEXTSIZE 262144 ///< max command history buffer characters in console #define HIST_MAXLINES 4096 ///< max command history buffer lines in console #define MAX_ALIAS_NAME 32 #define CMDBUFSIZE 655360 ///< maximum script size that can be loaded by the exec command (8192 in Quake) #define MAX_ARGS 80 ///< maximum number of parameters to a console command or alias #define NET_MAXMESSAGE 65536 ///< max reliable packet size (sent as multiple fragments of MAX_PACKETFRAGMENT) #define MAX_PACKETFRAGMENT 1024 ///< max length of packet fragment #define MAX_EDICTS 32768 ///< max number of objects in game world at once (32768 protocol limit) #define MAX_MODELS 8192 ///< max number of models loaded at once (including during level transitions) #define MAX_SOUNDS 4096 ///< max number of sounds loaded at once #define MAX_LIGHTSTYLES 256 ///< max flickering light styles in level (note: affects savegame format) #define MAX_STYLESTRING 64 ///< max length of flicker pattern for light style #define MAX_SCOREBOARD 255 ///< max number of players in game at once (255 protocol limit) #define MAX_SCOREBOARDNAME 128 ///< max length of player name in game #define MAX_USERINFO_STRING 1280 ///< max length of infostring for PROTOCOL_QUAKEWORLD (196 in QuakeWorld) #define MAX_SERVERINFO_STRING 1280 ///< max length of server infostring for PROTOCOL_QUAKEWORLD (512 in QuakeWorld) #define MAX_LOCALINFO_STRING 32768 ///< max length of server-local infostring for PROTOCOL_QUAKEWORLD (32768 in QuakeWorld) #define CL_MAX_USERCMDS 128 ///< max number of predicted input packets in queue #define CVAR_HASHSIZE 65536 ///< number of hash buckets for accelerating cvar name lookups #define M_MAX_EDICTS 32768 ///< max objects in menu vm #define MAX_DEMOS 8 ///< max demos provided to demos command #define MAX_DEMONAME 16 ///< max demo name length for demos command #define MAX_SAVEGAMES 12 ///< max savegames listed in savegame menu #define SAVEGAME_COMMENT_LENGTH 39 ///< max comment length of savegame in menu #define MAX_CLIENTNETWORKEYES 16 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) #define MAX_LEVELNETWORKEYES 512 ///< max number of locations that can be added to pvs when culling network entities (must be at least 2 for prediction) #define MAX_OCCLUSION_QUERIES 4096 ///< max number of GL_ARB_occlusion_query objects that can be used in one frame #define CRYPTO_HOSTKEY_HASHSIZE 8192 ///< number of hash buckets for accelerating host key lookups #define MAX_NETWM_ICON 352822 // 16x16, 22x22, 24x24, 32x32, 48x48, 64x64, 128x128, 256x256, 512x512 #define MAX_WATERPLANES 16 ///< max number of water planes visible (each one causes additional view renders) #define MAX_CUBEMAPS 1024 ///< max number of cubemap textures loaded for light filters #define MAX_EXPLOSIONS 64 ///< max number of explosion shell effects active at once (not particle related) #define MAX_DLIGHTS 256 ///< max number of dynamic lights (rocket flashes, etc) in scene at once #define MAX_CACHED_PICS 1024 ///< max number of 2D pics loaded at once #define CACHEPICHASHSIZE 256 ///< number of hash buckets for accelerating 2D pic name lookups #define MAX_PARTICLEEFFECTNAME 4096 ///< maximum number of unique names of particle effects (for particleeffectnum) #define MAX_PARTICLEEFFECTINFO 8192 ///< maximum number of unique particle effects (each name may associate with several of these) #define MAX_PARTICLETEXTURES 256 ///< maximum number of unique particle textures in the particle font #define MAXCLVIDEOS 65 ///< maximum number of video streams being played back at once (1 is reserved for the playvideo command) #define MAX_DYNAMIC_TEXTURE_COUNT 64 ///< maximum number of dynamic textures (web browsers, playvideo, etc) #define MAX_MAP_LEAFS 65536 ///< maximum number of BSP leafs in world (8192 in Quake) #define MAXTRACKS 256 ///< max CD track index // 0 to NUM_AMBIENTS - 1 = water, etc // NUM_AMBIENTS to NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS - 1 = normal entity sounds // NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS to total_channels = static sounds #define MAX_DYNAMIC_CHANNELS 512 #define MAX_CHANNELS (8192 + 4) #define MODLIST_TOTALSIZE 256 #define MAX_FAVORITESERVERS 256 #define MAX_DECALSYSTEM_QUEUE 1024 #define PAINTBUFFER_SIZE 2048 #define MAX_BINDMAPS 8 #define MAX_PARTICLES_INITIAL 8192 ///< initial allocation for cl.particles #define MAX_PARTICLES 1048576 ///< upper limit on cl.particles size #define MAX_DECALS_INITIAL 8192 ///< initial allocation for cl.decals #define MAX_DECALS 1048576 ///< upper limit on cl.decals size #define MAX_ENITIES_INITIAL 256 ///< initial size of cl.entities #define MAX_STATICENTITIES 1024 ///< limit on size of cl.static_entities #define MAX_EFFECTS 256 ///< limit on size of cl.effects #define MAX_BEAMS 256 ///< limit on size of cl.beams #define MAX_TEMPENTITIES 4096 ///< max number of temporary models visible per frame (certain sprite effects, certain types of CSQC entities also use this) #define SERVERLIST_TOTALSIZE 2048 ///< max servers in the server list #define SERVERLIST_ANDMASKCOUNT 16 ///< max items in server list AND mask #define SERVERLIST_ORMASKCOUNT 16 ///< max items in server list OR mask #endif #define CMD_TOKENIZELENGTH (MAX_INPUTLINE + MAX_ARGS) ///< maximum tokenizable commandline length (counting trailing 0) #define MAX_QPATH 128 ///< max length of a quake game pathname #ifdef PATH_MAX #define MAX_OSPATH PATH_MAX #elif MAX_PATH #define MAX_OSPATH MAX_PATH #else #define MAX_OSPATH 1024 ///< max length of a filesystem pathname #endif #define ON_EPSILON 0.1 ///< point on plane side epsilon #define NET_MINRATE 1000 ///< limits "rate" and "sv_maxrate" cvars // // stats are integers communicated to the client by the server // #define MAX_CL_STATS 256 #define STAT_HEALTH 0 //#define STAT_FRAGS 1 #define STAT_WEAPON 2 #define STAT_AMMO 3 #define STAT_ARMOR 4 #define STAT_WEAPONFRAME 5 #define STAT_SHELLS 6 #define STAT_NAILS 7 #define STAT_ROCKETS 8 #define STAT_CELLS 9 #define STAT_ACTIVEWEAPON 10 #define STAT_TOTALSECRETS 11 #define STAT_TOTALMONSTERS 12 #define STAT_SECRETS 13 ///< bumped on client side by svc_foundsecret #define STAT_MONSTERS 14 ///< bumped by svc_killedmonster #define STAT_ITEMS 15 ///< FTE, DP #define STAT_VIEWHEIGHT 16 ///< FTE, DP //#define STAT_TIME 17 ///< FTE //#define STAT_VIEW2 20 ///< FTE #define STAT_VIEWZOOM 21 ///< DP #define STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR 220 ///< DP #define STAT_MOVEVARS_AIRCONTROL_PENALTY 221 ///< DP #define STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW 222 ///< DP #define STAT_MOVEVARS_AIRSTRAFEACCEL_QW 223 ///< DP #define STAT_MOVEVARS_AIRCONTROL_POWER 224 ///< DP #define STAT_MOVEFLAGS 225 ///< DP #define STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL 226 ///< DP #define STAT_MOVEVARS_WARSOWBUNNY_ACCEL 227 ///< DP #define STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED 228 ///< DP #define STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL 229 ///< DP #define STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO 230 ///< DP #define STAT_MOVEVARS_AIRSTOPACCELERATE 231 ///< DP #define STAT_MOVEVARS_AIRSTRAFEACCELERATE 232 ///< DP #define STAT_MOVEVARS_MAXAIRSTRAFESPEED 233 ///< DP #define STAT_MOVEVARS_AIRCONTROL 234 ///< DP #define STAT_FRAGLIMIT 235 ///< DP #define STAT_TIMELIMIT 236 ///< DP #define STAT_MOVEVARS_WALLFRICTION 237 ///< DP #define STAT_MOVEVARS_FRICTION 238 ///< DP #define STAT_MOVEVARS_WATERFRICTION 239 ///< DP #define STAT_MOVEVARS_TICRATE 240 ///< DP #define STAT_MOVEVARS_TIMESCALE 241 ///< DP #define STAT_MOVEVARS_GRAVITY 242 ///< DP #define STAT_MOVEVARS_STOPSPEED 243 ///< DP #define STAT_MOVEVARS_MAXSPEED 244 ///< DP #define STAT_MOVEVARS_SPECTATORMAXSPEED 245 ///< DP #define STAT_MOVEVARS_ACCELERATE 246 ///< DP #define STAT_MOVEVARS_AIRACCELERATE 247 ///< DP #define STAT_MOVEVARS_WATERACCELERATE 248 ///< DP #define STAT_MOVEVARS_ENTGRAVITY 249 ///< DP #define STAT_MOVEVARS_JUMPVELOCITY 250 ///< DP #define STAT_MOVEVARS_EDGEFRICTION 251 ///< DP #define STAT_MOVEVARS_MAXAIRSPEED 252 ///< DP #define STAT_MOVEVARS_STEPHEIGHT 253 ///< DP #define STAT_MOVEVARS_AIRACCEL_QW 254 ///< DP #define STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION 255 ///< DP // moveflags values #define MOVEFLAG_VALID 0x80000000 #define MOVEFLAG_Q2AIRACCELERATE 0x00000001 #define MOVEFLAG_NOGRAVITYONGROUND 0x00000002 #define MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE 0x00000004 // stock defines #define IT_SHOTGUN 1 #define IT_SUPER_SHOTGUN 2 #define IT_NAILGUN 4 #define IT_SUPER_NAILGUN 8 #define IT_GRENADE_LAUNCHER 16 #define IT_ROCKET_LAUNCHER 32 #define IT_LIGHTNING 64 #define IT_SUPER_LIGHTNING 128 #define IT_SHELLS 256 #define IT_NAILS 512 #define IT_ROCKETS 1024 #define IT_CELLS 2048 #define IT_AXE 4096 #define IT_ARMOR1 8192 #define IT_ARMOR2 16384 #define IT_ARMOR3 32768 #define IT_SUPERHEALTH 65536 #define IT_KEY1 131072 #define IT_KEY2 262144 #define IT_INVISIBILITY 524288 #define IT_INVULNERABILITY 1048576 #define IT_SUIT 2097152 #define IT_QUAD 4194304 #define IT_SIGIL1 (1<<28) #define IT_SIGIL2 (1<<29) #define IT_SIGIL3 (1<<30) #define IT_SIGIL4 (1<<31) //=========================================== // AK nexuiz changed and added defines #define NEX_IT_UZI 1 #define NEX_IT_SHOTGUN 2 #define NEX_IT_GRENADE_LAUNCHER 4 #define NEX_IT_ELECTRO 8 #define NEX_IT_CRYLINK 16 #define NEX_IT_NEX 32 #define NEX_IT_HAGAR 64 #define NEX_IT_ROCKET_LAUNCHER 128 #define NEX_IT_SHELLS 256 #define NEX_IT_BULLETS 512 #define NEX_IT_ROCKETS 1024 #define NEX_IT_CELLS 2048 #define NEX_IT_LASER 4094 #define NEX_IT_STRENGTH 8192 #define NEX_IT_INVINCIBLE 16384 #define NEX_IT_SPEED 32768 #define NEX_IT_SLOWMO 65536 //=========================================== //rogue changed and added defines #define RIT_SHELLS 128 #define RIT_NAILS 256 #define RIT_ROCKETS 512 #define RIT_CELLS 1024 #define RIT_AXE 2048 #define RIT_LAVA_NAILGUN 4096 #define RIT_LAVA_SUPER_NAILGUN 8192 #define RIT_MULTI_GRENADE 16384 #define RIT_MULTI_ROCKET 32768 #define RIT_PLASMA_GUN 65536 #define RIT_ARMOR1 8388608 #define RIT_ARMOR2 16777216 #define RIT_ARMOR3 33554432 #define RIT_LAVA_NAILS 67108864 #define RIT_PLASMA_AMMO 134217728 #define RIT_MULTI_ROCKETS 268435456 #define RIT_SHIELD 536870912 #define RIT_ANTIGRAV 1073741824 #define RIT_SUPERHEALTH 2147483648 //MED 01/04/97 added hipnotic defines //=========================================== //hipnotic added defines #define HIT_PROXIMITY_GUN_BIT 16 #define HIT_MJOLNIR_BIT 7 #define HIT_LASER_CANNON_BIT 23 #define HIT_PROXIMITY_GUN (1<server packet. Demo // playback will ignore this, but it may be useful to make DP sniff packets to // debug protocol exploits. #define DEMOMSG_CLIENT_TO_SERVER 0x80000000 // In Quake, any char in 0..32 counts as whitespace //#define ISWHITESPACE(ch) ((unsigned char) ch <= (unsigned char) ' ') #define ISWHITESPACE(ch) (!(ch) || (ch) == ' ' || (ch) == '\t' || (ch) == '\r' || (ch) == '\n') // This also includes extended characters, and ALL control chars #define ISWHITESPACEORCONTROL(ch) ((signed char) (ch) <= (signed char) ' ') #ifdef PRVM_64 #define FLOAT_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFFFFFFFFFF) // also match "negative zero" doubles of value 0x8000000000000000 #define FLOAT_LOSSLESS_FORMAT "%.17g" #define VECTOR_LOSSLESS_FORMAT "%.17g %.17g %.17g" #else #define FLOAT_IS_TRUE_FOR_INT(x) ((x) & 0x7FFFFFFF) // also match "negative zero" floats of value 0x80000000 #define FLOAT_LOSSLESS_FORMAT "%.9g" #define VECTOR_LOSSLESS_FORMAT "%.9g %.9g %.9g" #endif // originally this was _MSC_VER // but here we want to test the system libc, which on win32 is borked, and NOT the compiler #ifdef WIN32 #define INT_LOSSLESS_FORMAT_SIZE "I64" #define INT_LOSSLESS_FORMAT_CONVERT_S(x) ((__int64)(x)) #define INT_LOSSLESS_FORMAT_CONVERT_U(x) ((unsigned __int64)(x)) #else #define INT_LOSSLESS_FORMAT_SIZE "j" #define INT_LOSSLESS_FORMAT_CONVERT_S(x) ((intmax_t)(x)) #define INT_LOSSLESS_FORMAT_CONVERT_U(x) ((uintmax_t)(x)) #endif #endif darkplaces/model_alias.c0000664000175000017500000053472713067716220014622 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "image.h" #include "r_shadow.h" #include "mod_skeletal_animatevertices_generic.h" #ifdef SSE_POSSIBLE #include "mod_skeletal_animatevertices_sse.h" #endif #ifdef SSE_POSSIBLE static qboolean r_skeletal_use_sse_defined = false; cvar_t r_skeletal_use_sse = {0, "r_skeletal_use_sse", "1", "use SSE for skeletal model animation"}; #endif cvar_t r_skeletal_debugbone = {0, "r_skeletal_debugbone", "-1", "development cvar for testing skeletal model code"}; cvar_t r_skeletal_debugbonecomponent = {0, "r_skeletal_debugbonecomponent", "3", "development cvar for testing skeletal model code"}; cvar_t r_skeletal_debugbonevalue = {0, "r_skeletal_debugbonevalue", "100", "development cvar for testing skeletal model code"}; cvar_t r_skeletal_debugtranslatex = {0, "r_skeletal_debugtranslatex", "1", "development cvar for testing skeletal model code"}; cvar_t r_skeletal_debugtranslatey = {0, "r_skeletal_debugtranslatey", "1", "development cvar for testing skeletal model code"}; cvar_t r_skeletal_debugtranslatez = {0, "r_skeletal_debugtranslatez", "1", "development cvar for testing skeletal model code"}; cvar_t mod_alias_supporttagscale = {0, "mod_alias_supporttagscale", "1", "support scaling factors in bone/tag attachment matrices as supported by MD3"}; cvar_t mod_alias_force_animated = {0, "mod_alias_force_animated", "", "if set to an non-empty string, overrides the is-animated flag of any alias models (for benchmarking)"}; float mod_md3_sin[320]; static size_t Mod_Skeletal_AnimateVertices_maxbonepose = 0; static void *Mod_Skeletal_AnimateVertices_bonepose = NULL; void Mod_Skeletal_FreeBuffers(void) { if(Mod_Skeletal_AnimateVertices_bonepose) Mem_Free(Mod_Skeletal_AnimateVertices_bonepose); Mod_Skeletal_AnimateVertices_maxbonepose = 0; Mod_Skeletal_AnimateVertices_bonepose = NULL; } void *Mod_Skeletal_AnimateVertices_AllocBuffers(size_t nbytes) { if(Mod_Skeletal_AnimateVertices_maxbonepose < nbytes) { if(Mod_Skeletal_AnimateVertices_bonepose) Mem_Free(Mod_Skeletal_AnimateVertices_bonepose); Mod_Skeletal_AnimateVertices_bonepose = Z_Malloc(nbytes); Mod_Skeletal_AnimateVertices_maxbonepose = nbytes; } return Mod_Skeletal_AnimateVertices_bonepose; } void Mod_Skeletal_BuildTransforms(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT bonepose, float * RESTRICT boneposerelative) { int i, blends; float m[12]; if (!bonepose) bonepose = (float * RESTRICT) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(float[12]) * model->num_bones); if (skeleton && !skeleton->relativetransforms) skeleton = NULL; // interpolate matrices if (skeleton) { for (i = 0;i < model->num_bones;i++) { Matrix4x4_ToArray12FloatD3D(&skeleton->relativetransforms[i], m); if (model->data_bones[i].parent >= 0) R_ConcatTransforms(bonepose + model->data_bones[i].parent * 12, m, bonepose + i * 12); else memcpy(bonepose + i * 12, m, sizeof(m)); // create a relative deformation matrix to describe displacement // from the base mesh, which is used by the actual weighting R_ConcatTransforms(bonepose + i * 12, model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12); } } else { for (i = 0;i < model->num_bones;i++) { // blend by transform each quaternion/translation into a dual-quaternion first, then blending const short * RESTRICT firstpose7s = model->data_poses7s + 7 * (frameblend[0].subframe * model->num_bones + i); float firstlerp = frameblend[0].lerp, firsttx = firstpose7s[0], firstty = firstpose7s[1], firsttz = firstpose7s[2], rx = firstpose7s[3] * firstlerp, ry = firstpose7s[4] * firstlerp, rz = firstpose7s[5] * firstlerp, rw = firstpose7s[6] * firstlerp, dx = firsttx*rw + firstty*rz - firsttz*ry, dy = -firsttx*rz + firstty*rw + firsttz*rx, dz = firsttx*ry - firstty*rx + firsttz*rw, dw = -firsttx*rx - firstty*ry - firsttz*rz, scale, sx, sy, sz, sw; for (blends = 1;blends < MAX_FRAMEBLENDS && frameblend[blends].lerp > 0;blends++) { const short * RESTRICT blendpose7s = model->data_poses7s + 7 * (frameblend[blends].subframe * model->num_bones + i); float blendlerp = frameblend[blends].lerp, blendtx = blendpose7s[0], blendty = blendpose7s[1], blendtz = blendpose7s[2], qx = blendpose7s[3], qy = blendpose7s[4], qz = blendpose7s[5], qw = blendpose7s[6]; if(rx*qx + ry*qy + rz*qz + rw*qw < 0) blendlerp = -blendlerp; qx *= blendlerp; qy *= blendlerp; qz *= blendlerp; qw *= blendlerp; rx += qx; ry += qy; rz += qz; rw += qw; dx += blendtx*qw + blendty*qz - blendtz*qy; dy += -blendtx*qz + blendty*qw + blendtz*qx; dz += blendtx*qy - blendty*qx + blendtz*qw; dw += -blendtx*qx - blendty*qy - blendtz*qz; } // generate a matrix from the dual-quaternion, implicitly normalizing it in the process scale = 1.0f / (rx*rx + ry*ry + rz*rz + rw*rw); sx = rx * scale; sy = ry * scale; sz = rz * scale; sw = rw * scale; m[0] = sw*rw + sx*rx - sy*ry - sz*rz; m[1] = 2*(sx*ry - sw*rz); m[2] = 2*(sx*rz + sw*ry); m[3] = model->num_posescale*(dx*sw - dy*sz + dz*sy - dw*sx); m[4] = 2*(sx*ry + sw*rz); m[5] = sw*rw + sy*ry - sx*rx - sz*rz; m[6] = 2*(sy*rz - sw*rx); m[7] = model->num_posescale*(dx*sz + dy*sw - dz*sx - dw*sy); m[8] = 2*(sx*rz - sw*ry); m[9] = 2*(sy*rz + sw*rx); m[10] = sw*rw + sz*rz - sx*rx - sy*ry; m[11] = model->num_posescale*(dy*sx + dz*sw - dx*sy - dw*sz); if (i == r_skeletal_debugbone.integer) m[r_skeletal_debugbonecomponent.integer % 12] += r_skeletal_debugbonevalue.value; m[3] *= r_skeletal_debugtranslatex.value; m[7] *= r_skeletal_debugtranslatey.value; m[11] *= r_skeletal_debugtranslatez.value; if (model->data_bones[i].parent >= 0) R_ConcatTransforms(bonepose + model->data_bones[i].parent * 12, m, bonepose + i * 12); else memcpy(bonepose + i * 12, m, sizeof(m)); // create a relative deformation matrix to describe displacement // from the base mesh, which is used by the actual weighting R_ConcatTransforms(bonepose + i * 12, model->data_baseboneposeinverse + i * 12, boneposerelative + i * 12); } } } static void Mod_Skeletal_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) { if (!model->surfmesh.num_vertices) return; if (!model->num_bones) { if (vertex3f) memcpy(vertex3f, model->surfmesh.data_vertex3f, model->surfmesh.num_vertices*sizeof(float[3])); if (normal3f) memcpy(normal3f, model->surfmesh.data_normal3f, model->surfmesh.num_vertices*sizeof(float[3])); if (svector3f) memcpy(svector3f, model->surfmesh.data_svector3f, model->surfmesh.num_vertices*sizeof(float[3])); if (tvector3f) memcpy(tvector3f, model->surfmesh.data_tvector3f, model->surfmesh.num_vertices*sizeof(float[3])); return; } #ifdef SSE_POSSIBLE if(r_skeletal_use_sse_defined) if(r_skeletal_use_sse.integer) { Mod_Skeletal_AnimateVertices_SSE(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); return; } #endif Mod_Skeletal_AnimateVertices_Generic(model, frameblend, skeleton, vertex3f, normal3f, svector3f, tvector3f); } void Mod_AliasInit (void) { int i; Cvar_RegisterVariable(&r_skeletal_debugbone); Cvar_RegisterVariable(&r_skeletal_debugbonecomponent); Cvar_RegisterVariable(&r_skeletal_debugbonevalue); Cvar_RegisterVariable(&r_skeletal_debugtranslatex); Cvar_RegisterVariable(&r_skeletal_debugtranslatey); Cvar_RegisterVariable(&r_skeletal_debugtranslatez); Cvar_RegisterVariable(&mod_alias_supporttagscale); Cvar_RegisterVariable(&mod_alias_force_animated); for (i = 0;i < 320;i++) mod_md3_sin[i] = sin(i * M_PI * 2.0f / 256.0); #ifdef SSE_POSSIBLE if(Sys_HaveSSE()) { Con_Printf("Skeletal animation uses SSE code path\n"); r_skeletal_use_sse_defined = true; Cvar_RegisterVariable(&r_skeletal_use_sse); } else Con_Printf("Skeletal animation uses generic code path (SSE disabled or not detected)\n"); #else Con_Printf("Skeletal animation uses generic code path (SSE not compiled in)\n"); #endif } static int Mod_Skeletal_AddBlend(dp_model_t *model, const blendweights_t *newweights) { int i; blendweights_t *weights; if(!newweights->influence[1]) return newweights->index[0]; weights = model->surfmesh.data_blendweights; for (i = 0;i < model->surfmesh.num_blends;i++, weights++) { if (!memcmp(weights, newweights, sizeof(blendweights_t))) return model->num_bones + i; } model->surfmesh.num_blends++; memcpy(weights, newweights, sizeof(blendweights_t)); return model->num_bones + i; } static int Mod_Skeletal_CompressBlend(dp_model_t *model, const int *newindex, const float *newinfluence) { int i, total; float scale; blendweights_t newweights; if(!newinfluence[1]) return newindex[0]; scale = 0; for (i = 0;i < 4;i++) scale += newinfluence[i]; scale = 255.0f / scale; total = 0; for (i = 0;i < 4;i++) { newweights.index[i] = newindex[i]; newweights.influence[i] = (unsigned char)(newinfluence[i] * scale); total += newweights.influence[i]; } while (total > 255) { for (i = 0;i < 4;i++) { if(newweights.influence[i] > 0 && total > 255) { newweights.influence[i]--; total--; } } } while (total < 255) { for (i = 0; i < 4;i++) { if(newweights.influence[i] < 255 && total < 255) { newweights.influence[i]++; total++; } } } return Mod_Skeletal_AddBlend(model, &newweights); } static void Mod_MD3_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) { // vertex morph int i, numblends, blendnum; int numverts = model->surfmesh.num_vertices; numblends = 0; for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) { //VectorMA(translate, model->surfmesh.num_morphmdlframetranslate, frameblend[blendnum].lerp, translate); if (frameblend[blendnum].lerp > 0) numblends = blendnum + 1; } // special case for the first blend because it avoids some adds and the need to memset the arrays first for (blendnum = 0;blendnum < numblends;blendnum++) { const md3vertex_t *verts = model->surfmesh.data_morphmd3vertex + numverts * frameblend[blendnum].subframe; if (vertex3f) { float scale = frameblend[blendnum].lerp * (1.0f / 64.0f); if (blendnum == 0) { for (i = 0;i < numverts;i++) { vertex3f[i * 3 + 0] = verts[i].origin[0] * scale; vertex3f[i * 3 + 1] = verts[i].origin[1] * scale; vertex3f[i * 3 + 2] = verts[i].origin[2] * scale; } } else { for (i = 0;i < numverts;i++) { vertex3f[i * 3 + 0] += verts[i].origin[0] * scale; vertex3f[i * 3 + 1] += verts[i].origin[1] * scale; vertex3f[i * 3 + 2] += verts[i].origin[2] * scale; } } } // the yaw and pitch stored in md3 models are 8bit quantized angles // (0-255), and as such a lookup table is very well suited to // decoding them, and since cosine is equivalent to sine with an // extra 45 degree rotation, this uses one lookup table for both // sine and cosine with a +64 bias to get cosine. if (normal3f) { float lerp = frameblend[blendnum].lerp; if (blendnum == 0) { for (i = 0;i < numverts;i++) { normal3f[i * 3 + 0] = mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; normal3f[i * 3 + 1] = mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; normal3f[i * 3 + 2] = mod_md3_sin[verts[i].pitch + 64] * lerp; } } else { for (i = 0;i < numverts;i++) { normal3f[i * 3 + 0] += mod_md3_sin[verts[i].yaw + 64] * mod_md3_sin[verts[i].pitch ] * lerp; normal3f[i * 3 + 1] += mod_md3_sin[verts[i].yaw ] * mod_md3_sin[verts[i].pitch ] * lerp; normal3f[i * 3 + 2] += mod_md3_sin[verts[i].pitch + 64] * lerp; } } } if (svector3f) { const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; float f = frameblend[blendnum].lerp * (1.0f / 127.0f); if (blendnum == 0) { for (i = 0;i < numverts;i++, texvecvert++) { VectorScale(texvecvert->svec, f, svector3f + i*3); VectorScale(texvecvert->tvec, f, tvector3f + i*3); } } else { for (i = 0;i < numverts;i++, texvecvert++) { VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); } } } } } static void Mod_MDL_AnimateVertices(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) { // vertex morph int i, numblends, blendnum; int numverts = model->surfmesh.num_vertices; float translate[3]; VectorClear(translate); numblends = 0; // blend the frame translates to avoid redundantly doing so on each vertex // (a bit of a brain twister but it works) for (blendnum = 0;blendnum < MAX_FRAMEBLENDS;blendnum++) { if (model->surfmesh.data_morphmd2framesize6f) VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6 + 3, translate); else VectorMA(translate, frameblend[blendnum].lerp, model->surfmesh.num_morphmdlframetranslate, translate); if (frameblend[blendnum].lerp > 0) numblends = blendnum + 1; } // special case for the first blend because it avoids some adds and the need to memset the arrays first for (blendnum = 0;blendnum < numblends;blendnum++) { const trivertx_t *verts = model->surfmesh.data_morphmdlvertex + numverts * frameblend[blendnum].subframe; if (vertex3f) { float scale[3]; if (model->surfmesh.data_morphmd2framesize6f) VectorScale(model->surfmesh.data_morphmd2framesize6f + frameblend[blendnum].subframe * 6, frameblend[blendnum].lerp, scale); else VectorScale(model->surfmesh.num_morphmdlframescale, frameblend[blendnum].lerp, scale); if (blendnum == 0) { for (i = 0;i < numverts;i++) { vertex3f[i * 3 + 0] = translate[0] + verts[i].v[0] * scale[0]; vertex3f[i * 3 + 1] = translate[1] + verts[i].v[1] * scale[1]; vertex3f[i * 3 + 2] = translate[2] + verts[i].v[2] * scale[2]; } } else { for (i = 0;i < numverts;i++) { vertex3f[i * 3 + 0] += verts[i].v[0] * scale[0]; vertex3f[i * 3 + 1] += verts[i].v[1] * scale[1]; vertex3f[i * 3 + 2] += verts[i].v[2] * scale[2]; } } } // the vertex normals in mdl models are an index into a table of // 162 unique values, this very crude quantization reduces the // vertex normal to only one byte, which saves a lot of space but // also makes lighting pretty coarse if (normal3f) { float lerp = frameblend[blendnum].lerp; if (blendnum == 0) { for (i = 0;i < numverts;i++) { const float *vn = m_bytenormals[verts[i].lightnormalindex]; VectorScale(vn, lerp, normal3f + i*3); } } else { for (i = 0;i < numverts;i++) { const float *vn = m_bytenormals[verts[i].lightnormalindex]; VectorMA(normal3f + i*3, lerp, vn, normal3f + i*3); } } } if (svector3f) { const texvecvertex_t *texvecvert = model->surfmesh.data_morphtexvecvertex + numverts * frameblend[blendnum].subframe; float f = frameblend[blendnum].lerp * (1.0f / 127.0f); if (blendnum == 0) { for (i = 0;i < numverts;i++, texvecvert++) { VectorScale(texvecvert->svec, f, svector3f + i*3); VectorScale(texvecvert->tvec, f, tvector3f + i*3); } } else { for (i = 0;i < numverts;i++, texvecvert++) { VectorMA(svector3f + i*3, f, texvecvert->svec, svector3f + i*3); VectorMA(tvector3f + i*3, f, texvecvert->tvec, tvector3f + i*3); } } } } } int Mod_Alias_GetTagMatrix(const dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, matrix4x4_t *outmatrix) { matrix4x4_t temp; matrix4x4_t parentbonematrix; matrix4x4_t tempbonematrix; matrix4x4_t bonematrix; matrix4x4_t blendmatrix; int blendindex; int parenttagindex; int k; float lerp; const float *input; float blendtag[12]; *outmatrix = identitymatrix; if (skeleton && skeleton->relativetransforms) { if (tagindex < 0 || tagindex >= skeleton->model->num_bones) return 4; *outmatrix = skeleton->relativetransforms[tagindex]; while ((tagindex = model->data_bones[tagindex].parent) >= 0) { temp = *outmatrix; Matrix4x4_Concat(outmatrix, &skeleton->relativetransforms[tagindex], &temp); } } else if (model->num_bones) { if (tagindex < 0 || tagindex >= model->num_bones) return 4; Matrix4x4_Clear(&blendmatrix); for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) { lerp = frameblend[blendindex].lerp; Matrix4x4_FromBonePose7s(&bonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); parenttagindex = tagindex; while ((parenttagindex = model->data_bones[parenttagindex].parent) >= 0) { Matrix4x4_FromBonePose7s(&parentbonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + parenttagindex)); tempbonematrix = bonematrix; Matrix4x4_Concat(&bonematrix, &parentbonematrix, &tempbonematrix); } Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); } *outmatrix = blendmatrix; } else if (model->num_tags) { if (tagindex < 0 || tagindex >= model->num_tags) return 4; for (k = 0;k < 12;k++) blendtag[k] = 0; for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) { lerp = frameblend[blendindex].lerp; input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; for (k = 0;k < 12;k++) blendtag[k] += input[k] * lerp; } Matrix4x4_FromArray12FloatGL(outmatrix, blendtag); } if(!mod_alias_supporttagscale.integer) Matrix4x4_Normalize3(outmatrix, outmatrix); return 0; } int Mod_Alias_GetExtendedTagInfoForIndex(const dp_model_t *model, unsigned int skin, const frameblend_t *frameblend, const skeleton_t *skeleton, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix) { int blendindex; int k; float lerp; matrix4x4_t bonematrix; matrix4x4_t blendmatrix; const float *input; float blendtag[12]; if (skeleton && skeleton->relativetransforms) { if (tagindex < 0 || tagindex >= skeleton->model->num_bones) return 1; *parentindex = skeleton->model->data_bones[tagindex].parent; *tagname = skeleton->model->data_bones[tagindex].name; *tag_localmatrix = skeleton->relativetransforms[tagindex]; return 0; } else if (model->num_bones) { if (tagindex < 0 || tagindex >= model->num_bones) return 1; *parentindex = model->data_bones[tagindex].parent; *tagname = model->data_bones[tagindex].name; Matrix4x4_Clear(&blendmatrix); for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) { lerp = frameblend[blendindex].lerp; Matrix4x4_FromBonePose7s(&bonematrix, model->num_posescale, model->data_poses7s + 7 * (frameblend[blendindex].subframe * model->num_bones + tagindex)); Matrix4x4_Accumulate(&blendmatrix, &bonematrix, lerp); } *tag_localmatrix = blendmatrix; return 0; } else if (model->num_tags) { if (tagindex < 0 || tagindex >= model->num_tags) return 1; *parentindex = -1; *tagname = model->data_tags[tagindex].name; for (k = 0;k < 12;k++) blendtag[k] = 0; for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++) { lerp = frameblend[blendindex].lerp; input = model->data_tags[frameblend[blendindex].subframe * model->num_tags + tagindex].matrixgl; for (k = 0;k < 12;k++) blendtag[k] += input[k] * lerp; } Matrix4x4_FromArray12FloatGL(tag_localmatrix, blendtag); return 0; } return 2; } int Mod_Alias_GetTagIndexForName(const dp_model_t *model, unsigned int skin, const char *tagname) { int i; if(skin >= (unsigned int)model->numskins) skin = 0; if (model->num_bones) for (i = 0;i < model->num_bones;i++) if (!strcasecmp(tagname, model->data_bones[i].name)) return i + 1; if (model->num_tags) for (i = 0;i < model->num_tags;i++) if (!strcasecmp(tagname, model->data_tags[i].name)) return i + 1; return 0; } static void Mod_BuildBaseBonePoses(void) { int boneindex; matrix4x4_t *basebonepose; float *outinvmatrix = loadmodel->data_baseboneposeinverse; matrix4x4_t bonematrix; matrix4x4_t tempbonematrix; if (!loadmodel->num_bones) return; basebonepose = (matrix4x4_t *)Mem_Alloc(tempmempool, loadmodel->num_bones * sizeof(matrix4x4_t)); for (boneindex = 0;boneindex < loadmodel->num_bones;boneindex++) { Matrix4x4_FromBonePose7s(&bonematrix, loadmodel->num_posescale, loadmodel->data_poses7s + 7 * boneindex); if (loadmodel->data_bones[boneindex].parent >= 0) { tempbonematrix = bonematrix; Matrix4x4_Concat(&bonematrix, basebonepose + loadmodel->data_bones[boneindex].parent, &tempbonematrix); } basebonepose[boneindex] = bonematrix; Matrix4x4_Invert_Simple(&tempbonematrix, basebonepose + boneindex); Matrix4x4_ToArray12FloatD3D(&tempbonematrix, outinvmatrix + 12*boneindex); } Mem_Free(basebonepose); } static qboolean Mod_Alias_CalculateBoundingBox(void) { int vnum; qboolean firstvertex = true; float dist, yawradius, radius; float *v; qboolean isanimated = false; VectorClear(loadmodel->normalmins); VectorClear(loadmodel->normalmaxs); yawradius = 0; radius = 0; if (loadmodel->AnimateVertices) { float *vertex3f, *refvertex3f; frameblend_t frameblend[MAX_FRAMEBLENDS]; memset(frameblend, 0, sizeof(frameblend)); frameblend[0].lerp = 1; vertex3f = (float *) Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[3]) * 2); refvertex3f = NULL; for (frameblend[0].subframe = 0;frameblend[0].subframe < loadmodel->num_poses;frameblend[0].subframe++) { loadmodel->AnimateVertices(loadmodel, frameblend, NULL, vertex3f, NULL, NULL, NULL); if (!refvertex3f) { // make a copy of the first frame for comparing all others refvertex3f = vertex3f + loadmodel->surfmesh.num_vertices * 3; memcpy(refvertex3f, vertex3f, loadmodel->surfmesh.num_vertices * sizeof(float[3])); } else { if (!isanimated && memcmp(refvertex3f, vertex3f, loadmodel->surfmesh.num_vertices * sizeof(float[3]))) isanimated = true; } for (vnum = 0, v = vertex3f;vnum < loadmodel->surfmesh.num_vertices;vnum++, v += 3) { if (firstvertex) { firstvertex = false; VectorCopy(v, loadmodel->normalmins); VectorCopy(v, loadmodel->normalmaxs); } else { if (loadmodel->normalmins[0] > v[0]) loadmodel->normalmins[0] = v[0]; if (loadmodel->normalmins[1] > v[1]) loadmodel->normalmins[1] = v[1]; if (loadmodel->normalmins[2] > v[2]) loadmodel->normalmins[2] = v[2]; if (loadmodel->normalmaxs[0] < v[0]) loadmodel->normalmaxs[0] = v[0]; if (loadmodel->normalmaxs[1] < v[1]) loadmodel->normalmaxs[1] = v[1]; if (loadmodel->normalmaxs[2] < v[2]) loadmodel->normalmaxs[2] = v[2]; } dist = v[0] * v[0] + v[1] * v[1]; if (yawradius < dist) yawradius = dist; dist += v[2] * v[2]; if (radius < dist) radius = dist; } } if (vertex3f) Mem_Free(vertex3f); } else { for (vnum = 0, v = loadmodel->surfmesh.data_vertex3f;vnum < loadmodel->surfmesh.num_vertices;vnum++, v += 3) { if (firstvertex) { firstvertex = false; VectorCopy(v, loadmodel->normalmins); VectorCopy(v, loadmodel->normalmaxs); } else { if (loadmodel->normalmins[0] > v[0]) loadmodel->normalmins[0] = v[0]; if (loadmodel->normalmins[1] > v[1]) loadmodel->normalmins[1] = v[1]; if (loadmodel->normalmins[2] > v[2]) loadmodel->normalmins[2] = v[2]; if (loadmodel->normalmaxs[0] < v[0]) loadmodel->normalmaxs[0] = v[0]; if (loadmodel->normalmaxs[1] < v[1]) loadmodel->normalmaxs[1] = v[1]; if (loadmodel->normalmaxs[2] < v[2]) loadmodel->normalmaxs[2] = v[2]; } dist = v[0] * v[0] + v[1] * v[1]; if (yawradius < dist) yawradius = dist; dist += v[2] * v[2]; if (radius < dist) radius = dist; } } radius = sqrt(radius); yawradius = sqrt(yawradius); loadmodel->yawmins[0] = loadmodel->yawmins[1] = -yawradius; loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = yawradius; loadmodel->yawmins[2] = loadmodel->normalmins[2]; loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; loadmodel->radius = radius; loadmodel->radius2 = radius * radius; return isanimated; } static void Mod_Alias_MorphMesh_CompileFrames(void) { int i, j; frameblend_t frameblend[MAX_FRAMEBLENDS]; unsigned char *datapointer; memset(frameblend, 0, sizeof(frameblend)); frameblend[0].lerp = 1; datapointer = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * (sizeof(float[3]) * 4 + loadmodel->surfmesh.num_morphframes * sizeof(texvecvertex_t))); loadmodel->surfmesh.data_vertex3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)datapointer;datapointer += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_morphtexvecvertex = (texvecvertex_t *)datapointer;datapointer += loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices * sizeof(texvecvertex_t); // this counts down from the last frame to the first so that the final data in surfmesh is for frame zero (which is what the renderer expects to be there) for (i = loadmodel->surfmesh.num_morphframes-1;i >= 0;i--) { frameblend[0].subframe = i; loadmodel->AnimateVertices(loadmodel, frameblend, NULL, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_normal3f, NULL, NULL); Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); // encode the svector and tvector in 3 byte format for permanent storage for (j = 0;j < loadmodel->surfmesh.num_vertices;j++) { VectorScaleCast(loadmodel->surfmesh.data_svector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].svec); VectorScaleCast(loadmodel->surfmesh.data_tvector3f + j * 3, 127.0f, signed char, loadmodel->surfmesh.data_morphtexvecvertex[i*loadmodel->surfmesh.num_vertices+j].tvec); } } } static void Mod_MDLMD2MD3_TraceLine(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { int i; float segmentmins[3], segmentmaxs[3]; msurface_t *surface; float vertex3fbuf[1024*3]; float *vertex3f = vertex3fbuf; memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; if (model->surfmesh.num_vertices > 1024) vertex3f = (float *)Mem_Alloc(tempmempool, model->surfmesh.num_vertices * sizeof(float[3])); segmentmins[0] = min(start[0], end[0]) - 1; segmentmins[1] = min(start[1], end[1]) - 1; segmentmins[2] = min(start[2], end[2]) - 1; segmentmaxs[0] = max(start[0], end[0]) + 1; segmentmaxs[1] = max(start[1], end[1]) + 1; segmentmaxs[2] = max(start[2], end[2]) + 1; model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) Collision_TraceLineTriangleMeshFloat(trace, start, end, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); if (vertex3f != vertex3fbuf) Mem_Free(vertex3f); } static void Mod_MDLMD2MD3_TraceBox(dp_model_t *model, const frameblend_t *frameblend, const skeleton_t *skeleton, trace_t *trace, const vec3_t start, const vec3_t boxmins, const vec3_t boxmaxs, const vec3_t end, int hitsupercontentsmask, int skipsupercontentsmask) { int i; vec3_t shiftstart, shiftend; float segmentmins[3], segmentmaxs[3]; msurface_t *surface; float vertex3fbuf[1024*3]; float *vertex3f = vertex3fbuf; colboxbrushf_t thisbrush_start, thisbrush_end; vec3_t boxstartmins, boxstartmaxs, boxendmins, boxendmaxs; if (VectorCompare(boxmins, boxmaxs)) { VectorAdd(start, boxmins, shiftstart); VectorAdd(end, boxmins, shiftend); Mod_MDLMD2MD3_TraceLine(model, frameblend, skeleton, trace, shiftstart, shiftend, hitsupercontentsmask, skipsupercontentsmask); VectorSubtract(trace->endpos, boxmins, trace->endpos); return; } // box trace, performed as brush trace memset(trace, 0, sizeof(*trace)); trace->fraction = 1; trace->hitsupercontentsmask = hitsupercontentsmask; trace->skipsupercontentsmask = skipsupercontentsmask; if (model->surfmesh.num_vertices > 1024) vertex3f = (float *)Mem_Alloc(tempmempool, model->surfmesh.num_vertices * sizeof(float[3])); segmentmins[0] = min(start[0], end[0]) + boxmins[0] - 1; segmentmins[1] = min(start[1], end[1]) + boxmins[1] - 1; segmentmins[2] = min(start[2], end[2]) + boxmins[2] - 1; segmentmaxs[0] = max(start[0], end[0]) + boxmaxs[0] + 1; segmentmaxs[1] = max(start[1], end[1]) + boxmaxs[1] + 1; segmentmaxs[2] = max(start[2], end[2]) + boxmaxs[2] + 1; VectorAdd(start, boxmins, boxstartmins); VectorAdd(start, boxmaxs, boxstartmaxs); VectorAdd(end, boxmins, boxendmins); VectorAdd(end, boxmaxs, boxendmaxs); Collision_BrushForBox(&thisbrush_start, boxstartmins, boxstartmaxs, 0, 0, NULL); Collision_BrushForBox(&thisbrush_end, boxendmins, boxendmaxs, 0, 0, NULL); model->AnimateVertices(model, frameblend, skeleton, vertex3f, NULL, NULL, NULL); for (i = 0, surface = model->data_surfaces;i < model->num_surfaces;i++, surface++) Collision_TraceBrushTriangleMeshFloat(trace, &thisbrush_start.brush, &thisbrush_end.brush, model->surfmesh.num_triangles, model->surfmesh.data_element3i, vertex3f, 0, NULL, SUPERCONTENTS_SOLID | (surface->texture->basematerialflags & MATERIALFLAGMASK_TRANSLUCENT ? 0 : SUPERCONTENTS_OPAQUE), 0, surface->texture, segmentmins, segmentmaxs); if (vertex3f != vertex3fbuf) Mem_Free(vertex3f); } static void Mod_ConvertAliasVerts (int inverts, trivertx_t *v, trivertx_t *out, int *vertremap) { int i, j; for (i = 0;i < inverts;i++) { if (vertremap[i] < 0 && vertremap[i+inverts] < 0) // only used vertices need apply... continue; j = vertremap[i]; // not onseam if (j >= 0) out[j] = v[i]; j = vertremap[i+inverts]; // onseam if (j >= 0) out[j] = v[i]; } } static void Mod_MDL_LoadFrames (unsigned char* datapointer, int inverts, int *vertremap) { int i, f, pose, groupframes; float interval; daliasframetype_t *pframetype; daliasframe_t *pinframe; daliasgroup_t *group; daliasinterval_t *intervals; animscene_t *scene; pose = 0; scene = loadmodel->animscenes; for (f = 0;f < loadmodel->numframes;f++) { pframetype = (daliasframetype_t *)datapointer; datapointer += sizeof(daliasframetype_t); if (LittleLong (pframetype->type) == ALIAS_SINGLE) { // a single frame is still treated as a group interval = 0.1f; groupframes = 1; } else { // read group header group = (daliasgroup_t *)datapointer; datapointer += sizeof(daliasgroup_t); groupframes = LittleLong (group->numframes); // intervals (time per frame) intervals = (daliasinterval_t *)datapointer; datapointer += sizeof(daliasinterval_t) * groupframes; interval = LittleFloat (intervals->interval); // FIXME: support variable framerate groups if (interval < 0.01f) { Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); interval = 0.1f; } } // get scene name from first frame pinframe = (daliasframe_t *)datapointer; strlcpy(scene->name, pinframe->name, sizeof(scene->name)); scene->firstframe = pose; scene->framecount = groupframes; scene->framerate = 1.0f / interval; scene->loop = true; scene++; // read frames for (i = 0;i < groupframes;i++) { datapointer += sizeof(daliasframe_t); Mod_ConvertAliasVerts(inverts, (trivertx_t *)datapointer, loadmodel->surfmesh.data_morphmdlvertex + pose * loadmodel->surfmesh.num_vertices, vertremap); datapointer += sizeof(trivertx_t) * inverts; pose++; } } } static void Mod_BuildAliasSkinFromSkinFrame(texture_t *texture, skinframe_t *skinframe) { if (cls.state == ca_dedicated) return; // hack if (!skinframe) skinframe = R_SkinFrame_LoadMissing(); memset(texture, 0, sizeof(*texture)); texture->currentframe = texture; //texture->animated = false; texture->numskinframes = 1; texture->skinframerate = 1; texture->skinframes[0] = skinframe; texture->currentskinframe = skinframe; //texture->backgroundnumskinframes = 0; //texture->customblendfunc[0] = 0; //texture->customblendfunc[1] = 0; //texture->surfaceflags = 0; //texture->supercontents = 0; //texture->surfaceparms = 0; //texture->textureflags = 0; texture->basematerialflags = MATERIALFLAG_WALL; texture->basealpha = 1.0f; if (texture->currentskinframe->hasalpha) texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; texture->currentmaterialflags = texture->basematerialflags; texture->offsetmapping = OFFSETMAPPING_DEFAULT; texture->offsetscale = 1; texture->offsetbias = 0; texture->specularscalemod = 1; texture->specularpowermod = 1; texture->surfaceflags = 0; texture->supercontents = SUPERCONTENTS_SOLID; if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) texture->supercontents |= SUPERCONTENTS_OPAQUE; texture->transparentsort = TRANSPARENTSORT_DISTANCE; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". } void Mod_BuildAliasSkinsFromSkinFiles(texture_t *skin, skinfile_t *skinfile, const char *meshname, const char *shadername) { int i; char stripbuf[MAX_QPATH]; skinfileitem_t *skinfileitem; if(developer_extra.integer) Con_DPrintf("Looking up texture for %s (default: %s)\n", meshname, shadername); if (skinfile) { // the skin += loadmodel->num_surfaces part of this is because data_textures on alias models is arranged as [numskins][numsurfaces] for (i = 0;skinfile;skinfile = skinfile->next, i++, skin += loadmodel->num_surfaces) { memset(skin, 0, sizeof(*skin)); // see if a mesh for (skinfileitem = skinfile->items;skinfileitem;skinfileitem = skinfileitem->next) { // leave the skin unitialized (nodraw) if the replacement is "common/nodraw" or "textures/common/nodraw" if (!strcmp(skinfileitem->name, meshname)) { Image_StripImageExtension(skinfileitem->replacement, stripbuf, sizeof(stripbuf)); if(developer_extra.integer) Con_DPrintf("--> got %s from skin file\n", stripbuf); Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); break; } } if (!skinfileitem) { // don't render unmentioned meshes Mod_BuildAliasSkinFromSkinFrame(skin, NULL); if(developer_extra.integer) Con_DPrintf("--> skipping\n"); skin->basematerialflags = skin->currentmaterialflags = MATERIALFLAG_NOSHADOW | MATERIALFLAG_NODRAW; } } } else { if(developer_extra.integer) Con_DPrintf("--> using default\n"); Image_StripImageExtension(shadername, stripbuf, sizeof(stripbuf)); Mod_LoadTextureFromQ3Shader(skin, stripbuf, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); } } #define BOUNDI(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%d exceeds %d - %d)", loadmodel->name, VALUE, MIN, MAX); #define BOUNDF(VALUE,MIN,MAX) if (VALUE < MIN || VALUE >= MAX) Host_Error("model %s has an invalid ##VALUE (%f exceeds %f - %f)", loadmodel->name, VALUE, MIN, MAX); void Mod_IDP0_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, version, totalskins, skinwidth, skinheight, groupframes, groupskins, numverts; float scales, scalet, interval; msurface_t *surface; unsigned char *data; mdl_t *pinmodel; stvert_t *pinstverts; dtriangle_t *pintriangles; daliasskintype_t *pinskintype; daliasskingroup_t *pinskingroup; daliasskininterval_t *pinskinintervals; daliasframetype_t *pinframetype; daliasgroup_t *pinframegroup; unsigned char *datapointer, *startframes, *startskins; char name[MAX_QPATH]; skinframe_t *tempskinframe; animscene_t *tempskinscenes; texture_t *tempaliasskins; float *vertst; int *vertonseam, *vertremap; skinfile_t *skinfiles; char vabuf[1024]; datapointer = (unsigned char *)buffer; pinmodel = (mdl_t *)datapointer; datapointer += sizeof(mdl_t); version = LittleLong (pinmodel->version); if (version != ALIAS_VERSION) Host_Error ("%s has wrong version number (%i should be %i)", loadmodel->name, version, ALIAS_VERSION); loadmodel->modeldatatypestring = "MDL"; loadmodel->type = mod_alias; loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; // FIXME add TraceBrush! loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; loadmodel->num_surfaces = 1; loadmodel->nummodelsurfaces = loadmodel->num_surfaces; data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int)); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->sortedmodelsurfaces[0] = 0; loadmodel->numskins = LittleLong(pinmodel->numskins); BOUNDI(loadmodel->numskins,0,65536); skinwidth = LittleLong (pinmodel->skinwidth); BOUNDI(skinwidth,0,65536); skinheight = LittleLong (pinmodel->skinheight); BOUNDI(skinheight,0,65536); numverts = LittleLong(pinmodel->numverts); BOUNDI(numverts,0,65536); loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->numtris); BOUNDI(loadmodel->surfmesh.num_triangles,0,65536); loadmodel->numframes = LittleLong(pinmodel->numframes); BOUNDI(loadmodel->numframes,0,65536); loadmodel->synctype = (synctype_t)LittleLong (pinmodel->synctype); BOUNDI((int)loadmodel->synctype,0,2); // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) i = LittleLong (pinmodel->flags); loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); for (i = 0;i < 3;i++) { loadmodel->surfmesh.num_morphmdlframescale[i] = LittleFloat (pinmodel->scale[i]); loadmodel->surfmesh.num_morphmdlframetranslate[i] = LittleFloat (pinmodel->scale_origin[i]); } startskins = datapointer; totalskins = 0; for (i = 0;i < loadmodel->numskins;i++) { pinskintype = (daliasskintype_t *)datapointer; datapointer += sizeof(daliasskintype_t); if (LittleLong(pinskintype->type) == ALIAS_SKIN_SINGLE) groupskins = 1; else { pinskingroup = (daliasskingroup_t *)datapointer; datapointer += sizeof(daliasskingroup_t); groupskins = LittleLong(pinskingroup->numskins); datapointer += sizeof(daliasskininterval_t) * groupskins; } for (j = 0;j < groupskins;j++) { datapointer += skinwidth * skinheight; totalskins++; } } pinstverts = (stvert_t *)datapointer; datapointer += sizeof(stvert_t) * numverts; pintriangles = (dtriangle_t *)datapointer; datapointer += sizeof(dtriangle_t) * loadmodel->surfmesh.num_triangles; startframes = datapointer; loadmodel->surfmesh.num_morphframes = 0; for (i = 0;i < loadmodel->numframes;i++) { pinframetype = (daliasframetype_t *)datapointer; datapointer += sizeof(daliasframetype_t); if (LittleLong (pinframetype->type) == ALIAS_SINGLE) groupframes = 1; else { pinframegroup = (daliasgroup_t *)datapointer; datapointer += sizeof(daliasgroup_t); groupframes = LittleLong(pinframegroup->numframes); datapointer += sizeof(daliasinterval_t) * groupframes; } for (j = 0;j < groupframes;j++) { datapointer += sizeof(daliasframe_t); datapointer += sizeof(trivertx_t) * numverts; loadmodel->surfmesh.num_morphframes++; } } loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; // store texture coordinates into temporary array, they will be stored // after usage is determined (triangle data) vertst = (float *)Mem_Alloc(tempmempool, numverts * 2 * sizeof(float[2])); vertremap = (int *)Mem_Alloc(tempmempool, numverts * 3 * sizeof(int)); vertonseam = vertremap + numverts * 2; scales = 1.0 / skinwidth; scalet = 1.0 / skinheight; for (i = 0;i < numverts;i++) { vertonseam[i] = LittleLong(pinstverts[i].onseam); vertst[i*2+0] = LittleLong(pinstverts[i].s) * scales; vertst[i*2+1] = LittleLong(pinstverts[i].t) * scalet; vertst[(i+numverts)*2+0] = vertst[i*2+0] + 0.5; vertst[(i+numverts)*2+1] = vertst[i*2+1]; } // load triangle data loadmodel->surfmesh.data_element3i = (int *)Mem_Alloc(loadmodel->mempool, sizeof(int[3]) * loadmodel->surfmesh.num_triangles); // read the triangle elements for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) for (j = 0;j < 3;j++) loadmodel->surfmesh.data_element3i[i*3+j] = LittleLong(pintriangles[i].vertindex[j]); // validate (note numverts is used because this is the original data) Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, numverts, __FILE__, __LINE__); // now butcher the elements according to vertonseam and tri->facesfront // and then compact the vertex set to remove duplicates for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) if (!LittleLong(pintriangles[i].facesfront)) // backface for (j = 0;j < 3;j++) if (vertonseam[loadmodel->surfmesh.data_element3i[i*3+j]]) loadmodel->surfmesh.data_element3i[i*3+j] += numverts; // count the usage // (this uses vertremap to count usage to save some memory) for (i = 0;i < numverts*2;i++) vertremap[i] = 0; for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) vertremap[loadmodel->surfmesh.data_element3i[i]]++; // build remapping table and compact array loadmodel->surfmesh.num_vertices = 0; for (i = 0;i < numverts*2;i++) { if (vertremap[i]) { vertremap[i] = loadmodel->surfmesh.num_vertices; vertst[loadmodel->surfmesh.num_vertices*2+0] = vertst[i*2+0]; vertst[loadmodel->surfmesh.num_vertices*2+1] = vertst[i*2+1]; loadmodel->surfmesh.num_vertices++; } else vertremap[i] = -1; // not used at all } // remap the elements to the new vertex set for (i = 0;i < loadmodel->surfmesh.num_triangles * 3;i++) loadmodel->surfmesh.data_element3i[i] = vertremap[loadmodel->surfmesh.data_element3i[i]]; // store the texture coordinates loadmodel->surfmesh.data_texcoordtexture2f = (float *)Mem_Alloc(loadmodel->mempool, sizeof(float[2]) * loadmodel->surfmesh.num_vertices); for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) { loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = vertst[i*2+0]; loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = vertst[i*2+1]; } // generate ushort elements array if possible if (loadmodel->surfmesh.num_vertices <= 65536) loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; // load the frames loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)Mem_Alloc(loadmodel->mempool, sizeof(trivertx_t) * loadmodel->surfmesh.num_morphframes * loadmodel->surfmesh.num_vertices); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_triangles * sizeof(int[3])); } Mod_MDL_LoadFrames (startframes, numverts, vertremap); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); Mod_Alias_MorphMesh_CompileFrames(); Mem_Free(vertst); Mem_Free(vertremap); // load the skins skinfiles = Mod_LoadSkinFiles(); if (skinfiles) { loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); Mod_FreeSkinFiles(skinfiles); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } } else { loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numskins * sizeof(animscene_t)); loadmodel->num_textures = loadmodel->num_surfaces * totalskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); totalskins = 0; datapointer = startskins; for (i = 0;i < loadmodel->numskins;i++) { pinskintype = (daliasskintype_t *)datapointer; datapointer += sizeof(daliasskintype_t); if (pinskintype->type == ALIAS_SKIN_SINGLE) { groupskins = 1; interval = 0.1f; } else { pinskingroup = (daliasskingroup_t *)datapointer; datapointer += sizeof(daliasskingroup_t); groupskins = LittleLong (pinskingroup->numskins); pinskinintervals = (daliasskininterval_t *)datapointer; datapointer += sizeof(daliasskininterval_t) * groupskins; interval = LittleFloat(pinskinintervals[0].interval); if (interval < 0.01f) { Con_Printf("%s has an invalid interval %f, changing to 0.1\n", loadmodel->name, interval); interval = 0.1f; } } dpsnprintf(loadmodel->skinscenes[i].name, sizeof(loadmodel->skinscenes[i].name), "skin %i", i); loadmodel->skinscenes[i].firstframe = totalskins; loadmodel->skinscenes[i].framecount = groupskins; loadmodel->skinscenes[i].framerate = 1.0f / interval; loadmodel->skinscenes[i].loop = true; for (j = 0;j < groupskins;j++) { if (groupskins > 1) dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); else dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); if (!Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, name, false, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS)) Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, R_SkinFrame_LoadInternalQuake(name, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_PICMIP, true, r_fullbrights.integer, (unsigned char *)datapointer, skinwidth, skinheight)); datapointer += skinwidth * skinheight; totalskins++; } } // check for skins that don't exist in the model, but do exist as external images // (this was added because yummyluv kept pestering me about support for it) // TODO: support shaders here? while ((tempskinframe = R_SkinFrame_LoadExternal(va(vabuf, sizeof(vabuf), "%s_%i", loadmodel->name, loadmodel->numskins), (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS, false))) { // expand the arrays to make room tempskinscenes = loadmodel->skinscenes; loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, (loadmodel->numskins + 1) * sizeof(animscene_t)); memcpy(loadmodel->skinscenes, tempskinscenes, loadmodel->numskins * sizeof(animscene_t)); Mem_Free(tempskinscenes); tempaliasskins = loadmodel->data_textures; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * (totalskins + 1) * sizeof(texture_t)); memcpy(loadmodel->data_textures, tempaliasskins, loadmodel->num_surfaces * totalskins * sizeof(texture_t)); Mem_Free(tempaliasskins); // store the info about the new skin Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + totalskins * loadmodel->num_surfaces, tempskinframe); strlcpy(loadmodel->skinscenes[loadmodel->numskins].name, name, sizeof(loadmodel->skinscenes[loadmodel->numskins].name)); loadmodel->skinscenes[loadmodel->numskins].firstframe = totalskins; loadmodel->skinscenes[loadmodel->numskins].framecount = 1; loadmodel->skinscenes[loadmodel->numskins].framerate = 10.0f; loadmodel->skinscenes[loadmodel->numskins].loop = true; //increase skin counts loadmodel->numskins++; totalskins++; // fix up the pointers since they are pointing at the old textures array // FIXME: this is a hack! for (j = 0;j < loadmodel->numskins * loadmodel->num_surfaces;j++) loadmodel->data_textures[j].currentframe = &loadmodel->data_textures[j]; } } surface = loadmodel->data_surfaces; surface->texture = loadmodel->data_textures; surface->num_firsttriangle = 0; surface->num_triangles = loadmodel->surfmesh.num_triangles; surface->num_firstvertex = 0; surface->num_vertices = loadmodel->surfmesh.num_vertices; if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } void Mod_IDP2_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, hashindex, numxyz, numst, xyz, st, skinwidth, skinheight, *vertremap, version, end; float iskinwidth, iskinheight; unsigned char *data; msurface_t *surface; md2_t *pinmodel; unsigned char *base, *datapointer; md2frame_t *pinframe; char *inskin; md2triangle_t *intri; unsigned short *inst; struct md2verthash_s { struct md2verthash_s *next; unsigned short xyz; unsigned short st; } *hash, **md2verthash, *md2verthashdata; skinfile_t *skinfiles; pinmodel = (md2_t *)buffer; base = (unsigned char *)buffer; version = LittleLong (pinmodel->version); if (version != MD2ALIAS_VERSION) Host_Error ("%s has wrong version number (%i should be %i)", loadmodel->name, version, MD2ALIAS_VERSION); loadmodel->modeldatatypestring = "MD2"; loadmodel->type = mod_alias; loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_MDL_AnimateVertices; if (LittleLong(pinmodel->num_tris) < 1 || LittleLong(pinmodel->num_tris) > 65536) Host_Error ("%s has invalid number of triangles: %i", loadmodel->name, LittleLong(pinmodel->num_tris)); if (LittleLong(pinmodel->num_xyz) < 1 || LittleLong(pinmodel->num_xyz) > 65536) Host_Error ("%s has invalid number of vertices: %i", loadmodel->name, LittleLong(pinmodel->num_xyz)); if (LittleLong(pinmodel->num_frames) < 1 || LittleLong(pinmodel->num_frames) > 65536) Host_Error ("%s has invalid number of frames: %i", loadmodel->name, LittleLong(pinmodel->num_frames)); if (LittleLong(pinmodel->num_skins) < 0 || LittleLong(pinmodel->num_skins) > 256) Host_Error ("%s has invalid number of skins: %i", loadmodel->name, LittleLong(pinmodel->num_skins)); end = LittleLong(pinmodel->ofs_end); if (LittleLong(pinmodel->num_skins) >= 1 && (LittleLong(pinmodel->ofs_skins) <= 0 || LittleLong(pinmodel->ofs_skins) >= end)) Host_Error ("%s is not a valid model", loadmodel->name); if (LittleLong(pinmodel->ofs_st) <= 0 || LittleLong(pinmodel->ofs_st) >= end) Host_Error ("%s is not a valid model", loadmodel->name); if (LittleLong(pinmodel->ofs_tris) <= 0 || LittleLong(pinmodel->ofs_tris) >= end) Host_Error ("%s is not a valid model", loadmodel->name); if (LittleLong(pinmodel->ofs_frames) <= 0 || LittleLong(pinmodel->ofs_frames) >= end) Host_Error ("%s is not a valid model", loadmodel->name); if (LittleLong(pinmodel->ofs_glcmds) <= 0 || LittleLong(pinmodel->ofs_glcmds) >= end) Host_Error ("%s is not a valid model", loadmodel->name); loadmodel->numskins = LittleLong(pinmodel->num_skins); numxyz = LittleLong(pinmodel->num_xyz); numst = LittleLong(pinmodel->num_st); loadmodel->surfmesh.num_triangles = LittleLong(pinmodel->num_tris); loadmodel->numframes = LittleLong(pinmodel->num_frames); loadmodel->surfmesh.num_morphframes = loadmodel->numframes; loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; skinwidth = LittleLong(pinmodel->skinwidth); skinheight = LittleLong(pinmodel->skinheight); iskinwidth = 1.0f / skinwidth; iskinheight = 1.0f / skinheight; loadmodel->num_surfaces = 1; loadmodel->nummodelsurfaces = loadmodel->num_surfaces; data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->numframes * sizeof(animscene_t) + loadmodel->numframes * sizeof(float[6]) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0)); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->sortedmodelsurfaces[0] = 0; loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); loadmodel->surfmesh.data_morphmd2framesize6f = (float *)data;data += loadmodel->numframes * sizeof(float[6]); loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); } loadmodel->synctype = ST_RAND; // load the skins inskin = (char *)(base + LittleLong(pinmodel->ofs_skins)); skinfiles = Mod_LoadSkinFiles(); if (skinfiles) { loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures, skinfiles, "default", ""); Mod_FreeSkinFiles(skinfiles); } else if (loadmodel->numskins) { // skins found (most likely not a player model) loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); for (i = 0;i < loadmodel->numskins;i++, inskin += MD2_SKINNAME) Mod_LoadTextureFromQ3Shader(loadmodel->data_textures + i * loadmodel->num_surfaces, inskin, true, true, (r_mipskins.integer ? TEXF_MIPMAP : 0) | TEXF_ALPHA | TEXF_PICMIP | TEXF_COMPRESS); } else { // no skins (most likely a player model) loadmodel->numskins = 1; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t)); Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures, NULL); } loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // load the triangles and stvert data inst = (unsigned short *)(base + LittleLong(pinmodel->ofs_st)); intri = (md2triangle_t *)(base + LittleLong(pinmodel->ofs_tris)); md2verthash = (struct md2verthash_s **)Mem_Alloc(tempmempool, 65536 * sizeof(hash)); md2verthashdata = (struct md2verthash_s *)Mem_Alloc(tempmempool, loadmodel->surfmesh.num_triangles * 3 * sizeof(*hash)); // swap the triangle list loadmodel->surfmesh.num_vertices = 0; for (i = 0;i < loadmodel->surfmesh.num_triangles;i++) { for (j = 0;j < 3;j++) { xyz = (unsigned short) LittleShort (intri[i].index_xyz[j]); st = (unsigned short) LittleShort (intri[i].index_st[j]); if (xyz >= numxyz) { Con_Printf("%s has an invalid xyz index (%i) on triangle %i, resetting to 0\n", loadmodel->name, xyz, i); xyz = 0; } if (st >= numst) { Con_Printf("%s has an invalid st index (%i) on triangle %i, resetting to 0\n", loadmodel->name, st, i); st = 0; } hashindex = (xyz * 256 + st) & 65535; for (hash = md2verthash[hashindex];hash;hash = hash->next) if (hash->xyz == xyz && hash->st == st) break; if (hash == NULL) { hash = md2verthashdata + loadmodel->surfmesh.num_vertices++; hash->xyz = xyz; hash->st = st; hash->next = md2verthash[hashindex]; md2verthash[hashindex] = hash; } loadmodel->surfmesh.data_element3i[i*3+j] = (hash - md2verthashdata); } } vertremap = (int *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(int)); data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t)); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); loadmodel->surfmesh.data_morphmdlvertex = (trivertx_t *)data;data += loadmodel->surfmesh.num_vertices * loadmodel->surfmesh.num_morphframes * sizeof(trivertx_t); for (i = 0;i < loadmodel->surfmesh.num_vertices;i++) { int sts, stt; hash = md2verthashdata + i; vertremap[i] = hash->xyz; sts = LittleShort(inst[hash->st*2+0]); stt = LittleShort(inst[hash->st*2+1]); if (sts < 0 || sts >= skinwidth || stt < 0 || stt >= skinheight) { Con_Printf("%s has an invalid skin coordinate (%i %i) on vert %i, changing to 0 0\n", loadmodel->name, sts, stt, i); sts = 0; stt = 0; } loadmodel->surfmesh.data_texcoordtexture2f[i*2+0] = sts * iskinwidth; loadmodel->surfmesh.data_texcoordtexture2f[i*2+1] = stt * iskinheight; } Mem_Free(md2verthash); Mem_Free(md2verthashdata); // generate ushort elements array if possible if (loadmodel->surfmesh.num_vertices <= 65536) loadmodel->surfmesh.data_element3s = (unsigned short *)Mem_Alloc(loadmodel->mempool, sizeof(unsigned short[3]) * loadmodel->surfmesh.num_triangles); if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; // load the frames datapointer = (base + LittleLong(pinmodel->ofs_frames)); for (i = 0;i < loadmodel->surfmesh.num_morphframes;i++) { int k; trivertx_t *v; trivertx_t *out; pinframe = (md2frame_t *)datapointer; datapointer += sizeof(md2frame_t); // store the frame scale/translate into the appropriate array for (j = 0;j < 3;j++) { loadmodel->surfmesh.data_morphmd2framesize6f[i*6+j] = LittleFloat(pinframe->scale[j]); loadmodel->surfmesh.data_morphmd2framesize6f[i*6+3+j] = LittleFloat(pinframe->translate[j]); } // convert the vertices v = (trivertx_t *)datapointer; out = loadmodel->surfmesh.data_morphmdlvertex + i * loadmodel->surfmesh.num_vertices; for (k = 0;k < loadmodel->surfmesh.num_vertices;k++) out[k] = v[vertremap[k]]; datapointer += numxyz * sizeof(trivertx_t); strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); loadmodel->animscenes[i].firstframe = i; loadmodel->animscenes[i].framecount = 1; loadmodel->animscenes[i].framerate = 10; loadmodel->animscenes[i].loop = true; } Mem_Free(vertremap); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); Mod_Alias_MorphMesh_CompileFrames(); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; surface = loadmodel->data_surfaces; surface->texture = loadmodel->data_textures; surface->num_firsttriangle = 0; surface->num_triangles = loadmodel->surfmesh.num_triangles; surface->num_firstvertex = 0; surface->num_vertices = loadmodel->surfmesh.num_vertices; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } void Mod_IDP3_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, k, version, meshvertices, meshtriangles; unsigned char *data; msurface_t *surface; md3modelheader_t *pinmodel; md3frameinfo_t *pinframe; md3mesh_t *pinmesh; md3tag_t *pintag; skinfile_t *skinfiles; pinmodel = (md3modelheader_t *)buffer; if (memcmp(pinmodel->identifier, "IDP3", 4)) Host_Error ("%s is not a MD3 (IDP3) file", loadmodel->name); version = LittleLong (pinmodel->version); if (version != MD3VERSION) Host_Error ("%s has wrong version number (%i should be %i)", loadmodel->name, version, MD3VERSION); skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; loadmodel->modeldatatypestring = "MD3"; loadmodel->type = mod_alias; loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_MD3_AnimateVertices; loadmodel->synctype = ST_RAND; // convert model flags to EF flags (MF_ROCKET becomes EF_ROCKET, etc) i = LittleLong (pinmodel->flags); loadmodel->effects = ((i & 255) << 24) | (i & 0x00FFFF00); // set up some global info about the model loadmodel->numframes = LittleLong(pinmodel->num_frames); loadmodel->num_surfaces = LittleLong(pinmodel->num_meshes); // make skinscenes for the skins (no groups) loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // load frameinfo loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, loadmodel->numframes * sizeof(animscene_t)); for (i = 0, pinframe = (md3frameinfo_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_frameinfo));i < loadmodel->numframes;i++, pinframe++) { strlcpy(loadmodel->animscenes[i].name, pinframe->name, sizeof(loadmodel->animscenes[i].name)); loadmodel->animscenes[i].firstframe = i; loadmodel->animscenes[i].framecount = 1; loadmodel->animscenes[i].framerate = 10; loadmodel->animscenes[i].loop = true; } // load tags loadmodel->num_tagframes = loadmodel->numframes; loadmodel->num_tags = LittleLong(pinmodel->num_tags); loadmodel->data_tags = (aliastag_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_tagframes * loadmodel->num_tags * sizeof(aliastag_t)); for (i = 0, pintag = (md3tag_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_tags));i < loadmodel->num_tagframes * loadmodel->num_tags;i++, pintag++) { strlcpy(loadmodel->data_tags[i].name, pintag->name, sizeof(loadmodel->data_tags[i].name)); for (j = 0;j < 9;j++) loadmodel->data_tags[i].matrixgl[j] = LittleFloat(pintag->rotationmatrix[j]); for (j = 0;j < 3;j++) loadmodel->data_tags[i].matrixgl[9+j] = LittleFloat(pintag->origin[j]); //Con_Printf("model \"%s\" frame #%i tag #%i \"%s\"\n", loadmodel->name, i / loadmodel->num_tags, i % loadmodel->num_tags, loadmodel->data_tags[i].name); } // load meshes meshvertices = 0; meshtriangles = 0; for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) { if (memcmp(pinmesh->identifier, "IDP3", 4)) Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); if (LittleLong(pinmesh->num_frames) != loadmodel->numframes) Host_Error("Mod_IDP3_Load: mesh numframes differs from header"); meshvertices += LittleLong(pinmesh->num_vertices); meshtriangles += LittleLong(pinmesh->num_triangles); } loadmodel->nummodelsurfaces = loadmodel->num_surfaces; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * sizeof(float[2]) + meshvertices * loadmodel->numframes * sizeof(md3vertex_t)); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.num_vertices = meshvertices; loadmodel->surfmesh.num_triangles = meshtriangles; loadmodel->surfmesh.num_morphframes = loadmodel->numframes; // TODO: remove? loadmodel->num_poses = loadmodel->surfmesh.num_morphframes; loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); } loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); loadmodel->surfmesh.data_morphmd3vertex = (md3vertex_t *)data;data += meshvertices * loadmodel->numframes * sizeof(md3vertex_t); if (meshvertices <= 65536) { loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); } meshvertices = 0; meshtriangles = 0; for (i = 0, pinmesh = (md3mesh_t *)((unsigned char *)pinmodel + LittleLong(pinmodel->lump_meshes));i < loadmodel->num_surfaces;i++, pinmesh = (md3mesh_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_end))) { if (memcmp(pinmesh->identifier, "IDP3", 4)) Host_Error("Mod_IDP3_Load: invalid mesh identifier (not IDP3)"); loadmodel->sortedmodelsurfaces[i] = i; surface = loadmodel->data_surfaces + i; surface->texture = loadmodel->data_textures + i; surface->num_firsttriangle = meshtriangles; surface->num_triangles = LittleLong(pinmesh->num_triangles); surface->num_firstvertex = meshvertices; surface->num_vertices = LittleLong(pinmesh->num_vertices); meshvertices += surface->num_vertices; meshtriangles += surface->num_triangles; for (j = 0;j < surface->num_triangles * 3;j++) loadmodel->surfmesh.data_element3i[j + surface->num_firsttriangle * 3] = surface->num_firstvertex + LittleLong(((int *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_elements)))[j]); for (j = 0;j < surface->num_vertices;j++) { loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 0] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 0]); loadmodel->surfmesh.data_texcoordtexture2f[(j + surface->num_firstvertex) * 2 + 1] = LittleFloat(((float *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_texcoords)))[j * 2 + 1]); } for (j = 0;j < loadmodel->numframes;j++) { const md3vertex_t *in = (md3vertex_t *)((unsigned char *)pinmesh + LittleLong(pinmesh->lump_framevertices)) + j * surface->num_vertices; md3vertex_t *out = loadmodel->surfmesh.data_morphmd3vertex + surface->num_firstvertex + j * loadmodel->surfmesh.num_vertices; for (k = 0;k < surface->num_vertices;k++, in++, out++) { out->origin[0] = LittleShort(in->origin[0]); out->origin[1] = LittleShort(in->origin[1]); out->origin[2] = LittleShort(in->origin[2]); out->pitch = in->pitch; out->yaw = in->yaw; } } Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, pinmesh->name, LittleLong(pinmesh->num_shaders) >= 1 ? ((md3shader_t *)((unsigned char *) pinmesh + LittleLong(pinmesh->lump_shaders)))->name : ""); Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); } if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); Mod_Alias_MorphMesh_CompileFrames(); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); Mod_FreeSkinFiles(skinfiles); Mod_MakeSortedSurfaces(loadmodel); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } void Mod_ZYMOTICMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) { zymtype1header_t *pinmodel, *pheader; unsigned char *pbase; int i, j, k, numposes, meshvertices, meshtriangles, *bonecount, *vertbonecounts, count, *renderlist, *renderlistend, *outelements; float modelradius, corner[2], *poses, *intexcoord2f, *outtexcoord2f, *bonepose, f, biggestorigin, tempvec[3], modelscale; zymvertex_t *verts, *vertdata; zymscene_t *scene; zymbone_t *bone; char *shadername; skinfile_t *skinfiles; unsigned char *data; msurface_t *surface; pinmodel = (zymtype1header_t *)buffer; pbase = (unsigned char *)buffer; if (memcmp(pinmodel->id, "ZYMOTICMODEL", 12)) Host_Error ("Mod_ZYMOTICMODEL_Load: %s is not a zymotic model", loadmodel->name); if (BigLong(pinmodel->type) != 1) Host_Error ("Mod_ZYMOTICMODEL_Load: only type 1 (skeletal pose) models are currently supported (name = %s)", loadmodel->name); loadmodel->modeldatatypestring = "ZYM"; loadmodel->type = mod_alias; loadmodel->synctype = ST_RAND; // byteswap header pheader = pinmodel; pheader->type = BigLong(pinmodel->type); pheader->filesize = BigLong(pinmodel->filesize); pheader->mins[0] = BigFloat(pinmodel->mins[0]); pheader->mins[1] = BigFloat(pinmodel->mins[1]); pheader->mins[2] = BigFloat(pinmodel->mins[2]); pheader->maxs[0] = BigFloat(pinmodel->maxs[0]); pheader->maxs[1] = BigFloat(pinmodel->maxs[1]); pheader->maxs[2] = BigFloat(pinmodel->maxs[2]); pheader->radius = BigFloat(pinmodel->radius); pheader->numverts = BigLong(pinmodel->numverts); pheader->numtris = BigLong(pinmodel->numtris); pheader->numshaders = BigLong(pinmodel->numshaders); pheader->numbones = BigLong(pinmodel->numbones); pheader->numscenes = BigLong(pinmodel->numscenes); pheader->lump_scenes.start = BigLong(pinmodel->lump_scenes.start); pheader->lump_scenes.length = BigLong(pinmodel->lump_scenes.length); pheader->lump_poses.start = BigLong(pinmodel->lump_poses.start); pheader->lump_poses.length = BigLong(pinmodel->lump_poses.length); pheader->lump_bones.start = BigLong(pinmodel->lump_bones.start); pheader->lump_bones.length = BigLong(pinmodel->lump_bones.length); pheader->lump_vertbonecounts.start = BigLong(pinmodel->lump_vertbonecounts.start); pheader->lump_vertbonecounts.length = BigLong(pinmodel->lump_vertbonecounts.length); pheader->lump_verts.start = BigLong(pinmodel->lump_verts.start); pheader->lump_verts.length = BigLong(pinmodel->lump_verts.length); pheader->lump_texcoords.start = BigLong(pinmodel->lump_texcoords.start); pheader->lump_texcoords.length = BigLong(pinmodel->lump_texcoords.length); pheader->lump_render.start = BigLong(pinmodel->lump_render.start); pheader->lump_render.length = BigLong(pinmodel->lump_render.length); pheader->lump_shaders.start = BigLong(pinmodel->lump_shaders.start); pheader->lump_shaders.length = BigLong(pinmodel->lump_shaders.length); pheader->lump_trizone.start = BigLong(pinmodel->lump_trizone.start); pheader->lump_trizone.length = BigLong(pinmodel->lump_trizone.length); if (pheader->numtris < 1 || pheader->numverts < 3 || pheader->numshaders < 1) { Con_Printf("%s has no geometry\n", loadmodel->name); return; } if (pheader->numscenes < 1 || pheader->lump_poses.length < (int)sizeof(float[3][4])) { Con_Printf("%s has no animations\n", loadmodel->name); return; } loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; loadmodel->numframes = pheader->numscenes; loadmodel->num_surfaces = pheader->numshaders; skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; // make skinscenes for the skins (no groups) loadmodel->skinscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numskins); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // model bbox // LordHavoc: actually we blow this away later with Mod_Alias_CalculateBoundingBox() modelradius = pheader->radius; for (i = 0;i < 3;i++) { loadmodel->normalmins[i] = pheader->mins[i]; loadmodel->normalmaxs[i] = pheader->maxs[i]; loadmodel->rotatedmins[i] = -modelradius; loadmodel->rotatedmaxs[i] = modelradius; } corner[0] = max(fabs(loadmodel->normalmins[0]), fabs(loadmodel->normalmaxs[0])); corner[1] = max(fabs(loadmodel->normalmins[1]), fabs(loadmodel->normalmaxs[1])); loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = sqrt(corner[0]*corner[0]+corner[1]*corner[1]); if (loadmodel->yawmaxs[0] > modelradius) loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = modelradius; loadmodel->yawmins[0] = loadmodel->yawmins[1] = -loadmodel->yawmaxs[0]; loadmodel->yawmins[2] = loadmodel->normalmins[2]; loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; loadmodel->radius = modelradius; loadmodel->radius2 = modelradius * modelradius; // go through the lumps, swapping things //zymlump_t lump_scenes; // zymscene_t scene[numscenes]; // name and other information for each scene (see zymscene struct) loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); scene = (zymscene_t *) (pheader->lump_scenes.start + pbase); numposes = pheader->lump_poses.length / pheader->numbones / sizeof(float[3][4]); for (i = 0;i < pheader->numscenes;i++) { memcpy(loadmodel->animscenes[i].name, scene->name, 32); loadmodel->animscenes[i].firstframe = BigLong(scene->start); loadmodel->animscenes[i].framecount = BigLong(scene->length); loadmodel->animscenes[i].framerate = BigFloat(scene->framerate); loadmodel->animscenes[i].loop = (BigLong(scene->flags) & ZYMSCENEFLAG_NOLOOP) == 0; if ((unsigned int) loadmodel->animscenes[i].firstframe >= (unsigned int) numposes) Host_Error("%s scene->firstframe (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, numposes); if ((unsigned int) loadmodel->animscenes[i].firstframe + (unsigned int) loadmodel->animscenes[i].framecount > (unsigned int) numposes) Host_Error("%s scene->firstframe (%i) + framecount (%i) >= numposes (%i)", loadmodel->name, loadmodel->animscenes[i].firstframe, loadmodel->animscenes[i].framecount, numposes); if (loadmodel->animscenes[i].framerate < 0) Host_Error("%s scene->framerate (%f) < 0", loadmodel->name, loadmodel->animscenes[i].framerate); scene++; } //zymlump_t lump_bones; // zymbone_t bone[numbones]; loadmodel->num_bones = pheader->numbones; loadmodel->data_bones = (aliasbone_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(aliasbone_t)); bone = (zymbone_t *) (pheader->lump_bones.start + pbase); for (i = 0;i < pheader->numbones;i++) { memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); loadmodel->data_bones[i].flags = BigLong(bone[i].flags); loadmodel->data_bones[i].parent = BigLong(bone[i].parent); if (loadmodel->data_bones[i].parent >= i) Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); } //zymlump_t lump_vertbonecounts; // int vertbonecounts[numvertices]; // how many bones influence each vertex (separate mainly to make this compress better) vertbonecounts = (int *)Mem_Alloc(loadmodel->mempool, pheader->numverts * sizeof(int)); bonecount = (int *) (pheader->lump_vertbonecounts.start + pbase); for (i = 0;i < pheader->numverts;i++) { vertbonecounts[i] = BigLong(bonecount[i]); if (vertbonecounts[i] != 1) Host_Error("%s bonecount[%i] != 1 (vertex weight support is impossible in this format)", loadmodel->name, i); } loadmodel->num_poses = pheader->lump_poses.length / sizeof(float[3][4]) / loadmodel->num_bones; meshvertices = pheader->numverts; meshtriangles = pheader->numtris; loadmodel->nummodelsurfaces = loadmodel->num_surfaces; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short) + sizeof(unsigned char[2][4])) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12])); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.num_vertices = meshvertices; loadmodel->surfmesh.num_triangles = meshtriangles; loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); } loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); loadmodel->surfmesh.num_blends = 0; loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); if (loadmodel->surfmesh.num_vertices <= 65536) { loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); } loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); loadmodel->surfmesh.data_blendweights = NULL; //zymlump_t lump_poses; // float pose[numposes][numbones][3][4]; // animation data poses = (float *) (pheader->lump_poses.start + pbase); // figure out scale of model from root bone, for compatibility with old zmodel versions tempvec[0] = BigFloat(poses[0]); tempvec[1] = BigFloat(poses[1]); tempvec[2] = BigFloat(poses[2]); modelscale = VectorLength(tempvec); biggestorigin = 0; for (i = 0;i < loadmodel->num_bones * numposes * 12;i++) { f = fabs(BigFloat(poses[i])); biggestorigin = max(biggestorigin, f); } loadmodel->num_posescale = biggestorigin / 32767.0f; loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; for (i = 0;i < numposes;i++) { const float *frameposes = (float *) (pheader->lump_poses.start + pbase) + 12*i*loadmodel->num_bones; for (j = 0;j < loadmodel->num_bones;j++) { float pose[12]; matrix4x4_t posematrix; for (k = 0;k < 12;k++) pose[k] = BigFloat(frameposes[j*12+k]); //if (j < loadmodel->num_bones) // Con_Printf("%s: bone %i = %f %f %f %f : %f %f %f %f : %f %f %f %f : scale = %f\n", loadmodel->name, j, pose[0], pose[1], pose[2], pose[3], pose[4], pose[5], pose[6], pose[7], pose[8], pose[9], pose[10], pose[11], VectorLength(pose)); // scale child bones to match the root scale if (loadmodel->data_bones[j].parent >= 0) { pose[3] *= modelscale; pose[7] *= modelscale; pose[11] *= modelscale; } // normalize rotation matrix VectorNormalize(pose + 0); VectorNormalize(pose + 4); VectorNormalize(pose + 8); Matrix4x4_FromArray12FloatD3D(&posematrix, pose); Matrix4x4_ToBonePose7s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses7s + 7*(i*loadmodel->num_bones+j)); } } //zymlump_t lump_verts; // zymvertex_t vert[numvertices]; // see vertex struct verts = (zymvertex_t *)Mem_Alloc(loadmodel->mempool, pheader->lump_verts.length); vertdata = (zymvertex_t *) (pheader->lump_verts.start + pbase); // reconstruct frame 0 matrices to allow reconstruction of the base mesh // (converting from weight-blending skeletal animation to // deformation-based skeletal animation) bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); for (i = 0;i < loadmodel->num_bones;i++) { float m[12]; for (k = 0;k < 12;k++) m[k] = BigFloat(poses[i*12+k]); if (loadmodel->data_bones[i].parent >= 0) R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); else for (k = 0;k < 12;k++) bonepose[12*i+k] = m[k]; } for (j = 0;j < pheader->numverts;j++) { // this format really should have had a per vertexweight weight value... // but since it does not, the weighting is completely ignored and // only one weight is allowed per vertex int boneindex = BigLong(vertdata[j].bonenum); const float *m = bonepose + 12 * boneindex; float relativeorigin[3]; relativeorigin[0] = BigFloat(vertdata[j].origin[0]); relativeorigin[1] = BigFloat(vertdata[j].origin[1]); relativeorigin[2] = BigFloat(vertdata[j].origin[2]); // transform the vertex bone weight into the base mesh loadmodel->surfmesh.data_vertex3f[j*3+0] = relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + m[ 3]; loadmodel->surfmesh.data_vertex3f[j*3+1] = relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + m[ 7]; loadmodel->surfmesh.data_vertex3f[j*3+2] = relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + m[11]; // store the weight as the primary weight on this vertex loadmodel->surfmesh.blends[j] = boneindex; loadmodel->surfmesh.data_skeletalindex4ub[j*4 ] = boneindex; loadmodel->surfmesh.data_skeletalindex4ub[j*4+1] = 0; loadmodel->surfmesh.data_skeletalindex4ub[j*4+2] = 0; loadmodel->surfmesh.data_skeletalindex4ub[j*4+3] = 0; loadmodel->surfmesh.data_skeletalweight4ub[j*4 ] = 255; loadmodel->surfmesh.data_skeletalweight4ub[j*4+1] = 0; loadmodel->surfmesh.data_skeletalweight4ub[j*4+2] = 0; loadmodel->surfmesh.data_skeletalweight4ub[j*4+3] = 0; } Z_Free(bonepose); // normals and tangents are calculated after elements are loaded //zymlump_t lump_texcoords; // float texcoords[numvertices][2]; outtexcoord2f = loadmodel->surfmesh.data_texcoordtexture2f; intexcoord2f = (float *) (pheader->lump_texcoords.start + pbase); for (i = 0;i < pheader->numverts;i++) { outtexcoord2f[i*2+0] = BigFloat(intexcoord2f[i*2+0]); // flip T coordinate for OpenGL outtexcoord2f[i*2+1] = 1 - BigFloat(intexcoord2f[i*2+1]); } //zymlump_t lump_trizone; // byte trizone[numtris]; // see trizone explanation //loadmodel->alias.zymdata_trizone = Mem_Alloc(loadmodel->mempool, pheader->numtris); //memcpy(loadmodel->alias.zymdata_trizone, (void *) (pheader->lump_trizone.start + pbase), pheader->numtris); //zymlump_t lump_shaders; // char shadername[numshaders][32]; // shaders used on this model //zymlump_t lump_render; // int renderlist[rendersize]; // sorted by shader with run lengths (int count), shaders are sequentially used, each run can be used with glDrawElements (each triangle is 3 int indices) // byteswap, validate, and swap winding order of tris count = pheader->numshaders * sizeof(int) + pheader->numtris * sizeof(int[3]); if (pheader->lump_render.length != count) Host_Error("%s renderlist is wrong size (%i bytes, should be %i bytes)", loadmodel->name, pheader->lump_render.length, count); renderlist = (int *) (pheader->lump_render.start + pbase); renderlistend = (int *) ((unsigned char *) renderlist + pheader->lump_render.length); meshtriangles = 0; for (i = 0;i < loadmodel->num_surfaces;i++) { int firstvertex, lastvertex; if (renderlist >= renderlistend) Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); count = BigLong(*renderlist);renderlist++; if (renderlist + count * 3 > renderlistend || (i == pheader->numshaders - 1 && renderlist + count * 3 != renderlistend)) Host_Error("%s corrupt renderlist (wrong size)", loadmodel->name); loadmodel->sortedmodelsurfaces[i] = i; surface = loadmodel->data_surfaces + i; surface->texture = loadmodel->data_textures + i; surface->num_firsttriangle = meshtriangles; surface->num_triangles = count; meshtriangles += surface->num_triangles; // load the elements outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; for (j = 0;j < surface->num_triangles;j++, renderlist += 3) { outelements[j*3+2] = BigLong(renderlist[0]); outelements[j*3+1] = BigLong(renderlist[1]); outelements[j*3+0] = BigLong(renderlist[2]); } // validate the elements and find the used vertex range firstvertex = meshvertices; lastvertex = 0; for (j = 0;j < surface->num_triangles * 3;j++) { if ((unsigned int)outelements[j] >= (unsigned int)meshvertices) Host_Error("%s corrupt renderlist (out of bounds index)", loadmodel->name); firstvertex = min(firstvertex, outelements[j]); lastvertex = max(lastvertex, outelements[j]); } surface->num_firstvertex = firstvertex; surface->num_vertices = lastvertex + 1 - firstvertex; // since zym models do not have named sections, reuse their shader // name as the section name shadername = (char *) (pheader->lump_shaders.start + pbase) + i * 32; Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, shadername, shadername); } Mod_FreeSkinFiles(skinfiles); Mem_Free(vertbonecounts); Mem_Free(verts); Mod_MakeSortedSurfaces(loadmodel); // compute all the mesh information that was not loaded from the file if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); Mod_BuildBaseBonePoses(); Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } void Mod_DARKPLACESMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) { dpmheader_t *pheader; dpmframe_t *frames; dpmbone_t *bone; dpmmesh_t *dpmmesh; unsigned char *pbase; int i, j, k, meshvertices, meshtriangles; skinfile_t *skinfiles; unsigned char *data; float *bonepose; float biggestorigin, tempvec[3], modelscale; float f; float *poses; pheader = (dpmheader_t *)buffer; pbase = (unsigned char *)buffer; if (memcmp(pheader->id, "DARKPLACESMODEL\0", 16)) Host_Error ("Mod_DARKPLACESMODEL_Load: %s is not a darkplaces model", loadmodel->name); if (BigLong(pheader->type) != 2) Host_Error ("Mod_DARKPLACESMODEL_Load: only type 2 (hierarchical skeletal pose) models are currently supported (name = %s)", loadmodel->name); loadmodel->modeldatatypestring = "DPM"; loadmodel->type = mod_alias; loadmodel->synctype = ST_RAND; // byteswap header pheader->type = BigLong(pheader->type); pheader->filesize = BigLong(pheader->filesize); pheader->mins[0] = BigFloat(pheader->mins[0]); pheader->mins[1] = BigFloat(pheader->mins[1]); pheader->mins[2] = BigFloat(pheader->mins[2]); pheader->maxs[0] = BigFloat(pheader->maxs[0]); pheader->maxs[1] = BigFloat(pheader->maxs[1]); pheader->maxs[2] = BigFloat(pheader->maxs[2]); pheader->yawradius = BigFloat(pheader->yawradius); pheader->allradius = BigFloat(pheader->allradius); pheader->num_bones = BigLong(pheader->num_bones); pheader->num_meshs = BigLong(pheader->num_meshs); pheader->num_frames = BigLong(pheader->num_frames); pheader->ofs_bones = BigLong(pheader->ofs_bones); pheader->ofs_meshs = BigLong(pheader->ofs_meshs); pheader->ofs_frames = BigLong(pheader->ofs_frames); if (pheader->num_bones < 1 || pheader->num_meshs < 1) { Con_Printf("%s has no geometry\n", loadmodel->name); return; } if (pheader->num_frames < 1) { Con_Printf("%s has no frames\n", loadmodel->name); return; } loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; // model bbox // LordHavoc: actually we blow this away later with Mod_Alias_CalculateBoundingBox() for (i = 0;i < 3;i++) { loadmodel->normalmins[i] = pheader->mins[i]; loadmodel->normalmaxs[i] = pheader->maxs[i]; loadmodel->yawmins[i] = i != 2 ? -pheader->yawradius : pheader->mins[i]; loadmodel->yawmaxs[i] = i != 2 ? pheader->yawradius : pheader->maxs[i]; loadmodel->rotatedmins[i] = -pheader->allradius; loadmodel->rotatedmaxs[i] = pheader->allradius; } loadmodel->radius = pheader->allradius; loadmodel->radius2 = pheader->allradius * pheader->allradius; // load external .skin files if present skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; meshvertices = 0; meshtriangles = 0; // gather combined statistics from the meshes dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); for (i = 0;i < (int)pheader->num_meshs;i++) { int numverts = BigLong(dpmmesh->num_verts); meshvertices += numverts; meshtriangles += BigLong(dpmmesh->num_tris); dpmmesh++; } loadmodel->numframes = pheader->num_frames; loadmodel->num_bones = pheader->num_bones; loadmodel->num_poses = loadmodel->numframes; loadmodel->nummodelsurfaces = loadmodel->num_surfaces = pheader->num_meshs; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; // do most allocations as one merged chunk data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + sizeof(unsigned short) + sizeof(unsigned char[2][4])) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.num_vertices = meshvertices; loadmodel->surfmesh.num_triangles = meshtriangles; loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); } loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); loadmodel->surfmesh.num_blends = 0; loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); if (meshvertices <= 65536) { loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); } loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // load the bone info bone = (dpmbone_t *) (pbase + pheader->ofs_bones); for (i = 0;i < loadmodel->num_bones;i++) { memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); loadmodel->data_bones[i].flags = BigLong(bone[i].flags); loadmodel->data_bones[i].parent = BigLong(bone[i].parent); if (loadmodel->data_bones[i].parent >= i) Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); } // load the frames frames = (dpmframe_t *) (pbase + pheader->ofs_frames); // figure out scale of model from root bone, for compatibility with old dpmodel versions poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); tempvec[0] = BigFloat(poses[0]); tempvec[1] = BigFloat(poses[1]); tempvec[2] = BigFloat(poses[2]); modelscale = VectorLength(tempvec); biggestorigin = 0; for (i = 0;i < loadmodel->numframes;i++) { memcpy(loadmodel->animscenes[i].name, frames[i].name, sizeof(frames[i].name)); loadmodel->animscenes[i].firstframe = i; loadmodel->animscenes[i].framecount = 1; loadmodel->animscenes[i].loop = true; loadmodel->animscenes[i].framerate = 10; // load the bone poses for this frame poses = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); for (j = 0;j < loadmodel->num_bones*12;j++) { f = fabs(BigFloat(poses[j])); biggestorigin = max(biggestorigin, f); } // stuff not processed here: mins, maxs, yawradius, allradius } loadmodel->num_posescale = biggestorigin / 32767.0f; loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; for (i = 0;i < loadmodel->numframes;i++) { const float *frameposes = (float *) (pbase + BigLong(frames[i].ofs_bonepositions)); for (j = 0;j < loadmodel->num_bones;j++) { float pose[12]; matrix4x4_t posematrix; for (k = 0;k < 12;k++) pose[k] = BigFloat(frameposes[j*12+k]); // scale child bones to match the root scale if (loadmodel->data_bones[j].parent >= 0) { pose[3] *= modelscale; pose[7] *= modelscale; pose[11] *= modelscale; } // normalize rotation matrix VectorNormalize(pose + 0); VectorNormalize(pose + 4); VectorNormalize(pose + 8); Matrix4x4_FromArray12FloatD3D(&posematrix, pose); Matrix4x4_ToBonePose7s(&posematrix, loadmodel->num_poseinvscale, loadmodel->data_poses7s + 7*(i*loadmodel->num_bones+j)); } } // load the meshes now dpmmesh = (dpmmesh_t *) (pbase + pheader->ofs_meshs); meshvertices = 0; meshtriangles = 0; // reconstruct frame 0 matrices to allow reconstruction of the base mesh // (converting from weight-blending skeletal animation to // deformation-based skeletal animation) poses = (float *) (pbase + BigLong(frames[0].ofs_bonepositions)); bonepose = (float *)Z_Malloc(loadmodel->num_bones * sizeof(float[12])); for (i = 0;i < loadmodel->num_bones;i++) { float m[12]; for (k = 0;k < 12;k++) m[k] = BigFloat(poses[i*12+k]); if (loadmodel->data_bones[i].parent >= 0) R_ConcatTransforms(bonepose + 12 * loadmodel->data_bones[i].parent, m, bonepose + 12 * i); else for (k = 0;k < 12;k++) bonepose[12*i+k] = m[k]; } for (i = 0;i < loadmodel->num_surfaces;i++, dpmmesh++) { const int *inelements; int *outelements; const float *intexcoord; msurface_t *surface; loadmodel->sortedmodelsurfaces[i] = i; surface = loadmodel->data_surfaces + i; surface->texture = loadmodel->data_textures + i; surface->num_firsttriangle = meshtriangles; surface->num_triangles = BigLong(dpmmesh->num_tris); surface->num_firstvertex = meshvertices; surface->num_vertices = BigLong(dpmmesh->num_verts); meshvertices += surface->num_vertices; meshtriangles += surface->num_triangles; inelements = (int *) (pbase + BigLong(dpmmesh->ofs_indices)); outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3; for (j = 0;j < surface->num_triangles;j++) { // swap element order to flip triangles, because Quake uses clockwise (rare) and dpm uses counterclockwise (standard) outelements[0] = surface->num_firstvertex + BigLong(inelements[2]); outelements[1] = surface->num_firstvertex + BigLong(inelements[1]); outelements[2] = surface->num_firstvertex + BigLong(inelements[0]); inelements += 3; outelements += 3; } intexcoord = (float *) (pbase + BigLong(dpmmesh->ofs_texcoords)); for (j = 0;j < surface->num_vertices*2;j++) loadmodel->surfmesh.data_texcoordtexture2f[j + surface->num_firstvertex * 2] = BigFloat(intexcoord[j]); data = (unsigned char *) (pbase + BigLong(dpmmesh->ofs_verts)); for (j = surface->num_firstvertex;j < surface->num_firstvertex + surface->num_vertices;j++) { int weightindex[4] = { 0, 0, 0, 0 }; float weightinfluence[4] = { 0, 0, 0, 0 }; int l; int numweights = BigLong(((dpmvertex_t *)data)->numbones); data += sizeof(dpmvertex_t); for (k = 0;k < numweights;k++) { const dpmbonevert_t *vert = (dpmbonevert_t *) data; int boneindex = BigLong(vert->bonenum); const float *m = bonepose + 12 * boneindex; float influence = BigFloat(vert->influence); float relativeorigin[3], relativenormal[3]; relativeorigin[0] = BigFloat(vert->origin[0]); relativeorigin[1] = BigFloat(vert->origin[1]); relativeorigin[2] = BigFloat(vert->origin[2]); relativenormal[0] = BigFloat(vert->normal[0]); relativenormal[1] = BigFloat(vert->normal[1]); relativenormal[2] = BigFloat(vert->normal[2]); // blend the vertex bone weights into the base mesh loadmodel->surfmesh.data_vertex3f[j*3+0] += relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + influence * m[ 3]; loadmodel->surfmesh.data_vertex3f[j*3+1] += relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + influence * m[ 7]; loadmodel->surfmesh.data_vertex3f[j*3+2] += relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + influence * m[11]; loadmodel->surfmesh.data_normal3f[j*3+0] += relativenormal[0] * m[0] + relativenormal[1] * m[1] + relativenormal[2] * m[ 2]; loadmodel->surfmesh.data_normal3f[j*3+1] += relativenormal[0] * m[4] + relativenormal[1] * m[5] + relativenormal[2] * m[ 6]; loadmodel->surfmesh.data_normal3f[j*3+2] += relativenormal[0] * m[8] + relativenormal[1] * m[9] + relativenormal[2] * m[10]; if (!k) { // store the first (and often only) weight weightinfluence[0] = influence; weightindex[0] = boneindex; } else { // sort the new weight into this vertex's weight table // (which only accepts up to 4 bones per vertex) for (l = 0;l < 4;l++) { if (weightinfluence[l] < influence) { // move weaker influence weights out of the way first int l2; for (l2 = 3;l2 > l;l2--) { weightinfluence[l2] = weightinfluence[l2-1]; weightindex[l2] = weightindex[l2-1]; } // store the new weight weightinfluence[l] = influence; weightindex[l] = boneindex; break; } } } data += sizeof(dpmbonevert_t); } loadmodel->surfmesh.blends[j] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); loadmodel->surfmesh.data_skeletalindex4ub[j*4 ] = weightindex[0]; loadmodel->surfmesh.data_skeletalindex4ub[j*4+1] = weightindex[1]; loadmodel->surfmesh.data_skeletalindex4ub[j*4+2] = weightindex[2]; loadmodel->surfmesh.data_skeletalindex4ub[j*4+3] = weightindex[3]; loadmodel->surfmesh.data_skeletalweight4ub[j*4 ] = (unsigned char)(weightinfluence[0]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[j*4+1] = (unsigned char)(weightinfluence[1]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[j*4+2] = (unsigned char)(weightinfluence[2]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[j*4+3] = (unsigned char)(weightinfluence[3]*255.0f); } // since dpm models do not have named sections, reuse their shader name as the section name Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, dpmmesh->shadername, dpmmesh->shadername); Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__); } if (loadmodel->surfmesh.num_blends < meshvertices) loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); Z_Free(bonepose); Mod_FreeSkinFiles(skinfiles); Mod_MakeSortedSurfaces(loadmodel); // compute all the mesh information that was not loaded from the file if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; Mod_BuildBaseBonePoses(); Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } // no idea why PSK/PSA files contain weird quaternions but they do... #define PSKQUATNEGATIONS void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, j, index, version, recordsize, numrecords, meshvertices, meshtriangles; int numpnts, numvtxw, numfaces, nummatts, numbones, numrawweights, numanimbones, numanims, numanimkeys; fs_offset_t filesize; pskpnts_t *pnts; pskvtxw_t *vtxw; pskface_t *faces; pskmatt_t *matts; pskboneinfo_t *bones; pskrawweights_t *rawweights; //pskboneinfo_t *animbones; pskaniminfo_t *anims; pskanimkeys_t *animkeys; void *animfilebuffer, *animbuffer, *animbufferend; unsigned char *data; pskchunk_t *pchunk; skinfile_t *skinfiles; char animname[MAX_QPATH]; size_t size; float biggestorigin; pchunk = (pskchunk_t *)buffer; if (strcmp(pchunk->id, "ACTRHEAD")) Host_Error ("Mod_PSKMODEL_Load: %s is not an Unreal Engine ActorX (.psk + .psa) model", loadmodel->name); loadmodel->modeldatatypestring = "PSK"; loadmodel->type = mod_alias; loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; loadmodel->synctype = ST_RAND; FS_StripExtension(loadmodel->name, animname, sizeof(animname)); strlcat(animname, ".psa", sizeof(animname)); animbuffer = animfilebuffer = FS_LoadFile(animname, loadmodel->mempool, false, &filesize); animbufferend = (void *)((unsigned char*)animbuffer + (int)filesize); if (!animbuffer) animbufferend = animbuffer; numpnts = 0; pnts = NULL; numvtxw = 0; vtxw = NULL; numfaces = 0; faces = NULL; nummatts = 0; matts = NULL; numbones = 0; bones = NULL; numrawweights = 0; rawweights = NULL; numanims = 0; anims = NULL; numanimkeys = 0; animkeys = NULL; while (buffer < bufferend) { pchunk = (pskchunk_t *)buffer; buffer = (void *)((unsigned char *)buffer + sizeof(pskchunk_t)); version = LittleLong(pchunk->version); recordsize = LittleLong(pchunk->recordsize); numrecords = LittleLong(pchunk->numrecords); if (developer_extra.integer) Con_DPrintf("%s: %s %x: %i * %i = %i\n", loadmodel->name, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", loadmodel->name, pchunk->id, version); if (!strcmp(pchunk->id, "ACTRHEAD")) { // nothing to do } else if (!strcmp(pchunk->id, "PNTS0000")) { pskpnts_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer numpnts = numrecords; pnts = (pskpnts_t *)buffer; for (index = 0, p = (pskpnts_t *)buffer;index < numrecords;index++, p++) { p->origin[0] = LittleFloat(p->origin[0]); p->origin[1] = LittleFloat(p->origin[1]); p->origin[2] = LittleFloat(p->origin[2]); } buffer = p; } else if (!strcmp(pchunk->id, "VTXW0000")) { pskvtxw_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer numvtxw = numrecords; vtxw = (pskvtxw_t *)buffer; for (index = 0, p = (pskvtxw_t *)buffer;index < numrecords;index++, p++) { p->pntsindex = LittleShort(p->pntsindex); p->texcoord[0] = LittleFloat(p->texcoord[0]); p->texcoord[1] = LittleFloat(p->texcoord[1]); if (p->pntsindex >= numpnts) { Con_Printf("%s: vtxw->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); p->pntsindex = 0; } } buffer = p; } else if (!strcmp(pchunk->id, "FACE0000")) { pskface_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer numfaces = numrecords; faces = (pskface_t *)buffer; for (index = 0, p = (pskface_t *)buffer;index < numrecords;index++, p++) { p->vtxwindex[0] = LittleShort(p->vtxwindex[0]); p->vtxwindex[1] = LittleShort(p->vtxwindex[1]); p->vtxwindex[2] = LittleShort(p->vtxwindex[2]); p->group = LittleLong(p->group); if (p->vtxwindex[0] >= numvtxw) { Con_Printf("%s: face->vtxwindex[0] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[0], numvtxw); p->vtxwindex[0] = 0; } if (p->vtxwindex[1] >= numvtxw) { Con_Printf("%s: face->vtxwindex[1] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[1], numvtxw); p->vtxwindex[1] = 0; } if (p->vtxwindex[2] >= numvtxw) { Con_Printf("%s: face->vtxwindex[2] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[2], numvtxw); p->vtxwindex[2] = 0; } } buffer = p; } else if (!strcmp(pchunk->id, "MATT0000")) { pskmatt_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer nummatts = numrecords; matts = (pskmatt_t *)buffer; for (index = 0, p = (pskmatt_t *)buffer;index < numrecords;index++, p++) { // nothing to do } buffer = p; } else if (!strcmp(pchunk->id, "REFSKELT")) { pskboneinfo_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer numbones = numrecords; bones = (pskboneinfo_t *)buffer; for (index = 0, p = (pskboneinfo_t *)buffer;index < numrecords;index++, p++) { p->numchildren = LittleLong(p->numchildren); p->parent = LittleLong(p->parent); p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); p->basepose.unknown = LittleFloat(p->basepose.unknown); p->basepose.size[0] = LittleFloat(p->basepose.size[0]); p->basepose.size[1] = LittleFloat(p->basepose.size[1]); p->basepose.size[2] = LittleFloat(p->basepose.size[2]); #ifdef PSKQUATNEGATIONS if (index) { p->basepose.quat[0] *= -1; p->basepose.quat[1] *= -1; p->basepose.quat[2] *= -1; } else { p->basepose.quat[0] *= 1; p->basepose.quat[1] *= -1; p->basepose.quat[2] *= 1; } #endif if (p->parent < 0 || p->parent >= numbones) { Con_Printf("%s: bone->parent %i >= numbones %i\n", loadmodel->name, p->parent, numbones); p->parent = 0; } } buffer = p; } else if (!strcmp(pchunk->id, "RAWWEIGHTS")) { pskrawweights_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id); // byteswap in place and keep the pointer numrawweights = numrecords; rawweights = (pskrawweights_t *)buffer; for (index = 0, p = (pskrawweights_t *)buffer;index < numrecords;index++, p++) { p->weight = LittleFloat(p->weight); p->pntsindex = LittleLong(p->pntsindex); p->boneindex = LittleLong(p->boneindex); if (p->pntsindex < 0 || p->pntsindex >= numpnts) { Con_Printf("%s: weight->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts); p->pntsindex = 0; } if (p->boneindex < 0 || p->boneindex >= numbones) { Con_Printf("%s: weight->boneindex %i >= numbones %i\n", loadmodel->name, p->boneindex, numbones); p->boneindex = 0; } } buffer = p; } } while (animbuffer < animbufferend) { pchunk = (pskchunk_t *)animbuffer; animbuffer = (void *)((unsigned char *)animbuffer + sizeof(pskchunk_t)); version = LittleLong(pchunk->version); recordsize = LittleLong(pchunk->recordsize); numrecords = LittleLong(pchunk->numrecords); if (developer_extra.integer) Con_DPrintf("%s: %s %x: %i * %i = %i\n", animname, pchunk->id, version, recordsize, numrecords, recordsize * numrecords); if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0) Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", animname, pchunk->id, version); if (!strcmp(pchunk->id, "ANIMHEAD")) { // nothing to do } else if (!strcmp(pchunk->id, "BONENAMES")) { pskboneinfo_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); // byteswap in place and keep the pointer numanimbones = numrecords; //animbones = (pskboneinfo_t *)animbuffer; // NOTE: supposedly psa does not need to match the psk model, the // bones missing from the psa would simply use their base // positions from the psk, but this is hard for me to implement // and people can easily make animations that match. if (numanimbones != numbones) Host_Error("%s: this loader only supports animations with the same bones as the mesh", loadmodel->name); for (index = 0, p = (pskboneinfo_t *)animbuffer;index < numrecords;index++, p++) { p->numchildren = LittleLong(p->numchildren); p->parent = LittleLong(p->parent); p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]); p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]); p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]); p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]); p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]); p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]); p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]); p->basepose.unknown = LittleFloat(p->basepose.unknown); p->basepose.size[0] = LittleFloat(p->basepose.size[0]); p->basepose.size[1] = LittleFloat(p->basepose.size[1]); p->basepose.size[2] = LittleFloat(p->basepose.size[2]); #ifdef PSKQUATNEGATIONS if (index) { p->basepose.quat[0] *= -1; p->basepose.quat[1] *= -1; p->basepose.quat[2] *= -1; } else { p->basepose.quat[0] *= 1; p->basepose.quat[1] *= -1; p->basepose.quat[2] *= 1; } #endif if (p->parent < 0 || p->parent >= numanimbones) { Con_Printf("%s: bone->parent %i >= numanimbones %i\n", animname, p->parent, numanimbones); p->parent = 0; } // check that bones are the same as in the base if (strcmp(p->name, bones[index].name) || p->parent != bones[index].parent) Host_Error("%s: this loader only supports animations with the same bones as the mesh", animname); } animbuffer = p; } else if (!strcmp(pchunk->id, "ANIMINFO")) { pskaniminfo_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); // byteswap in place and keep the pointer numanims = numrecords; anims = (pskaniminfo_t *)animbuffer; for (index = 0, p = (pskaniminfo_t *)animbuffer;index < numrecords;index++, p++) { p->numbones = LittleLong(p->numbones); p->playtime = LittleFloat(p->playtime); p->fps = LittleFloat(p->fps); p->firstframe = LittleLong(p->firstframe); p->numframes = LittleLong(p->numframes); if (p->numbones != numbones) Con_Printf("%s: animinfo->numbones != numbones, trying to load anyway!\n", animname); } animbuffer = p; } else if (!strcmp(pchunk->id, "ANIMKEYS")) { pskanimkeys_t *p; if (recordsize != sizeof(*p)) Host_Error("%s: %s has unsupported recordsize", animname, pchunk->id); numanimkeys = numrecords; animkeys = (pskanimkeys_t *)animbuffer; for (index = 0, p = (pskanimkeys_t *)animbuffer;index < numrecords;index++, p++) { p->origin[0] = LittleFloat(p->origin[0]); p->origin[1] = LittleFloat(p->origin[1]); p->origin[2] = LittleFloat(p->origin[2]); p->quat[0] = LittleFloat(p->quat[0]); p->quat[1] = LittleFloat(p->quat[1]); p->quat[2] = LittleFloat(p->quat[2]); p->quat[3] = LittleFloat(p->quat[3]); p->frametime = LittleFloat(p->frametime); #ifdef PSKQUATNEGATIONS if (index % numbones) { p->quat[0] *= -1; p->quat[1] *= -1; p->quat[2] *= -1; } else { p->quat[0] *= 1; p->quat[1] *= -1; p->quat[2] *= 1; } #endif } animbuffer = p; // TODO: allocate bonepose stuff } else Con_Printf("%s: unknown chunk ID \"%s\"\n", animname, pchunk->id); } if (!numpnts || !pnts || !numvtxw || !vtxw || !numfaces || !faces || !nummatts || !matts || !numbones || !bones || !numrawweights || !rawweights) Host_Error("%s: missing required chunks", loadmodel->name); if (numanims) { loadmodel->numframes = 0; for (index = 0;index < numanims;index++) loadmodel->numframes += anims[index].numframes; if (numanimkeys != numbones * loadmodel->numframes) Host_Error("%s: %s has incorrect number of animation keys", animname, pchunk->id); } else loadmodel->numframes = loadmodel->num_poses = 1; meshvertices = numvtxw; meshtriangles = numfaces; // load external .skin files if present skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; loadmodel->num_bones = numbones; loadmodel->num_poses = loadmodel->numframes; loadmodel->nummodelsurfaces = loadmodel->num_surfaces = nummatts; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; loadmodel->surfmesh.num_vertices = meshvertices; loadmodel->surfmesh.num_triangles = meshtriangles; // do most allocations as one merged chunk size = loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + loadmodel->surfmesh.num_triangles * sizeof(int[3]) + (r_enableshadowvolumes.integer ? loadmodel->surfmesh.num_triangles * sizeof(int[3]) : 0) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[3]) + loadmodel->surfmesh.num_vertices * sizeof(float[2]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]) + loadmodel->surfmesh.num_vertices * sizeof(unsigned short) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t) + ((loadmodel->surfmesh.num_vertices <= 65536) ? (loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3])) : 0); data = (unsigned char *)Mem_Alloc(loadmodel->mempool, size); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.data_element3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += loadmodel->surfmesh.num_triangles * sizeof(int[3]); } loadmodel->surfmesh.data_vertex3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[3]); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += loadmodel->surfmesh.num_vertices * sizeof(float[2]); loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += loadmodel->surfmesh.num_vertices * sizeof(unsigned char[4]); loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); loadmodel->surfmesh.num_blends = 0; loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); if (loadmodel->surfmesh.num_vertices <= 65536) { loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += loadmodel->surfmesh.num_triangles * sizeof(unsigned short[3]); } loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, loadmodel->surfmesh.num_vertices * sizeof(blendweights_t)); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // create surfaces for (index = 0, i = 0;index < nummatts;index++) { // since psk models do not have named sections, reuse their shader name as the section name Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + index, skinfiles, matts[index].name, matts[index].name); loadmodel->sortedmodelsurfaces[index] = index; loadmodel->data_surfaces[index].texture = loadmodel->data_textures + index; loadmodel->data_surfaces[index].num_firstvertex = 0; loadmodel->data_surfaces[index].num_vertices = loadmodel->surfmesh.num_vertices; } // copy over the vertex locations and texcoords for (index = 0;index < numvtxw;index++) { loadmodel->surfmesh.data_vertex3f[index*3+0] = pnts[vtxw[index].pntsindex].origin[0]; loadmodel->surfmesh.data_vertex3f[index*3+1] = pnts[vtxw[index].pntsindex].origin[1]; loadmodel->surfmesh.data_vertex3f[index*3+2] = pnts[vtxw[index].pntsindex].origin[2]; loadmodel->surfmesh.data_texcoordtexture2f[index*2+0] = vtxw[index].texcoord[0]; loadmodel->surfmesh.data_texcoordtexture2f[index*2+1] = vtxw[index].texcoord[1]; } // loading the faces is complicated because we need to sort them into surfaces by mattindex for (index = 0;index < numfaces;index++) loadmodel->data_surfaces[faces[index].mattindex].num_triangles++; for (index = 0, i = 0;index < nummatts;index++) { loadmodel->data_surfaces[index].num_firsttriangle = i; i += loadmodel->data_surfaces[index].num_triangles; loadmodel->data_surfaces[index].num_triangles = 0; } for (index = 0;index < numfaces;index++) { i = (loadmodel->data_surfaces[faces[index].mattindex].num_firsttriangle + loadmodel->data_surfaces[faces[index].mattindex].num_triangles++)*3; loadmodel->surfmesh.data_element3i[i+0] = faces[index].vtxwindex[0]; loadmodel->surfmesh.data_element3i[i+1] = faces[index].vtxwindex[1]; loadmodel->surfmesh.data_element3i[i+2] = faces[index].vtxwindex[2]; } // copy over the bones for (index = 0;index < numbones;index++) { strlcpy(loadmodel->data_bones[index].name, bones[index].name, sizeof(loadmodel->data_bones[index].name)); loadmodel->data_bones[index].parent = (index || bones[index].parent > 0) ? bones[index].parent : -1; if (loadmodel->data_bones[index].parent >= index) Host_Error("%s bone[%i].parent >= %i", loadmodel->name, index, index); } // convert the basepose data if (loadmodel->num_bones) { int boneindex; matrix4x4_t *basebonepose; float *outinvmatrix = loadmodel->data_baseboneposeinverse; matrix4x4_t bonematrix; matrix4x4_t tempbonematrix; basebonepose = (matrix4x4_t *)Mem_Alloc(tempmempool, loadmodel->num_bones * sizeof(matrix4x4_t)); for (boneindex = 0;boneindex < loadmodel->num_bones;boneindex++) { Matrix4x4_FromOriginQuat(&bonematrix, bones[boneindex].basepose.origin[0], bones[boneindex].basepose.origin[1], bones[boneindex].basepose.origin[2], bones[boneindex].basepose.quat[0], bones[boneindex].basepose.quat[1], bones[boneindex].basepose.quat[2], bones[boneindex].basepose.quat[3]); if (loadmodel->data_bones[boneindex].parent >= 0) { tempbonematrix = bonematrix; Matrix4x4_Concat(&bonematrix, basebonepose + loadmodel->data_bones[boneindex].parent, &tempbonematrix); } basebonepose[boneindex] = bonematrix; Matrix4x4_Invert_Simple(&tempbonematrix, basebonepose + boneindex); Matrix4x4_ToArray12FloatD3D(&tempbonematrix, outinvmatrix + 12*boneindex); } Mem_Free(basebonepose); } // sort the psk point weights into the vertex weight tables // (which only accept up to 4 bones per vertex) for (index = 0;index < numvtxw;index++) { int weightindex[4] = { 0, 0, 0, 0 }; float weightinfluence[4] = { 0, 0, 0, 0 }; int l; for (j = 0;j < numrawweights;j++) { if (rawweights[j].pntsindex == vtxw[index].pntsindex) { int boneindex = rawweights[j].boneindex; float influence = rawweights[j].weight; for (l = 0;l < 4;l++) { if (weightinfluence[l] < influence) { // move lower influence weights out of the way first int l2; for (l2 = 3;l2 > l;l2--) { weightinfluence[l2] = weightinfluence[l2-1]; weightindex[l2] = weightindex[l2-1]; } // store the new weight weightinfluence[l] = influence; weightindex[l] = boneindex; break; } } } } loadmodel->surfmesh.blends[index] = Mod_Skeletal_CompressBlend(loadmodel, weightindex, weightinfluence); loadmodel->surfmesh.data_skeletalindex4ub[index*4 ] = weightindex[0]; loadmodel->surfmesh.data_skeletalindex4ub[index*4+1] = weightindex[1]; loadmodel->surfmesh.data_skeletalindex4ub[index*4+2] = weightindex[2]; loadmodel->surfmesh.data_skeletalindex4ub[index*4+3] = weightindex[3]; loadmodel->surfmesh.data_skeletalweight4ub[index*4 ] = (unsigned char)(weightinfluence[0]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[index*4+1] = (unsigned char)(weightinfluence[1]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[index*4+2] = (unsigned char)(weightinfluence[2]*255.0f); loadmodel->surfmesh.data_skeletalweight4ub[index*4+3] = (unsigned char)(weightinfluence[3]*255.0f); } if (loadmodel->surfmesh.num_blends < loadmodel->surfmesh.num_vertices) loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Realloc(loadmodel->mempool, loadmodel->surfmesh.data_blendweights, loadmodel->surfmesh.num_blends * sizeof(blendweights_t)); // set up the animscenes based on the anims if (numanims) { for (index = 0, i = 0;index < numanims;index++) { for (j = 0;j < anims[index].numframes;j++, i++) { dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "%s_%d", anims[index].name, j); loadmodel->animscenes[i].firstframe = i; loadmodel->animscenes[i].framecount = 1; loadmodel->animscenes[i].loop = true; loadmodel->animscenes[i].framerate = anims[index].fps; } } // calculate the scaling value for bone origins so they can be compressed to short biggestorigin = 0; for (index = 0;index < numanimkeys;index++) { pskanimkeys_t *k = animkeys + index; biggestorigin = max(biggestorigin, fabs(k->origin[0])); biggestorigin = max(biggestorigin, fabs(k->origin[1])); biggestorigin = max(biggestorigin, fabs(k->origin[2])); } loadmodel->num_posescale = biggestorigin / 32767.0f; loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; // load the poses from the animkeys for (index = 0;index < numanimkeys;index++) { pskanimkeys_t *k = animkeys + index; float quat[4]; Vector4Copy(k->quat, quat); if (quat[3] > 0) Vector4Negate(quat, quat); Vector4Normalize2(quat, quat); // compress poses to the short[7] format for longterm storage loadmodel->data_poses7s[index*7+0] = k->origin[0] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+1] = k->origin[1] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+2] = k->origin[2] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+3] = quat[0] * 32767.0f; loadmodel->data_poses7s[index*7+4] = quat[1] * 32767.0f; loadmodel->data_poses7s[index*7+5] = quat[2] * 32767.0f; loadmodel->data_poses7s[index*7+6] = quat[3] * 32767.0f; } } else { strlcpy(loadmodel->animscenes[0].name, "base", sizeof(loadmodel->animscenes[0].name)); loadmodel->animscenes[0].firstframe = 0; loadmodel->animscenes[0].framecount = 1; loadmodel->animscenes[0].loop = true; loadmodel->animscenes[0].framerate = 10; // calculate the scaling value for bone origins so they can be compressed to short biggestorigin = 0; for (index = 0;index < numbones;index++) { pskboneinfo_t *p = bones + index; biggestorigin = max(biggestorigin, fabs(p->basepose.origin[0])); biggestorigin = max(biggestorigin, fabs(p->basepose.origin[1])); biggestorigin = max(biggestorigin, fabs(p->basepose.origin[2])); } loadmodel->num_posescale = biggestorigin / 32767.0f; loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; // load the basepose as a frame for (index = 0;index < numbones;index++) { pskboneinfo_t *p = bones + index; float quat[4]; Vector4Copy(p->basepose.quat, quat); if (quat[3] > 0) Vector4Negate(quat, quat); Vector4Normalize2(quat, quat); // compress poses to the short[7] format for longterm storage loadmodel->data_poses7s[index*7+0] = p->basepose.origin[0] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+1] = p->basepose.origin[1] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+2] = p->basepose.origin[2] * loadmodel->num_poseinvscale; loadmodel->data_poses7s[index*7+3] = quat[0] * 32767.0f; loadmodel->data_poses7s[index*7+4] = quat[1] * 32767.0f; loadmodel->data_poses7s[index*7+5] = quat[2] * 32767.0f; loadmodel->data_poses7s[index*7+6] = quat[3] * 32767.0f; } } Mod_FreeSkinFiles(skinfiles); if (animfilebuffer) Mem_Free(animfilebuffer); Mod_MakeSortedSurfaces(loadmodel); // compute all the mesh information that was not loaded from the file // TODO: honor smoothing groups somehow? if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, loadmodel->surfmesh.num_vertices, __FILE__, __LINE__); Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); if (loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); loadmodel->surfmesh.isanimated = Mod_Alias_CalculateBoundingBox(); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; if (!loadmodel->surfmesh.isanimated) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } void Mod_INTERQUAKEMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend) { unsigned char *data; const char *text; const unsigned char *pbase, *pend; iqmheader_t header; skinfile_t *skinfiles; int i, j, k, meshvertices, meshtriangles; float biggestorigin; const unsigned int *inelements; int *outelements; const int *inneighbors; int *outneighbors; float *outvertex, *outnormal, *outtexcoord, *outsvector, *outtvector, *outcolor; // this pointers into the file data are read only through Little* functions so they can be unaligned memory const float *vnormal = NULL; const float *vposition = NULL; const float *vtangent = NULL; const float *vtexcoord = NULL; const float *vcolor4f = NULL; const unsigned char *vblendindexes = NULL; const unsigned char *vblendweights = NULL; const unsigned char *vcolor4ub = NULL; const unsigned short *framedata = NULL; // temporary memory allocations (because the data in the file may be misaligned) iqmanim_t *anims = NULL; iqmbounds_t *bounds = NULL; iqmjoint1_t *joint1 = NULL; iqmjoint_t *joint = NULL; iqmmesh_t *meshes = NULL; iqmpose1_t *pose1 = NULL; iqmpose_t *pose = NULL; iqmvertexarray_t *vas = NULL; pbase = (unsigned char *)buffer; pend = (unsigned char *)bufferend; if (pbase + sizeof(iqmheader_t) > pend) Host_Error ("Mod_INTERQUAKEMODEL_Load: %s is not an Inter-Quake Model %d", loadmodel->name, (int)(pend - pbase)); // copy struct (otherwise it may be misaligned) // LordHavoc: okay it's definitely not misaligned here, but for consistency... memcpy(&header, pbase, sizeof(iqmheader_t)); if (memcmp(header.id, "INTERQUAKEMODEL", 16)) Host_Error ("Mod_INTERQUAKEMODEL_Load: %s is not an Inter-Quake Model", loadmodel->name); if (LittleLong(header.version) != 1 && LittleLong(header.version) != 2) Host_Error ("Mod_INTERQUAKEMODEL_Load: only version 1 and 2 models are currently supported (name = %s)", loadmodel->name); loadmodel->modeldatatypestring = "IQM"; loadmodel->type = mod_alias; loadmodel->synctype = ST_RAND; // byteswap header header.version = LittleLong(header.version); header.filesize = LittleLong(header.filesize); header.flags = LittleLong(header.flags); header.num_text = LittleLong(header.num_text); header.ofs_text = LittleLong(header.ofs_text); header.num_meshes = LittleLong(header.num_meshes); header.ofs_meshes = LittleLong(header.ofs_meshes); header.num_vertexarrays = LittleLong(header.num_vertexarrays); header.num_vertexes = LittleLong(header.num_vertexes); header.ofs_vertexarrays = LittleLong(header.ofs_vertexarrays); header.num_triangles = LittleLong(header.num_triangles); header.ofs_triangles = LittleLong(header.ofs_triangles); header.ofs_neighbors = LittleLong(header.ofs_neighbors); header.num_joints = LittleLong(header.num_joints); header.ofs_joints = LittleLong(header.ofs_joints); header.num_poses = LittleLong(header.num_poses); header.ofs_poses = LittleLong(header.ofs_poses); header.num_anims = LittleLong(header.num_anims); header.ofs_anims = LittleLong(header.ofs_anims); header.num_frames = LittleLong(header.num_frames); header.num_framechannels = LittleLong(header.num_framechannels); header.ofs_frames = LittleLong(header.ofs_frames); header.ofs_bounds = LittleLong(header.ofs_bounds); header.num_comment = LittleLong(header.num_comment); header.ofs_comment = LittleLong(header.ofs_comment); header.num_extensions = LittleLong(header.num_extensions); header.ofs_extensions = LittleLong(header.ofs_extensions); if (header.version == 1) { if (pbase + header.ofs_joints + header.num_joints*sizeof(iqmjoint1_t) > pend || pbase + header.ofs_poses + header.num_poses*sizeof(iqmpose1_t) > pend) { Con_Printf("%s has invalid size or offset information\n", loadmodel->name); return; } } else { if (pbase + header.ofs_joints + header.num_joints*sizeof(iqmjoint_t) > pend || pbase + header.ofs_poses + header.num_poses*sizeof(iqmpose_t) > pend) { Con_Printf("%s has invalid size or offset information\n", loadmodel->name); return; } } if (pbase + header.ofs_text + header.num_text > pend || pbase + header.ofs_meshes + header.num_meshes*sizeof(iqmmesh_t) > pend || pbase + header.ofs_vertexarrays + header.num_vertexarrays*sizeof(iqmvertexarray_t) > pend || pbase + header.ofs_triangles + header.num_triangles*sizeof(int[3]) > pend || (header.ofs_neighbors && pbase + header.ofs_neighbors + header.num_triangles*sizeof(int[3]) > pend) || pbase + header.ofs_anims + header.num_anims*sizeof(iqmanim_t) > pend || pbase + header.ofs_frames + header.num_frames*header.num_framechannels*sizeof(unsigned short) > pend || (header.ofs_bounds && pbase + header.ofs_bounds + header.num_frames*sizeof(iqmbounds_t) > pend) || pbase + header.ofs_comment + header.num_comment > pend) { Con_Printf("%s has invalid size or offset information\n", loadmodel->name); return; } // copy structs to make them aligned in memory (otherwise we crash on Sparc and PowerPC and others) if (header.num_vertexarrays) vas = (iqmvertexarray_t *)(pbase + header.ofs_vertexarrays); if (header.num_anims) anims = (iqmanim_t *)(pbase + header.ofs_anims); if (header.ofs_bounds) bounds = (iqmbounds_t *)(pbase + header.ofs_bounds); if (header.num_meshes) meshes = (iqmmesh_t *)(pbase + header.ofs_meshes); for (i = 0;i < (int)header.num_vertexarrays;i++) { iqmvertexarray_t va; size_t vsize; va.type = LittleLong(vas[i].type); va.flags = LittleLong(vas[i].flags); va.format = LittleLong(vas[i].format); va.size = LittleLong(vas[i].size); va.offset = LittleLong(vas[i].offset); vsize = header.num_vertexes*va.size; switch (va.format) { case IQM_FLOAT: vsize *= sizeof(float); break; case IQM_UBYTE: vsize *= sizeof(unsigned char); break; default: continue; } if (pbase + va.offset + vsize > pend) continue; // no need to copy the vertex data for alignment because LittleLong/LittleShort will be invoked on reading them, and the destination is aligned switch (va.type) { case IQM_POSITION: if (va.format == IQM_FLOAT && va.size == 3) vposition = (const float *)(pbase + va.offset); break; case IQM_TEXCOORD: if (va.format == IQM_FLOAT && va.size == 2) vtexcoord = (const float *)(pbase + va.offset); break; case IQM_NORMAL: if (va.format == IQM_FLOAT && va.size == 3) vnormal = (const float *)(pbase + va.offset); break; case IQM_TANGENT: if (va.format == IQM_FLOAT && va.size == 4) vtangent = (const float *)(pbase + va.offset); break; case IQM_BLENDINDEXES: if (va.format == IQM_UBYTE && va.size == 4) vblendindexes = (const unsigned char *)(pbase + va.offset); break; case IQM_BLENDWEIGHTS: if (va.format == IQM_UBYTE && va.size == 4) vblendweights = (const unsigned char *)(pbase + va.offset); break; case IQM_COLOR: if (va.format == IQM_FLOAT && va.size == 4) vcolor4f = (const float *)(pbase + va.offset); if (va.format == IQM_UBYTE && va.size == 4) vcolor4ub = (const unsigned char *)(pbase + va.offset); break; } } if (header.num_vertexes > 0 && (!vposition || !vtexcoord || ((header.num_frames > 0 || header.num_anims > 0) && (!vblendindexes || !vblendweights)))) { Con_Printf("%s is missing vertex array data\n", loadmodel->name); return; } text = header.num_text && header.ofs_text ? (const char *)(pbase + header.ofs_text) : ""; loadmodel->DrawSky = NULL; loadmodel->DrawAddWaterPlanes = NULL; loadmodel->Draw = R_Q1BSP_Draw; loadmodel->DrawDepth = R_Q1BSP_DrawDepth; loadmodel->DrawDebug = R_Q1BSP_DrawDebug; loadmodel->DrawPrepass = R_Q1BSP_DrawPrepass; loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap; loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap; loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume; loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; loadmodel->DrawLight = R_Q1BSP_DrawLight; loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine; loadmodel->PointSuperContents = NULL; loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices; // load external .skin files if present skinfiles = Mod_LoadSkinFiles(); if (loadmodel->numskins < 1) loadmodel->numskins = 1; loadmodel->numframes = max(header.num_anims, 1); loadmodel->num_bones = header.num_joints; loadmodel->num_poses = max(header.num_frames, 1); loadmodel->nummodelsurfaces = loadmodel->num_surfaces = header.num_meshes; loadmodel->num_textures = loadmodel->num_surfaces * loadmodel->numskins; loadmodel->num_texturesperskin = loadmodel->num_surfaces; meshvertices = header.num_vertexes; meshtriangles = header.num_triangles; // do most allocations as one merged chunk data = (unsigned char *)Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + meshtriangles * sizeof(int[3]) + (meshvertices <= 65536 ? meshtriangles * sizeof(unsigned short[3]) : 0) + (r_enableshadowvolumes.integer ? meshtriangles * sizeof(int[3]) : 0) + meshvertices * (sizeof(float[14]) + (vcolor4f || vcolor4ub ? sizeof(float[4]) : 0)) + (vblendindexes && vblendweights ? meshvertices * (sizeof(unsigned short) + sizeof(unsigned char[2][4])) : 0) + loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]) + loadmodel->num_bones * sizeof(float[12]) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->numframes * sizeof(animscene_t)); loadmodel->data_surfaces = (msurface_t *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); loadmodel->sortedmodelsurfaces = (int *)data;data += loadmodel->num_surfaces * sizeof(int); loadmodel->data_textures = (texture_t *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); loadmodel->surfmesh.num_vertices = meshvertices; loadmodel->surfmesh.num_triangles = meshtriangles; loadmodel->surfmesh.data_element3i = (int *)data;data += meshtriangles * sizeof(int[3]); if (r_enableshadowvolumes.integer) { loadmodel->surfmesh.data_neighbor3i = (int *)data;data += meshtriangles * sizeof(int[3]); } loadmodel->surfmesh.data_vertex3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_svector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_tvector3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_normal3f = (float *)data;data += meshvertices * sizeof(float[3]); loadmodel->surfmesh.data_texcoordtexture2f = (float *)data;data += meshvertices * sizeof(float[2]); if (vcolor4f || vcolor4ub) { loadmodel->surfmesh.data_lightmapcolor4f = (float *)data;data += meshvertices * sizeof(float[4]); } if (vblendindexes && vblendweights) { loadmodel->surfmesh.data_skeletalindex4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); loadmodel->surfmesh.data_skeletalweight4ub = (unsigned char *)data;data += meshvertices * sizeof(unsigned char[4]); } loadmodel->data_baseboneposeinverse = (float *)data;data += loadmodel->num_bones * sizeof(float[12]); loadmodel->skinscenes = (animscene_t *)data;data += loadmodel->numskins * sizeof(animscene_t); loadmodel->data_bones = (aliasbone_t *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); loadmodel->animscenes = (animscene_t *)data;data += loadmodel->numframes * sizeof(animscene_t); if (vblendindexes && vblendweights) { loadmodel->surfmesh.num_blends = 0; loadmodel->surfmesh.blends = (unsigned short *)data;data += meshvertices * sizeof(unsigned short); } if (meshvertices <= 65536) { loadmodel->surfmesh.data_element3s = (unsigned short *)data;data += meshtriangles * sizeof(unsigned short[3]); } loadmodel->data_poses7s = (short *)data;data += loadmodel->num_poses * loadmodel->num_bones * sizeof(short[7]); if (vblendindexes && vblendweights) loadmodel->surfmesh.data_blendweights = (blendweights_t *)Mem_Alloc(loadmodel->mempool, meshvertices * sizeof(blendweights_t)); for (i = 0;i < loadmodel->numskins;i++) { loadmodel->skinscenes[i].firstframe = i; loadmodel->skinscenes[i].framecount = 1; loadmodel->skinscenes[i].loop = true; loadmodel->skinscenes[i].framerate = 10; } // load the bone info if (header.version == 1) { iqmjoint1_t *injoint1 = (iqmjoint1_t *)(pbase + header.ofs_joints); if (loadmodel->num_bones) joint1 = (iqmjoint1_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(iqmjoint1_t)); for (i = 0;i < loadmodel->num_bones;i++) { matrix4x4_t relbase, relinvbase, pinvbase, invbase; joint1[i].name = LittleLong(injoint1[i].name); joint1[i].parent = LittleLong(injoint1[i].parent); for (j = 0;j < 3;j++) { joint1[i].origin[j] = LittleFloat(injoint1[i].origin[j]); joint1[i].rotation[j] = LittleFloat(injoint1[i].rotation[j]); joint1[i].scale[j] = LittleFloat(injoint1[i].scale[j]); } strlcpy(loadmodel->data_bones[i].name, &text[joint1[i].name], sizeof(loadmodel->data_bones[i].name)); loadmodel->data_bones[i].parent = joint1[i].parent; if (loadmodel->data_bones[i].parent >= i) Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); Matrix4x4_FromDoom3Joint(&relbase, joint1[i].origin[0], joint1[i].origin[1], joint1[i].origin[2], joint1[i].rotation[0], joint1[i].rotation[1], joint1[i].rotation[2]); Matrix4x4_Invert_Simple(&relinvbase, &relbase); if (loadmodel->data_bones[i].parent >= 0) { Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); } else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); } } else { iqmjoint_t *injoint = (iqmjoint_t *)(pbase + header.ofs_joints); if (header.num_joints) joint = (iqmjoint_t *)Mem_Alloc(loadmodel->mempool, loadmodel->num_bones * sizeof(iqmjoint_t)); for (i = 0;i < loadmodel->num_bones;i++) { matrix4x4_t relbase, relinvbase, pinvbase, invbase; joint[i].name = LittleLong(injoint[i].name); joint[i].parent = LittleLong(injoint[i].parent); for (j = 0;j < 3;j++) { joint[i].origin[j] = LittleFloat(injoint[i].origin[j]); joint[i].rotation[j] = LittleFloat(injoint[i].rotation[j]); joint[i].scale[j] = LittleFloat(injoint[i].scale[j]); } joint[i].rotation[3] = LittleFloat(injoint[i].rotation[3]); strlcpy(loadmodel->data_bones[i].name, &text[joint[i].name], sizeof(loadmodel->data_bones[i].name)); loadmodel->data_bones[i].parent = joint[i].parent; if (loadmodel->data_bones[i].parent >= i) Host_Error("%s bone[%i].parent >= %i", loadmodel->name, i, i); if (joint[i].rotation[3] > 0) Vector4Negate(joint[i].rotation, joint[i].rotation); Vector4Normalize2(joint[i].rotation, joint[i].rotation); Matrix4x4_FromDoom3Joint(&relbase, joint[i].origin[0], joint[i].origin[1], joint[i].origin[2], joint[i].rotation[0], joint[i].rotation[1], joint[i].rotation[2]); Matrix4x4_Invert_Simple(&relinvbase, &relbase); if (loadmodel->data_bones[i].parent >= 0) { Matrix4x4_FromArray12FloatD3D(&pinvbase, loadmodel->data_baseboneposeinverse + 12*loadmodel->data_bones[i].parent); Matrix4x4_Concat(&invbase, &relinvbase, &pinvbase); Matrix4x4_ToArray12FloatD3D(&invbase, loadmodel->data_baseboneposeinverse + 12*i); } else Matrix4x4_ToArray12FloatD3D(&relinvbase, loadmodel->data_baseboneposeinverse + 12*i); } } // set up the animscenes based on the anims for (i = 0;i < (int)header.num_anims;i++) { iqmanim_t anim; anim.name = LittleLong(anims[i].name); anim.first_frame = LittleLong(anims[i].first_frame); anim.num_frames = LittleLong(anims[i].num_frames); anim.framerate = LittleFloat(anims[i].framerate); anim.flags = LittleLong(anims[i].flags); strlcpy(loadmodel->animscenes[i].name, &text[anim.name], sizeof(loadmodel->animscenes[i].name)); loadmodel->animscenes[i].firstframe = anim.first_frame; loadmodel->animscenes[i].framecount = anim.num_frames; loadmodel->animscenes[i].loop = ((anim.flags & IQM_LOOP) != 0); loadmodel->animscenes[i].framerate = anim.framerate; } if (header.num_anims <= 0) { strlcpy(loadmodel->animscenes[0].name, "static", sizeof(loadmodel->animscenes[0].name)); loadmodel->animscenes[0].firstframe = 0; loadmodel->animscenes[0].framecount = 1; loadmodel->animscenes[0].loop = true; loadmodel->animscenes[0].framerate = 10; } loadmodel->surfmesh.isanimated = loadmodel->num_bones > 1 || loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); if(mod_alias_force_animated.string[0]) loadmodel->surfmesh.isanimated = mod_alias_force_animated.integer != 0; biggestorigin = 0; if (header.version == 1) { iqmpose1_t *inpose1 = (iqmpose1_t *)(pbase + header.ofs_poses); if (header.num_poses) pose1 = (iqmpose1_t *)Mem_Alloc(loadmodel->mempool, header.num_poses * sizeof(iqmpose1_t)); for (i = 0;i < (int)header.num_poses;i++) { float f; pose1[i].parent = LittleLong(inpose1[i].parent); pose1[i].channelmask = LittleLong(inpose1[i].channelmask); for (j = 0;j < 9;j++) { pose1[i].channeloffset[j] = LittleFloat(inpose1[i].channeloffset[j]); pose1[i].channelscale[j] = LittleFloat(inpose1[i].channelscale[j]); } f = fabs(pose1[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); f = fabs(pose1[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); f = fabs(pose1[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); f = fabs(pose1[i].channeloffset[0] + 0xFFFF*pose1[i].channelscale[0]); biggestorigin = max(biggestorigin, f); f = fabs(pose1[i].channeloffset[1] + 0xFFFF*pose1[i].channelscale[1]); biggestorigin = max(biggestorigin, f); f = fabs(pose1[i].channeloffset[2] + 0xFFFF*pose1[i].channelscale[2]); biggestorigin = max(biggestorigin, f); } if (header.num_frames <= 0) { for (i = 0;i < loadmodel->num_bones;i++) { float f; f = fabs(joint1[i].origin[0]); biggestorigin = max(biggestorigin, f); f = fabs(joint1[i].origin[1]); biggestorigin = max(biggestorigin, f); f = fabs(joint1[i].origin[2]); biggestorigin = max(biggestorigin, f); } } } else { iqmpose_t *inpose = (iqmpose_t *)(pbase + header.ofs_poses); if (header.num_poses) pose = (iqmpose_t *)Mem_Alloc(loadmodel->mempool, header.num_poses * sizeof(iqmpose_t)); for (i = 0;i < (int)header.num_poses;i++) { float f; pose[i].parent = LittleLong(inpose[i].parent); pose[i].channelmask = LittleLong(inpose[i].channelmask); for (j = 0;j < 10;j++) { pose[i].channeloffset[j] = LittleFloat(inpose[i].channeloffset[j]); pose[i].channelscale[j] = LittleFloat(inpose[i].channelscale[j]); } f = fabs(pose[i].channeloffset[0]); biggestorigin = max(biggestorigin, f); f = fabs(pose[i].channeloffset[1]); biggestorigin = max(biggestorigin, f); f = fabs(pose[i].channeloffset[2]); biggestorigin = max(biggestorigin, f); f = fabs(pose[i].channeloffset[0] + 0xFFFF*pose[i].channelscale[0]); biggestorigin = max(biggestorigin, f); f = fabs(pose[i].channeloffset[1] + 0xFFFF*pose[i].channelscale[1]); biggestorigin = max(biggestorigin, f); f = fabs(pose[i].channeloffset[2] + 0xFFFF*pose[i].channelscale[2]); biggestorigin = max(biggestorigin, f); } if (header.num_frames <= 0) { for (i = 0;i < loadmodel->num_bones;i++) { float f; f = fabs(joint[i].origin[0]); biggestorigin = max(biggestorigin, f); f = fabs(joint[i].origin[1]); biggestorigin = max(biggestorigin, f); f = fabs(joint[i].origin[2]); biggestorigin = max(biggestorigin, f); } } } loadmodel->num_posescale = biggestorigin / 32767.0f; loadmodel->num_poseinvscale = 1.0f / loadmodel->num_posescale; // load the pose data // this unaligned memory access is safe (LittleShort reads as bytes) framedata = (const unsigned short *)(pbase + header.ofs_frames); if (header.version == 1) { for (i = 0, k = 0;i < (int)header.num_frames;i++) { for (j = 0;j < (int)header.num_poses;j++, k++) { float qx, qy, qz, qw; loadmodel->data_poses7s[k*7 + 0] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[0] + (pose1[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[0] : 0)); loadmodel->data_poses7s[k*7 + 1] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[1] + (pose1[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[1] : 0)); loadmodel->data_poses7s[k*7 + 2] = loadmodel->num_poseinvscale * (pose1[j].channeloffset[2] + (pose1[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[2] : 0)); qx = pose1[j].channeloffset[3] + (pose1[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[3] : 0); qy = pose1[j].channeloffset[4] + (pose1[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[4] : 0); qz = pose1[j].channeloffset[5] + (pose1[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose1[j].channelscale[5] : 0); qw = 1.0f - (qx*qx + qy*qy + qz*qz); qw = qw > 0.0f ? -sqrt(qw) : 0.0f; loadmodel->data_poses7s[k*7 + 3] = 32767.0f * qx; loadmodel->data_poses7s[k*7 + 4] = 32767.0f * qy; loadmodel->data_poses7s[k*7 + 5] = 32767.0f * qz; loadmodel->data_poses7s[k*7 + 6] = 32767.0f * qw; // skip scale data for now if(pose1[j].channelmask&64) framedata++; if(pose1[j].channelmask&128) framedata++; if(pose1[j].channelmask&256) framedata++; } } if (header.num_frames <= 0) { for (i = 0;i < loadmodel->num_bones;i++) { float qx, qy, qz, qw; loadmodel->data_poses7s[i*7 + 0] = loadmodel->num_poseinvscale * joint1[i].origin[0]; loadmodel->data_poses7s[i*7 + 1] = loadmodel->num_poseinvscale * joint1[i].origin[1]; loadmodel->data_poses7s[i*7 + 2] = loadmodel->num_poseinvscale * joint1[i].origin[2]; qx = joint1[i].rotation[0]; qy = joint1[i].rotation[1]; qz = joint1[i].rotation[2]; qw = 1.0f - (qx*qx + qy*qy + qz*qz); qw = qw > 0.0f ? -sqrt(qw) : 0.0f; loadmodel->data_poses7s[i*7 + 3] = 32767.0f * qx; loadmodel->data_poses7s[i*7 + 4] = 32767.0f * qy; loadmodel->data_poses7s[i*7 + 5] = 32767.0f * qz; loadmodel->data_poses7s[i*7 + 6] = 32767.0f * qw; } } } else { for (i = 0, k = 0;i < (int)header.num_frames;i++) { for (j = 0;j < (int)header.num_poses;j++, k++) { float rot[4]; loadmodel->data_poses7s[k*7 + 0] = loadmodel->num_poseinvscale * (pose[j].channeloffset[0] + (pose[j].channelmask&1 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[0] : 0)); loadmodel->data_poses7s[k*7 + 1] = loadmodel->num_poseinvscale * (pose[j].channeloffset[1] + (pose[j].channelmask&2 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[1] : 0)); loadmodel->data_poses7s[k*7 + 2] = loadmodel->num_poseinvscale * (pose[j].channeloffset[2] + (pose[j].channelmask&4 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[2] : 0)); rot[0] = pose[j].channeloffset[3] + (pose[j].channelmask&8 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[3] : 0); rot[1] = pose[j].channeloffset[4] + (pose[j].channelmask&16 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[4] : 0); rot[2] = pose[j].channeloffset[5] + (pose[j].channelmask&32 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[5] : 0); rot[3] = pose[j].channeloffset[6] + (pose[j].channelmask&64 ? (unsigned short)LittleShort(*framedata++) * pose[j].channelscale[6] : 0); if (rot[3] > 0) Vector4Negate(rot, rot); Vector4Normalize2(rot, rot); loadmodel->data_poses7s[k*7 + 3] = 32767.0f * rot[0]; loadmodel->data_poses7s[k*7 + 4] = 32767.0f * rot[1]; loadmodel->data_poses7s[k*7 + 5] = 32767.0f * rot[2]; loadmodel->data_poses7s[k*7 + 6] = 32767.0f * rot[3]; // skip scale data for now if(pose[j].channelmask&128) framedata++; if(pose[j].channelmask&256) framedata++; if(pose[j].channelmask&512) framedata++; } } if (header.num_frames <= 0) { for (i = 0;i < loadmodel->num_bones;i++) { loadmodel->data_poses7s[i*7 + 0] = loadmodel->num_poseinvscale * joint[i].origin[0]; loadmodel->data_poses7s[i*7 + 1] = loadmodel->num_poseinvscale * joint[i].origin[1]; loadmodel->data_poses7s[i*7 + 2] = loadmodel->num_poseinvscale * joint[i].origin[2]; loadmodel->data_poses7s[i*7 + 3] = 32767.0f * joint[i].rotation[0]; loadmodel->data_poses7s[i*7 + 4] = 32767.0f * joint[i].rotation[1]; loadmodel->data_poses7s[i*7 + 5] = 32767.0f * joint[i].rotation[2]; loadmodel->data_poses7s[i*7 + 6] = 32767.0f * joint[i].rotation[3]; } } } // load bounding box data if (header.ofs_bounds) { float xyradius = 0, radius = 0; VectorClear(loadmodel->normalmins); VectorClear(loadmodel->normalmaxs); for (i = 0; i < (int)header.num_frames;i++) { iqmbounds_t bound; bound.mins[0] = LittleFloat(bounds[i].mins[0]); bound.mins[1] = LittleFloat(bounds[i].mins[1]); bound.mins[2] = LittleFloat(bounds[i].mins[2]); bound.maxs[0] = LittleFloat(bounds[i].maxs[0]); bound.maxs[1] = LittleFloat(bounds[i].maxs[1]); bound.maxs[2] = LittleFloat(bounds[i].maxs[2]); bound.xyradius = LittleFloat(bounds[i].xyradius); bound.radius = LittleFloat(bounds[i].radius); if (!i) { VectorCopy(bound.mins, loadmodel->normalmins); VectorCopy(bound.maxs, loadmodel->normalmaxs); } else { if (loadmodel->normalmins[0] > bound.mins[0]) loadmodel->normalmins[0] = bound.mins[0]; if (loadmodel->normalmins[1] > bound.mins[1]) loadmodel->normalmins[1] = bound.mins[1]; if (loadmodel->normalmins[2] > bound.mins[2]) loadmodel->normalmins[2] = bound.mins[2]; if (loadmodel->normalmaxs[0] < bound.maxs[0]) loadmodel->normalmaxs[0] = bound.maxs[0]; if (loadmodel->normalmaxs[1] < bound.maxs[1]) loadmodel->normalmaxs[1] = bound.maxs[1]; if (loadmodel->normalmaxs[2] < bound.maxs[2]) loadmodel->normalmaxs[2] = bound.maxs[2]; } if (bound.xyradius > xyradius) xyradius = bound.xyradius; if (bound.radius > radius) radius = bound.radius; } loadmodel->yawmins[0] = loadmodel->yawmins[1] = -xyradius; loadmodel->yawmaxs[0] = loadmodel->yawmaxs[1] = xyradius; loadmodel->yawmins[2] = loadmodel->normalmins[2]; loadmodel->yawmaxs[2] = loadmodel->normalmaxs[2]; loadmodel->rotatedmins[0] = loadmodel->rotatedmins[1] = loadmodel->rotatedmins[2] = -radius; loadmodel->rotatedmaxs[0] = loadmodel->rotatedmaxs[1] = loadmodel->rotatedmaxs[2] = radius; loadmodel->radius = radius; loadmodel->radius2 = radius * radius; } // load triangle data // this unaligned memory access is safe (LittleLong reads as bytes) inelements = (const unsigned int *)(pbase + header.ofs_triangles); outelements = loadmodel->surfmesh.data_element3i; for (i = 0;i < (int)header.num_triangles;i++) { outelements[0] = LittleLong(inelements[0]); outelements[1] = LittleLong(inelements[1]); outelements[2] = LittleLong(inelements[2]); outelements += 3; inelements += 3; } Mod_ValidateElements(loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles, 0, header.num_vertexes, __FILE__, __LINE__); if (header.ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) { // this unaligned memory access is safe (LittleLong reads as bytes) inneighbors = (const int *)(pbase + header.ofs_neighbors); outneighbors = loadmodel->surfmesh.data_neighbor3i; for (i = 0;i < (int)header.num_triangles;i++) { outneighbors[0] = LittleLong(inneighbors[0]); outneighbors[1] = LittleLong(inneighbors[1]); outneighbors[2] = LittleLong(inneighbors[2]); outneighbors += 3; inneighbors += 3; } } // load vertex data // this unaligned memory access is safe (LittleFloat reads as bytes) outvertex = loadmodel->surfmesh.data_vertex3f; for (i = 0;i < (int)header.num_vertexes;i++) { outvertex[0] = LittleFloat(vposition[0]); outvertex[1] = LittleFloat(vposition[1]); outvertex[2] = LittleFloat(vposition[2]); vposition += 3; outvertex += 3; } outtexcoord = loadmodel->surfmesh.data_texcoordtexture2f; // this unaligned memory access is safe (LittleFloat reads as bytes) for (i = 0;i < (int)header.num_vertexes;i++) { outtexcoord[0] = LittleFloat(vtexcoord[0]); outtexcoord[1] = LittleFloat(vtexcoord[1]); vtexcoord += 2; outtexcoord += 2; } // this unaligned memory access is safe (LittleFloat reads as bytes) if(vnormal) { outnormal = loadmodel->surfmesh.data_normal3f; for (i = 0;i < (int)header.num_vertexes;i++) { outnormal[0] = LittleFloat(vnormal[0]); outnormal[1] = LittleFloat(vnormal[1]); outnormal[2] = LittleFloat(vnormal[2]); vnormal += 3; outnormal += 3; } } // this unaligned memory access is safe (LittleFloat reads as bytes) if(vnormal && vtangent) { outnormal = loadmodel->surfmesh.data_normal3f; outsvector = loadmodel->surfmesh.data_svector3f; outtvector = loadmodel->surfmesh.data_tvector3f; for (i = 0;i < (int)header.num_vertexes;i++) { outsvector[0] = LittleFloat(vtangent[0]); outsvector[1] = LittleFloat(vtangent[1]); outsvector[2] = LittleFloat(vtangent[2]); if(LittleFloat(vtangent[3]) < 0) CrossProduct(outsvector, outnormal, outtvector); else CrossProduct(outnormal, outsvector, outtvector); vtangent += 4; outnormal += 3; outsvector += 3; outtvector += 3; } } // this unaligned memory access is safe (all bytes) if (vblendindexes && vblendweights) { for (i = 0; i < (int)header.num_vertexes;i++) { blendweights_t weights; memcpy(weights.index, vblendindexes + i*4, 4); memcpy(weights.influence, vblendweights + i*4, 4); loadmodel->surfmesh.blends[i] = Mod_Skeletal_AddBlend(loadmodel, &weights); loadmodel->surfmesh.data_skeletalindex4ub[i*4 ] = weights.index[0]; loadmodel->surfmesh.data_skeletalindex4ub[i*4+1] = weights.index[1]; loadmodel->surfmesh.data_skeletalindex4ub[i*4+2] = weights.index[2]; loadmodel->surfmesh.data_skeletalindex4ub[i*4+3] = weights.index[3]; loadmodel->surfmesh.data_skeletalweight4ub[i*4 ] = weights.influence[0]; loadmodel->surfmesh.data_skeletalweight4ub[i*4+1] = weights.influence[1]; loadmodel->surfmesh.data_skeletalweight4ub[i*4+2] = weights.influence[2]; loadmodel->surfmesh.data_skeletalweight4ub[i*4+3] = weights.influence[3]; } } if (vcolor4f) { outcolor = loadmodel->surfmesh.data_lightmapcolor4f; // this unaligned memory access is safe (LittleFloat reads as bytes) for (i = 0;i < (int)header.num_vertexes;i++) { outcolor[0] = LittleFloat(vcolor4f[0]); outcolor[1] = LittleFloat(vcolor4f[1]); outcolor[2] = LittleFloat(vcolor4f[2]); outcolor[3] = LittleFloat(vcolor4f[3]); vcolor4f += 4; outcolor += 4; } } else if (vcolor4ub) { outcolor = loadmodel->surfmesh.data_lightmapcolor4f; // this unaligned memory access is safe (all bytes) for (i = 0;i < (int)header.num_vertexes;i++) { outcolor[0] = vcolor4ub[0] * (1.0f / 255.0f); outcolor[1] = vcolor4ub[1] * (1.0f / 255.0f); outcolor[2] = vcolor4ub[2] * (1.0f / 255.0f); outcolor[3] = vcolor4ub[3] * (1.0f / 255.0f); vcolor4ub += 4; outcolor += 4; } } // load meshes for (i = 0;i < (int)header.num_meshes;i++) { iqmmesh_t mesh; msurface_t *surface; mesh.name = LittleLong(meshes[i].name); mesh.material = LittleLong(meshes[i].material); mesh.first_vertex = LittleLong(meshes[i].first_vertex); mesh.num_vertexes = LittleLong(meshes[i].num_vertexes); mesh.first_triangle = LittleLong(meshes[i].first_triangle); mesh.num_triangles = LittleLong(meshes[i].num_triangles); loadmodel->sortedmodelsurfaces[i] = i; surface = loadmodel->data_surfaces + i; surface->texture = loadmodel->data_textures + i; surface->num_firsttriangle = mesh.first_triangle; surface->num_triangles = mesh.num_triangles; surface->num_firstvertex = mesh.first_vertex; surface->num_vertices = mesh.num_vertexes; Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, &text[mesh.name], &text[mesh.material]); } Mod_FreeSkinFiles(skinfiles); Mod_MakeSortedSurfaces(loadmodel); // compute all the mesh information that was not loaded from the file if (loadmodel->surfmesh.data_element3s) for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++) loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i]; if (!vnormal) Mod_BuildNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_normal3f, r_smoothnormals_areaweighting.integer != 0); if (!vnormal || !vtangent) Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, r_smoothnormals_areaweighting.integer != 0); if (!header.ofs_neighbors && loadmodel->surfmesh.data_neighbor3i) Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles); if (!header.ofs_bounds) Mod_Alias_CalculateBoundingBox(); if (!loadmodel->surfmesh.isanimated && loadmodel->surfmesh.num_triangles >= 1) { Mod_MakeCollisionBIH(loadmodel, true, &loadmodel->collision_bih); loadmodel->TraceBox = Mod_CollisionBIH_TraceBox; loadmodel->TraceBrush = Mod_CollisionBIH_TraceBrush; loadmodel->TraceLine = Mod_CollisionBIH_TraceLine; loadmodel->TracePoint = Mod_CollisionBIH_TracePoint_Mesh; loadmodel->PointSuperContents = Mod_CollisionBIH_PointSuperContents_Mesh; } if (joint ) Mem_Free(joint );joint = NULL; if (joint1 ) Mem_Free(joint1 );joint1 = NULL; if (pose ) Mem_Free(pose );pose = NULL; if (pose1 ) Mem_Free(pose1 );pose1 = NULL; // because shaders can do somewhat unexpected things, check for unusual features now for (i = 0;i < loadmodel->num_textures;i++) { if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_SKY)) mod->DrawSky = R_Q1BSP_DrawSky; if (loadmodel->data_textures[i].basematerialflags & (MATERIALFLAG_WATERSHADER | MATERIALFLAG_REFRACTION | MATERIALFLAG_REFLECTION | MATERIALFLAG_CAMERA)) mod->DrawAddWaterPlanes = R_Q1BSP_DrawAddWaterPlanes; } } darkplaces/cd_sdl.c0000664000175000017500000001116013067716216013563 0ustar kalevkalev/* Copyright (C) 2004 Andreas Kirsch (used cd_null.c as template) Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "cdaudio.h" #if SDL_MAJOR_VERSION == 1 && SDL_MINOR_VERSION == 2 // SDL 1.2 has CD audio #include #include // If one of the functions fails, it returns -1, if not 0 // SDL supports multiple cd devices - so we are going to support this, too. static void CDAudio_SDL_CDDrive_f( void ); // we only support playing one CD at a time static SDL_CD *cd; static int ValidateDrive( void ) { if( cd && SDL_CDStatus( cd ) > 0 ) return cdValid = true; return cdValid = false; } void CDAudio_SysEject (void) { SDL_CDEject( cd ); } void CDAudio_SysCloseDoor (void) { //NO SDL FUNCTION } int CDAudio_SysGetAudioDiskInfo (void) { if( ValidateDrive() ) // everything > 0 is ok, 0 is trayempty and -1 is error return cd->numtracks; return -1; } float CDAudio_SysGetVolume (void) { return -1.0f; } void CDAudio_SysSetVolume (float volume) { //NO SDL FUNCTION } int CDAudio_SysPlay (int track) { return SDL_CDPlayTracks(cd, track-1, 0, 1, 0); } int CDAudio_SysStop (void) { return SDL_CDStop( cd ); } int CDAudio_SysPause (void) { return SDL_CDPause( cd ); } int CDAudio_SysResume (void) { return SDL_CDResume( cd ); } int CDAudio_SysUpdate (void) { static time_t lastchk = 0; if (cdPlaying && lastchk < time(NULL)) { lastchk = time(NULL) + 2; //two seconds between chks if( !cd || cd->status <= 0 ) { cdValid = false; return -1; } if (SDL_CDStatus( cd ) == CD_STOPPED) { if( cdPlayLooping ) CDAudio_SysPlay( cdPlayTrack ); else cdPlaying = false; } } return 0; } void CDAudio_SysInit (void) { if( SDL_InitSubSystem( SDL_INIT_CDROM ) == -1 ) Con_Print( "Failed to init the CDROM SDL subsystem!\n" ); Cmd_AddCommand( "cddrive", CDAudio_SDL_CDDrive_f, "select an SDL-detected CD drive by number" ); } static int IsAudioCD( void ) { int i; for( i = 0 ; i < cd->numtracks ; i++ ) if( cd->track[ i ].type == SDL_AUDIO_TRACK ) return true; return false; } int CDAudio_SysStartup (void) { int i; int numdrives; numdrives = SDL_CDNumDrives(); if( numdrives == -1 ) // was the CDROM system initialized correctly? return -1; Con_Printf( "Found %i cdrom drives.\n", numdrives ); for( i = 0 ; i < numdrives ; i++, cd = NULL ) { cd = SDL_CDOpen( i ); if( !cd ) { Con_Printf( "CD drive %i is invalid.\n", i ); continue; } if( CD_INDRIVE( SDL_CDStatus( cd ) ) ) if( IsAudioCD() ) break; else Con_Printf( "The CD in drive %i is not an audio cd.\n", i ); else Con_Printf( "No CD in drive %i.\n", i ); SDL_CDClose( cd ); } if( i == numdrives && !cd ) return -1; return 0; } void CDAudio_SysShutdown (void) { if( cd ) SDL_CDClose( cd ); } void CDAudio_SDL_CDDrive_f( void ) { int i; int numdrives = SDL_CDNumDrives(); if( Cmd_Argc() != 2 ) { Con_Print( "cddrive \n" ); return; } i = atoi( Cmd_Argv( 1 ) ); if( i >= numdrives ) { Con_Printf("Only %i drives!\n", numdrives ); return; } if( cd ) SDL_CDClose( cd ); cd = SDL_CDOpen( i ); if( !cd ) { Con_Printf( "Couldn't open drive %i.\n", i ); return; } if( !CD_INDRIVE( SDL_CDStatus( cd ) ) ) Con_Printf( "No cd in drive %i.\n", i ); else if( !IsAudioCD() ) Con_Printf( "The CD in drive %i is not an audio CD.\n", i ); ValidateDrive(); } #else // SDL 1.3 does not have CD audio void CDAudio_SysEject (void) { } void CDAudio_SysCloseDoor (void) { } int CDAudio_SysGetAudioDiskInfo (void) { return -1; } float CDAudio_SysGetVolume (void) { return -1.0f; } void CDAudio_SysSetVolume (float fvolume) { } int CDAudio_SysPlay (int track) { return -1; } int CDAudio_SysStop (void) { return -1; } int CDAudio_SysPause (void) { return -1; } int CDAudio_SysResume (void) { return -1; } int CDAudio_SysUpdate (void) { return -1; } void CDAudio_SysInit (void) { } int CDAudio_SysStartup (void) { return -1; } void CDAudio_SysShutdown (void) { } #endif darkplaces/keys.c0000664000175000017500000013736013067716220013314 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #include "quakedef.h" #include "cl_video.h" #include "utf8lib.h" #include "csprogs.h" cvar_t con_closeontoggleconsole = {CVAR_SAVE, "con_closeontoggleconsole","1", "allows toggleconsole binds to close the console as well; when set to 2, this even works when not at the start of the line in console input; when set to 3, this works even if the toggleconsole key is the color tag"}; /* key up events are sent even if in console mode */ char key_line[MAX_INPUTLINE]; int key_linepos; qboolean key_insert = true; // insert key toggle (for editing) keydest_t key_dest; int key_consoleactive; char *keybindings[MAX_BINDMAPS][MAX_KEYS]; int history_line; char history_savedline[MAX_INPUTLINE]; char history_searchstring[MAX_INPUTLINE]; qboolean history_matchfound = false; conbuffer_t history; extern cvar_t con_textsize; static void Key_History_Init(void) { qfile_t *historyfile; ConBuffer_Init(&history, HIST_TEXTSIZE, HIST_MAXLINES, zonemempool); // not necessary for mobile #ifndef DP_MOBILETOUCH historyfile = FS_OpenRealFile("darkplaces_history.txt", "rb", false); // rb to handle unix line endings on windows too if(historyfile) { char buf[MAX_INPUTLINE]; int bufpos; int c; bufpos = 0; for(;;) { c = FS_Getc(historyfile); if(c < 0 || c == 0 || c == '\r' || c == '\n') { if(bufpos > 0) { buf[bufpos] = 0; ConBuffer_AddLine(&history, buf, bufpos, 0); bufpos = 0; } if(c < 0) break; } else { if(bufpos < MAX_INPUTLINE - 1) buf[bufpos++] = c; } } FS_Close(historyfile); } #endif history_line = -1; } static void Key_History_Shutdown(void) { // TODO write history to a file // not necessary for mobile #ifndef DP_MOBILETOUCH qfile_t *historyfile = FS_OpenRealFile("darkplaces_history.txt", "w", false); if(historyfile) { int i; for(i = 0; i < CONBUFFER_LINES_COUNT(&history); ++i) FS_Printf(historyfile, "%s\n", ConBuffer_GetLine(&history, i)); FS_Close(historyfile); } #endif ConBuffer_Shutdown(&history); } static void Key_History_Push(void) { if(key_line[1]) // empty? if(strcmp(key_line, "]quit")) // putting these into the history just sucks if(strncmp(key_line, "]quit ", 6)) // putting these into the history just sucks if(strcmp(key_line, "]rcon_password")) // putting these into the history just sucks if(strncmp(key_line, "]rcon_password ", 15)) // putting these into the history just sucks ConBuffer_AddLine(&history, key_line + 1, (int)strlen(key_line) - 1, 0); Con_Printf("%s\n", key_line); // don't mark empty lines as history history_line = -1; if (history_matchfound) history_matchfound = false; } static qboolean Key_History_Get_foundCommand(void) { if (!history_matchfound) return false; strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); key_linepos = (int)strlen(key_line); history_matchfound = false; return true; } static void Key_History_Up(void) { if(history_line == -1) // editing the "new" line strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); if (Key_History_Get_foundCommand()) return; if(history_line == -1) { history_line = CONBUFFER_LINES_COUNT(&history) - 1; if(history_line != -1) { strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); key_linepos = (int)strlen(key_line); } } else if(history_line > 0) { --history_line; // this also does -1 -> 0, so it is good strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); key_linepos = (int)strlen(key_line); } } static void Key_History_Down(void) { if(history_line == -1) // editing the "new" line return; if (Key_History_Get_foundCommand()) return; if(history_line < CONBUFFER_LINES_COUNT(&history) - 1) { ++history_line; strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); } else { history_line = -1; strlcpy(key_line + 1, history_savedline, sizeof(key_line) - 1); } key_linepos = (int)strlen(key_line); } static void Key_History_First(void) { if(history_line == -1) // editing the "new" line strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); if (CONBUFFER_LINES_COUNT(&history) > 0) { history_line = 0; strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); key_linepos = (int)strlen(key_line); } } static void Key_History_Last(void) { if(history_line == -1) // editing the "new" line strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); if (CONBUFFER_LINES_COUNT(&history) > 0) { history_line = CONBUFFER_LINES_COUNT(&history) - 1; strlcpy(key_line + 1, ConBuffer_GetLine(&history, history_line), sizeof(key_line) - 1); key_linepos = (int)strlen(key_line); } } static void Key_History_Find_Backwards(void) { int i; const char *partial = key_line + 1; char vabuf[1024]; size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); if (history_line == -1) // editing the "new" line strlcpy(history_savedline, key_line + 1, sizeof(history_savedline)); if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search { strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); i = CONBUFFER_LINES_COUNT(&history) - 1; } else if (history_line == -1) i = CONBUFFER_LINES_COUNT(&history) - 1; else i = history_line - 1; if (!*partial) partial = "*"; else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? partial = va(vabuf, sizeof(vabuf), "*%s*", partial); for ( ; i >= 0; i--) if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) { Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); history_line = i; history_matchfound = true; return; } } static void Key_History_Find_Forwards(void) { int i; const char *partial = key_line + 1; char vabuf[1024]; size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); if (history_line == -1) // editing the "new" line return; if (strcmp(key_line + 1, history_searchstring)) // different string? Start a new search { strlcpy(history_searchstring, key_line + 1, sizeof(history_searchstring)); i = 0; } else i = history_line + 1; if (!*partial) partial = "*"; else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? partial = va(vabuf, sizeof(vabuf), "*%s*", partial); for ( ; i < CONBUFFER_LINES_COUNT(&history); i++) if (matchpattern_with_separator(ConBuffer_GetLine(&history, i), partial, true, "", false)) { Con_Printf("^2%*i^7 %s\n", (int)digits, i+1, ConBuffer_GetLine(&history, i)); history_line = i; history_matchfound = true; return; } } static void Key_History_Find_All(void) { const char *partial = key_line + 1; int i, count = 0; char vabuf[1024]; size_t digits = strlen(va(vabuf, sizeof(vabuf), "%i", HIST_MAXLINES)); Con_Printf("History commands containing \"%s\":\n", key_line + 1); if (!*partial) partial = "*"; else if (!( strchr(partial, '*') || strchr(partial, '?') )) // no pattern? partial = va(vabuf, sizeof(vabuf), "*%s*", partial); for (i=0; i 1) { if (!strcmp(Cmd_Argv (1), "-c")) { ConBuffer_Clear(&history); return; } i = strtol(Cmd_Argv (1), &errchar, 0); if ((i < 0) || (i > CONBUFFER_LINES_COUNT(&history)) || (errchar && *errchar)) i = 0; else i = CONBUFFER_LINES_COUNT(&history) - i; } for ( ; i= MAX_INPUTLINE) i= MAX_INPUTLINE - key_linepos - 1; if (i > 0) { cbd[i] = 0; memmove(key_line + key_linepos + i, key_line + key_linepos, sizeof(key_line) - key_linepos - i); memcpy(key_line + key_linepos, cbd, i); key_linepos += i; } Z_Free(cbd); } return; } if (key == 'l' && keydown[K_CTRL]) { Cbuf_AddText ("clear\n"); return; } if (key == 'u' && keydown[K_CTRL]) // like vi/readline ^u: delete currently edited line { // clear line key_line[0] = ']'; key_line[1] = 0; key_linepos = 1; return; } if (key == 'q' && keydown[K_CTRL]) // like zsh ^q: push line to history, don't execute, and clear { // clear line Key_History_Push(); key_line[0] = ']'; key_line[1] = 0; key_linepos = 1; return; } if (key == K_ENTER || key == K_KP_ENTER) { Cbuf_AddText (key_line+1); // skip the ] Cbuf_AddText ("\n"); Key_History_Push(); key_line[0] = ']'; key_line[1] = 0; // EvilTypeGuy: null terminate key_linepos = 1; // force an update, because the command may take some time if (cls.state == ca_disconnected) CL_UpdateScreen (); return; } if (key == K_TAB) { if(keydown[K_CTRL]) // append to the cvar its value { int cvar_len, cvar_str_len, chars_to_move; char k; char cvar[MAX_INPUTLINE]; const char *cvar_str; // go to the start of the variable while(--key_linepos) { k = key_line[key_linepos]; if(k == '\"' || k == ';' || k == ' ' || k == '\'') break; } key_linepos++; // save the variable name in cvar for(cvar_len=0; (k = key_line[key_linepos + cvar_len]) != 0; cvar_len++) { if(k == '\"' || k == ';' || k == ' ' || k == '\'') break; cvar[cvar_len] = k; } if (cvar_len==0) return; cvar[cvar_len] = 0; // go to the end of the cvar key_linepos += cvar_len; // save the content of the variable in cvar_str cvar_str = Cvar_VariableString(cvar); cvar_str_len = (int)strlen(cvar_str); if (cvar_str_len==0) return; // insert space and cvar_str in key_line chars_to_move = (int)strlen(&key_line[key_linepos]); if (key_linepos + 1 + cvar_str_len + chars_to_move < MAX_INPUTLINE) { if (chars_to_move) memmove(&key_line[key_linepos + 1 + cvar_str_len], &key_line[key_linepos], chars_to_move); key_line[key_linepos++] = ' '; memcpy(&key_line[key_linepos], cvar_str, cvar_str_len); key_linepos += cvar_str_len; key_line[key_linepos + chars_to_move] = 0; } else Con_Printf("Couldn't append cvar value, edit line too long.\n"); return; } // Enhanced command completion // by EvilTypeGuy eviltypeguy@qeradiant.com // Thanks to Fett, Taniwha Con_CompleteCommandLine(); return; } // Advanced Console Editing by Radix radix@planetquake.com // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com // Enhanced by [515] // Enhanced by terencehill // move cursor to the previous character if (key == K_LEFTARROW || key == K_KP_LEFTARROW) { if (key_linepos < 2) return; if(keydown[K_CTRL]) // move cursor to the previous word { int pos; char k; pos = key_linepos-1; if(pos) // skip all "; ' after the word while(--pos) { k = key_line[pos]; if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) break; } if(pos) while(--pos) { k = key_line[pos]; if(k == '\"' || k == ';' || k == ' ' || k == '\'') break; } key_linepos = pos + 1; } else if(keydown[K_SHIFT]) // move cursor to the previous character ignoring colors { int pos; size_t inchar = 0; pos = (int)u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte while (pos) if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && isdigit(key_line[pos])) pos-=2; else if(pos-4 > 0 && key_line[pos-4] == STRING_COLOR_TAG && key_line[pos-3] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos-2]) && isxdigit(key_line[pos-1]) && isxdigit(key_line[pos])) pos-=5; else { if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && key_line[pos] == STRING_COLOR_TAG) // consider ^^ as a character pos--; pos--; break; } // we need to move to the beginning of the character when in a wide character: u8_charidx(key_line, pos + 1, &inchar); key_linepos = (int)(pos + 1 - inchar); } else { key_linepos = (int)u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte } return; } // delete char before cursor if (key == K_BACKSPACE || (key == 'h' && keydown[K_CTRL])) { if (key_linepos > 1) { int newpos = (int)u8_prevbyte(key_line+1, key_linepos-1) + 1; // do NOT give the ']' to u8_prevbyte strlcpy(key_line + newpos, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos); key_linepos = newpos; } return; } // delete char on cursor if (key == K_DEL || key == K_KP_DEL) { size_t linelen; linelen = strlen(key_line); if (key_linepos < (int)linelen) memmove(key_line + key_linepos, key_line + key_linepos + u8_bytelen(key_line + key_linepos, 1), linelen - key_linepos); return; } // move cursor to the next character if (key == K_RIGHTARROW || key == K_KP_RIGHTARROW) { if (key_linepos >= (int)strlen(key_line)) return; if(keydown[K_CTRL]) // move cursor to the next word { int pos, len; char k; len = (int)strlen(key_line); pos = key_linepos; while(++pos < len) { k = key_line[pos]; if(k == '\"' || k == ';' || k == ' ' || k == '\'') break; } if (pos < len) // skip all "; ' after the word while(++pos < len) { k = key_line[pos]; if (!(k == '\"' || k == ';' || k == ' ' || k == '\'')) break; } key_linepos = pos; } else if(keydown[K_SHIFT]) // move cursor to the next character ignoring colors { int pos, len; len = (int)strlen(key_line); pos = key_linepos; // go beyond all initial consecutive color tags, if any if(pos < len) while (key_line[pos] == STRING_COLOR_TAG) { if(isdigit(key_line[pos+1])) pos+=2; else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) pos+=5; else break; } // skip the char if (key_line[pos] == STRING_COLOR_TAG && key_line[pos+1] == STRING_COLOR_TAG) // consider ^^ as a character pos++; pos += (int)u8_bytelen(key_line + pos, 1); // now go beyond all next consecutive color tags, if any if(pos < len) while (key_line[pos] == STRING_COLOR_TAG) { if(isdigit(key_line[pos+1])) pos+=2; else if(key_line[pos+1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(key_line[pos+2]) && isxdigit(key_line[pos+3]) && isxdigit(key_line[pos+4])) pos+=5; else break; } key_linepos = pos; } else key_linepos += (int)u8_bytelen(key_line + key_linepos, 1); return; } if (key == K_INS || key == K_KP_INS) // toggle insert mode { key_insert ^= 1; return; } // End Advanced Console Editing if (key == K_UPARROW || key == K_KP_UPARROW || (key == 'p' && keydown[K_CTRL])) { Key_History_Up(); return; } if (key == K_DOWNARROW || key == K_KP_DOWNARROW || (key == 'n' && keydown[K_CTRL])) { Key_History_Down(); return; } if (keydown[K_CTRL]) { // prints all the matching commands if (key == 'f') { Key_History_Find_All(); return; } // Search forwards/backwards, pointing the history's index to the // matching command but without fetching it to let one continue the search. // To fetch it, it suffices to just press UP or DOWN. if (key == 'r') { if (keydown[K_SHIFT]) Key_History_Find_Forwards(); else Key_History_Find_Backwards(); return; } // go to the last/first command of the history if (key == ',') { Key_History_First(); return; } if (key == '.') { Key_History_Last(); return; } } if (key == K_PGUP || key == K_KP_PGUP) { if(keydown[K_CTRL]) { con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; } else con_backscroll += ((vid_conheight.integer >> 1) / con_textsize.integer)-3; return; } if (key == K_PGDN || key == K_KP_PGDN) { if(keydown[K_CTRL]) { con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; } else con_backscroll -= ((vid_conheight.integer >> 1) / con_textsize.integer)-3; return; } if (key == K_MWHEELUP) { if(keydown[K_CTRL]) con_backscroll += 1; else if(keydown[K_SHIFT]) con_backscroll += ((vid_conheight.integer >> 2) / con_textsize.integer)-1; else con_backscroll += 5; return; } if (key == K_MWHEELDOWN) { if(keydown[K_CTRL]) con_backscroll -= 1; else if(keydown[K_SHIFT]) con_backscroll -= ((vid_conheight.integer >> 2) / con_textsize.integer)-1; else con_backscroll -= 5; return; } if (keydown[K_CTRL]) { // text zoom in if (key == '+' || key == K_KP_PLUS) { if (con_textsize.integer < 128) Cvar_SetValueQuick(&con_textsize, con_textsize.integer + 1); return; } // text zoom out if (key == '-' || key == K_KP_MINUS) { if (con_textsize.integer > 1) Cvar_SetValueQuick(&con_textsize, con_textsize.integer - 1); return; } // text zoom reset if (key == '0' || key == K_KP_INS) { Cvar_SetValueQuick(&con_textsize, atoi(Cvar_VariableDefString("con_textsize"))); return; } } if (key == K_HOME || key == K_KP_HOME) { if (keydown[K_CTRL]) con_backscroll = CON_TEXTSIZE; else key_linepos = 1; return; } if (key == K_END || key == K_KP_END) { if (keydown[K_CTRL]) con_backscroll = 0; else key_linepos = (int)strlen(key_line); return; } // non printable if (unicode < 32) return; if (key_linepos < MAX_INPUTLINE-1) { char buf[16]; int len; int blen; blen = u8_fromchar(unicode, buf, sizeof(buf)); if (!blen) return; len = (int)strlen(&key_line[key_linepos]); // check insert mode, or always insert if at end of line if (key_insert || len == 0) { if (key_linepos + len + blen >= MAX_INPUTLINE) return; // can't use strcpy to move string to right len++; if (key_linepos + blen + len >= MAX_INPUTLINE) return; memmove(&key_line[key_linepos + blen], &key_line[key_linepos], len); } else if (key_linepos + len + blen - u8_bytelen(key_line + key_linepos, 1) >= MAX_INPUTLINE) return; memcpy(key_line + key_linepos, buf, blen); if (blen > len) key_line[key_linepos + blen] = 0; // END OF FIXME key_linepos += blen; } } //============================================================================ int chat_mode; char chat_buffer[MAX_INPUTLINE]; unsigned int chat_bufferlen = 0; static void Key_Message (int key, int ascii) { char vabuf[1024]; if (key == K_ENTER || key == K_KP_ENTER || ascii == 10 || ascii == 13) { if(chat_mode < 0) Cmd_ExecuteString(chat_buffer, src_command, true); // not Cbuf_AddText to allow semiclons in args; however, this allows no variables then. Use aliases! else Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "%s %s", chat_mode ? "say_team" : "say ", chat_buffer)); key_dest = key_game; chat_bufferlen = 0; chat_buffer[0] = 0; return; } // TODO add support for arrow keys and simple editing if (key == K_ESCAPE) { key_dest = key_game; chat_bufferlen = 0; chat_buffer[0] = 0; return; } if (key == K_BACKSPACE) { if (chat_bufferlen) { chat_bufferlen = (unsigned int)u8_prevbyte(chat_buffer, chat_bufferlen); chat_buffer[chat_bufferlen] = 0; } return; } if(key == K_TAB) { chat_bufferlen = Nicks_CompleteChatLine(chat_buffer, sizeof(chat_buffer), chat_bufferlen); return; } // ctrl+key generates an ascii value < 32 and shows a char from the charmap if (ascii > 0 && ascii < 32 && utf8_enable.integer) ascii = 0xE000 + ascii; if (chat_bufferlen == sizeof (chat_buffer) - 1) return; // all full if (!ascii) return; // non printable chat_bufferlen += u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1); //chat_buffer[chat_bufferlen++] = ascii; //chat_buffer[chat_bufferlen] = 0; } //============================================================================ /* =================== Returns a key number to be used to index keybindings[] by looking at the given string. Single ascii characters return themselves, while the K_* names are matched up. =================== */ int Key_StringToKeynum (const char *str) { const keyname_t *kn; if (!str || !str[0]) return -1; if (!str[1]) return tolower(str[0]); for (kn = keynames; kn->name; kn++) { if (!strcasecmp (str, kn->name)) return kn->keynum; } return -1; } /* =================== Returns a string (either a single ascii char, or a K_* name) for the given keynum. FIXME: handle quote special (general escape sequence?) =================== */ const char * Key_KeynumToString (int keynum, char *tinystr, size_t tinystrlength) { const keyname_t *kn; // -1 is an invalid code if (keynum < 0) return ""; // search overrides first, because some characters are special for (kn = keynames; kn->name; kn++) if (keynum == kn->keynum) return kn->name; // if it is printable, output it as a single character if (keynum > 32 && keynum < 256) { if (tinystrlength >= 2) { tinystr[0] = keynum; tinystr[1] = 0; } return tinystr; } // if it is not overridden and not printable, we don't know what to do with it return ""; } qboolean Key_SetBinding (int keynum, int bindmap, const char *binding) { char *newbinding; size_t l; if (keynum == -1 || keynum >= MAX_KEYS) return false; if ((bindmap < 0) || (bindmap >= MAX_BINDMAPS)) return false; // free old bindings if (keybindings[bindmap][keynum]) { Z_Free (keybindings[bindmap][keynum]); keybindings[bindmap][keynum] = NULL; } if(!binding[0]) // make "" binds be removed --blub return true; // allocate memory for new binding l = strlen (binding); newbinding = (char *)Z_Malloc (l + 1); memcpy (newbinding, binding, l + 1); newbinding[l] = 0; keybindings[bindmap][keynum] = newbinding; return true; } void Key_GetBindMap(int *fg, int *bg) { if(fg) *fg = key_bmap; if(bg) *bg = key_bmap2; } qboolean Key_SetBindMap(int fg, int bg) { if(fg >= MAX_BINDMAPS) return false; if(bg >= MAX_BINDMAPS) return false; if(fg >= 0) key_bmap = fg; if(bg >= 0) key_bmap2 = bg; return true; } static void Key_In_Unbind_f (void) { int b, m; char *errchar = NULL; if (Cmd_Argc () != 3) { Con_Print("in_unbind : remove commands from a key\n"); return; } m = strtol(Cmd_Argv (1), &errchar, 0); if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); return; } b = Key_StringToKeynum (Cmd_Argv (2)); if (b == -1) { Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); return; } if(!Key_SetBinding (b, m, "")) Con_Printf("Key_SetBinding failed for unknown reason\n"); } static void Key_In_Bind_f (void) { int i, c, b, m; char cmd[MAX_INPUTLINE]; char *errchar = NULL; c = Cmd_Argc (); if (c != 3 && c != 4) { Con_Print("in_bind [command] : attach a command to a key\n"); return; } m = strtol(Cmd_Argv (1), &errchar, 0); if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); return; } b = Key_StringToKeynum (Cmd_Argv (2)); if (b == -1 || b >= MAX_KEYS) { Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (2)); return; } if (c == 3) { if (keybindings[m][b]) Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (2), keybindings[m][b]); else Con_Printf("\"%s\" is not bound\n", Cmd_Argv (2)); return; } // copy the rest of the command line cmd[0] = 0; // start out with a null string for (i = 3; i < c; i++) { strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); if (i != (c - 1)) strlcat (cmd, " ", sizeof (cmd)); } if(!Key_SetBinding (b, m, cmd)) Con_Printf("Key_SetBinding failed for unknown reason\n"); } static void Key_In_Bindmap_f (void) { int m1, m2, c; char *errchar = NULL; c = Cmd_Argc (); if (c != 3) { Con_Print("in_bindmap : set current bindmap and fallback\n"); return; } m1 = strtol(Cmd_Argv (1), &errchar, 0); if ((m1 < 0) || (m1 >= MAX_BINDMAPS) || (errchar && *errchar)) { Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); return; } m2 = strtol(Cmd_Argv (2), &errchar, 0); if ((m2 < 0) || (m2 >= MAX_BINDMAPS) || (errchar && *errchar)) { Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(2)); return; } key_bmap = m1; key_bmap2 = m2; } static void Key_Unbind_f (void) { int b; if (Cmd_Argc () != 2) { Con_Print("unbind : remove commands from a key\n"); return; } b = Key_StringToKeynum (Cmd_Argv (1)); if (b == -1) { Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); return; } if(!Key_SetBinding (b, 0, "")) Con_Printf("Key_SetBinding failed for unknown reason\n"); } static void Key_Unbindall_f (void) { int i, j; for (j = 0; j < MAX_BINDMAPS; j++) for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) if (keybindings[j][i]) Key_SetBinding (i, j, ""); } static void Key_PrintBindList(int j) { char bindbuf[MAX_INPUTLINE]; char tinystr[2]; const char *p; int i; for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) { p = keybindings[j][i]; if (p) { Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); if (j == 0) Con_Printf("^2%s ^7= \"%s\"\n", Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); else Con_Printf("^3bindmap %d: ^2%s ^7= \"%s\"\n", j, Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); } } } static void Key_In_BindList_f (void) { int m; char *errchar = NULL; if(Cmd_Argc() >= 2) { m = strtol(Cmd_Argv(1), &errchar, 0); if ((m < 0) || (m >= MAX_BINDMAPS) || (errchar && *errchar)) { Con_Printf("%s isn't a valid bindmap\n", Cmd_Argv(1)); return; } Key_PrintBindList(m); } else { for (m = 0; m < MAX_BINDMAPS; m++) Key_PrintBindList(m); } } static void Key_BindList_f (void) { Key_PrintBindList(0); } static void Key_Bind_f (void) { int i, c, b; char cmd[MAX_INPUTLINE]; c = Cmd_Argc (); if (c != 2 && c != 3) { Con_Print("bind [command] : attach a command to a key\n"); return; } b = Key_StringToKeynum (Cmd_Argv (1)); if (b == -1 || b >= MAX_KEYS) { Con_Printf("\"%s\" isn't a valid key\n", Cmd_Argv (1)); return; } if (c == 2) { if (keybindings[0][b]) Con_Printf("\"%s\" = \"%s\"\n", Cmd_Argv (1), keybindings[0][b]); else Con_Printf("\"%s\" is not bound\n", Cmd_Argv (1)); return; } // copy the rest of the command line cmd[0] = 0; // start out with a null string for (i = 2; i < c; i++) { strlcat (cmd, Cmd_Argv (i), sizeof (cmd)); if (i != (c - 1)) strlcat (cmd, " ", sizeof (cmd)); } if(!Key_SetBinding (b, 0, cmd)) Con_Printf("Key_SetBinding failed for unknown reason\n"); } /* ============ Writes lines containing "bind key value" ============ */ void Key_WriteBindings (qfile_t *f) { int i, j; char bindbuf[MAX_INPUTLINE]; char tinystr[2]; const char *p; for (j = 0; j < MAX_BINDMAPS; j++) { for (i = 0; i < (int)(sizeof(keybindings[0])/sizeof(keybindings[0][0])); i++) { p = keybindings[j][i]; if (p) { Cmd_QuoteString(bindbuf, sizeof(bindbuf), p, "\"\\", false); // don't need to escape $ because cvars are not expanded inside bind if (j == 0) FS_Printf(f, "bind %s \"%s\"\n", Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); else FS_Printf(f, "in_bind %d %s \"%s\"\n", j, Key_KeynumToString (i, tinystr, sizeof(tinystr)), bindbuf); } } } } void Key_Init (void) { Key_History_Init(); key_line[0] = ']'; key_line[1] = 0; key_linepos = 1; // // register our functions // Cmd_AddCommand ("in_bind", Key_In_Bind_f, "binds a command to the specified key in the selected bindmap"); Cmd_AddCommand ("in_unbind", Key_In_Unbind_f, "removes command on the specified key in the selected bindmap"); Cmd_AddCommand ("in_bindlist", Key_In_BindList_f, "bindlist: displays bound keys for all bindmaps, or the given bindmap"); Cmd_AddCommand ("in_bindmap", Key_In_Bindmap_f, "selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing"); Cmd_AddCommand ("bind", Key_Bind_f, "binds a command to the specified key in bindmap 0"); Cmd_AddCommand ("unbind", Key_Unbind_f, "removes a command on the specified key in bindmap 0"); Cmd_AddCommand ("bindlist", Key_BindList_f, "bindlist: displays bound keys for bindmap 0 bindmaps"); Cmd_AddCommand ("unbindall", Key_Unbindall_f, "removes all commands from all keys in all bindmaps (leaving only shift-escape and escape)"); Cmd_AddCommand ("history", Key_History_f, "prints the history of executed commands (history X prints the last X entries, history -c clears the whole history)"); Cvar_RegisterVariable (&con_closeontoggleconsole); } void Key_Shutdown (void) { Key_History_Shutdown(); } const char *Key_GetBind (int key, int bindmap) { const char *bind; if (key < 0 || key >= MAX_KEYS) return NULL; if(bindmap >= MAX_BINDMAPS) return NULL; if(bindmap >= 0) { bind = keybindings[bindmap][key]; } else { bind = keybindings[key_bmap][key]; if (!bind) bind = keybindings[key_bmap2][key]; } return bind; } void Key_FindKeysForCommand (const char *command, int *keys, int numkeys, int bindmap) { int count; int j; const char *b; for (j = 0;j < numkeys;j++) keys[j] = -1; if(bindmap >= MAX_BINDMAPS) return; count = 0; for (j = 0; j < MAX_KEYS; ++j) { b = Key_GetBind(j, bindmap); if (!b) continue; if (!strcmp (b, command) ) { keys[count++] = j; if (count == numkeys) break; } } } /* =================== Called by the system between frames for both key up and key down events Should NOT be called during an interrupt! =================== */ static char tbl_keyascii[MAX_KEYS]; static keydest_t tbl_keydest[MAX_KEYS]; typedef struct eventqueueitem_s { int key; int ascii; qboolean down; } eventqueueitem_t; static int events_blocked = 0; static eventqueueitem_t eventqueue[32]; static unsigned eventqueue_idx = 0; static void Key_EventQueue_Add(int key, int ascii, qboolean down) { if(eventqueue_idx < sizeof(eventqueue) / sizeof(*eventqueue)) { eventqueue[eventqueue_idx].key = key; eventqueue[eventqueue_idx].ascii = ascii; eventqueue[eventqueue_idx].down = down; ++eventqueue_idx; } } void Key_EventQueue_Block(void) { // block key events until call to Unblock events_blocked = true; } void Key_EventQueue_Unblock(void) { // unblocks key events again unsigned i; events_blocked = false; for(i = 0; i < eventqueue_idx; ++i) Key_Event(eventqueue[i].key, eventqueue[i].ascii, eventqueue[i].down); eventqueue_idx = 0; } void Key_Event (int key, int ascii, qboolean down) { const char *bind; qboolean q; keydest_t keydest = key_dest; char vabuf[1024]; if (key < 0 || key >= MAX_KEYS) return; if(events_blocked) { Key_EventQueue_Add(key, ascii, down); return; } // get key binding bind = keybindings[key_bmap][key]; if (!bind) bind = keybindings[key_bmap2][key]; if (developer_insane.integer) Con_DPrintf("Key_Event(%i, '%c', %s) keydown %i bind \"%s\"\n", key, ascii ? ascii : '?', down ? "down" : "up", keydown[key], bind ? bind : ""); if(key_consoleactive) keydest = key_console; if (down) { // increment key repeat count each time a down is received so that things // which want to ignore key repeat can ignore it keydown[key] = min(keydown[key] + 1, 2); if(keydown[key] == 1) { tbl_keyascii[key] = ascii; tbl_keydest[key] = keydest; } else { ascii = tbl_keyascii[key]; keydest = tbl_keydest[key]; } } else { // clear repeat count now that the key is released keydown[key] = 0; keydest = tbl_keydest[key]; ascii = tbl_keyascii[key]; } if(keydest == key_void) return; // key_consoleactive is a flag not a key_dest because the console is a // high priority overlay ontop of the normal screen (designed as a safety // feature so that developers and users can rescue themselves from a bad // situation). // // this also means that toggling the console on/off does not lose the old // key_dest state // specially handle escape (togglemenu) and shift-escape (toggleconsole) // engine bindings, these are not handled as normal binds so that the user // can recover from a completely empty bindmap if (key == K_ESCAPE) { // ignore key repeats on escape if (keydown[key] > 1) return; // escape does these things: // key_consoleactive - close console // key_message - abort messagemode // key_menu - go to parent menu (or key_game) // key_game - open menu // in all modes shift-escape toggles console if (keydown[K_SHIFT]) { if(down) { Con_ToggleConsole_f (); tbl_keydest[key] = key_void; // esc release should go nowhere (especially not to key_menu or key_game) } return; } switch (keydest) { case key_console: if(down) { if(key_consoleactive & KEY_CONSOLEACTIVE_FORCED) { key_consoleactive &= ~KEY_CONSOLEACTIVE_USER; #ifdef CONFIG_MENU MR_ToggleMenu(1); #endif } else Con_ToggleConsole_f(); } break; case key_message: if (down) Key_Message (key, ascii); // that'll close the message input break; case key_menu: case key_menu_grabbed: #ifdef CONFIG_MENU MR_KeyEvent (key, ascii, down); #endif break; case key_game: // csqc has priority over toggle menu if it wants to (e.g. handling escape for UI stuff in-game.. :sick:) q = CL_VM_InputEvent(down ? 0 : 1, key, ascii); #ifdef CONFIG_MENU if (!q && down) MR_ToggleMenu(1); #endif break; default: Con_Printf ("Key_Event: Bad key_dest\n"); } return; } // send function keydowns to interpreter no matter what mode is (unless the menu has specifically grabbed the keyboard, for rebinding keys) // VorteX: Omnicide does bind F* keys if (keydest != key_menu_grabbed) if (key >= K_F1 && key <= K_F12 && gamemode != GAME_BLOODOMNICIDE) { if (bind) { if(keydown[key] == 1 && down) { // button commands add keynum as a parm if (bind[0] == '+') Cbuf_AddText (va(vabuf, sizeof(vabuf), "%s %i\n", bind, key)); else { Cbuf_AddText (bind); Cbuf_AddText ("\n"); } } else if(bind[0] == '+' && !down && keydown[key] == 0) Cbuf_AddText(va(vabuf, sizeof(vabuf), "-%s %i\n", bind + 1, key)); } return; } // send input to console if it wants it if (keydest == key_console) { if (!down) return; // con_closeontoggleconsole enables toggleconsole keys to close the // console, as long as they are not the color prefix character // (special exemption for german keyboard layouts) if (con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && (key_consoleactive & KEY_CONSOLEACTIVE_USER) && (con_closeontoggleconsole.integer >= ((ascii != STRING_COLOR_TAG) ? 2 : 3) || key_linepos == 1)) { Con_ToggleConsole_f (); return; } if (COM_CheckParm ("-noconsole")) return; // only allow the key bind to turn off console Key_Console (key, ascii); return; } // handle toggleconsole in menu too if (keydest == key_menu) { if (down && con_closeontoggleconsole.integer && bind && !strncmp(bind, "toggleconsole", strlen("toggleconsole")) && ascii != STRING_COLOR_TAG) { Cbuf_AddText("toggleconsole\n"); // Deferred to next frame so we're not sending the text event to the console. tbl_keydest[key] = key_void; // key release should go nowhere (especially not to key_menu or key_game) return; } } // ignore binds while a video is played, let the video system handle the key event if (cl_videoplaying) { if (gamemode == GAME_BLOODOMNICIDE) // menu controls key events #ifdef CONFIG_MENU MR_KeyEvent(key, ascii, down); #else { } #endif else CL_Video_KeyEvent (key, ascii, keydown[key] != 0); return; } // anything else is a key press into the game, chat line, or menu switch (keydest) { case key_message: if (down) Key_Message (key, ascii); break; case key_menu: case key_menu_grabbed: #ifdef CONFIG_MENU MR_KeyEvent (key, ascii, down); #endif break; case key_game: q = CL_VM_InputEvent(down ? 0 : 1, key, ascii); // ignore key repeats on binds and only send the bind if the event hasnt been already processed by csqc if (!q && bind) { if(keydown[key] == 1 && down) { // button commands add keynum as a parm if (bind[0] == '+') Cbuf_AddText (va(vabuf, sizeof(vabuf), "%s %i\n", bind, key)); else { Cbuf_AddText (bind); Cbuf_AddText ("\n"); } } else if(bind[0] == '+' && !down && keydown[key] == 0) Cbuf_AddText(va(vabuf, sizeof(vabuf), "-%s %i\n", bind + 1, key)); } break; default: Con_Printf ("Key_Event: Bad key_dest\n"); } } // a helper to simulate release of ALL keys void Key_ReleaseAll (void) { int key; // clear the event queue first eventqueue_idx = 0; // then send all down events (possibly into the event queue) for(key = 0; key < MAX_KEYS; ++key) if(keydown[key]) Key_Event(key, 0, false); // now all keys are guaranteed down (once the event queue is unblocked) // and only future events count } /* =================== Key_ClearStates =================== */ void Key_ClearStates (void) { memset(keydown, 0, sizeof(keydown)); } darkplaces/snd_main.c0000664000175000017500000023022513067716222014125 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // snd_main.c -- main control for any streaming sound output device #include "quakedef.h" #include "snd_main.h" #include "snd_ogg.h" #include "csprogs.h" #include "cl_collision.h" #ifdef CONFIG_CD #include "cdaudio.h" #endif #define SND_MIN_SPEED 8000 #define SND_MAX_SPEED 192000 #define SND_MIN_WIDTH 1 #define SND_MAX_WIDTH 2 #define SND_MIN_CHANNELS 1 #define SND_MAX_CHANNELS 8 #if SND_LISTENERS != 8 # error this data only supports up to 8 channel, update it! #endif speakerlayout_t snd_speakerlayout; // Our speaker layouts are based on ALSA. They differ from those // Win32 and Mac OS X APIs use when there's more than 4 channels. // (rear left + rear right, and front center + LFE are swapped). #define SND_SPEAKERLAYOUTS (sizeof(snd_speakerlayouts) / sizeof(snd_speakerlayouts[0])) static const speakerlayout_t snd_speakerlayouts[] = { { "surround71", 8, { {0, 45, 0.2f, 0.2f, 0.5f}, // front left {1, 315, 0.2f, 0.2f, 0.5f}, // front right {2, 135, 0.2f, 0.2f, 0.5f}, // rear left {3, 225, 0.2f, 0.2f, 0.5f}, // rear right {4, 0, 0.2f, 0.2f, 0.5f}, // front center {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) {6, 90, 0.2f, 0.2f, 0.5f}, // side left {7, 180, 0.2f, 0.2f, 0.5f}, // side right } }, { "surround51", 6, { {0, 45, 0.2f, 0.2f, 0.5f}, // front left {1, 315, 0.2f, 0.2f, 0.5f}, // front right {2, 135, 0.2f, 0.2f, 0.5f}, // rear left {3, 225, 0.2f, 0.2f, 0.5f}, // rear right {4, 0, 0.2f, 0.2f, 0.5f}, // front center {5, 0, 0, 0, 0}, // lfe (we don't have any good lfe sound sources and it would take some filtering work to generate them (and they'd probably still be wrong), so... no lfe) {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, } }, { // these systems sometimes have a subwoofer as well, but it has no // channel of its own "surround40", 4, { {0, 45, 0.3f, 0.3f, 0.8f}, // front left {1, 315, 0.3f, 0.3f, 0.8f}, // front right {2, 135, 0.3f, 0.3f, 0.8f}, // rear left {3, 225, 0.3f, 0.3f, 0.8f}, // rear right {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, } }, { // these systems sometimes have a subwoofer as well, but it has no // channel of its own "stereo", 2, { {0, 90, 0.5f, 0.5f, 1}, // side left {1, 270, 0.5f, 0.5f, 1}, // side right {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, } }, { "mono", 1, { {0, 0, 0, 1, 1}, // center {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 0, 0}, } } }; // ======================================================================= // Internal sound data & structures // ======================================================================= channel_t channels[MAX_CHANNELS]; unsigned int total_channels; snd_ringbuffer_t *snd_renderbuffer = NULL; static unsigned int soundtime = 0; static unsigned int oldpaintedtime = 0; static unsigned int extrasoundtime = 0; static double snd_starttime = 0.0; qboolean snd_threaded = false; qboolean snd_usethreadedmixing = false; vec3_t listener_origin; matrix4x4_t listener_basematrix; static unsigned char *listener_pvs = NULL; static int listener_pvsbytes = 0; matrix4x4_t listener_matrix[SND_LISTENERS]; mempool_t *snd_mempool; // Linked list of known sfx static sfx_t *known_sfx = NULL; static qboolean sound_spatialized = false; qboolean simsound = false; static qboolean recording_sound = false; int snd_blocked = 0; static int current_swapstereo = false; static int current_channellayout = SND_CHANNELLAYOUT_AUTO; static int current_channellayout_used = SND_CHANNELLAYOUT_AUTO; static float spatialpower, spatialmin, spatialdiff, spatialoffset, spatialfactor; typedef enum { SPATIAL_NONE, SPATIAL_LOG, SPATIAL_POW, SPATIAL_THRESH } spatialmethod_t; spatialmethod_t spatialmethod; // Cvars declared in sound.h (part of the sound API) cvar_t bgmvolume = {CVAR_SAVE, "bgmvolume", "1", "volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg)"}; cvar_t mastervolume = {CVAR_SAVE, "mastervolume", "0.7", "master volume"}; cvar_t volume = {CVAR_SAVE, "volume", "0.7", "volume of sound effects"}; cvar_t snd_initialized = { CVAR_READONLY, "snd_initialized", "0", "indicates the sound subsystem is active"}; cvar_t snd_staticvolume = {CVAR_SAVE, "snd_staticvolume", "1", "volume of ambient sound effects (such as swampy sounds at the start of e1m2)"}; cvar_t snd_soundradius = {CVAR_SAVE, "snd_soundradius", "1200", "radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius)"}; cvar_t snd_attenuation_exponent = {CVAR_SAVE, "snd_attenuation_exponent", "1", "Exponent of (1-radius) in sound attenuation formula"}; cvar_t snd_attenuation_decibel = {CVAR_SAVE, "snd_attenuation_decibel", "0", "Decibel sound attenuation per sound radius distance"}; cvar_t snd_spatialization_min_radius = {CVAR_SAVE, "snd_spatialization_min_radius", "10000", "use minimum spatialization above to this radius"}; cvar_t snd_spatialization_max_radius = {CVAR_SAVE, "snd_spatialization_max_radius", "100", "use maximum spatialization below this radius"}; cvar_t snd_spatialization_min = {CVAR_SAVE, "snd_spatialization_min", "0.70", "minimum spatializazion of sounds"}; cvar_t snd_spatialization_max = {CVAR_SAVE, "snd_spatialization_max", "0.95", "maximum spatialization of sounds"}; cvar_t snd_spatialization_power = {CVAR_SAVE, "snd_spatialization_power", "0", "exponent of the spatialization falloff curve (0: logarithmic)"}; cvar_t snd_spatialization_control = {CVAR_SAVE, "snd_spatialization_control", "0", "enable spatialization control (headphone friendly mode)"}; cvar_t snd_spatialization_prologic = {CVAR_SAVE, "snd_spatialization_prologic", "0", "use dolby prologic (I, II or IIx) encoding (snd_channels must be 2)"}; cvar_t snd_spatialization_prologic_frontangle = {CVAR_SAVE, "snd_spatialization_prologic_frontangle", "30", "the angle between the front speakers and the center speaker"}; cvar_t snd_spatialization_occlusion = {CVAR_SAVE, "snd_spatialization_occlusion", "1", "enable occlusion testing on spatialized sounds, which simply quiets sounds that are blocked by the world; 1 enables PVS method, 2 enables LineOfSight method, 3 enables both"}; // Cvars declared in snd_main.h (shared with other snd_*.c files) cvar_t _snd_mixahead = {CVAR_SAVE, "_snd_mixahead", "0.15", "how much sound to mix ahead of time"}; cvar_t snd_streaming = { CVAR_SAVE, "snd_streaming", "1", "enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory); when set to 2, streaming is performed even if this would waste memory"}; cvar_t snd_streaming_length = { CVAR_SAVE, "snd_streaming_length", "1", "decompress sounds completely if they are less than this play time when snd_streaming is 1"}; cvar_t snd_swapstereo = {CVAR_SAVE, "snd_swapstereo", "0", "swaps left/right speakers for old ISA soundblaster cards"}; extern cvar_t v_flipped; cvar_t snd_channellayout = {0, "snd_channellayout", "0", "channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout)"}; cvar_t snd_mutewhenidle = {CVAR_SAVE, "snd_mutewhenidle", "1", "whether to disable sound output when game window is inactive"}; cvar_t snd_maxchannelvolume = {CVAR_SAVE, "snd_maxchannelvolume", "10", "maximum volume of a single sound"}; cvar_t snd_softclip = {CVAR_SAVE, "snd_softclip", "0", "Use soft-clipping. Soft-clipping can make the sound more smooth if very high volume levels are used. Enable this option if the dynamic range of the loudspeakers is very low. WARNING: This feature creates distortion and should be considered a last resort."}; //cvar_t snd_softclip = {CVAR_SAVE, "snd_softclip", "0", "Use soft-clipping (when set to 2, use it even if output is floating point). Soft-clipping can make the sound more smooth if very high volume levels are used. Enable this option if the dynamic range of the loudspeakers is very low. WARNING: This feature creates distortion and should be considered a last resort."}; cvar_t snd_entchannel0volume = {CVAR_SAVE, "snd_entchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel1volume = {CVAR_SAVE, "snd_entchannel1volume", "1", "volume multiplier of the 1st entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel2volume = {CVAR_SAVE, "snd_entchannel2volume", "1", "volume multiplier of the 2nd entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel3volume = {CVAR_SAVE, "snd_entchannel3volume", "1", "volume multiplier of the 3rd entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel4volume = {CVAR_SAVE, "snd_entchannel4volume", "1", "volume multiplier of the 4th entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel5volume = {CVAR_SAVE, "snd_entchannel5volume", "1", "volume multiplier of the 5th entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel6volume = {CVAR_SAVE, "snd_entchannel6volume", "1", "volume multiplier of the 6th entity channel of regular entities (DEPRECATED)"}; cvar_t snd_entchannel7volume = {CVAR_SAVE, "snd_entchannel7volume", "1", "volume multiplier of the 7th entity channel of regular entities (DEPRECATED)"}; cvar_t snd_playerchannel0volume = {CVAR_SAVE, "snd_playerchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel1volume = {CVAR_SAVE, "snd_playerchannel1volume", "1", "volume multiplier of the 1st entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel2volume = {CVAR_SAVE, "snd_playerchannel2volume", "1", "volume multiplier of the 2nd entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel3volume = {CVAR_SAVE, "snd_playerchannel3volume", "1", "volume multiplier of the 3rd entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel4volume = {CVAR_SAVE, "snd_playerchannel4volume", "1", "volume multiplier of the 4th entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel5volume = {CVAR_SAVE, "snd_playerchannel5volume", "1", "volume multiplier of the 5th entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel6volume = {CVAR_SAVE, "snd_playerchannel6volume", "1", "volume multiplier of the 6th entity channel of player entities (DEPRECATED)"}; cvar_t snd_playerchannel7volume = {CVAR_SAVE, "snd_playerchannel7volume", "1", "volume multiplier of the 7th entity channel of player entities (DEPRECATED)"}; cvar_t snd_worldchannel0volume = {CVAR_SAVE, "snd_worldchannel0volume", "1", "volume multiplier of the auto-allocate entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel1volume = {CVAR_SAVE, "snd_worldchannel1volume", "1", "volume multiplier of the 1st entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel2volume = {CVAR_SAVE, "snd_worldchannel2volume", "1", "volume multiplier of the 2nd entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel3volume = {CVAR_SAVE, "snd_worldchannel3volume", "1", "volume multiplier of the 3rd entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel4volume = {CVAR_SAVE, "snd_worldchannel4volume", "1", "volume multiplier of the 4th entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel5volume = {CVAR_SAVE, "snd_worldchannel5volume", "1", "volume multiplier of the 5th entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel6volume = {CVAR_SAVE, "snd_worldchannel6volume", "1", "volume multiplier of the 6th entity channel of the world entity (DEPRECATED)"}; cvar_t snd_worldchannel7volume = {CVAR_SAVE, "snd_worldchannel7volume", "1", "volume multiplier of the 7th entity channel of the world entity (DEPRECATED)"}; cvar_t snd_csqcchannel0volume = {CVAR_SAVE, "snd_csqcchannel0volume", "1", "volume multiplier of the auto-allocate entity channel CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel1volume = {CVAR_SAVE, "snd_csqcchannel1volume", "1", "volume multiplier of the 1st entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel2volume = {CVAR_SAVE, "snd_csqcchannel2volume", "1", "volume multiplier of the 2nd entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel3volume = {CVAR_SAVE, "snd_csqcchannel3volume", "1", "volume multiplier of the 3rd entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel4volume = {CVAR_SAVE, "snd_csqcchannel4volume", "1", "volume multiplier of the 4th entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel5volume = {CVAR_SAVE, "snd_csqcchannel5volume", "1", "volume multiplier of the 5th entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel6volume = {CVAR_SAVE, "snd_csqcchannel6volume", "1", "volume multiplier of the 6th entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_csqcchannel7volume = {CVAR_SAVE, "snd_csqcchannel7volume", "1", "volume multiplier of the 7th entity channel of CSQC entities (DEPRECATED)"}; cvar_t snd_channel0volume = {CVAR_SAVE, "snd_channel0volume", "1", "volume multiplier of the auto-allocate entity channel"}; cvar_t snd_channel1volume = {CVAR_SAVE, "snd_channel1volume", "1", "volume multiplier of the 1st entity channel"}; cvar_t snd_channel2volume = {CVAR_SAVE, "snd_channel2volume", "1", "volume multiplier of the 2nd entity channel"}; cvar_t snd_channel3volume = {CVAR_SAVE, "snd_channel3volume", "1", "volume multiplier of the 3rd entity channel"}; cvar_t snd_channel4volume = {CVAR_SAVE, "snd_channel4volume", "1", "volume multiplier of the 4th entity channel"}; cvar_t snd_channel5volume = {CVAR_SAVE, "snd_channel5volume", "1", "volume multiplier of the 5th entity channel"}; cvar_t snd_channel6volume = {CVAR_SAVE, "snd_channel6volume", "1", "volume multiplier of the 6th entity channel"}; cvar_t snd_channel7volume = {CVAR_SAVE, "snd_channel7volume", "1", "volume multiplier of the 7th entity channel"}; // Local cvars static cvar_t nosound = {0, "nosound", "0", "disables sound"}; static cvar_t snd_precache = {0, "snd_precache", "1", "loads sounds before they are used"}; static cvar_t ambient_level = {0, "ambient_level", "0.3", "volume of environment noises (water and wind)"}; static cvar_t ambient_fade = {0, "ambient_fade", "100", "rate of volume fading when moving from one environment to another"}; static cvar_t snd_noextraupdate = {0, "snd_noextraupdate", "0", "disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates"}; static cvar_t snd_show = {0, "snd_show", "0", "shows some statistics about sound mixing"}; // Default sound format is 48KHz, 16-bit, stereo // (48KHz because a lot of onboard sound cards sucks at any other speed) static cvar_t snd_speed = {CVAR_SAVE, "snd_speed", "48000", "sound output frequency, in hertz"}; static cvar_t snd_width = {CVAR_SAVE, "snd_width", "2", "sound output precision, in bytes (1 and 2 supported)"}; static cvar_t snd_channels = {CVAR_SAVE, "snd_channels", "2", "number of channels for the sound output (2 for stereo; up to 8 supported for 3D sound)"}; static cvar_t snd_startloopingsounds = {0, "snd_startloopingsounds", "1", "whether to start sounds that would loop (you want this to be 1); existing sounds are not affected"}; static cvar_t snd_startnonloopingsounds = {0, "snd_startnonloopingsounds", "1", "whether to start sounds that would not loop (you want this to be 1); existing sounds are not affected"}; // randomization static cvar_t snd_identicalsoundrandomization_time = {0, "snd_identicalsoundrandomization_time", "0.1", "how much seconds to randomly skip (positive) or delay (negative) sounds when multiple identical sounds are started on the same frame"}; static cvar_t snd_identicalsoundrandomization_tics = {0, "snd_identicalsoundrandomization_tics", "0", "if nonzero, how many tics to limit sound randomization as defined by snd_identicalsoundrandomization_time"}; // Ambient sounds static sfx_t* ambient_sfxs [2] = { NULL, NULL }; static const char* ambient_names [2] = { "sound/ambience/water1.wav", "sound/ambience/wind2.wav" }; // ==================================================================== // Functions // ==================================================================== void S_FreeSfx (sfx_t *sfx, qboolean force); static void S_Play_Common (float fvol, float attenuation) { int i, ch_ind; char name [MAX_QPATH]; sfx_t *sfx; i = 1; while (i < Cmd_Argc ()) { // Get the name, and appends ".wav" as an extension if there's none strlcpy (name, Cmd_Argv (i), sizeof (name)); if (!strrchr (name, '.')) strlcat (name, ".wav", sizeof (name)); i++; // If we need to get the volume from the command line if (fvol == -1.0f) { fvol = atof (Cmd_Argv (i)); i++; } sfx = S_PrecacheSound (name, true, true); if (sfx) { ch_ind = S_StartSound (-1, 0, sfx, listener_origin, fvol, attenuation); // Free the sfx if the file didn't exist if (!sfx->fetcher) S_FreeSfx (sfx, false); else channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; } } } static void S_Play_f(void) { S_Play_Common (1.0f, 1.0f); } static void S_Play2_f(void) { S_Play_Common (1.0f, 0.0f); } static void S_PlayVol_f(void) { S_Play_Common (-1.0f, 0.0f); } static void S_SoundList_f (void) { unsigned int i; sfx_t *sfx; unsigned int total; total = 0; for (sfx = known_sfx, i = 0; sfx != NULL; sfx = sfx->next, i++) { if (sfx->fetcher != NULL) { unsigned int size; size = (unsigned int)sfx->memsize; Con_Printf ("%c%c%c(%5iHz %2db %6s) %8i : %s\n", (sfx->loopstart < sfx->total_length) ? 'L' : ' ', (sfx->flags & SFXFLAG_STREAMED) ? 'S' : ' ', (sfx->flags & SFXFLAG_MENUSOUND) ? 'P' : ' ', sfx->format.speed, sfx->format.width * 8, (sfx->format.channels == 1) ? "mono" : "stereo", size, sfx->name); total += size; } else Con_Printf (" ( unknown ) unloaded : %s\n", sfx->name); } Con_Printf("Total resident: %i\n", total); } static void S_SoundInfo_f(void) { if (snd_renderbuffer == NULL) { Con_Print("sound system not started\n"); return; } Con_Printf("%5d speakers\n", snd_renderbuffer->format.channels); Con_Printf("%5d frames\n", snd_renderbuffer->maxframes); Con_Printf("%5d samplebits\n", snd_renderbuffer->format.width * 8); Con_Printf("%5d speed\n", snd_renderbuffer->format.speed); Con_Printf("%5u total_channels\n", total_channels); } int S_GetSoundRate(void) { return snd_renderbuffer ? snd_renderbuffer->format.speed : 0; } int S_GetSoundChannels(void) { return snd_renderbuffer ? snd_renderbuffer->format.channels : 0; } static qboolean S_ChooseCheaperFormat (snd_format_t* format, qboolean fixed_speed, qboolean fixed_width, qboolean fixed_channels) { static const snd_format_t thresholds [] = { // speed width channels { SND_MIN_SPEED, SND_MIN_WIDTH, SND_MIN_CHANNELS }, { 11025, 1, 2 }, { 22050, 2, 2 }, { 44100, 2, 2 }, { 48000, 2, 6 }, { 96000, 2, 6 }, { SND_MAX_SPEED, SND_MAX_WIDTH, SND_MAX_CHANNELS }, }; const unsigned int nb_thresholds = sizeof(thresholds) / sizeof(thresholds[0]); unsigned int speed_level, width_level, channels_level; // If we have reached the minimum values, there's nothing more we can do if ((format->speed == thresholds[0].speed || fixed_speed) && (format->width == thresholds[0].width || fixed_width) && (format->channels == thresholds[0].channels || fixed_channels)) return false; // Check the min and max values #define CHECK_BOUNDARIES(param) \ if (format->param < thresholds[0].param) \ { \ format->param = thresholds[0].param; \ return true; \ } \ if (format->param > thresholds[nb_thresholds - 1].param) \ { \ format->param = thresholds[nb_thresholds - 1].param; \ return true; \ } CHECK_BOUNDARIES(speed); CHECK_BOUNDARIES(width); CHECK_BOUNDARIES(channels); #undef CHECK_BOUNDARIES // Find the level of each parameter #define FIND_LEVEL(param) \ param##_level = 0; \ while (param##_level < nb_thresholds - 1) \ { \ if (format->param <= thresholds[param##_level].param) \ break; \ \ param##_level++; \ } FIND_LEVEL(speed); FIND_LEVEL(width); FIND_LEVEL(channels); #undef FIND_LEVEL // Decrease the parameter with the highest level to the previous level if (channels_level >= speed_level && channels_level >= width_level && !fixed_channels) { format->channels = thresholds[channels_level - 1].channels; return true; } if (speed_level >= width_level && !fixed_speed) { format->speed = thresholds[speed_level - 1].speed; return true; } format->width = thresholds[width_level - 1].width; return true; } #define SWAP_LISTENERS(l1, l2, tmpl) { tmpl = (l1); (l1) = (l2); (l2) = tmpl; } static void S_SetChannelLayout (void) { unsigned int i; listener_t swaplistener; listener_t *listeners; int layout; for (i = 0; i < SND_SPEAKERLAYOUTS; i++) if (snd_speakerlayouts[i].channels == snd_renderbuffer->format.channels) break; if (i >= SND_SPEAKERLAYOUTS) { Con_Printf("S_SetChannelLayout: can't find the speaker layout for %hu channels. Defaulting to mono output\n", snd_renderbuffer->format.channels); i = SND_SPEAKERLAYOUTS - 1; } snd_speakerlayout = snd_speakerlayouts[i]; listeners = snd_speakerlayout.listeners; // Swap the left and right channels if snd_swapstereo is set if (boolxor(snd_swapstereo.integer, v_flipped.integer)) { switch (snd_speakerlayout.channels) { case 8: SWAP_LISTENERS(listeners[6], listeners[7], swaplistener); // no break case 4: case 6: SWAP_LISTENERS(listeners[2], listeners[3], swaplistener); // no break case 2: SWAP_LISTENERS(listeners[0], listeners[1], swaplistener); break; default: case 1: // Nothing to do break; } } // Sanity check if (snd_channellayout.integer < SND_CHANNELLAYOUT_AUTO || snd_channellayout.integer > SND_CHANNELLAYOUT_ALSA) Cvar_SetValueQuick (&snd_channellayout, SND_CHANNELLAYOUT_STANDARD); if (snd_channellayout.integer == SND_CHANNELLAYOUT_AUTO) { // If we're in the sound engine initialization if (current_channellayout_used == SND_CHANNELLAYOUT_AUTO) { layout = SND_CHANNELLAYOUT_STANDARD; Cvar_SetValueQuick (&snd_channellayout, layout); } else layout = current_channellayout_used; } else layout = snd_channellayout.integer; // Convert our layout (= ALSA) to the standard layout if necessary if (snd_speakerlayout.channels == 6 || snd_speakerlayout.channels == 8) { if (layout == SND_CHANNELLAYOUT_STANDARD) { SWAP_LISTENERS(listeners[2], listeners[4], swaplistener); SWAP_LISTENERS(listeners[3], listeners[5], swaplistener); } Con_Printf("S_SetChannelLayout: using %s speaker layout for 3D sound\n", (layout == SND_CHANNELLAYOUT_ALSA) ? "ALSA" : "standard"); } current_swapstereo = boolxor(snd_swapstereo.integer, v_flipped.integer); current_channellayout = snd_channellayout.integer; current_channellayout_used = layout; } void S_Startup (void) { qboolean fixed_speed, fixed_width, fixed_channels; snd_format_t chosen_fmt; static snd_format_t prev_render_format = {0, 0, 0}; char* env; #if _MSC_VER >= 1400 size_t envlen; #endif int i; if (!snd_initialized.integer) return; fixed_speed = false; fixed_width = false; fixed_channels = false; // Get the starting sound format from the cvars chosen_fmt.speed = snd_speed.integer; chosen_fmt.width = snd_width.integer; chosen_fmt.channels = snd_channels.integer; // Check the environment variables to see if the player wants a particular sound format #if _MSC_VER >= 1400 _dupenv_s(&env, &envlen, "QUAKE_SOUND_CHANNELS"); #else env = getenv("QUAKE_SOUND_CHANNELS"); #endif if (env != NULL) { chosen_fmt.channels = atoi (env); #if _MSC_VER >= 1400 free(env); #endif fixed_channels = true; } #if _MSC_VER >= 1400 _dupenv_s(&env, &envlen, "QUAKE_SOUND_SPEED"); #else env = getenv("QUAKE_SOUND_SPEED"); #endif if (env != NULL) { chosen_fmt.speed = atoi (env); #if _MSC_VER >= 1400 free(env); #endif fixed_speed = true; } #if _MSC_VER >= 1400 _dupenv_s(&env, &envlen, "QUAKE_SOUND_SAMPLEBITS"); #else env = getenv("QUAKE_SOUND_SAMPLEBITS"); #endif if (env != NULL) { chosen_fmt.width = atoi (env) / 8; #if _MSC_VER >= 1400 free(env); #endif fixed_width = true; } // Parse the command line to see if the player wants a particular sound format // COMMANDLINEOPTION: Sound: -sndquad sets sound output to 4 channel surround if (COM_CheckParm ("-sndquad") != 0) { chosen_fmt.channels = 4; fixed_channels = true; } // COMMANDLINEOPTION: Sound: -sndstereo sets sound output to stereo else if (COM_CheckParm ("-sndstereo") != 0) { chosen_fmt.channels = 2; fixed_channels = true; } // COMMANDLINEOPTION: Sound: -sndmono sets sound output to mono else if (COM_CheckParm ("-sndmono") != 0) { chosen_fmt.channels = 1; fixed_channels = true; } // COMMANDLINEOPTION: Sound: -sndspeed chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) i = COM_CheckParm ("-sndspeed"); if (0 < i && i < com_argc - 1) { chosen_fmt.speed = atoi (com_argv[i + 1]); fixed_speed = true; } // COMMANDLINEOPTION: Sound: -sndbits chooses 8 bit or 16 bit sound output i = COM_CheckParm ("-sndbits"); if (0 < i && i < com_argc - 1) { chosen_fmt.width = atoi (com_argv[i + 1]) / 8; fixed_width = true; } #if 0 // LordHavoc: now you can with the resampler... // You can't change sound speed after start time (not yet supported) if (prev_render_format.speed != 0) { fixed_speed = true; if (chosen_fmt.speed != prev_render_format.speed) { Con_Printf("S_Startup: sound speed has changed! This is NOT supported yet. Falling back to previous speed (%u Hz)\n", prev_render_format.speed); chosen_fmt.speed = prev_render_format.speed; } } #endif // Sanity checks if (chosen_fmt.speed < SND_MIN_SPEED) { chosen_fmt.speed = SND_MIN_SPEED; fixed_speed = false; } else if (chosen_fmt.speed > SND_MAX_SPEED) { chosen_fmt.speed = SND_MAX_SPEED; fixed_speed = false; } if (chosen_fmt.width < SND_MIN_WIDTH) { chosen_fmt.width = SND_MIN_WIDTH; fixed_width = false; } else if (chosen_fmt.width > SND_MAX_WIDTH) { chosen_fmt.width = SND_MAX_WIDTH; fixed_width = false; } if (chosen_fmt.channels < SND_MIN_CHANNELS) { chosen_fmt.channels = SND_MIN_CHANNELS; fixed_channels = false; } else if (chosen_fmt.channels > SND_MAX_CHANNELS) { chosen_fmt.channels = SND_MAX_CHANNELS; fixed_channels = false; } // create the sound buffer used for sumitting the samples to the plaform-dependent module if (!simsound) { snd_format_t suggest_fmt; qboolean accepted; accepted = false; do { Con_Printf("S_Startup: initializing sound output format: %dHz, %d bit, %d channels...\n", chosen_fmt.speed, chosen_fmt.width * 8, chosen_fmt.channels); memset(&suggest_fmt, 0, sizeof(suggest_fmt)); accepted = SndSys_Init(&chosen_fmt, &suggest_fmt); if (!accepted) { Con_Printf("S_Startup: sound output initialization FAILED\n"); // If the module is suggesting another one if (suggest_fmt.speed != 0) { memcpy(&chosen_fmt, &suggest_fmt, sizeof(chosen_fmt)); Con_Printf (" Driver has suggested %dHz, %d bit, %d channels. Retrying...\n", suggest_fmt.speed, suggest_fmt.width * 8, suggest_fmt.channels); } // Else, try to find a less resource-demanding format else if (!S_ChooseCheaperFormat (&chosen_fmt, fixed_speed, fixed_width, fixed_channels)) break; } } while (!accepted); // If we haven't found a suitable format if (!accepted) { Con_Print("S_Startup: SndSys_Init failed.\n"); sound_spatialized = false; return; } } else { snd_renderbuffer = Snd_CreateRingBuffer(&chosen_fmt, 0, NULL); Con_Print ("S_Startup: simulating sound output\n"); } memcpy(&prev_render_format, &snd_renderbuffer->format, sizeof(prev_render_format)); Con_Printf("Sound format: %dHz, %d channels, %d bits per sample\n", chosen_fmt.speed, chosen_fmt.channels, chosen_fmt.width * 8); // Update the cvars if (snd_speed.integer != (int)chosen_fmt.speed) Cvar_SetValueQuick(&snd_speed, chosen_fmt.speed); if (snd_width.integer != chosen_fmt.width) Cvar_SetValueQuick(&snd_width, chosen_fmt.width); if (snd_channels.integer != chosen_fmt.channels) Cvar_SetValueQuick(&snd_channels, chosen_fmt.channels); current_channellayout_used = SND_CHANNELLAYOUT_AUTO; S_SetChannelLayout(); snd_starttime = realtime; // If the sound module has already run, add an extra time to make sure // the sound time doesn't decrease, to not confuse playing SFXs if (oldpaintedtime != 0) { // The extra time must be a multiple of the render buffer size // to avoid modifying the current position in the buffer, // some modules write directly to a shared (DMA) buffer extrasoundtime = oldpaintedtime + snd_renderbuffer->maxframes - 1; extrasoundtime -= extrasoundtime % snd_renderbuffer->maxframes; Con_Printf("S_Startup: extra sound time = %u\n", extrasoundtime); soundtime = extrasoundtime; } else extrasoundtime = 0; snd_renderbuffer->startframe = soundtime; snd_renderbuffer->endframe = soundtime; recording_sound = false; } void S_Shutdown(void) { if (snd_renderbuffer == NULL) return; oldpaintedtime = snd_renderbuffer->endframe; if (simsound) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } else SndSys_Shutdown(); sound_spatialized = false; } static void S_Restart_f(void) { // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) // So, refuse to do this if we are connected. if(cls.state == ca_connected) { Con_Printf("snd_restart would wreak havoc if you do that while connected!\n"); return; } S_Shutdown(); S_Startup(); } /* ================ S_Init ================ */ void S_Init(void) { Cvar_RegisterVariable(&volume); Cvar_RegisterVariable(&bgmvolume); Cvar_RegisterVariable(&mastervolume); Cvar_RegisterVariable(&snd_staticvolume); Cvar_RegisterVariable(&snd_entchannel0volume); Cvar_RegisterVariable(&snd_entchannel1volume); Cvar_RegisterVariable(&snd_entchannel2volume); Cvar_RegisterVariable(&snd_entchannel3volume); Cvar_RegisterVariable(&snd_entchannel4volume); Cvar_RegisterVariable(&snd_entchannel5volume); Cvar_RegisterVariable(&snd_entchannel6volume); Cvar_RegisterVariable(&snd_entchannel7volume); Cvar_RegisterVariable(&snd_worldchannel0volume); Cvar_RegisterVariable(&snd_worldchannel1volume); Cvar_RegisterVariable(&snd_worldchannel2volume); Cvar_RegisterVariable(&snd_worldchannel3volume); Cvar_RegisterVariable(&snd_worldchannel4volume); Cvar_RegisterVariable(&snd_worldchannel5volume); Cvar_RegisterVariable(&snd_worldchannel6volume); Cvar_RegisterVariable(&snd_worldchannel7volume); Cvar_RegisterVariable(&snd_playerchannel0volume); Cvar_RegisterVariable(&snd_playerchannel1volume); Cvar_RegisterVariable(&snd_playerchannel2volume); Cvar_RegisterVariable(&snd_playerchannel3volume); Cvar_RegisterVariable(&snd_playerchannel4volume); Cvar_RegisterVariable(&snd_playerchannel5volume); Cvar_RegisterVariable(&snd_playerchannel6volume); Cvar_RegisterVariable(&snd_playerchannel7volume); Cvar_RegisterVariable(&snd_csqcchannel0volume); Cvar_RegisterVariable(&snd_csqcchannel1volume); Cvar_RegisterVariable(&snd_csqcchannel2volume); Cvar_RegisterVariable(&snd_csqcchannel3volume); Cvar_RegisterVariable(&snd_csqcchannel4volume); Cvar_RegisterVariable(&snd_csqcchannel5volume); Cvar_RegisterVariable(&snd_csqcchannel6volume); Cvar_RegisterVariable(&snd_csqcchannel7volume); Cvar_RegisterVariable(&snd_channel0volume); Cvar_RegisterVariable(&snd_channel1volume); Cvar_RegisterVariable(&snd_channel2volume); Cvar_RegisterVariable(&snd_channel3volume); Cvar_RegisterVariable(&snd_channel4volume); Cvar_RegisterVariable(&snd_channel5volume); Cvar_RegisterVariable(&snd_channel6volume); Cvar_RegisterVariable(&snd_channel7volume); Cvar_RegisterVariable(&snd_attenuation_exponent); Cvar_RegisterVariable(&snd_attenuation_decibel); Cvar_RegisterVariable(&snd_spatialization_min_radius); Cvar_RegisterVariable(&snd_spatialization_max_radius); Cvar_RegisterVariable(&snd_spatialization_min); Cvar_RegisterVariable(&snd_spatialization_max); Cvar_RegisterVariable(&snd_spatialization_power); Cvar_RegisterVariable(&snd_spatialization_control); Cvar_RegisterVariable(&snd_spatialization_occlusion); Cvar_RegisterVariable(&snd_spatialization_prologic); Cvar_RegisterVariable(&snd_spatialization_prologic_frontangle); Cvar_RegisterVariable(&snd_speed); Cvar_RegisterVariable(&snd_width); Cvar_RegisterVariable(&snd_channels); Cvar_RegisterVariable(&snd_mutewhenidle); Cvar_RegisterVariable(&snd_maxchannelvolume); Cvar_RegisterVariable(&snd_softclip); Cvar_RegisterVariable(&snd_startloopingsounds); Cvar_RegisterVariable(&snd_startnonloopingsounds); Cvar_RegisterVariable(&snd_identicalsoundrandomization_time); Cvar_RegisterVariable(&snd_identicalsoundrandomization_tics); // COMMANDLINEOPTION: Sound: -nosound disables sound (including CD audio) if (COM_CheckParm("-nosound")) { // dummy out Play and Play2 because mods stuffcmd that Cmd_AddCommand("play", Host_NoOperation_f, "does nothing because -nosound was specified"); Cmd_AddCommand("play2", Host_NoOperation_f, "does nothing because -nosound was specified"); return; } snd_mempool = Mem_AllocPool("sound", 0, NULL); // COMMANDLINEOPTION: Sound: -simsound runs sound mixing but with no output if (COM_CheckParm("-simsound")) simsound = true; Cmd_AddCommand("play", S_Play_f, "play a sound at your current location (not heard by anyone else)"); Cmd_AddCommand("play2", S_Play2_f, "play a sound globally throughout the level (not heard by anyone else)"); Cmd_AddCommand("playvol", S_PlayVol_f, "play a sound at the specified volume level at your current location (not heard by anyone else)"); Cmd_AddCommand("stopsound", S_StopAllSounds, "silence"); Cmd_AddCommand("soundlist", S_SoundList_f, "list loaded sounds"); Cmd_AddCommand("soundinfo", S_SoundInfo_f, "print sound system information (such as channels and speed)"); Cmd_AddCommand("snd_restart", S_Restart_f, "restart sound system"); Cmd_AddCommand("snd_unloadallsounds", S_UnloadAllSounds_f, "unload all sound files"); Cvar_RegisterVariable(&nosound); Cvar_RegisterVariable(&snd_precache); Cvar_RegisterVariable(&snd_initialized); Cvar_RegisterVariable(&snd_streaming); Cvar_RegisterVariable(&snd_streaming_length); Cvar_RegisterVariable(&ambient_level); Cvar_RegisterVariable(&ambient_fade); Cvar_RegisterVariable(&snd_noextraupdate); Cvar_RegisterVariable(&snd_show); Cvar_RegisterVariable(&_snd_mixahead); Cvar_RegisterVariable(&snd_swapstereo); // for people with backwards sound wiring Cvar_RegisterVariable(&snd_channellayout); Cvar_RegisterVariable(&snd_soundradius); Cvar_SetValueQuick(&snd_initialized, true); known_sfx = NULL; total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); OGG_OpenLibrary (); } /* ================ S_Terminate Shutdown and free all resources ================ */ void S_Terminate (void) { S_Shutdown (); OGG_CloseLibrary (); // Free all SFXs while (known_sfx != NULL) S_FreeSfx (known_sfx, true); Cvar_SetValueQuick (&snd_initialized, false); Mem_FreePool (&snd_mempool); } /* ================== S_UnloadAllSounds_f ================== */ void S_UnloadAllSounds_f (void) { int i; // NOTE: we can't free all sounds if we are running a map (this frees sfx_t that are still referenced by precaches) // So, refuse to do this if we are connected. if(cls.state == ca_connected) { Con_Printf("snd_unloadallsounds would wreak havoc if you do that while connected!\n"); return; } // stop any active sounds S_StopAllSounds(); // because the ambient sounds will be freed, clear the pointers for (i = 0;i < (int)sizeof (ambient_sfxs) / (int)sizeof (ambient_sfxs[0]);i++) ambient_sfxs[i] = NULL; // now free all sounds while (known_sfx != NULL) S_FreeSfx (known_sfx, true); } /* ================== S_FindName ================== */ sfx_t changevolume_sfx = {""}; sfx_t *S_FindName (const char *name) { sfx_t *sfx; if (!snd_initialized.integer) return NULL; if(!strcmp(name, changevolume_sfx.name)) return &changevolume_sfx; if (strlen (name) >= sizeof (sfx->name)) { Con_Printf ("S_FindName: sound name too long (%s)\n", name); return NULL; } // Look for this sound in the list of known sfx // TODO: hash table search? for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) if(!strcmp (sfx->name, name)) return sfx; // check for # in the beginning, try lookup by soundindex if (name[0] == '#' && name[1]) { int soundindex = atoi(name + 1); if (soundindex > 0 && soundindex < MAX_SOUNDS) if (cl.sound_precache[soundindex]->name[0]) return cl.sound_precache[soundindex]; } // Add a sfx_t struct for this sound sfx = (sfx_t *)Mem_Alloc (snd_mempool, sizeof (*sfx)); memset (sfx, 0, sizeof(*sfx)); strlcpy (sfx->name, name, sizeof (sfx->name)); sfx->memsize = sizeof(*sfx); sfx->next = known_sfx; known_sfx = sfx; return sfx; } /* ================== S_FreeSfx ================== */ void S_FreeSfx (sfx_t *sfx, qboolean force) { unsigned int i; // Do not free a precached sound during purge if (!force && (sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) return; if (developer_loading.integer) Con_Printf ("unloading sound %s\n", sfx->name); // Remove it from the list of known sfx if (sfx == known_sfx) known_sfx = known_sfx->next; else { sfx_t *prev_sfx; for (prev_sfx = known_sfx; prev_sfx != NULL; prev_sfx = prev_sfx->next) if (prev_sfx->next == sfx) { prev_sfx->next = sfx->next; break; } if (prev_sfx == NULL) { Con_Printf ("S_FreeSfx: Can't find SFX %s in the list!\n", sfx->name); return; } } // Stop all channels using this sfx for (i = 0; i < total_channels; i++) { if (channels[i].sfx == sfx) { Con_Printf("S_FreeSfx: stopping channel %i for sfx \"%s\"\n", i, sfx->name); S_StopChannel (i, true, false); } } // Free it if (sfx->fetcher != NULL && sfx->fetcher->freesfx != NULL) sfx->fetcher->freesfx(sfx); Mem_Free (sfx); } /* ================== S_ClearUsed ================== */ void S_ClearUsed (void) { sfx_t *sfx; // sfx_t *sfxnext; unsigned int i; // Start the ambient sounds and make them loop for (i = 0; i < sizeof (ambient_sfxs) / sizeof (ambient_sfxs[0]); i++) { // Precache it if it's not done (and pass false for levelsound because these are permanent) if (ambient_sfxs[i] == NULL) ambient_sfxs[i] = S_PrecacheSound (ambient_names[i], false, false); if (ambient_sfxs[i] != NULL) { channels[i].sfx = ambient_sfxs[i]; channels[i].sfx->flags |= SFXFLAG_MENUSOUND; channels[i].flags |= CHANNELFLAG_FORCELOOP; channels[i].basevolume = 0.0f; channels[i].basespeed = channels[i].mixspeed = 1.0f; } } // Clear SFXFLAG_LEVELSOUND flag so that sounds not precached this level will be purged for (sfx = known_sfx; sfx != NULL; sfx = sfx->next) sfx->flags &= ~SFXFLAG_LEVELSOUND; } /* ================== S_PurgeUnused ================== */ void S_PurgeUnused(void) { sfx_t *sfx; sfx_t *sfxnext; // Free all not-precached per-level sfx for (sfx = known_sfx;sfx;sfx = sfxnext) { sfxnext = sfx->next; if (!(sfx->flags & (SFXFLAG_LEVELSOUND | SFXFLAG_MENUSOUND))) S_FreeSfx (sfx, false); } } /* ================== S_PrecacheSound ================== */ sfx_t *S_PrecacheSound (const char *name, qboolean complain, qboolean levelsound) { sfx_t *sfx; if (!snd_initialized.integer) return NULL; if (name == NULL || name[0] == 0) return NULL; sfx = S_FindName (name); if (sfx == NULL) return NULL; // clear the FILEMISSING flag so that S_LoadSound will try again on a // previously missing file sfx->flags &= ~ SFXFLAG_FILEMISSING; // set a flag to indicate this has been precached for this level or permanently if (levelsound) sfx->flags |= SFXFLAG_LEVELSOUND; else sfx->flags |= SFXFLAG_MENUSOUND; if (!nosound.integer && snd_precache.integer) S_LoadSound(sfx, complain); return sfx; } /* ================== S_SoundLength ================== */ float S_SoundLength(const char *name) { sfx_t *sfx; if (!snd_initialized.integer) return -1; if (name == NULL || name[0] == 0) return -1; sfx = S_FindName(name); if (sfx == NULL) return -1; return sfx->total_length / (float) S_GetSoundRate(); } /* ================== S_IsSoundPrecached ================== */ qboolean S_IsSoundPrecached (const sfx_t *sfx) { return (sfx != NULL && sfx->fetcher != NULL) || (sfx == &changevolume_sfx); } /* ================== S_BlockSound ================== */ void S_BlockSound (void) { snd_blocked++; } /* ================== S_UnblockSound ================== */ void S_UnblockSound (void) { snd_blocked--; } /* ================= SND_PickChannel Picks a channel based on priorities, empty slots, number of channels ================= */ static channel_t *SND_PickChannel(int entnum, int entchannel) { int ch_idx; int first_to_die; int first_life_left, life_left; channel_t* ch; sfx_t *sfx; // use this instead of ch->sfx->, because that is volatile. // Check for replacement sound, or find the best one to replace first_to_die = -1; first_life_left = 0x7fffffff; // entity channels try to replace the existing sound on the channel // channels <= 0 are autochannels if (IS_CHAN_SINGLE(entchannel)) { for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) { ch = &channels[ch_idx]; if (ch->entnum == entnum && ch->entchannel == entchannel) { // always override sound from same entity S_StopChannel (ch_idx, true, false); return &channels[ch_idx]; } } } // there was no channel to override, so look for the first empty one for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) { ch = &channels[ch_idx]; sfx = ch->sfx; // fetch the volatile variable if (!sfx) { // no sound on this channel first_to_die = ch_idx; goto emptychan_found; } // don't let monster sounds override player sounds if ((ch->entnum == cl.viewentity || ch->entnum == CL_VM_GetViewEntity()) && !(entnum == cl.viewentity || entnum == CL_VM_GetViewEntity())) continue; // don't override looped sounds if ((ch->flags & CHANNELFLAG_FORCELOOP) || sfx->loopstart < sfx->total_length) continue; life_left = (int)((double)sfx->total_length - ch->position); if (life_left < first_life_left) { first_life_left = life_left; first_to_die = ch_idx; } } if (first_to_die == -1) return NULL; S_StopChannel (first_to_die, true, false); emptychan_found: return &channels[first_to_die]; } /* ================= SND_Spatialize Spatializes a channel ================= */ extern cvar_t cl_gameplayfix_soundsmovewithentities; static void SND_Spatialize_WithSfx(channel_t *ch, qboolean isstatic, sfx_t *sfx) { int i; double f; float angle_side, angle_front, angle_factor, mixspeed; vec_t dist, mastervol, intensity; vec3_t source_vec; char vabuf[1024]; // update sound origin if we know about the entity if (ch->entnum > 0 && cls.state == ca_connected && cl_gameplayfix_soundsmovewithentities.integer) { if (ch->entnum >= MAX_EDICTS) { //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); if (ch->entnum > MAX_EDICTS) if (!CL_VM_GetEntitySoundOrigin(ch->entnum, ch->origin)) ch->entnum = MAX_EDICTS; // entity was removed, disown sound } else if (cl.entities[ch->entnum].state_current.active) { dp_model_t *model; //Con_Printf("-- entnum %i origin %f %f %f neworigin %f %f %f\n", ch->entnum, ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); model = CL_GetModelByIndex(cl.entities[ch->entnum].state_current.modelindex); if (model && model->soundfromcenter) VectorMAM(0.5f, cl.entities[ch->entnum].render.mins, 0.5f, cl.entities[ch->entnum].render.maxs, ch->origin); else Matrix4x4_OriginFromMatrix(&cl.entities[ch->entnum].render.matrix, ch->origin); } else if (cl.csqc_server2csqcentitynumber[ch->entnum]) { //Con_Printf("-- entnum %i (client %i) origin %f %f %f neworigin %f %f %f\n", ch->entnum, cl.csqc_server2csqcentitynumber[ch->entnum], ch->origin[0], ch->origin[1], ch->origin[2], cl.entities[ch->entnum].state_current.origin[0], cl.entities[ch->entnum].state_current.origin[1], cl.entities[ch->entnum].state_current.origin[2]); if (!CL_VM_GetEntitySoundOrigin(cl.csqc_server2csqcentitynumber[ch->entnum] + MAX_EDICTS, ch->origin)) ch->entnum = MAX_EDICTS; // entity was removed, disown sound } } mastervol = ch->basevolume; mixspeed = ch->basespeed; // TODO: implement doppler based on origin change relative to viewer and time of recent origin changes // Adjust volume of static sounds if (isstatic) mastervol *= snd_staticvolume.value; else if(!(ch->flags & CHANNELFLAG_FULLVOLUME)) // same as SND_PaintChannel uses { // old legacy separated cvars if(ch->entnum >= MAX_EDICTS) { switch(ch->entchannel) { case 0: mastervol *= snd_csqcchannel0volume.value; break; case 1: mastervol *= snd_csqcchannel1volume.value; break; case 2: mastervol *= snd_csqcchannel2volume.value; break; case 3: mastervol *= snd_csqcchannel3volume.value; break; case 4: mastervol *= snd_csqcchannel4volume.value; break; case 5: mastervol *= snd_csqcchannel5volume.value; break; case 6: mastervol *= snd_csqcchannel6volume.value; break; case 7: mastervol *= snd_csqcchannel7volume.value; break; default: break; } } else if(ch->entnum == 0) { switch(ch->entchannel) { case 0: mastervol *= snd_worldchannel0volume.value; break; case 1: mastervol *= snd_worldchannel1volume.value; break; case 2: mastervol *= snd_worldchannel2volume.value; break; case 3: mastervol *= snd_worldchannel3volume.value; break; case 4: mastervol *= snd_worldchannel4volume.value; break; case 5: mastervol *= snd_worldchannel5volume.value; break; case 6: mastervol *= snd_worldchannel6volume.value; break; case 7: mastervol *= snd_worldchannel7volume.value; break; default: break; } } else if(ch->entnum > 0 && ch->entnum <= cl.maxclients) { switch(ch->entchannel) { case 0: mastervol *= snd_playerchannel0volume.value; break; case 1: mastervol *= snd_playerchannel1volume.value; break; case 2: mastervol *= snd_playerchannel2volume.value; break; case 3: mastervol *= snd_playerchannel3volume.value; break; case 4: mastervol *= snd_playerchannel4volume.value; break; case 5: mastervol *= snd_playerchannel5volume.value; break; case 6: mastervol *= snd_playerchannel6volume.value; break; case 7: mastervol *= snd_playerchannel7volume.value; break; default: break; } } else { switch(ch->entchannel) { case 0: mastervol *= snd_entchannel0volume.value; break; case 1: mastervol *= snd_entchannel1volume.value; break; case 2: mastervol *= snd_entchannel2volume.value; break; case 3: mastervol *= snd_entchannel3volume.value; break; case 4: mastervol *= snd_entchannel4volume.value; break; case 5: mastervol *= snd_entchannel5volume.value; break; case 6: mastervol *= snd_entchannel6volume.value; break; case 7: mastervol *= snd_entchannel7volume.value; break; default: break; } } switch(ch->entchannel) { case 0: mastervol *= snd_channel0volume.value; break; case 1: mastervol *= snd_channel1volume.value; break; case 2: mastervol *= snd_channel2volume.value; break; case 3: mastervol *= snd_channel3volume.value; break; case 4: mastervol *= snd_channel4volume.value; break; case 5: mastervol *= snd_channel5volume.value; break; case 6: mastervol *= snd_channel6volume.value; break; case 7: mastervol *= snd_channel7volume.value; break; default: mastervol *= Cvar_VariableValueOr(va(vabuf, sizeof(vabuf), "snd_channel%dvolume", CHAN_ENGINE2CVAR(ch->entchannel)), 1.0); break; } } // If this channel does not manage its own volume (like CD tracks) if (!(ch->flags & CHANNELFLAG_FULLVOLUME)) mastervol *= volume.value; if(snd_maxchannelvolume.value > 0) { // clamp HERE to allow to go at most 10dB past mastervolume (before clamping), when mastervolume < -10dB (so relative volumes don't get too messy) mastervol = bound(0.0f, mastervol, 10.0f * snd_maxchannelvolume.value); } // always apply "master" mastervol *= mastervolume.value; // add in ReplayGain very late; prevent clipping when close if(sfx) if(sfx->volume_peak > 0) { // Replaygain support // Con_DPrintf("Setting volume on ReplayGain-enabled track... %f -> ", fvol); mastervol *= sfx->volume_mult; if(snd_maxchannelvolume.value > 0) { if(mastervol * sfx->volume_peak > snd_maxchannelvolume.value) mastervol = snd_maxchannelvolume.value / sfx->volume_peak; } // Con_DPrintf("%f\n", fvol); } if(snd_maxchannelvolume.value > 0) { // clamp HERE to keep relative volumes of the channels correct mastervol = min(mastervol, snd_maxchannelvolume.value); } mastervol = max(0.0f, mastervol); ch->mixspeed = mixspeed; // anything coming from the view entity will always be full volume // LordHavoc: make sounds with ATTN_NONE have no spatialization if (ch->entnum == cl.viewentity || ch->entnum == CL_VM_GetViewEntity() || ch->distfade == 0) { ch->prologic_invert = 1; if (snd_spatialization_prologic.integer != 0) { ch->volume[0] = mastervol * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); ch->volume[1] = mastervol * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); for (i = 2;i < SND_LISTENERS;i++) ch->volume[i] = 0; } else { for (i = 0;i < SND_LISTENERS;i++) ch->volume[i] = mastervol * snd_speakerlayout.listeners[i].ambientvolume; } } else { // calculate stereo seperation and distance attenuation VectorSubtract(listener_origin, ch->origin, source_vec); dist = VectorLength(source_vec); f = dist * ch->distfade; f = ((snd_attenuation_exponent.value == 0) ? 1.0 : pow(1.0 - min(1.0, f), (double)snd_attenuation_exponent.value)) * ((snd_attenuation_decibel.value == 0) ? 1.0 : pow(0.1, 0.1 * snd_attenuation_decibel.value * f)); intensity = mastervol * f; if (intensity > 0) { qboolean occluded = false; if (snd_spatialization_occlusion.integer) { if(snd_spatialization_occlusion.integer & 1) if(listener_pvs && cl.worldmodel) { int cluster = cl.worldmodel->brush.PointInLeaf(cl.worldmodel, ch->origin)->clusterindex; if(cluster >= 0 && cluster < 8 * listener_pvsbytes && !CHECKPVSBIT(listener_pvs, cluster)) occluded = true; } if(snd_spatialization_occlusion.integer & 2) if(!occluded) if(cl.worldmodel && cl.worldmodel->brush.TraceLineOfSight && !cl.worldmodel->brush.TraceLineOfSight(cl.worldmodel, listener_origin, ch->origin)) occluded = true; } if(occluded) intensity *= 0.5f; ch->prologic_invert = 1; if (snd_spatialization_prologic.integer != 0) { if (dist == 0) angle_factor = 0.5f; else { Matrix4x4_Transform(&listener_basematrix, ch->origin, source_vec); VectorNormalize(source_vec); switch(spatialmethod) { case SPATIAL_LOG: if(dist == 0) f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing else f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); VectorScale(source_vec, f, source_vec); break; case SPATIAL_POW: f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; f = spatialmin + spatialdiff * bound(0, f, 1); VectorScale(source_vec, f, source_vec); break; case SPATIAL_THRESH: f = spatialmin + spatialdiff * (dist < spatialoffset); VectorScale(source_vec, f, source_vec); break; case SPATIAL_NONE: default: break; } // the z axis needs to be removed and normalized because otherwise the volume would get lower as the sound source goes higher or lower then normal source_vec[2] = 0; VectorNormalize(source_vec); angle_side = acos(source_vec[0]) / M_PI * 180; // angle between 0 and 180 degrees angle_front = asin(source_vec[1]) / M_PI * 180; // angle between -90 and 90 degrees if (angle_side > snd_spatialization_prologic_frontangle.value) { ch->prologic_invert = -1; // this will cause the right channel to do a 180 degrees phase shift (turning the sound wave upside down), // but the best would be 90 degrees phase shift left and a -90 degrees phase shift right. angle_factor = (angle_side - snd_spatialization_prologic_frontangle.value) / (360 - 2 * snd_spatialization_prologic_frontangle.value); // angle_factor is between 0 and 1 and represents the angle range from the front left, to all the surround speakers (amount may vary, // 1 in prologic I 2 in prologic II and 3 or 4 in prologic IIx) to the front right speaker. if (angle_front > 0) angle_factor = 1 - angle_factor; } else angle_factor = angle_front / snd_spatialization_prologic_frontangle.value / 2.0 + 0.5; //angle_factor is between 0 and 1 and represents the angle range from the front left to the center to the front right speaker } ch->volume[0] = intensity * sqrt(angle_factor); ch->volume[1] = intensity * sqrt(1 - angle_factor); for (i = 2;i < SND_LISTENERS;i++) ch->volume[i] = 0; } else { for (i = 0;i < SND_LISTENERS;i++) { Matrix4x4_Transform(&listener_matrix[i], ch->origin, source_vec); VectorNormalize(source_vec); switch(spatialmethod) { case SPATIAL_LOG: if(dist == 0) f = spatialmin + spatialdiff * (spatialfactor < 0); // avoid log(0), but do the right thing else f = spatialmin + spatialdiff * bound(0, (log(dist) - spatialoffset) * spatialfactor, 1); VectorScale(source_vec, f, source_vec); break; case SPATIAL_POW: f = (pow(dist, spatialpower) - spatialoffset) * spatialfactor; f = spatialmin + spatialdiff * bound(0, f, 1); VectorScale(source_vec, f, source_vec); break; case SPATIAL_THRESH: f = spatialmin + spatialdiff * (dist < spatialoffset); VectorScale(source_vec, f, source_vec); break; case SPATIAL_NONE: default: break; } ch->volume[i] = intensity * max(0, source_vec[0] * snd_speakerlayout.listeners[i].dotscale + snd_speakerlayout.listeners[i].dotbias); } } } else for (i = 0;i < SND_LISTENERS;i++) ch->volume[i] = 0; } } static void SND_Spatialize(channel_t *ch, qboolean isstatic) { sfx_t *sfx = ch->sfx; SND_Spatialize_WithSfx(ch, isstatic, sfx); } // ======================================================================= // Start a sound effect // ======================================================================= static void S_PlaySfxOnChannel (sfx_t *sfx, channel_t *target_chan, unsigned int flags, vec3_t origin, float fvol, float attenuation, qboolean isstatic, int entnum, int entchannel, int startpos, float fspeed) { if (!sfx) { Con_Printf("S_PlaySfxOnChannel called with NULL??\n"); return; } if ((sfx->loopstart < sfx->total_length) || (flags & CHANNELFLAG_FORCELOOP)) { if(!snd_startloopingsounds.integer) return; } else { if(!snd_startnonloopingsounds.integer) return; } // Initialize the channel // a crash was reported on an in-use channel, so check here... if (target_chan->sfx) { int channelindex = (int)(target_chan - channels); Con_Printf("S_PlaySfxOnChannel(%s): channel %i already in use?? Clearing.\n", sfx->name, channelindex); S_StopChannel (channelindex, true, false); } // We MUST set sfx LAST because otherwise we could crash a threaded mixer // (otherwise we'd have to call SndSys_LockRenderBuffer here) memset (target_chan, 0, sizeof (*target_chan)); VectorCopy (origin, target_chan->origin); target_chan->flags = flags; target_chan->position = startpos; // start of the sound target_chan->entnum = entnum; target_chan->entchannel = entchannel; // If it's a static sound if (isstatic) { if (sfx->loopstart >= sfx->total_length && (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEWORLD)) Con_DPrintf("Quake compatibility warning: Static sound \"%s\" is not looped\n", sfx->name); target_chan->distfade = attenuation / (64.0f * snd_soundradius.value); } else target_chan->distfade = attenuation / snd_soundradius.value; // set the listener volumes S_SetChannelVolume(target_chan - channels, fvol); S_SetChannelSpeed(target_chan - channels, fspeed); SND_Spatialize_WithSfx (target_chan, isstatic, sfx); // finally, set the sfx pointer, so the channel becomes valid for playback // and will be noticed by the mixer target_chan->sfx = sfx; } int S_StartSound_StartPosition_Flags (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation, float startposition, int flags, float fspeed) { channel_t *target_chan, *check, *ch; int ch_idx, startpos, i; if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) return -1; if(sfx == &changevolume_sfx) { if (!IS_CHAN_SINGLE(entchannel)) return -1; for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++) { ch = &channels[ch_idx]; if (ch->entnum == entnum && ch->entchannel == entchannel) { S_SetChannelVolume(ch_idx, fvol); S_SetChannelSpeed(ch_idx, fspeed); for(i = 1; i > 0 && (i <= flags || i <= (int) channels[ch_idx].flags); i <<= 1) if((flags ^ channels[ch_idx].flags) & i) S_SetChannelFlag(ch_idx, i, (flags & i) != 0); ch->distfade = attenuation / snd_soundradius.value; SND_Spatialize(ch, false); return ch_idx; } } return -1; } if (sfx->fetcher == NULL) return -1; // Pick a channel to play on target_chan = SND_PickChannel(entnum, entchannel); if (!target_chan) return -1; // if an identical sound has also been started this frame, offset the pos // a bit to keep it from just making the first one louder check = &channels[NUM_AMBIENTS]; startpos = (int)(startposition * sfx->format.speed); if (startpos == 0) { for (ch_idx=NUM_AMBIENTS ; ch_idx < NUM_AMBIENTS + MAX_DYNAMIC_CHANNELS ; ch_idx++, check++) { if (check == target_chan) continue; if (check->sfx == sfx && check->position == 0 && check->basespeed == fspeed) { // calculate max offset float maxtime = snd_identicalsoundrandomization_time.value; float maxtics = snd_identicalsoundrandomization_tics.value; float maxticsdelta = ((cls.state == ca_connected) ? (maxtics * (cl.mtime[0] - cl.mtime[1])) : 0); float maxdelta = 0; if(maxticsdelta == 0 || fabs(maxticsdelta) > fabs(maxtime)) maxdelta = maxtime; else maxdelta = fabs(maxticsdelta) * ((maxtime > 0) ? 1 : -1); // use negative pos offset to delay this sound effect startpos = lhrandom(0, maxdelta * sfx->format.speed); break; } } } S_PlaySfxOnChannel (sfx, target_chan, flags, origin, fvol, attenuation, false, entnum, entchannel, startpos, fspeed); return (target_chan - channels); } int S_StartSound (int entnum, int entchannel, sfx_t *sfx, vec3_t origin, float fvol, float attenuation) { return S_StartSound_StartPosition_Flags(entnum, entchannel, sfx, origin, fvol, attenuation, 0, CHANNELFLAG_NONE, 1.0f); } void S_StopChannel (unsigned int channel_ind, qboolean lockmutex, qboolean freesfx) { channel_t *ch; sfx_t *sfx; if (channel_ind >= total_channels) return; // we have to lock an audio mutex to prevent crashes if an audio mixer // thread is currently mixing this channel // the SndSys_LockRenderBuffer function uses such a mutex in // threaded sound backends if (lockmutex && !simsound) SndSys_LockRenderBuffer(); ch = &channels[channel_ind]; sfx = ch->sfx; if (sfx != NULL) { if (sfx->fetcher != NULL && sfx->fetcher->stopchannel != NULL) sfx->fetcher->stopchannel(ch); ch->fetcher_data = NULL; ch->sfx = NULL; if (freesfx) S_FreeSfx(sfx, true); } if (lockmutex && !simsound) SndSys_UnlockRenderBuffer(); } qboolean S_SetChannelFlag (unsigned int ch_ind, unsigned int flag, qboolean value) { if (ch_ind >= total_channels) return false; if (flag != CHANNELFLAG_FORCELOOP && flag != CHANNELFLAG_PAUSED && flag != CHANNELFLAG_FULLVOLUME && flag != CHANNELFLAG_LOCALSOUND) return false; if (value) channels[ch_ind].flags |= flag; else channels[ch_ind].flags &= ~flag; return true; } void S_StopSound(int entnum, int entchannel) { unsigned int i; for (i = 0; i < MAX_DYNAMIC_CHANNELS; i++) if (channels[i].entnum == entnum && channels[i].entchannel == entchannel) { S_StopChannel (i, true, false); return; } } void S_StopAllSounds (void) { unsigned int i; // TOCHECK: is this test necessary? if (snd_renderbuffer == NULL) return; #ifdef CONFIG_CD // stop CD audio because it may be using a faketrack CDAudio_Stop(); #endif if (simsound || SndSys_LockRenderBuffer ()) { int clear; size_t memsize; for (i = 0; i < total_channels; i++) if (channels[i].sfx) S_StopChannel (i, false, false); total_channels = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS; // no statics memset(channels, 0, MAX_CHANNELS * sizeof(channel_t)); // Mute the contents of the submittion buffer clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; memset(snd_renderbuffer->ring, clear, memsize); if (!simsound) SndSys_UnlockRenderBuffer (); } } void S_PauseGameSounds (qboolean toggle) { unsigned int i; for (i = 0; i < total_channels; i++) { channel_t *ch; ch = &channels[i]; if (ch->sfx != NULL && ! (ch->flags & CHANNELFLAG_LOCALSOUND)) S_SetChannelFlag (i, CHANNELFLAG_PAUSED, toggle); } } void S_SetChannelVolume(unsigned int ch_ind, float fvol) { channels[ch_ind].basevolume = fvol; } void S_SetChannelSpeed(unsigned int ch_ind, float fspeed) { channels[ch_ind].basespeed = fspeed; } float S_GetChannelPosition (unsigned int ch_ind) { // note: this is NOT accurate yet double s; channel_t *ch = &channels[ch_ind]; sfx_t *sfx = ch->sfx; if (!sfx) return -1; s = ch->position / sfx->format.speed; /* if(!snd_usethreadedmixing) s += _snd_mixahead.value; */ return (float)s; } float S_GetEntChannelPosition(int entnum, int entchannel) { channel_t *ch; unsigned int i; for (i = 0; i < total_channels; i++) { ch = &channels[i]; if (ch->entnum == entnum && ch->entchannel == entchannel) return S_GetChannelPosition(i); } return -1; // no playing sound in this channel } /* ================= S_StaticSound ================= */ void S_StaticSound (sfx_t *sfx, vec3_t origin, float fvol, float attenuation) { channel_t *target_chan; if (snd_renderbuffer == NULL || sfx == NULL || nosound.integer) return; if (!sfx->fetcher) { Con_Printf ("S_StaticSound: \"%s\" hasn't been precached\n", sfx->name); return; } if (total_channels == MAX_CHANNELS) { Con_Print("S_StaticSound: total_channels == MAX_CHANNELS\n"); return; } target_chan = &channels[total_channels++]; S_PlaySfxOnChannel (sfx, target_chan, CHANNELFLAG_FORCELOOP, origin, fvol, attenuation, true, 0, 0, 0, 1.0f); } /* =================== S_UpdateAmbientSounds =================== */ static void S_UpdateAmbientSounds (void) { int i; float vol; float fade = (float)max(0.0, cl.time - cl.oldtime) * ambient_fade.value / 256.0f; int ambient_channel; channel_t *chan; unsigned char ambientlevels[NUM_AMBIENTS]; sfx_t *sfx; memset(ambientlevels, 0, sizeof(ambientlevels)); if (cl.worldmodel && cl.worldmodel->brush.AmbientSoundLevelsForPoint) cl.worldmodel->brush.AmbientSoundLevelsForPoint(cl.worldmodel, listener_origin, ambientlevels, sizeof(ambientlevels)); // Calc ambient sound levels for (ambient_channel = 0 ; ambient_channel< NUM_AMBIENTS ; ambient_channel++) { chan = &channels[ambient_channel]; sfx = chan->sfx; // fetch the volatile variable if (sfx == NULL || sfx->fetcher == NULL) continue; i = ambientlevels[ambient_channel]; if (i < 8) i = 0; vol = i * (1.0f / 256.0f); // Don't adjust volume too fast if (chan->basevolume < vol) { chan->basevolume += fade; if (chan->basevolume > vol) chan->basevolume = vol; } else if (chan->basevolume > vol) { chan->basevolume -= fade; if (chan->basevolume < vol) chan->basevolume = vol; } if (snd_spatialization_prologic.integer != 0) { chan->volume[0] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[0].ambientvolume * sqrt(0.5); chan->volume[1] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[1].ambientvolume * sqrt(0.5); for (i = 2;i < SND_LISTENERS;i++) chan->volume[i] = 0.0f; } else { for (i = 0;i < SND_LISTENERS;i++) chan->volume[i] = chan->basevolume * ambient_level.value * volume.value * mastervolume.value * snd_speakerlayout.listeners[i].ambientvolume; } } } static void S_PaintAndSubmit (void) { unsigned int newsoundtime, paintedtime, endtime, maxtime, usedframes; int usesoundtimehack; static int soundtimehack = -1; static int oldsoundtime = 0; if (snd_renderbuffer == NULL || nosound.integer) return; // Update sound time snd_usethreadedmixing = false; usesoundtimehack = true; if (cls.timedemo) // SUPER NASTY HACK to mix non-realtime sound for more reliable benchmarking { usesoundtimehack = 1; newsoundtime = (unsigned int)((double)cl.mtime[0] * (double)snd_renderbuffer->format.speed); } else if (cls.capturevideo.soundrate && !cls.capturevideo.realtime) // SUPER NASTY HACK to record non-realtime sound { usesoundtimehack = 2; newsoundtime = (unsigned int)((double)cls.capturevideo.frame * (double)snd_renderbuffer->format.speed / (double)cls.capturevideo.framerate); } else if (simsound) { usesoundtimehack = 3; newsoundtime = (unsigned int)((realtime - snd_starttime) * (double)snd_renderbuffer->format.speed); } else { snd_usethreadedmixing = snd_threaded && !cls.capturevideo.soundrate; usesoundtimehack = 0; newsoundtime = SndSys_GetSoundTime(); } // if the soundtimehack state changes we need to reset the soundtime if (soundtimehack != usesoundtimehack) { snd_renderbuffer->startframe = snd_renderbuffer->endframe = soundtime = newsoundtime; // Mute the contents of the submission buffer if (simsound || SndSys_LockRenderBuffer ()) { int clear; size_t memsize; clear = (snd_renderbuffer->format.width == 1) ? 0x80 : 0; memsize = snd_renderbuffer->maxframes * snd_renderbuffer->format.width * snd_renderbuffer->format.channels; memset(snd_renderbuffer->ring, clear, memsize); if (!simsound) SndSys_UnlockRenderBuffer (); } } soundtimehack = usesoundtimehack; if (!soundtimehack && snd_blocked > 0) return; if (snd_usethreadedmixing) return; // the audio thread will mix its own data newsoundtime += extrasoundtime; if (newsoundtime < soundtime) { if ((cls.capturevideo.soundrate != 0) != recording_sound) { unsigned int additionaltime; // add some time to extrasoundtime make newsoundtime higher // The extra time must be a multiple of the render buffer size // to avoid modifying the current position in the buffer, // some modules write directly to a shared (DMA) buffer additionaltime = (soundtime - newsoundtime) + snd_renderbuffer->maxframes - 1; additionaltime -= additionaltime % snd_renderbuffer->maxframes; extrasoundtime += additionaltime; newsoundtime += additionaltime; Con_DPrintf("S_PaintAndSubmit: new extra sound time = %u\n", extrasoundtime); } else if (!soundtimehack) Con_Printf("S_PaintAndSubmit: WARNING: newsoundtime < soundtime (%u < %u)\n", newsoundtime, soundtime); } soundtime = newsoundtime; recording_sound = (cls.capturevideo.soundrate != 0); // Lock submitbuffer if (!simsound && !SndSys_LockRenderBuffer()) { // If the lock failed, stop here Con_DPrint(">> S_PaintAndSubmit: SndSys_LockRenderBuffer() failed\n"); return; } // Check to make sure that we haven't overshot paintedtime = snd_renderbuffer->endframe; if (paintedtime < soundtime) paintedtime = soundtime; // mix ahead of current position if (soundtimehack) endtime = soundtime + (unsigned int)(_snd_mixahead.value * (float)snd_renderbuffer->format.speed); else endtime = soundtime + (unsigned int)(max(_snd_mixahead.value * (float)snd_renderbuffer->format.speed, min(3 * (soundtime - oldsoundtime), 0.3 * (float)snd_renderbuffer->format.speed))); usedframes = snd_renderbuffer->endframe - snd_renderbuffer->startframe; maxtime = paintedtime + snd_renderbuffer->maxframes - usedframes; endtime = min(endtime, maxtime); while (paintedtime < endtime) { unsigned int startoffset; unsigned int nbframes; // see how much we can fit in the paint buffer nbframes = endtime - paintedtime; // limit to the end of the ring buffer (in case of wrapping) startoffset = paintedtime % snd_renderbuffer->maxframes; nbframes = min(nbframes, snd_renderbuffer->maxframes - startoffset); // mix into the buffer S_MixToBuffer(&snd_renderbuffer->ring[startoffset * snd_renderbuffer->format.width * snd_renderbuffer->format.channels], nbframes); paintedtime += nbframes; snd_renderbuffer->endframe = paintedtime; } if (!simsound) SndSys_UnlockRenderBuffer(); // Remove outdated samples from the ring buffer, if any if (snd_renderbuffer->startframe < soundtime) snd_renderbuffer->startframe = soundtime; if (simsound) snd_renderbuffer->startframe = snd_renderbuffer->endframe; else SndSys_Submit(); oldsoundtime = soundtime; cls.soundstats.latency_milliseconds = (snd_renderbuffer->endframe - snd_renderbuffer->startframe) * 1000 / snd_renderbuffer->format.speed; R_TimeReport("audiomix"); } /* ============ S_Update Called once each time through the main loop ============ */ void S_Update(const matrix4x4_t *listenermatrix) { unsigned int i, j, k; channel_t *ch, *combine; matrix4x4_t rotatematrix; if (snd_renderbuffer == NULL || nosound.integer) return; { double mindist_trans, maxdist_trans; spatialmin = snd_spatialization_min.value; spatialdiff = snd_spatialization_max.value - spatialmin; if(snd_spatialization_control.value) { spatialpower = snd_spatialization_power.value; if(spatialpower == 0) { spatialmethod = SPATIAL_LOG; mindist_trans = log(max(1, snd_spatialization_min_radius.value)); maxdist_trans = log(max(1, snd_spatialization_max_radius.value)); } else { spatialmethod = SPATIAL_POW; mindist_trans = pow(snd_spatialization_min_radius.value, spatialpower); maxdist_trans = pow(snd_spatialization_max_radius.value, spatialpower); } if(mindist_trans - maxdist_trans == 0) { spatialmethod = SPATIAL_THRESH; mindist_trans = snd_spatialization_min_radius.value; } else { spatialoffset = mindist_trans; spatialfactor = 1 / (maxdist_trans - mindist_trans); } } else spatialmethod = SPATIAL_NONE; } // If snd_swapstereo or snd_channellayout has changed, recompute the channel layout if (current_swapstereo != boolxor(snd_swapstereo.integer, v_flipped.integer) || current_channellayout != snd_channellayout.integer) S_SetChannelLayout(); Matrix4x4_Invert_Simple(&listener_basematrix, listenermatrix); Matrix4x4_OriginFromMatrix(listenermatrix, listener_origin); if (cl.worldmodel && cl.worldmodel->brush.FatPVS && cl.worldmodel->brush.num_pvsclusterbytes && cl.worldmodel->brush.PointInLeaf) { if(cl.worldmodel->brush.num_pvsclusterbytes != listener_pvsbytes) { if(listener_pvs) Mem_Free(listener_pvs); listener_pvsbytes = cl.worldmodel->brush.num_pvsclusterbytes; listener_pvs = (unsigned char *) Mem_Alloc(snd_mempool, listener_pvsbytes); } cl.worldmodel->brush.FatPVS(cl.worldmodel, listener_origin, 2, listener_pvs, listener_pvsbytes, 0); } else { if(listener_pvs) { Mem_Free(listener_pvs); listener_pvs = NULL; } listener_pvsbytes = 0; } // calculate the current matrices for (j = 0;j < SND_LISTENERS;j++) { Matrix4x4_CreateFromQuakeEntity(&rotatematrix, 0, 0, 0, 0, -snd_speakerlayout.listeners[j].yawangle, 0, 1); Matrix4x4_Concat(&listener_matrix[j], &rotatematrix, &listener_basematrix); // I think this should now do this: // 1. create a rotation matrix for rotating by e.g. -90 degrees CCW // (note: the matrix will rotate the OBJECT, not the VIEWER, so its // angle has to be taken negative) // 2. create a transform which first rotates and moves its argument // into the player's view coordinates (using basematrix which is // an inverted "absolute" listener matrix), then applies the // rotation matrix for the ear // Isn't Matrix4x4_CreateFromQuakeEntity a bit misleading because this // does not actually refer to an entity? } // update general area ambient sound sources S_UpdateAmbientSounds (); combine = NULL; R_TimeReport("audioprep"); // update spatialization for static and dynamic sounds cls.soundstats.totalsounds = 0; cls.soundstats.mixedsounds = 0; ch = channels+NUM_AMBIENTS; for (i=NUM_AMBIENTS ; isfx) continue; cls.soundstats.totalsounds++; // respatialize channel SND_Spatialize(ch, i >= MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS); // try to combine static sounds with a previous channel of the same // sound effect so we don't mix five torches every frame if (i > MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS) { // no need to merge silent channels for (j = 0;j < SND_LISTENERS;j++) if (ch->volume[j]) break; if (j == SND_LISTENERS) continue; // if the last combine chosen isn't suitable, find a new one if (!(combine && combine != ch && combine->sfx == ch->sfx)) { // search for one combine = NULL; for (j = MAX_DYNAMIC_CHANNELS + NUM_AMBIENTS;j < i;j++) { if (channels[j].sfx == ch->sfx) { combine = channels + j; break; } } } if (combine && combine != ch && combine->sfx == ch->sfx) { for (j = 0;j < SND_LISTENERS;j++) { combine->volume[j] += ch->volume[j]; ch->volume[j] = 0; } } } for (k = 0;k < SND_LISTENERS;k++) if (ch->volume[k]) break; if (k < SND_LISTENERS) cls.soundstats.mixedsounds++; } R_TimeReport("audiospatialize"); sound_spatialized = true; // debugging output if (snd_show.integer) Con_Printf("----(%u)----\n", cls.soundstats.mixedsounds); S_PaintAndSubmit(); } void S_ExtraUpdate (void) { if (snd_noextraupdate.integer || !sound_spatialized) return; S_PaintAndSubmit(); } qboolean S_LocalSound (const char *sound) { sfx_t *sfx; int ch_ind; if (!snd_initialized.integer || nosound.integer) return true; sfx = S_PrecacheSound (sound, true, false); if (!sfx) { Con_Printf("S_LocalSound: can't precache %s\n", sound); return false; } // menu sounds must not be freed on level change sfx->flags |= SFXFLAG_MENUSOUND; // fun fact: in Quake 1, this used -1 "replace any entity channel", // which we no longer support anyway // changed by Black in r4297 "Changed S_LocalSound to play multiple sounds at a time." ch_ind = S_StartSound (cl.viewentity, 0, sfx, vec3_origin, 1, 0); if (ch_ind < 0) return false; channels[ch_ind].flags |= CHANNELFLAG_LOCALSOUND; return true; } darkplaces/darkplaces48x48.png0000664000175000017500000000743013067716220015526 0ustar kalevkalev‰PNG  IHDR00Wù‡ßIDATxœÕZkpTçy~ÎÙ³Wíê~=º,ºsÕqÇã1;ÁnHì¸Óâà™Ì4ɤNœÖi;)M¦‰ÛdZ÷2Nƒ±)Ä6ƒ©mŒ­%$Œ V—•V»ÚÛ9{öÜúãûŽ´d‘§í;sæüXÍ÷½Ï{}Þ÷ˆÁˆ ÖM›6m ƒWO:5@ Ð}>Ÿ1wÌ$ìc…yÂb±Ü`€|ö9<F™“ š››ï;ž›••µ@ /7‹ Ì\Ü1“Ü5ª {¨·¿^Q”<­æÈÀø¿ c·Ùª‹€Œœœ/€RU lø€µJ¾Ý*z–,Y‚ââb/€"¹ ¾Ð<àæâ 92Ñ:QŒõë×s»víòàA8@ò@À455Ù†q°ëº®wvv€BŸ}ϪrÍ«K”6ÛÀæp8 y •ÈµmÛ6Îï÷/(˜ ·¤ ²¬u…Exç­|Gä:÷¥ÒÂ~-·ì|‚s~ÔÑqî2€€¸ "Õçóé·»ü®ãS„¼Èµ+gF‚áê˜aà±ÇS/]ºÔà5‡\ ‚äƼ… –ºÖš ¥‘»Ö¶ÀÒןyÆ™'nÜôGWGázkrròÝîîîAA&=2'!×[¶ÇmG†‘”’"H ÍAïÐLêîî6&**š‡D›w©ÿòÕòÒ®×~ôÓÝ•ýN÷†aüËÅ‹ÛøDAHÎä»J0ZB¹l+§=ù%¥X¶| ÒÄÜ\, ±0  @'€Ž£ñƒ­­‚oßd\²j«gU¦¾¨¾¾~çâÅ‹°¤ ¸A¸eO™•èêbmll´(ïd't ,ËšãÄt2($,®à÷û+ªuàØúç;3ÞÝý·9 S»CUÕÜžžžƒ:(x Ä›³  À¶téÒlVŽ~õòåk«Z›šµ]èõpV®``èºgu•×MÓ Übéù¦— z¹NÈ ^ ¯©oüaiëWwüØ:b¨Ö§Ÿ~f³$II¿ß¯RŒ ‚ §‡“åvçyÞ¾lÙ2ïÚî»î®ÿú·ÞwÚ·¼íª‰†‚Î<žÙŽ1V’Šä±€Åp¸Ð?8¬ ªú| `€èóù´@ €@ `çyJš^ …ÓõU•y™}}ñ‰K–¿üå‹Ì¡C‡¼A"džç@ à3r@¶¹¹ÙÝP[µÃÚqäHìøÁ~ÒÏ,˜/>°áËq[N¾8zýº244$Ù]³º˜íßÿ>ÃZ-njm•¾‘öžŸÏgø|>Z6"]®¬ì=ÑZoøÏv½Êà6nÜèáyþ> pI¼%AØ–––\ùÚùŸìÙ»ï%EÕ+Ú½âL¯Œ'4záEŸNȪ¶aQ­À¸r劎éxW@BfFI"ëíííªoë€öþ3›ñÍG²;Î:’X Àƒ4’øè®2ˆ¿¨éøÎ׊amœŒö]d†ÇƒˆF£aÝ|>pvBUÕýgÏ‹°,¨òI àŽº*!Xœoí!öÃ|`Üp»Ý.š^ €´‡ÝʶµÕÿx÷n­øÆSÌóUÏ,qj:tærÀ)'| ×­ëj\U Ã0f7½ ß)œˆuww_^ØØ2rÿ<©ëÌš5kt›ÍVBo£‚À®X±¢²ãèáï|}¡Ýñø_ýÌxþ…$UUõD"1 à€3NS ~AÞÊÈ Q2=æõY*ÚqSB)5Õîb“¯¼j<ñÄ*H_)P±þ;31ôäøD´ö¹ßà}x*#‘H$©²í Vï0’|)w]ý…€W÷wªëg‰ 1Œ%_¼äe€Õ0 +HwÏé1¬ ÌA˜ÖÖÖbß‘ã¼õË‹¯°;wîÔEQŒƒ$l€« µ; ÀºråÊÚK=}•_)`™B$Že˜ÜÜ\³ÙÙ@Jõs.ê-;ËùÆ|eÖ$€SÅ’ÀY ,¸ÑV;ÔMBkUþ×w|× >}Ú´þHÈôƒÔciÕªU|eI᳜žz‰«Y¾n½þËŸ<«?ú¥&1'Ñ[]]]2Ô¸0ûÑR—bQgטŽžT®À°Ùí Hì;A’˜À¤wb«%俯2Ô 'Ž`ï¾·S’$YAºåUþ:(CL¥RÑ‚òyí“¢”!lٲ—”ËÔÁýÏožd¬—°3J€ª‘Á̳kæû&²Æ$ãñü K@Ò4•É'“ªH£±XÌv¶íZ¹lQ˜ßüzžûûŸqÑ@xË HÌÇ1=tˆ ÖC•ðRe9’×ëã‹ ï/áËV'eY¢^—°UUU Ã0úØØXב#GÞ¤F™Á0iscc£µãÝß×®rÃý˜7ö¼a¤WÈô¾躞sO!¼K¬œ1tö8»SO$* §oÓdJ §6òæ¡îUü~¬aÑ¢Âs§?Øèê©ÿéÚböÅ7G™žå÷DË««?MÊ) „mž ç‚ °N§³¢Éš­kx¼w`Ÿ1d”J*)Ó±4=tŽD"¶Ý=*›?©âØ$t]7K¡D-/Qä†Ïç3AP¨w$ꦓVÀt]¸µ¤eeFwÿð3åö5On–;ú®]çó)PÈÌ`ÀÐ<±%GE3N³ÈL´';cNzW(íN HKb†ad¡ÔÎfÁ,h]ÍpÇPERin›ŠSên“‡ŒÐPóƒäÌ€A–e¯jš¦;Šç3U MEQôÌUFÆ´°­­­ü«GßýÓÒº\føT.4T*õö0H3M‚ä1 33S­à‹"×Á@fa±X˜¬¬,³r¤7©)ñù|ºÏç39O’ZF¤ï€0Çqã¢(ªA ÆöíÛ9;©& U$R$Tät†®öþŃåY%g{BFý²j­WsÛi%)á& 9@C"e8sü¾~4ôîûî_Ú :dI¯ç,f¨çi3ë AY–M`À’Âaè9U> ö ë×?µã©omy}ï|{i þî¹`”¤œ¢ÞíÃtJÝ€JÊÈ*üx¿Œ9{ÞæZJ>°9íV³…{Ö¾g!†aÊêª ¦Ø2…ÍvL8öÕ-M&û~PV^æY±°VÝõÂó¢(%¢(†AÆÐó a ˜FKWHÖ­öO„ââèøðSóð·Øàð\PPÂ?Ì 3ë9:02nØXâ½4pù+ë¼Ï¾öò+/<¸º¹`çÓßÓ~õÛ—1 ÛEQŒ‚ð¯v= Åb*|n tuuùkš8æëQ‡ŸÇÉOÎÙNg1H/Áì»*Ëhz]¿$qÞ?m<¹qtMÎëõfϯœ·áâ[o½þ6Ï|ùØéÔÙî+ÊhpRN$ Ý¿ 9%½é¥‡ l+(Ý¿¨¢ìÞƒvå û+ú;䒲ŋFFF:\Ãt;a¸žA˜¸$æp<òÍ-xÿäÇãq‹×ë-íéêú“h_ŸkЊ„ÊXcÎ<} œ0"‘H $Q¯8GôašÝpïTKæy0>>®77787ß»¼aqò2çžè·gUç¸Ü™“ÑhtÌ<ˆçy=|&UæyÞ6¯¸p‘ûÿýõƒÌòÖV¼²o2œ.ÝåñÄJëëBõõu1Éž“ †#¢,Ëc˜^»œa¾—Aé-—\Sè ­L„㢧°bø£7 vŸIØNœyO9úߧÊÝnw8‡èaÊí@ðãü×*Ô æ8(…¢±`fåk×H¬ô¡‘ýìM5®sa½¥¬ª.ßãñˆÁ`pŠTñ<Ïð×çØw£”™ 'ÄJf™+ù€‘IAHTa3!ÍZ­Xga¿£µž¼|Í?2*ƒX> š ŸGñÛ0AÐè'"L„º0=Ÿš 3A Ó+•DYyÉÕâñ>CE¤M‚„N wù1üŽ¶Ó&÷§1G»Hˆ™ÄÌ€ÌY× [ÝÙ…SÚ6Ûôj= JÊ0‹½Ñçp Ä+&‡GÚ{ŠV›VA±Xl‘eëÖÅN%ÇAj¼9_§/€¿xéB¼“ËÕ¶¶¶¾@–c˜Ë* ƒ$ùuPb†Û,o'sñìv¢ˆååü¾ýê@H¢@råϳ@âýξ£{ˆâæ~é®*ðÿ€)‚ 8@¾gƒ$}$‘E:SnùKý ÿvÄ ¥IEND®B`‚darkplaces/netconn.h0000775000175000017500000003216413067716222014013 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. Copyright (C) 2003 Forest Hale This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef NET_H #define NET_H #include "lhnet.h" #define NET_HEADERSIZE (2 * sizeof(unsigned int)) // NetHeader flags #define NETFLAG_LENGTH_MASK 0x0000ffff #define NETFLAG_DATA 0x00010000 #define NETFLAG_ACK 0x00020000 #define NETFLAG_NAK 0x00040000 #define NETFLAG_EOM 0x00080000 #define NETFLAG_UNRELIABLE 0x00100000 #define NETFLAG_CRYPTO0 0x10000000 #define NETFLAG_CRYPTO1 0x20000000 #define NETFLAG_CRYPTO2 0x40000000 #define NETFLAG_CTL 0x80000000 #define NET_PROTOCOL_VERSION 3 #define NET_EXTRESPONSE_MAX 16 /// \page netconn The network info/connection protocol. /// It is used to find Quake /// servers, get info about them, and connect to them. Once connected, the /// Quake game protocol (documented elsewhere) is used. /// /// /// General notes:\code /// game_name is currently always "QUAKE", but is there so this same protocol /// can be used for future games as well; can you say Quake2? /// /// CCREQ_CONNECT /// string game_name "QUAKE" /// byte net_protocol_version NET_PROTOCOL_VERSION /// /// CCREQ_SERVER_INFO /// string game_name "QUAKE" /// byte net_protocol_version NET_PROTOCOL_VERSION /// /// CCREQ_PLAYER_INFO /// byte player_number /// /// CCREQ_RULE_INFO /// string rule /// /// CCREQ_RCON /// string password /// string command /// /// /// /// CCREP_ACCEPT /// long port /// /// CCREP_REJECT /// string reason /// /// CCREP_SERVER_INFO /// string server_address /// string host_name /// string level_name /// byte current_players /// byte max_players /// byte protocol_version NET_PROTOCOL_VERSION /// /// CCREP_PLAYER_INFO /// byte player_number /// string name /// long colors /// long frags /// long connect_time /// string address /// /// CCREP_RULE_INFO /// string rule /// string value /// /// CCREP_RCON /// string reply /// \endcode /// \note /// There are two address forms used above. The short form is just a /// port number. The address that goes along with the port is defined as /// "whatever address you receive this reponse from". This lets us use /// the host OS to solve the problem of multiple host addresses (possibly /// with no routing between them); the host will use the right address /// when we reply to the inbound connection request. The long from is /// a full address and port in a string. It is used for returning the /// address of a server that is not running locally. #define CCREQ_CONNECT 0x01 #define CCREQ_SERVER_INFO 0x02 #define CCREQ_PLAYER_INFO 0x03 #define CCREQ_RULE_INFO 0x04 #define CCREQ_RCON 0x05 // RocketGuy: ProQuake rcon support #define CCREP_ACCEPT 0x81 #define CCREP_REJECT 0x82 #define CCREP_SERVER_INFO 0x83 #define CCREP_PLAYER_INFO 0x84 #define CCREP_RULE_INFO 0x85 #define CCREP_RCON 0x86 // RocketGuy: ProQuake rcon support typedef struct netgraphitem_s { double time; int reliablebytes; int unreliablebytes; int ackbytes; double cleartime; } netgraphitem_t; typedef struct netconn_s { struct netconn_s *next; lhnetsocket_t *mysocket; lhnetaddress_t peeraddress; // this is mostly identical to qsocket_t from quake /// if this time is reached, kick off peer double connecttime; double timeout; double lastMessageTime; double lastSendTime; /// writing buffer to send to peer as the next reliable message /// can be added to at any time, copied into sendMessage buffer when it is /// possible to send a reliable message and then cleared /// @{ sizebuf_t message; unsigned char messagedata[NET_MAXMESSAGE]; /// @} /// reliable message that is currently sending /// (for building fragments) int sendMessageLength; unsigned char sendMessage[NET_MAXMESSAGE]; /// reliable message that is currently being received /// (for putting together fragments) int receiveMessageLength; unsigned char receiveMessage[NET_MAXMESSAGE]; /// used by both NQ and QW protocols unsigned int outgoing_unreliable_sequence; struct netconn_nq_s { unsigned int ackSequence; unsigned int sendSequence; unsigned int receiveSequence; unsigned int unreliableReceiveSequence; } nq; struct netconn_qw_s { // QW protocol qboolean fatal_error; float last_received; // for timeouts // the statistics are cleared at each client begin, because // the server connecting process gives a bogus picture of the data float frame_latency; // rolling average float frame_rate; int drop_count; ///< dropped packets, cleared each level int good_count; ///< cleared each level int qport; // sequencing variables unsigned int incoming_sequence; unsigned int incoming_acknowledged; qboolean incoming_reliable_acknowledged; ///< single bit qboolean incoming_reliable_sequence; ///< single bit, maintained local qboolean reliable_sequence; ///< single bit unsigned int last_reliable_sequence; ///< sequence number of last send } qw; // bandwidth estimator double cleartime; // if realtime > nc->cleartime, free to go double incoming_cleartime; // if realtime > nc->cleartime, free to go (netgraph cleartime simulation only) // this tracks packet loss and packet sizes on the most recent packets // used by shownetgraph feature #define NETGRAPH_PACKETS 256 #define NETGRAPH_NOPACKET 0 #define NETGRAPH_LOSTPACKET -1 #define NETGRAPH_CHOKEDPACKET -2 int incoming_packetcounter; netgraphitem_t incoming_netgraph[NETGRAPH_PACKETS]; int outgoing_packetcounter; netgraphitem_t outgoing_netgraph[NETGRAPH_PACKETS]; char address[128]; crypto_t crypto; // statistic counters int packetsSent; int packetsReSent; int packetsReceived; int receivedDuplicateCount; int droppedDatagrams; int unreliableMessagesSent; int unreliableMessagesReceived; int reliableMessagesSent; int reliableMessagesReceived; } netconn_t; extern netconn_t *netconn_list; extern mempool_t *netconn_mempool; extern cvar_t hostname; extern cvar_t developer_networking; #ifdef CONFIG_MENU #define SERVERLIST_VIEWLISTSIZE SERVERLIST_TOTALSIZE typedef enum serverlist_maskop_e { // SLMO_CONTAINS is the default for strings // SLMO_GREATEREQUAL is the default for numbers (also used when OP == CONTAINS or NOTCONTAINS SLMO_CONTAINS, SLMO_NOTCONTAIN, SLMO_LESSEQUAL, SLMO_LESS, SLMO_EQUAL, SLMO_GREATER, SLMO_GREATEREQUAL, SLMO_NOTEQUAL, SLMO_STARTSWITH, SLMO_NOTSTARTSWITH } serverlist_maskop_t; /// struct with all fields that you can search for or sort by typedef struct serverlist_info_s { /// address for connecting char cname[128]; /// ping time for sorting servers int ping; /// name of the game char game[32]; /// name of the mod char mod[32]; /// name of the map char map[32]; /// name of the session char name[128]; /// qc-defined short status string char qcstatus[128]; /// frags/ping/name list (if they fit in the packet) char players[1400]; /// max client number int maxplayers; /// number of currently connected players (including bots) int numplayers; /// number of currently connected players that are bots int numbots; /// number of currently connected players that are not bots int numhumans; /// number of free slots int freeslots; /// protocol version int protocol; /// game data version /// (an integer that is used for filtering incompatible servers, /// not filterable by QC) int gameversion; // categorized sorting int category; /// favorite server flag qboolean isfavorite; } serverlist_info_t; typedef enum { SLIF_CNAME, SLIF_PING, SLIF_GAME, SLIF_MOD, SLIF_MAP, SLIF_NAME, SLIF_MAXPLAYERS, SLIF_NUMPLAYERS, SLIF_PROTOCOL, SLIF_NUMBOTS, SLIF_NUMHUMANS, SLIF_FREESLOTS, SLIF_QCSTATUS, SLIF_PLAYERS, SLIF_CATEGORY, SLIF_ISFAVORITE, SLIF_COUNT } serverlist_infofield_t; typedef enum { SLSF_DESCENDING = 1, SLSF_FAVORITES = 2, SLSF_CATEGORIES = 4 } serverlist_sortflags_t; typedef enum { SQS_NONE = 0, SQS_QUERYING, SQS_QUERIED, SQS_TIMEDOUT, SQS_REFRESHING } serverlist_query_state; typedef struct serverlist_entry_s { /// used to determine whether this entry should be included into the final view serverlist_query_state query; /// used to count the number of times the host has tried to query this server already unsigned querycounter; /// used to calculate ping when update comes in double querytime; /// query protocol to use on this server, may be PROTOCOL_QUAKEWORLD or PROTOCOL_DARKPLACES7 int protocol; serverlist_info_t info; // legacy stuff char line1[128]; char line2[128]; } serverlist_entry_t; typedef struct serverlist_mask_s { qboolean active; serverlist_maskop_t tests[SLIF_COUNT]; serverlist_info_t info; } serverlist_mask_t; #define ServerList_GetCacheEntry(x) (&serverlist_cache[(x)]) #define ServerList_GetViewEntry(x) (ServerList_GetCacheEntry(serverlist_viewlist[(x)])) extern serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; extern serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; extern serverlist_infofield_t serverlist_sortbyfield; extern int serverlist_sortflags; // not using the enum, as it is a bitmask #if SERVERLIST_TOTALSIZE > 65536 #error too many servers, change type of index array #endif extern int serverlist_viewcount; extern unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; extern int serverlist_cachecount; extern serverlist_entry_t *serverlist_cache; extern const serverlist_entry_t *serverlist_callbackentry; extern qboolean serverlist_consoleoutput; void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer); #endif //============================================================================ // // public network functions // //============================================================================ extern char cl_net_extresponse[NET_EXTRESPONSE_MAX][1400]; extern int cl_net_extresponse_count; extern int cl_net_extresponse_last; extern char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; extern int sv_net_extresponse_count; extern int sv_net_extresponse_last; #ifdef CONFIG_MENU extern double masterquerytime; extern int masterquerycount; extern int masterreplycount; extern int serverquerycount; extern int serverreplycount; #endif extern sizebuf_t cl_message; extern sizebuf_t sv_message; extern char cl_readstring[MAX_INPUTLINE]; extern char sv_readstring[MAX_INPUTLINE]; extern cvar_t sv_public; extern cvar_t cl_netlocalping; extern cvar_t cl_netport; extern cvar_t sv_netport; extern cvar_t net_address; extern cvar_t net_address_ipv6; extern cvar_t net_usesizelimit; extern cvar_t net_burstreserve; qboolean NetConn_CanSend(netconn_t *conn); int NetConn_SendUnreliableMessage(netconn_t *conn, sizebuf_t *data, protocolversion_t protocol, int rate, int burstsize, qboolean quakesignon_suppressreliables); qboolean NetConn_HaveClientPorts(void); qboolean NetConn_HaveServerPorts(void); void NetConn_CloseClientPorts(void); void NetConn_OpenClientPorts(void); void NetConn_CloseServerPorts(void); void NetConn_OpenServerPorts(int opennetports); void NetConn_UpdateSockets(void); lhnetsocket_t *NetConn_ChooseClientSocketForAddress(lhnetaddress_t *address); lhnetsocket_t *NetConn_ChooseServerSocketForAddress(lhnetaddress_t *address); void NetConn_Init(void); void NetConn_Shutdown(void); netconn_t *NetConn_Open(lhnetsocket_t *mysocket, lhnetaddress_t *peeraddress); void NetConn_Close(netconn_t *conn); void NetConn_Listen(qboolean state); int NetConn_Read(lhnetsocket_t *mysocket, void *data, int maxlength, lhnetaddress_t *peeraddress); int NetConn_Write(lhnetsocket_t *mysocket, const void *data, int length, const lhnetaddress_t *peeraddress); int NetConn_WriteString(lhnetsocket_t *mysocket, const char *string, const lhnetaddress_t *peeraddress); int NetConn_IsLocalGame(void); void NetConn_ClientFrame(void); void NetConn_ServerFrame(void); void NetConn_SleepMicroseconds(int microseconds); void NetConn_Heartbeat(int priority); void Net_Stats_f(void); #ifdef CONFIG_MENU void NetConn_QueryMasters(qboolean querydp, qboolean queryqw); void NetConn_QueryQueueFrame(void); void Net_Slist_f(void); void Net_SlistQW_f(void); void Net_Refresh_f(void); /// ServerList interface (public) /// manually refresh the view set, do this after having changed the mask or any other flag void ServerList_RebuildViewList(void); void ServerList_ResetMasks(void); void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryqw, qboolean consoleoutput); /// called whenever net_slist_favorites changes void NetConn_UpdateFavorites(void); #endif #define MAX_CHALLENGES 128 typedef struct challenge_s { lhnetaddress_t address; double time; char string[12]; } challenge_t; extern challenge_t challenges[MAX_CHALLENGES]; #endif darkplaces/darkplaces.txt0000664000175000017500000044627513067716220015057 0ustar kalevkalevDarkPlaces engine readme : updated 20070311: About the DarkPlaces glquake engine: DarkPlaces engine was started because I was unsatisfied with the other engines available soon after the quake source release (which did little more than add some flashy effects), and craved modding features for my DarkPlaces mod, and wanted some real enhancements to the online gaming experience as well. DarkPlaces engine is the result, I hope everyone likes it. I am not very good at writing documentation, so this readme is organized as a feature list, with information on each feature, I hope it is still adequate documentation. If you have any suggestions for features to document in detail in the readme or any other questions/comments/bugreports/suggestions/etc, send me an email with the address darkplacesengine gmail com (add @ and . characters as appropriate) Input Tips: If mouse movement is jerky but framerate is high, try typing "gl_finish 1" (without quotes) into the console (makes cpu wait for gpu before ending frame, which gives lousy input drivers a chance to catch up). Graphics Tips: Visit the Color Control submenu of Options, it's near the top, fiddle with gamma (or grey level if using the color levels mode) until the grey box surrounding the white/black dither pattern matches up with the grey you see while looking at the dither from a distance, this will calibrate quake to look approximately as id Software intended, and ensure everyone sees it the same. Note: Different resolutions may be different intensities depending on monitor. Note2: ATI Radeon Catalyst 3.10 drivers seem to have a weird gamma limiting 'feature' which rejects gamma settings it doesn't like, feel free to complain to ATI about this if it gets in your way (it probably will). Visit the Effects Options submenu of Options, and check out the options. Networking tips: Visit the Player Setup submenu of the Multiplayer menu to configure your network speed (as well as the usual settings from quake like name and colors). To host a server behind a router/firewall, simply set up a port forward on the UDP port you are running the server on (default is 26000), to forward incoming UDP packets on that port to the server, then people can connect. To make your server show up on the server browser (in the Join Game menu), either set sv_public 1 in the console, or use the multiplayer new game menu and check the Public server checkbox. Supported games: Quake : -quake, this is active by default, gamedirs: id1 Quake: Scourge of Armagon : -hipnotic or hipnotic in executable name or path, gamedirs: hipnotic, id1 Quake: Dissolution of Eternity : -rogue or rogue in executable name or path, gamedirs: rogue, id1 Nexuiz : -nexuiz or nexuiz in executable name or path, gamedirs: data Nehahra : -nehahra or nehahra in executable name or path, gamedirs: nehahra, id1 GoodVsBad2 : -goodvsbad2 or gvb2 in executable name or path, gamedirs: rts BattleMech : -battlemech or battlemech in executable name or path, gamedirs: base PrydonGate : -prydon or prydon in executable name or path, gamedirs: prydon These games are considered officially supported, if any problems are seen, please make sure you are running the latest version of the game and engine, if you are, please report the problem. Graphics features: Redesigned effects including smoke, blood, bubbles and explosions. Better looking dynamic lights. External texture support (see Replacement Content section below) Realtime bumpmapped lighting/shadowing support (r_shadow_realtime_world cvar) with many options. (note: very slow if you do not have .rtlights files installed, be sure to get some from dpmod or search on the web for rtlights files) .rtlights file support (improves performance/appearance of realtime lighting) .rtlights file editing (see r_editlights_help in game) Alpha blended sprites (instead of glquake's masked sprites). Interpolated entity movement and animations (both models and sprites). Overbright and fullbright support on walls and models (like winquake). Colormapping support on any q1 model (like winquake). Fog (set with "fog density red green blue" command) Skybox (loadsky "mtnsun_" will load "env/mtnsun_ft.tga" and so on). Sky rendering improved (no more glquake distortion). Sky polygons obscure geometry just like in winquake. Color calibration menu to ensure a proper Quake experience. Improved model lighting (directional shading). No messy .ms2 model mesh files (no glquake dir anymore either). New improved crosshair (team color coded). Improved image loading (smoother menus and such). Ability to disable particle effects (cl_particles* cvars). Decals (cl_decals cvar to enable). Stainmaps (cl_stainmap cvar to enable). Sorted transparent stuff to render better. Improved multitexture support (r_textureunits (1-4 supported), needs gl_combine 1 because of overbright) Improved chase cam (chase_active 1 no longer goes into walls) More configurable console background (scr_conalpha and scr_conbrightness) Optional fullbrights (r_fullbrights 0/1 followed by r_restart) Dynamic Farclip (no distance limits in huge maps) Improved gl_flashblend (now renders a corona instead of an ugly blob) DynamicLight coronas (more realism) Transparent statusbar (sbar_alpha) that does not block your view as much. No 8bit texture uploads (fixes 'green' walls in the distance). Fixed view blends (glquake was quite broken). JPEG texture support using libjpeg (Thanks Elric) Video Options, Color Control, and Effects Options menus added, and more options. .dlit file support (produced by hmap2 -light) for fast per-pixel lighting without shadowing. pointfile command is improved (for leak finding in maps when you have a .pnt file from a failed qbsp compile) configurable particle effects (effectinfo.txt, can be reloaded at any time by cl_particles_reloadeffects command for quick testing) fixed envmap command (makes a skybox of the current scene) Sound features: Ogg and wav file overrides for cd tracks (example: sound/cdtracks/track01.ogg or .wav) (Thanks Elric) Streaming ogg sounds to save memory (Ogg sounds over a certain size are streamed automatically) (Thanks Elric) Ogg Vorbis sound support (all .wav sounds look for .ogg if the .wav is missing, useful for making mods smaller, particularly useful for cd tracks) (Thanks Elric) Stereo sound file support (useful for cd tracks) 7.1 surround sound mixing support (snd_channels cvar selects how many to use, default 2 for stereo) Client features: showtime cvar. showdate cvar. -benchmark option to run automated timedemo benchmarks (-benchmark demo1 does +timedemo demo1 and quits immediately when finished) timedemo automatically puts results in gamedir/benchmark.log Slightly improved aiming on quake servers (does not support proquake aiming). -sndspeed samplerate (default: 44100, quake used 11025) snd_swapstereo cvar (for people with backwards SB16 sound cards) Saves video settings to config and restores them properly Ability to change video settings during game (video options menu or vid_* cvars) showfps cvar. Sends 20fps network packets to improve modem play instead of one per frame. (sys_ticrate controls network framerate) Allow skin colormaps 14 and 15 (freaky :) Longer chat messages. No more 72fps limit, cl_maxfps lets you decide. Support for more mouse buttons (mouse1-mouse16, mwheelup/mwheeldown are aliases to mouse4 and mouse5). Server browser for public (sv_public 1) darkplaces servers as well as quakeworld servers. log_file cvar to log console messages to a file. condump command to dump recent console history to a file. PK3 archive support with compression support using zlib (Thanks Elric) maps command lists installed maps tab completion of map names on map/changelevel commands tab completion of rcon commands .ent file replacement allows you to modify sky and fog settings on a per-map basis (use sv_saveentfile command in singleplayer and then edit the worldspawn entity in a text editor, for example setting "sky" "mtnsun_" to load the skybox mtnsun_ in your favorite maps, or "fog" "0.03 0.2 0.2 0.2" to put fog in the level) Switchable bindmaps (in_bind command allows you to bind keys in one of 8 bindmaps, 0-7, in_bindmap command allows you to select two active bindmaps to use at once, ones missing in the first are checked in the second) Options menu "Reset to Defaults" option works better than in Quake cvarlist and cmdlist commands improved tab completion of commands and cvars, with default value and description listed curl command (downloads a URL to a pk3 archive and loads it) fs_rescan command (allows you to load a newly installed pak/pk3 archive without quitting the game) saveconfig command (allows you to save settings before quitting the game, mostly useful when debugging the engine if you expect it to crash) gamedir command to change current mod (only works while disconnected). QuakeWorld support. ProQuake message macros (%l location, %d last death location, %h health, %a armor, %x rockets, %c cells, %t current time, %r rocket launcher status (I need RL, I need rockets, I have RL), %p powerup status (quad pent ring), %w weapon status (SSG:NG:SNG:GL:RL:LG). Support for ProQuake .loc files (locs/e1m1.loc or maps/e1m1.loc) Support for QIZMO .loc files (maps/e1m1.loc) Ingame editing of .loc files using locs_* commands (locs_save saves a new .loc file to maps directory) bestweapon command (takes a number sequence like bestweapon 87654321, digits corresponding to weapons, first ones are preferred over last ones, only uses weapons that have 1 ammo or more) ls and dir commands to list files in the Quake virtual filesystem (useful to find files inside paks) toggle command allows you to use a single bind to toggle a cvar between 0 and 1 slowmo cvar allows you to pause/slow down/speed up demo playback (try using these binds for example: bind f1 "slowmo 0";bind f2 "slowmo 0.1";bind f3 "slowmo 1") AVI video recording using builtin I420 codec (bind f4 "toggle cl_capturevideo"), with automatic creation of sequentially numbered avi files for each recording session. (WARNING: HUGE files, make sure you have several gigabytes of disk space available! and it is only recommended during demo playback! You will probably want to reencode these videos using VirtualDub, mencoder, or other utilities before posting them on a website) ping display in scoreboard, even on Quake servers (on DarkPlaces servers it also shows packet loss) Server features: Allows clients to connect through firewalls (automatic feature) Works behind firewalls unlike NetQuake (must port forward UDP packets on the relevant port from the firewall to the server, as with any game) More accurate movement and precise aiming. 255 player support. sv_cheats cvar controls cheats (no longer based on deathmatch). slowmo cvar controls game speed. No crash with the buggy 'teleport train' in shub's pit. Allow skin colormaps 14 and 15 (freaky :) sys_ticrate applies to listen (client) servers as well as dedicated. sv_public cvar to advertise to master server. log_file cvar to log console messages to a file. condump command to dump recent console history to a file. PK3 archive support with compression support using zlib (Thanks Elric) Option to prevent wallhacks from working (sv_cullentities_trace 1). Selectable protocol (sv_protocolname QUAKE for example allows quake clients to play, default is sv_protocolname DP7 or a later protocol) Automatic file downloads to DarkPlaces clients. Ability to send URLs to DarkPlaces clients to download pk3 archives needed to play on this server. rcon support for remote administration by trusted clients with matching rcon_password, or external quakeworld rcon tools prvm_edictset, prvm_global, prvm_globals, and prvm_globalset commands aid in QuakeC debugging prvm_printfunction function prints out the QuakeC assembly opcodes of a function, can be useful if you can't decompile the progs.dat file prvm_profile command gives call count and estimated builtin-function cost sys_colortranslation cvar controls processing of Quake3-style ^ color codes in terminal output, default is white text on windows and ANSI color output on Linux/Mac OSX sys_specialcharactertranslation controls processing of special Quake characters to make colored names more readable in terminal output Modding features: HalfLife map support (place your HalfLife wads in quake/id1/textures/ or quake/MODDIR/textures/ as the maps need them) Larger q1 and hl map size of +-32768 units. Colored lighting (.lit support) for q1 maps. Q3 map support (no shaders though), with no limits. Q2 and Q3 model support, with greatly increased limits (256 skins, 65536 frames, 65536 vertices, 65536 triangles). (Note: md2 player models are not supported because they have no skin list) Optimized QuakeC interpreter so mods run faster. Bounds checking QuakeC interpreter so mods can't do naughty things with memory. Warnings for many common QuakeC errors. Unprecached models are now a warning (does not kill the server anymore). External texture support (see dpextensions.qc DP_GFX_EXTERNALTEXTURES). Fog ("fog" key in worldspawn, same parameters as fog command). .spr32 and halflife .spr sprites supported. (Use Krimzon's tool to make spr32, and lhfire can render directly to spr32, or just use replacement textures on .spr). Skybox ("sky" key in worldspawn, works like loadsky and quake2). Stereo wav sounds supported. Ogg Vorbis sounds supported. (Thanks Elric) ATTN_NONE sounds are no longer directional (good for music). play2 sound testing command (ATTN_NONE variant of play). r_texturestats and memstats and memlist commands to give memory use info. Lighting on sprites (put ! anywhere in sprite filename to enable). More r_speeds info (now a transparent overlay instead of spewing to console). Supports rotating bmodels (use avelocity, and keep in mind the bmodel needs the "origin" key set to rotate (read up on hipnotic rotation support in your qbsp docs, or choose another qbsp if yours does not support this feature), or in q3 maps an origin brush works). More sound channels. More dynamic lights (32 changed to 256). More precached models and sounds (256 changed to 4096). Many more features documented in dpextensions.qc. (bullet tracing on models, qc player input, etc) Replacing Content: Formats supported: tga (recommended), png (loads very slowly), jpg (loads slowly), pcx, wal, lmp Usually you want to put replacement content in either id1/ or another directory such as pretty/ inside your quake directory, in DarkPlaces you can run multiple -game options at once (such as -game ctf -game pretty -game dpmod to have texture overrides from pretty, maps from ctf, and gameplay from dpmod) or multiple gamedirs specified with the gamedir console command (gamedir ctf pretty dpmod). All texture layers are optional except diffuse (the others are NOT loaded without it) Replacing skins: progs/player.mdl_0.tga - diffuse progs/player.mdl_0_norm.tga - normalmap (can have alpha channel with bumpmap height for offsetmapping/reliefmapping) progs/player.mdl_0_bump.tga - bumpmap (not loaded if normalmap is present) progs/player.mdl_0_glow.tga - glow map (use _luma if tenebrae compatibility is a concern) progs/player.mdl_0_luma.tga - alternate tenebrae-compatible name for glow map (use one or the other) progs/player.mdl_0_pants.tga - pants image, greyscale and does not cover the same pixels as the diffuse texture (this is additive blended ('Screen' mode in photoshop) ontop of the diffuse texture with a color tint according to your pants color) progs/player.mdl_0_shirt.tga - shirt image, same type as pants Replacing textures in specific maps: textures/e1m1/ecop1_6.tga textures/e1m1/ecop1_6_norm.tga textures/e1m1/ecop1_6_bump.tga textures/e1m1/ecop1_6_glow.tga textures/e1m1/ecop1_6_luma.tga textures/e1m1/ecop1_6_pants.tga - pants and shirt layers are possible on bmodel entities with quakec modifications to set their .colormap field textures/e1m1/ecop1_6_shirt.tga Replacing textures in all maps: textures/quake.tga textures/quake_norm.tga textures/quake_bump.tga textures/quake_glow.tga textures/quake_luma.tga textures/quake_pants.tga textures/quake_shirt.tga Replacing hud and menu pictures: gfx/conchars.tga Replacing models: same as in Quake, you can replace a model with exactly the same file name (including file extension), so for example an md3 player model has to be renamed progs/player.mdl, and a small box of shells in md3 format has to be renamed maps/b_shell0.bsp How to make .skin files for multiple skins on a Quake3 (md3) or DarkPlacesModel (dpm) model: These files use the same format as the ones in Quake3 (except being named modelname_0.skin, modelname_1.skin, and so on), they specify what texture to use on each part of the md3 (or zym or dpm or psk) model, their contents look like the following... torso,progs/player_default.tga says that the model part named "torso" should use the image progs/player_default.tga gun,progs/player_default.tga says that the model part named "gun" should use the image progs/player_default.tga muzzleflash,progs/player_default_muzzleflash.tga says that the model part named "muzzleflash" should use the image progs/player_default_muzzleflash.tga - this is useful for transparent skin areas which should be kept separate from opaque skins tag_head, says that the first tag is named "tag_head" - this is only useful for QuakeC mods using segmented player models so that they can look up/down without their legs rotating, don't worry about it as a user tag_torso, second tag name tag_weapon, third tag name How to install a soundtrack in ogg format These files must be in ogg or wav format, and numbers begin at 002 if you wish to replace (or install) the Quake cd music - since track 001 was the Quake data track.

quake/id1/sound/cdtracks/track002.ogg replacement track for "cd loop 2" quake/id1/sound/cdtracks/track003.ogg replacement track for "cd loop 3" Example list of filenames: quake/id1/progs/player.mdl replaces the player model) quake/id1/progs/player.mdl_0.skin text file that specifies textures to use on an md3 model) quake/id1/progs/player_default.tga texture referenced by the .skin, make sure that any special parts of this are black, like pants should be black here otherwise you get pink pants when you use a red color ingame) quake/id1/progs/player_default_pants.tga white pants area of the skin, this is colored by the engine according to your color settings, additive blended (which is called "Screen" mode in Photoshop if you wish to preview the layers)) quake/id1/progs/player_default_shirt.tga white shirt area of the skin, similar to pants described above) quake/id1/progs/player_default_norm.tga normalmap texture for player_default, alpha channel can contain a heightmap for offsetmapping (r_glsl_offsetmapping 1 in console) to use, alternatively you can use _bump.tga instead of this which is only a heightmap and the engine will generate the normalmap for you) quake/id1/progs/player_default_gloss.tga glossmap (shiny areas) for player_default) quake/id1/progs/player_default_glow.tga glowmap (glowing stuff) for player_default, this is fullbrights and such, be sure the corresponding pixels are black in the player_default.tga, because just like pants/shirt this is additive blended) quake/id1/textures/quake.tga replaces the quake logo on the arch in start.bsp) quake/id1/textures/quake_norm.tga same as for a player) quake/id1/textures/quake_gloss.tga same as for a player) quake/id1/textures/#water1.tga replaces *water1 texture in the maps, # is used instead of * in filenames) quake/id1/gfx/conchars.tga replacement font image, this was in gfx.wad in quake) quake/id1/gfx/conback.tga replacement console background, just like in quake) quake/id1/gfx/mainmenu.tga replacement main menu image, just like in quake) quake/id1/maps/b_bh25.bsp replacement for normal health pack, for example this could be an md3 model instead) quake/id1/sound/cdtracks/track002.ogg replacement track for "cd loop 2" quake/id1/sound/cdtracks/track003.ogg replacement track for "cd loop 3" Commandline options as of 2007-03-11: BSD GLX: -gl_driver drivername selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it BSD GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) BSD GLX: -novideosync disables GLX_SGI_swap_control BSD Sound: -cddev devicepath chooses which CD drive to use Client: -benchmark demoname runs a timedemo and quits, results of any timedemo can be found in gamedir/benchmark.log (for example id1/benchmark.log) Client: -demo demoname runs a playdemo and quits Client: -forceqmenu disables menu.dat (same as +forceqmenu 1) Client: -particles number changes maximum number of particles at once, default 32768 Client: -texbrightness number sets the quake palette brightness (brightness of black), allowing you to make quake textures brighter/darker, not recommended Client: -texcontrast number sets the quake palette contrast, allowing you to make quake textures brighter/darker, not recommended Client: -texgamma number sets the quake palette gamma, allowing you to make quake textures brighter/darker, not recommended Client: -useqmenu causes the first time you open the menu to use the quake menu, then reverts to menu.dat (if forceqmenu is 0) Console: -condebug logs console messages to qconsole.log, see also log_file Console: -developer enables warnings and other notices (RECOMMENDED for mod developers) Console: -nostdout disables text output to the terminal the game was launched from Filesystem: -basedir path chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1) GL: -noanisotropy disables GL_EXT_texture_filter_anisotropic (allows higher quality texturing) GL: -nocombine disables GL_ARB_texture_env_combine or GL_EXT_texture_env_combine (required for bumpmapping and faster map rendering) GL: -nocubemap disables GL_ARB_texture_cube_map (required for bumpmapping) GL: -nocva disables GL_EXT_compiled_vertex_array (renders faster) GL: -nodot3 disables GL_ARB_texture_env_dot3 (required for bumpmapping) GL: -nodrawrangeelements disables GL_EXT_draw_range_elements (renders faster) GL: -noedgeclamp disables GL_EXT_texture_edge_clamp or GL_SGIS_texture_edge_clamp (recommended, some cards do not support the other texture clamp method) GL: -nofragmentshader disables GL_ARB_fragment_shader (allows pixel shader effects, can improve per pixel lighting performance and capabilities) GL: -nomtex disables GL_ARB_multitexture (required for faster map rendering) GL: -noseparatestencil disables use of OpenGL2.0 glStencilOpSeparate and GL_ATI_separate_stencil extensions (which accelerate shadow rendering) GL: -noshaderobjects disables GL_ARB_shader_objects (required for vertex shader and fragment shader) GL: -noshadinglanguage100 disables GL_ARB_shading_language_100 (required for vertex shader and fragment shader) GL: -nostenciltwoside disables GL_EXT_stencil_two_side (which accelerate shadow rendering) GL: -notexture3d disables GL_EXT_texture3D (required for spherical lights, otherwise they render as a column) GL: -novertexshader disables GL_ARB_vertex_shader (allows vertex shader effects) Game: -battlemech runs the multiplayer topdown deathmatch game BattleMech Game: -contagiontheory runs the game Contagion Theory Game: -darsana runs the game Darsana Game: -did2 runs the game Defeat In Detail 2 Game: -goodvsbad2 runs the psychadelic RTS FPS game Good Vs Bad 2 Game: -hipnotic runs Quake mission pack 1: The Scourge of Armagon Game: -nehahra runs The Seal of Nehahra movie and game Game: -neoteric runs the game Neoteric Game: -netherworld runs the game Netherworld: Dark Master Game: -nexuiz runs the multiplayer game Nexuiz Game: -openquartz runs the game OpenQuartz, a standalone GPL replacement of the quake content Game: -prydon runs the topdown point and click action-RPG Prydon Gate Game: -quake runs the game Quake (default) Game: -rogue runs Quake mission pack 2: The Dissolution of Eternity Game: -setheral runs the multiplayer game Setheral Game: -som runs the multiplayer game Son Of Man Game: -tenebrae runs the graphics test mod known as Tenebrae (some features not implemented) Game: -teu runs The Evil Unleashed (this option is obsolete as they are not using darkplaces) Game: -thehunted runs the game The Hunted Game: -transfusion runs Transfusion (the recreation of Blood in Quake) Game: -zymotic runs the singleplayer game Zymotic Input: -nomouse disables mouse support (see also vid_mouse cvar) Input: -nomouse disables mouse support (see also vid_mouse cvar) Linux ALSA Sound: -sndpcm devicename selects which pcm device to us, default is "default" Linux GLX: -gl_driver drivername selects a GL driver library, default is libGL.so.1, useful only for using fxmesa or similar, if you don't know what this is for, you don't need it Linux GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) Linux GLX: -novideosync disables GLX_SGI_swap_control Linux Sound: -cddev devicepath chooses which CD drive to use MacOSX GLX: -nogetprocaddress disables GLX_ARB_get_proc_address (not required, more formal method of getting extension functions) MacOSX GLX: -novideosync disables GLX_SGI_swap_control SDL GL: -gl_driver drivername selects a GL driver library, default is whatever SDL recommends, useful only for 3dfxogl.dll/3dfxvgl.dll or fxmesa or similar, if you don't know what this is for, you don't need it Server: -dedicated [playerlimit] starts a dedicated server (with a command console), default playerlimit is 8 Server: -ip ipaddress sets the ip address of this machine for purposes of networking (default 0.0.0.0 also known as INADDR_ANY), use only if you have multiple network adapters and need to choose one specifically. Server: -listen [playerlimit] starts a multiplayer server with graphical client, like singleplayer but other players can connect, default playerlimit is 8 Server: -port portnumber sets the port to use for a server (default 26000, the same port as QUAKE itself), useful if you host multiple servers on your machine Sound: -nocdaudio disables CD audio support Sound: -nosound disables sound (including CD audio) Sound: -novorbis disables ogg vorbis sound support Sound: -simsound runs sound mixing but with no output Sound: -sndbits bits chooses 8 bit or 16 bit sound output Sound: -sndmono sets sound output to mono Sound: -sndquad sets sound output to 4 channel surround Sound: -sndspeed hz chooses sound output rate (supported values are 48000, 44100, 32000, 24000, 22050, 16000, 11025 (quake), 8000) Sound: -sndstereo sets sound output to stereo Video: -bpp bits performs +vid_bitsperpixel bits (example -bpp 32 or -bpp 16) Video: -fullscreen performs +vid_fullscreen 1 Video: -height pixels performs +vid_height pixels and also +vid_width pixels*4/3 if only -height is specified (example: -height 768 sets 1024x768 mode) Video: -width pixels performs +vid_width pixels and also +vid_height pixels*3/4 if only -width is specified (example: -width 1024 sets 1024x768 mode) Video: -window performs +vid_fullscreen 0 Windows DirectSound: -primarysound locks the sound hardware for exclusive use Windows DirectSound: -snoforceformat uses the format that DirectSound returns, rather than forcing it Windows GDI Input: -noforcemaccel disables setting of mouse acceleration (not used with -dinput, windows only) Windows GDI Input: -noforcemparms disables setting of mouse parameters (not used with -dinput, windows only) Windows GDI Input: -noforcemspd disables setting of mouse speed (not used with -dinput, windows only) Windows Input: -dinput enables DirectInput for mouse/joystick input Windows Input: -nojoy disables joystick support, may be a small speed increase Windows Sound: -wavonly uses wave sound instead of DirectSound Windows WGL: -gl_driver drivername selects a GL driver library, default is opengl32.dll, useful only for 3dfxogl.dll or 3dfxvgl.dll, if you don't know what this is for, you don't need it Windows WGL: -novideosync disables WGL_EXT_swap_control Full Console Variable List as of 2007-03-11: _cl_color 0 internal storage cvar for current player colors (changed by color command) _cl_name player internal storage cvar for current player name (changed by name command) _cl_playermodel internal storage cvar for current player model in Nexuiz (changed by playermodel command) _cl_playerskin internal storage cvar for current player skin in Nexuiz (changed by playerskin command) _cl_pmodel 0 internal storage cvar for current player model number in nehahra (changed by pmodel command) _cl_rate 10000 internal storage cvar for current rate (changed by rate command) _snd_mixahead 0.1 how much sound to mix ahead of time ambient_fade 100 rate of volume fading when moving from one environment to another ambient_level 0.3 volume of environment noises (water and wind) bgmvolume 1 volume of background music (such as CD music or replacement files such as sound/cdtracks/track002.ogg) cdaudioinitialized 0 indicates if CD Audio system is active chase_active 0 enables chase cam chase_back 48 chase cam distance from the player chase_stevie 0 chase cam view from above (used only by GoodVsBad2) chase_up 24 chase cam distance from the player cl_anglespeedkey 1.5 how much +speed multiplies keyboard turning speed cl_autodemo 0 records every game played, using the date/time and map name to name the demo file cl_autodemo_nameformat %Y-%m-%d_%H-%M The format of the cl_autodemo filename, followed by the map name cl_backspeed 400 backward movement speed cl_beams_instantaimhack 1 makes your lightning gun aiming update instantly cl_beams_lightatend 0 make a light at the end of the beam cl_beams_polygons 1 use beam polygons instead of models cl_beams_quakepositionhack 1 makes your lightning gun appear to fire from your waist (as in Quake and QuakeWorld) cl_bob 0.02 view bobbing amount cl_bobcycle 0.6 view bobbing speed cl_bob2 0 sideways view bobbing amount cl_bob2cycle 0.6 sideways view bobbing speed cl_bob2smooth 0.05 how fast the view goes back when you stop touching the ground cl_bobfall 0 how much the view swings down when falling (influenced by the speed you hit the ground with) cl_bobfallcycle 3 speed of the bobfall swing cl_bobfallspeed 200 necessary amount of speed for bob-falling to occur cl_bobmodel 1 enables gun bobbing cl_bobmodel_side 0.05 gun bobbing sideways sway amount cl_bobmodel_speed 7 gun bobbing speed cl_bobmodel_up 0.02 gun bobbing upward movement amount cl_leanmodel 0 enables gun leaning cl_leanmodel_side_speed 0.7 gun leaning sideways speed cl_leanmodel_side_limit 35 gun leaning sideways limit cl_leanmodel_side_highpass1 30 gun leaning sideways pre-highpass in 1/s cl_leanmodel_side_highpass 3 gun leaning sideways highpass in 1/s cl_leanmodel_side_lowpass 20 gun leaning sideways lowpass in 1/s cl_leanmodel_up_speed 0.65 gun leaning upward speed cl_leanmodel_up_limit 50 gun leaning upward limit cl_leanmodel_up_highpass1 5 gun leaning upward pre-highpass in 1/s cl_leanmodel_up_highpass 15 gun leaning upward highpass in 1/s cl_leanmodel_up_lowpass 20 gun leaning upward lowpass in 1/s cl_followmodel 0 enables gun following cl_followmodel_side_speed 0.25 gun following sideways speed cl_followmodel_side_limit 6 gun following sideways limit cl_followmodel_side_highpass1 30 gun following sideways pre-highpass in 1/s cl_followmodel_side_highpass 5 gun following sideways highpass in 1/s cl_followmodel_side_lowpass 10 gun following sideways lowpass in 1/s cl_followmodel_up_speed 0.5 gun following upward speed cl_followmodel_up_limit 5 gun following upward limit cl_followmodel_up_highpass1 60 gun following upward pre-highpass in 1/s cl_followmodel_up_highpass 2 gun following upward highpass in 1/s cl_followmodel_up_lowpass 10 gun following upward lowpass in 1/s cl_bobup 0.5 view bobbing adjustment that makes the up or down swing of the bob last longer cl_capturevideo 0 enables saving of video to a .avi file using uncompressed I420 colorspace and PCM audio, note that scr_screenshot_gammaboost affects the brightness of the output) cl_capturevideo_fps 30 how many frames per second to save (29.97 for NTSC, 30 for typical PC video, 15 can be useful) cl_capturevideo_number 1 number to append to video filename, incremented each time a capture begins cl_capturevideo_realtime 0 causes video saving to operate in realtime (mostly useful while playing, not while capturing demos), this can produce a much lower quality video due to poor sound/video sync and will abort saving if your machine stalls for over 1 second cl_curl_enabled 0 whether client's download support is enabled cl_curl_maxdownloads 1 maximum number of concurrent HTTP/FTP downloads cl_curl_maxspeed 100 maximum download speed (KiB/s) cl_deathnoviewmodel 1 hides gun model when dead cl_deathscoreboard 1 shows scoreboard (+showscores) while dead cl_decals 0 enables decals (bullet holes, blood, etc) cl_decals_fadetime 20 how long decals take to fade away cl_decals_time 0 how long before decals start to fade away cl_dlights_decaybrightness 1 reduces brightness of light flashes over time cl_dlights_decayradius 1 reduces size of light flashes over time cl_explosions_alpha_end 0 end alpha of an explosion shell (just before it disappears) cl_explosions_alpha_start 1.5 starting alpha of an explosion shell cl_explosions_lifetime 0.5 how long an explosion shell lasts cl_explosions_size_end 128 ending alpha of an explosion shell (just before it disappears) cl_explosions_size_start 16 starting size of an explosion shell cl_forwardspeed 400 forward movement speed cl_gravity 800 how much gravity to apply in client physics (should match sv_gravity) cl_itembobheight 0 how much items bob up and down (try 8) cl_itembobspeed 0.5 how frequently items bob up and down cl_joinbeforedownloadsfinish 1 if non-zero the game will begin after the map is loaded before other downloads finish cl_maxfps 1000 maximum fps cap, if game is running faster than this it will wait before running another frame (useful to make cpu time available to other programs) cl_movement 0 enables clientside prediction of your player movement cl_movement_accelerate 10 how fast you accelerate (should match sv_accelerate) cl_movement_airaccel_qw 1 ratio of QW-style air control as opposed to simple acceleration (should match sv_airaccel_qw) cl_movement_airaccel_sideways_friction 0 anti-sideways movement stabilization (should match sv_airaccel_sideways_friction) cl_movement_airaccelerate -1 how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead cl_movement_edgefriction 2 how much to slow down when you may be about to fall off a ledge (should match edgefriction) cl_movement_friction 4 how fast you slow down (should match sv_friction) cl_movement_jumpvelocity 270 how fast you move upward when you begin a jump (should match the quakec code) cl_movement_maxairspeed 30 how fast you can move while in the air (should match sv_maxairspeed) cl_movement_maxspeed 320 how fast you can move (should match sv_maxspeed) cl_movement_stepheight 18 how tall a step you can step in one instant (should match sv_stepheight) cl_movement_stopspeed 100 speed below which you will be slowed rapidly to a stop rather than sliding endlessly (should match sv_stopspeed) cl_movement_wateraccelerate -1 how fast you accelerate while in the air (should match sv_airaccelerate), if less than 0 the cl_movement_accelerate variable is used instead cl_movement_waterfriction -1 how fast you slow down (should match sv_friction), if less than 0 the cl_movement_friction variable is used instead cl_movespeedkey 2.0 how much +speed multiplies keyboard movement speed cl_netinputpacketlosstolerance 4 how many packets in a row can be lost without movement issues when using cl_movement (technically how many input messages to repeat in each packet that have not yet been acknowledged by the server) cl_netinputpacketspersecond 50 how many input packets to send to server each second cl_netlocalping 0 lags local loopback connection by this much ping time (useful to play more fairly on your own server with people with higher pings) cl_netpacketloss 0 drops this percentage of packets (incoming and outgoing), useful for testing network protocol robustness (effects failing to start, sounds failing to play, etc) cl_nettimesyncmode 2 selects method of time synchronization in client with regard to server packets, values are: 0 = no sync, 1 = exact sync (reset timing each packet), 2 = loose sync (reset timing only if it is out of bounds), 3 = tight sync and bounding cl_nodelta 0 disables delta compression of non-player entities in QW network protocol cl_nolerp 0 network update smoothing cl_noplayershadow 0 hide player shadow cl_particles 1 enables particle effects cl_particles_blood 1 enables blood effects cl_particles_blood_alpha 0.5 opacity of blood cl_particles_blood_bloodhack 1 make certain quake particle() calls create blood effects instead cl_particles_bubbles 1 enables bubbles (used by multiple effects) cl_particles_bulletimpacts 1 enables bulletimpact effects cl_particles_explosions_shell 0 enables polygonal shell from explosions cl_particles_explosions_smokes 0 enables smoke from explosions cl_particles_explosions_sparks 1 enables sparks from explosions cl_particles_quake 0 makes particle effects look mostly like the ones in Quake cl_particles_quality 1 multiplies number of particles and reduces their alpha cl_particles_size 1 multiplies particle size cl_particles_smoke 1 enables smoke (used by multiple effects) cl_particles_smoke_alpha 0.5 smoke brightness cl_particles_smoke_alphafade 0.55 brightness fade per second cl_particles_sparks 1 enables sparks (used by multiple effects) cl_pitchspeed 150 keyboard pitch turning speed cl_port 0 forces client to use chosen port number if not 0 cl_prydoncursor 0 enables a mouse pointer which is able to click on entities in the world, useful for point and click mods, see PRYDON_CLIENTCURSOR extension in dpextensions.qc cl_rollangle 2.0 how much to tilt the view when strafing cl_rollspeed 200 how much strafing is necessary to tilt the view cl_serverextension_download 0 indicates whether the server supports the download command cl_shownet 0 1 = print packet size, 2 = print packet message list cl_sidespeed 350 strafe movement speed cl_slowmo 1 speed of game time (should match slowmo) cl_sound_hknighthit hknight/hit.wav sound to play during TE_KNIGHTSPIKE (empty cvar disables sound) cl_sound_r_exp3 weapons/r_exp3.wav sound to play during TE_EXPLOSION and related effects (empty cvar disables sound) cl_sound_ric1 weapons/ric1.wav sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) cl_sound_ric2 weapons/ric2.wav sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) cl_sound_ric3 weapons/ric3.wav sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) cl_sound_tink1 weapons/tink1.wav sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound) cl_sound_wizardhit wizard/hit.wav sound to play during TE_WIZSPIKE (empty cvar disables sound) cl_stainmaps 1 stains lightmaps, much faster than decals but blurred cl_stainmaps_clearonload 1 clear stainmaps on map restart cl_stairsmoothspeed 160 how fast your view moves upward/downward when running up/down stairs cl_upspeed 400 vertical movement speed (while swimming or flying) cl_viewmodel_scale 1 changes size of gun model, lower values prevent poking into walls but cause strange artifacts on lighting and especially r_stereo/vid_stereobuffer options where the size of the gun becomes visible cl_yawspeed 140 keyboard yaw turning speed cmdline 0 contains commandline the engine was launched with collision_endnudge 0 how much to bias collision trace end collision_enternudge 0 how much to bias collision entry fraction collision_impactnudge 0.03125 how much to back off from the impact collision_leavenudge 0 how much to bias collision exit fraction collision_prefernudgedfraction 1 whether to sort collision events by nudged fraction (1) or real fraction (0) collision_startnudge 0 how much to bias collision trace start con_closeontoggleconsole 1 allows toggleconsole binds to close the console as well con_chat 0 how many chat lines to show in a dedicated chat area con_chatpos 0 where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top) con_chatsize 8 chat text size in virtual 2D pixels con_chattime 30 how long chat lines last, in seconds con_chatwidth 1.0 relative chat window width con_notify 4 how many notify lines to show (0-32) con_notifyalign 3 how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default) con_notifysize 8 notify text size in virtual 2D pixels con_notifytime 3 how long notify lines last, in seconds con_textsize 8 console text size in virtual 2D pixels coop 0 coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch) crosshair 0 selects crosshair to use (0 is none) crosshair_color_alpha 1 how opaque the crosshair should be crosshair_color_blue 0 customizable crosshair color crosshair_color_green 0 customizable crosshair color crosshair_color_red 1 customizable crosshair color crosshair_size 1 adjusts size of the crosshair on the screen csqc_progcrc -1 CRC of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1 csqc_progname csprogs.dat name of csprogs.dat file to load csqc_progsize -1 file size of csprogs.dat file to load (-1 is none), only used during level changes and then reset to -1 cutscene 1 enables cutscenes in nehahra, can be used by other mods deathmatch 0 deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons) demo_nehahra 0 reads all quake demos as nehahra movie protocol developer 0 prints additional debugging messages and information (recommended for modders and level designers) developer_entityparsing 0 prints detailed network entities information each time a packet is received developer_memory 0 prints debugging information about memory allocations developer_memorydebug 0 enables memory corruption checks (very slow) developer_networkentities 0 prints received entities, value is 0-4 (higher for more info) developer_networking 0 prints all received and sent packets (recommended only for debugging) developer_texturelogging 0 produces a textures.log file containing names of skins and map textures the engine tried to load edgefriction 2 how much you slow down when nearing a ledge you might fall off forceqmenu 0 enables the quake menu instead of the quakec menu.dat (if present) fov 90 field of vision, 1-170 degrees, default 90, some players use 110-130 fraglimit 0 ends level if this many frags is reached by any player freelook 1 mouse controls pitch instead of forward/back gamecfg 0 unused cvar in quake, can be used by mods gameversion 0 version of game data (mod-specific), when client and server gameversion mismatch in the server browser the server is shown as incompatible gl_combine 1 faster rendering by using GL_ARB_texture_env_combine extension (part of OpenGL 1.3 and above) gl_dither 1 enables OpenGL dithering (16bit looks bad with this off) gl_ext_separatetencil 1 make use of OpenGL 2.0 glStencilOpSeparate or GL_ATI_separate_stencil extension gl_ext_stenciltwoside 1 make use of GL_EXT_stenciltwoside extension (NVIDIA only) gl_finish 0 make the cpu wait for the graphics processor at the end of each rendered frame (can help with strange input or video lag problems on some machines) gl_flashblend 0 render bright coronas for dynamic lights instead of actual lighting, fast but ugly gl_fogblue 0.3 nehahra fog color blue value (for Nehahra compatibility only) gl_fogdensity 0.25 nehahra fog density (recommend values below 0.1) (for Nehahra compatibility only) gl_fogenable 0 nehahra fog enable (for Nehahra compatibility only) gl_fogend 0 nehahra fog end distance (for Nehahra compatibility only) gl_foggreen 0.3 nehahra fog color green value (for Nehahra compatibility only) gl_fogred 0.3 nehahra fog color red value (for Nehahra compatibility only) gl_fogstart 0 nehahra fog start distance (for Nehahra compatibility only) gl_lightmaps 0 draws only lightmaps, no texture (for level designers) gl_lockarrays 0 enables use of glLockArraysEXT, may cause glitches with some broken drivers, and may be slower than normal gl_lockarrays_minimumvertices 1 minimum number of vertices required for use of glLockArraysEXT, setting this too low may reduce performance gl_max_size 2048 maximum allowed texture size, can be used to reduce video memory usage, note: this is automatically reduced to match video card capabilities (such as 256 on 3Dfx cards before Voodoo4/5) gl_mesh_drawrangeelements 1 use glDrawRangeElements function if available instead of glDrawElements (for performance comparisons or bug testing) gl_mesh_testarrayelement 0 use glBegin(GL_TRIANGLES);glArrayElement();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements) gl_mesh_testmanualfeeding 0 use glBegin(GL_TRIANGLES);glTexCoord2f();glVertex3f();glEnd(); primitives instead of glDrawElements (useful to test for driver bugs with glDrawElements) gl_paranoid 0 enables OpenGL error checking and other tests gl_picmip 0 reduces resolution of textures by powers of 2, for example 1 will halve width/height, reducing texture memory usage by 75% gl_polyblend 1 tints view while underwater, hurt, etc gl_printcheckerror 0 prints all OpenGL error checks, useful to identify location of driver crashes gl_texture_anisotropy 1 anisotropic filtering quality (if supported by hardware), 1 sample (no anisotropy) and 8 sample (8 tap anisotropy) are recommended values halflifebsp 0 indicates the current map is hlbsp format (useful to know because of different bounding box sizes) host_framerate 0 locks frame timing to this value in seconds, 0.05 is 20fps for example, note that this can easily run too fast, use cl_maxfps if you want to limit your framerate instead, or sys_ticrate to limit server speed host_speeds 0 reports how much time is used in server/graphics/sound hostname UNNAMED server message to show in server browser in_pitch_max 90 how far upward you can aim (quake used 80 in_pitch_min -90 how far downward you can aim (quake used -70 joy_axisforward 1 which joystick axis to query for forward/backward movement joy_axispitch 3 which joystick axis to query for looking up/down joy_axisroll -1 which joystick axis to query for tilting head right/left joy_axisside 0 which joystick axis to query for right/left movement joy_axisup -1 which joystick axis to query for up/down movement joy_axisyaw 2 which joystick axis to query for looking right/left joy_deadzoneforward 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_deadzonepitch 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_deadzoneroll 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_deadzoneside 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_deadzoneup 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_deadzoneyaw 0 deadzone tolerance, suggested values are in the range 0 to 0.01 joy_detected 0 number of joysticks detected by engine joy_enable 1 enables joystick support joy_index 0 selects which joystick to use if you have multiple joy_sensitivityforward -1 movement multiplier joy_sensitivitypitch 1 movement multiplier joy_sensitivityroll 1 movement multiplier joy_sensitivityside 1 movement multiplier joy_sensitivityup 1 movement multiplier joy_sensitivityyaw -1 movement multiplier joyadvanced 0 use more than 2 axis joysticks (configuring this is very technical) joyadvaxisr 0 axis mapping for joyadvanced 1 mode joyadvaxisu 0 axis mapping for joyadvanced 1 mode joyadvaxisv 0 axis mapping for joyadvanced 1 mode joyadvaxisx 0 axis mapping for joyadvanced 1 mode joyadvaxisy 0 axis mapping for joyadvanced 1 mode joyadvaxisz 0 axis mapping for joyadvanced 1 mode joyforwardsensitivity -1.0 how fast the joystick moves forward joyforwardthreshold 0.15 minimum joystick movement necessary to move forward joyname joystick name of joystick to use (informational only, used only by joyadvanced 1 mode) joypitchsensitivity 1.0 how fast the joystick looks up/down joypitchthreshold 0.15 minimum joystick movement necessary to look up/down joysidesensitivity -1.0 how fast the joystick moves sideways (strafing) joysidethreshold 0.15 minimum joystick movement necessary to move sideways (strafing) joystick 0 enables joysticks joywwhack1 0.0 special hack for wingman warrior joywwhack2 0.0 special hack for wingman warrior joyyawsensitivity -1.0 how fast the joystick turns left/right joyyawthreshold 0.15 minimum joystick movement necessary to turn left/right locs_enable 1 enables replacement of certain % codes in chat messages: %l (location), %d (last death location), %h (health), %a (armor), %x (rockets), %c (cells), %r (rocket launcher status), %p (powerup status), %w (weapon status), %t (current time in level) locs_show 0 shows defined locations for editing purposes log_file filename to log messages to lookspring 0 returns pitch to level with the floor when no longer holding a pitch key lookstrafe 0 move instead of turning m_filter 0 smoothes mouse movement, less responsive but smoother aiming m_forward 1 mouse forward speed multiplier m_pitch 0.022 mouse pitch speed multiplier m_side 0.8 mouse side speed multiplier m_yaw 0.022 mouse yaw speed multiplier mcbsp 0 indicates the current map is mcbsp format (useful to know because of different bounding box sizes) menu_options_colorcontrol_correctionvalue 0.5 intensity value that matches up to white/black dither pattern, should be 0.5 for linear color mod_q3bsp_curves_collisions 1 enables collisions with curves (SLOW) mod_q3bsp_debugtracebrush 0 selects different tracebrush bsp recursion algorithms (for debugging purposes only) mod_q3bsp_lightmapmergepower 4 merges the quake3 128x128 lightmap textures into larger lightmap group textures to speed up rendering, 1 = 256x256, 2 = 512x512, 3 = 1024x1024, 4 = 2048x2048, 5 = 4096x4096, ... mod_q3bsp_optimizedtraceline 1 whether to use optimized traceline code for line traces (as opposed to tracebox code) nehx00 0 nehahra data storage cvar (used in singleplayer) nehx01 0 nehahra data storage cvar (used in singleplayer) nehx02 0 nehahra data storage cvar (used in singleplayer) nehx03 0 nehahra data storage cvar (used in singleplayer) nehx04 0 nehahra data storage cvar (used in singleplayer) nehx05 0 nehahra data storage cvar (used in singleplayer) nehx06 0 nehahra data storage cvar (used in singleplayer) nehx07 0 nehahra data storage cvar (used in singleplayer) nehx08 0 nehahra data storage cvar (used in singleplayer) nehx09 0 nehahra data storage cvar (used in singleplayer) nehx10 0 nehahra data storage cvar (used in singleplayer) nehx11 0 nehahra data storage cvar (used in singleplayer) nehx12 0 nehahra data storage cvar (used in singleplayer) nehx13 0 nehahra data storage cvar (used in singleplayer) nehx14 0 nehahra data storage cvar (used in singleplayer) nehx15 0 nehahra data storage cvar (used in singleplayer) nehx16 0 nehahra data storage cvar (used in singleplayer) nehx17 0 nehahra data storage cvar (used in singleplayer) nehx18 0 nehahra data storage cvar (used in singleplayer) nehx19 0 nehahra data storage cvar (used in singleplayer) net_address 0.0.0.0 network address to open ports on net_address_ipv6 [0:0:0:0:0:0:0:0] network address to open ipv6 ports on net_connectfloodblockingtimeout 5 when a connection packet is received, it will block all future connect packets from that IP address for this many seconds (cuts down on connect floods) net_connecttimeout 10 after requesting a connection, the client must reply within this many seconds or be dropped (cuts down on connect floods) net_messagetimeout 300 drops players who have not sent any packets for this many seconds net_slist_maxtries 3 how many times to ask the same server for information (more times gives better ping reports but takes longer) net_slist_queriesperframe 4 maximum number of server information requests to send each rendered frame (guards against low framerates causing problems) net_slist_queriespersecond 20 how many server information requests to send per second net_slist_timeout 4 how long to listen for a server information response before giving up noaim 1 QW option to disable vertical autoaim noexit 0 kills anyone attempting to use an exit nomonsters 0 unused cvar in quake, can be used by mods nosound 0 disables sound pausable 1 allow players to pause or not port 26000 server port for players to connect to pr_checkextension 1 indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions) prvm_boundscheck 1 enables detection of out of bounds memory access in the QuakeC code being run (in other words, prevents really exceedingly bad QuakeC code from doing nasty things to your computer) prvm_statementprofiling 0 counts how many times each QuakeC statement has been executed, these counts are displayed in prvm_printfunction output (if enabled) prvm_traceqc 0 prints every QuakeC statement as it is executed (only for really thorough debugging!) qport 0 identification key for playing on qw servers (allows you to maintain a connection to a quakeworld server even if your port changes) r_ambient 0 brightens map, value is 0-128 r_batchmode 1 selects method of rendering multiple surfaces with one driver call (values are 0, 1, 2, etc...) r_bloom 0 enables bloom effect (makes bright pixels affect neighboring pixels) r_bloom_blur 4 how large the glow is r_bloom_brighten 2 how bright the glow is, after subtract/power r_bloom_colorexponent 1 how exagerated the glow is r_bloom_colorscale 1 how bright the glow is r_bloom_colorsubtract 0.125 reduces bloom colors by a certain amount r_bloom_resolution 320 what resolution to perform the bloom effect at (independent of screen resolution) r_coronas 1 brightness of corona flare effects around certain lights, 0 disables corona effects r_cullentities_trace 1 probabistically cull invisible entities r_cullentities_trace_delay 1 number of seconds until the entity gets actually culled r_cullentities_trace_enlarge 0 box enlargement for entity culling r_cullentities_trace_samples 2 number of samples to test for entity culling r_draweffects 1 renders temporary sprite effects r_drawentities 1 draw entities (doors, players, projectiles, etc) r_drawexplosions 1 enables rendering of explosion shells (see also cl_particles_explosions_shell) r_drawparticles 1 enables drawing of particles r_drawportals 0 shows portals (separating polygons) in world interior in quake1 maps r_drawviewmodel 1 draw your weapon model r_dynamic 1 enables dynamic lights (rocket glow and such) r_editlights 0 enables .rtlights file editing mode r_editlights_cursordistance 1024 maximum distance of cursor from eye r_editlights_cursorgrid 4 snaps cursor to this grid size r_editlights_cursorpushback 0 how far to pull the cursor back toward the eye r_editlights_cursorpushoff 4 how far to push the cursor off the impacted surface r_editlights_quakelightsizescale 1 changes size of light entities loaded from a map r_explosionclip 1 enables collision detection for explosion shell (so that it flattens against walls and floors) r_fullbright 0 makes map very bright and renders faster r_fullbrights 1 enables glowing pixels in quake textures (changes need r_restart to take effect) r_glsl 1 enables use of OpenGL 2.0 pixel shaders for lighting r_glsl_deluxemapping 1 use per pixel lighting on deluxemap-compiled q3bsp maps (or a value of 2 forces deluxemap shading even without deluxemaps) r_glsl_offsetmapping 0 offset mapping effect (also known as parallax mapping or virtual displacement mapping) r_glsl_offsetmapping_reliefmapping 0 relief mapping effect (higher quality) r_glsl_offsetmapping_scale 0.04 how deep the offset mapping effect is r_hdr 0 enables High Dynamic Range bloom effect (higher quality version of r_bloom) r_hdr_glowintensity 1 how bright light emitting textures should appear r_hdr_range 4 how much dynamic range to render bloom with (equivilant to multiplying r_bloom_brighten by this value and dividing r_bloom_colorscale by this value) r_hdr_scenebrightness 1 global rendering brightness r_lerpimages 1 bilinear filters images when scaling them up to power of 2 size (mode 1), looks better than glquake (mode 0) r_lerpmodels 1 enables animation smoothing on models r_lerpsprites 1 enables animation smoothing on sprites r_letterbox 0 reduces vertical height of view to simulate a letterboxed movie effect (can be used by mods for cutscenes) r_lightmaprgba 1 whether to use RGBA (32bit) or RGB (24bit) lightmaps r_lightningbeam_color_blue 1 color of the lightning beam effect r_lightningbeam_color_green 1 color of the lightning beam effect r_lightningbeam_color_red 1 color of the lightning beam effect r_lightningbeam_qmbtexture 0 load the qmb textures/particles/lightning.pcx texture instead of generating one, can look better r_lightningbeam_repeatdistance 128 how far to stretch the texture along the lightning beam effect r_lightningbeam_scroll 5 speed of texture scrolling on the lightning beam effect r_lightningbeam_thickness 4 thickness of the lightning beam effect r_lockpvs 0 disables pvs switching, allows you to walk around and inspect what is visible from a given location in the map (anything not visible from your current location will not be drawn) r_lockvisibility 0 disables visibility updates, allows you to walk around and inspect what is visible from a given viewpoint in the map (anything offscreen at the moment this is enabled will not be drawn) r_mipskins 0 mipmaps skins (so they become blurrier in the distance), disabled by default because it tends to blur with strange border colors from the skin r_mipsprites 1 mipmaps skins (so they become blurrier in the distance), unlike skins the sprites do not have strange border colors r_nearclip 1 distance from camera of nearclip plane r_nosurftextures 0 pretends there was no texture lump found in the q1bsp/hlbsp loading (useful for debugging this rare case) r_novis 0 draws whole level, see also sv_cullentities_pvs 0 r_precachetextures 1 0 = never upload textures until used, 1 = upload most textures before use (exceptions: rarely used skin colormap layers), 2 = upload all textures before use (can increase texture memory usage significantly) r_q3bsp_renderskydepth 0 draws sky depth masking in q3 maps (as in q1 maps), this means for example that sky polygons can hide other things r_qb1sp_skymasking 1 allows sky polygons in quake1 maps to obscure other geometry r_render 1 enables rendering calls (you want this on!) r_shadow_bumpscale_basetexture 0 generate fake bumpmaps from diffuse textures at this bumpyness, try 4 to match tenebrae, higher values increase depth, requires r_restart to take effect r_shadow_bumpscale_bumpmap 4 what magnitude to interpret _bump.tga textures as, higher values increase depth, requires r_restart to take effect r_shadow_culltriangles 1 performs more expensive tests to remove unnecessary triangles of lit surfaces r_shadow_debuglight -1 renders only one light, for level design purposes or debugging r_shadow_frontsidecasting 1 whether to cast shadows from illuminated triangles (front side of model) or unlit triangles (back side of model) r_shadow_gloss 1 0 disables gloss (specularity) rendering, 1 uses gloss if textures are found, 2 forces a flat metallic specular effect on everything without textures (similar to tenebrae) r_shadow_gloss2intensity 0.125 how bright the forced flat gloss should look if r_shadow_gloss is 2 r_shadow_glossexponent 32 how 'sharp' the gloss should appear (specular power) r_shadow_glossintensity 1 how bright textured glossmaps should look if r_shadow_gloss is 1 or 2 r_shadow_lightattenuationpower 0.5 changes attenuation texture generation (does not affect r_glsl lighting) r_shadow_lightattenuationscale 1 changes attenuation texture generation (does not affect r_glsl lighting) r_shadow_lightintensityscale 1 renders all world lights brighter or darker r_shadow_lightradiusscale 1 renders all world lights larger or smaller r_shadow_portallight 1 use portal culling to exactly determine lit triangles when compiling world lights r_shadow_projectdistance 1000000 how far to cast shadows r_shadow_realtime_dlight 1 enables rendering of dynamic lights such as explosions and rocket light r_shadow_realtime_dlight_portalculling 0 enables portal optimization on dynamic lights (slow!) r_shadow_realtime_dlight_shadows 1 enables rendering of shadows from dynamic lights r_shadow_realtime_dlight_svbspculling 0 enables svbsp optimization on dynamic lights (very slow!) r_shadow_realtime_world 0 enables rendering of full world lighting (whether loaded from the map, or a .rtlights file, or a .ent file, or a .lights file produced by hlight) r_shadow_realtime_world_compile 1 enables compilation of world lights for higher performance rendering r_shadow_realtime_world_compileportalculling 1 enables portal-based culling optimization during compilation r_shadow_realtime_world_compileshadow 1 enables compilation of shadows from world lights for higher performance rendering r_shadow_realtime_world_compilesvbsp 1 enables svbsp optimization during compilation r_shadow_realtime_world_lightmaps 0 brightness to render lightmaps when using full world lighting, try 0.5 for a tenebrae-like appearance r_shadow_realtime_world_shadows 1 enables rendering of shadows from world lights r_shadow_scissor 1 use scissor optimization of light rendering (restricts rendering to the portion of the screen affected by the light) r_shadow_polygonfactor 0 how much to enlarge shadow volume polygons when rendering (should be 0!) r_shadow_polygonoffset 1 how much to push shadow volumes into the distance when rendering, to reduce chances of zfighting artifacts (should not be less than 0) r_shadow_texture3d 1 use 3D voxel textures for spherical attenuation rather than cylindrical (does not affect r_glsl lighting) r_shadows 0 casts fake stencil shadows from models onto the world (rtlights are unaffected by this) r_shadows_throwdistance 500 how far to cast shadows from models r_showcollisionbrushes 0 draws collision brushes in quake3 maps (mode 1), mode 2 disables rendering of world (trippy!) r_showcollisionbrushes_polygonfactor -1 expands outward the brush polygons a little bit, used to make collision brushes appear infront of walls r_showcollisionbrushes_polygonoffset 0 nudges brush polygon depth in hardware depth units, used to make collision brushes appear infront of walls r_showdisabledepthtest 0 disables depth testing on r_show* cvars, allowing you to see what hidden geometry the graphics card is processing r_showlighting 0 shows areas lit by lights, useful for finding out why some areas of a map render slowly (bright orange = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful r_shownormals 0 shows per-vertex surface normals and tangent vectors for bumpmapped lighting r_showshadowvolumes 0 shows areas shadowed by lights, useful for finding out why some areas of a map render slowly (bright blue = lots of passes = slow), a value of 2 disables depth testing which can be interesting but not very useful r_showsurfaces 0 1 shows surfaces as different colors, or a value of 2 shows triangle draw order (for analyzing whether meshes are optimized for vertex cache) r_showtris 0 shows triangle outlines, value controls brightness (can be above 1) r_skeletal_debugbone -1 development cvar for testing skeletal model code r_skeletal_debugbonecomponent 3 development cvar for testing skeletal model code r_skeletal_debugbonevalue 100 development cvar for testing skeletal model code r_skeletal_debugtranslatex 1 development cvar for testing skeletal model code r_skeletal_debugtranslatey 1 development cvar for testing skeletal model code r_skeletal_debugtranslatez 1 development cvar for testing skeletal model code r_sky 1 enables sky rendering (black otherwise) r_skyscroll1 1 speed at which upper clouds layer scrolls in quake sky r_skyscroll2 2 speed at which lower clouds layer scrolls in quake sky r_smoothnormals_areaweighting 1 uses significantly faster (and supposedly higher quality) area-weighted vertex normals and tangent vectors rather than summing normalized triangle normals and tangents r_speeds 0 displays rendering statistics and per-subsystem timings r_stereo_redblue 0 red/blue anaglyph stereo glasses (note: most of these glasses are actually red/cyan, try that one too) r_stereo_redcyan 0 red/cyan anaglyph stereo glasses, the kind given away at drive-in movies like Creature From The Black Lagoon In 3D r_stereo_redgreen 0 red/green anaglyph stereo glasses (for those who don't mind yellow) r_stereo_separation 4 separation of eyes in the world (try negative values too) r_stereo_sidebyside 0 side by side views (for those who can't afford glasses but can afford eye strain) r_subdivide_size 128 how large water polygons should be (smaller values produce more polygons which give better warping effects) r_subdivisions_collision_maxtess 1024 maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing) r_subdivisions_collision_maxvertices 4225 maximum vertices allowed per subdivided curve r_subdivisions_collision_mintess 1 minimum number of subdivisions (values above 1 will smooth curves that don't need it) r_subdivisions_collision_tolerance 15 maximum error tolerance on curve subdivision for collision purposes (usually a larger error tolerance than for rendering) r_subdivisions_maxtess 1024 maximum number of subdivisions (prevents curves beyond a certain detail level, limits smoothing) r_subdivisions_maxvertices 65536 maximum vertices allowed per subdivided curve r_subdivisions_mintess 1 minimum number of subdivisions (values above 1 will smooth curves that don't need it) r_subdivisions_tolerance 4 maximum error tolerance on curve subdivision for rendering purposes (in other words, the curves will be given as many polygons as necessary to represent curves at this quality) r_test 0 internal development use only, leave it alone (usually does nothing anyway) r_textshadow 0 draws a shadow on all text to improve readability r_textureunits 32 number of hardware texture units reported by driver (note: setting this to 1 turns off gl_combine) r_useportalculling 1 use advanced portal culling visibility method to improve performance over just Potentially Visible Set, provides an even more significant speed improvement in unvised maps r_wateralpha 1 opacity of water polygons r_waterscroll 1 makes water scroll around, value controls how much r_waterwarp 1 warp view while underwater rcon_address server address to send rcon commands to (when not connected to a server) rcon_password password to authenticate rcon commands registered 0 indicates if this is running registered quake (whether gfx/pop.lmp was found) samelevel 0 repeats same level if level ends (due to timelimit or someone hitting an exit) saved1 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods saved2 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods saved3 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods saved4 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods savedgamecfg 0 unused cvar in quake that is saved to config.cfg on exit, can be used by mods sbar_alpha_bg 0.4 opacity value of the statusbar background image sbar_alpha_fg 1 opacity value of the statusbar weapon/item icons and numbers scr_centertime 2 how long centerprint messages show scr_conalpha 1 opacity of console background scr_conbrightness 1 brightness of console background (0 = black, 1 = image) scr_conforcewhiledisconnected 1 forces fullscreen console while disconnected scr_menuforcewhiledisconnected 0 forces menu while disconnected scr_printspeed 8 speed of intermission printing (episode end texts) scr_refresh 1 allows you to completely shut off rendering for benchmarking purposes scr_screenshot_gammaboost 1 gamma correction on saved screenshots and videos, 1.0 saves unmodified images scr_screenshot_jpeg 1 save jpeg instead of targa scr_screenshot_jpeg_quality 0.9 image quality of saved jpeg scr_screenshot_name dp prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running) scr_stipple 0 interlacing-like stippling of the display scr_zoomwindow 0 displays a zoomed in overlay window scr_zoomwindow_fov 20 fov of zoom window scr_zoomwindow_viewsizex 20 horizontal viewsize of zoom window scr_zoomwindow_viewsizey 20 vertical viewsize of zoom window scratch1 0 unused cvar in quake, can be used by mods scratch2 0 unused cvar in quake, can be used by mods scratch3 0 unused cvar in quake, can be used by mods scratch4 0 unused cvar in quake, can be used by mods sensitivity 3 mouse speed multiplier showbrand 0 shows gfx/brand.tga in a corner of the screen (different values select different positions, including centered) showdate 0 shows current date (useful on screenshots) showdate_format %Y-%m-%d format string for date showfps 0 shows your rendered fps (frames per second) showpause 1 show pause icon when game is paused showram 1 show ram icon if low on surface cache memory (not used) showspeed 0 shows your current speed (qu per second); number selects unit: 1 = qups, 2 = m/s, 3 = km/h, 4 = mph, 5 = knots showtime 0 shows current time of day (useful on screenshots) showtime_format %H:%M:%S format string for time of day showturtle 0 show turtle icon when framerate is too low (not used) skill 1 difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice) skin QW player skin name (example: base) slowmo 1.0 controls game speed, 0.5 is half speed, 2 is double speed snd_channellayout 0 channel layout. Can be 0 (auto - snd_restart needed), 1 (standard layout), or 2 (ALSA layout) snd_channels 2 number of channels for the sound ouput (2 for stereo; up to 8 supported for 3D sound) snd_initialized 0 indicates the sound subsystem is active snd_noextraupdate 0 disables extra sound mixer calls that are meant to reduce the chance of sound breakup at very low framerates snd_precache 1 loads sounds before they are used snd_show 0 shows some statistics about sound mixing snd_soundradius 1000 radius of weapon sounds and other standard sound effects (monster idle noises are half this radius and flickering light noises are one third of this radius) snd_speed 48000 sound output frequency, in hertz snd_staticvolume 1 volume of ambient sound effects (such as swampy sounds at the start of e1m2) snd_streaming 1 enables keeping compressed ogg sound files compressed, decompressing them only as needed, otherwise they will be decompressed completely at load (may use a lot of memory) snd_swapstereo 0 swaps left/right speakers for old ISA soundblaster cards snd_width 2 sound output precision, in bytes (1 and 2 supported) sv_accelerate 10 rate at which a player accelerates to sv_maxspeed sv_adminnick nick name to use for admin messages instead of host name sv_aim 2 maximum cosine angle for quake's vertical autoaim, a value above 1 completely disables the autoaim, quake used 0.93 sv_airaccelerate -1 rate at which a player accelerates to sv_maxairspeed while in the air, if less than 0 the sv_accelerate variable is used instead sv_allowdownloads 1 whether to allow clients to download files from the server (does not affect http downloads) sv_allowdownloads_archive 0 whether to allow downloads of archives (pak/pk3) sv_allowdownloads_config 0 whether to allow downloads of config files (cfg) sv_allowdownloads_dlcache 0 whether to allow downloads of dlcache files (dlcache/) sv_allowdownloads_inarchive 0 whether to allow downloads from archives (pak/pk3) sv_areagrid_mingridsize 64 minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects sv_cheats 0 enables cheat commands in any game, and cheat impulses in dpmod sv_clmovement_enable 1 whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players sv_clmovement_minping 0 if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it) sv_clmovement_minping_disabletime 1000 when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently) sv_clmovement_inputtimeout 0.2 when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld) sv_cullentities_nevercullbmodels 0 if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!) sv_cullentities_pvs 1 fast but loose culling of hidden entities sv_cullentities_stats 0 displays stats on network entities culled by various methods for each client sv_cullentities_trace 0 somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless sv_cullentities_trace_delay 1 number of seconds until the entity gets actually culled sv_cullentities_trace_enlarge 0 box enlargement for entity culling sv_cullentities_trace_prediction 1 also trace from the predicted player position sv_cullentities_trace_samples 1 number of samples to test for entity culling sv_cullentities_trace_samples_extra 2 number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight sv_curl_defaulturl default autodownload source URL sv_curl_serverpackages list of required files for the clients, separated by spaces sv_debugmove 0 disables collision detection optimizations for debugging purposes sv_echobprint 1 prints gamecode bprint() calls to server console sv_entpatch 1 enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps) sv_fixedframeratesingleplayer 0 allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate) sv_freezenonclients 0 freezes time, except for players, allowing you to walk around and take screenshots of explosions sv_friction 4 how fast you slow down sv_gameplayfix_blowupfallenzombies 1 causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them sv_gameplayfix_droptofloorstartsolid 1 prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome) sv_gameplayfix_findradiusdistancetobox 1 causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage sv_gameplayfix_grenadebouncedownslopes 1 prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface sv_gameplayfix_noairborncorpse 1 causes entities (corpses) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them sv_gameplayfix_qwplayerphysics 1 changes water jumping to make it easier to get out of water, and prevents friction on landing when bunnyhopping sv_gameplayfix_setmodelrealbox 1 fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods sv_gameplayfix_stepdown 0 attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes) sv_gameplayfix_stepwhilejumping 1 applies step-up onto a ledge even while airborn, useful if you would otherwise just-miss the floor when running across small areas with gaps (for instance running across the moving platforms in dm2, or jumping to the megahealth and red armor in dm2 rather than using the bridge) sv_gameplayfix_swiminbmodels 1 causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities sv_gameplayfix_upwardvelocityclearsongroundflag 1 prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods sv_gravity 800 how fast you fall (512 = roughly earth gravity) sv_heartbeatperiod 120 how often to send heartbeat in seconds (only used if sv_public is 1) sv_idealpitchscale 0.8 how much to look up/down slopes and stairs when not using freelook sv_jumpstep 0 whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1) sv_master1 user-chosen master server 1 sv_master2 user-chosen master server 2 sv_master3 user-chosen master server 3 sv_master4 user-chosen master server 4 sv_maxairspeed 30 maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction) sv_maxrate 10000 upper limit on client rate cvar, should reflect your network connection quality sv_maxspeed 320 maximum speed a player can accelerate to when on ground (can be exceeded by tricks) sv_maxvelocity 2000 universal speed limit on all entities sv_newflymove 0 enables simpler/buggier player physics (not recommended) sv_nostep 0 prevents MOVETYPE_STEP entities (monsters) from moving sv_playerphysicsqc 1 enables QuakeC function to override player physics sv_progs progs.dat selects which quakec progs.dat file to run sv_protocolname DP7 selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up) sv_public 0 1: advertises this server on the master server (so that players can find it in the server browser); 0: allow direct queries only; -1: do not respond to direct queries; -2: do not allow anyone to connect sv_qwmaster1 user-chosen qwmaster server 1 sv_qwmaster2 user-chosen qwmaster server 2 sv_qwmaster3 user-chosen qwmaster server 3 sv_qwmaster4 user-chosen qwmaster server 4 sv_random_seed random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging sv_ratelimitlocalplayer 0 whether to apply rate limiting to the local player in a listen server (only useful for testing) sv_sound_land demon/dland2.wav sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound) sv_sound_watersplash misc/h2ohit1.wav sound to play when MOVETYPE_FLY/TOSS/BOUNCE/STEP entity enters or leaves water (empty cvar disables the sound) sv_stepheight 18 how high you can step up (TW_SV_STEPCONTROL extension) sv_stopspeed 100 how fast you come to a complete stop sv_wallfriction 1 how much you slow down when sliding along a wall sv_wateraccelerate -1 rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead sv_waterfriction -1 how fast you slow down, if less than 0 the sv_friction variable is used instead sys_colortranslation 0 terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation) sys_colortranslation 1 terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation) sys_specialcharactertranslation 1 terminal console conchars to ASCII translation (set to 0 if your conchars.tga is for an 8bit character set or if you want raw output) sys_ticrate 0.05 how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players) sys_usetimegettime 1 use windows timeGetTime function (which has issues on some motherboards) for timing rather than QueryPerformanceCounter timer (which has issues on multicore/multiprocessor machines and processors which are designed to conserve power) team none QW team (4 character limit, example: blue) teamplay 0 teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self temp1 0 general cvar for mods to use, in stock id1 this selects which death animation to use on players (0 = random death, other values select specific death scenes) timeformat [%Y-%m-%d %H:%M:%S] time format to use on timestamped console messages timelimit 0 ends level at this time (in minutes) timestamps 0 prints timestamps on console messages v_brightness 0 brightness of black, useful for monitors that are too dark v_centermove 0.15 how long before the view begins to center itself (if freelook/+mlook/+jlook/+klook are off) v_centerspeed 500 how fast the view centers itself v_color_black_b 0 desired color of black v_color_black_g 0 desired color of black v_color_black_r 0 desired color of black v_color_enable 0 enables black-grey-white color correction curve controls v_color_grey_b 0.5 desired color of grey v_color_grey_g 0.5 desired color of grey v_color_grey_r 0.5 desired color of grey v_color_white_b 1 desired color of white v_color_white_g 1 desired color of white v_color_white_r 1 desired color of white v_contrast 1 brightness of white (values above 1 give a brighter image with increased color saturation, unlike v_gamma) v_deathtilt 1 whether to use sideways view when dead v_deathtiltangle 80 what roll angle to use when tilting the view while dead v_gamma 1 inverse gamma correction value, a brightness effect that does not affect white or black, and tends to make the image grey and dull v_hwgamma 1 enables use of hardware gamma correction ramps if available (note: does not work very well on Windows2000 and above), values are 0 = off, 1 = attempt to use hardware gamma, 2 = use hardware gamma whether it works or not v_idlescale 0 how much of the quake 'drunken view' effect to use v_ipitch_cycle 1 v_idlescale pitch speed v_ipitch_level 0.3 v_idlescale pitch amount v_iroll_cycle 0.5 v_idlescale roll speed v_iroll_level 0.1 v_idlescale roll amount v_iyaw_cycle 2 v_idlescale yaw speed v_iyaw_level 0.3 v_idlescale yaw amount v_kickpitch 0.6 how much a view kick from damage pitches your view v_kickroll 0.6 how much a view kick from damage rolls your view v_kicktime 0.5 how long a view kick from damage lasts v_psycho 0 easter egg (does not work on Windows2000 or above) vid_bitsperpixel 32 how many bits per pixel to render at (32 or 16, 32 is recommended) vid_conheight 480 virtual height of 2D graphics system vid_conwidth 640 virtual width of 2D graphics system vid_dgamouse 1 make use of DGA mouse input vid_fullscreen 1 use fullscreen (1) or windowed (0) vid_grabkeyboard 1 whether to grab the keyboard when mouse is active (prevents use of volume control keys, music player keys, etc on some keyboards) vid_hardwaregammasupported 1 indicates whether hardware gamma is supported (updated by attempts to set hardware gamma ramps) vid_height 480 resolution vid_minheight 0 minimum vid_height that is acceptable (to be set in default.cfg in mods) vid_minwidth 0 minimum vid_width that is acceptable (to be set in default.cfg in mods) vid_mouse 1 whether to use the mouse in windowed mode (fullscreen always does) vid_pixelheight 1 adjusts vertical field of vision to account for non-square pixels (1280x1024 on a CRT monitor for example) vid_refreshrate 60 refresh rate to use, in hz (higher values flicker less, if supported by your monitor) vid_stereobuffer 0 enables 'quad-buffered' stereo rendering for stereo shutterglasses, HMD (head mounted display) devices, or polarized stereo LCDs, if supported by your drivers vid_vsync 0 sync to vertical blank, prevents 'tearing' (seeing part of one frame and part of another on the screen at the same time), automatically disabled when doing timedemo benchmarks vid_width 640 resolution viewsize 100 how large the view should be, 110 disables inventory bar, 120 disables status bar volume 0.7 volume of sound effects Full console command list as of 2007-03-11: +attack begin firing +back move backward +button3 activate button3 (behavior depends on mod) +button4 activate button4 (behavior depends on mod) +button5 activate button5 (behavior depends on mod) +button6 activate button6 (behavior depends on mod) +button7 activate button7 (behavior depends on mod) +button8 activate button8 (behavior depends on mod) +button9 activate button9 (behavior depends on mod) +button10 activate button10 (behavior depends on mod) +button11 activate button11 (behavior depends on mod) +button12 activate button12 (behavior depends on mod) +button13 activate button13 (behavior depends on mod) +button14 activate button14 (behavior depends on mod) +button15 activate button15 (behavior depends on mod) +button16 activate button16 (behavior depends on mod) +forward move forward +jump jump +klook activate keyboard looking mode, do not recenter view +left turn left +lookdown look downward +lookup look upward +mlook activate mouse looking mode, do not recenter view +movedown swim downward +moveleft strafe left +moveright strafe right +moveup swim upward +right turn right +showscores show scoreboard +speed activate run mode (faster movement and turning) +strafe activate strafing mode (move instead of turn) +use use something (may be used by some mods) -attack stop firing -back stop moving backward -button3 deactivate button3 -button4 deactivate button4 -button5 deactivate button5 -button6 deactivate button6 -button7 deactivate button7 -button8 deactivate button8 -button9 deactivate button9 -button10 deactivate button10 -button11 deactivate button11 -button12 deactivate button12 -button13 deactivate button13 -button14 deactivate button14 -button15 deactivate button15 -button16 deactivate button16 -forward stop moving forward -jump end jump (so you can jump again) -klook deactivate keyboard looking mode -left stop turning left -lookdown stop looking downward -lookup stop looking upward -mlook deactivate mouse looking mode -movedown stop swimming downward -moveleft stop strafing left -moveright stop strafing right -moveup stop swimming upward -right stop turning right -showscores hide scoreboard -speed deactivate run mode -strafe deactivate strafing mode -use stop using something alias create a script function (parameters are passed in as $1 through $9, and $* for all parameters) begin signon 3 (client asks server to start sending entities, and will go to signon 4 (playing) when the first entity update is received) bestweapon send an impulse number to server to select the first usable weapon out of several (example: 8 7 6 5 4 3 2 1) bf briefly flashes a bright color tint on view (used when items are picked up) bind binds a command to the specified key in bindmap 0 bottomcolor QW command to set bottom color without changing top color cd execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage cddrive select an SDL-detected CD drive by number centerview gradually recenter view (stop looking up/down) changelevel change to another level, bringing along all connected clients changing sent by qw servers to tell client to wait for level change cycleweapon send an impulse number to server to select the next usable weapon out of several, or the first if you are not holding any (example: 8 7 3) cl_areastats prints statistics on entity culling during collision traces cl_begindownloads used internally by darkplaces client while connecting (causes loading of models and sounds or triggers downloads for missing ones) cl_downloadbegin (networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer cl_downloadfinished signals that a download has finished and provides the client with file size and crc to check its integrity cl_particles_reloadeffects reloads effectinfo.txt clear clear console history cmd send a console commandline to the server (used by some mods) cmdlist lists all console commands beginning with the specified prefix color change your player shirt and pants colors condump output console history to a file (see also log_file) connect connect to a server by IP address or hostname curl download data from an URL and add to search path cvar_lockdefaults stores the current values of all cvars into their default values, only used once during startup after parsing default.cfg cvar_resettodefaults_all sets all cvars to their locked default values cvar_resettodefaults_nosaveonly sets all non-saved cvars to their locked default values (variables that will not be saved to config.cfg) cvar_resettodefaults_saveonly sets all saved cvars to their locked default values (variables that will be saved to config.cfg) cvarlist lists all console variables beginning with the specified prefix demos restart looping demos defined by the last startdemos command dir list files in searchpath matching an * filename pattern, one per line disconnect disconnect from server (or disconnect all clients if running a server) download downloads a specified file from the server echo print a message to the console (useful in scripts) entities print information on network entities known to client envmap render a cubemap (skybox) of the current scene exec execute a script file fly fly mode (flight) fog set global fog parameters (density red green blue mindist maxdist) force_centerview recenters view (stops looking up/down) fs_rescan rescans filesystem for new pack archives and any other changes fullinfo allows client to modify their userinfo fullserverinfo internal use only, sent by server to client to update client's local copy of serverinfo string gamedir changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf) give alter inventory gl_texturemode set texture filtering mode (GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, etc) god god mode (invulnerability) heartbeat send a heartbeat to the master server (updates your server information) help open the help menu impulse send an impulse number to server (select weapon, use item, etc) in_bind binds a command to the specified key in the selected bindmap in_bindmap selects active foreground and background (used only if a key is not bound in the foreground) bindmaps for typing in_unbind removes command on the specified key in the selected bindmap joyadvancedupdate applies current joyadv* cvar settings to the joystick driver kick kick a player off the server by number or name kill die instantly load load a saved game file loadconfig reset everything and reload configs loadsky load a skybox by basename (for example loadsky mtnsun_ loads mtnsun_ft.tga and so on) locs_add add a point or box location (usage: x y z[ x y z] \ locs_clear remove all loc points/boxes locs_reload reload .loc file for this map locs_removenearest remove the nearest point or box (note: you need to be very near a box to remove it) locs_save save .loc file for this map containing currently defined points and boxes ls list files in searchpath matching an * filename pattern, multiple per line map kick everyone off the server and start a new level maps list information about available maps maxplayers sets limit on how many players (or bots) may be connected to the server at once memlist prints memory pool information (or if used as memlist 5 lists individual allocations of 5K or larger, 0 lists all allocations) memstats prints memory system statistics menu_credits open the credits menu menu_fallback switch to engine menu (unload menu.dat) menu_keys open the key binding menu menu_load open the loadgame menu menu_main open the main menu menu_multiplayer open the multiplayer menu menu_options open the options menu menu_options_colorcontrol open the color control menu menu_options_effects open the effects options menu menu_options_graphics open the graphics options menu menu_quit open the quit menu menu_reset open the reset to defaults menu menu_restart restart menu system (reloads menu.dat menu_save open the savegame menu menu_setup open the player setup menu menu_singleplayer open the singleplayer menu menu_transfusion_episode open the transfusion episode select menu menu_transfusion_skill open the transfusion skill select menu menu_video open the video options menu messagemode input a chat message to say to everyone messagemode2 input a chat message to say to only your team modellist prints a list of loaded models modelprecache load a model name change your player name net_slist query dp master servers and print all server information net_slistqw query qw master servers and print all server information net_stats print network statistics nextul sends next fragment of current upload buffer (screenshot for example) noclip noclip mode (flight without collisions, move through walls) notarget notarget mode (monsters do not see you) packet send a packet to the specified address:port containing a text string path print searchpath (game directories and archives) pause pause the game (if the server allows pausing) pausedemo pause demo playback (can also safely pause demo recording if using QUAKE, QUAKEDP or NEHAHRAMOVIE protocol, useful for making movies) ping print ping times of all players on the server pingplreport command sent by server containing client ping and packet loss values for scoreboard, triggered by pings command from client (not used by QW servers) pings command sent by clients to request updated ping and packetloss of players on scoreboard (originally from QW, but also used on NQ servers) play play a sound at your current location (not heard by anyone else) play2 play a sound globally throughout the level (not heard by anyone else) playdemo watch a demo file playermodel change your player model playerskin change your player skin number playvideo play a .dpv video file playvol play a sound at the specified volume level at your current location (not heard by anyone else) pmodel change your player model choice (Nehahra specific) pointfile display point file produced by qbsp when a leak was detected in the map (a line leading through the leak hole, to an entity inside the level) prespawn signon 1 (client acknowledges that server information has been received) prvm_edict print all data about an entity number in the selected VM (server, client, menu) prvm_edictcount prints number of active entities in the selected VM (server, client, menu) prvm_edicts set a property on an entity number in the selected VM (server, client, menu) prvm_edictset changes value of a specified property of a specified entity in the selected VM (server, client, menu) prvm_fields prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu) prvm_global prints value of a specified global variable in the selected VM (server, client, menu) prvm_globals prints all global variables in the selected VM (server, client, menu) prvm_globalset sets value of a specified global variable in the selected VM (server, client, menu) prvm_printfunction prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, menu) prvm_profile prints execution statistics about the most used QuakeC functions in the selected VM (server, client, menu) quit quit the game r_editlights_clear removes all world lights (let there be darkness!) r_editlights_copyinfo store a copy of all properties (except origin) of the selected light r_editlights_edit changes a property on the selected light r_editlights_editall changes a property on ALL lights at once (tip: use radiusscale and colorscale to alter these properties) r_editlights_help prints documentation on console commands and variables in rtlight editing system r_editlights_importlightentitiesfrommap load lights from .ent file or map entities (ignoring .rtlights or .lights file) r_editlights_importlightsfile load lights from .lights file (ignoring .rtlights or .ent files and map entities) r_editlights_pasteinfo apply the stored properties onto the selected light (making it exactly identical except for origin) r_editlights_reload reloads rtlights file (or imports from .lights file or .ent file or the map itself) r_editlights_remove remove selected light r_editlights_save save .rtlights file for current level r_editlights_spawn creates a light with default properties (let there be light!) r_editlights_togglecorona toggle on/off the corona option on the selected light r_editlights_toggleshadow toggle on/off the shadow option on the selected light r_glsl_restart unloads GLSL shaders, they will then be reloaded as needed r_listmaptextures list all textures used by the current map r_replacemaptexture override a map texture for testing purposes r_restart restarts renderer r_shadow_help prints documentation on console commands and variables used by realtime lighting and shadowing system r_texturestats print information about all loaded textures and some statistics rate change your network connection speed rcon sends a command to the server console (if your rcon_password matches the server's rcon_password), or to the address specified by rcon_address when not connected (again rcon_password must match the server's) reconnect reconnect to the last server you were on, or resets a quakeworld connection (do not use if currently playing on a netquake server) record record a demo restart restart current level save save the game to a file saveconfig save settings to config.cfg immediately (also automatic when quitting) say send a chat message to everyone on the server say_team send a chat message to your team on the server screenshot takes a screenshot of the next rendered frame sendcvar sends the value of a cvar to the server as a sentcvar command, for use by QuakeC set create or change the value of a console variable seta create or change the value of a console variable that will be saved to config.cfg setinfo modifies your userinfo sizedown decrease view size (decreases viewsize cvar) sizeup increase view size (increases viewsize cvar) skins downloads missing qw skins from server snd_unloadallsounds unload all sound files snd_restart restart sound system soundinfo print sound system information (such as channels and speed) soundlist list loaded sounds spawn signon 2 (client has sent player information, and is asking server to send scoreboard rankings) startdemos start playing back the selected demos sequentially (used at end of startup script) status print server status information stop stop recording or playing a demo stopdemo stop playing or recording demo (like stop command) and return to looping demos stopdownload terminates a download stopsound silence stopul aborts current upload (screenshot for example) stopvideo stop playing a .dpv video file stuffcmds execute commandline parameters (must be present in quake.rc script) sv_areastats prints statistics on entity culling during collision traces sv_saveentfile save map entities to .ent file (to allow external editing) sv_startdownload begins sending a file to the client (network protocol use only) tell send a chat message to only one person on the server timedemo play back a demo as fast as possible and save statistics to benchmark.log timerefresh turn quickly and print rendering statistcs toggle toggles a console variable's values (use for more info) toggleconsole opens or closes the console togglemenu opens or closes menu topcolor QW command to set top color without changing bottom color unbind removes a command on the specified key in bindmap 0 unbindall removes all commands from all keys in all bindmaps (leaving only shift-escape and escape) user prints additional information about a player number or name on the scoreboard users prints additional information about all players on the scoreboard v_cshift sets tint color of view version print engine version vid_restart restarts video system (closes and reopens the window, restarts renderer) viewframe change animation frame of viewthing entity in current level viewmodel change model of viewthing entity in current level viewnext change to next animation frame of viewthing entity in current level viewprev change to previous animation frame of viewthing entity in current level wait make script execution wait for next rendered frame which accepts a file name as argument and reports where the file is taken from How to install Quake on Windows: All that DarkPlaces needs from the Quake CD is pak files (be sure not to copy opengl32.dll from the Quake CD, it will not work with DarkPlaces!), with this in mind, all you need to do is make a Quake directory, extract the darkplaces engine zip to that directory, then make a quake/id1 directory, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well. How to deal with a DOS Quake CD on Windows: Try to use the DOS Quake installer if you can, use DOSBox if necessary to run the installer, then copy the pak0.pak and pak1.pak to your id1 directory in the darkplaces installation. ( http://dosbox.sourceforge.net ) How to deal with a WinQuake CD on Windows: Copy the D:\Data\id1\pak0.pak and pak1.pak to your id1 directory. How to deal with a Linux Quake CD on Windows: Find an archiver (perhaps 7zip or winrar) that can extract files from rpm archives, locate the pak files and copy them to your id1 directory. How to install Quake on Linux: All that DarkPlaces needs from the Quake CD is pak files, with this in mind, all you need to do is make a ~/quake directory, extract the darkplaces engine zip to that directory, then make a quake/id1 directory, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well, you will probably also want to make a ~/bin/darkplaces script containing the following text: #!/bin/sh cd ~/quake ./darkplaces-sdl $* Then do chmod +x ~/bin/darkplaces For more information on Quake installation on Linux see http://www.tldp.org/HOWTO/Quake-HOWTO.html" (the Linux Quake How To) How to deal with a DOS Quake CD on Linux: cat /media/cdrom/resource.001 /media/cdrom/resource.002 >quake.lha unlha x quake.lha If you can't get unlha or lha for your distribution, try using DOSBox to run the Quake installer. How to deal with a WinQuake CD on Linux: Copy the /media/cdrom/data/id1/pak*.pak to your id1 directory. How to deal with a Linux Quake CD on Linux: mkdir temp cd temp # in the following line replace quake.rpm with a correct rpm filename cat /media/cdrom/quake.rpm | rpm2cpio | cpio -i Now you should have a mess of subdirectories, locate the pak files and copy to your id1 directory. Alternatively if you have an rpm-based distribution you could install the rpm, but it is easier to maintain your ~/quake directory than /usr/share/games/quake so you may want to copy the id1/pak*.pak from there and uninstall the rpm. How to install Quake on Mac OS X: All that DarkPlaces needs from the Quake CD is pak files, with this in mind, make a folder named Quake, drag the Darkplaces.app into this Quake folder, make a folder inside the Quake folder (alongside Darkplaces) named id1, and put the pak0.pak and pak1.pak from your Quake CD into the quake/id1 directory, then all should be well, simply run the Darkplaces app How to deal with a DOS Quake CD on Mac OS X: Unknown. Linux solution may work if you can get hold of lha, otherwise use DOSBox to run the Quake installer. How to deal with a WinQuake CD on Mac OS X: Find the data folder on the cdrom and copy the data/id1/pak*.pak files to your id1 folder. How to deal with a Linux Quake CD on Mac OS X: Unknown. If you can get hold of rpm2cpio and cpio you should be able to follow the Linux method. Shader parameters for DP's own features: - dp_reflect Makes surfaces of this shader reflective with r_water. The reflection is alpha blended on the texture with the given alpha, and modulated by the given color. distort is used in conjunction with the normalmap to simulate a nonplanar water surface. - dp_refract Makes surfaces of this shader refractive with r_water. The refraction replaces the transparency of the texture. distort is used in conjunction with the normalmap to simulate a nonplanar water surface. - dp_water This combines the effects of dp_reflect and dp_refract to simulate a water surface. However, the refraction and the reflection are mixed using a Fresnel equation that makes the amount of reflection slide from reflectmin when looking parallel to the water to reflectmax when looking directly into the water. The result of this reflection/refraction mix is then layered BELOW the texture of the shader, so basically, it "fills up" the alpha values of the water. The alpha value is a multiplicator for the alpha value on the texture - set this to a small value like 0.1 to emphasize the reflection and make the water transparent; but if r_water is 0, alpha isn't used, so the water can be very visible then too. - tcmod page The texture is shifted by 1/ every seconds, and by 1/ every * seconds. It is some sort of animmap replacement that keeps all animation frames in a single texture. To use it, make a texture with the frames aligned in a grid like this: 1 2 3 4 5 6 7 8 then align it in Radiant so only one of the animation frames can be seen on the surface, and specify "tcmod page 4 2 0.1". DP will then display the frames in order and the cycle will repeat every 0.8 seconds. - dppolygonoffset This surface gets glPolygonOffset(factor, offset); useful for decals Thanks to: Tomaz for adding features, fixing many bugs, and being generally helpful. Andreas 'Black' Kirsch for much work on the QuakeC VM (menu.dat, someday clprogs.dat) and other contributions. Mathieu 'Elric' Olivier for much work on the sound engine (especially the Ogg vorbis support) MoALTz for some bugfixes and cleanups Joseph Caporale for adding 5 mouse button support. KGB|romi for his contributions to the Quake community, including his rtlights project and many suggestions, his id1 romi_rtlights.pk3 is included in darkplaces mod releases. Zombie for making great levels and general DarkPlaces publicity. FrikaC for FrikQCC and FrikBot and general community support. Transfusion Project for recreating Blood in the world of Quake. de-we for the great icons. |Rain| for running my favorite anynet IRC server and his bot feh (which although a bit antisocial never seems to grow tired of being my calculator). VorteX for the DP_QC_GETTAGINFO extension. Ludwig Nussel for the ~/.games/darkplaces/ user directory support on non-Windows platforms (allowing games to be installed in a non-writable system location as is the standard on UNIX but still save configs to the user's home directory). darkplaces/vs2012_sdl2_win64.props0000664000175000017500000000130413067716222016160 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL2-2.0\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x64;C:\dev\SDL2-2.0\lib\x64;$(LibraryPath) <_PropertySheetDisplayName>vs2012_win64 darkplaces/cd_shared.c0000664000175000017500000005272313067716216014261 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All // rights reserved. #include "quakedef.h" #include "cdaudio.h" #include "sound.h" // used by menu to ghost CD audio slider cvar_t cdaudioinitialized = {CVAR_READONLY,"cdaudioinitialized","0","indicates if CD Audio system is active"}; cvar_t cdaudio = {CVAR_SAVE,"cdaudio","1","CD playing mode (0 = never access CD drive, 1 = play CD tracks if no replacement available, 2 = play fake tracks if no CD track available, 3 = play only real CD tracks, 4 = play real CD tracks even instead of named fake tracks)"}; #define MAX_PLAYLISTS 10 int music_playlist_active = -1; int music_playlist_playing = 0; // 0 = not playing, 1 = playing, -1 = tried and failed cvar_t music_playlist_index = {0, "music_playlist_index", "-1", "selects which of the music_playlist_ variables is the active one, -1 disables playlists"}; cvar_t music_playlist_list[MAX_PLAYLISTS] = { {0, "music_playlist_list0", "", "list of tracks to play"}, {0, "music_playlist_list1", "", "list of tracks to play"}, {0, "music_playlist_list2", "", "list of tracks to play"}, {0, "music_playlist_list3", "", "list of tracks to play"}, {0, "music_playlist_list4", "", "list of tracks to play"}, {0, "music_playlist_list5", "", "list of tracks to play"}, {0, "music_playlist_list6", "", "list of tracks to play"}, {0, "music_playlist_list7", "", "list of tracks to play"}, {0, "music_playlist_list8", "", "list of tracks to play"}, {0, "music_playlist_list9", "", "list of tracks to play"} }; cvar_t music_playlist_current[MAX_PLAYLISTS] = { {0, "music_playlist_current0", "0", "current track index to play in list"}, {0, "music_playlist_current1", "0", "current track index to play in list"}, {0, "music_playlist_current2", "0", "current track index to play in list"}, {0, "music_playlist_current3", "0", "current track index to play in list"}, {0, "music_playlist_current4", "0", "current track index to play in list"}, {0, "music_playlist_current5", "0", "current track index to play in list"}, {0, "music_playlist_current6", "0", "current track index to play in list"}, {0, "music_playlist_current7", "0", "current track index to play in list"}, {0, "music_playlist_current8", "0", "current track index to play in list"}, {0, "music_playlist_current9", "0", "current track index to play in list"}, }; cvar_t music_playlist_random[MAX_PLAYLISTS] = { {0, "music_playlist_random0", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random1", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random2", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random3", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random4", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random5", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random6", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random7", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random8", "0", "enables random play order if 1, 0 is sequential play"}, {0, "music_playlist_random9", "0", "enables random play order if 1, 0 is sequential play"}, }; cvar_t music_playlist_sampleposition[MAX_PLAYLISTS] = { {0, "music_playlist_sampleposition0", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition1", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition2", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition3", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition4", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition5", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition6", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition7", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition8", "-1", "resume position for track, -1 restarts every time"}, {0, "music_playlist_sampleposition9", "-1", "resume position for track, -1 restarts every time"}, }; static qboolean wasPlaying = false; static qboolean initialized = false; static qboolean enabled = false; static float cdvolume; typedef char filename_t[MAX_QPATH]; #ifdef MAXTRACKS static filename_t remap[MAXTRACKS]; #endif static unsigned char maxTrack; static int faketrack = -1; static float saved_vol = 1.0f; // exported variables qboolean cdValid = false; qboolean cdPlaying = false; qboolean cdPlayLooping = false; unsigned char cdPlayTrack; cl_cdstate_t cd; static void CDAudio_Eject (void) { if (!enabled) return; if(cdaudio.integer == 0) return; CDAudio_SysEject(); } static void CDAudio_CloseDoor (void) { if (!enabled) return; if(cdaudio.integer == 0) return; CDAudio_SysCloseDoor(); } static int CDAudio_GetAudioDiskInfo (void) { int ret; cdValid = false; if(cdaudio.integer == 0) return -1; ret = CDAudio_SysGetAudioDiskInfo(); if (ret < 1) return -1; cdValid = true; maxTrack = ret; return 0; } static qboolean CDAudio_Play_real (int track, qboolean looping, qboolean complain) { if(track < 1) { if(complain) Con_Print("Could not load BGM track.\n"); return false; } if (!cdValid) { CDAudio_GetAudioDiskInfo(); if (!cdValid) { if(complain) Con_DPrint ("No CD in player.\n"); return false; } } if (track > maxTrack) { if(complain) Con_DPrintf("CDAudio: Bad track number %u.\n", track); return false; } if (CDAudio_SysPlay(track) == -1) return false; if(cdaudio.integer != 3) Con_DPrintf ("CD track %u playing...\n", track); return true; } void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition) { unsigned int track; sfx_t* sfx; char filename[MAX_QPATH]; Host_StartVideo(); if (!enabled) return; if(tryreal && strspn(trackname, "0123456789") == strlen(trackname)) { track = (unsigned char) atoi(trackname); #ifdef MAXTRACKS if(track > 0 && track < MAXTRACKS) if(*remap[track]) { if(strspn(remap[track], "0123456789") == strlen(remap[track])) { trackname = remap[track]; } else { // ignore remappings to fake tracks if we're going to play a real track switch(cdaudio.integer) { case 0: // we never access CD case 1: // we have a replacement trackname = remap[track]; break; case 2: // we only use fake track replacement if CD track is invalid CDAudio_GetAudioDiskInfo(); if(!cdValid || track > maxTrack) trackname = remap[track]; break; case 3: // we always play from CD - ignore this remapping then case 4: // we randomize anyway break; } } } #endif } if(tryreal && strspn(trackname, "0123456789") == strlen(trackname)) { track = (unsigned char) atoi(trackname); if (track < 1) { Con_DPrintf("CDAudio: Bad track number %u.\n", track); return; } } else track = 0; // div0: I assume this code was intentionally there. Maybe turn it into a cvar? if (cdPlaying && cdPlayTrack == track && faketrack == -1) return; CDAudio_Stop (); if(track >= 1) { if(cdaudio.integer == 3) // only play real CD tracks at all { if(CDAudio_Play_real(track, looping, true)) goto success; return; } if(cdaudio.integer == 2) // prefer real CD track over fake { if(CDAudio_Play_real(track, looping, false)) goto success; } } if(cdaudio.integer == 4) // only play real CD tracks, EVEN instead of fake tracks! { if(CDAudio_Play_real(track, looping, false)) goto success; if(cdValid && maxTrack > 0) { track = 1 + (rand() % maxTrack); if(CDAudio_Play_real(track, looping, true)) goto success; } else { Con_DPrint ("No CD in player.\n"); } return; } // Try playing a fake track (sound file) first if(track >= 1) { dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.wav", track); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.ogg", track); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%03u.ogg", track);// added by motorsep if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%03u.ogg", track);// added by motorsep if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.wav", track); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.ogg", track); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%02u.ogg", track);// added by motorsep if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%02u.ogg", track);// added by motorsep } else { dpsnprintf(filename, sizeof(filename), "%s", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.wav", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.ogg", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.wav", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.ogg", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.wav", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.ogg", trackname); if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/%s.ogg", trackname); // added by motorsep if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/%s.ogg", trackname); // added by motorsep } if (FS_FileExists(filename) && (sfx = S_PrecacheSound (filename, false, false))) { faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND, 1.0f); if (faketrack != -1) { if(track >= 1) { if(cdaudio.integer != 0) // we don't need these messages if only fake tracks can be played anyway Con_DPrintf ("Fake CD track %u playing...\n", track); } else Con_DPrintf ("BGM track %s playing...\n", trackname); } } // If we can't play a fake CD track, try the real one if (faketrack == -1) { if(cdaudio.integer == 0 || track < 1) { Con_Print("Could not load BGM track.\n"); return; } else { if(!CDAudio_Play_real(track, looping, true)) return; } } success: cdPlayLooping = looping; cdPlayTrack = track; cdPlaying = true; if (cdvolume == 0.0 || bgmvolume.value == 0) CDAudio_Pause (); } void CDAudio_Play (int track, qboolean looping) { char buf[20]; if (music_playlist_index.integer >= 0) return; dpsnprintf(buf, sizeof(buf), "%d", (int) track); CDAudio_Play_byName(buf, looping, true, 0); } float CDAudio_GetPosition (void) { if(faketrack != -1) return S_GetChannelPosition(faketrack); return -1; } static void CDAudio_StopPlaylistTrack(void); void CDAudio_Stop (void) { if (!enabled) return; // save the playlist position CDAudio_StopPlaylistTrack(); if (faketrack != -1) { S_StopChannel (faketrack, true, true); faketrack = -1; } else if (cdPlaying && (CDAudio_SysStop() == -1)) return; else if(wasPlaying) { CDAudio_Resume(); // needed by SDL - can't stop while paused there (causing pause/stop to fail after play, pause, stop, play otherwise) if (cdPlaying && (CDAudio_SysStop() == -1)) return; } wasPlaying = false; cdPlaying = false; } void CDAudio_Pause (void) { if (!enabled || !cdPlaying) return; if (faketrack != -1) S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, true); else if (CDAudio_SysPause() == -1) return; wasPlaying = cdPlaying; cdPlaying = false; } void CDAudio_Resume (void) { if (!enabled || cdPlaying || !wasPlaying) return; if (faketrack != -1) S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, false); else if (CDAudio_SysResume() == -1) return; cdPlaying = true; } static void CD_f (void) { const char *command; #ifdef MAXTRACKS int ret; int n; #endif command = Cmd_Argv (1); if (strcasecmp(command, "remap") != 0) Host_StartVideo(); if (strcasecmp(command, "on") == 0) { enabled = true; return; } if (strcasecmp(command, "off") == 0) { CDAudio_Stop(); enabled = false; return; } if (strcasecmp(command, "reset") == 0) { enabled = true; CDAudio_Stop(); #ifdef MAXTRACKS for (n = 0; n < MAXTRACKS; n++) *remap[n] = 0; // empty string, that is, unremapped #endif CDAudio_GetAudioDiskInfo(); return; } if (strcasecmp(command, "rescan") == 0) { CDAudio_Shutdown(); CDAudio_Startup(); return; } if (strcasecmp(command, "remap") == 0) { #ifdef MAXTRACKS ret = Cmd_Argc() - 2; if (ret <= 0) { for (n = 1; n < MAXTRACKS; n++) if (*remap[n]) Con_Printf(" %u -> %s\n", n, remap[n]); return; } for (n = 1; n <= ret; n++) strlcpy(remap[n], Cmd_Argv (n+1), sizeof(*remap)); #endif return; } if (strcasecmp(command, "close") == 0) { CDAudio_CloseDoor(); return; } if (strcasecmp(command, "play") == 0) { if (music_playlist_index.integer >= 0) return; CDAudio_Play_byName(Cmd_Argv (2), false, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0); return; } if (strcasecmp(command, "loop") == 0) { if (music_playlist_index.integer >= 0) return; CDAudio_Play_byName(Cmd_Argv (2), true, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0); return; } if (strcasecmp(command, "stop") == 0) { if (music_playlist_index.integer >= 0) return; CDAudio_Stop(); return; } if (strcasecmp(command, "pause") == 0) { if (music_playlist_index.integer >= 0) return; CDAudio_Pause(); return; } if (strcasecmp(command, "resume") == 0) { if (music_playlist_index.integer >= 0) return; CDAudio_Resume(); return; } if (strcasecmp(command, "eject") == 0) { if (faketrack == -1) CDAudio_Stop(); CDAudio_Eject(); cdValid = false; return; } if (strcasecmp(command, "info") == 0) { CDAudio_GetAudioDiskInfo (); if (cdValid) Con_Printf("%u tracks on CD.\n", maxTrack); else Con_Print ("No CD in player.\n"); if (cdPlaying) Con_Printf("Currently %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack); else if (wasPlaying) Con_Printf("Paused %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack); if (cdvolume >= 0) Con_Printf("Volume is %f\n", cdvolume); else Con_Printf("Can't get CD volume\n"); return; } Con_Printf("CD commands:\n"); Con_Printf("cd on - enables CD audio system\n"); Con_Printf("cd off - stops and disables CD audio system\n"); Con_Printf("cd reset - resets CD audio system (clears track remapping and re-reads disc information)\n"); Con_Printf("cd rescan - rescans disks in drives (to use another disc)\n"); Con_Printf("cd remap [remap2] [remap3] [...] - chooses (possibly emulated) CD tracks to play when a map asks for a particular track, this has many uses\n"); Con_Printf("cd close - closes CD tray\n"); Con_Printf("cd eject - stops playing music and opens CD tray to allow you to change disc\n"); Con_Printf("cd play - plays selected track in remapping table\n"); Con_Printf("cd loop - plays and repeats selected track in remapping table\n"); Con_Printf("cd stop - stops playing current CD track\n"); Con_Printf("cd pause - pauses CD playback\n"); Con_Printf("cd resume - unpauses CD playback\n"); Con_Printf("cd info - prints basic disc information (number of tracks, currently playing track, volume level)\n"); } static void CDAudio_SetVolume (float newvol) { // If the volume hasn't changed if (newvol == cdvolume) return; // If the CD has been muted if (newvol == 0.0f) CDAudio_Pause (); else { // If the CD has been unmuted if (cdvolume == 0.0f) CDAudio_Resume (); if (faketrack != -1) S_SetChannelVolume (faketrack, newvol); else CDAudio_SysSetVolume (newvol * mastervolume.value); } cdvolume = newvol; } static void CDAudio_StopPlaylistTrack(void) { if (music_playlist_active >= 0 && music_playlist_active < MAX_PLAYLISTS && music_playlist_sampleposition[music_playlist_active].value >= 0) { // save position for resume float position = CDAudio_GetPosition(); Cvar_SetValueQuick(&music_playlist_sampleposition[music_playlist_active], position >= 0 ? position : 0); } music_playlist_active = -1; music_playlist_playing = 0; // not playing } void CDAudio_StartPlaylist(qboolean resume) { const char *list; const char *t; int index; int current; int randomplay; int count; int listindex; float position; char trackname[MAX_QPATH]; CDAudio_Stop(); index = music_playlist_index.integer; if (index >= 0 && index < MAX_PLAYLISTS && bgmvolume.value > 0) { list = music_playlist_list[index].string; current = music_playlist_current[index].integer; randomplay = music_playlist_random[index].integer; position = music_playlist_sampleposition[index].value; count = 0; trackname[0] = 0; if (list && list[0]) { for (t = list;;count++) { if (!COM_ParseToken_Console(&t)) break; // if we don't find the desired track, use the first one if (count == 0) strlcpy(trackname, com_token, sizeof(trackname)); } } if (count > 0) { // position < 0 means never resume track if (position < 0) position = 0; // advance to next track in playlist if the last one ended if (!resume) { position = 0; current++; if (randomplay) current = (int)lhrandom(0, count); } // wrap playlist position if needed if (current >= count) current = 0; // set current Cvar_SetValueQuick(&music_playlist_current[index], current); // get the Nth trackname if (current >= 0 && current < count) { for (listindex = 0, t = list;;listindex++) { if (!COM_ParseToken_Console(&t)) break; if (listindex == current) { strlcpy(trackname, com_token, sizeof(trackname)); break; } } } if (trackname[0]) { CDAudio_Play_byName(trackname, false, false, position); if (faketrack != -1) music_playlist_active = index; } } } music_playlist_playing = music_playlist_active >= 0 ? 1 : -1; } void CDAudio_Update (void) { static int lastplaylist = -1; if (!enabled) return; CDAudio_SetVolume (bgmvolume.value); if (music_playlist_playing > 0 && CDAudio_GetPosition() < 0) { // this track ended, start a new track from the beginning CDAudio_StartPlaylist(false); lastplaylist = music_playlist_index.integer; } else if (lastplaylist != music_playlist_index.integer || (bgmvolume.value > 0 && !music_playlist_playing && music_playlist_index.integer >= 0)) { // active playlist changed, save position and switch track CDAudio_StartPlaylist(true); lastplaylist = music_playlist_index.integer; } if (faketrack == -1 && cdaudio.integer != 0 && bgmvolume.value != 0) CDAudio_SysUpdate(); } int CDAudio_Init (void) { int i; if (cls.state == ca_dedicated) return -1; // COMMANDLINEOPTION: Sound: -nocdaudio disables CD audio support if (COM_CheckParm("-nocdaudio")) return -1; CDAudio_SysInit(); #ifdef MAXTRACKS for (i = 0; i < MAXTRACKS; i++) *remap[i] = 0; #endif Cvar_RegisterVariable(&cdaudio); Cvar_RegisterVariable(&cdaudioinitialized); Cvar_SetValueQuick(&cdaudioinitialized, true); enabled = true; Cvar_RegisterVariable(&music_playlist_index); for (i = 0;i < MAX_PLAYLISTS;i++) { Cvar_RegisterVariable(&music_playlist_list[i]); Cvar_RegisterVariable(&music_playlist_current[i]); Cvar_RegisterVariable(&music_playlist_random[i]); Cvar_RegisterVariable(&music_playlist_sampleposition[i]); } Cmd_AddCommand("cd", CD_f, "execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage"); return 0; } int CDAudio_Startup (void) { if (COM_CheckParm("-nocdaudio")) return -1; CDAudio_SysStartup (); if (CDAudio_GetAudioDiskInfo()) { Con_Print("CDAudio_Init: No CD in player.\n"); cdValid = false; } saved_vol = CDAudio_SysGetVolume (); if (saved_vol < 0.0f) { Con_Print ("Can't get initial CD volume\n"); saved_vol = 1.0f; } else Con_Printf ("Initial CD volume: %g\n", saved_vol); initialized = true; Con_Print("CD Audio Initialized\n"); return 0; } void CDAudio_Shutdown (void) { if (!initialized) return; CDAudio_SysSetVolume (saved_vol); CDAudio_Stop(); CDAudio_SysShutdown(); initialized = false; } darkplaces/render.h0000664000175000017500000006762213067716222013632 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef RENDER_H #define RENDER_H #include "svbsp.h" // 1.0f / N table extern float ixtable[4096]; // fog stuff void FOG_clear(void); // sky stuff extern cvar_t r_sky; extern cvar_t r_skyscroll1; extern cvar_t r_skyscroll2; extern int skyrenderlater, skyrendermasked; int R_SetSkyBox(const char *sky); void R_SkyStartFrame(void); void R_Sky(void); void R_ResetSkyBox(void); // SHOWLMP stuff (Nehahra) void SHOWLMP_decodehide(void); void SHOWLMP_decodeshow(void); void SHOWLMP_drawall(void); // render profiling stuff extern int r_timereport_active; // lighting stuff extern cvar_t r_ambient; extern cvar_t gl_flashblend; // vis stuff extern cvar_t r_novis; extern cvar_t r_trippy; extern cvar_t r_fxaa; extern cvar_t r_lerpsprites; extern cvar_t r_lerpmodels; extern cvar_t r_lerplightstyles; extern cvar_t r_waterscroll; extern cvar_t developer_texturelogging; // shadow volume bsp struct with automatically growing nodes buffer extern svbsp_t r_svbsp; typedef struct rmesh_s { // vertices of this mesh int maxvertices; int numvertices; float *vertex3f; float *svector3f; float *tvector3f; float *normal3f; float *texcoord2f; float *texcoordlightmap2f; float *color4f; // triangles of this mesh int maxtriangles; int numtriangles; int *element3i; int *neighbor3i; // snapping epsilon float epsilon2; } rmesh_t; // useful functions for rendering void R_ModulateColors(float *in, float *out, int verts, float r, float g, float b); void R_FillColors(float *out, int verts, float r, float g, float b, float a); int R_Mesh_AddVertex3f(rmesh_t *mesh, const float *v); void R_Mesh_AddPolygon3f(rmesh_t *mesh, int numvertices, float *vertex3f); void R_Mesh_AddBrushMeshFromPlanes(rmesh_t *mesh, int numplanes, mplane_t *planes); #define TOP_RANGE 16 // soldier uniform colors #define BOTTOM_RANGE 96 //============================================================================= extern cvar_t r_nearclip; // forces all rendering to draw triangle outlines extern cvar_t r_showoverdraw; extern cvar_t r_showtris; extern cvar_t r_shownormals; extern cvar_t r_showlighting; extern cvar_t r_showshadowvolumes; extern cvar_t r_showcollisionbrushes; extern cvar_t r_showcollisionbrushes_polygonfactor; extern cvar_t r_showcollisionbrushes_polygonoffset; extern cvar_t r_showdisabledepthtest; extern cvar_t r_drawentities; extern cvar_t r_draw2d; extern qboolean r_draw2d_force; extern cvar_t r_drawviewmodel; extern cvar_t r_drawworld; extern cvar_t r_speeds; extern cvar_t r_fullbright; extern cvar_t r_wateralpha; extern cvar_t r_dynamic; void R_Init(void); void R_UpdateVariables(void); // must call after setting up most of r_refdef, but before calling R_RenderView void R_RenderView(void); // must set r_refdef and call R_UpdateVariables first void R_RenderView_UpdateViewVectors(void); // just updates r_refdef.view.{forward,left,up,origin,right,inverse_matrix} typedef enum r_refdef_scene_type_s { RST_CLIENT, RST_MENU, RST_COUNT } r_refdef_scene_type_t; void R_SelectScene( r_refdef_scene_type_t scenetype ); r_refdef_scene_t * R_GetScenePointer( r_refdef_scene_type_t scenetype ); void R_SkinFrame_PrepareForPurge(void); void R_SkinFrame_MarkUsed(skinframe_t *skinframe); void R_SkinFrame_Purge(void); // set last to NULL to start from the beginning skinframe_t *R_SkinFrame_FindNextByName( skinframe_t *last, const char *name ); skinframe_t *R_SkinFrame_Find(const char *name, int textureflags, int comparewidth, int compareheight, int comparecrc, qboolean add); skinframe_t *R_SkinFrame_LoadExternal(const char *name, int textureflags, qboolean complain); skinframe_t *R_SkinFrame_LoadInternalBGRA(const char *name, int textureflags, const unsigned char *skindata, int width, int height, qboolean sRGB); skinframe_t *R_SkinFrame_LoadInternalQuake(const char *name, int textureflags, int loadpantsandshirt, int loadglowtexture, const unsigned char *skindata, int width, int height); skinframe_t *R_SkinFrame_LoadInternal8bit(const char *name, int textureflags, const unsigned char *skindata, int width, int height, const unsigned int *palette, const unsigned int *alphapalette); skinframe_t *R_SkinFrame_LoadMissing(void); rtexture_t *R_GetCubemap(const char *basename); void R_View_WorldVisibility(qboolean forcenovis); void R_DrawDecals(void); void R_DrawParticles(void); void R_DrawExplosions(void); #define gl_solid_format 3 #define gl_alpha_format 4 int R_CullBox(const vec3_t mins, const vec3_t maxs); int R_CullBoxCustomPlanes(const vec3_t mins, const vec3_t maxs, int numplanes, const mplane_t *planes); #include "r_modules.h" #include "meshqueue.h" /// free all R_FrameData memory void R_FrameData_Reset(void); /// prepare for a new frame, recycles old buffers if a resize occurred previously void R_FrameData_NewFrame(void); /// allocate some temporary memory for your purposes void *R_FrameData_Alloc(size_t size); /// allocate some temporary memory and copy this data into it void *R_FrameData_Store(size_t size, void *data); /// set a marker that allows you to discard the following temporary memory allocations void R_FrameData_SetMark(void); /// discard recent memory allocations (rewind to marker) void R_FrameData_ReturnToMark(void); /// enum of the various types of hardware buffer object used in rendering /// note that the r_buffermegs[] array must be maintained to match this typedef enum r_bufferdata_type_e { R_BUFFERDATA_VERTEX, /// vertex buffer R_BUFFERDATA_INDEX16, /// index buffer - 16bit (because D3D cares) R_BUFFERDATA_INDEX32, /// index buffer - 32bit (because D3D cares) R_BUFFERDATA_UNIFORM, /// uniform buffer R_BUFFERDATA_COUNT /// how many kinds of buffer we have } r_bufferdata_type_t; /// free all dynamic vertex/index/uniform buffers void R_BufferData_Reset(void); /// begin a new frame (recycle old buffers) void R_BufferData_NewFrame(void); /// request space in a vertex/index/uniform buffer for the chosen data, returns the buffer pointer and offset, always successful r_meshbuffer_t *R_BufferData_Store(size_t size, const void *data, r_bufferdata_type_t type, int *returnbufferoffset); /// free all R_AnimCache memory void R_AnimCache_Free(void); /// clear the animcache pointers on all known render entities void R_AnimCache_ClearCache(void); /// get the skeletal data or cached animated mesh data for an entity (optionally with normals and tangents) qboolean R_AnimCache_GetEntity(entity_render_t *ent, qboolean wantnormals, qboolean wanttangents); /// generate animcache data for all entities marked visible void R_AnimCache_CacheVisibleEntities(void); #include "r_lerpanim.h" extern cvar_t r_render; extern cvar_t r_renderview; extern cvar_t r_waterwarp; extern cvar_t r_textureunits; extern cvar_t r_glsl_offsetmapping; extern cvar_t r_glsl_offsetmapping_reliefmapping; extern cvar_t r_glsl_offsetmapping_scale; extern cvar_t r_glsl_offsetmapping_lod; extern cvar_t r_glsl_offsetmapping_lod_distance; extern cvar_t r_glsl_deluxemapping; extern cvar_t gl_polyblend; extern cvar_t gl_dither; extern cvar_t cl_deathfade; extern cvar_t r_smoothnormals_areaweighting; extern cvar_t r_test; #include "gl_backend.h" extern rtexture_t *r_texture_blanknormalmap; extern rtexture_t *r_texture_white; extern rtexture_t *r_texture_grey128; extern rtexture_t *r_texture_black; extern rtexture_t *r_texture_notexture; extern rtexture_t *r_texture_whitecube; extern rtexture_t *r_texture_normalizationcube; extern rtexture_t *r_texture_fogattenuation; extern rtexture_t *r_texture_fogheighttexture; extern unsigned int r_queries[MAX_OCCLUSION_QUERIES]; extern unsigned int r_numqueries; extern unsigned int r_maxqueries; void R_TimeReport(const char *name); // r_stain void R_Stain(const vec3_t origin, float radius, int cr1, int cg1, int cb1, int ca1, int cr2, int cg2, int cb2, int ca2); void R_CalcBeam_Vertex3f(float *vert, const float *org1, const float *org2, float width); void R_CalcSprite_Vertex3f(float *vertex3f, const float *origin, const float *left, const float *up, float scalex1, float scalex2, float scaley1, float scaley2); extern mempool_t *r_main_mempool; typedef struct rsurfacestate_s { // current model array pointers // these may point to processing buffers if model is animated, // otherwise they point to static data. // these are not directly used for rendering, they are just another level // of processing // // these either point at array_model* buffers (if the model is animated) // or the model->surfmesh.data_* buffers (if the model is not animated) // // these are only set when an entity render begins, they do not change on // a per surface basis. // // this indicates the model* arrays are pointed at array_model* buffers // (in other words, the model has been animated in software) qboolean forcecurrenttextureupdate; // set for RSurf_ActiveCustomEntity to force R_GetCurrentTexture to recalculate the texture parameters (such as entity alpha) qboolean modelgeneratedvertex; // skeletal animation can be done by entity (animcache) or per batch, // batch may be non-skeletal even if entity is skeletal, indicating that // the dynamicvertex code path had to apply skeletal manually for a case // where gpu-skinning is not possible, for this reason batch has its own // variables int entityskeletalnumtransforms; // how many transforms are used for this mesh float *entityskeletaltransform3x4; // use gpu-skinning shader on this mesh const r_meshbuffer_t *entityskeletaltransform3x4buffer; // uniform buffer int entityskeletaltransform3x4offset; int entityskeletaltransform3x4size; float *modelvertex3f; const r_meshbuffer_t *modelvertex3f_vertexbuffer; int modelvertex3f_bufferoffset; float *modelsvector3f; const r_meshbuffer_t *modelsvector3f_vertexbuffer; int modelsvector3f_bufferoffset; float *modeltvector3f; const r_meshbuffer_t *modeltvector3f_vertexbuffer; int modeltvector3f_bufferoffset; float *modelnormal3f; const r_meshbuffer_t *modelnormal3f_vertexbuffer; int modelnormal3f_bufferoffset; float *modellightmapcolor4f; const r_meshbuffer_t *modellightmapcolor4f_vertexbuffer; int modellightmapcolor4f_bufferoffset; float *modeltexcoordtexture2f; const r_meshbuffer_t *modeltexcoordtexture2f_vertexbuffer; int modeltexcoordtexture2f_bufferoffset; float *modeltexcoordlightmap2f; const r_meshbuffer_t *modeltexcoordlightmap2f_vertexbuffer; int modeltexcoordlightmap2f_bufferoffset; unsigned char *modelskeletalindex4ub; const r_meshbuffer_t *modelskeletalindex4ub_vertexbuffer; int modelskeletalindex4ub_bufferoffset; unsigned char *modelskeletalweight4ub; const r_meshbuffer_t *modelskeletalweight4ub_vertexbuffer; int modelskeletalweight4ub_bufferoffset; r_vertexmesh_t *modelvertexmesh; const r_meshbuffer_t *modelvertexmesh_vertexbuffer; int modelvertexmesh_bufferoffset; int *modelelement3i; const r_meshbuffer_t *modelelement3i_indexbuffer; int modelelement3i_bufferoffset; unsigned short *modelelement3s; const r_meshbuffer_t *modelelement3s_indexbuffer; int modelelement3s_bufferoffset; int *modellightmapoffsets; int modelnumvertices; int modelnumtriangles; const msurface_t *modelsurfaces; // current rendering array pointers // these may point to any of several different buffers depending on how // much processing was needed to prepare this model for rendering // these usually equal the model* pointers, they only differ if // deformvertexes is used in a q3 shader, and consequently these can // change on a per-surface basis (according to rsurface.texture) qboolean batchgeneratedvertex; qboolean batchmultidraw; int batchmultidrawnumsurfaces; const msurface_t **batchmultidrawsurfacelist; int batchfirstvertex; int batchnumvertices; int batchfirsttriangle; int batchnumtriangles; r_vertexmesh_t *batchvertexmesh; const r_meshbuffer_t *batchvertexmesh_vertexbuffer; int batchvertexmesh_bufferoffset; float *batchvertex3f; const r_meshbuffer_t *batchvertex3f_vertexbuffer; int batchvertex3f_bufferoffset; float *batchsvector3f; const r_meshbuffer_t *batchsvector3f_vertexbuffer; int batchsvector3f_bufferoffset; float *batchtvector3f; const r_meshbuffer_t *batchtvector3f_vertexbuffer; int batchtvector3f_bufferoffset; float *batchnormal3f; const r_meshbuffer_t *batchnormal3f_vertexbuffer; int batchnormal3f_bufferoffset; float *batchlightmapcolor4f; const r_meshbuffer_t *batchlightmapcolor4f_vertexbuffer; int batchlightmapcolor4f_bufferoffset; float *batchtexcoordtexture2f; const r_meshbuffer_t *batchtexcoordtexture2f_vertexbuffer; int batchtexcoordtexture2f_bufferoffset; float *batchtexcoordlightmap2f; const r_meshbuffer_t *batchtexcoordlightmap2f_vertexbuffer; int batchtexcoordlightmap2f_bufferoffset; unsigned char *batchskeletalindex4ub; const r_meshbuffer_t *batchskeletalindex4ub_vertexbuffer; int batchskeletalindex4ub_bufferoffset; unsigned char *batchskeletalweight4ub; const r_meshbuffer_t *batchskeletalweight4ub_vertexbuffer; int batchskeletalweight4ub_bufferoffset; int *batchelement3i; const r_meshbuffer_t *batchelement3i_indexbuffer; int batchelement3i_bufferoffset; unsigned short *batchelement3s; const r_meshbuffer_t *batchelement3s_indexbuffer; int batchelement3s_bufferoffset; int batchskeletalnumtransforms; float *batchskeletaltransform3x4; const r_meshbuffer_t *batchskeletaltransform3x4buffer; // uniform buffer int batchskeletaltransform3x4offset; int batchskeletaltransform3x4size; // rendering pass processing arrays in GL11 and GL13 paths float *passcolor4f; const r_meshbuffer_t *passcolor4f_vertexbuffer; int passcolor4f_bufferoffset; // some important fields from the entity int ent_skinnum; int ent_qwskin; int ent_flags; int ent_alttextures; // used by q1bsp animated textures (pressed buttons) double shadertime; // r_refdef.scene.time - ent->shadertime // transform matrices to render this entity and effects on this entity matrix4x4_t matrix; matrix4x4_t inversematrix; // scale factors for transforming lengths into/out of entity space float matrixscale; float inversematrixscale; // animation blending state from entity frameblend_t frameblend[MAX_FRAMEBLENDS]; skeleton_t *skeleton; // directional model shading state from entity vec3_t modellight_ambient; vec3_t modellight_diffuse; vec3_t modellight_lightdir; // colormapping state from entity (these are black if colormapping is off) vec3_t colormap_pantscolor; vec3_t colormap_shirtcolor; // special coloring of ambient/diffuse textures (gloss not affected) // colormod[3] is the alpha of the entity float colormod[4]; // special coloring of glow textures float glowmod[3]; // view location in model space vec3_t localvieworigin; // polygon offset data for submodels float basepolygonfactor; float basepolygonoffset; // current textures in batching code texture_t *texture; rtexture_t *lightmaptexture; rtexture_t *deluxemaptexture; // whether lightmapping is active on this batch // (otherwise vertex colored) qboolean uselightmaptexture; // fog plane in model space for direct application to vertices float fograngerecip; float fogmasktabledistmultiplier; float fogplane[4]; float fogheightfade; float fogplaneviewdist; // rtlight rendering // light currently being rendered const rtlight_t *rtlight; // this is the location of the light in entity space vec3_t entitylightorigin; // this transforms entity coordinates to light filter cubemap coordinates // (also often used for other purposes) matrix4x4_t entitytolight; // based on entitytolight this transforms -1 to +1 to 0 to 1 for purposes // of attenuation texturing in full 3D (Z result often ignored) matrix4x4_t entitytoattenuationxyz; // this transforms only the Z to S, and T is always 0.5 matrix4x4_t entitytoattenuationz; // user wavefunc parameters (from csqc) float userwavefunc_param[Q3WAVEFUNC_USER_COUNT]; // pointer to an entity_render_t used only by R_GetCurrentTexture and // RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity as a unique id within // each frame (see r_frame also) entity_render_t *entity; } rsurfacestate_t; extern rsurfacestate_t rsurface; void R_HDR_UpdateIrisAdaptation(const vec3_t point); void RSurf_ActiveWorldEntity(void); void RSurf_ActiveModelEntity(const entity_render_t *ent, qboolean wantnormals, qboolean wanttangents, qboolean prepass); void RSurf_ActiveCustomEntity(const matrix4x4_t *matrix, const matrix4x4_t *inversematrix, int entflags, double shadertime, float r, float g, float b, float a, int numvertices, const float *vertex3f, const float *texcoord2f, const float *normal3f, const float *svector3f, const float *tvector3f, const float *color4f, int numtriangles, const int *element3i, const unsigned short *element3s, qboolean wantnormals, qboolean wanttangents); void RSurf_SetupDepthAndCulling(void); void R_Mesh_ResizeArrays(int newvertices); texture_t *R_GetCurrentTexture(texture_t *t); void R_DrawWorldSurfaces(qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); void R_DrawModelSurfaces(entity_render_t *ent, qboolean skysurfaces, qboolean writedepth, qboolean depthonly, qboolean debug, qboolean prepass); void R_AddWaterPlanes(entity_render_t *ent); void R_DrawCustomSurface(skinframe_t *skinframe, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); void R_DrawCustomSurface_Texture(texture_t *texture, const matrix4x4_t *texmatrix, int materialflags, int firstvertex, int numvertices, int firsttriangle, int numtriangles, qboolean writedepth, qboolean prepass); #define BATCHNEED_VERTEXMESH_VERTEX (1<< 1) // set up rsurface.batchvertexmesh #define BATCHNEED_VERTEXMESH_NORMAL (1<< 2) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS #define BATCHNEED_VERTEXMESH_VECTOR (1<< 3) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS #define BATCHNEED_VERTEXMESH_VERTEXCOLOR (1<< 4) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_VERTEXMESH_TEXCOORD (1<< 5) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_VERTEXMESH_LIGHTMAP (1<< 6) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_VERTEXMESH_SKELETAL (1<< 7) // set up skeletal index and weight data for vertex shader #define BATCHNEED_ARRAY_VERTEX (1<< 8) // set up rsurface.batchvertex3f and optionally others #define BATCHNEED_ARRAY_NORMAL (1<< 9) // set up normals in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchnormal3f if BATCHNEED_ARRAYS #define BATCHNEED_ARRAY_VECTOR (1<<10) // set up vectors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchsvector3f and rsurface.batchtvector3f if BATCHNEED_ARRAYS #define BATCHNEED_ARRAY_VERTEXCOLOR (1<<11) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_ARRAY_TEXCOORD (1<<12) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_ARRAY_LIGHTMAP (1<<13) // set up vertex colors in rsurface.batchvertexmesh if BATCHNEED_MESH, set up rsurface.batchlightmapcolor4f if BATCHNEED_ARRAYS #define BATCHNEED_ARRAY_SKELETAL (1<<14) // set up skeletal index and weight data for vertex shader #define BATCHNEED_NOGAPS (1<<15) // force vertex copying if firstvertex is not zero or there are gaps #define BATCHNEED_ALLOWMULTIDRAW (1<<16) // allow multiple draws void RSurf_PrepareVerticesForBatch(int batchneed, int texturenumsurfaces, const msurface_t **texturesurfacelist); void RSurf_DrawBatch(void); void R_DecalSystem_SplatEntities(const vec3_t org, const vec3_t normal, float r, float g, float b, float a, float s1, float t1, float s2, float t2, float size); typedef enum rsurfacepass_e { RSURFPASS_BASE, RSURFPASS_BACKGROUND, RSURFPASS_RTLIGHT, RSURFPASS_DEFERREDGEOMETRY } rsurfacepass_t; void R_SetupShader_Generic(rtexture_t *first, rtexture_t *second, int texturemode, int rgbscale, qboolean usegamma, qboolean notrippy, qboolean suppresstexalpha); void R_SetupShader_Generic_NoTexture(qboolean usegamma, qboolean notrippy); void R_SetupShader_DepthOrShadow(qboolean notrippy, qboolean depthrgb, qboolean skeletal); void R_SetupShader_Surface(const vec3_t lightcolorbase, qboolean modellighting, float ambientscale, float diffusescale, float specularscale, rsurfacepass_t rsurfacepass, int texturenumsurfaces, const msurface_t **texturesurfacelist, void *waterplane, qboolean notrippy); void R_SetupShader_DeferredLight(const rtlight_t *rtlight); typedef struct r_waterstate_waterplane_s { rtexture_t *texture_refraction; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFRACTION rtexture_t *texture_reflection; // MATERIALFLAG_WATERSHADER or MATERIALFLAG_REFLECTION rtexture_t *texture_camera; // MATERIALFLAG_CAMERA int fbo_refraction; int fbo_reflection; int fbo_camera; mplane_t plane; int materialflags; // combined flags of all water surfaces on this plane unsigned char pvsbits[(MAX_MAP_LEAFS+7)>>3]; // FIXME: buffer overflow on huge maps qboolean pvsvalid; int camera_entity; vec3_t mins, maxs; } r_waterstate_waterplane_t; typedef struct r_waterstate_s { int waterwidth, waterheight; int texturewidth, textureheight; int camerawidth, cameraheight; rtexture_t *depthtexture; int maxwaterplanes; // same as MAX_WATERPLANES int numwaterplanes; r_waterstate_waterplane_t waterplanes[MAX_WATERPLANES]; float screenscale[2]; float screencenter[2]; qboolean enabled; qboolean renderingscene; // true while rendering a refraction or reflection texture, disables water surfaces qboolean hideplayer; } r_waterstate_t; typedef struct r_framebufferstate_s { textype_t textype; // type of color buffer we're using (dependent on r_viewfbo cvar) int fbo; // non-zero if r_viewfbo is enabled and working int screentexturewidth, screentextureheight; // dimensions of texture rtexture_t *colortexture; // non-NULL if fbo is non-zero rtexture_t *depthtexture; // non-NULL if fbo is non-zero rtexture_t *ghosttexture; // for r_motionblur (not recommended on multi-GPU hardware!) rtexture_t *bloomtexture[2]; // for r_bloom, multi-stage processing int bloomfbo[2]; // fbos for rendering into bloomtexture[] int bloomindex; // which bloomtexture[] contains the final image int bloomwidth, bloomheight; int bloomtexturewidth, bloomtextureheight; // arrays for rendering the screen passes float screentexcoord2f[8]; // texcoords for colortexture or ghosttexture float bloomtexcoord2f[8]; // texcoords for bloomtexture[] float offsettexcoord2f[8]; // temporary use while updating bloomtexture[] r_viewport_t bloomviewport; r_waterstate_t water; qboolean ghosttexture_valid; // don't draw garbage on first frame with motionblur qboolean usedepthtextures; // use depth texture instead of depth renderbuffer (faster if you need to read it later anyway) } r_framebufferstate_t; extern r_framebufferstate_t r_fb; extern cvar_t r_viewfbo; void R_ResetViewRendering2D_Common(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture, float x2, float y2); // this is called by R_ResetViewRendering2D and _DrawQ_Setup and internal void R_ResetViewRendering2D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_ResetViewRendering3D(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_SetupView(qboolean allowwaterclippingplane, int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); extern const float r_screenvertex3f[12]; extern cvar_t r_shadows; extern cvar_t r_shadows_darken; extern cvar_t r_shadows_drawafterrtlighting; extern cvar_t r_shadows_castfrombmodels; extern cvar_t r_shadows_throwdistance; extern cvar_t r_shadows_throwdirection; extern cvar_t r_shadows_focus; extern cvar_t r_shadows_shadowmapscale; extern cvar_t r_shadows_shadowmapbias; extern cvar_t r_transparent_alphatocoverage; extern cvar_t r_transparent_sortsurfacesbynearest; extern cvar_t r_transparent_useplanardistance; extern cvar_t r_transparent_sortarraysize; extern cvar_t r_transparent_sortmindist; extern cvar_t r_transparent_sortmaxdist; void R_Model_Sprite_Draw(entity_render_t *ent); struct prvm_prog_s; void R_UpdateFog(void); qboolean CL_VM_UpdateView(double frametime); void SCR_DrawConsole(void); void R_Shadow_EditLights_DrawSelectedLightProperties(void); void R_DecalSystem_Reset(decalsystem_t *decalsystem); void R_Shadow_UpdateBounceGridTexture(void); void R_DrawLightningBeams(void); void VM_CL_AddPolygonsToMeshQueue(struct prvm_prog_s *prog); void R_DrawPortals(void); void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture); void R_BuildLightMap(const entity_render_t *ent, msurface_t *surface); void R_Water_AddWaterPlane(msurface_t *surface, int entno); int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color); dp_font_t *FindFont(const char *title, qboolean allocate_new); void LoadFont(qboolean override, const char *name, dp_font_t *fnt, float scale, float voffset); void Render_Init(void); // these are called by Render_Init void R_Textures_Init(void); void GL_Draw_Init(void); void GL_Main_Init(void); void R_Shadow_Init(void); void R_Sky_Init(void); void GL_Surf_Init(void); void R_Particles_Init(void); void R_Explosion_Init(void); void gl_backend_init(void); void Sbar_Init(void); void R_LightningBeams_Init(void); void Mod_RenderInit(void); void Font_Init(void); qboolean R_CompileShader_CheckStaticParms(void); void R_GLSL_Restart_f(void); #endif darkplaces/shader_hlsl.h0000664000175000017500000021133213067716222014630 0ustar kalevkalev"// ambient+diffuse+specular+normalmap+attenuation+cubemap+fog shader\n", "// written by Forest 'LordHavoc' Hale\n", "// shadowmapping enhancements by Lee 'eihrul' Salzman\n", "\n", "// FIXME: we need to get rid of ModelViewProjectionPosition to make room for the texcoord for this\n", "#if defined(USEREFLECTION)\n", "#undef USESHADOWMAPORTHO\n", "#endif\n", "\n", "#if defined(USEFOGINSIDE) || defined(USEFOGOUTSIDE) || defined(USEFOGHEIGHTTEXTURE)\n", "# define USEFOG\n", "#endif\n", "#if defined(MODE_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP)\n", "#define USELIGHTMAP\n", "#endif\n", "#if defined(USESPECULAR) || defined(USEOFFSETMAPPING) || defined(USEREFLECTCUBE) || defined(MODE_FAKELIGHT)\n", "#define USEEYEVECTOR\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "#ifdef HLSL\n", "//#undef USESHADOWMAPPCF\n", "//#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n", "#define texDepth2D(tex,texcoord) dot(tex2D(tex,texcoord).rgb, float3(1.0, 255.0/65536.0, 255.0/16777216.0))\n", "#else\n", "#define texDepth2D(tex,texcoord) tex2D(tex,texcoord).r\n", "#endif\n", "#endif\n", "\n", "#ifdef VERTEX_SHADER\n", "#ifdef USETRIPPY\n", "// LordHavoc: based on shader code linked at: http://www.youtube.com/watch?v=JpksyojwqzE\n", "// tweaked scale\n", "float4 TrippyVertex\n", "(\n", "float4 position,\n", "float ClientTime\n", ")\n", "{\n", " float worldTime = ClientTime;\n", " // tweaked for Quake\n", " worldTime *= 10.0;\n", " position *= 0.125;\n", " //~tweaked for Quake\n", " float distanceSquared = (position.x * position.x + position.z * position.z);\n", " position.y += 5.0*sin(distanceSquared*sin(worldTime/143.0)/1000.0);\n", " float y = position.y;\n", " float x = position.x;\n", " float om = sin(distanceSquared*sin(worldTime/256.0)/5000.0) * sin(worldTime/200.0);\n", " position.y = x*sin(om)+y*cos(om);\n", " position.x = x*cos(om)-y*sin(om);\n", " return position;\n", "}\n", "#endif\n", "#endif\n", "\n", "#ifdef MODE_DEPTH_OR_SHADOW\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "uniform float ClientTime : register(c2),\n", "out float4 gl_Position : POSITION,\n", "out float Depth : TEXCOORD0\n", ")\n", "{\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", " Depth = gl_Position.z;\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "float Depth : TEXCOORD0,\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", "// float4 temp = float4(Depth,Depth*(65536.0/255.0),Depth*(16777216.0/255.0),0.0);\n", " float4 temp = float4(Depth,Depth*256.0,Depth*65536.0,0.0);\n", " temp.yz -= floor(temp.yz);\n", " dp_FragColor = temp;\n", "// dp_FragColor = float4(Depth,0,0,0);\n", "}\n", "#endif\n", "#else // !MODE_DEPTH_OR_SHADOW\n", "\n", "\n", "\n", "\n", "#ifdef MODE_POSTPROCESS\n", "\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "float4 gl_MultiTexCoord4 : TEXCOORD4,\n", "out float4 gl_Position : POSITION,\n", "out float2 TexCoord1 : TEXCOORD0\n", "#ifdef USEBLOOM\n", ", out float2 TexCoord2 : TEXCOORD1\n", "#endif\n", ")\n", "{\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", " TexCoord1 = gl_MultiTexCoord0.xy;\n", "#ifdef USEBLOOM\n", " TexCoord2 = gl_MultiTexCoord4.xy;\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "#ifdef USEFXAA\n", "// graphitemaster: based off the white paper by Timothy Lottes\n", "// http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf\n", "float4 fxaa\n", "(\n", " float4 inColor,\n", " float maxspan,\n", " float2 TexCoord1,\n", " uniform sampler Texture_First,\n", " uniform float2 PixelSize\n", ")\n", "{\n", " float4 ret = inColor; // preserve old\n", " float mulreduct = 1.0/maxspan;\n", " float minreduct = (1.0 / 128.0);\n", "\n", " float3 NW = tex2D(Texture_First, TexCoord1 + (float2(-1.0, -1.0) * PixelSize)).xyz;\n", " float3 NE = tex2D(Texture_First, TexCoord1 + (float2(+1.0, -1.0) * PixelSize)).xyz;\n", " float3 SW = tex2D(Texture_First, TexCoord1 + (float2(-1.0, +1.0) * PixelSize)).xyz;\n", " float3 SE = tex2D(Texture_First, TexCoord1 + (float2(+1.0, +1.0) * PixelSize)).xyz;\n", " float3 M = tex2D(Texture_First, TexCoord1).xyz;\n", "\n", " // luminance directions\n", " float3 luma = float3(0.299, 0.587, 0.114);\n", " float lNW = dot(NW, luma);\n", " float lNE = dot(NE, luma);\n", " float lSW = dot(SW, luma);\n", " float lSE = dot(SE, luma);\n", " float lM = dot(M, luma);\n", " float lMin = min(lM, min(min(lNW, lNE), min(lSW, lSE)));\n", " float lMax = max(lM, max(max(lNW, lNE), max(lSW, lSE)));\n", "\n", " // direction and reciprocal\n", " float2 dir = float2(-((lNW + lNE) - (lSW + lSE)), ((lNW + lSW) - (lNE + lSE)));\n", " float rcp = 1.0/(min(abs(dir.x), abs(dir.y)) + max((lNW + lNE + lSW + lSE) * (0.25 * mulreduct), minreduct));\n", "\n", " // span\n", " dir = min(float2(maxspan, maxspan), max(float2(-maxspan, -maxspan), dir * rcp)) * PixelSize;\n", "\n", " float3 rA = (1.0/2.0) * (\n", " tex2D(Texture_First, TexCoord1 + dir * (1.0/3.0 - 0.5)).xyz +\n", " tex2D(Texture_First, TexCoord1 + dir * (2.0/3.0 - 0.5)).xyz);\n", " float3 rB = rA * (1.0/2.0) + (1.0/4.0) * (\n", " tex2D(Texture_First, TexCoord1 + dir * (0.0/3.0 - 0.5)).xyz +\n", " tex2D(Texture_First, TexCoord1 + dir * (3.0/3.0 - 0.5)).xyz);\n", " float lB = dot(rB, luma);\n", "\n", " ret.xyz = ((lB < lMin) || (lB > lMax)) ? rA : rB;\n", " ret.a = 1.0;\n", " return ret;\n", "}\n", "#endif\n", "\n", "void main\n", "(\n", "float2 TexCoord1 : TEXCOORD0,\n", "#ifdef USEBLOOM\n", "float2 TexCoord2 : TEXCOORD1,\n", "#endif\n", "uniform sampler Texture_First : register(s0),\n", "#ifdef USEBLOOM\n", "uniform sampler Texture_Second : register(s1),\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", "uniform sampler Texture_GammaRamps : register(s2),\n", "#endif\n", "#ifdef USESATURATION\n", "uniform float Saturation : register(c30),\n", "#endif\n", "#ifdef USEVIEWTINT\n", "uniform float4 ViewTintColor : register(c41),\n", "#endif\n", "uniform float4 UserVec1 : register(c37),\n", "uniform float4 UserVec2 : register(c38),\n", "uniform float4 UserVec3 : register(c39),\n", "uniform float4 UserVec4 : register(c40),\n", "uniform float ClientTime : register(c2),\n", "uniform float2 PixelSize : register(c25),\n", "uniform float4 BloomColorSubtract : register(c43),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " dp_FragColor = tex2D(Texture_First, TexCoord1);\n", "\n", "#ifdef USEFXAA\n", " dp_FragColor = fxaa(dp_FragColor, 8.0, TexCoord1, Texture_First, PixelSize); // 8.0 can be changed for larger span\n", "#endif\n", "\n", "#ifdef USEPOSTPROCESSING\n", "// do r_glsl_dumpshader, edit glsl/default.glsl, and replace this by your own postprocessing if you want\n", "// this code does a blur with the radius specified in the first component of r_glsl_postprocess_uservec1 and blends it using the second component\n", "#if defined(USERVEC1) || defined(USERVEC2)\n", " float sobel = 1.0;\n", " // float2 ts = textureSize(Texture_First, 0);\n", " // float2 px = float2(1/ts.x, 1/ts.y);\n", " float2 px = PixelSize;\n", " float3 x1 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n", " float3 x2 = tex2D(Texture_First, TexCoord1 + float2(-px.x, 0.0)).rgb;\n", " float3 x3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n", " float3 x4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n", " float3 x5 = tex2D(Texture_First, TexCoord1 + float2( px.x, 0.0)).rgb;\n", " float3 x6 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n", " float3 y1 = tex2D(Texture_First, TexCoord1 + float2( px.x,-px.y)).rgb;\n", " float3 y2 = tex2D(Texture_First, TexCoord1 + float2( 0.0,-px.y)).rgb;\n", " float3 y3 = tex2D(Texture_First, TexCoord1 + float2(-px.x,-px.y)).rgb;\n", " float3 y4 = tex2D(Texture_First, TexCoord1 + float2( px.x, px.y)).rgb;\n", " float3 y5 = tex2D(Texture_First, TexCoord1 + float2( 0.0, px.y)).rgb;\n", " float3 y6 = tex2D(Texture_First, TexCoord1 + float2(-px.x, px.y)).rgb;\n", " float px1 = -1.0 * dot(float3(0.3, 0.59, 0.11), x1);\n", " float px2 = -2.0 * dot(float3(0.3, 0.59, 0.11), x2);\n", " float px3 = -1.0 * dot(float3(0.3, 0.59, 0.11), x3);\n", " float px4 = 1.0 * dot(float3(0.3, 0.59, 0.11), x4);\n", " float px5 = 2.0 * dot(float3(0.3, 0.59, 0.11), x5);\n", " float px6 = 1.0 * dot(float3(0.3, 0.59, 0.11), x6);\n", " float py1 = -1.0 * dot(float3(0.3, 0.59, 0.11), y1);\n", " float py2 = -2.0 * dot(float3(0.3, 0.59, 0.11), y2);\n", " float py3 = -1.0 * dot(float3(0.3, 0.59, 0.11), y3);\n", " float py4 = 1.0 * dot(float3(0.3, 0.59, 0.11), y4);\n", " float py5 = 2.0 * dot(float3(0.3, 0.59, 0.11), y5);\n", " float py6 = 1.0 * dot(float3(0.3, 0.59, 0.11), y6);\n", " sobel = 0.25 * abs(px1 + px2 + px3 + px4 + px5 + px6) + 0.25 * abs(py1 + py2 + py3 + py4 + py5 + py6);\n", " dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.987688, -0.156434)) * UserVec1.y;\n", " dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.156434, -0.891007)) * UserVec1.y;\n", " dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.891007, -0.453990)) * UserVec1.y;\n", " dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2( 0.707107, 0.707107)) * UserVec1.y;\n", " dp_FragColor += tex2D(Texture_First, TexCoord1 + PixelSize*UserVec1.x*float2(-0.453990, 0.891007)) * UserVec1.y;\n", " dp_FragColor /= (1.0 + 5.0 * UserVec1.y);\n", " dp_FragColor.rgb = dp_FragColor.rgb * (1.0 + UserVec2.x) + float3(1,1,1)*max(0.0, sobel - UserVec2.z)*UserVec2.y;\n", "#endif\n", "#endif\n", "\n", "#ifdef USEBLOOM\n", " dp_FragColor += max(float4(0,0,0,0), tex2D(Texture_Second, TexCoord2) - BloomColorSubtract);\n", "#endif\n", "\n", "#ifdef USEVIEWTINT\n", " dp_FragColor = lerp(dp_FragColor, ViewTintColor, ViewTintColor.a);\n", "#endif\n", "\n", "#ifdef USESATURATION\n", " //apply saturation BEFORE gamma ramps, so v_glslgamma value does not matter\n", " float y = dot(dp_FragColor.rgb, float3(0.299, 0.587, 0.114));\n", " // 'vampire sight' effect, wheres red is compensated\n", " #ifdef SATURATION_REDCOMPENSATE\n", " float rboost = max(0.0, (dp_FragColor.r - max(dp_FragColor.g, dp_FragColor.b))*(1.0 - Saturation));\n", " dp_FragColor.rgb = lerp(float3(y,y,y), dp_FragColor.rgb, Saturation);\n", " dp_FragColor.r += r;\n", " #else\n", " // normal desaturation\n", " //dp_FragColor = float3(y,y,y) + (dp_FragColor.rgb - float3(y)) * Saturation;\n", " dp_FragColor.rgb = lerp(float3(y,y,y), dp_FragColor.rgb, Saturation);\n", " #endif\n", "#endif\n", "\n", "#ifdef USEGAMMARAMPS\n", " dp_FragColor.r = tex2D(Texture_GammaRamps, float2(dp_FragColor.r, 0)).r;\n", " dp_FragColor.g = tex2D(Texture_GammaRamps, float2(dp_FragColor.g, 0)).g;\n", " dp_FragColor.b = tex2D(Texture_GammaRamps, float2(dp_FragColor.b, 0)).b;\n", "#endif\n", "}\n", "#endif\n", "#else // !MODE_POSTPROCESS\n", "\n", "\n", "\n", "\n", "#ifdef MODE_GENERIC\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "float4 gl_Color : COLOR0,\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "float4 gl_MultiTexCoord1 : TEXCOORD1,\n", "uniform float ClientTime : register(c2),\n", "out float4 gl_Position : POSITION,\n", "#ifdef USEDIFFUSE\n", "out float2 TexCoord1 : TEXCOORD0,\n", "#endif\n", "#ifdef USESPECULAR\n", "out float2 TexCoord2 : TEXCOORD1,\n", "#endif\n", "out float4 gl_FrontColor : COLOR\n", ")\n", "{\n", " gl_FrontColor = gl_Color;\n", "#ifdef USEDIFFUSE\n", " TexCoord1 = gl_MultiTexCoord0.xy;\n", "#endif\n", "#ifdef USESPECULAR\n", " TexCoord2 = gl_MultiTexCoord1.xy;\n", "#endif\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "\n", "void main\n", "(\n", "float4 gl_FrontColor : COLOR0,\n", "float2 TexCoord1 : TEXCOORD0,\n", "float2 TexCoord2 : TEXCOORD1,\n", "#ifdef USEDIFFUSE\n", "uniform sampler Texture_First : register(s0),\n", "#endif\n", "#ifdef USESPECULAR\n", "uniform sampler Texture_Second : register(s1),\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", "uniform sampler Texture_GammaRamps : register(s2),\n", "#endif\n", "uniform half Alpha : register(c0),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", "#ifdef USEVIEWTINT\n", " dp_FragColor = gl_FrontColor;\n", "#else\n", " dp_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n", "#endif\n", "#ifdef USEDIFFUSE\n", "# ifdef USEREFLECTCUBE\n", " // suppress texture alpha\n", " dp_FragColor.rgb *= tex2D(Texture_First, TexCoord1).rgb;\n", "# else\n", " dp_FragColor *= tex2D(Texture_First, TexCoord1);\n", "# endif\n", "#endif\n", "\n", "#ifdef USESPECULAR\n", " float4 tex2 = tex2D(Texture_Second, TexCoord2);\n", "# ifdef USECOLORMAPPING\n", " dp_FragColor *= tex2;\n", "# endif\n", "# ifdef USEGLOW\n", " dp_FragColor += tex2;\n", "# endif\n", "# ifdef USEVERTEXTEXTUREBLEND\n", " dp_FragColor = lerp(dp_FragColor, tex2, tex2.a);\n", "# endif\n", "#endif\n", "#ifdef USEGAMMARAMPS\n", " dp_FragColor.r = tex2D(Texture_GammaRamps, vec2(dp_FragColor.r, 0)).r;\n", " dp_FragColor.g = tex2D(Texture_GammaRamps, vec2(dp_FragColor.g, 0)).g;\n", " dp_FragColor.b = tex2D(Texture_GammaRamps, vec2(dp_FragColor.b, 0)).b;\n", "#endif\n", "#ifdef USEALPHAKILL\n", " dp_FragColor.a *= Alpha;\n", "#endif\n", "}\n", "#endif\n", "#else // !MODE_GENERIC\n", "\n", "\n", "\n", "\n", "#ifdef MODE_BLOOMBLUR\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "out float4 gl_Position : POSITION,\n", "out float2 TexCoord : TEXCOORD0\n", ")\n", "{\n", " TexCoord = gl_MultiTexCoord0.xy;\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "\n", "void main\n", "(\n", "float2 TexCoord : TEXCOORD0,\n", "uniform sampler Texture_First : register(s0),\n", "uniform float4 BloomBlur_Parameters : register(c1),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " int i;\n", " float2 tc = TexCoord;\n", " float3 color = tex2D(Texture_First, tc).rgb;\n", " tc += BloomBlur_Parameters.xy;\n", " for (i = 1;i < SAMPLES;i++)\n", " {\n", " color += tex2D(Texture_First, tc).rgb;\n", " tc += BloomBlur_Parameters.xy;\n", " }\n", " dp_FragColor = float4(color * BloomBlur_Parameters.z + float3(BloomBlur_Parameters.w), 1);\n", "}\n", "#endif\n", "#else // !MODE_BLOOMBLUR\n", "#ifdef MODE_REFRACTION\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "#ifdef USEALPHAGENVERTEX\n", "float4 gl_Color : COLOR0,\n", "#endif\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "uniform float4x4 TexMatrix : register(c0),\n", "uniform float3 EyePosition : register(c24),\n", "uniform float ClientTime : register(c2),\n", "#ifdef USEALPHAGENVERTEX\n", "out float4 gl_FrontColor : COLOR,\n", "#endif\n", "out float4 gl_Position : POSITION,\n", "out float2 TexCoord : TEXCOORD0,\n", "out float3 EyeVector : TEXCOORD1,\n", "out float4 ModelViewProjectionPosition : TEXCOORD2\n", ")\n", "{\n", "#ifdef USEALPHAGENVERTEX\n", " gl_FrontColor = gl_Color;\n", "#endif\n", " TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", " ModelViewProjectionPosition = gl_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "#ifdef USEALPHAGENVERTEX\n", "float4 gl_FrontColor : COLOR,\n", "#endif\n", "float2 TexCoord : TEXCOORD0,\n", "float3 EyeVector : TEXCOORD1,\n", "float4 ModelViewProjectionPosition : TEXCOORD2,\n", "uniform sampler Texture_Normal : register(s0),\n", "uniform sampler Texture_Refraction : register(s3),\n", "uniform sampler Texture_Reflection : register(s7),\n", "uniform float4 DistortScaleRefractReflect : register(c14),\n", "uniform float4 ScreenScaleRefractReflect : register(c32),\n", "uniform float4 ScreenCenterRefractReflect : register(c31),\n", "uniform float4 RefractColor : register(c29),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " float2 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect.xy * (1.0 / ModelViewProjectionPosition.w);\n", " //float2 ScreenTexCoord = (ModelViewProjectionPosition.xy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * DistortScaleRefractReflect.xy * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", " float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect.xy;\n", "#ifdef USEALPHAGENVERTEX\n", " float2 distort = DistortScaleRefractReflect.xy * gl_FrontColor.a;\n", " float4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), gl_FrontColor.a);\n", "#else\n", " float2 distort = DistortScaleRefractReflect.xy;\n", " float4 refractcolor = RefractColor;\n", "#endif\n", " float2 ScreenTexCoord = SafeScreenTexCoord + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy * distort;\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n", " dp_FragColor = float4(tex2D(Texture_Refraction, ScreenTexCoord).rgb, 1) * refractcolor;\n", "}\n", "#endif\n", "#else // !MODE_REFRACTION\n", "\n", "\n", "\n", "\n", "#ifdef MODE_WATER\n", "#ifdef VERTEX_SHADER\n", "\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "#ifdef USEALPHAGENVERTEX\n", "float4 gl_Color : COLOR0,\n", "#endif\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "float4 gl_MultiTexCoord1 : TEXCOORD1,\n", "float4 gl_MultiTexCoord2 : TEXCOORD2,\n", "float4 gl_MultiTexCoord3 : TEXCOORD3,\n", "uniform float4x4 TexMatrix : register(c0),\n", "uniform float3 EyePosition : register(c24),\n", "uniform float ClientTime : register(c2),\n", "#ifdef USEALPHAGENVERTEX\n", "out float4 gl_FrontColor : COLOR,\n", "#endif\n", "out float4 gl_Position : POSITION,\n", "out float2 TexCoord : TEXCOORD0,\n", "out float3 EyeVector : TEXCOORD1,\n", "out float4 ModelViewProjectionPosition : TEXCOORD2\n", ")\n", "{\n", "#ifdef USEALPHAGENVERTEX\n", " gl_FrontColor = gl_Color;\n", "#endif\n", " TexCoord = mul(TexMatrix, gl_MultiTexCoord0).xy;\n", " float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", " EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", " ModelViewProjectionPosition = gl_Position;\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "#ifdef USEALPHAGENVERTEX\n", "float4 gl_FrontColor : COLOR,\n", "#endif\n", "float2 TexCoord : TEXCOORD0,\n", "float3 EyeVector : TEXCOORD1,\n", "float4 ModelViewProjectionPosition : TEXCOORD2,\n", "uniform sampler Texture_Normal : register(s0),\n", "uniform sampler Texture_Refraction : register(s3),\n", "uniform sampler Texture_Reflection : register(s7),\n", "uniform float4 DistortScaleRefractReflect : register(c14),\n", "uniform float4 ScreenScaleRefractReflect : register(c32),\n", "uniform float4 ScreenCenterRefractReflect : register(c31),\n", "uniform float4 RefractColor : register(c29),\n", "uniform float4 ReflectColor : register(c26),\n", "uniform float ReflectFactor : register(c27),\n", "uniform float ReflectOffset : register(c28),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", " //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " float4 SafeScreenTexCoord = ModelViewProjectionPosition.xyxy * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " //SafeScreenTexCoord = gl_FragCoord.xyxy * float4(1.0 / 1920.0, 1.0 / 1200.0, 1.0 / 1920.0, 1.0 / 1200.0);\n", "#ifdef USEALPHAGENVERTEX\n", " float4 distort = DistortScaleRefractReflect * gl_FrontColor.a;\n", " float reflectoffset = ReflectOffset * gl_FrontColor.a;\n", " float reflectfactor = ReflectFactor * gl_FrontColor.a;\n", " float4 refractcolor = mix(RefractColor, vec4(1.0, 1.0, 1.0, 1.0), gl_FrontColor.a);\n", "#else\n", " float4 distort = DistortScaleRefractReflect;\n", " float reflectoffset = ReflectOffset;\n", " float reflectfactor = ReflectFactor;\n", " float4 refractcolor = RefractColor;\n", "#endif\n", " float4 ScreenTexCoord = SafeScreenTexCoord + float2(normalize(tex2D(Texture_Normal, TexCoord).rgb - float3(0.5,0.5,0.5)).xy).xyxy * distort;\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Refraction, ScreenTexCoord.xy + float2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord.xy = lerp(SafeScreenTexCoord.xy, ScreenTexCoord.xy, f);\n", " f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord.zw + float2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord.zw = lerp(SafeScreenTexCoord.zw, ScreenTexCoord.zw, f);\n", " float Fresnel = pow(min(1.0, 1.0 - float(normalize(EyeVector).z)), 2.0) * reflectfactor + reflectoffset;\n", " dp_FragColor = lerp(float4(tex2D(Texture_Refraction, ScreenTexCoord.xy).rgb, 1) * refractcolor, float4(tex2D(Texture_Reflection, ScreenTexCoord.zw).rgb, 1) * ReflectColor, Fresnel);\n", "}\n", "#endif\n", "#else // !MODE_WATER\n", "\n", "\n", "\n", "\n", "// TODO: get rid of tangentt (texcoord2) and use a crossproduct to regenerate it from tangents (texcoord1) and normal (texcoord3), this would require sending a 4 component texcoord1 with W as 1 or -1 according to which side the texcoord2 should be on\n", "\n", "// fragment shader specific:\n", "#ifdef FRAGMENT_SHADER\n", "\n", "#ifdef USEFOG\n", "float3 FogVertex(float4 surfacecolor, float3 FogColor, float3 EyeVectorModelSpace, float FogPlaneVertexDist, float FogRangeRecip, float FogPlaneViewDist, float FogHeightFade, sampler Texture_FogMask, sampler Texture_FogHeightTexture)\n", "{\n", " float fogfrac;\n", " float3 fc = FogColor;\n", "#ifdef USEFOGALPHAHACK\n", " fc *= surfacecolor.a;\n", "#endif\n", "#ifdef USEFOGHEIGHTTEXTURE\n", " float4 fogheightpixel = tex2D(Texture_FogHeightTexture, float2(1,1) + float2(FogPlaneVertexDist, FogPlaneViewDist) * (-2.0 * FogHeightFade));\n", " fogfrac = fogheightpixel.a;\n", " return lerp(fogheightpixel.rgb * fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", "#else\n", "# ifdef USEFOGOUTSIDE\n", " fogfrac = min(0.0, FogPlaneVertexDist) / (FogPlaneVertexDist - FogPlaneViewDist) * min(1.0, min(0.0, FogPlaneVertexDist) * FogHeightFade);\n", "# else\n", " fogfrac = FogPlaneViewDist / (FogPlaneViewDist - max(0.0, FogPlaneVertexDist)) * min(1.0, (min(0.0, FogPlaneVertexDist) + FogPlaneViewDist) * FogHeightFade);\n", "# endif\n", " return lerp(fc, surfacecolor.rgb, tex2D(Texture_FogMask, float2(length(EyeVectorModelSpace)*fogfrac*FogRangeRecip, 0.0)).r);\n", "#endif\n", "}\n", "#endif\n", "\n", "#ifdef USEOFFSETMAPPING\n", "float2 OffsetMapping(float2 TexCoord, float4 OffsetMapping_ScaleSteps, float OffsetMapping_Bias, float OffsetMapping_LodDistance, float3 EyeVector, sampler Texture_Normal, float2 dPdx, float2 dPdy)\n", "{\n", " float i;\n", " // distance-based LOD\n", "#ifdef USEOFFSETMAPPING_LOD\n", " //float LODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", " //float4 ScaleSteps = float4(OffsetMapping_ScaleSteps.x, OffsetMapping_ScaleSteps.y * LODFactor, OffsetMapping_ScaleSteps.z / LODFactor, OffsetMapping_ScaleSteps.w * LODFactor);\n", " float GuessLODFactor = min(1.0, OffsetMapping_LodDistance / EyeVectorFogDepth.z);\n", "#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", " // stupid workaround because 1-step and 2-step reliefmapping is void\n", " float LODSteps = max(3.0, ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y));\n", "#else\n", " float LODSteps = ceil(GuessLODFactor * OffsetMapping_ScaleSteps.y);\n", "#endif\n", " float LODFactor = LODSteps / OffsetMapping_ScaleSteps.y;\n", " float4 ScaleSteps = float4(OffsetMapping_ScaleSteps.x, LODSteps, 1.0 / LODSteps, OffsetMapping_ScaleSteps.w * LODFactor);\n", "#else\n", " #define ScaleSteps OffsetMapping_ScaleSteps\n", "#endif\n", "#ifdef USEOFFSETMAPPING_RELIEFMAPPING\n", " // 14 sample relief mapping: linear search and then binary search\n", " // this basically steps forward a small amount repeatedly until it finds\n", " // itself inside solid, then jitters forward and back using decreasing\n", " // amounts to find the impact\n", " //float3 OffsetVector = float3(EyeVector.xy * ((1.0 / EyeVector.z) * ScaleSteps.x) * float2(-1, 1), -1);\n", " //float3 OffsetVector = float3(normalize(EyeVector.xy) * ScaleSteps.x * float2(-1, 1), -1);\n", " float3 OffsetVector = float3(normalize(EyeVector).xy * ScaleSteps.x * float2(-1, 1), -1);\n", " float3 RT = float3(float2(TexCoord.xy - OffsetVector.xy*OffsetMapping_Bias), 1);\n", " OffsetVector *= ScaleSteps.z;\n", " for(i = 1.0; i < ScaleSteps.y; ++i)\n", " RT += OffsetVector * step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z);\n", " for(i = 0.0, f = 1.0; i < ScaleSteps.w; ++i, f *= 0.5)\n", " RT += OffsetVector * (step(tex2Dgrad(Texture_Normal, RT.xy, dPdx, dPdy).a, RT.z) * f - 0.5 * f);\n", " return RT.xy;\n", "#else\n", " // 2 sample offset mapping (only 2 samples because of ATI Radeon 9500-9800/X300 limits)\n", " //float2 OffsetVector = float2(EyeVector.xy * ((1.0 / EyeVector.z) * ScaleSteps.x) * float2(-1, 1));\n", " //float2 OffsetVector = float2(normalize(EyeVector.xy) * ScaleSteps.x * float2(-1, 1));\n", " float2 OffsetVector = float2(normalize(EyeVector).xy * ScaleSteps.x * float2(-1, 1));\n", " OffsetVector *= ScaleSteps.z;\n", " for(i = 0.0; i < ScaleSteps.y; ++i)\n", " TexCoord += OffsetVector * ((1.0 - OffsetMapping_Bias) - tex2Dgrad(Texture_Normal, TexCoord, dPdx, dPdy).a);\n", " return TexCoord;\n", "#endif\n", "}\n", "#endif // USEOFFSETMAPPING\n", "\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", "#if defined(USESHADOWMAP2D)\n", "# ifdef USESHADOWMAPORTHO\n", "# define GetShadowMapTC2D(dir, ShadowMap_Parameters) (min(dir, ShadowMap_Parameters.xyz))\n", "# else\n", "# ifdef USESHADOWMAPVSDCT\n", "float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters, samplerCUBE Texture_CubeProjection)\n", "{\n", " float3 adir = abs(dir);\n", " float2 mparams = ShadowMap_Parameters.xy / max(max(adir.x, adir.y), adir.z);\n", " float4 proj = texCUBE(Texture_CubeProjection, dir);\n", " return float3(lerp(dir.xy, dir.zz, proj.xy) * mparams.x + proj.zw * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", "}\n", "# else\n", "float3 GetShadowMapTC2D(float3 dir, float4 ShadowMap_Parameters)\n", "{\n", " float3 adir = abs(dir);\n", " float m; float4 proj;\n", " if (adir.x > adir.y) { m = adir.x; proj = float4(dir.zyx, 0.5); } else { m = adir.y; proj = float4(dir.xzy, 1.5); }\n", " if (adir.z > m) { m = adir.z; proj = float4(dir, 2.5); }\n", "#ifdef HLSL\n", " return float3(proj.xy * ShadowMap_Parameters.x / m + float2(0.5,0.5) + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, m + 64 * ShadowMap_Parameters.w);\n", "#else\n", " float2 mparams = ShadowMap_Parameters.xy / m;\n", " return float3(proj.xy * mparams.x + float2(proj.z < 0.0 ? 1.5 : 0.5, proj.w) * ShadowMap_Parameters.z, mparams.y + ShadowMap_Parameters.w);\n", "#endif\n", "}\n", "# endif\n", "# endif\n", "#endif // defined(USESHADOWMAP2D)\n", "\n", "# ifdef USESHADOWMAP2D\n", "#ifdef USESHADOWMAPVSDCT\n", "float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale, samplerCUBE Texture_CubeProjection)\n", "#else\n", "float ShadowMapCompare(float3 dir, sampler Texture_ShadowMap2D, float4 ShadowMap_Parameters, float2 ShadowMap_TextureScale)\n", "#endif\n", "{\n", "#ifdef USESHADOWMAPVSDCT\n", " float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters, Texture_CubeProjection);\n", "#else\n", " float3 shadowmaptc = GetShadowMapTC2D(dir, ShadowMap_Parameters);\n", "#endif\n", " float f;\n", "\n", "# ifdef USESHADOWSAMPLER\n", "# ifdef USESHADOWMAPPCF\n", "# define texval(x, y) tex2Dproj(Texture_ShadowMap2D, float4(center + float2(x, y)*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r \n", " float2 center = shadowmaptc.xy*ShadowMap_TextureScale;\n", " f = dot(float4(0.25,0.25,0.25,0.25), float4(texval(-0.4, 1.0), texval(-1.0, -0.4), texval(0.4, -1.0), texval(1.0, 0.4)));\n", "# else\n", " f = tex2Dproj(Texture_ShadowMap2D, float4(shadowmaptc.xy*ShadowMap_TextureScale, shadowmaptc.z, 1.0)).r;\n", "# endif\n", "# else\n", "# ifdef USESHADOWMAPPCF\n", "# if defined(GL_ARB_texture_gather) || defined(GL_AMD_texture_texture4)\n", "# ifdef GL_ARB_texture_gather\n", "# define texval(x, y) textureGatherOffset(Texture_ShadowMap2D, center, int2(x, y))\n", "# else\n", "# define texval(x, y) texture4(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale)\n", "# endif\n", " float2 offset = frac(shadowmaptc.xy - 0.5), center = (shadowmaptc.xy - offset)*ShadowMap_TextureScale;\n", "# if USESHADOWMAPPCF > 1\n", " float4 group1 = step(shadowmaptc.z, texval(-2.0, -2.0));\n", " float4 group2 = step(shadowmaptc.z, texval( 0.0, -2.0));\n", " float4 group3 = step(shadowmaptc.z, texval( 2.0, -2.0));\n", " float4 group4 = step(shadowmaptc.z, texval(-2.0, 0.0));\n", " float4 group5 = step(shadowmaptc.z, texval( 0.0, 0.0));\n", " float4 group6 = step(shadowmaptc.z, texval( 2.0, 0.0));\n", " float4 group7 = step(shadowmaptc.z, texval(-2.0, 2.0));\n", " float4 group8 = step(shadowmaptc.z, texval( 0.0, 2.0));\n", " float4 group9 = step(shadowmaptc.z, texval( 2.0, 2.0));\n", " float4 locols = float4(group1.ab, group3.ab);\n", " float4 hicols = float4(group7.rg, group9.rg);\n", " locols.yz += group2.ab;\n", " hicols.yz += group8.rg;\n", " float4 midcols = float4(group1.rg, group3.rg) + float4(group7.ab, group9.ab) +\n", " float4(group4.rg, group6.rg) + float4(group4.ab, group6.ab) +\n", " lerp(locols, hicols, offset.y);\n", " float4 cols = group5 + float4(group2.rg, group8.ab);\n", " cols.xyz += lerp(midcols.xyz, midcols.yzw, offset.x);\n", " f = dot(cols, float4(1.0/25.0));\n", "# else\n", " float4 group1 = step(shadowmaptc.z, texval(-1.0, -1.0));\n", " float4 group2 = step(shadowmaptc.z, texval( 1.0, -1.0));\n", " float4 group3 = step(shadowmaptc.z, texval(-1.0, 1.0));\n", " float4 group4 = step(shadowmaptc.z, texval( 1.0, 1.0));\n", " float4 cols = float4(group1.rg, group2.rg) + float4(group3.ab, group4.ab) +\n", " lerp(float4(group1.ab, group2.ab), float4(group3.rg, group4.rg), offset.y);\n", " f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n", "# endif\n", "# else\n", "# ifdef GL_EXT_gpu_shader4\n", "# define texval(x, y) tex2DOffset(Texture_ShadowMap2D, center, int2(x, y)).r\n", "# else\n", "# define texval(x, y) texDepth2D(Texture_ShadowMap2D, center + float2(x, y)*ShadowMap_TextureScale).r \n", "# endif\n", "# if USESHADOWMAPPCF > 1\n", " float2 center = shadowmaptc.xy - 0.5, offset = frac(center);\n", " center *= ShadowMap_TextureScale;\n", " float4 row1 = step(shadowmaptc.z, float4(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0), texval( 2.0, -1.0)));\n", " float4 row2 = step(shadowmaptc.z, float4(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0), texval( 2.0, 0.0)));\n", " float4 row3 = step(shadowmaptc.z, float4(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0), texval( 2.0, 1.0)));\n", " float4 row4 = step(shadowmaptc.z, float4(texval(-1.0, 2.0), texval( 0.0, 2.0), texval( 1.0, 2.0), texval( 2.0, 2.0)));\n", " float4 cols = row2 + row3 + lerp(row1, row4, offset.y);\n", " f = dot(lerp(cols.xyz, cols.yzw, offset.x), float3(1.0/9.0));\n", "# else\n", " float2 center = shadowmaptc.xy*ShadowMap_TextureScale, offset = frac(shadowmaptc.xy);\n", " float3 row1 = step(shadowmaptc.z, float3(texval(-1.0, -1.0), texval( 0.0, -1.0), texval( 1.0, -1.0)));\n", " float3 row2 = step(shadowmaptc.z, float3(texval(-1.0, 0.0), texval( 0.0, 0.0), texval( 1.0, 0.0)));\n", " float3 row3 = step(shadowmaptc.z, float3(texval(-1.0, 1.0), texval( 0.0, 1.0), texval( 1.0, 1.0)));\n", " float3 cols = row2 + lerp(row1, row3, offset.y);\n", " f = dot(lerp(cols.xy, cols.yz, offset.x), float2(0.25,0.25));\n", "# endif\n", "# endif\n", "# else\n", " f = step(shadowmaptc.z, tex2D(Texture_ShadowMap2D, shadowmaptc.xy*ShadowMap_TextureScale).r);\n", "# endif\n", "# endif\n", "# ifdef USESHADOWMAPORTHO\n", " return lerp(ShadowMap_Parameters.w, 1.0, f);\n", "# else\n", " return f;\n", "# endif\n", "}\n", "# endif\n", "#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", "#endif // FRAGMENT_SHADER\n", "\n", "\n", "\n", "\n", "#ifdef MODE_DEFERREDGEOMETRY\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "float4 gl_Color : COLOR0,\n", "#endif\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "float4 gl_MultiTexCoord1 : TEXCOORD1,\n", "float4 gl_MultiTexCoord2 : TEXCOORD2,\n", "float4 gl_MultiTexCoord3 : TEXCOORD3,\n", "uniform float4x4 TexMatrix : register(c0),\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform float4x4 BackgroundTexMatrix : register(c4),\n", "#endif\n", "uniform float4x4 ModelViewMatrix : register(c12),\n", "#ifdef USEOFFSETMAPPING\n", "uniform float3 EyePosition : register(c24),\n", "#endif\n", "uniform float ClientTime : register(c2),\n", "out float4 gl_Position : POSITION,\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "out float4 gl_FrontColor : COLOR,\n", "#endif\n", "out float4 TexCoordBoth : TEXCOORD0,\n", "#ifdef USEOFFSETMAPPING\n", "out float3 EyeVector : TEXCOORD2,\n", "#endif\n", "out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", "out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", "out float4 VectorR : TEXCOORD7 // direction of R texcoord (surface normal), Depth value\n", ")\n", "{\n", " TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " gl_FrontColor = gl_Color;\n", " TexCoordBoth.zw = float2(Backgroundmul(TexMatrix, gl_MultiTexCoord0));\n", "#endif\n", "\n", " // transform unnormalized eye direction into tangent space\n", "#ifdef USEOFFSETMAPPING\n", " float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", " EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", "#endif\n", "\n", " VectorS = mul(ModelViewMatrix, float4(gl_MultiTexCoord1.xyz, 0)).xyz;\n", " VectorT = mul(ModelViewMatrix, float4(gl_MultiTexCoord2.xyz, 0)).xyz;\n", " VectorR.xyz = mul(ModelViewMatrix, float4(gl_MultiTexCoord3.xyz, 0)).xyz;\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", " VectorR.w = mul(ModelViewMatrix, gl_Vertex).z;\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "float4 TexCoordBoth : TEXCOORD0,\n", "float3 EyeVector : TEXCOORD2,\n", "float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", "float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", "float4 VectorR : TEXCOORD7, // direction of R texcoord (surface normal), Depth value\n", "uniform sampler Texture_Normal : register(s0),\n", "#ifdef USEALPHAKILL\n", "uniform sampler Texture_Color : register(s1),\n", "#endif\n", "uniform sampler Texture_Gloss : register(s2),\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform sampler Texture_SecondaryNormal : register(s4),\n", "uniform sampler Texture_SecondaryGloss : register(s6),\n", "#endif\n", "#ifdef USEOFFSETMAPPING\n", "uniform float4 OffsetMapping_ScaleSteps : register(c24),\n", "uniform float4 OffsetMapping_LodDistance : register(c53),\n", "uniform float OffsetMapping_Bias : register(c54),\n", "#endif\n", "uniform half SpecularPower : register(c36),\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " float2 TexCoord = TexCoordBoth.xy;\n", "#ifdef USEOFFSETMAPPING\n", " // apply offsetmapping\n", " float2 dPdx = ddx(TexCoord);\n", " float2 dPdy = ddy(TexCoord);\n", " float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, OffsetMapping_Bias, OffsetMapping_LodDistance, EyeVector, Texture_Normal, dPdx, dPdy);\n", "# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n", "#else\n", "# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n", "#endif\n", "\n", "#ifdef USEALPHAKILL\n", " if (offsetMappedTexture2D(Texture_Color).a < 0.5)\n", " discard;\n", "#endif\n", "\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " float alpha = offsetMappedTexture2D(Texture_Color).a;\n", " float terrainblend = clamp(float(gl_FrontColor.a) * alpha * 2.0 - 0.5, float(0.0), float(1.0));\n", " //float terrainblend = min(float(gl_FrontColor.a) * alpha * 2.0, float(1.0));\n", " //float terrainblend = float(gl_FrontColor.a) * alpha > 0.5;\n", "#endif\n", "\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " float3 surfacenormal = lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend) - float3(0.5, 0.5, 0.5);\n", " float a = lerp(tex2D(Texture_SecondaryGloss, TexCoord2).a, offsetMappedTexture2D(Texture_Gloss).a, terrainblend);\n", "#else\n", " float3 surfacenormal = offsetMappedTexture2D(Texture_Normal).rgb - float3(0.5, 0.5, 0.5);\n", " float a = offsetMappedTexture2D(Texture_Gloss).a;\n", "#endif\n", "\n", " float3 pixelnormal = normalize(surfacenormal.x * VectorS.xyz + surfacenormal.y * VectorT.xyz + surfacenormal.z * VectorR.xyz);\n", " dp_FragColor = float4(pixelnormal.x, pixelnormal.y, VectorR.w, a);\n", "}\n", "#endif // FRAGMENT_SHADER\n", "#else // !MODE_DEFERREDGEOMETRY\n", "\n", "\n", "\n", "\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "uniform float4x4 ModelViewMatrix : register(c12),\n", "out float4 gl_Position : POSITION,\n", "out float4 ModelViewPosition : TEXCOORD0\n", ")\n", "{\n", " ModelViewPosition = mul(ModelViewMatrix, gl_Vertex);\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "#ifdef HLSL\n", "float2 Pixel : VPOS,\n", "#else\n", "float2 Pixel : WPOS,\n", "#endif\n", "float4 ModelViewPosition : TEXCOORD0,\n", "uniform float4x4 ViewToLight : register(c44),\n", "uniform float2 ScreenToDepth : register(c33), // ScreenToDepth = float2(Far / (Far - Near), Far * Near / (Near - Far));\n", "uniform float3 LightPosition : register(c23),\n", "uniform half2 PixelToScreenTexCoord : register(c42),\n", "uniform half3 DeferredColor_Ambient : register(c9),\n", "uniform half3 DeferredColor_Diffuse : register(c10),\n", "#ifdef USESPECULAR\n", "uniform half3 DeferredColor_Specular : register(c11),\n", "uniform half SpecularPower : register(c36),\n", "#endif\n", "uniform sampler Texture_Attenuation : register(s9),\n", "uniform sampler Texture_ScreenDepth : register(s13),\n", "uniform sampler Texture_ScreenNormalMap : register(s14),\n", "\n", "#ifdef USECUBEFILTER\n", "uniform samplerCUBE Texture_Cube : register(s10),\n", "#endif\n", "\n", "#ifdef USESHADOWMAP2D\n", "# ifdef USESHADOWSAMPLER\n", "uniform sampler Texture_ShadowMap2D : register(s15),\n", "# else\n", "uniform sampler Texture_ShadowMap2D : register(s15),\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAPVSDCT\n", "uniform samplerCUBE Texture_CubeProjection : register(s12),\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", "uniform float2 ShadowMap_TextureScale : register(c35),\n", "uniform float4 ShadowMap_Parameters : register(c34),\n", "#endif\n", "\n", "out float4 gl_FragData0 : COLOR0,\n", "out float4 gl_FragData1 : COLOR1\n", ")\n", "{\n", " // calculate viewspace pixel position\n", " float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n", " float3 position;\n", " // get the geometry information (depth, normal, specular exponent)\n", " half4 normalmap = half4(tex2D(Texture_ScreenNormalMap, ScreenTexCoord));\n", " // decode viewspace pixel normal\n", "// float3 surfacenormal = normalize(normalmap.rgb - cast_myhalf3(0.5,0.5,0.5));\n", " float3 surfacenormal = half3(normalmap.rg, sqrt(1.0-dot(normalmap.rg, normalmap.rg)));\n", " // decode viewspace pixel position\n", "// position.z = decodedepthmacro(dp_texture2D(Texture_ScreenDepth, ScreenTexCoord));\n", " position.z = normalmap.b;\n", "// position.z = ScreenToDepth.y / (dp_texture2D(Texture_ScreenDepth, ScreenTexCoord).r + ScreenToDepth.x);\n", " position.xy = ModelViewPosition.xy * (position.z / ModelViewPosition.z);\n", "\n", " // now do the actual shading\n", " // surfacenormal = pixel normal in viewspace\n", " // LightVector = pixel to light in viewspace\n", " // CubeVector = position in lightspace\n", " // eyevector = pixel to view in viewspace\n", " float3 CubeVector = mul(ViewToLight, float4(position,1)).xyz;\n", " half fade = half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n", "#ifdef USEDIFFUSE\n", " // calculate diffuse shading\n", " half3 lightnormal = half3(normalize(LightPosition - position));\n", " half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", "#endif\n", "#ifdef USESPECULAR\n", " // calculate directional shading\n", " float3 eyevector = position * -1.0;\n", "# ifdef USEEXACTSPECULARMATH\n", " half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(eyevector)))*-1.0, 0.0)), 1.0 + SpecularPower * normalmap.a));\n", "# else\n", " half3 specularnormal = half3(normalize(lightnormal + half3(normalize(eyevector))));\n", " half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * normalmap.a));\n", "# endif\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", " fade *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n", "#ifdef USESHADOWMAPVSDCT\n", ", Texture_CubeProjection\n", "#endif\n", " ));\n", "#endif\n", "\n", "#ifdef USEDIFFUSE\n", " gl_FragData0 = float4((DeferredColor_Ambient + DeferredColor_Diffuse * diffuse) * fade, 1.0);\n", "#else\n", " gl_FragData0 = float4(DeferredColor_Ambient * fade, 1.0);\n", "#endif\n", "#ifdef USESPECULAR\n", " gl_FragData1 = float4(DeferredColor_Specular * (specular * fade), 1.0);\n", "#else\n", " gl_FragData1 = float4(0.0, 0.0, 0.0, 1.0);\n", "#endif\n", "\n", "# ifdef USECUBEFILTER\n", " float3 cubecolor = texCUBE(Texture_Cube, CubeVector).rgb;\n", " gl_FragData0.rgb *= cubecolor;\n", " gl_FragData1.rgb *= cubecolor;\n", "# endif\n", "}\n", "#endif // FRAGMENT_SHADER\n", "#else // !MODE_DEFERREDLIGHTSOURCE\n", "\n", "\n", "\n", "\n", "#ifdef VERTEX_SHADER\n", "void main\n", "(\n", "float4 gl_Vertex : POSITION,\n", "uniform float4x4 ModelViewProjectionMatrix : register(c8),\n", "#if defined(USEVERTEXTEXTUREBLEND) || defined(MODE_VERTEXCOLOR) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", "float4 gl_Color : COLOR0,\n", "#endif\n", "float4 gl_MultiTexCoord0 : TEXCOORD0,\n", "float4 gl_MultiTexCoord1 : TEXCOORD1,\n", "float4 gl_MultiTexCoord2 : TEXCOORD2,\n", "float4 gl_MultiTexCoord3 : TEXCOORD3,\n", "float4 gl_MultiTexCoord4 : TEXCOORD4,\n", "\n", "uniform float3 EyePosition : register(c24),\n", "uniform float4x4 TexMatrix : register(c0),\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform float4x4 BackgroundTexMatrix : register(c4),\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform float4x4 ModelToLight : register(c20),\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform float3 LightPosition : register(c27),\n", "#endif\n", "#ifdef MODE_LIGHTDIRECTION\n", "uniform float3 LightDir : register(c26),\n", "#endif\n", "uniform float4 FogPlane : register(c25),\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "uniform float3 LightPosition : register(c27),\n", "#endif\n", "#ifdef USESHADOWMAPORTHO\n", "uniform float4x4 ShadowMapMatrix : register(c16),\n", "#endif\n", "uniform float ClientTime : register(c2),\n", "#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", "out float4 gl_FrontColor : COLOR,\n", "#endif\n", "out float4 TexCoordBoth : TEXCOORD0,\n", "#ifdef USELIGHTMAP\n", "out float2 TexCoordLightmap : TEXCOORD1,\n", "#endif\n", "#ifdef USEEYEVECTOR\n", "out float3 EyeVector : TEXCOORD2,\n", "#endif\n", "#ifdef USEREFLECTION\n", "out float4 ModelViewProjectionPosition : TEXCOORD3,\n", "#endif\n", "#ifdef USEFOG\n", "out float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE) || defined(USEDIFFUSE)\n", "out float3 LightVector : TEXCOORD1,\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "out float3 CubeVector : TEXCOORD3,\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n", "out float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", "out float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", "out float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n", "#endif\n", "#ifdef USESHADOWMAPORTHO\n", "out float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n", "#endif\n", "out float4 gl_Position : POSITION\n", ")\n", "{\n", "#if defined(MODE_VERTEXCOLOR) || defined(USEVERTEXTEXTUREBLEND) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR) || defined(USEALPHAGENVERTEX)\n", " gl_FrontColor = gl_Color;\n", "#endif\n", " // copy the surface texcoord\n", " TexCoordBoth = mul(TexMatrix, gl_MultiTexCoord0);\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " TexCoordBoth.zw = mul(BackgroundTexMatrix, gl_MultiTexCoord0).xy;\n", "#endif\n", "#ifdef USELIGHTMAP\n", " TexCoordLightmap = gl_MultiTexCoord4.xy;\n", "#endif\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", " // transform vertex position into light attenuation/cubemap space\n", " // (-1 to +1 across the light box)\n", " CubeVector = mul(ModelToLight, gl_Vertex).xyz;\n", "\n", "# ifdef USEDIFFUSE\n", " // transform unnormalized light direction into tangent space\n", " // (we use unnormalized to ensure that it interpolates correctly and then\n", " // normalize it per pixel)\n", " float3 lightminusvertex = LightPosition - gl_Vertex.xyz;\n", " LightVector = float3(dot(lightminusvertex, gl_MultiTexCoord1.xyz), dot(lightminusvertex, gl_MultiTexCoord2.xyz), dot(lightminusvertex, gl_MultiTexCoord3.xyz));\n", "# endif\n", "#endif\n", "\n", "#if defined(MODE_LIGHTDIRECTION) && defined(USEDIFFUSE)\n", " LightVector = float3(dot(LightDir, gl_MultiTexCoord1.xyz), dot(LightDir, gl_MultiTexCoord2.xyz), dot(LightDir, gl_MultiTexCoord3.xyz));\n", "#endif\n", "\n", " // transform unnormalized eye direction into tangent space\n", "#ifdef USEEYEVECTOR\n", " float3 EyeVectorModelSpace = EyePosition - gl_Vertex.xyz;\n", " EyeVector = float3(dot(EyeVectorModelSpace, gl_MultiTexCoord1.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord2.xyz), dot(EyeVectorModelSpace, gl_MultiTexCoord3.xyz));\n", "#endif\n", "\n", "#ifdef USEFOG\n", " EyeVectorModelSpaceFogPlaneVertexDist.xyz = EyePosition - gl_Vertex.xyz;\n", " EyeVectorModelSpaceFogPlaneVertexDist.w = dot(FogPlane, gl_Vertex);\n", "#endif\n", "\n", "#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " VectorS = gl_MultiTexCoord1.xyz;\n", " VectorT = gl_MultiTexCoord2.xyz;\n", " VectorR = gl_MultiTexCoord3.xyz;\n", "#endif\n", "\n", " // transform vertex to camera space, using ftransform to match non-VS rendering\n", " gl_Position = mul(ModelViewProjectionMatrix, gl_Vertex);\n", "\n", "#ifdef USESHADOWMAPORTHO\n", " ShadowMapTC = mul(ShadowMapMatrix, gl_Position).xyz;\n", "#endif\n", "\n", "#ifdef USEREFLECTION\n", " ModelViewProjectionPosition = gl_Position;\n", "#endif\n", "#ifdef USETRIPPY\n", " gl_Position = TrippyVertex(gl_Position, ClientTime);\n", "#endif\n", "}\n", "#endif // VERTEX_SHADER\n", "\n", "\n", "\n", "\n", "#ifdef FRAGMENT_SHADER\n", "void main\n", "(\n", "#ifdef USEDEFERREDLIGHTMAP\n", "#ifdef HLSL\n", "float2 Pixel : VPOS,\n", "#else\n", "float2 Pixel : WPOS,\n", "#endif\n", "#endif\n", "float4 gl_FrontColor : COLOR,\n", "float4 TexCoordBoth : TEXCOORD0,\n", "#ifdef USELIGHTMAP\n", "float2 TexCoordLightmap : TEXCOORD1,\n", "#endif\n", "#ifdef USEEYEVECTOR\n", "float3 EyeVector : TEXCOORD2,\n", "#endif\n", "#ifdef USEREFLECTION\n", "float4 ModelViewProjectionPosition : TEXCOORD3,\n", "#endif\n", "#ifdef USEFOG\n", "float4 EyeVectorModelSpaceFogPlaneVertexDist : TEXCOORD4,\n", "#endif\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_LIGHTDIRECTION)\n", "float3 LightVector : TEXCOORD1,\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "float3 CubeVector : TEXCOORD3,\n", "#endif\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "float4 ModelViewPosition : TEXCOORD0,\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_DEFERREDGEOMETRY) || defined(USEREFLECTCUBE)\n", "float3 VectorS : TEXCOORD5, // direction of S texcoord (sometimes crudely called tangent)\n", "float3 VectorT : TEXCOORD6, // direction of T texcoord (sometimes crudely called binormal)\n", "float3 VectorR : TEXCOORD7, // direction of R texcoord (surface normal)\n", "#endif\n", "#ifdef USESHADOWMAPORTHO\n", "float3 ShadowMapTC : TEXCOORD3, // CONFLICTS WITH USEREFLECTION!\n", "#endif\n", "\n", "uniform sampler Texture_Normal : register(s0),\n", "uniform sampler Texture_Color : register(s1),\n", "#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", "uniform sampler Texture_Gloss : register(s2),\n", "#endif\n", "#ifdef USEGLOW\n", "uniform sampler Texture_Glow : register(s3),\n", "#endif\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "uniform sampler Texture_SecondaryNormal : register(s4),\n", "uniform sampler Texture_SecondaryColor : register(s5),\n", "#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", "uniform sampler Texture_SecondaryGloss : register(s6),\n", "#endif\n", "#ifdef USEGLOW\n", "uniform sampler Texture_SecondaryGlow : register(s7),\n", "#endif\n", "#endif\n", "#ifdef USECOLORMAPPING\n", "uniform sampler Texture_Pants : register(s4),\n", "uniform sampler Texture_Shirt : register(s7),\n", "#endif\n", "#ifdef USEFOG\n", "uniform sampler Texture_FogHeightTexture : register(s14),\n", "uniform sampler Texture_FogMask : register(s8),\n", "#endif\n", "#ifdef USELIGHTMAP\n", "uniform sampler Texture_Lightmap : register(s9),\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_MODELSPACE) || defined(MODE_LIGHTDIRECTIONMAP_TANGENTSPACE)\n", "uniform sampler Texture_Deluxemap : register(s10),\n", "#endif\n", "#ifdef USEREFLECTION\n", "uniform sampler Texture_Reflection : register(s7),\n", "#endif\n", "\n", "#ifdef MODE_DEFERREDLIGHTSOURCE\n", "uniform sampler Texture_ScreenDepth : register(s13),\n", "uniform sampler Texture_ScreenNormalMap : register(s14),\n", "#endif\n", "#ifdef USEDEFERREDLIGHTMAP\n", "uniform sampler Texture_ScreenDepth : register(s13),\n", "uniform sampler Texture_ScreenNormalMap : register(s14),\n", "uniform sampler Texture_ScreenDiffuse : register(s11),\n", "uniform sampler Texture_ScreenSpecular : register(s12),\n", "#endif\n", "\n", "#ifdef USECOLORMAPPING\n", "uniform half3 Color_Pants : register(c7),\n", "uniform half3 Color_Shirt : register(c8),\n", "#endif\n", "#ifdef USEFOG\n", "uniform float3 FogColor : register(c16),\n", "uniform float FogRangeRecip : register(c20),\n", "uniform float FogPlaneViewDist : register(c19),\n", "uniform float FogHeightFade : register(c17),\n", "#endif\n", "\n", "#ifdef USEOFFSETMAPPING\n", "uniform float4 OffsetMapping_ScaleSteps : register(c24),\n", "uniform float OffsetMapping_Bias : register(c54),\n", "#endif\n", "\n", "#ifdef USEDEFERREDLIGHTMAP\n", "uniform half2 PixelToScreenTexCoord : register(c42),\n", "uniform half3 DeferredMod_Diffuse : register(c12),\n", "uniform half3 DeferredMod_Specular : register(c13),\n", "#endif\n", "uniform half3 Color_Ambient : register(c3),\n", "uniform half3 Color_Diffuse : register(c4),\n", "uniform half3 Color_Specular : register(c5),\n", "uniform half SpecularPower : register(c36),\n", "#ifdef USEGLOW\n", "uniform half3 Color_Glow : register(c6),\n", "#endif\n", "uniform half Alpha : register(c0),\n", "#ifdef USEREFLECTION\n", "uniform float4 DistortScaleRefractReflect : register(c14),\n", "uniform float4 ScreenScaleRefractReflect : register(c32),\n", "uniform float4 ScreenCenterRefractReflect : register(c31),\n", "uniform half4 ReflectColor : register(c26),\n", "#endif\n", "#ifdef USEREFLECTCUBE\n", "uniform float4x4 ModelToReflectCube : register(c48),\n", "uniform sampler Texture_ReflectMask : register(s5),\n", "uniform samplerCUBE Texture_ReflectCube : register(s6),\n", "#endif\n", "#ifdef MODE_LIGHTDIRECTION\n", "uniform half3 LightColor : register(c21),\n", "#endif\n", "#ifdef MODE_LIGHTSOURCE\n", "uniform half3 LightColor : register(c21),\n", "#endif\n", "\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE)\n", "uniform sampler Texture_Attenuation : register(s9),\n", "uniform samplerCUBE Texture_Cube : register(s10),\n", "#endif\n", "\n", "#if defined(MODE_LIGHTSOURCE) || defined(MODE_DEFERREDLIGHTSOURCE) || defined(USESHADOWMAPORTHO)\n", "\n", "#ifdef USESHADOWMAP2D\n", "# ifdef USESHADOWSAMPLER\n", "uniform sampler Texture_ShadowMap2D : register(s15),\n", "# else\n", "uniform sampler Texture_ShadowMap2D : register(s15),\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAPVSDCT\n", "uniform samplerCUBE Texture_CubeProjection : register(s12),\n", "#endif\n", "\n", "#if defined(USESHADOWMAP2D)\n", "uniform float2 ShadowMap_TextureScale : register(c35),\n", "uniform float4 ShadowMap_Parameters : register(c34),\n", "#endif\n", "#endif // !defined(MODE_LIGHTSOURCE) && !defined(MODE_DEFERREDLIGHTSOURCE) && !defined(USESHADOWMAPORTHO)\n", "\n", "out float4 dp_FragColor : COLOR\n", ")\n", "{\n", " float2 TexCoord = TexCoordBoth.xy;\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " float2 TexCoord2 = TexCoordBoth.zw;\n", "#endif\n", "#ifdef USEOFFSETMAPPING\n", " // apply offsetmapping\n", " float2 dPdx = ddx(TexCoord);\n", " float2 dPdy = ddy(TexCoord);\n", " float2 TexCoordOffset = OffsetMapping(TexCoord, OffsetMapping_ScaleSteps, OffsetMapping_Bias, OffsetMapping_LodDistance, EyeVector, Texture_Normal, dPdx, dPdy);\n", "# define offsetMappedTexture2D(t) tex2Dgrad(t, TexCoordOffset, dPdx, dPdy)\n", "#else\n", "# define offsetMappedTexture2D(t) tex2D(t, TexCoord)\n", "#endif\n", "\n", " // combine the diffuse textures (base, pants, shirt)\n", " half4 color = half4(offsetMappedTexture2D(Texture_Color));\n", "#ifdef USEALPHAKILL\n", " if (color.a < 0.5)\n", " discard;\n", "#endif\n", " color.a *= Alpha;\n", "#ifdef USECOLORMAPPING\n", " color.rgb += half3(offsetMappedTexture2D(Texture_Pants).rgb) * Color_Pants + half3(offsetMappedTexture2D(Texture_Shirt).rgb) * Color_Shirt;\n", "#endif\n", "#ifdef USEVERTEXTEXTUREBLEND\n", "#ifdef USEBOTHALPHAS\n", " half4 color2 = half4(tex2D(Texture_SecondaryColor, TexCoord2));\n", " half terrainblend = clamp(half(gl_FrontColor.a) * color.a, half(1.0 - color2.a), half(1.0));\n", " color.rgb = lerp(color2.rgb, color.rgb, terrainblend);\n", "#else\n", " half terrainblend = clamp(half(gl_FrontColor.a) * color.a * 2.0 - 0.5, half(0.0), half(1.0));\n", " //half terrainblend = min(half(gl_FrontColor.a) * color.a * 2.0, half(1.0));\n", " //half terrainblend = half(gl_FrontColor.a) * color.a > 0.5;\n", " color.rgb = half3(lerp(tex2D(Texture_SecondaryColor, TexCoord2).rgb, float3(color.rgb), terrainblend));\n", " color.a = 1.0;\n", " //color = half4(lerp(float4(1, 0, 0, 1), color, terrainblend));\n", "#endif\n", "#endif\n", "#ifdef USEALPHAGENVERTEX\n", " color.a *= gl_FrontColor.a;\n", "#endif\n", "\n", " // get the surface normal\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " half3 surfacenormal = normalize(half3(lerp(tex2D(Texture_SecondaryNormal, TexCoord2).rgb, offsetMappedTexture2D(Texture_Normal).rgb, terrainblend)) - half3(0.5, 0.5, 0.5));\n", "#else\n", " half3 surfacenormal = half3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5, 0.5, 0.5)));\n", "#endif\n", "\n", " // get the material colors\n", " half3 diffusetex = color.rgb;\n", "#if defined(USESPECULAR) || defined(USEDEFERREDLIGHTMAP)\n", "# ifdef USEVERTEXTEXTUREBLEND\n", " half4 glosstex = half4(lerp(tex2D(Texture_SecondaryGloss, TexCoord2), offsetMappedTexture2D(Texture_Gloss), terrainblend));\n", "# else\n", " half4 glosstex = half4(offsetMappedTexture2D(Texture_Gloss));\n", "# endif\n", "#endif\n", "\n", "#ifdef USEREFLECTCUBE\n", " float3 TangentReflectVector = reflect(-EyeVector, surfacenormal);\n", " float3 ModelReflectVector = TangentReflectVector.x * VectorS + TangentReflectVector.y * VectorT + TangentReflectVector.z * VectorR;\n", " float3 ReflectCubeTexCoord = mul(ModelToReflectCube, float4(ModelReflectVector, 0)).xyz;\n", " diffusetex += half3(offsetMappedTexture2D(Texture_ReflectMask).rgb) * half3(texCUBE(Texture_ReflectCube, ReflectCubeTexCoord).rgb);\n", "#endif\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTSOURCE\n", " // light source\n", "#ifdef USEDIFFUSE\n", " half3 lightnormal = half3(normalize(LightVector));\n", " half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", " color.rgb = diffusetex * (Color_Ambient + diffuse * Color_Diffuse);\n", "#ifdef USESPECULAR\n", "#ifdef USEEXACTSPECULARMATH\n", " half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), 1.0 + SpecularPower * glosstex.a));\n", "#else\n", " half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n", " half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * glosstex.a));\n", "#endif\n", " color.rgb += glosstex.rgb * (specular * Color_Specular);\n", "#endif\n", "#else\n", " color.rgb = diffusetex * Color_Ambient;\n", "#endif\n", " color.rgb *= LightColor;\n", " color.rgb *= half(tex2D(Texture_Attenuation, float2(length(CubeVector), 0.0)).r);\n", "#if defined(USESHADOWMAP2D)\n", " color.rgb *= half(ShadowMapCompare(CubeVector, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale\n", "#ifdef USESHADOWMAPVSDCT\n", ", Texture_CubeProjection\n", "#endif\n", " ));\n", "\n", "#endif\n", "# ifdef USECUBEFILTER\n", " color.rgb *= half3(texCUBE(Texture_Cube, CubeVector).rgb);\n", "# endif\n", "\n", "#ifdef USESHADOWMAP2D\n", "#ifdef USESHADOWMAPVSDCT\n", "// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters, Texture_CubeProjection);\n", "#else\n", "// float3 shadowmaptc = GetShadowMapTC2D(CubeVector, ShadowMap_Parameters);\n", "#endif\n", "// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n", "// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n", "// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n", "// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", "// color.rgb = half3(tex2D(Texture_ShadowMap2D, float2(0.1,0.1)).rgb);\n", "// color.rgb = half3(tex2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale).rgb);\n", "// color.rgb = half3(shadowmaptc.xyz * float3(ShadowMap_TextureScale,1.0));\n", "// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", "// color.r = half(shadowmaptc.z - texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", "// color.r = half(shadowmaptc.z);\n", "// color.r = half(texDepth2D(Texture_ShadowMap2D, shadowmaptc.xy * ShadowMap_TextureScale));\n", "// color.r = half(shadowmaptc.z);\n", "// color.r = 1;\n", "// color.rgb = abs(CubeVector);\n", "#endif\n", "// color.rgb = half3(1,1,1);\n", "#endif // MODE_LIGHTSOURCE\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTDIRECTION\n", " #define SHADING\n", " #ifdef USEDIFFUSE\n", " half3 lightnormal = half3(normalize(LightVector));\n", " #endif\n", " #define lightcolor LightColor\n", "#endif // MODE_LIGHTDIRECTION\n", "#ifdef MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", " #define SHADING\n", " // deluxemap lightmapping using light vectors in modelspace (q3map2 -light -deluxe)\n", " half3 lightnormal_modelspace = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n", " half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", " // convert modelspace light vector to tangentspace\n", " half3 lightnormal = half3(dot(lightnormal_modelspace, half3(VectorS)), dot(lightnormal_modelspace, half3(VectorT)), dot(lightnormal_modelspace, half3(VectorR)));\n", " // calculate directional shading (and undoing the existing angle attenuation on the lightmap by the division)\n", " // note that q3map2 is too stupid to calculate proper surface normals when q3map_nonplanar\n", " // is used (the lightmap and deluxemap coords correspond to virtually random coordinates\n", " // on that luxel, and NOT to its center, because recursive triangle subdivision is used\n", " // to map the luxels to coordinates on the draw surfaces), which also causes\n", " // deluxemaps to be wrong because light contributions from the wrong side of the surface\n", " // are added up. To prevent divisions by zero or strong exaggerations, a max()\n", " // nudge is done here at expense of some additional fps. This is ONLY needed for\n", " // deluxemaps, tangentspace deluxemap avoid this problem by design.\n", " lightcolor *= 1.0 / max(0.25, lightnormal.z);\n", "#endif // MODE_LIGHTDIRECTIONMAP_MODELSPACE\n", "#ifdef MODE_LIGHTDIRECTIONMAP_TANGENTSPACE\n", " #define SHADING\n", " // deluxemap lightmapping using light vectors in tangentspace (hmap2 -light)\n", " half3 lightnormal = half3(tex2D(Texture_Deluxemap, TexCoordLightmap).rgb) * 2.0 + half3(-1.0, -1.0, -1.0);\n", " half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", "#endif\n", "#if defined(MODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP) || defined(MODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR)\n", " #define SHADING\n", " // forced deluxemap on lightmapped/vertexlit surfaces\n", " half3 lightnormal = half3(0.0, 0.0, 1.0);\n", " #ifdef USELIGHTMAP\n", " half3 lightcolor = half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb);\n", " #else\n", " half3 lightcolor = half3(gl_FrontColor.rgb);\n", " #endif\n", "#endif\n", "#ifdef MODE_FAKELIGHT\n", " #define SHADING\n", " half3 lightnormal = half3(normalize(EyeVector));\n", " half3 lightcolor = half3(1.0,1.0,1.0);\n", "#endif // MODE_FAKELIGHT\n", "\n", "\n", "\n", "\n", "#ifdef MODE_LIGHTMAP\n", " color.rgb = diffusetex * (Color_Ambient + half3(tex2D(Texture_Lightmap, TexCoordLightmap).rgb) * Color_Diffuse);\n", "#endif // MODE_LIGHTMAP\n", "#ifdef MODE_VERTEXCOLOR\n", " color.rgb = diffusetex * (Color_Ambient + half3(gl_FrontColor.rgb) * Color_Diffuse);\n", "#endif // MODE_VERTEXCOLOR\n", "#ifdef MODE_FLATCOLOR\n", " color.rgb = diffusetex * Color_Ambient;\n", "#endif // MODE_FLATCOLOR\n", "\n", "\n", "\n", "\n", "#ifdef SHADING\n", "# ifdef USEDIFFUSE\n", " half diffuse = half(max(float(dot(surfacenormal, lightnormal)), 0.0));\n", "# ifdef USESPECULAR\n", "# ifdef USEEXACTSPECULARMATH\n", " half specular = half(pow(half(max(float(dot(reflect(lightnormal, surfacenormal), normalize(EyeVector)))*-1.0, 0.0)), 1.0 + SpecularPower * glosstex.a));\n", "# else\n", " half3 specularnormal = half3(normalize(lightnormal + half3(normalize(EyeVector))));\n", " half specular = half(pow(half(max(float(dot(surfacenormal, specularnormal)), 0.0)), 1.0 + SpecularPower * glosstex.a));\n", "# endif\n", " color.rgb = diffusetex * Color_Ambient + (diffusetex * Color_Diffuse * diffuse + glosstex.rgb * Color_Specular * specular) * lightcolor;\n", "# else\n", " color.rgb = diffusetex * (Color_Ambient + Color_Diffuse * diffuse * lightcolor);\n", "# endif\n", "# else\n", " color.rgb = diffusetex * Color_Ambient;\n", "# endif\n", "#endif\n", "\n", "#ifdef USESHADOWMAPORTHO\n", " color.rgb *= half(ShadowMapCompare(ShadowMapTC, Texture_ShadowMap2D, ShadowMap_Parameters, ShadowMap_TextureScale));\n", "#endif\n", "\n", "#ifdef USEDEFERREDLIGHTMAP\n", " float2 ScreenTexCoord = Pixel * PixelToScreenTexCoord;\n", " color.rgb += diffusetex * half3(tex2D(Texture_ScreenDiffuse, ScreenTexCoord).rgb) * DeferredMod_Diffuse;\n", " color.rgb += glosstex.rgb * half3(tex2D(Texture_ScreenSpecular, ScreenTexCoord).rgb) * DeferredMod_Specular;\n", "// color.rgb = half3(tex2D(Texture_ScreenDepth, ScreenTexCoord).rgb);\n", "// color.r = half(texDepth2D(Texture_ScreenDepth, ScreenTexCoord)) * 1.0;\n", "#endif\n", "\n", "#ifdef USEGLOW\n", "#ifdef USEVERTEXTEXTUREBLEND\n", " color.rgb += half3(lerp(tex2D(Texture_SecondaryGlow, TexCoord2).rgb, offsetMappedTexture2D(Texture_Glow).rgb, terrainblend)) * Color_Glow;\n", "#else\n", " color.rgb += half3(offsetMappedTexture2D(Texture_Glow).rgb) * Color_Glow;\n", "#endif\n", "#endif\n", "\n", "#ifdef USEFOG\n", " color.rgb = half3(FogVertex(color, FogColor, EyeVectorModelSpaceFogPlaneVertexDist.xyz, EyeVectorModelSpaceFogPlaneVertexDist.w, FogRangeRecip, FogPlaneViewDist, FogHeightFade, Texture_FogMask, Texture_FogHeightTexture));\n", "#endif\n", "\n", " // reflection must come last because it already contains exactly the correct fog (the reflection render preserves camera distance from the plane, it only flips the side) and ContrastBoost/SceneBrightness\n", "#ifdef USEREFLECTION\n", " float4 ScreenScaleRefractReflectIW = ScreenScaleRefractReflect * (1.0 / ModelViewProjectionPosition.w);\n", " //float4 ScreenTexCoord = (ModelViewProjectionPosition.xyxy + normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5)).xyxy * DistortScaleRefractReflect * 100) * ScreenScaleRefractReflectIW + ScreenCenterRefractReflect;\n", " float2 SafeScreenTexCoord = ModelViewProjectionPosition.xy * ScreenScaleRefractReflectIW.zw + ScreenCenterRefractReflect.zw;\n", " float2 ScreenTexCoord = SafeScreenTexCoord + float3(normalize(half3(offsetMappedTexture2D(Texture_Normal).rgb) - half3(0.5,0.5,0.5))).xy * DistortScaleRefractReflect.zw;\n", " // FIXME temporary hack to detect the case that the reflection\n", " // gets blackened at edges due to leaving the area that contains actual\n", " // content.\n", " // Remove this 'ack once we have a better way to stop this thing from\n", " // 'appening.\n", " float f = min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(0.01, -0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, 0.01)).rgb) / 0.05);\n", " f *= min(1.0, length(tex2D(Texture_Reflection, ScreenTexCoord + float2(-0.01, -0.01)).rgb) / 0.05);\n", " ScreenTexCoord = lerp(SafeScreenTexCoord, ScreenTexCoord, f);\n", " color.rgb = lerp(color.rgb, half3(tex2D(Texture_Reflection, ScreenTexCoord).rgb) * ReflectColor.rgb, ReflectColor.a);\n", "#endif\n", "\n", " dp_FragColor = float4(color);\n", "}\n", "#endif // FRAGMENT_SHADER\n", "\n", "#endif // !MODE_DEFERREDLIGHTSOURCE\n", "#endif // !MODE_DEFERREDGEOMETRY\n", "#endif // !MODE_WATER\n", "#endif // !MODE_REFRACTION\n", "#endif // !MODE_BLOOMBLUR\n", "#endif // !MODE_GENERIC\n", "#endif // !MODE_POSTPROCESS\n", "#endif // !MODE_DEPTH_OR_SHADOW\n", darkplaces/darkplaces-sdl.dev0000775000175000017500000006446613067716220015577 0ustar kalevkalev[Project] FileName=darkplaces-sdl.dev Name=DarkPlaces UnitCount=183 Type=0 Ver=1 ObjFiles= Includes=C:\Dev-Cpp\SDL\include;C:\Dev-Cpp\SDL\include\SDL Libs=C:\Dev-Cpp\SDL\lib PrivateResource=darkplaces_private.rc ResourceIncludes= MakeIncludes= Compiler=-Wall -O2 -fno-strict-aliasing -ffast-math -funroll-loops -D_GNU_SOURCE=1 -Dmain=SDL_main -D_FILE_OFFSET_BITS=64 -D__KERNEL_STRICT_NAMES_@@__@@_ CppCompiler= Linker=-lwinmm -lws2_32 -luser32 -lgdi32 -lcomctl32 -Wl,--large-address-aware -lmingw32 -lSDLmain -lSDL -mwindows_@@__@@_ IsCpp=0 Icon= ExeOutput= ObjectOutput= OverrideOutput=1 OverrideOutputName=darkplaces-sdl.exe HostApplication= Folders="Header Files","Source Files" CommandLine= UseCustomMakefile=0 CustomMakefile= IncludeVersionInfo=1 SupportXPThemes=0 CompilerSet=0 CompilerSettings=0000000001100000000100 [Unit1] FileName=dpvsimpledecode.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit2] FileName=cdaudio.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit3] FileName=cl_collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit4] FileName=cl_screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit5] FileName=cl_video.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit6] FileName=client.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit7] FileName=clprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit8] FileName=cmd.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit9] FileName=collision.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit10] FileName=common.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit11] FileName=conproc.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit12] FileName=console.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit13] FileName=snd_main.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit14] FileName=curves.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit15] FileName=cvar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit16] FileName=bspfile.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit17] FileName=draw.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit18] FileName=fs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit19] FileName=gl_backend.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit20] FileName=polygon.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit21] FileName=glquake.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit22] FileName=image.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit23] FileName=input.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit24] FileName=jpeg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit25] FileName=keys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit26] FileName=lhnet.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit27] FileName=mathlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit28] FileName=matrixlib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit29] FileName=menu.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit30] FileName=meshqueue.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit31] FileName=model_alias.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit32] FileName=model_brush.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit33] FileName=model_shared.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit34] FileName=model_sprite.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit35] FileName=model_zymotic.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit36] FileName=modelgen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit37] FileName=mprogdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit38] FileName=netconn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit39] FileName=palette.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit40] FileName=portals.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit41] FileName=pr_comp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit42] FileName=progdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit43] FileName=progs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit44] FileName=progsvm.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit45] FileName=protocol.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit46] FileName=prvm_execprogram.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit47] FileName=qtypes.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit48] FileName=quakedef.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit49] FileName=r_lerpanim.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit50] FileName=r_modules.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit51] FileName=r_shadow.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit52] FileName=r_textures.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit53] FileName=render.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit54] FileName=resource.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit55] FileName=sbar.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit56] FileName=screen.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit57] FileName=server.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit58] FileName=snd_ogg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit59] FileName=snd_wav.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit60] FileName=sound.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit61] FileName=spritegn.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit62] FileName=sys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit63] FileName=vid.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit64] FileName=wad.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit65] FileName=world.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit66] FileName=zone.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit67] FileName=zone.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit68] FileName=cd_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit70] FileName=cl_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit71] FileName=cl_input.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit72] FileName=cl_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit73] FileName=cl_parse.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit74] FileName=cl_particles.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit75] FileName=cl_screen.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit76] FileName=cl_video.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit77] FileName=cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit78] FileName=collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit79] FileName=common.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit80] FileName=conproc.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit81] FileName=console.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit82] FileName=polygon.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit83] FileName=curves.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit84] FileName=cvar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit85] FileName=dpvsimpledecode.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit86] FileName=filematch.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit87] FileName=fractalnoise.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit88] FileName=fs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit89] FileName=gl_backend.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit90] FileName=gl_draw.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit91] FileName=gl_rmain.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit92] FileName=gl_rsurf.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit93] FileName=gl_textures.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit94] FileName=host.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit95] FileName=host_cmd.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit96] FileName=image.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit97] FileName=jpeg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit98] FileName=keys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit99] FileName=lhnet.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit100] FileName=mathlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit101] FileName=matrixlib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit102] FileName=menu.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit103] FileName=meshqueue.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit104] FileName=model_alias.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit105] FileName=model_brush.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit106] FileName=model_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit107] FileName=model_sprite.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit108] FileName=netconn.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit109] FileName=palette.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit110] FileName=portals.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit111] FileName=protocol.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit112] FileName=prvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit113] FileName=prvm_edict.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit114] FileName=prvm_exec.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit115] FileName=builddate.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit116] FileName=r_explosion.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit118] FileName=r_lightning.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit119] FileName=r_modules.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit120] FileName=r_shadow.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit121] FileName=r_sky.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit123] FileName=sbar.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit124] FileName=snd_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit125] FileName=snd_mem.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit126] FileName=snd_mix.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit127] FileName=snd_ogg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit128] FileName=snd_wav.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit130] FileName=sv_move.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit131] FileName=sv_phys.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit132] FileName=sv_user.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit133] FileName=sys_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit136] FileName=wad.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit137] FileName=world.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit138] FileName=svvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit139] FileName=mvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit140] FileName=prvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit141] FileName=csprogs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit142] FileName=image_png.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit143] FileName=lhfont.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit145] FileName=model_dpmodel.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit146] FileName=model_psk.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit147] FileName=csprogs.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit148] FileName=image_png.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit149] FileName=mdfour.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit150] FileName=libcurl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit151] FileName=libcurl.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit152] FileName=clvm_cmds.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit153] FileName=svbsp.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit154] FileName=svbsp.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit155] FileName=sv_demo.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit156] FileName=sv_demo.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit157] FileName=snd_modplug.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit117] FileName=r_lerpanim.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [VersionInfo] Major=1 Minor=0 Release=0 Build=0 LanguageID=1033 CharsetID=1252 CompanyName=Forest Hale Digital Services FileVersion=1.0 FileDescription=DarkPlaces Game Engine InternalName=darkplaces.exe LegalCopyright=id Software, Forest Hale, and contributors LegalTrademarks= OriginalFilename=darkplaces.exe ProductName=DarkPlaces ProductVersion=1.0 AutoIncBuildNr=0 [Unit122] FileName=r_sprites.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit144] FileName=mdfour.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit158] FileName=snd_modplug.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit159] FileName=cl_gecko.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit160] FileName=cl_gecko.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit161] FileName=cl_dyntexture.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit162] FileName=cl_dyntexture.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit163] FileName=sys_sdl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit164] FileName=vid_sdl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit165] FileName=cd_sdl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit166] FileName=snd_sdl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit69] FileName=cl_collision.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit129] FileName=sv_main.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit134] FileName=vid_shared.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit135] FileName=view.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit167] FileName=cap_ogg.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit168] FileName=cap_ogg.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit169] FileName=cap_avi.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit170] FileName=cap_avi.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit175] FileName=hmac.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit179] FileName=ft2_fontdefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit181] FileName=utf8lib.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit182] FileName=bih.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit185] FileName=timing.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit186] FileName=vid_agl_mackeys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit187] FileName=vid_wgl.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit188] FileName=clvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit189] FileName=darkplaces_private.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit190] FileName=SDLMain.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit191] FileName=snd_3dras.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit192] FileName=snd_3dras_typedefs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit193] FileName=timing.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit194] FileName=vid_agl_mackeys.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit174] FileName=hmac.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit178] FileName=ft2.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit180] FileName=ft2.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit177] FileName=ft2_defs.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit172] FileName=SDLMain.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit173] FileName=timing.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit171] FileName=clvm_cmds.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit176] FileName=utf8lib.h CompileCpp=0 Folder=Header Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= [Unit183] FileName=bih.c CompileCpp=0 Folder=Source Files Compile=1 Link=1 Priority=1000 OverrideBuildCmd=0 BuildCmd= darkplaces/fogeval.pl0000664000175000017500000000634513067716220014153 0ustar kalevkalevuse strict; use warnings; # generates the blendfunc flags function in gl_rmain.c my %blendfuncs = ( GL_ONE => sub { (1, 1); }, GL_ZERO => sub { (0, 0); }, GL_SRC_COLOR => sub { ($_[0], $_[1]); }, GL_ONE_MINUS_SRC_COLOR => sub { (1-$_[0], 1-$_[1]); }, GL_SRC_ALPHA => sub { ($_[1], $_[1]); }, GL_ONE_MINUS_SRC_ALPHA => sub { (1-$_[1], 1-$_[1]); }, GL_DST_COLOR => sub { ($_[2], $_[3]); }, GL_ONE_MINUS_DST_COLOR => sub { (1-$_[2], 1-$_[3]); }, GL_DST_ALPHA => sub { ($_[3], $_[3]); }, GL_ONE_MINUS_DST_ALPHA => sub { (1-$_[3], 1-$_[3]); }, ); sub evalblend($$$$$$) { my ($fs, $fd, $s, $sa, $d, $da) = @_; my @fs = $fs->($s, $sa, $d, $da); my @fd = $fd->($s, $sa, $d, $da); return ( $fs[0] * $s + $fd[0] * $d, $fs[1] * $sa + $fd[1] * $da ); } sub isinvariant($$$$) { my ($fs, $fd, $s, $sa) = @_; my ($d, $da) = (rand, rand); my ($out, $outa) = evalblend $fs, $fd, $s, $sa, $d, $da; return abs($out - $d) < 0.0001 && abs($outa - $da) < 0.0001; } sub isfogfriendly($$$$$) { my ($fs, $fd, $s, $sa, $foghack) = @_; my ($d, $da) = (rand, rand); my $fogamount = rand; my $fogcolor = rand; # compare: # 1. blend(fog(s), sa, fog(d), da) # 2. fog(blend(s, sa, d, da)) my ($out1, $out1a) = evalblend $fs, $fd, $s + ((defined $foghack ? $foghack eq 'ALPHA' ? $fogcolor*$sa : $foghack : $fogcolor) - $s) * $fogamount, $sa, $d + ($fogcolor - $d) * $fogamount, $da; my ($out2, $out2a) = evalblend $fs, $fd, $s, $sa, $d, $da; $out2 = $out2 + ($fogcolor - $out2) * $fogamount; return abs($out1 - $out2) < 0.0001 && abs($out1a - $out2a) < 0.0001; } use Carp; sub decide(&) { my ($sub) = @_; my $good = 0; my $bad = 0; for(;;) { for(1..200) { my $r = $sub->(); ++$good if $r; ++$bad if not $r; } #print STDERR "decide: $good vs $bad\n"; return 1 if $good > $bad + 150; return 0 if $bad > $good + 150; warn "No clear decision, continuing to test ($good : $bad)"; } } #die isfogfriendly $blendfuncs{GL_ONE}, $blendfuncs{GL_ONE}, 1, 0, 0; # out1 = 0 + fog($d) # out2 = fog(1 + $d) sub willitblend($$) { my ($fs, $fd) = @_; for my $s(0, 0.25, 0.5, 0.75, 1) { for my $sa(0, 0.25, 0.5, 0.75, 1) { if(decide { isinvariant($fs, $fd, $s, $sa); }) { if(!decide { isinvariant($fs, $fd, 0, $sa); }) { return 0; # no colormod possible } } } } return 1; } sub willitfog($$) { my ($fs, $fd) = @_; FOGHACK: for my $foghack(undef, 0, 'ALPHA') { for my $s(0, 0.25, 0.5, 0.75, 1) { for my $sa(0, 0.25, 0.5, 0.75, 1) { if(!decide { isfogfriendly($fs, $fd, $s, $sa, $foghack); }) { next FOGHACK; } } } return (1, $foghack); } return (0, undef); } print "\tr |= BLENDFUNC_ALLOWS_COLORMOD;\n"; for my $s(sort keys %blendfuncs) { for my $d(sort keys %blendfuncs) { #print STDERR "$s $d\n"; if(!willitblend $blendfuncs{$s}, $blendfuncs{$d}) { print "\tif(src == $s && dst == $d) r &= ~BLENDFUNC_ALLOWS_COLORMOD;\n"; } my ($result, $h) = willitfog $blendfuncs{$s}, $blendfuncs{$d}; if($result) { if(defined $h) { print "\tif(src == $s && dst == $d) r |= BLENDFUNC_ALLOWS_FOG_HACK$h;\n"; } else { print "\tif(src == $s && dst == $d) r |= BLENDFUNC_ALLOWS_FOG;\n"; } } } } darkplaces/snd_3dras_typedefs.h0000664000175000017500000000674113067716222016131 0ustar kalevkalev// This file defines a few "basis measurement" types that extern program will need. #ifndef Typedefs_h #define Typedefs_h #include ///To address a location in a file. typedef unsigned int FilePosition; ///This will express an Amount of something. typedef uint64_t Amount; /// Index expresses an address in a certain array of data. typedef uint64_t Index; /// A signed index, to access things that can be access before 0 typedef int64_t SignedIndex; ///The depth at witch we are tracing. typedef unsigned int TraceDepth; ///The type of a Location (as in a messurement ... from 0) typedef int64_t Location; ///The type of a Location on a texture typedef float TextureLocation; ///The type of a Distance typedef float Distance; ///The type of a Scalar type for use in: Normal3D, Dot product, Direction3D, ... typedef float Scalar; ///Howmuch of something ? typedef float Ratio; ///The type of a an EulerAngle for use in: EulerAngle2D, EulerAngle3D, ... typedef float EulerAngle; ///The type that detemens the size of 1 Location. Expressed in meters/Location. typedef float Scale; ///The frequency of something. typedef float Frequency; ///The wavelength of a frequency typedef Distance WaveLength; /// Howmany samples we take per secod typedef float SampleRate; /// The type in witch we will express a SoundSample. typedef float Sample; /// The type that express the speed of sound (meter/second). typedef float SoundSpeed; /// The type that that express 1 Time. As in a small step. Note in the feature this will be a class. To make it ring. typedef unsigned int Time; typedef float Duration; //typedef StrongType Time; // Example of a strong typecheck /// The amplitude of the SoundPower. This for export to an AudioOutputDevice. typedef float SoundVolume; /// How mutch power per square meter is received per meter (Watt/Meter^2) typedef float SoundIntensity; /// An expression of the power of sound source (Watt) typedef float SoundPower; // W, The power of the sound source typedef float LightIntensity; typedef float LightPower; typedef float Brightness; typedef float Gamma; typedef float Color; typedef float RefractionIndex; typedef unsigned int Resolution; #endif darkplaces/mod_skeletal_animatevertices_sse.h0000664000175000017500000000061513067716220021116 0ustar kalevkalev#ifndef MOD_SKELTAL_ANIMATEVERTICES_SSE_H #define MOD_SKELTAL_ANIMATEVERTICES_SSE_H #include "quakedef.h" #ifdef SSE_POSSIBLE void Mod_Skeletal_AnimateVertices_SSE(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f); #endif #endif darkplaces/builddate.c0000664000175000017500000000045213067716216014272 0ustar kalevkalev#define STRINGIFY2(arg) #arg #define STRINGIFY(arg) STRINGIFY2(arg) extern const char *buildstring; const char *buildstring = #ifndef NO_BUILD_TIMESTAMPS __TIME__ " " __DATE__ " " #endif #ifdef SVNREVISION STRINGIFY(SVNREVISION) #else "-" #endif #ifdef BUILDTYPE " " STRINGIFY(BUILDTYPE) #endif ; darkplaces/wad.h0000664000175000017500000000366013067716222013116 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // wad.h #ifndef WAD_H #define WAD_H //=============== // TYPES //=============== #define CMP_NONE 0 #define CMP_LZSS 1 #define TYP_NONE 0 #define TYP_LABEL 1 #define TYP_LUMPY 64 // 64 + grab command number #define TYP_PALETTE 64 #define TYP_QTEX 65 #define TYP_QPIC 66 #define TYP_SOUND 67 #define TYP_MIPTEX 68 typedef struct qpic_s { int width, height; unsigned char data[4]; // variably sized } qpic_t; typedef struct wadinfo_s { char identification[4]; // should be WAD2 or 2DAW int numlumps; int infotableofs; } wadinfo_t; typedef struct lumpinfo_s { int filepos; int disksize; int size; // uncompressed char type; char compression; char pad1, pad2; char name[16]; // must be null terminated } lumpinfo_t; void W_UnloadAll(void); unsigned char *W_GetLumpName(const char *name); // halflife texture wads void W_LoadTextureWadFile(char *filename, int complain); unsigned char *W_GetTextureBGRA(char *name); // returns tempmempool allocated image data, width and height are in image_width and image_height unsigned char *W_ConvertWAD3TextureBGRA(sizebuf_t *sb); // returns tempmempool allocated image data, width and height are in image_width and image_height #endif darkplaces/protocol.c0000664000175000017500000033353213067716222014203 0ustar kalevkalev#include "quakedef.h" #define ENTITYSIZEPROFILING_START(msg, num, flags) \ int entityprofiling_startsize = msg->cursize #define ENTITYSIZEPROFILING_END(msg, num, flags) \ if(developer_networkentities.integer >= 2) \ { \ prvm_edict_t *edict = prog->edicts + num; \ Con_Printf("sent entity update of size %u for %d classname %s flags %d\n", (msg->cursize - entityprofiling_startsize), num, PRVM_serveredictstring(edict, classname) ? PRVM_GetString(prog, PRVM_serveredictstring(edict, classname)) : "(no classname)", flags); \ } // CSQC entity scope values. Bitflags! #define SCOPE_WANTREMOVE 1 // Set if a remove has been scheduled. Never set together with WANTUPDATE. #define SCOPE_WANTUPDATE 2 // Set if an update has been scheduled. #define SCOPE_WANTSEND (SCOPE_WANTREMOVE | SCOPE_WANTUPDATE) #define SCOPE_EXISTED_ONCE 4 // Set if the entity once existed. All these get resent on a full loss. #define SCOPE_ASSUMED_EXISTING 8 // Set if the entity is currently assumed existing and therefore needs removes. // this is 88 bytes (must match entity_state_t in protocol.h) entity_state_t defaultstate = { // ! means this is not sent to client 0,//double time; // ! time this state was built (used on client for interpolation) {0,0,0},//float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) {0,0,0},//float origin[3]; {0,0,0},//float angles[3]; 0,//int effects; 0,//unsigned int customizeentityforclient; // ! 0,//unsigned short number; // entity number this state is for 0,//unsigned short modelindex; 0,//unsigned short frame; 0,//unsigned short tagentity; 0,//unsigned short specialvisibilityradius; // ! larger if it has effects/light 0,//unsigned short viewmodelforclient; // ! 0,//unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases 0,//unsigned short nodrawtoclient; // ! 0,//unsigned short drawonlytoclient; // ! 0,//unsigned short traileffectnum; {0,0,0,0},//unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 ACTIVE_NOT,//unsigned char active; // true if a valid state 0,//unsigned char lightstyle; 0,//unsigned char lightpflags; 0,//unsigned char colormap; 0,//unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC 255,//unsigned char alpha; 16,//unsigned char scale; 0,//unsigned char glowsize; 254,//unsigned char glowcolor; 0,//unsigned char flags; 0,//unsigned char internaleffects; // INTEF_FLAG1QW and so on 0,//unsigned char tagindex; {32, 32, 32},//unsigned char colormod[3]; {32, 32, 32},//unsigned char glowmod[3]; }; // LordHavoc: I own protocol ranges 96, 97, 3500-3599 struct protocolversioninfo_s { int number; protocolversion_t version; const char *name; } protocolversioninfo[] = { { 3504, PROTOCOL_DARKPLACES7 , "DP7"}, { 3503, PROTOCOL_DARKPLACES6 , "DP6"}, { 3502, PROTOCOL_DARKPLACES5 , "DP5"}, { 3501, PROTOCOL_DARKPLACES4 , "DP4"}, { 3500, PROTOCOL_DARKPLACES3 , "DP3"}, { 97, PROTOCOL_DARKPLACES2 , "DP2"}, { 96, PROTOCOL_DARKPLACES1 , "DP1"}, { 15, PROTOCOL_QUAKEDP , "QUAKEDP"}, { 15, PROTOCOL_QUAKE , "QUAKE"}, { 28, PROTOCOL_QUAKEWORLD , "QW"}, { 250, PROTOCOL_NEHAHRAMOVIE, "NEHAHRAMOVIE"}, {10000, PROTOCOL_NEHAHRABJP , "NEHAHRABJP"}, {10001, PROTOCOL_NEHAHRABJP2 , "NEHAHRABJP2"}, {10002, PROTOCOL_NEHAHRABJP3 , "NEHAHRABJP3"}, { 0, PROTOCOL_UNKNOWN , NULL} }; protocolversion_t Protocol_EnumForName(const char *s) { int i; for (i = 0;protocolversioninfo[i].name;i++) if (!strcasecmp(s, protocolversioninfo[i].name)) return protocolversioninfo[i].version; return PROTOCOL_UNKNOWN; } const char *Protocol_NameForEnum(protocolversion_t p) { int i; for (i = 0;protocolversioninfo[i].name;i++) if (protocolversioninfo[i].version == p) return protocolversioninfo[i].name; return "UNKNOWN"; } protocolversion_t Protocol_EnumForNumber(int n) { int i; for (i = 0;protocolversioninfo[i].name;i++) if (protocolversioninfo[i].number == n) return protocolversioninfo[i].version; return PROTOCOL_UNKNOWN; } int Protocol_NumberForEnum(protocolversion_t p) { int i; for (i = 0;protocolversioninfo[i].name;i++) if (protocolversioninfo[i].version == p) return protocolversioninfo[i].number; return 0; } void Protocol_Names(char *buffer, size_t buffersize) { int i; if (buffersize < 1) return; buffer[0] = 0; for (i = 0;protocolversioninfo[i].name;i++) { if (i > 1) strlcat(buffer, " ", buffersize); strlcat(buffer, protocolversioninfo[i].name, buffersize); } } void EntityFrameQuake_ReadEntity(int bits) { int num; entity_t *ent; entity_state_t s; if (bits & U_MOREBITS) bits |= (MSG_ReadByte(&cl_message)<<8); if ((bits & U_EXTEND1) && cls.protocol != PROTOCOL_NEHAHRAMOVIE) { bits |= MSG_ReadByte(&cl_message) << 16; if (bits & U_EXTEND2) bits |= MSG_ReadByte(&cl_message) << 24; } if (bits & U_LONGENTITY) num = (unsigned short) MSG_ReadShort(&cl_message); else num = MSG_ReadByte(&cl_message); if (num >= MAX_EDICTS) Host_Error("EntityFrameQuake_ReadEntity: entity number (%i) >= MAX_EDICTS (%i)", num, MAX_EDICTS); if (num < 1) Host_Error("EntityFrameQuake_ReadEntity: invalid entity number (%i)", num); if (cl.num_entities <= num) { cl.num_entities = num + 1; if (num >= cl.max_entities) CL_ExpandEntities(num); } ent = cl.entities + num; // note: this inherits the 'active' state of the baseline chosen // (state_baseline is always active, state_current may not be active if // the entity was missing in the last frame) if (bits & U_DELTA) s = ent->state_current; else { s = ent->state_baseline; s.active = ACTIVE_NETWORK; } cl.isquakeentity[num] = true; if (cl.lastquakeentity < num) cl.lastquakeentity = num; s.number = num; s.time = cl.mtime[0]; s.flags = 0; if (bits & U_MODEL) { if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) s.modelindex = (unsigned short) MSG_ReadShort(&cl_message); else s.modelindex = (s.modelindex & 0xFF00) | MSG_ReadByte(&cl_message); } if (bits & U_FRAME) s.frame = (s.frame & 0xFF00) | MSG_ReadByte(&cl_message); if (bits & U_COLORMAP) s.colormap = MSG_ReadByte(&cl_message); if (bits & U_SKIN) s.skin = MSG_ReadByte(&cl_message); if (bits & U_EFFECTS) s.effects = (s.effects & 0xFF00) | MSG_ReadByte(&cl_message); if (bits & U_ORIGIN1) s.origin[0] = MSG_ReadCoord(&cl_message, cls.protocol); if (bits & U_ANGLE1) s.angles[0] = MSG_ReadAngle(&cl_message, cls.protocol); if (bits & U_ORIGIN2) s.origin[1] = MSG_ReadCoord(&cl_message, cls.protocol); if (bits & U_ANGLE2) s.angles[1] = MSG_ReadAngle(&cl_message, cls.protocol); if (bits & U_ORIGIN3) s.origin[2] = MSG_ReadCoord(&cl_message, cls.protocol); if (bits & U_ANGLE3) s.angles[2] = MSG_ReadAngle(&cl_message, cls.protocol); if (bits & U_STEP) s.flags |= RENDER_STEP; if (bits & U_ALPHA) s.alpha = MSG_ReadByte(&cl_message); if (bits & U_SCALE) s.scale = MSG_ReadByte(&cl_message); if (bits & U_EFFECTS2) s.effects = (s.effects & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); if (bits & U_GLOWSIZE) s.glowsize = MSG_ReadByte(&cl_message); if (bits & U_GLOWCOLOR) s.glowcolor = MSG_ReadByte(&cl_message); if (bits & U_COLORMOD) {int c = MSG_ReadByte(&cl_message);s.colormod[0] = (unsigned char)(((c >> 5) & 7) * (32.0f / 7.0f));s.colormod[1] = (unsigned char)(((c >> 2) & 7) * (32.0f / 7.0f));s.colormod[2] = (unsigned char)((c & 3) * (32.0f / 3.0f));} if (bits & U_GLOWTRAIL) s.flags |= RENDER_GLOWTRAIL; if (bits & U_FRAME2) s.frame = (s.frame & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); if (bits & U_MODEL2) s.modelindex = (s.modelindex & 0x00FF) | (MSG_ReadByte(&cl_message) << 8); if (bits & U_VIEWMODEL) s.flags |= RENDER_VIEWMODEL; if (bits & U_EXTERIORMODEL) s.flags |= RENDER_EXTERIORMODEL; // LordHavoc: to allow playback of the Nehahra movie if (cls.protocol == PROTOCOL_NEHAHRAMOVIE && (bits & U_EXTEND1)) { // LordHavoc: evil format int i = (int)MSG_ReadFloat(&cl_message); int j = (int)(MSG_ReadFloat(&cl_message) * 255.0f); if (i == 2) { i = (int)MSG_ReadFloat(&cl_message); if (i) s.effects |= EF_FULLBRIGHT; } if (j < 0) s.alpha = 0; else if (j == 0 || j >= 255) s.alpha = 255; else s.alpha = j; } ent->state_previous = ent->state_current; ent->state_current = s; if (ent->state_current.active == ACTIVE_NETWORK) { CL_MoveLerpEntityStates(ent); cl.entities_active[ent->state_current.number] = true; } if (cl_message.badread) Host_Error("EntityFrameQuake_ReadEntity: read error"); } void EntityFrameQuake_ISeeDeadEntities(void) { int num, lastentity; if (cl.lastquakeentity == 0) return; lastentity = cl.lastquakeentity; cl.lastquakeentity = 0; for (num = 0;num <= lastentity;num++) { if (cl.isquakeentity[num]) { if (cl.entities_active[num] && cl.entities[num].state_current.time == cl.mtime[0]) { cl.isquakeentity[num] = true; cl.lastquakeentity = num; } else { cl.isquakeentity[num] = false; cl.entities_active[num] = ACTIVE_NOT; cl.entities[num].state_current = defaultstate; cl.entities[num].state_current.number = num; } } } } // NOTE: this only works with DP5 protocol and upwards. For lower protocols // (including QUAKE), no packet loss handling for CSQC is done, which makes // CSQC basically useless. // Always use the DP5 protocol, or a higher one, when using CSQC entities. static void EntityFrameCSQC_LostAllFrames(client_t *client) { prvm_prog_t *prog = SVVM_prog; // mark ALL csqc entities as requiring a FULL resend! // I know this is a bad workaround, but better than nothing. int i, n; prvm_edict_t *ed; n = client->csqcnumedicts; for(i = 0; i < n; ++i) { if(client->csqcentityscope[i] & SCOPE_EXISTED_ONCE) { ed = prog->edicts + i; client->csqcentitysendflags[i] |= 0xFFFFFF; // FULL RESEND. We can't clear SCOPE_ASSUMED_EXISTING yet as this would cancel removes on a rejected send attempt. if (!PRVM_serveredictfunction(ed, SendEntity)) // If it was ever sent to that client as a CSQC entity... client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE. } } } void EntityFrameCSQC_LostFrame(client_t *client, int framenum) { // marks a frame as lost int i, j; qboolean valid; int ringfirst, ringlast; static int recoversendflags[MAX_EDICTS]; // client only csqcentityframedb_t *d; if(client->csqcentityframe_lastreset < 0) return; if(framenum < client->csqcentityframe_lastreset) return; // no action required, as we resent that data anyway // is our frame out of history? ringfirst = client->csqcentityframehistory_next; // oldest entry ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry valid = false; for(j = 0; j < NUM_CSQCENTITYDB_FRAMES; ++j) { d = &client->csqcentityframehistory[(ringfirst + j) % NUM_CSQCENTITYDB_FRAMES]; if(d->framenum < 0) continue; if(d->framenum == framenum) break; else if(d->framenum < framenum) valid = true; } if(j == NUM_CSQCENTITYDB_FRAMES) { if(valid) // got beaten, i.e. there is a frame < framenum { // a non-csqc frame got lost... great return; } else { // a too old frame got lost... sorry, cannot handle this Con_DPrintf("CSQC entity DB: lost a frame too early to do any handling (resending ALL)...\n"); Con_DPrintf("Lost frame = %d\n", framenum); Con_DPrintf("Entity DB = %d to %d\n", client->csqcentityframehistory[ringfirst].framenum, client->csqcentityframehistory[ringlast].framenum); EntityFrameCSQC_LostAllFrames(client); client->csqcentityframe_lastreset = -1; } return; } // so j is the frame that got lost // ringlast is the frame that we have to go to ringfirst = (ringfirst + j) % NUM_CSQCENTITYDB_FRAMES; if(ringlast < ringfirst) ringlast += NUM_CSQCENTITYDB_FRAMES; memset(recoversendflags, 0, sizeof(recoversendflags)); for(j = ringfirst; j <= ringlast; ++j) { d = &client->csqcentityframehistory[j % NUM_CSQCENTITYDB_FRAMES]; if(d->framenum < 0) { // deleted frame } else if(d->framenum < framenum) { // a frame in the past... should never happen Con_Printf("CSQC entity DB encountered a frame from the past when recovering from PL...?\n"); } else if(d->framenum == framenum) { // handling the actually lost frame now for(i = 0; i < d->num; ++i) { int sf = d->sendflags[i]; int ent = d->entno[i]; if(sf < 0) // remove recoversendflags[ent] |= -1; // all bits, including sign else if(sf > 0) recoversendflags[ent] |= sf; } } else { // handling the frames that followed it now for(i = 0; i < d->num; ++i) { int sf = d->sendflags[i]; int ent = d->entno[i]; if(sf < 0) // remove { recoversendflags[ent] = 0; // no need to update, we got a more recent remove (and will fix it THEN) break; // no flags left to remove... } else if(sf > 0) recoversendflags[ent] &= ~sf; // no need to update these bits, we already got them later } } } for(i = 0; i < client->csqcnumedicts; ++i) { if(recoversendflags[i] < 0) client->csqcentityscope[i] |= SCOPE_ASSUMED_EXISTING; // FORCE REMOVE. else client->csqcentitysendflags[i] |= recoversendflags[i]; } } static int EntityFrameCSQC_AllocFrame(client_t *client, int framenum) { int ringfirst = client->csqcentityframehistory_next; // oldest entry client->csqcentityframehistory_next += 1; client->csqcentityframehistory_next %= NUM_CSQCENTITYDB_FRAMES; client->csqcentityframehistory[ringfirst].framenum = framenum; client->csqcentityframehistory[ringfirst].num = 0; return ringfirst; } static void EntityFrameCSQC_DeallocFrame(client_t *client, int framenum) { int ringfirst = client->csqcentityframehistory_next; // oldest entry int ringlast = (ringfirst + NUM_CSQCENTITYDB_FRAMES - 1) % NUM_CSQCENTITYDB_FRAMES; // most recently added entry if(framenum == client->csqcentityframehistory[ringlast].framenum) { client->csqcentityframehistory[ringlast].framenum = -1; client->csqcentityframehistory[ringlast].num = 0; client->csqcentityframehistory_next = ringlast; } else Con_Printf("Trying to dealloc the wrong entity frame\n"); } //[515]: we use only one array per-client for SendEntity feature // TODO: add some handling for entity send priorities, to better deal with huge // amounts of csqc networked entities qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum) { prvm_prog_t *prog = SVVM_prog; int num, number, end, sendflags; qboolean sectionstarted = false; const unsigned short *n; prvm_edict_t *ed; client_t *client = svs.clients + sv.writeentitiestoclient_clientnumber; int dbframe = EntityFrameCSQC_AllocFrame(client, framenum); csqcentityframedb_t *db = &client->csqcentityframehistory[dbframe]; if(client->csqcentityframe_lastreset < 0) client->csqcentityframe_lastreset = framenum; maxsize -= 24; // always fit in an empty svc_entities message (for packet loss detection!) // make sure there is enough room to store the svc_csqcentities byte, // the terminator (0x0000) and at least one entity update if (msg->cursize + 32 >= maxsize) return false; if (client->csqcnumedicts < prog->num_edicts) client->csqcnumedicts = prog->num_edicts; number = 1; for (num = 0, n = numbers;num < numnumbers;num++, n++) { end = *n; for (;number < end;number++) { client->csqcentityscope[number] &= ~SCOPE_WANTSEND; if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) client->csqcentityscope[number] |= SCOPE_WANTREMOVE; client->csqcentitysendflags[number] = 0xFFFFFF; } ed = prog->edicts + number; client->csqcentityscope[number] &= ~SCOPE_WANTSEND; if (PRVM_serveredictfunction(ed, SendEntity)) client->csqcentityscope[number] |= SCOPE_WANTUPDATE; else { if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) client->csqcentityscope[number] |= SCOPE_WANTREMOVE; client->csqcentitysendflags[number] = 0xFFFFFF; } number++; } end = client->csqcnumedicts; for (;number < end;number++) { client->csqcentityscope[number] &= ~SCOPE_WANTSEND; if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) client->csqcentityscope[number] |= SCOPE_WANTREMOVE; client->csqcentitysendflags[number] = 0xFFFFFF; } // now try to emit the entity updates // (FIXME: prioritize by distance?) end = client->csqcnumedicts; for (number = 1;number < end;number++) { if (!(client->csqcentityscope[number] & SCOPE_WANTSEND)) continue; if(db->num >= NUM_CSQCENTITIES_PER_FRAME) break; ed = prog->edicts + number; if (client->csqcentityscope[number] & SCOPE_WANTREMOVE) // Also implies ASSUMED_EXISTING. { // A removal. SendFlags have no power here. // write a remove message // first write the message identifier if needed if(!sectionstarted) { sectionstarted = 1; MSG_WriteByte(msg, svc_csqcentities); } // write the remove message { ENTITYSIZEPROFILING_START(msg, number, 0); MSG_WriteShort(msg, (unsigned short)number | 0x8000); client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING); client->csqcentitysendflags[number] = 0xFFFFFF; // resend completely if it becomes active again db->entno[db->num] = number; db->sendflags[db->num] = -1; db->num += 1; ENTITYSIZEPROFILING_END(msg, number, 0); } if (msg->cursize + 17 >= maxsize) break; } else { // save the cursize value in case we overflow and have to rollback int oldcursize = msg->cursize; // An update. sendflags = client->csqcentitysendflags[number]; // Nothing to send? FINE. if (!sendflags) continue; // If it's a new entity, always assume sendflags 0xFFFFFF. if (!(client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING)) sendflags = 0xFFFFFF; // write an update if (PRVM_serveredictfunction(ed, SendEntity)) { if(!sectionstarted) MSG_WriteByte(msg, svc_csqcentities); { int oldcursize2 = msg->cursize; ENTITYSIZEPROFILING_START(msg, number, sendflags); MSG_WriteShort(msg, number); msg->allowoverflow = true; PRVM_G_INT(OFS_PARM0) = sv.writeentitiestoclient_cliententitynumber; PRVM_G_FLOAT(OFS_PARM1) = sendflags; PRVM_serverglobaledict(self) = number; prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, SendEntity), "Null SendEntity\n"); msg->allowoverflow = false; if(!PRVM_G_FLOAT(OFS_RETURN)) { // Send rejected by CSQC. This means we want to remove it. // CSQC requests we remove this one. if (client->csqcentityscope[number] & SCOPE_ASSUMED_EXISTING) { msg->cursize = oldcursize2; msg->overflowed = false; MSG_WriteShort(msg, (unsigned short)number | 0x8000); client->csqcentityscope[number] &= ~(SCOPE_WANTSEND | SCOPE_ASSUMED_EXISTING); client->csqcentitysendflags[number] = 0; db->entno[db->num] = number; db->sendflags[db->num] = -1; db->num += 1; // and take note that we have begun the svc_csqcentities // section of the packet sectionstarted = 1; ENTITYSIZEPROFILING_END(msg, number, 0); if (msg->cursize + 17 >= maxsize) break; } else { // Nothing to do. Just don't do it again. msg->cursize = oldcursize; msg->overflowed = false; client->csqcentityscope[number] &= ~SCOPE_WANTSEND; client->csqcentitysendflags[number] = 0; } continue; } else if(PRVM_G_FLOAT(OFS_RETURN) && msg->cursize + 2 <= maxsize) { // an update has been successfully written client->csqcentitysendflags[number] = 0; db->entno[db->num] = number; db->sendflags[db->num] = sendflags; db->num += 1; client->csqcentityscope[number] &= ~SCOPE_WANTSEND; client->csqcentityscope[number] |= SCOPE_EXISTED_ONCE | SCOPE_ASSUMED_EXISTING; // and take note that we have begun the svc_csqcentities // section of the packet sectionstarted = 1; ENTITYSIZEPROFILING_END(msg, number, sendflags); if (msg->cursize + 17 >= maxsize) break; continue; } } } // self.SendEntity returned false (or does not exist) or the // update was too big for this packet - rollback the buffer to its // state before the writes occurred, we'll try again next frame msg->cursize = oldcursize; msg->overflowed = false; } } if (sectionstarted) { // write index 0 to end the update (0 is never used by real entities) MSG_WriteShort(msg, 0); } if(db->num == 0) // if no single ent got added, remove the frame from the DB again, to allow // for a larger history EntityFrameCSQC_DeallocFrame(client, framenum); return sectionstarted; } void Protocol_UpdateClientStats(const int *stats) { int i; // update the stats array and set deltabits for any changed stats for (i = 0;i < MAX_CL_STATS;i++) { if (host_client->stats[i] != stats[i]) { host_client->statsdeltabits[i >> 3] |= 1 << (i & 7); host_client->stats[i] = stats[i]; } } } // only a few stats are within the 32 stat limit of Quake, and most of them // are sent every frame in svc_clientdata messages, so we only send the // remaining ones here static const int sendquakestats[] = { // quake did not send these secrets/monsters stats in this way, but doing so // allows a mod to increase STAT_TOTALMONSTERS during the game, and ensures // that STAT_SECRETS and STAT_MONSTERS are always correct (even if a client // didn't receive an svc_foundsecret or svc_killedmonster), which may be most // valuable if randomly seeking around in a demo STAT_TOTALSECRETS, // never changes during game STAT_TOTALMONSTERS, // changes in some mods STAT_SECRETS, // this makes svc_foundsecret unnecessary STAT_MONSTERS, // this makes svc_killedmonster unnecessary STAT_VIEWHEIGHT, // sent just for FTEQW clients STAT_VIEWZOOM, // this rarely changes -1, }; void Protocol_WriteStatsReliable(void) { int i, j; if (!host_client->netconnection) return; // detect changes in stats and write reliable messages // this only deals with 32 stats because the older protocols which use // this function can only cope with 32 stats, // they also do not support svc_updatestatubyte which was introduced in // DP6 protocol (except for QW) for (j = 0;sendquakestats[j] >= 0;j++) { i = sendquakestats[j]; // check if this bit is set if (host_client->statsdeltabits[i >> 3] & (1 << (i & 7))) { host_client->statsdeltabits[i >> 3] -= (1 << (i & 7)); // send the stat as a byte if possible if (sv.protocol == PROTOCOL_QUAKEWORLD) { if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) { MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestat); MSG_WriteByte(&host_client->netconnection->message, i); MSG_WriteByte(&host_client->netconnection->message, host_client->stats[i]); } else { MSG_WriteByte(&host_client->netconnection->message, qw_svc_updatestatlong); MSG_WriteByte(&host_client->netconnection->message, i); MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); } } else { // this could make use of svc_updatestatubyte in DP6 and later // protocols but those protocols do not use this function MSG_WriteByte(&host_client->netconnection->message, svc_updatestat); MSG_WriteByte(&host_client->netconnection->message, i); MSG_WriteLong(&host_client->netconnection->message, host_client->stats[i]); } } } } qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states) { prvm_prog_t *prog = SVVM_prog; const entity_state_t *s; entity_state_t baseline; int i, bits; sizebuf_t buf; unsigned char data[128]; qboolean success = false; // prepare the buffer memset(&buf, 0, sizeof(buf)); buf.data = data; buf.maxsize = sizeof(data); for (i = 0;i < numstates;i++) { s = states[i]; if(PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) continue; // prepare the buffer SZ_Clear(&buf); // send an update bits = 0; if (s->number >= 256) bits |= U_LONGENTITY; if (s->flags & RENDER_STEP) bits |= U_STEP; if (s->flags & RENDER_VIEWMODEL) bits |= U_VIEWMODEL; if (s->flags & RENDER_GLOWTRAIL) bits |= U_GLOWTRAIL; if (s->flags & RENDER_EXTERIORMODEL) bits |= U_EXTERIORMODEL; // LordHavoc: old stuff, but rewritten to have more exact tolerances baseline = prog->edicts[s->number].priv.server->baseline; if (baseline.origin[0] != s->origin[0]) bits |= U_ORIGIN1; if (baseline.origin[1] != s->origin[1]) bits |= U_ORIGIN2; if (baseline.origin[2] != s->origin[2]) bits |= U_ORIGIN3; if (baseline.angles[0] != s->angles[0]) bits |= U_ANGLE1; if (baseline.angles[1] != s->angles[1]) bits |= U_ANGLE2; if (baseline.angles[2] != s->angles[2]) bits |= U_ANGLE3; if (baseline.colormap != s->colormap) bits |= U_COLORMAP; if (baseline.skin != s->skin) bits |= U_SKIN; if (baseline.frame != s->frame) { bits |= U_FRAME; if (s->frame & 0xFF00) bits |= U_FRAME2; } if (baseline.effects != s->effects) { bits |= U_EFFECTS; if (s->effects & 0xFF00) bits |= U_EFFECTS2; } if (baseline.modelindex != s->modelindex) { bits |= U_MODEL; if ((s->modelindex & 0xFF00) && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) bits |= U_MODEL2; } if (baseline.alpha != s->alpha) bits |= U_ALPHA; if (baseline.scale != s->scale) bits |= U_SCALE; if (baseline.glowsize != s->glowsize) bits |= U_GLOWSIZE; if (baseline.glowcolor != s->glowcolor) bits |= U_GLOWCOLOR; if (!VectorCompare(baseline.colormod, s->colormod)) bits |= U_COLORMOD; // if extensions are disabled, clear the relevant update flags if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_NEHAHRAMOVIE) bits &= 0x7FFF; if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) if (s->alpha != 255 || s->effects & EF_FULLBRIGHT) bits |= U_EXTEND1; // write the message if (bits >= 16777216) bits |= U_EXTEND2; if (bits >= 65536) bits |= U_EXTEND1; if (bits >= 256) bits |= U_MOREBITS; bits |= U_SIGNAL; { ENTITYSIZEPROFILING_START(msg, states[i]->number, bits); MSG_WriteByte (&buf, bits); if (bits & U_MOREBITS) MSG_WriteByte(&buf, bits>>8); if (sv.protocol != PROTOCOL_NEHAHRAMOVIE) { if (bits & U_EXTEND1) MSG_WriteByte(&buf, bits>>16); if (bits & U_EXTEND2) MSG_WriteByte(&buf, bits>>24); } if (bits & U_LONGENTITY) MSG_WriteShort(&buf, s->number); else MSG_WriteByte(&buf, s->number); if (bits & U_MODEL) { if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) MSG_WriteShort(&buf, s->modelindex); else MSG_WriteByte(&buf, s->modelindex); } if (bits & U_FRAME) MSG_WriteByte(&buf, s->frame); if (bits & U_COLORMAP) MSG_WriteByte(&buf, s->colormap); if (bits & U_SKIN) MSG_WriteByte(&buf, s->skin); if (bits & U_EFFECTS) MSG_WriteByte(&buf, s->effects); if (bits & U_ORIGIN1) MSG_WriteCoord(&buf, s->origin[0], sv.protocol); if (bits & U_ANGLE1) MSG_WriteAngle(&buf, s->angles[0], sv.protocol); if (bits & U_ORIGIN2) MSG_WriteCoord(&buf, s->origin[1], sv.protocol); if (bits & U_ANGLE2) MSG_WriteAngle(&buf, s->angles[1], sv.protocol); if (bits & U_ORIGIN3) MSG_WriteCoord(&buf, s->origin[2], sv.protocol); if (bits & U_ANGLE3) MSG_WriteAngle(&buf, s->angles[2], sv.protocol); if (bits & U_ALPHA) MSG_WriteByte(&buf, s->alpha); if (bits & U_SCALE) MSG_WriteByte(&buf, s->scale); if (bits & U_EFFECTS2) MSG_WriteByte(&buf, s->effects >> 8); if (bits & U_GLOWSIZE) MSG_WriteByte(&buf, s->glowsize); if (bits & U_GLOWCOLOR) MSG_WriteByte(&buf, s->glowcolor); if (bits & U_COLORMOD) {int c = ((int)bound(0, s->colormod[0] * (7.0f / 32.0f), 7) << 5) | ((int)bound(0, s->colormod[1] * (7.0f / 32.0f), 7) << 2) | ((int)bound(0, s->colormod[2] * (3.0f / 32.0f), 3) << 0);MSG_WriteByte(&buf, c);} if (bits & U_FRAME2) MSG_WriteByte(&buf, s->frame >> 8); if (bits & U_MODEL2) MSG_WriteByte(&buf, s->modelindex >> 8); // the nasty protocol if ((bits & U_EXTEND1) && sv.protocol == PROTOCOL_NEHAHRAMOVIE) { if (s->effects & EF_FULLBRIGHT) { MSG_WriteFloat(&buf, 2); // QSG protocol version MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha MSG_WriteFloat(&buf, 1); // fullbright } else { MSG_WriteFloat(&buf, 1); // QSG protocol version MSG_WriteFloat(&buf, s->alpha <= 0 ? 0 : (s->alpha >= 255 ? 1 : s->alpha * (1.0f / 255.0f))); // alpha } } // if the commit is full, we're done this frame if (msg->cursize + buf.cursize > maxsize) { // next frame we will continue where we left off break; } // write the message to the packet SZ_Write(msg, buf.data, buf.cursize); success = true; ENTITYSIZEPROFILING_END(msg, s->number, bits); } } return success; } int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n) { unsigned int bits; // if o is not active, delta from default if (o->active != ACTIVE_NETWORK) o = &defaultstate; bits = 0; if (fabs(n->origin[0] - o->origin[0]) > (1.0f / 256.0f)) bits |= E_ORIGIN1; if (fabs(n->origin[1] - o->origin[1]) > (1.0f / 256.0f)) bits |= E_ORIGIN2; if (fabs(n->origin[2] - o->origin[2]) > (1.0f / 256.0f)) bits |= E_ORIGIN3; if ((unsigned char) (n->angles[0] * (256.0f / 360.0f)) != (unsigned char) (o->angles[0] * (256.0f / 360.0f))) bits |= E_ANGLE1; if ((unsigned char) (n->angles[1] * (256.0f / 360.0f)) != (unsigned char) (o->angles[1] * (256.0f / 360.0f))) bits |= E_ANGLE2; if ((unsigned char) (n->angles[2] * (256.0f / 360.0f)) != (unsigned char) (o->angles[2] * (256.0f / 360.0f))) bits |= E_ANGLE3; if ((n->modelindex ^ o->modelindex) & 0x00FF) bits |= E_MODEL1; if ((n->modelindex ^ o->modelindex) & 0xFF00) bits |= E_MODEL2; if ((n->frame ^ o->frame) & 0x00FF) bits |= E_FRAME1; if ((n->frame ^ o->frame) & 0xFF00) bits |= E_FRAME2; if ((n->effects ^ o->effects) & 0x00FF) bits |= E_EFFECTS1; if ((n->effects ^ o->effects) & 0xFF00) bits |= E_EFFECTS2; if (n->colormap != o->colormap) bits |= E_COLORMAP; if (n->skin != o->skin) bits |= E_SKIN; if (n->alpha != o->alpha) bits |= E_ALPHA; if (n->scale != o->scale) bits |= E_SCALE; if (n->glowsize != o->glowsize) bits |= E_GLOWSIZE; if (n->glowcolor != o->glowcolor) bits |= E_GLOWCOLOR; if (n->flags != o->flags) bits |= E_FLAGS; if (n->tagindex != o->tagindex || n->tagentity != o->tagentity) bits |= E_TAGATTACHMENT; if (n->light[0] != o->light[0] || n->light[1] != o->light[1] || n->light[2] != o->light[2] || n->light[3] != o->light[3]) bits |= E_LIGHT; if (n->lightstyle != o->lightstyle) bits |= E_LIGHTSTYLE; if (n->lightpflags != o->lightpflags) bits |= E_LIGHTPFLAGS; if (bits) { if (bits & 0xFF000000) bits |= 0x00800000; if (bits & 0x00FF0000) bits |= 0x00008000; if (bits & 0x0000FF00) bits |= 0x00000080; } return bits; } void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits) { MSG_WriteByte(msg, bits & 0xFF); if (bits & 0x00000080) { MSG_WriteByte(msg, (bits >> 8) & 0xFF); if (bits & 0x00008000) { MSG_WriteByte(msg, (bits >> 16) & 0xFF); if (bits & 0x00800000) MSG_WriteByte(msg, (bits >> 24) & 0xFF); } } } void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits) { if (sv.protocol == PROTOCOL_DARKPLACES2) { if (bits & E_ORIGIN1) MSG_WriteCoord16i(msg, ent->origin[0]); if (bits & E_ORIGIN2) MSG_WriteCoord16i(msg, ent->origin[1]); if (bits & E_ORIGIN3) MSG_WriteCoord16i(msg, ent->origin[2]); } else { // LordHavoc: have to write flags first, as they can modify protocol if (bits & E_FLAGS) MSG_WriteByte(msg, ent->flags); if (ent->flags & RENDER_LOWPRECISION) { if (bits & E_ORIGIN1) MSG_WriteCoord16i(msg, ent->origin[0]); if (bits & E_ORIGIN2) MSG_WriteCoord16i(msg, ent->origin[1]); if (bits & E_ORIGIN3) MSG_WriteCoord16i(msg, ent->origin[2]); } else { if (bits & E_ORIGIN1) MSG_WriteCoord32f(msg, ent->origin[0]); if (bits & E_ORIGIN2) MSG_WriteCoord32f(msg, ent->origin[1]); if (bits & E_ORIGIN3) MSG_WriteCoord32f(msg, ent->origin[2]); } } if ((sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4) && (ent->flags & RENDER_LOWPRECISION)) { if (bits & E_ANGLE1) MSG_WriteAngle8i(msg, ent->angles[0]); if (bits & E_ANGLE2) MSG_WriteAngle8i(msg, ent->angles[1]); if (bits & E_ANGLE3) MSG_WriteAngle8i(msg, ent->angles[2]); } else { if (bits & E_ANGLE1) MSG_WriteAngle16i(msg, ent->angles[0]); if (bits & E_ANGLE2) MSG_WriteAngle16i(msg, ent->angles[1]); if (bits & E_ANGLE3) MSG_WriteAngle16i(msg, ent->angles[2]); } if (bits & E_MODEL1) MSG_WriteByte(msg, ent->modelindex & 0xFF); if (bits & E_MODEL2) MSG_WriteByte(msg, (ent->modelindex >> 8) & 0xFF); if (bits & E_FRAME1) MSG_WriteByte(msg, ent->frame & 0xFF); if (bits & E_FRAME2) MSG_WriteByte(msg, (ent->frame >> 8) & 0xFF); if (bits & E_EFFECTS1) MSG_WriteByte(msg, ent->effects & 0xFF); if (bits & E_EFFECTS2) MSG_WriteByte(msg, (ent->effects >> 8) & 0xFF); if (bits & E_COLORMAP) MSG_WriteByte(msg, ent->colormap); if (bits & E_SKIN) MSG_WriteByte(msg, ent->skin); if (bits & E_ALPHA) MSG_WriteByte(msg, ent->alpha); if (bits & E_SCALE) MSG_WriteByte(msg, ent->scale); if (bits & E_GLOWSIZE) MSG_WriteByte(msg, ent->glowsize); if (bits & E_GLOWCOLOR) MSG_WriteByte(msg, ent->glowcolor); if (sv.protocol == PROTOCOL_DARKPLACES2) if (bits & E_FLAGS) MSG_WriteByte(msg, ent->flags); if (bits & E_TAGATTACHMENT) { MSG_WriteShort(msg, ent->tagentity); MSG_WriteByte(msg, ent->tagindex); } if (bits & E_LIGHT) { MSG_WriteShort(msg, ent->light[0]); MSG_WriteShort(msg, ent->light[1]); MSG_WriteShort(msg, ent->light[2]); MSG_WriteShort(msg, ent->light[3]); } if (bits & E_LIGHTSTYLE) MSG_WriteByte(msg, ent->lightstyle); if (bits & E_LIGHTPFLAGS) MSG_WriteByte(msg, ent->lightpflags); } void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta) { prvm_prog_t *prog = SVVM_prog; unsigned int bits; if (ent->active == ACTIVE_NETWORK) { // entity is active, check for changes from the delta if ((bits = EntityState_DeltaBits(delta, ent))) { // write the update number, bits, and fields ENTITYSIZEPROFILING_START(msg, ent->number, bits); MSG_WriteShort(msg, ent->number); EntityState_WriteExtendBits(msg, bits); EntityState_WriteFields(ent, msg, bits); ENTITYSIZEPROFILING_END(msg, ent->number, bits); } } else { // entity is inactive, check if the delta was active if (delta->active == ACTIVE_NETWORK) { // write the remove number ENTITYSIZEPROFILING_START(msg, ent->number, 0); MSG_WriteShort(msg, ent->number | 0x8000); ENTITYSIZEPROFILING_END(msg, ent->number, 0); } } } int EntityState_ReadExtendBits(void) { unsigned int bits; bits = MSG_ReadByte(&cl_message); if (bits & 0x00000080) { bits |= MSG_ReadByte(&cl_message) << 8; if (bits & 0x00008000) { bits |= MSG_ReadByte(&cl_message) << 16; if (bits & 0x00800000) bits |= MSG_ReadByte(&cl_message) << 24; } } return bits; } void EntityState_ReadFields(entity_state_t *e, unsigned int bits) { if (cls.protocol == PROTOCOL_DARKPLACES2) { if (bits & E_ORIGIN1) e->origin[0] = MSG_ReadCoord16i(&cl_message); if (bits & E_ORIGIN2) e->origin[1] = MSG_ReadCoord16i(&cl_message); if (bits & E_ORIGIN3) e->origin[2] = MSG_ReadCoord16i(&cl_message); } else { if (bits & E_FLAGS) e->flags = MSG_ReadByte(&cl_message); if (e->flags & RENDER_LOWPRECISION) { if (bits & E_ORIGIN1) e->origin[0] = MSG_ReadCoord16i(&cl_message); if (bits & E_ORIGIN2) e->origin[1] = MSG_ReadCoord16i(&cl_message); if (bits & E_ORIGIN3) e->origin[2] = MSG_ReadCoord16i(&cl_message); } else { if (bits & E_ORIGIN1) e->origin[0] = MSG_ReadCoord32f(&cl_message); if (bits & E_ORIGIN2) e->origin[1] = MSG_ReadCoord32f(&cl_message); if (bits & E_ORIGIN3) e->origin[2] = MSG_ReadCoord32f(&cl_message); } } if ((cls.protocol == PROTOCOL_DARKPLACES5 || cls.protocol == PROTOCOL_DARKPLACES6) && !(e->flags & RENDER_LOWPRECISION)) { if (bits & E_ANGLE1) e->angles[0] = MSG_ReadAngle16i(&cl_message); if (bits & E_ANGLE2) e->angles[1] = MSG_ReadAngle16i(&cl_message); if (bits & E_ANGLE3) e->angles[2] = MSG_ReadAngle16i(&cl_message); } else { if (bits & E_ANGLE1) e->angles[0] = MSG_ReadAngle8i(&cl_message); if (bits & E_ANGLE2) e->angles[1] = MSG_ReadAngle8i(&cl_message); if (bits & E_ANGLE3) e->angles[2] = MSG_ReadAngle8i(&cl_message); } if (bits & E_MODEL1) e->modelindex = (e->modelindex & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); if (bits & E_MODEL2) e->modelindex = (e->modelindex & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); if (bits & E_FRAME1) e->frame = (e->frame & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); if (bits & E_FRAME2) e->frame = (e->frame & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); if (bits & E_EFFECTS1) e->effects = (e->effects & 0xFF00) | (unsigned int) MSG_ReadByte(&cl_message); if (bits & E_EFFECTS2) e->effects = (e->effects & 0x00FF) | ((unsigned int) MSG_ReadByte(&cl_message) << 8); if (bits & E_COLORMAP) e->colormap = MSG_ReadByte(&cl_message); if (bits & E_SKIN) e->skin = MSG_ReadByte(&cl_message); if (bits & E_ALPHA) e->alpha = MSG_ReadByte(&cl_message); if (bits & E_SCALE) e->scale = MSG_ReadByte(&cl_message); if (bits & E_GLOWSIZE) e->glowsize = MSG_ReadByte(&cl_message); if (bits & E_GLOWCOLOR) e->glowcolor = MSG_ReadByte(&cl_message); if (cls.protocol == PROTOCOL_DARKPLACES2) if (bits & E_FLAGS) e->flags = MSG_ReadByte(&cl_message); if (bits & E_TAGATTACHMENT) { e->tagentity = (unsigned short) MSG_ReadShort(&cl_message); e->tagindex = MSG_ReadByte(&cl_message); } if (bits & E_LIGHT) { e->light[0] = (unsigned short) MSG_ReadShort(&cl_message); e->light[1] = (unsigned short) MSG_ReadShort(&cl_message); e->light[2] = (unsigned short) MSG_ReadShort(&cl_message); e->light[3] = (unsigned short) MSG_ReadShort(&cl_message); } if (bits & E_LIGHTSTYLE) e->lightstyle = MSG_ReadByte(&cl_message); if (bits & E_LIGHTPFLAGS) e->lightpflags = MSG_ReadByte(&cl_message); if (developer_networkentities.integer >= 2) { Con_Printf("ReadFields e%i", e->number); if (bits & E_ORIGIN1) Con_Printf(" E_ORIGIN1 %f", e->origin[0]); if (bits & E_ORIGIN2) Con_Printf(" E_ORIGIN2 %f", e->origin[1]); if (bits & E_ORIGIN3) Con_Printf(" E_ORIGIN3 %f", e->origin[2]); if (bits & E_ANGLE1) Con_Printf(" E_ANGLE1 %f", e->angles[0]); if (bits & E_ANGLE2) Con_Printf(" E_ANGLE2 %f", e->angles[1]); if (bits & E_ANGLE3) Con_Printf(" E_ANGLE3 %f", e->angles[2]); if (bits & (E_MODEL1 | E_MODEL2)) Con_Printf(" E_MODEL %i", e->modelindex); if (bits & (E_FRAME1 | E_FRAME2)) Con_Printf(" E_FRAME %i", e->frame); if (bits & (E_EFFECTS1 | E_EFFECTS2)) Con_Printf(" E_EFFECTS %i", e->effects); if (bits & E_ALPHA) Con_Printf(" E_ALPHA %f", e->alpha / 255.0f); if (bits & E_SCALE) Con_Printf(" E_SCALE %f", e->scale / 16.0f); if (bits & E_COLORMAP) Con_Printf(" E_COLORMAP %i", e->colormap); if (bits & E_SKIN) Con_Printf(" E_SKIN %i", e->skin); if (bits & E_GLOWSIZE) Con_Printf(" E_GLOWSIZE %i", e->glowsize * 4); if (bits & E_GLOWCOLOR) Con_Printf(" E_GLOWCOLOR %i", e->glowcolor); if (bits & E_LIGHT) Con_Printf(" E_LIGHT %i:%i:%i:%i", e->light[0], e->light[1], e->light[2], e->light[3]); if (bits & E_LIGHTPFLAGS) Con_Printf(" E_LIGHTPFLAGS %i", e->lightpflags); if (bits & E_TAGATTACHMENT) Con_Printf(" E_TAGATTACHMENT e%i:%i", e->tagentity, e->tagindex); if (bits & E_LIGHTSTYLE) Con_Printf(" E_LIGHTSTYLE %i", e->lightstyle); Con_Print("\n"); } } // (client and server) allocates a new empty database entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool) { return (entityframe_database_t *)Mem_Alloc(mempool, sizeof(entityframe_database_t)); } // (client and server) frees the database void EntityFrame_FreeDatabase(entityframe_database_t *d) { Mem_Free(d); } // (server) clears the database to contain no frames (thus delta compression compresses against nothing) void EntityFrame_ClearDatabase(entityframe_database_t *d) { memset(d, 0, sizeof(*d)); } // (server and client) removes frames older than 'frame' from database void EntityFrame_AckFrame(entityframe_database_t *d, int frame) { int i; d->ackframenum = frame; for (i = 0;i < d->numframes && d->frames[i].framenum < frame;i++); // ignore outdated frame acks (out of order packets) if (i == 0) return; d->numframes -= i; // if some queue is left, slide it down to beginning of array if (d->numframes) memmove(&d->frames[0], &d->frames[i], sizeof(d->frames[0]) * d->numframes); } // (server) clears frame, to prepare for adding entities void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum) { f->time = 0; f->framenum = framenum; f->numentities = 0; if (eye == NULL) VectorClear(f->eye); else VectorCopy(eye, f->eye); } // (server and client) reads a frame from the database void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f) { int i, n; EntityFrame_Clear(f, NULL, -1); for (i = 0;i < d->numframes && d->frames[i].framenum < framenum;i++); if (i < d->numframes && framenum == d->frames[i].framenum) { f->framenum = framenum; f->numentities = d->frames[i].endentity - d->frames[i].firstentity; n = MAX_ENTITY_DATABASE - (d->frames[i].firstentity % MAX_ENTITY_DATABASE); if (n > f->numentities) n = f->numentities; memcpy(f->entitydata, d->entitydata + d->frames[i].firstentity % MAX_ENTITY_DATABASE, sizeof(*f->entitydata) * n); if (f->numentities > n) memcpy(f->entitydata + n, d->entitydata, sizeof(*f->entitydata) * (f->numentities - n)); VectorCopy(d->eye, f->eye); } } // (client) adds a entity_frame to the database, for future reference void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata) { int n, e; entity_frameinfo_t *info; VectorCopy(eye, d->eye); // figure out how many entity slots are used already if (d->numframes) { n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) { // ran out of room, dump database EntityFrame_ClearDatabase(d); } } info = &d->frames[d->numframes]; info->framenum = framenum; e = -1000; // make sure we check the newly added frame as well, but we haven't incremented numframes yet for (n = 0;n <= d->numframes;n++) { if (e >= d->frames[n].framenum) { if (e == framenum) Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); else Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); return; } e = d->frames[n].framenum; } // if database still has frames after that... if (d->numframes) info->firstentity = d->frames[d->numframes - 1].endentity; else info->firstentity = 0; info->endentity = info->firstentity + numentities; d->numframes++; n = info->firstentity % MAX_ENTITY_DATABASE; e = MAX_ENTITY_DATABASE - n; if (e > numentities) e = numentities; memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); if (numentities > e) memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); } // (server) adds a entity_frame to the database, for future reference void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata) { int n, e; entity_frameinfo_t *info; VectorCopy(eye, d->eye); // figure out how many entity slots are used already if (d->numframes) { n = d->frames[d->numframes - 1].endentity - d->frames[0].firstentity; if (n + numentities > MAX_ENTITY_DATABASE || d->numframes >= MAX_ENTITY_HISTORY) { // ran out of room, dump database EntityFrame_ClearDatabase(d); } } info = &d->frames[d->numframes]; info->framenum = framenum; e = -1000; // make sure we check the newly added frame as well, but we haven't incremented numframes yet for (n = 0;n <= d->numframes;n++) { if (e >= d->frames[n].framenum) { if (e == framenum) Con_Print("EntityFrame_AddFrame: tried to add out of sequence frame to database\n"); else Con_Print("EntityFrame_AddFrame: out of sequence frames in database\n"); return; } e = d->frames[n].framenum; } // if database still has frames after that... if (d->numframes) info->firstentity = d->frames[d->numframes - 1].endentity; else info->firstentity = 0; info->endentity = info->firstentity + numentities; d->numframes++; n = info->firstentity % MAX_ENTITY_DATABASE; e = MAX_ENTITY_DATABASE - n; if (e > numentities) e = numentities; memcpy(d->entitydata + n, entitydata, sizeof(entity_state_t) * e); if (numentities > e) memcpy(d->entitydata, entitydata + e, sizeof(entity_state_t) * (numentities - e)); } // (server) writes a frame to network stream qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum) { prvm_prog_t *prog = SVVM_prog; int i, onum, number; entity_frame_t *o = &d->deltaframe; const entity_state_t *ent, *delta; vec3_t eye; d->latestframenum++; VectorClear(eye); for (i = 0;i < numstates;i++) { ent = states[i]; if (ent->number == viewentnum) { VectorSet(eye, ent->origin[0], ent->origin[1], ent->origin[2] + 22); break; } } EntityFrame_AddFrame_Server(d, eye, d->latestframenum, numstates, states); EntityFrame_FetchFrame(d, d->ackframenum, o); MSG_WriteByte (msg, svc_entities); MSG_WriteLong (msg, o->framenum); MSG_WriteLong (msg, d->latestframenum); MSG_WriteFloat (msg, eye[0]); MSG_WriteFloat (msg, eye[1]); MSG_WriteFloat (msg, eye[2]); onum = 0; for (i = 0;i < numstates;i++) { ent = states[i]; number = ent->number; if (PRVM_serveredictfunction((&prog->edicts[number]), SendEntity)) continue; for (;onum < o->numentities && o->entitydata[onum].number < number;onum++) { // write remove message MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); } if (onum < o->numentities && (o->entitydata[onum].number == number)) { // delta from previous frame delta = o->entitydata + onum; // advance to next entity in delta frame onum++; } else { // delta from defaults delta = &defaultstate; } EntityState_WriteUpdate(ent, msg, delta); } for (;onum < o->numentities;onum++) { // write remove message MSG_WriteShort(msg, o->entitydata[onum].number | 0x8000); } MSG_WriteShort(msg, 0xFFFF); return true; } // (client) reads a frame from network stream void EntityFrame_CL_ReadFrame(void) { int i, number, removed; entity_frame_t *f, *delta; entity_state_t *e, *old, *oldend; entity_t *ent; entityframe_database_t *d; if (!cl.entitydatabase) cl.entitydatabase = EntityFrame_AllocDatabase(cls.levelmempool); d = cl.entitydatabase; f = &d->framedata; delta = &d->deltaframe; EntityFrame_Clear(f, NULL, -1); // read the frame header info f->time = cl.mtime[0]; number = MSG_ReadLong(&cl_message); f->framenum = MSG_ReadLong(&cl_message); CL_NewFrameReceived(f->framenum); f->eye[0] = MSG_ReadFloat(&cl_message); f->eye[1] = MSG_ReadFloat(&cl_message); f->eye[2] = MSG_ReadFloat(&cl_message); EntityFrame_AckFrame(d, number); EntityFrame_FetchFrame(d, number, delta); old = delta->entitydata; oldend = old + delta->numentities; // read entities until we hit the magic 0xFFFF end tag while ((number = (unsigned short) MSG_ReadShort(&cl_message)) != 0xFFFF && !cl_message.badread) { if (cl_message.badread) Host_Error("EntityFrame_Read: read error"); removed = number & 0x8000; number &= 0x7FFF; if (number >= MAX_EDICTS) Host_Error("EntityFrame_Read: number (%i) >= MAX_EDICTS (%i)", number, MAX_EDICTS); // seek to entity, while copying any skipped entities (assume unchanged) while (old < oldend && old->number < number) { if (f->numentities >= MAX_ENTITY_DATABASE) Host_Error("EntityFrame_Read: entity list too big"); f->entitydata[f->numentities] = *old++; f->entitydata[f->numentities++].time = cl.mtime[0]; } if (removed) { if (old < oldend && old->number == number) old++; else Con_Printf("EntityFrame_Read: REMOVE on unused entity %i\n", number); } else { if (f->numentities >= MAX_ENTITY_DATABASE) Host_Error("EntityFrame_Read: entity list too big"); // reserve this slot e = f->entitydata + f->numentities++; if (old < oldend && old->number == number) { // delta from old entity *e = *old++; } else { // delta from defaults *e = defaultstate; } if (cl.num_entities <= number) { cl.num_entities = number + 1; if (number >= cl.max_entities) CL_ExpandEntities(number); } cl.entities_active[number] = true; e->active = ACTIVE_NETWORK; e->time = cl.mtime[0]; e->number = number; EntityState_ReadFields(e, EntityState_ReadExtendBits()); } } while (old < oldend) { if (f->numentities >= MAX_ENTITY_DATABASE) Host_Error("EntityFrame_Read: entity list too big"); f->entitydata[f->numentities] = *old++; f->entitydata[f->numentities++].time = cl.mtime[0]; } EntityFrame_AddFrame_Client(d, f->eye, f->framenum, f->numentities, f->entitydata); memset(cl.entities_active, 0, cl.num_entities * sizeof(unsigned char)); number = 1; for (i = 0;i < f->numentities;i++) { for (;number < f->entitydata[i].number && number < cl.num_entities;number++) { if (cl.entities_active[number]) { cl.entities_active[number] = false; cl.entities[number].state_current.active = ACTIVE_NOT; } } if (number >= cl.num_entities) break; // update the entity ent = &cl.entities[number]; ent->state_previous = ent->state_current; ent->state_current = f->entitydata[i]; CL_MoveLerpEntityStates(ent); // the entity lives again... cl.entities_active[number] = true; number++; } for (;number < cl.num_entities;number++) { if (cl.entities_active[number]) { cl.entities_active[number] = false; cl.entities[number].state_current.active = ACTIVE_NOT; } } } // (client) returns the frame number of the most recent frame recieved int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d) { if (d->numframes) return d->frames[d->numframes - 1].framenum; else return -1; } entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number) { if (d->maxreferenceentities <= number) { int oldmax = d->maxreferenceentities; entity_state_t *oldentity = d->referenceentity; d->maxreferenceentities = (number + 15) & ~7; d->referenceentity = (entity_state_t *)Mem_Alloc(d->mempool, d->maxreferenceentities * sizeof(*d->referenceentity)); if (oldentity) { memcpy(d->referenceentity, oldentity, oldmax * sizeof(*d->referenceentity)); Mem_Free(oldentity); } // clear the newly created entities for (;oldmax < d->maxreferenceentities;oldmax++) { d->referenceentity[oldmax] = defaultstate; d->referenceentity[oldmax].number = oldmax; } } return d->referenceentity + number; } void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s) { // resize commit's entity list if full if (d->currentcommit->maxentities <= d->currentcommit->numentities) { entity_state_t *oldentity = d->currentcommit->entity; d->currentcommit->maxentities += 8; d->currentcommit->entity = (entity_state_t *)Mem_Alloc(d->mempool, d->currentcommit->maxentities * sizeof(*d->currentcommit->entity)); if (oldentity) { memcpy(d->currentcommit->entity, oldentity, d->currentcommit->numentities * sizeof(*d->currentcommit->entity)); Mem_Free(oldentity); } } d->currentcommit->entity[d->currentcommit->numentities++] = *s; } entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool) { entityframe4_database_t *d; d = (entityframe4_database_t *)Mem_Alloc(pool, sizeof(*d)); d->mempool = pool; EntityFrame4_ResetDatabase(d); return d; } void EntityFrame4_FreeDatabase(entityframe4_database_t *d) { int i; for (i = 0;i < MAX_ENTITY_HISTORY;i++) if (d->commit[i].entity) Mem_Free(d->commit[i].entity); if (d->referenceentity) Mem_Free(d->referenceentity); Mem_Free(d); } void EntityFrame4_ResetDatabase(entityframe4_database_t *d) { int i; d->referenceframenum = -1; for (i = 0;i < MAX_ENTITY_HISTORY;i++) d->commit[i].numentities = 0; for (i = 0;i < d->maxreferenceentities;i++) d->referenceentity[i] = defaultstate; } int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode) { int i, j, found; entity_database4_commit_t *commit; if (framenum == -1) { // reset reference, but leave commits alone d->referenceframenum = -1; for (i = 0;i < d->maxreferenceentities;i++) d->referenceentity[i] = defaultstate; // if this is the server, remove commits for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) commit->numentities = 0; found = true; } else if (d->referenceframenum == framenum) found = true; else { found = false; for (i = 0, commit = d->commit;i < MAX_ENTITY_HISTORY;i++, commit++) { if (commit->numentities && commit->framenum <= framenum) { if (commit->framenum == framenum) { found = true; d->referenceframenum = framenum; if (developer_networkentities.integer >= 3) { for (j = 0;j < commit->numentities;j++) { entity_state_t *s = EntityFrame4_GetReferenceEntity(d, commit->entity[j].number); if (commit->entity[j].active != s->active) { if (commit->entity[j].active == ACTIVE_NETWORK) Con_Printf("commit entity %i has become active (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); else Con_Printf("commit entity %i has become inactive (modelindex %i)\n", commit->entity[j].number, commit->entity[j].modelindex); } *s = commit->entity[j]; } } else for (j = 0;j < commit->numentities;j++) *EntityFrame4_GetReferenceEntity(d, commit->entity[j].number) = commit->entity[j]; } commit->numentities = 0; } } } if (developer_networkentities.integer >= 1) { Con_Printf("ack ref:%i database updated to: ref:%i commits:", framenum, d->referenceframenum); for (i = 0;i < MAX_ENTITY_HISTORY;i++) if (d->commit[i].numentities) Con_Printf(" %i", d->commit[i].framenum); Con_Print("\n"); } return found; } void EntityFrame4_CL_ReadFrame(void) { int i, n, cnumber, referenceframenum, framenum, enumber, done, stopnumber, skip = false; entity_state_t *s; entityframe4_database_t *d; if (!cl.entitydatabase4) cl.entitydatabase4 = EntityFrame4_AllocDatabase(cls.levelmempool); d = cl.entitydatabase4; // read the number of the frame this refers to referenceframenum = MSG_ReadLong(&cl_message); // read the number of this frame framenum = MSG_ReadLong(&cl_message); CL_NewFrameReceived(framenum); // read the start number enumber = (unsigned short) MSG_ReadShort(&cl_message); if (developer_networkentities.integer >= 10) { Con_Printf("recv svc_entities num:%i ref:%i database: ref:%i commits:", framenum, referenceframenum, d->referenceframenum); for (i = 0;i < MAX_ENTITY_HISTORY;i++) if (d->commit[i].numentities) Con_Printf(" %i", d->commit[i].framenum); Con_Print("\n"); } if (!EntityFrame4_AckFrame(d, referenceframenum, false)) { Con_Print("EntityFrame4_CL_ReadFrame: reference frame invalid (VERY BAD ERROR), this update will be skipped\n"); skip = true; } d->currentcommit = NULL; for (i = 0;i < MAX_ENTITY_HISTORY;i++) { if (!d->commit[i].numentities) { d->currentcommit = d->commit + i; d->currentcommit->framenum = framenum; d->currentcommit->numentities = 0; } } if (d->currentcommit == NULL) { Con_Printf("EntityFrame4_CL_ReadFrame: error while decoding frame %i: database full, reading but not storing this update\n", framenum); skip = true; } done = false; while (!done && !cl_message.badread) { // read the number of the modified entity // (gaps will be copied unmodified) n = (unsigned short)MSG_ReadShort(&cl_message); if (n == 0x8000) { // no more entities in this update, but we still need to copy the // rest of the reference entities (final gap) done = true; // read end of range number, then process normally n = (unsigned short)MSG_ReadShort(&cl_message); } // high bit means it's a remove message cnumber = n & 0x7FFF; // if this is a live entity we may need to expand the array if (cl.num_entities <= cnumber && !(n & 0x8000)) { cl.num_entities = cnumber + 1; if (cnumber >= cl.max_entities) CL_ExpandEntities(cnumber); } // add one (the changed one) if not done stopnumber = cnumber + !done; // process entities in range from the last one to the changed one for (;enumber < stopnumber;enumber++) { if (skip || enumber >= cl.num_entities) { if (enumber == cnumber && (n & 0x8000) == 0) { entity_state_t tempstate; EntityState_ReadFields(&tempstate, EntityState_ReadExtendBits()); } continue; } // slide the current into the previous slot cl.entities[enumber].state_previous = cl.entities[enumber].state_current; // copy a new current from reference database cl.entities[enumber].state_current = *EntityFrame4_GetReferenceEntity(d, enumber); s = &cl.entities[enumber].state_current; // if this is the one to modify, read more data... if (enumber == cnumber) { if (n & 0x8000) { // simply removed if (developer_networkentities.integer >= 2) Con_Printf("entity %i: remove\n", enumber); *s = defaultstate; } else { // read the changes if (developer_networkentities.integer >= 2) Con_Printf("entity %i: update\n", enumber); s->active = ACTIVE_NETWORK; EntityState_ReadFields(s, EntityState_ReadExtendBits()); } } else if (developer_networkentities.integer >= 4) Con_Printf("entity %i: copy\n", enumber); // set the cl.entities_active flag cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); // set the update time s->time = cl.mtime[0]; // fix the number (it gets wiped occasionally by copying from defaultstate) s->number = enumber; // check if we need to update the lerp stuff if (s->active == ACTIVE_NETWORK) CL_MoveLerpEntityStates(&cl.entities[enumber]); // add this to the commit entry whether it is modified or not if (d->currentcommit) EntityFrame4_AddCommitEntity(d, &cl.entities[enumber].state_current); // print extra messages if desired if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) { if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) Con_Printf("entity #%i has become active\n", enumber); else if (cl.entities[enumber].state_previous.active) Con_Printf("entity #%i has become inactive\n", enumber); } } } d->currentcommit = NULL; if (skip) EntityFrame4_ResetDatabase(d); } qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states) { prvm_prog_t *prog = SVVM_prog; const entity_state_t *e, *s; entity_state_t inactiveentitystate; int i, n, startnumber; sizebuf_t buf; unsigned char data[128]; // if there isn't enough space to accomplish anything, skip it if (msg->cursize + 24 > maxsize) return false; // prepare the buffer memset(&buf, 0, sizeof(buf)); buf.data = data; buf.maxsize = sizeof(data); for (i = 0;i < MAX_ENTITY_HISTORY;i++) if (!d->commit[i].numentities) break; // if commit buffer full, just don't bother writing an update this frame if (i == MAX_ENTITY_HISTORY) return false; d->currentcommit = d->commit + i; // this state's number gets played around with later inactiveentitystate = defaultstate; d->currentcommit->numentities = 0; d->currentcommit->framenum = ++d->latestframenumber; MSG_WriteByte(msg, svc_entities); MSG_WriteLong(msg, d->referenceframenum); MSG_WriteLong(msg, d->currentcommit->framenum); if (developer_networkentities.integer >= 10) { Con_Printf("send svc_entities num:%i ref:%i (database: ref:%i commits:", d->currentcommit->framenum, d->referenceframenum, d->referenceframenum); for (i = 0;i < MAX_ENTITY_HISTORY;i++) if (d->commit[i].numentities) Con_Printf(" %i", d->commit[i].framenum); Con_Print(")\n"); } if (d->currententitynumber >= prog->max_edicts) startnumber = 1; else startnumber = bound(1, d->currententitynumber, prog->max_edicts - 1); MSG_WriteShort(msg, startnumber); // reset currententitynumber so if the loop does not break it we will // start at beginning next frame (if it does break, it will set it) d->currententitynumber = 1; for (i = 0, n = startnumber;n < prog->max_edicts;n++) { if (PRVM_serveredictfunction((&prog->edicts[n]), SendEntity)) continue; // find the old state to delta from e = EntityFrame4_GetReferenceEntity(d, n); // prepare the buffer SZ_Clear(&buf); // entity exists, build an update (if empty there is no change) // find the state in the list for (;i < numstates && states[i]->number < n;i++); // make the message s = states[i]; if (s->number == n) { // build the update EntityState_WriteUpdate(s, &buf, e); } else { inactiveentitystate.number = n; s = &inactiveentitystate; if (e->active == ACTIVE_NETWORK) { // entity used to exist but doesn't anymore, send remove MSG_WriteShort(&buf, n | 0x8000); } } // if the commit is full, we're done this frame if (msg->cursize + buf.cursize > maxsize - 4) { // next frame we will continue where we left off break; } // add the entity to the commit EntityFrame4_AddCommitEntity(d, s); // if the message is empty, skip out now if (buf.cursize) { // write the message to the packet SZ_Write(msg, buf.data, buf.cursize); } } d->currententitynumber = n; // remove world message (invalid, and thus a good terminator) MSG_WriteShort(msg, 0x8000); // write the number of the end entity MSG_WriteShort(msg, d->currententitynumber); // just to be sure d->currentcommit = NULL; return true; } entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool) { int i; entityframe5_database_t *d; d = (entityframe5_database_t *)Mem_Alloc(pool, sizeof(*d)); d->latestframenum = 0; for (i = 0;i < d->maxedicts;i++) d->states[i] = defaultstate; return d; } void EntityFrame5_FreeDatabase(entityframe5_database_t *d) { // all the [maxedicts] memory is allocated at once, so there's only one // thing to free if (d->maxedicts) Mem_Free(d->deltabits); Mem_Free(d); } static void EntityFrame5_ExpandEdicts(entityframe5_database_t *d, int newmax) { if (d->maxedicts < newmax) { unsigned char *data; int oldmaxedicts = d->maxedicts; int *olddeltabits = d->deltabits; unsigned char *oldpriorities = d->priorities; int *oldupdateframenum = d->updateframenum; entity_state_t *oldstates = d->states; unsigned char *oldvisiblebits = d->visiblebits; d->maxedicts = newmax; data = (unsigned char *)Mem_Alloc(sv_mempool, d->maxedicts * sizeof(int) + d->maxedicts * sizeof(unsigned char) + d->maxedicts * sizeof(int) + d->maxedicts * sizeof(entity_state_t) + (d->maxedicts+7)/8 * sizeof(unsigned char)); d->deltabits = (int *)data;data += d->maxedicts * sizeof(int); d->priorities = (unsigned char *)data;data += d->maxedicts * sizeof(unsigned char); d->updateframenum = (int *)data;data += d->maxedicts * sizeof(int); d->states = (entity_state_t *)data;data += d->maxedicts * sizeof(entity_state_t); d->visiblebits = (unsigned char *)data;data += (d->maxedicts+7)/8 * sizeof(unsigned char); if (oldmaxedicts) { memcpy(d->deltabits, olddeltabits, oldmaxedicts * sizeof(int)); memcpy(d->priorities, oldpriorities, oldmaxedicts * sizeof(unsigned char)); memcpy(d->updateframenum, oldupdateframenum, oldmaxedicts * sizeof(int)); memcpy(d->states, oldstates, oldmaxedicts * sizeof(entity_state_t)); memcpy(d->visiblebits, oldvisiblebits, (oldmaxedicts+7)/8 * sizeof(unsigned char)); // the previous buffers were a single allocation, so just one free Mem_Free(olddeltabits); } } } static int EntityState5_Priority(entityframe5_database_t *d, int stateindex) { int limit, priority; entity_state_t *s = NULL; // hush compiler warning by initializing this // if it is the player, update urgently if (stateindex == d->viewentnum) return ENTITYFRAME5_PRIORITYLEVELS - 1; // priority increases each frame no matter what happens priority = d->priorities[stateindex] + 1; // players get an extra priority boost if (stateindex <= svs.maxclients) priority++; // remove dead entities very quickly because they are just 2 bytes if (d->states[stateindex].active != ACTIVE_NETWORK) { priority++; return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); } // certain changes are more noticable than others if (d->deltabits[stateindex] & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_FLAGS | E5_COLORMAP)) priority++; // find the root entity this one is attached to, and judge relevance by it for (limit = 0;limit < 256;limit++) { s = d->states + stateindex; if (s->flags & RENDER_VIEWMODEL) stateindex = d->viewentnum; else if (s->tagentity) stateindex = s->tagentity; else break; if (d->maxedicts < stateindex) EntityFrame5_ExpandEdicts(d, (stateindex+256)&~255); } if (limit >= 256) Con_DPrintf("Protocol: Runaway loop recursing tagentity links on entity %i\n", stateindex); // now that we have the parent entity we can make some decisions based on // distance from the player if (VectorDistance(d->states[d->viewentnum].netcenter, s->netcenter) < 1024.0f) priority++; return bound(1, priority, ENTITYFRAME5_PRIORITYLEVELS - 1); } static double anim_reducetime(double t, double frameduration, double maxtime) { if(t < 0) // clamp to non-negative return 0; if(t <= maxtime) // time can be represented normally return t; if(frameduration == 0) // don't like dividing by zero return t; if(maxtime <= 2 * frameduration) // if two frames don't fit, we better not do this return t; t -= frameduration * ceil((t - maxtime) / frameduration); // now maxtime - frameduration < t <= maxtime return t; } // see VM_SV_frameduration static double anim_frameduration(dp_model_t *model, int framenum) { if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes) return 0; if(model->animscenes[framenum].framerate) return model->animscenes[framenum].framecount / model->animscenes[framenum].framerate; return 0; } void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg) { prvm_prog_t *prog = SVVM_prog; unsigned int bits = 0; //dp_model_t *model; if (s->active != ACTIVE_NETWORK) { ENTITYSIZEPROFILING_START(msg, s->number, 0); MSG_WriteShort(msg, number | 0x8000); ENTITYSIZEPROFILING_END(msg, s->number, 0); } else { if (PRVM_serveredictfunction((&prog->edicts[s->number]), SendEntity)) return; bits = changedbits; if ((bits & E5_ORIGIN) && (!(s->flags & RENDER_LOWPRECISION) || s->exteriormodelforclient || s->tagentity || s->viewmodelforclient || (s->number >= 1 && s->number <= svs.maxclients) || s->origin[0] <= -4096.0625 || s->origin[0] >= 4095.9375 || s->origin[1] <= -4096.0625 || s->origin[1] >= 4095.9375 || s->origin[2] <= -4096.0625 || s->origin[2] >= 4095.9375)) // maybe also add: ((model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*') bits |= E5_ORIGIN32; // possible values: // negative origin: // (int)(f * 8 - 0.5) >= -32768 // (f * 8 - 0.5) > -32769 // f > -4096.0625 // positive origin: // (int)(f * 8 + 0.5) <= 32767 // (f * 8 + 0.5) < 32768 // f * 8 + 0.5) < 4095.9375 if ((bits & E5_ANGLES) && !(s->flags & RENDER_LOWPRECISION)) bits |= E5_ANGLES16; if ((bits & E5_MODEL) && s->modelindex >= 256) bits |= E5_MODEL16; if ((bits & E5_FRAME) && s->frame >= 256) bits |= E5_FRAME16; if (bits & E5_EFFECTS) { if (s->effects & 0xFFFF0000) bits |= E5_EFFECTS32; else if (s->effects & 0xFFFFFF00) bits |= E5_EFFECTS16; } if (bits >= 256) bits |= E5_EXTEND1; if (bits >= 65536) bits |= E5_EXTEND2; if (bits >= 16777216) bits |= E5_EXTEND3; { ENTITYSIZEPROFILING_START(msg, s->number, bits); MSG_WriteShort(msg, number); MSG_WriteByte(msg, bits & 0xFF); if (bits & E5_EXTEND1) MSG_WriteByte(msg, (bits >> 8) & 0xFF); if (bits & E5_EXTEND2) MSG_WriteByte(msg, (bits >> 16) & 0xFF); if (bits & E5_EXTEND3) MSG_WriteByte(msg, (bits >> 24) & 0xFF); if (bits & E5_FLAGS) MSG_WriteByte(msg, s->flags); if (bits & E5_ORIGIN) { if (bits & E5_ORIGIN32) { MSG_WriteCoord32f(msg, s->origin[0]); MSG_WriteCoord32f(msg, s->origin[1]); MSG_WriteCoord32f(msg, s->origin[2]); } else { MSG_WriteCoord13i(msg, s->origin[0]); MSG_WriteCoord13i(msg, s->origin[1]); MSG_WriteCoord13i(msg, s->origin[2]); } } if (bits & E5_ANGLES) { if (bits & E5_ANGLES16) { MSG_WriteAngle16i(msg, s->angles[0]); MSG_WriteAngle16i(msg, s->angles[1]); MSG_WriteAngle16i(msg, s->angles[2]); } else { MSG_WriteAngle8i(msg, s->angles[0]); MSG_WriteAngle8i(msg, s->angles[1]); MSG_WriteAngle8i(msg, s->angles[2]); } } if (bits & E5_MODEL) { if (bits & E5_MODEL16) MSG_WriteShort(msg, s->modelindex); else MSG_WriteByte(msg, s->modelindex); } if (bits & E5_FRAME) { if (bits & E5_FRAME16) MSG_WriteShort(msg, s->frame); else MSG_WriteByte(msg, s->frame); } if (bits & E5_SKIN) MSG_WriteByte(msg, s->skin); if (bits & E5_EFFECTS) { if (bits & E5_EFFECTS32) MSG_WriteLong(msg, s->effects); else if (bits & E5_EFFECTS16) MSG_WriteShort(msg, s->effects); else MSG_WriteByte(msg, s->effects); } if (bits & E5_ALPHA) MSG_WriteByte(msg, s->alpha); if (bits & E5_SCALE) MSG_WriteByte(msg, s->scale); if (bits & E5_COLORMAP) MSG_WriteByte(msg, s->colormap); if (bits & E5_ATTACHMENT) { MSG_WriteShort(msg, s->tagentity); MSG_WriteByte(msg, s->tagindex); } if (bits & E5_LIGHT) { MSG_WriteShort(msg, s->light[0]); MSG_WriteShort(msg, s->light[1]); MSG_WriteShort(msg, s->light[2]); MSG_WriteShort(msg, s->light[3]); MSG_WriteByte(msg, s->lightstyle); MSG_WriteByte(msg, s->lightpflags); } if (bits & E5_GLOW) { MSG_WriteByte(msg, s->glowsize); MSG_WriteByte(msg, s->glowcolor); } if (bits & E5_COLORMOD) { MSG_WriteByte(msg, s->colormod[0]); MSG_WriteByte(msg, s->colormod[1]); MSG_WriteByte(msg, s->colormod[2]); } if (bits & E5_GLOWMOD) { MSG_WriteByte(msg, s->glowmod[0]); MSG_WriteByte(msg, s->glowmod[1]); MSG_WriteByte(msg, s->glowmod[2]); } if (bits & E5_COMPLEXANIMATION) { if (s->skeletonobject.model && s->skeletonobject.relativetransforms) { int numbones = s->skeletonobject.model->num_bones; int bonenum; short pose7s[7]; MSG_WriteByte(msg, 4); MSG_WriteShort(msg, s->modelindex); MSG_WriteByte(msg, numbones); for (bonenum = 0;bonenum < numbones;bonenum++) { Matrix4x4_ToBonePose7s(s->skeletonobject.relativetransforms + bonenum, 64, pose7s); MSG_WriteShort(msg, pose7s[0]); MSG_WriteShort(msg, pose7s[1]); MSG_WriteShort(msg, pose7s[2]); MSG_WriteShort(msg, pose7s[3]); MSG_WriteShort(msg, pose7s[4]); MSG_WriteShort(msg, pose7s[5]); MSG_WriteShort(msg, pose7s[6]); } } else { dp_model_t *model = SV_GetModelByIndex(s->modelindex); if (s->framegroupblend[3].lerp > 0) { MSG_WriteByte(msg, 3); MSG_WriteShort(msg, s->framegroupblend[0].frame); MSG_WriteShort(msg, s->framegroupblend[1].frame); MSG_WriteShort(msg, s->framegroupblend[2].frame); MSG_WriteShort(msg, s->framegroupblend[3].frame); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[2].start, anim_frameduration(model, s->framegroupblend[2].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[3].start, anim_frameduration(model, s->framegroupblend[3].frame), 65.535) * 1000.0)); MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[3].lerp * 255.0f); } else if (s->framegroupblend[2].lerp > 0) { MSG_WriteByte(msg, 2); MSG_WriteShort(msg, s->framegroupblend[0].frame); MSG_WriteShort(msg, s->framegroupblend[1].frame); MSG_WriteShort(msg, s->framegroupblend[2].frame); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[2].start, anim_frameduration(model, s->framegroupblend[2].frame), 65.535) * 1000.0)); MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[2].lerp * 255.0f); } else if (s->framegroupblend[1].lerp > 0) { MSG_WriteByte(msg, 1); MSG_WriteShort(msg, s->framegroupblend[0].frame); MSG_WriteShort(msg, s->framegroupblend[1].frame); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[1].start, anim_frameduration(model, s->framegroupblend[1].frame), 65.535) * 1000.0)); MSG_WriteByte(msg, s->framegroupblend[0].lerp * 255.0f); MSG_WriteByte(msg, s->framegroupblend[1].lerp * 255.0f); } else { MSG_WriteByte(msg, 0); MSG_WriteShort(msg, s->framegroupblend[0].frame); MSG_WriteShort(msg, (int)(anim_reducetime(sv.time - s->framegroupblend[0].start, anim_frameduration(model, s->framegroupblend[0].frame), 65.535) * 1000.0)); } } } if (bits & E5_TRAILEFFECTNUM) MSG_WriteShort(msg, s->traileffectnum); ENTITYSIZEPROFILING_END(msg, s->number, bits); } } } static void EntityState5_ReadUpdate(entity_state_t *s, int number) { int bits; int startoffset = cl_message.readcount; int bytes = 0; bits = MSG_ReadByte(&cl_message); if (bits & E5_EXTEND1) { bits |= MSG_ReadByte(&cl_message) << 8; if (bits & E5_EXTEND2) { bits |= MSG_ReadByte(&cl_message) << 16; if (bits & E5_EXTEND3) bits |= MSG_ReadByte(&cl_message) << 24; } } if (bits & E5_FULLUPDATE) { *s = defaultstate; s->active = ACTIVE_NETWORK; } if (bits & E5_FLAGS) s->flags = MSG_ReadByte(&cl_message); if (bits & E5_ORIGIN) { if (bits & E5_ORIGIN32) { s->origin[0] = MSG_ReadCoord32f(&cl_message); s->origin[1] = MSG_ReadCoord32f(&cl_message); s->origin[2] = MSG_ReadCoord32f(&cl_message); } else { s->origin[0] = MSG_ReadCoord13i(&cl_message); s->origin[1] = MSG_ReadCoord13i(&cl_message); s->origin[2] = MSG_ReadCoord13i(&cl_message); } } if (bits & E5_ANGLES) { if (bits & E5_ANGLES16) { s->angles[0] = MSG_ReadAngle16i(&cl_message); s->angles[1] = MSG_ReadAngle16i(&cl_message); s->angles[2] = MSG_ReadAngle16i(&cl_message); } else { s->angles[0] = MSG_ReadAngle8i(&cl_message); s->angles[1] = MSG_ReadAngle8i(&cl_message); s->angles[2] = MSG_ReadAngle8i(&cl_message); } } if (bits & E5_MODEL) { if (bits & E5_MODEL16) s->modelindex = (unsigned short) MSG_ReadShort(&cl_message); else s->modelindex = MSG_ReadByte(&cl_message); } if (bits & E5_FRAME) { if (bits & E5_FRAME16) s->frame = (unsigned short) MSG_ReadShort(&cl_message); else s->frame = MSG_ReadByte(&cl_message); } if (bits & E5_SKIN) s->skin = MSG_ReadByte(&cl_message); if (bits & E5_EFFECTS) { if (bits & E5_EFFECTS32) s->effects = (unsigned int) MSG_ReadLong(&cl_message); else if (bits & E5_EFFECTS16) s->effects = (unsigned short) MSG_ReadShort(&cl_message); else s->effects = MSG_ReadByte(&cl_message); } if (bits & E5_ALPHA) s->alpha = MSG_ReadByte(&cl_message); if (bits & E5_SCALE) s->scale = MSG_ReadByte(&cl_message); if (bits & E5_COLORMAP) s->colormap = MSG_ReadByte(&cl_message); if (bits & E5_ATTACHMENT) { s->tagentity = (unsigned short) MSG_ReadShort(&cl_message); s->tagindex = MSG_ReadByte(&cl_message); } if (bits & E5_LIGHT) { s->light[0] = (unsigned short) MSG_ReadShort(&cl_message); s->light[1] = (unsigned short) MSG_ReadShort(&cl_message); s->light[2] = (unsigned short) MSG_ReadShort(&cl_message); s->light[3] = (unsigned short) MSG_ReadShort(&cl_message); s->lightstyle = MSG_ReadByte(&cl_message); s->lightpflags = MSG_ReadByte(&cl_message); } if (bits & E5_GLOW) { s->glowsize = MSG_ReadByte(&cl_message); s->glowcolor = MSG_ReadByte(&cl_message); } if (bits & E5_COLORMOD) { s->colormod[0] = MSG_ReadByte(&cl_message); s->colormod[1] = MSG_ReadByte(&cl_message); s->colormod[2] = MSG_ReadByte(&cl_message); } if (bits & E5_GLOWMOD) { s->glowmod[0] = MSG_ReadByte(&cl_message); s->glowmod[1] = MSG_ReadByte(&cl_message); s->glowmod[2] = MSG_ReadByte(&cl_message); } if (bits & E5_COMPLEXANIMATION) { skeleton_t *skeleton; const dp_model_t *model; int modelindex; int type; int bonenum; int numbones; short pose7s[7]; type = MSG_ReadByte(&cl_message); switch(type) { case 0: s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); s->framegroupblend[1].frame = 0; s->framegroupblend[2].frame = 0; s->framegroupblend[3].frame = 0; s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[1].start = 0; s->framegroupblend[2].start = 0; s->framegroupblend[3].start = 0; s->framegroupblend[0].lerp = 1; s->framegroupblend[1].lerp = 0; s->framegroupblend[2].lerp = 0; s->framegroupblend[3].lerp = 0; break; case 1: s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); s->framegroupblend[2].frame = 0; s->framegroupblend[3].frame = 0; s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[2].start = 0; s->framegroupblend[3].start = 0; s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[2].lerp = 0; s->framegroupblend[3].lerp = 0; break; case 2: s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); s->framegroupblend[2].frame = MSG_ReadShort(&cl_message); s->framegroupblend[3].frame = 0; s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[2].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[3].start = 0; s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[2].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[3].lerp = 0; break; case 3: s->framegroupblend[0].frame = MSG_ReadShort(&cl_message); s->framegroupblend[1].frame = MSG_ReadShort(&cl_message); s->framegroupblend[2].frame = MSG_ReadShort(&cl_message); s->framegroupblend[3].frame = MSG_ReadShort(&cl_message); s->framegroupblend[0].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[1].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[2].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[3].start = cl.time - (unsigned short)MSG_ReadShort(&cl_message) * (1.0f / 1000.0f); s->framegroupblend[0].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[1].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[2].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); s->framegroupblend[3].lerp = MSG_ReadByte(&cl_message) * (1.0f / 255.0f); break; case 4: if (!cl.engineskeletonobjects) cl.engineskeletonobjects = (skeleton_t *) Mem_Alloc(cls.levelmempool, sizeof(*cl.engineskeletonobjects) * MAX_EDICTS); skeleton = &cl.engineskeletonobjects[number]; modelindex = MSG_ReadShort(&cl_message); model = CL_GetModelByIndex(modelindex); numbones = MSG_ReadByte(&cl_message); if (model && numbones != model->num_bones) Host_Error("E5_COMPLEXANIMATION: model has different number of bones than network packet describes\n"); if (!skeleton->relativetransforms || skeleton->model != model) { skeleton->model = model; skeleton->relativetransforms = (matrix4x4_t *) Mem_Realloc(cls.levelmempool, skeleton->relativetransforms, sizeof(*skeleton->relativetransforms) * numbones); for (bonenum = 0;bonenum < numbones;bonenum++) skeleton->relativetransforms[bonenum] = identitymatrix; } for (bonenum = 0;bonenum < numbones;bonenum++) { pose7s[0] = (short)MSG_ReadShort(&cl_message); pose7s[1] = (short)MSG_ReadShort(&cl_message); pose7s[2] = (short)MSG_ReadShort(&cl_message); pose7s[3] = (short)MSG_ReadShort(&cl_message); pose7s[4] = (short)MSG_ReadShort(&cl_message); pose7s[5] = (short)MSG_ReadShort(&cl_message); pose7s[6] = (short)MSG_ReadShort(&cl_message); Matrix4x4_FromBonePose7s(skeleton->relativetransforms + bonenum, 1.0f / 64.0f, pose7s); } s->skeletonobject = *skeleton; break; default: Host_Error("E5_COMPLEXANIMATION: Parse error - unknown type %i\n", type); break; } } if (bits & E5_TRAILEFFECTNUM) s->traileffectnum = (unsigned short) MSG_ReadShort(&cl_message); bytes = cl_message.readcount - startoffset; if (developer_networkentities.integer >= 2) { Con_Printf("ReadFields e%i (%i bytes)", number, bytes); if (bits & E5_ORIGIN) Con_Printf(" E5_ORIGIN %f %f %f", s->origin[0], s->origin[1], s->origin[2]); if (bits & E5_ANGLES) Con_Printf(" E5_ANGLES %f %f %f", s->angles[0], s->angles[1], s->angles[2]); if (bits & E5_MODEL) Con_Printf(" E5_MODEL %i", s->modelindex); if (bits & E5_FRAME) Con_Printf(" E5_FRAME %i", s->frame); if (bits & E5_SKIN) Con_Printf(" E5_SKIN %i", s->skin); if (bits & E5_EFFECTS) Con_Printf(" E5_EFFECTS %i", s->effects); if (bits & E5_FLAGS) { Con_Printf(" E5_FLAGS %i (", s->flags); if (s->flags & RENDER_STEP) Con_Print(" STEP"); if (s->flags & RENDER_GLOWTRAIL) Con_Print(" GLOWTRAIL"); if (s->flags & RENDER_VIEWMODEL) Con_Print(" VIEWMODEL"); if (s->flags & RENDER_EXTERIORMODEL) Con_Print(" EXTERIORMODEL"); if (s->flags & RENDER_LOWPRECISION) Con_Print(" LOWPRECISION"); if (s->flags & RENDER_COLORMAPPED) Con_Print(" COLORMAPPED"); if (s->flags & RENDER_SHADOW) Con_Print(" SHADOW"); if (s->flags & RENDER_LIGHT) Con_Print(" LIGHT"); if (s->flags & RENDER_NOSELFSHADOW) Con_Print(" NOSELFSHADOW"); Con_Print(")"); } if (bits & E5_ALPHA) Con_Printf(" E5_ALPHA %f", s->alpha / 255.0f); if (bits & E5_SCALE) Con_Printf(" E5_SCALE %f", s->scale / 16.0f); if (bits & E5_COLORMAP) Con_Printf(" E5_COLORMAP %i", s->colormap); if (bits & E5_ATTACHMENT) Con_Printf(" E5_ATTACHMENT e%i:%i", s->tagentity, s->tagindex); if (bits & E5_LIGHT) Con_Printf(" E5_LIGHT %i:%i:%i:%i %i:%i", s->light[0], s->light[1], s->light[2], s->light[3], s->lightstyle, s->lightpflags); if (bits & E5_GLOW) Con_Printf(" E5_GLOW %i:%i", s->glowsize * 4, s->glowcolor); if (bits & E5_COLORMOD) Con_Printf(" E5_COLORMOD %f:%f:%f", s->colormod[0] / 32.0f, s->colormod[1] / 32.0f, s->colormod[2] / 32.0f); if (bits & E5_GLOWMOD) Con_Printf(" E5_GLOWMOD %f:%f:%f", s->glowmod[0] / 32.0f, s->glowmod[1] / 32.0f, s->glowmod[2] / 32.0f); if (bits & E5_COMPLEXANIMATION) Con_Printf(" E5_COMPLEXANIMATION"); if (bits & E5_TRAILEFFECTNUM) Con_Printf(" E5_TRAILEFFECTNUM %i", s->traileffectnum); Con_Print("\n"); } } static int EntityState5_DeltaBits(const entity_state_t *o, const entity_state_t *n) { unsigned int bits = 0; if (n->active == ACTIVE_NETWORK) { if (o->active != ACTIVE_NETWORK) bits |= E5_FULLUPDATE; if (!VectorCompare(o->origin, n->origin)) bits |= E5_ORIGIN; if (!VectorCompare(o->angles, n->angles)) bits |= E5_ANGLES; if (o->modelindex != n->modelindex) bits |= E5_MODEL; if (o->frame != n->frame) bits |= E5_FRAME; if (o->skin != n->skin) bits |= E5_SKIN; if (o->effects != n->effects) bits |= E5_EFFECTS; if (o->flags != n->flags) bits |= E5_FLAGS; if (o->alpha != n->alpha) bits |= E5_ALPHA; if (o->scale != n->scale) bits |= E5_SCALE; if (o->colormap != n->colormap) bits |= E5_COLORMAP; if (o->tagentity != n->tagentity || o->tagindex != n->tagindex) bits |= E5_ATTACHMENT; if (o->light[0] != n->light[0] || o->light[1] != n->light[1] || o->light[2] != n->light[2] || o->light[3] != n->light[3] || o->lightstyle != n->lightstyle || o->lightpflags != n->lightpflags) bits |= E5_LIGHT; if (o->glowsize != n->glowsize || o->glowcolor != n->glowcolor) bits |= E5_GLOW; if (o->colormod[0] != n->colormod[0] || o->colormod[1] != n->colormod[1] || o->colormod[2] != n->colormod[2]) bits |= E5_COLORMOD; if (o->glowmod[0] != n->glowmod[0] || o->glowmod[1] != n->glowmod[1] || o->glowmod[2] != n->glowmod[2]) bits |= E5_GLOWMOD; if (n->flags & RENDER_COMPLEXANIMATION) { if ((o->skeletonobject.model && o->skeletonobject.relativetransforms) != (n->skeletonobject.model && n->skeletonobject.relativetransforms)) { bits |= E5_COMPLEXANIMATION; } else if (o->skeletonobject.model && o->skeletonobject.relativetransforms) { if(o->modelindex != n->modelindex) bits |= E5_COMPLEXANIMATION; else if(o->skeletonobject.model->num_bones != n->skeletonobject.model->num_bones) bits |= E5_COMPLEXANIMATION; else if(memcmp(o->skeletonobject.relativetransforms, n->skeletonobject.relativetransforms, o->skeletonobject.model->num_bones * sizeof(*o->skeletonobject.relativetransforms))) bits |= E5_COMPLEXANIMATION; } else if (memcmp(o->framegroupblend, n->framegroupblend, sizeof(o->framegroupblend))) { bits |= E5_COMPLEXANIMATION; } } if (o->traileffectnum != n->traileffectnum) bits |= E5_TRAILEFFECTNUM; } else if (o->active == ACTIVE_NETWORK) bits |= E5_FULLUPDATE; return bits; } void EntityFrame5_CL_ReadFrame(void) { int n, enumber, framenum; entity_t *ent; entity_state_t *s; // read the number of this frame to echo back in next input packet framenum = MSG_ReadLong(&cl_message); CL_NewFrameReceived(framenum); if (cls.protocol != PROTOCOL_QUAKE && cls.protocol != PROTOCOL_QUAKEDP && cls.protocol != PROTOCOL_NEHAHRAMOVIE && cls.protocol != PROTOCOL_DARKPLACES1 && cls.protocol != PROTOCOL_DARKPLACES2 && cls.protocol != PROTOCOL_DARKPLACES3 && cls.protocol != PROTOCOL_DARKPLACES4 && cls.protocol != PROTOCOL_DARKPLACES5 && cls.protocol != PROTOCOL_DARKPLACES6) cls.servermovesequence = MSG_ReadLong(&cl_message); // read entity numbers until we find a 0x8000 // (which would be remove world entity, but is actually a terminator) while ((n = (unsigned short)MSG_ReadShort(&cl_message)) != 0x8000 && !cl_message.badread) { // get the entity number enumber = n & 0x7FFF; // we may need to expand the array if (cl.num_entities <= enumber) { cl.num_entities = enumber + 1; if (enumber >= cl.max_entities) CL_ExpandEntities(enumber); } // look up the entity ent = cl.entities + enumber; // slide the current into the previous slot ent->state_previous = ent->state_current; // read the update s = &ent->state_current; if (n & 0x8000) { // remove entity *s = defaultstate; } else { // update entity EntityState5_ReadUpdate(s, enumber); } // set the cl.entities_active flag cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); // set the update time s->time = cl.mtime[0]; // fix the number (it gets wiped occasionally by copying from defaultstate) s->number = enumber; // check if we need to update the lerp stuff if (s->active == ACTIVE_NETWORK) CL_MoveLerpEntityStates(&cl.entities[enumber]); // print extra messages if desired if (developer_networkentities.integer >= 2 && cl.entities[enumber].state_current.active != cl.entities[enumber].state_previous.active) { if (cl.entities[enumber].state_current.active == ACTIVE_NETWORK) Con_Printf("entity #%i has become active\n", enumber); else if (cl.entities[enumber].state_previous.active) Con_Printf("entity #%i has become inactive\n", enumber); } } } static int packetlog5cmp(const void *a_, const void *b_) { const entityframe5_packetlog_t *a = (const entityframe5_packetlog_t *) a_; const entityframe5_packetlog_t *b = (const entityframe5_packetlog_t *) b_; return a->packetnumber - b->packetnumber; } void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum) { int i, j, l, bits; entityframe5_changestate_t *s; entityframe5_packetlog_t *p; static unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; static int deltabits[MAX_EDICTS]; entityframe5_packetlog_t *packetlogs[ENTITYFRAME5_MAXPACKETLOGS]; for (i = 0, p = d->packetlog;i < ENTITYFRAME5_MAXPACKETLOGS;i++, p++) packetlogs[i] = p; qsort(packetlogs, sizeof(*packetlogs), ENTITYFRAME5_MAXPACKETLOGS, packetlog5cmp); memset(deltabits, 0, sizeof(deltabits)); memset(statsdeltabits, 0, sizeof(statsdeltabits)); for (i = 0; i < ENTITYFRAME5_MAXPACKETLOGS; i++) { p = packetlogs[i]; if (!p->packetnumber) continue; if (p->packetnumber <= framenum) { for (j = 0, s = p->states;j < p->numstates;j++, s++) deltabits[s->number] |= s->bits; for (l = 0;l < (MAX_CL_STATS+7)/8;l++) statsdeltabits[l] |= p->statsdeltabits[l]; p->packetnumber = 0; } else { for (j = 0, s = p->states;j < p->numstates;j++, s++) deltabits[s->number] &= ~s->bits; for (l = 0;l < (MAX_CL_STATS+7)/8;l++) statsdeltabits[l] &= ~p->statsdeltabits[l]; } } for(i = 0; i < d->maxedicts; ++i) { bits = deltabits[i] & ~d->deltabits[i]; if(bits) { d->deltabits[i] |= bits; // if it was a very important update, set priority higher if (bits & (E5_FULLUPDATE | E5_ATTACHMENT | E5_MODEL | E5_COLORMAP)) d->priorities[i] = max(d->priorities[i], 4); else d->priorities[i] = max(d->priorities[i], 1); } } for (l = 0;l < (MAX_CL_STATS+7)/8;l++) host_client->statsdeltabits[l] |= statsdeltabits[l]; // no need to mask out the already-set bits here, as we do not // do that priorities stuff } void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum) { int i; // scan for packets made obsolete by this ack and delete them for (i = 0;i < ENTITYFRAME5_MAXPACKETLOGS;i++) if (d->packetlog[i].packetnumber <= framenum) d->packetlog[i].packetnumber = 0; } qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, unsigned int movesequence, qboolean need_empty) { prvm_prog_t *prog = SVVM_prog; const entity_state_t *n; int i, num, l, framenum, packetlognumber, priority; sizebuf_t buf; unsigned char data[128]; entityframe5_packetlog_t *packetlog; if (prog->max_edicts > d->maxedicts) EntityFrame5_ExpandEdicts(d, prog->max_edicts); framenum = d->latestframenum + 1; d->viewentnum = viewentnum; // if packet log is full, mark all frames as lost, this will cause // it to send the lost data again for (packetlognumber = 0;packetlognumber < ENTITYFRAME5_MAXPACKETLOGS;packetlognumber++) if (d->packetlog[packetlognumber].packetnumber == 0) break; if (packetlognumber == ENTITYFRAME5_MAXPACKETLOGS) { Con_DPrintf("EntityFrame5_WriteFrame: packetlog overflow for a client, resetting\n"); EntityFrame5_LostFrame(d, framenum); packetlognumber = 0; } // prepare the buffer memset(&buf, 0, sizeof(buf)); buf.data = data; buf.maxsize = sizeof(data); // detect changes in states num = 1; for (i = 0;i < numstates;i++) { n = states[i]; // mark gaps in entity numbering as removed entities for (;num < n->number;num++) { // if the entity used to exist, clear it if (CHECKPVSBIT(d->visiblebits, num)) { CLEARPVSBIT(d->visiblebits, num); d->deltabits[num] = E5_FULLUPDATE; d->priorities[num] = max(d->priorities[num], 8); // removal is cheap d->states[num] = defaultstate; d->states[num].number = num; } } // update the entity state data if (!CHECKPVSBIT(d->visiblebits, num)) { // entity just spawned in, don't let it completely hog priority // because of being ancient on the first frame d->updateframenum[num] = framenum; // initial priority is a bit high to make projectiles send on the // first frame, among other things d->priorities[num] = max(d->priorities[num], 4); } SETPVSBIT(d->visiblebits, num); d->deltabits[num] |= EntityState5_DeltaBits(d->states + num, n); d->priorities[num] = max(d->priorities[num], 1); d->states[num] = *n; d->states[num].number = num; // advance to next entity so the next iteration doesn't immediately remove it num++; } // all remaining entities are dead for (;num < d->maxedicts;num++) { if (CHECKPVSBIT(d->visiblebits, num)) { CLEARPVSBIT(d->visiblebits, num); d->deltabits[num] = E5_FULLUPDATE; d->priorities[num] = max(d->priorities[num], 8); // removal is cheap d->states[num] = defaultstate; d->states[num].number = num; } } // if there isn't at least enough room for an empty svc_entities, // don't bother trying... if (buf.cursize + 11 > buf.maxsize) return false; // build lists of entities by priority level memset(d->prioritychaincounts, 0, sizeof(d->prioritychaincounts)); l = 0; for (num = 0;num < d->maxedicts;num++) { if (d->priorities[num]) { if (d->deltabits[num]) { if (d->priorities[num] < (ENTITYFRAME5_PRIORITYLEVELS - 1)) d->priorities[num] = EntityState5_Priority(d, num); l = num; priority = d->priorities[num]; if (d->prioritychaincounts[priority] < ENTITYFRAME5_MAXSTATES) d->prioritychains[priority][d->prioritychaincounts[priority]++] = num; } else d->priorities[num] = 0; } } packetlog = NULL; // write stat updates if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3 && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5) { for (i = 0;i < MAX_CL_STATS && msg->cursize + 6 + 11 <= maxsize;i++) { if (host_client->statsdeltabits[i>>3] & (1<<(i&7))) { host_client->statsdeltabits[i>>3] &= ~(1<<(i&7)); // add packetlog entry now that we have something for it if (!packetlog) { packetlog = d->packetlog + packetlognumber; packetlog->packetnumber = framenum; packetlog->numstates = 0; memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); } packetlog->statsdeltabits[i>>3] |= (1<<(i&7)); if (host_client->stats[i] >= 0 && host_client->stats[i] < 256) { MSG_WriteByte(msg, svc_updatestatubyte); MSG_WriteByte(msg, i); MSG_WriteByte(msg, host_client->stats[i]); l = 1; } else { MSG_WriteByte(msg, svc_updatestat); MSG_WriteByte(msg, i); MSG_WriteLong(msg, host_client->stats[i]); l = 1; } } } } // only send empty svc_entities frame if needed if(!l && !need_empty) return false; // add packetlog entry now that we have something for it if (!packetlog) { packetlog = d->packetlog + packetlognumber; packetlog->packetnumber = framenum; packetlog->numstates = 0; memset(packetlog->statsdeltabits, 0, sizeof(packetlog->statsdeltabits)); } // write state updates if (developer_networkentities.integer >= 10) Con_Printf("send: svc_entities %i\n", framenum); d->latestframenum = framenum; MSG_WriteByte(msg, svc_entities); MSG_WriteLong(msg, framenum); if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_DARKPLACES1 && sv.protocol != PROTOCOL_DARKPLACES2 && sv.protocol != PROTOCOL_DARKPLACES3 && sv.protocol != PROTOCOL_DARKPLACES4 && sv.protocol != PROTOCOL_DARKPLACES5 && sv.protocol != PROTOCOL_DARKPLACES6) MSG_WriteLong(msg, movesequence); for (priority = ENTITYFRAME5_PRIORITYLEVELS - 1;priority >= 0 && packetlog->numstates < ENTITYFRAME5_MAXSTATES;priority--) { for (i = 0;i < d->prioritychaincounts[priority] && packetlog->numstates < ENTITYFRAME5_MAXSTATES;i++) { num = d->prioritychains[priority][i]; n = d->states + num; if (d->deltabits[num] & E5_FULLUPDATE) d->deltabits[num] = E5_FULLUPDATE | EntityState5_DeltaBits(&defaultstate, n); buf.cursize = 0; EntityState5_WriteUpdate(num, n, d->deltabits[num], &buf); // if the entity won't fit, try the next one if (msg->cursize + buf.cursize + 2 > maxsize) continue; // write entity to the packet SZ_Write(msg, buf.data, buf.cursize); // mark age on entity for prioritization d->updateframenum[num] = framenum; // log entity so deltabits can be restored later if lost packetlog->states[packetlog->numstates].number = num; packetlog->states[packetlog->numstates].bits = d->deltabits[num]; packetlog->numstates++; // clear deltabits and priority so it won't be sent again d->deltabits[num] = 0; d->priorities[num] = 0; } } MSG_WriteShort(msg, 0x8000); return true; } static void QW_TranslateEffects(entity_state_t *s, int qweffects) { s->effects = 0; s->internaleffects = 0; if (qweffects & QW_EF_BRIGHTFIELD) s->effects |= EF_BRIGHTFIELD; if (qweffects & QW_EF_MUZZLEFLASH) s->effects |= EF_MUZZLEFLASH; if (qweffects & QW_EF_FLAG1) { // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities if (s->number > cl.maxclients) s->effects |= EF_NODRAW; else s->internaleffects |= INTEF_FLAG1QW; } if (qweffects & QW_EF_FLAG2) { // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities if (s->number > cl.maxclients) s->effects |= EF_ADDITIVE; else s->internaleffects |= INTEF_FLAG2QW; } if (qweffects & QW_EF_RED) { if (qweffects & QW_EF_BLUE) s->effects |= EF_RED | EF_BLUE; else s->effects |= EF_RED; } else if (qweffects & QW_EF_BLUE) s->effects |= EF_BLUE; else if (qweffects & QW_EF_BRIGHTLIGHT) s->effects |= EF_BRIGHTLIGHT; else if (qweffects & QW_EF_DIMLIGHT) s->effects |= EF_DIMLIGHT; } void EntityStateQW_ReadPlayerUpdate(void) { int slot = MSG_ReadByte(&cl_message); int enumber = slot + 1; int weaponframe; int msec; int playerflags; int bits; entity_state_t *s; // look up the entity entity_t *ent = cl.entities + enumber; vec3_t viewangles; vec3_t velocity; // slide the current state into the previous ent->state_previous = ent->state_current; // read the update s = &ent->state_current; *s = defaultstate; s->active = ACTIVE_NETWORK; s->number = enumber; s->colormap = enumber; playerflags = MSG_ReadShort(&cl_message); MSG_ReadVector(&cl_message, s->origin, cls.protocol); s->frame = MSG_ReadByte(&cl_message); VectorClear(viewangles); VectorClear(velocity); if (playerflags & QW_PF_MSEC) { // time difference between last update this player sent to the server, // and last input we sent to the server (this packet is in response to // our input, so msec is how long ago the last update of this player // entity occurred, compared to our input being received) msec = MSG_ReadByte(&cl_message); } else msec = 0; if (playerflags & QW_PF_COMMAND) { bits = MSG_ReadByte(&cl_message); if (bits & QW_CM_ANGLE1) viewangles[0] = MSG_ReadAngle16i(&cl_message); // cmd->angles[0] if (bits & QW_CM_ANGLE2) viewangles[1] = MSG_ReadAngle16i(&cl_message); // cmd->angles[1] if (bits & QW_CM_ANGLE3) viewangles[2] = MSG_ReadAngle16i(&cl_message); // cmd->angles[2] if (bits & QW_CM_FORWARD) MSG_ReadShort(&cl_message); // cmd->forwardmove if (bits & QW_CM_SIDE) MSG_ReadShort(&cl_message); // cmd->sidemove if (bits & QW_CM_UP) MSG_ReadShort(&cl_message); // cmd->upmove if (bits & QW_CM_BUTTONS) (void) MSG_ReadByte(&cl_message); // cmd->buttons if (bits & QW_CM_IMPULSE) (void) MSG_ReadByte(&cl_message); // cmd->impulse (void) MSG_ReadByte(&cl_message); // cmd->msec } if (playerflags & QW_PF_VELOCITY1) velocity[0] = MSG_ReadShort(&cl_message); if (playerflags & QW_PF_VELOCITY2) velocity[1] = MSG_ReadShort(&cl_message); if (playerflags & QW_PF_VELOCITY3) velocity[2] = MSG_ReadShort(&cl_message); if (playerflags & QW_PF_MODEL) s->modelindex = MSG_ReadByte(&cl_message); else s->modelindex = cl.qw_modelindex_player; if (playerflags & QW_PF_SKINNUM) s->skin = MSG_ReadByte(&cl_message); if (playerflags & QW_PF_EFFECTS) QW_TranslateEffects(s, MSG_ReadByte(&cl_message)); if (playerflags & QW_PF_WEAPONFRAME) weaponframe = MSG_ReadByte(&cl_message); else weaponframe = 0; if (enumber == cl.playerentity) { // if this is an update on our player, update the angles VectorCopy(cl.viewangles, viewangles); } // calculate the entity angles from the viewangles s->angles[0] = viewangles[0] * -0.0333; s->angles[1] = viewangles[1]; s->angles[2] = 0; s->angles[2] = V_CalcRoll(s->angles, velocity)*4; // if this is an update on our player, update interpolation state if (enumber == cl.playerentity) { VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); cl.mviewzoom[1] = cl.mviewzoom[0]; cl.idealpitch = 0; cl.mpunchangle[0][0] = 0; cl.mpunchangle[0][1] = 0; cl.mpunchangle[0][2] = 0; cl.mpunchvector[0][0] = 0; cl.mpunchvector[0][1] = 0; cl.mpunchvector[0][2] = 0; cl.mvelocity[0][0] = 0; cl.mvelocity[0][1] = 0; cl.mvelocity[0][2] = 0; cl.mviewzoom[0] = 1; VectorCopy(velocity, cl.mvelocity[0]); cl.stats[STAT_WEAPONFRAME] = weaponframe; if (playerflags & QW_PF_GIB) cl.stats[STAT_VIEWHEIGHT] = 8; else if (playerflags & QW_PF_DEAD) cl.stats[STAT_VIEWHEIGHT] = -16; else cl.stats[STAT_VIEWHEIGHT] = 22; } // set the cl.entities_active flag cl.entities_active[enumber] = (s->active == ACTIVE_NETWORK); // set the update time s->time = cl.mtime[0] - msec * 0.001; // qw has no clock // check if we need to update the lerp stuff if (s->active == ACTIVE_NETWORK) CL_MoveLerpEntityStates(&cl.entities[enumber]); } static void EntityStateQW_ReadEntityUpdate(entity_state_t *s, int bits) { int qweffects = 0; s->active = ACTIVE_NETWORK; s->number = bits & 511; bits &= ~511; if (bits & QW_U_MOREBITS) bits |= MSG_ReadByte(&cl_message); // store the QW_U_SOLID bit here? if (bits & QW_U_MODEL) s->modelindex = MSG_ReadByte(&cl_message); if (bits & QW_U_FRAME) s->frame = MSG_ReadByte(&cl_message); if (bits & QW_U_COLORMAP) s->colormap = MSG_ReadByte(&cl_message); if (bits & QW_U_SKIN) s->skin = MSG_ReadByte(&cl_message); if (bits & QW_U_EFFECTS) QW_TranslateEffects(s, qweffects = MSG_ReadByte(&cl_message)); if (bits & QW_U_ORIGIN1) s->origin[0] = MSG_ReadCoord13i(&cl_message); if (bits & QW_U_ANGLE1) s->angles[0] = MSG_ReadAngle8i(&cl_message); if (bits & QW_U_ORIGIN2) s->origin[1] = MSG_ReadCoord13i(&cl_message); if (bits & QW_U_ANGLE2) s->angles[1] = MSG_ReadAngle8i(&cl_message); if (bits & QW_U_ORIGIN3) s->origin[2] = MSG_ReadCoord13i(&cl_message); if (bits & QW_U_ANGLE3) s->angles[2] = MSG_ReadAngle8i(&cl_message); if (developer_networkentities.integer >= 2) { Con_Printf("ReadFields e%i", s->number); if (bits & QW_U_MODEL) Con_Printf(" U_MODEL %i", s->modelindex); if (bits & QW_U_FRAME) Con_Printf(" U_FRAME %i", s->frame); if (bits & QW_U_COLORMAP) Con_Printf(" U_COLORMAP %i", s->colormap); if (bits & QW_U_SKIN) Con_Printf(" U_SKIN %i", s->skin); if (bits & QW_U_EFFECTS) Con_Printf(" U_EFFECTS %i", qweffects); if (bits & QW_U_ORIGIN1) Con_Printf(" U_ORIGIN1 %f", s->origin[0]); if (bits & QW_U_ANGLE1) Con_Printf(" U_ANGLE1 %f", s->angles[0]); if (bits & QW_U_ORIGIN2) Con_Printf(" U_ORIGIN2 %f", s->origin[1]); if (bits & QW_U_ANGLE2) Con_Printf(" U_ANGLE2 %f", s->angles[1]); if (bits & QW_U_ORIGIN3) Con_Printf(" U_ORIGIN3 %f", s->origin[2]); if (bits & QW_U_ANGLE3) Con_Printf(" U_ANGLE3 %f", s->angles[2]); if (bits & QW_U_SOLID) Con_Printf(" U_SOLID"); Con_Print("\n"); } } entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool) { entityframeqw_database_t *d; d = (entityframeqw_database_t *)Mem_Alloc(pool, sizeof(*d)); return d; } void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d) { Mem_Free(d); } void EntityFrameQW_CL_ReadFrame(qboolean delta) { qboolean invalid = false; int number, oldsnapindex, newsnapindex, oldindex, newindex, oldnum, newnum; entity_t *ent; entityframeqw_database_t *d; entityframeqw_snapshot_t *oldsnap, *newsnap; if (!cl.entitydatabaseqw) cl.entitydatabaseqw = EntityFrameQW_AllocDatabase(cls.levelmempool); d = cl.entitydatabaseqw; // there is no cls.netcon in demos, so this reading code can't access // cls.netcon-> at all... so cls.qw_incoming_sequence and // cls.qw_outgoing_sequence are updated every time the corresponding // cls.netcon->qw. variables are updated // read the number of this frame to echo back in next input packet cl.qw_validsequence = cls.qw_incoming_sequence; newsnapindex = cl.qw_validsequence & QW_UPDATE_MASK; newsnap = d->snapshot + newsnapindex; memset(newsnap, 0, sizeof(*newsnap)); oldsnap = NULL; if (delta) { number = MSG_ReadByte(&cl_message); oldsnapindex = cl.qw_deltasequence[newsnapindex]; if ((number & QW_UPDATE_MASK) != (oldsnapindex & QW_UPDATE_MASK)) Con_DPrintf("WARNING: from mismatch\n"); if (oldsnapindex != -1) { if (cls.qw_outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1) { Con_DPrintf("delta update too old\n"); newsnap->invalid = invalid = true; // too old delta = false; } oldsnap = d->snapshot + (oldsnapindex & QW_UPDATE_MASK); } else delta = false; } // if we can't decode this frame properly, report that to the server if (invalid) cl.qw_validsequence = 0; // read entity numbers until we find a 0x0000 // (which would be an empty update on world entity, but is actually a terminator) newsnap->num_entities = 0; oldindex = 0; for (;;) { int word = (unsigned short)MSG_ReadShort(&cl_message); if (cl_message.badread) return; // just return, the main parser will print an error newnum = word == 0 ? 512 : (word & 511); oldnum = delta ? (oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number) : 9999; // copy unmodified oldsnap entities while (newnum > oldnum) // delta only { if (developer_networkentities.integer >= 2) Con_Printf("copy %i\n", oldnum); // copy one of the old entities if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); newsnap->entities[newsnap->num_entities] = oldsnap->entities[oldindex++]; newsnap->num_entities++; oldnum = oldindex >= oldsnap->num_entities ? 9999 : oldsnap->entities[oldindex].number; } if (word == 0) break; if (developer_networkentities.integer >= 2) { if (word & QW_U_REMOVE) Con_Printf("remove %i\n", newnum); else if (newnum == oldnum) Con_Printf("delta %i\n", newnum); else Con_Printf("baseline %i\n", newnum); } if (word & QW_U_REMOVE) { if (newnum != oldnum && !delta && !invalid) { cl.qw_validsequence = 0; Con_Printf("WARNING: U_REMOVE %i on full update\n", newnum); } } else { if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES) Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES"); newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex] : cl.entities[newnum].state_baseline; EntityStateQW_ReadEntityUpdate(newsnap->entities + newsnap->num_entities, word); newsnap->num_entities++; } if (newnum == oldnum) oldindex++; } // expand cl.num_entities to include every entity we've seen this game newnum = newsnap->num_entities ? newsnap->entities[newsnap->num_entities - 1].number : 1; if (cl.num_entities <= newnum) { cl.num_entities = newnum + 1; if (cl.max_entities < newnum + 1) CL_ExpandEntities(newnum); } // now update the non-player entities from the snapshot states number = cl.maxclients + 1; for (newindex = 0;;newindex++) { newnum = newindex >= newsnap->num_entities ? cl.num_entities : newsnap->entities[newindex].number; // kill any missing entities for (;number < newnum;number++) { if (cl.entities_active[number]) { cl.entities_active[number] = false; cl.entities[number].state_current.active = ACTIVE_NOT; } } if (number >= cl.num_entities) break; // update the entity ent = &cl.entities[number]; ent->state_previous = ent->state_current; ent->state_current = newsnap->entities[newindex]; ent->state_current.time = cl.mtime[0]; CL_MoveLerpEntityStates(ent); // the entity lives again... cl.entities_active[number] = true; number++; } } darkplaces/wad.c0000664000175000017500000001772613067716222013121 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "quakedef.h" #include "image.h" #include "wad.h" typedef struct mwad_s { qfile_t *file; int numlumps; lumpinfo_t *lumps; } mwad_t; typedef struct wadstate_s { unsigned char *gfx_base; mwad_t gfx; memexpandablearray_t hlwads; } wadstate_t; static wadstate_t wad; /* ================== W_CleanupName Lowercases name and pads with spaces and a terminating 0 to the length of lumpinfo_t->name. Used so lumpname lookups can proceed rapidly by comparing 4 chars at a time Space padding is so names can be printed nicely in tables. Can safely be performed in place. ================== */ static void W_CleanupName (const char *in, char *out) { int i; int c; for (i=0 ; i<16 ; i++ ) { c = in[i]; if (!c) break; if (c >= 'A' && c <= 'Z') c += ('a' - 'A'); out[i] = c; } for ( ; i< 16 ; i++ ) out[i] = 0; } static void W_SwapLumps(int numlumps, lumpinfo_t *lumps) { int i; for (i = 0;i < numlumps;i++) { lumps[i].filepos = LittleLong(lumps[i].filepos); lumps[i].disksize = LittleLong(lumps[i].disksize); lumps[i].size = LittleLong(lumps[i].size); W_CleanupName(lumps[i].name, lumps[i].name); } } void W_UnloadAll(void) { unsigned int i; mwad_t *w; // free gfx.wad if it is loaded if (wad.gfx_base) Mem_Free(wad.gfx_base); wad.gfx_base = NULL; // close all hlwad files and free their lumps data for (i = 0;i < Mem_ExpandableArray_IndexRange(&wad.hlwads);i++) { w = (mwad_t *) Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, i); if (!w) continue; if (w->file) FS_Close(w->file); w->file = NULL; if (w->lumps) Mem_Free(w->lumps); w->lumps = NULL; } // free the hlwads array Mem_ExpandableArray_FreeArray(&wad.hlwads); // clear all state memset(&wad, 0, sizeof(wad)); } unsigned char *W_GetLumpName(const char *name) { int i; fs_offset_t filesize; lumpinfo_t *lump; char clean[16]; wadinfo_t *header; int infotableofs; W_CleanupName (name, clean); if (!wad.gfx_base) { if ((wad.gfx_base = FS_LoadFile ("gfx.wad", cls.permanentmempool, false, &filesize))) { if (memcmp(wad.gfx_base, "WAD2", 4)) { Con_Print("gfx.wad doesn't have WAD2 id\n"); Mem_Free(wad.gfx_base); wad.gfx_base = NULL; } else { header = (wadinfo_t *)wad.gfx_base; wad.gfx.numlumps = LittleLong(header->numlumps); infotableofs = LittleLong(header->infotableofs); wad.gfx.lumps = (lumpinfo_t *)(wad.gfx_base + infotableofs); // byteswap the gfx.wad lumps in place W_SwapLumps(wad.gfx.numlumps, wad.gfx.lumps); } } } for (lump = wad.gfx.lumps, i = 0;i < wad.gfx.numlumps;i++, lump++) if (!strcmp(clean, lump->name)) return (wad.gfx_base + lump->filepos); return NULL; } /* ==================== W_LoadTextureWadFile ==================== */ void W_LoadTextureWadFile (char *filename, int complain) { wadinfo_t header; int infotableofs; qfile_t *file; int numlumps; mwad_t *w; file = FS_OpenVirtualFile(filename, false); if (!file) { if (complain) Con_Printf("W_LoadTextureWadFile: couldn't find %s\n", filename); return; } if (FS_Read(file, &header, sizeof(wadinfo_t)) != sizeof(wadinfo_t)) {Con_Print("W_LoadTextureWadFile: unable to read wad header\n");FS_Close(file);file = NULL;return;} if(memcmp(header.identification, "WAD3", 4)) {Con_Printf("W_LoadTextureWadFile: Wad file %s doesn't have WAD3 id\n",filename);FS_Close(file);file = NULL;return;} numlumps = LittleLong(header.numlumps); if (numlumps < 1 || numlumps > 65536) {Con_Printf("W_LoadTextureWadFile: invalid number of lumps (%i)\n", numlumps);FS_Close(file);file = NULL;return;} infotableofs = LittleLong(header.infotableofs); if (FS_Seek (file, infotableofs, SEEK_SET)) {Con_Print("W_LoadTextureWadFile: unable to seek to lump table\n");FS_Close(file);file = NULL;return;} if (!wad.hlwads.mempool) Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); w = (mwad_t *) Mem_ExpandableArray_AllocRecord(&wad.hlwads); w->file = file; w->numlumps = numlumps; w->lumps = (lumpinfo_t *) Mem_Alloc(cls.permanentmempool, w->numlumps * sizeof(lumpinfo_t)); if (!w->lumps) { Con_Print("W_LoadTextureWadFile: unable to allocate temporary memory for lump table\n"); FS_Close(w->file); w->file = NULL; w->numlumps = 0; return; } if (FS_Read(file, w->lumps, sizeof(lumpinfo_t) * w->numlumps) != (fs_offset_t)sizeof(lumpinfo_t) * numlumps) { Con_Print("W_LoadTextureWadFile: unable to read lump table\n"); FS_Close(w->file); w->file = NULL; w->numlumps = 0; Mem_Free(w->lumps); w->lumps = NULL; return; } W_SwapLumps(w->numlumps, w->lumps); // leaves the file open } unsigned char *W_ConvertWAD3TextureBGRA(sizebuf_t *sb) { unsigned char *in, *data, *out, *pal; int d, p; unsigned char name[16]; unsigned int mipoffset[4]; MSG_BeginReading(sb); MSG_ReadBytes(sb, 16, name); image_width = MSG_ReadLittleLong(sb); image_height = MSG_ReadLittleLong(sb); mipoffset[0] = MSG_ReadLittleLong(sb); mipoffset[1] = MSG_ReadLittleLong(sb); // should be mipoffset[0] + image_width*image_height mipoffset[2] = MSG_ReadLittleLong(sb); // should be mipoffset[1] + image_width*image_height/4 mipoffset[3] = MSG_ReadLittleLong(sb); // should be mipoffset[2] + image_width*image_height/16 pal = sb->data + mipoffset[3] + (image_width / 8 * image_height / 8) + 2; // bail if any data looks wrong if (image_width < 0 || image_width > 4096 || image_height < 0 || image_height > 4096 || mipoffset[0] != 40 || mipoffset[1] != mipoffset[0] + image_width * image_height || mipoffset[2] != mipoffset[1] + image_width / 2 * image_height / 2 || mipoffset[3] != mipoffset[2] + image_width / 4 * image_height / 4 || (unsigned int)sb->cursize < (mipoffset[3] + image_width / 8 * image_height / 8 + 2 + 768)) return NULL; in = (unsigned char *)sb->data + mipoffset[0]; data = out = (unsigned char *)Mem_Alloc(tempmempool, image_width * image_height * 4); if (!data) return NULL; for (d = 0;d < image_width * image_height;d++) { p = *in++; if (name[0] == '{' && p == 255) out[0] = out[1] = out[2] = out[3] = 0; else { p *= 3; out[2] = pal[p]; out[1] = pal[p+1]; out[0] = pal[p+2]; out[3] = 255; } out += 4; } return data; } unsigned char *W_GetTextureBGRA(char *name) { unsigned int i, k; sizebuf_t sb; unsigned char *data; mwad_t *w; char texname[17]; size_t range; texname[16] = 0; W_CleanupName(name, texname); if (!wad.hlwads.mempool) Mem_ExpandableArray_NewArray(&wad.hlwads, cls.permanentmempool, sizeof(mwad_t), 16); range = Mem_ExpandableArray_IndexRange(&wad.hlwads); for (k = 0;k < range;k++) { w = (mwad_t *)Mem_ExpandableArray_RecordAtIndex(&wad.hlwads, k); if (!w) continue; for (i = 0;i < (unsigned int)w->numlumps;i++) { if (!strcmp(texname, w->lumps[i].name)) // found it { if (FS_Seek(w->file, w->lumps[i].filepos, SEEK_SET)) {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} MSG_InitReadBuffer(&sb, (unsigned char *)Mem_Alloc(tempmempool, w->lumps[i].disksize), w->lumps[i].disksize); if (!sb.data) return NULL; if (FS_Read(w->file, sb.data, w->lumps[i].size) < w->lumps[i].disksize) {Con_Print("W_GetTexture: corrupt WAD3 file\n");return NULL;} data = W_ConvertWAD3TextureBGRA(&sb); Mem_Free(sb.data); return data; } } } image_width = image_height = 0; return NULL; } darkplaces/darkplaces-vs2012.sln0000664000175000017500000000572413067716220015755 0ustar kalevkalev Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Express 2012 for Windows Desktop Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-wgl-vs2012", "darkplaces-wgl-vs2012.vcxproj", "{6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl-vs2012", "darkplaces-sdl-vs2012.vcxproj", "{7470C8B3-FCA7-42D3-9909-5F9E735C7C51}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-dedicated-vs2012", "darkplaces-dedicated-vs2012.vcxproj", "{389AE334-D907-4069-90B3-F0551B3EFDE9}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "darkplaces-sdl2-vs2012", "darkplaces-sdl2-vs2012.vcxproj", "{72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.ActiveCfg = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|Win32.Build.0 = Debug|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.ActiveCfg = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Debug|x64.Build.0 = Debug|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.ActiveCfg = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|Win32.Build.0 = Release|Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.ActiveCfg = Release|x64 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28}.Release|x64.Build.0 = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.ActiveCfg = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|Win32.Build.0 = Debug|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.ActiveCfg = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Debug|x64.Build.0 = Debug|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.ActiveCfg = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|Win32.Build.0 = Release|Win32 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.ActiveCfg = Release|x64 {7470C8B3-FCA7-42D3-9909-5F9E735C7C51}.Release|x64.Build.0 = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.ActiveCfg = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|Win32.Build.0 = Debug|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.ActiveCfg = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Debug|x64.Build.0 = Debug|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.ActiveCfg = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|Win32.Build.0 = Release|Win32 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.ActiveCfg = Release|x64 {389AE334-D907-4069-90B3-F0551B3EFDE9}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal darkplaces/darkplaces-sdl2-vs2015.vcxproj0000664000175000017500000004660313067716220017522 0ustar kalevkalev Debug Win32 Debug x64 Release Win32 Release x64 {72D93E63-FDBB-4AA3-B42B-FAADA6D7F5B2} darkplacessdl2 Win32Proj darkplaces-sdl2-vs2015 Application v140 MultiByte true Application v140 MultiByte Application v140 MultiByte true Application v140 MultiByte <_ProjectFileVersion>11.0.50727.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX86 kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) true X64 Disabled CONFIG_MENU;CONFIG_CD;WIN32;WIN64;_DEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) msvcrt.lib;%(IgnoreSpecificDefaultLibraries) true Windows MachineX64 kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) true X64 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;WIN64;NDEBUG;_WINDOWS;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase 4706;4127;4100;4055;4054;4244;4305;4702;%(DisableSpecificWarnings) true /wd"4201" %(AdditionalOptions) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX64 kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) /wd"4800" %(AdditionalOptions) darkplaces/lhnet.h0000664000175000017500000000350513067716220013451 0ustar kalevkalev // Written by Forest Hale 2003-06-15 and placed into public domain. #ifndef LHNET_H #define LHNET_H typedef enum lhnetaddresstype_e { LHNETADDRESSTYPE_NONE, LHNETADDRESSTYPE_LOOP, LHNETADDRESSTYPE_INET4, LHNETADDRESSTYPE_INET6 } lhnetaddresstype_t; typedef struct lhnetaddress_s { lhnetaddresstype_t addresstype; int port; // used by LHNETADDRESSTYPE_LOOP unsigned char storage[256]; // sockaddr_in or sockaddr_in6 } lhnetaddress_t; int LHNETADDRESS_FromPort(lhnetaddress_t *address, lhnetaddresstype_t addresstype, int port); int LHNETADDRESS_FromString(lhnetaddress_t *address, const char *string, int defaultport); int LHNETADDRESS_ToString(const lhnetaddress_t *address, char *string, int stringbuffersize, int includeport); int LHNETADDRESS_GetAddressType(const lhnetaddress_t *address); const char *LHNETADDRESS_GetInterfaceName(const lhnetaddress_t *address, char *ifname, size_t ifnamelength); int LHNETADDRESS_GetPort(const lhnetaddress_t *address); int LHNETADDRESS_SetPort(lhnetaddress_t *address, int port); int LHNETADDRESS_Compare(const lhnetaddress_t *address1, const lhnetaddress_t *address2); typedef struct lhnetsocket_s { lhnetaddress_t address; int inetsocket; struct lhnetsocket_s *next, *prev; } lhnetsocket_t; void LHNET_Init(void); void LHNET_Shutdown(void); int LHNET_DefaultDSCP(int dscp); // < 0: query; >= 0: set (returns previous value) void LHNET_SleepUntilPacket_Microseconds(int microseconds); lhnetsocket_t *LHNET_OpenSocket_Connectionless(lhnetaddress_t *address); void LHNET_CloseSocket(lhnetsocket_t *lhnetsocket); lhnetaddress_t *LHNET_AddressFromSocket(lhnetsocket_t *sock); int LHNET_Read(lhnetsocket_t *lhnetsocket, void *content, int maxcontentlength, lhnetaddress_t *address); int LHNET_Write(lhnetsocket_t *lhnetsocket, const void *content, int contentlength, const lhnetaddress_t *address); #endif darkplaces/darkplaces64x64.png0000664000175000017500000001345213067716220015523 0ustar kalevkalev‰PNG  IHDR@@ªiqÞñIDATxœÝ[yt[Õþî{O»,Éò.[¶cg_X 44PB² Ã:) ¡…nÓö N',:SZÎœ–áІ9e:¬¥´ÂPÊl’8NHBH¼ÆŽ-˲¬ÍZŸžžÞ?î}‘ì8d!n9½çèX–Þ»ïÞï·÷'‚²,‹sçνÀl6×8pàq ½³³“ÎäóOf3<¿Å$Š×¶µµ}À–hà ʲLføù'3@:<ܦªêl¯×{-€‹ÌP Àòxþ ÇŒ. ¡¡Á½¿»¿¡®¦¦@;Àbõì„¿¶Ì²,BH…ÍÏÝ·ã}¡P(¸ÀÔ' Àß&I’j[*Dá†K/Òm6›€@#˜Tào‰µÅ[ûG+Î_2Ïîv»ól`ö_€Ø$ÏÀ"´,Aee¥LânU\`üUý€4ƒs[”Øز€&»?ÄâÅ‹•ýû÷ÛE±£€ ˆ,Ëàï%0`,Dþ™@寀"ÎP1#p‰Ú%__– —Á1´z<žl(²¨à3 €ÖÜÜìs:‹u]w¹ÝîÅÕÕÕ ƒCƒ¶Hp$eµÛƒ9*ô~þJ˲œ¤³³S?µÎ˜4777)½û¼5àÈŽX³f yþùç%0©ºÁ@°ÚýþN·{±–ˆ×Z§$ÆÎíìmëïéõ^± Î†ß6VúU¢JŠRÓrÀætíËkÅÝccc½Bb²,gh§ ÄL iØØ;R«äÚ¥+€x<)ê`ЖJýÃÃC¶‚i„@?“§¥ìRH­,^'õ^Ýwwaࢻî¹mY$»ÉfkíH§ÓïD"‘ ˆp Š'kâ l>ŸOt¹Üçöõõ]­išéÀÎ÷ÞÃÿ½ù&Éçó@@€/z̦§UQ”b.§ [ú¬Íö˜Óš×AM;-¿¸ïb‡Ñ9±Üº®Û³Ù¬ €Æ_ŸÏ§ƒÁ‚0S˜¼ÏYz_ßÂ…‚å¦/ÝDf·Ï"¿å(Š0g6&é1iy0`²&ÄÀl}@NUU­ÂãµÕVWÙÌNÞ}G ‡L_½ÿAioWßBHÅbs¹œæ >ŸO÷ù|4 þåàÐìq»Ï9Ø×we¦P0?ùË'as8ÑßßOz{{)` @"ÅÈs0&Dù5A0;ªªšQ 1UÖ8?L‹ô™íÝBó¡ ¦/¬ýŽ¤›¬uccc³R©”¦ iÂ'ö², L¬ìuuuQ›(¥>û”k5MXXQ uÆt0 PÁ´ Á¯3•n]Æ“é¥îÆ–³.–hÅýïõ[Wí¹›3)ó† ›Ö¯_}(rð¹ ¿?-Ëò´~á´0¤\__ï4Kâ"UQÎʦ&V-^¸¨iBQ]»;vU^¸byE<›µ6GJ)-Ox„²áPþW+"ÅÁˆ0 811qUûW¶¶kwJ·_žûãk–Í›7WåóùÕñx|Ã/ä0¯9%øÆM ¯M¼¢"9ø÷ï-ŠÅÜ!ÉÁÜ(µšêjéÀ¤ÈI€óÈîNìýèCŒOrRX&! @çÏ+‚Ù´  FªÄ’é´âo¨»zppÐRغÁ‰ø}ì±ÇL÷Üs+¯àà%ùæò,Ó©aò¤}€,Ëׂö–+›²‡ÿýŠà–{“c¡yuéœu·Ý›;{ö,ÍRï3y«ª¤¢®G…“ 2¦(’ÝlAmó,š.¨¤¿¿|30•>fó…©^; " Ò`0H}>ŸÎÁ0üD @Æå©47Ø¥…ÕѨôÓçžÃÚ¼Ÿ$ ôôô¸E9~:Õœ”Ȳlöû›æÄF?4÷ï^mÎÆݯÃÐÜ‚…ų½5AM¥Rf0.BM#)=þ8Ð?®YcHÜØL‘¿?áàÚQ”eÙ¸7±XÌUU×Ôöì‡ÝóMéq&F±fÍó¶m[ ==Úœt:}˜ÙD9ðE”™å ‹!Y–m­ÍMçvoßñßû::¯Ÿ5¶ë®ÿ|‚JŸ»jqli¿ù¦\xoó[—Ëe6›Ív¾ùY`U¨µ|ßÓ Ë2ihhðŒ ~è¥ww.J™ÜýÄðÒ[[•",ãã,ÎæÁ2¶;l° À^I’Buüfá˜Ç&`”¶§UÖròB„n“š§+BO'`ÅŠ:˜Ä½ZÔ€™ÁQFúx`©¶21A,GÿÀ>0-‹ }dd$–ÉÀŸwl‡«¦ŽB¦R_FøI‡ k®pçΩ~{µÕøܪ5ƒƒp‚q”q‘Óà,†þáâš¼óÙuw¢é®®.W},ë“z7)0³0ûýþö¦úš«sù|å vàÑ/é‡Ål&6›ðYÀ$# (€‚@3×8Š¨ìÓd…Bðù­`4œÃx¦,Ëd²,‹-ÍÍó[Úfýlôš¯=ô±Ç³F£Q>+¾án°j.dl¾µµµ­Õï¿#:úɵ+—ßmµµ_ºšnüõ£tÛ¦·pÃ%ÒÖ&_ÆëõV€©d ÎÜùM&&l›¦é3‡tfZ®j8Nð¹‚Í g pl&h±B»luæ}Û­÷.!Ïý×Ïè}ßù”2™L†ß”0¦#`!0 Hˆà …_Ô(š_ßµOk\±"µ//Z÷9e1¶»‡˜÷Q A”—ËÕ’L&›9ª,ËÇõ 'Áð·Eîÿ‚•,½ãûÆgÙ¼ªênT­"Êœà1Ä#£ç¯Ûš _ÿæåX¶`6j›šŠç]üyLu²`aoˆoÞ8é- ‡¼ඡ#Gì¼±Xôèä*SGP `¼` ØIQ‘[˜åó«²,çÀŠšÂt¼ŸÏçsvvt´½R°¶ø&žºõA¬_ÿ”­X,Pb–¹oŠ¢Tä•TÃy>à|÷νã;è¿n­xýõ×küÚÿÜ–}YËP/Pzü-Š$PýÈЈ"ˆ¢Àm^á+Šh™YXÄ¢²òR!\qÉŽíß‚²Õfóù¼áçpz ¥d’®C£ºåö½YbÆA<þÙ'©®PJ 0Uà›ÕÀU–ƒ`Tä uy^;JÞž(NLLX´6´Î•b©ü¢ìð°©~ÙJzNMž{›Ž²Ñh´‚/Ô `.×€×¼£‚«¯¯wÓ‘þË« ª•ºâ—w¢âœÕ.~™Q>'ùû£ëžd„bFŠcÖ-pÝ]÷aýúõÁÈÚŒ¢'‡éÓXƒÖÒøƒB\ò†ó!e×Û„Ãá°Ãaµ,Þ `¥R…úÕ°ãÖ[…h4êÓ0?SC‰BÊæ2[ùü¶ÁñEX±íåØÛÆìEç(ý™¬A¿)`þ*Š’ÙÒÎÎN:5Pk½±ï΂)“Jù÷Ue”²“¿ßï?*0_r˜3=Ã:̺vËDOÿEµxéîãX{U:ër„’™ò¼eŒƒ?Î8ª¹“LÀjµ*«-æÓ€P2KЯ|å+äí·ß‰„¦Nå*xÜäeJÜ>(ós’&ɬ fÑD§Ì©ƒIo L‚åa×2gÎœ• .üFK]­Ã¤D:_¢¿-ÖÖÛœ™LÔ0¶€i€a¢&kKöý?„öú.ëþ~xë牒M—'V0»þDg  @A é\–-¤T4l±q`bb&¿ß?/øɺuëÚ}òW(ÖÎÆÿüæ½¾±Q rüþ ˜¹õñ¿qÊÑ“å©& PÑth~K‹úºØmøâ՗Þ­V+;»3¼ºišûOuPôªK/Ñüf9˜ß1™Lɲk çi$2––––%zxøgß^{÷"ÃMÀ‘ýú—n¹E 3®ÂÆA ƒj=`~Ä8M>–äˆä‚Á`ïŸ7€æ•Ýô úÛM&—ÃQÞááå M)?ÉøèСb @í&Ð|>ïB)¹2<¹TWWçm÷7^è~ZÌŒ¯*LŒ‘þå?è¯+šë›„èDZÍf³FÁ“üG`êo¢“ú ¦¦Â*€ñÔmŸërù×>üSÛÎM¯ØáƒøÞ#ZÓ¹œ˜ÍfëÀF˜mæq’Ìî4ƒø|¾Š{öXä­®º »›¤ƒÃ@ÉϘTÔ××Ï’ñk¢[·]¯Ž XÚÖ–ýݱS,MS2™ŒAÑÅùæ÷¡T­ÒŸ´Ö©*\Óˆ°½inkì6+ОGAUñÐ5æL¢`cUÚÀ:¾\`,ëéš1›ÍÁH¤6üï¯Ñ4>ú?:°Ûí¢×ë­µ ¸©«³ó‰Î­ÛnÿçUpÐPëKß÷ó§Í¬.Æ 1N[Á$á›Þ`?IÔÔ¢j’#óù|€L&#UÖ5ÔìHfç ÆMÖb]‡ºÌ½û?ÈJ5>»ªª°O‰Ot ;Ýðù|¢Ûí^Ü××wG$–_|—\r y~ÊÓY¡{<KïÁn[üÐÁYé´9ä{sàY²š«jI,1aVUU“z,RtƒQs»Ál”7m5Éx:›0 ¿»tÎ,ùW7ÞÐܳo‡åÂK¯‚gÿ¬¿·.)P꜕N§Ï /ºFx:•!JÍ÷~óÛtùU×b9@ïðaS:'­~®º¾Æ!5Ôý’‰Xu „“ Å8e΃…ÊQ0µ?à XîÂqTÿè¦ù¬æ0¨Ç›ßÛQ[?ûwŇ¾ý0ùq„õ¿|R²ÛíU.—ëlç˜ æͧa ‹‹áù<Ù’/ßvK®¶±Q‹$âR¨:ÕUÕsJ^Ëçó*xþÀ7Ø&ñÍ6´‡P:v?Z°M·€cbyÙ9œžËå¨Na{»£³ýµp^cÍ ×|f‘höÏ׺ú{M&³UQ åJ¦pRD§Ïç“<ÏÒÙ.²zÓÆw-wÜ{/­ïmSϹ„;oúª8Ò6ϼñõWõÛ¾»î,I’LãããF‚Ôv ›ÅÉõé@´ ÑÞ½¥‡kš“¿UÁòÿ ßtÌžã`¦æŸß'Á†ÔO¦ ³³Sç¾  #‘HØ¢«yÙ`±Üžê¦û¸ÎòÂÝ_Åמï˜WQa¯N&3 ‘Hä=0¦8F\äÁóîã‚A)ªüͤ¸£“ND£pWUQ“Éd˜ƒæÕ?SëJ%mL+R`Ò6È §ÐCøq§ÃF>À¯ÓÉT®¹¹YÞQ¬¯Þ½?Z¼aÛws[àzíŧ|o §¯Û³gÏïkš¶#÷¡%rŒòFG£rÌô÷véëß|ƒôöõQ¹ªŠ‹G}iLƒ©x¥~"cÃÆ|§Õ::D)+Ñ#G†+LB²&1è>÷ò+CCCåäÀÔ; ¦c¤Æ§a''Lc¹g—À’žF Á¼ÿ9`¿ðÚív D”€ê”jZÑl±äDÉ”0;œC”Ò`0óÌ»PjŠÐœUi*>«ø`ÞŸƒÔât‘Ë.»LK$˜ú¿à5þ7€²V—¿H«,÷1a0Bc`ÖBs³Ùl&·¾R²€€Ä„ŒÉqò )tšo$å¬nø`_ꃹ_^½ Õsçæa/”<úë6ÆIÅmîM5”z‚oð2˜„6èàß €Iy„_?Æß'À|BùÈ*=v:sѱæÊË k'3øÃÉôØý¡ÕI7I•ùƒÌ‚Ùò°Ã*°:¡ %Ø £ *=ŒR¿Ž!iE”¤ó½Þ;.»p.(¥ÎA$l “é÷3:N©KŒ«•eÙð¼FS'ØŠ ¥ÈJX ¸rj:G‰¨é÷v챘 nk2™ù5™²{¦šœ‘qZ}‚S– Þ/ –œH8–6øA£gÏhŽ¢ÔÑÑÑ NË@E ¿JE Ì4 |Î4ÊNŸÎôo ?Q§¨¡`=}J<áÔÓ^:õel„›UJļ¿½5Ý1>n?âO7SÇ)ªj® ëcO=û»œÝ]ePZ 0“28ãøürÓhq)龺 K÷ÄÄD¥Ì/ fŸ„vûØ1“?š:•‘…BA“Hö€e—X¢e?ÌЭ?-D Eú'çƒE‘0&·ÞœñÍŸt”š- ` S,iÊb† ü?áühƒ÷IEND®B`‚darkplaces/xonotic_256.png0000664000175000017500000021530313070766633014763 0ustar kalevkalev‰PNG  IHDR\r¨fsBIT|dˆ pHYsIÒIÒ¨EŠøtEXtSoftwarewww.inkscape.org›î<tEXtAuthorXonotic Community4…÷„,tEXtDescriptionThe icon of the Xonotic project.AtEXtCreation Time2010Fbõ IDATxœì½i°¤çuö<çýÖ^oÏà¶`B ‰á&ƒµPâÈvy‰—$’)9NTblÙI¬$U±~8q¬Â l'Q¥*ù‘HYlËQ)fb‹–’ì”#'H¦$Ú„DŠPAp –ÁÅ̽}{ý–÷<ùñõ  D‘ 0 îSÕuïéþºû{Ï9ïs–÷à‡8Ä!qˆCâ‡8Ä!qˆCâ‡8Ä!qˆCâ‡8Ä7øf€·è«{ÚW÷ÄC|ý8\“× ‡àÕ¸*\ðÈ#›{ô0pîç^}¿N}TÀ#ÝïgÖ•K Þë«ëræ÷±&?¬«R~¸.7âÐ\E'`W”~#\;ÛÄi/1€ØÔЃPÈ¥‚ÂmG†ùŸûÃÜõmï¾ý¶åº©þÙ§¿ô;0& ‘ð𖆛€ ¸PK‘Fʆ٢Ö'ÞùÁw}äÞý…_úø¿øíg_º¼¨Eº—€JˆÝOWICGZN*m“!h©˜$D“°ª“4+ª¬­še6 ŸˆíÉ:ãùɽÀÙmð«là­é¼…õ†"›¼þš~šlÒ&"K’S¡ «lÉàýDìË4@äP¦CpCûÁ>à=€„@zd«,~ð{ß}Çw¿ïÄ­fä§á…¿ó‹ŸýÜlQï Ø#qÄ%— ¼äî»lj1ÌQ`Y·^ |Ôœ?‰xÔqæŒð”7›|ÄNžG2·ý4QUX,Kg5ô˜Œ-ĉӎPºÀQÑ&tm ûÙäGþäÞÿ¡î¸Ã]ú•ßzîâ?øä^¸¼·Zh@T€Ö€--- Ìa˜Ñ9GÐŒÎyK-[$ÒRÆ•×m«4 ^+mÒEÓæ@ñºº·èÁ¯·xµ¿¿ ôíNÊP·ŠY•2±Ôjäfe®¶*PÊÔ7÷N äÂ04q ãÂ`ROd õóâÏ|ï;oÿƒàä­I0îÍ×õßù…Ï>ñ™'^| ÀZƆ®F@#¡1¢q¡50šZ_Çèåšâ UVÂÙ‡q€¯Ép%ÆJaž8bpKajÖÖª^jhhfËfýßÿÃOŸûà;o¿ô#êïüÈîºí»Þ{üÖÿï7Î_üÄ'ŸxqY­f”ònUÀ,§”»¡€3'›[ÆÌ)ÀD­³2X\…,3Ë[óÆã}—Vñj€ðัÞRqðf€¯Wý}‚8û¨áø‡ìd}),V뤚#-Ó*0@Aæe`=ˆ²ApŽŒAnØ‚8!84¹iLaÃB?$V~ÿGÞyâ¯üàC÷Ýâè$?ù›ç/þÄßÿµßyvg¶p piàÄ ÄÌÀ™¤¹Q¶ôØ®I«Ö1oÒfó =¿ÜþªpÂÙ³oö½¼‰ñqú4qö™ÝiM²oiØÚÛÄ‚È$˜R9RP©)ÜR‚  `xáò¼ùŸ9iÔÏÒ{ï˜ î»s2øcº÷Ö`–<ñÜåÊ] ÄTP*2…”P–L` $ƒ‰ÆHsÂR‹€GÖFäV³š±ÝǶ {ƒw=I|ì4ðIœé¾Ë[o…yýÉ°mžÚîJy‹ä*å/³À¶ «dHJ öå€ 4lãïkÐQ~õ²÷Þ»=ù‹êw›ôsxyoYýOÿä7¾ü…ó¯ìvôKs8÷IŸŠ¶+i—Ô®h»”M©zƆs\Õë¶êG}á"€ˆøQï\Å·Æîð&€8sæ éG8~aqYV$¹e*Õ¦W;’alâ„ò‰È -ÉÆ0ºuE`ñî“·Lþ£ëÜsl«×­çî¢úÛ¿ðÙg?ÿôÎ.ZÀ`çssÂgAšÑ8ÂB±]ɸŠJÖiX]q úýí¶¿Øñs;Û~}áÍ¿Ö7¹øÊþþz˜'½uHÖŠY’VyceζéÁЃc³Ñ‡€  ŽD ê;Ø'UÂQl óÞÿñ÷ÿŽSwNÀ]úg¿þ¥‹ÿçÿûøómÔúŠòs3Bû§öØ#´çfS@3´6W†eÁj]å¨ûËE{>«#îøKCà[ËG|ƒ±1ö ^ø_ÃÉ: ‹^?É«Q¶VUè!ú ‘œa,×Ø€-[Ç‚F†®$°ø³è;ÿoÇ­fõýµsÏïþÌÿý[öfÕ†µÄ•A € ff ö î>sÙ îsæp,•¤ËÔWUÛäUÁP/‹Ø^9÷ñ‹ ܬ._¥ü“]»ïñ^˜Ý’•/Ó2¥kS’¶$QFú ±0taŒc[&&&·Hß00¤0 Ñûcº÷¶û³ß~ï=·oõàÂËû«Ÿøø¯éÑÏ>ûr”Ö–çföIN!NIí98%}?šÍ ,²`Ë`Zu½®M9>Ö/­}ç¹/ '¾÷4®ôª;ÄWÀâô#ÄcsÞ™Þ‰:/¹œ.˜e‘‰ƒ¯œ«„C"Œ”m$F¡!Jøü—^^üæ/ÎßyâHÔÏÓÇFåzðž[ª¦Å—žßm $  7®»øXH˜ÍAK‚1²e „¬C‚ÂR,5c;àÖÇ#.ßzxö.à>œ=Ý}§›Ô%¸?Ôkû®œ Iá{Y]ö³Ð0gâ%û€õåa4#G&ŽŽ Œ@ ì;Ð#TœØŽ~ôû?x÷ÉÛÆ%ÐíúÿèÑ'.þü/?ñ¢äÀ5„%È€9à33‚ûNí¶÷}Zœ·Jf“…B½ŠLÖuëU{Íp+¶O-#ÎíèþÕ $à~ÎÖ}¶’*,Ó,±<¨-Pw…0\<ŽL>êX€ qÀ¤>ˆ ‚´üû¾ç·ÿGÞy… œiºúÉô™gžÛ™í \°ôM†€À>È}BÝš»ötŸç€/À°Pk«˜ªÊV‹zm[uÑ_¡D÷Õp“­ÿÍÆ^Cùï½ìK‘æ¬óšý<(–4ô7ùû¡ˆ±Á·Û081pr‹Ô˜àÆ ÷Ç¿ý·ÿg?øÐ=“a‘’ĺný¿ý?>}þìo>ó2À€% 9Œû §„¦öìܳŽþOÍlÖsÁ—!ø*2YWËPgëªíå£ø4Ž8ž}¾£ƒÝîS-þM‹GÎ?ð àÙ»p9=ŽmEÄÞ µå`]ÃÒR5‚U"àpw’Q@à£AÀap@F@|á™WV_¼°»þ¶wß>N‚qÜÏÓ?øàÉ£«ºå—.\nD„noW-0#ÝœdwòEºÐX‰,]rZÄù5ÁÁg“k™n6&p3€ë#ýçv6Êÿù°Šw„*Xš×–‡*)”6%#ûp@0†4qK„Ä&Ò-c‡ú£A>ø+?ð¡{ÿèC÷ÞbFJÐåýuóÈÿö©§žºpyÀJ¢‹îsŸÂÀä®A{ö ›Ê´/³™C 8¹²ULXËPk«ßôÙoû‹ßy.ú™Ӻ9ÉÖÍŠGˆ³Ÿ~àðØ“¼3ÝA»ÐßR¹€š,*k©Æ[)$Ž¢`B Š¢EÀE:º–,0wÍ¿þ—æ|çm£"K,ãî»uô-Ç ~ëé—×U d€Ð„ 4.3’.‘„D†É:…9Q…ŠG´­/]cNÝ”‚›åƒ\Ó¸ÄÙG ÛØÉk"ýyTØ5’‚Æ\ƒA E%Œ Ž!Œe0”0Ô'X|à[n=ò£ß÷à]ežZ0Ò]úò‹{«Ÿøø¯=½¿¨—è|ýˆ9„à3’ûîÚ§m"ÂŽ9] HZºÚu¦°nuX¿*"|ú´w?àfÝœ¯ÊÃY;µ½)öÚFh–<$®¬fRcA²COÞUwÊ14ãHڸęÐõ³Þ_ýsßqï=·o•fdtiU5þ“?ÿس¿ùÅ‹—»à/$æÌèØ14%1¥8…0‹Ð ƹ\Ë í:*Y_+ç;ŽÇ§¾V€›À¸ À ÊÖ°½sEùË´—®»_¦²nÙK†‚DÍm,j,rLùÀâT/ ¡ü¡?úžãä¡{BÙ¹˜¿þøóÓŸüùÇηQ+H]zOš‹Ü´O¨óÿ\ûnaù°…„ej¶r´ëÖ¬ÎVV¯†«&[Û2¼Ï/úŽCå=pý ÏkäbëP—Ó¤œ•i]zš*¡Ì÷’Dð>hó8”Ä88¢4Ù¥É^Xþè÷=xòÛ¸sLÚTõþ?ÿêéK?ûÏûBã âT¦M)M)NÝ|JqJø>Y1Ï-Uw©Â‚¡^5Ëæªx•\o²x³]€×Tþã@Xm-“²é¥«l™'yQ@±Zß’d(p,„±[pN?BrBp ÐÄààؤ?>óï÷·¼ÿ¾[‡¶qÙàÿÊ/ÿô?ýÜsÞùú 3‚S{”öŒÜ±GØÈ©9÷¡vN³¹Ø.¹Š‰U9ª®õ—eí`€ø4vëÒñПСò¿x8ûÉŽ:?sØy\{ƒçqû`(Ä u;U™Âc’»ÉÝÙºÑ"=º!D#À(Ê tHé¯L rŸ>÷ü"ڻᆬOv †{oßê}û©;'Ÿ}êâz±n"0‚w@Fi¤#$CH\XX‘©¶%Jô´Nqd ì÷—Àã;Ýw¹IÜ7óÍ_“ößäø‹Y•Vý~PŒi‰4öÕ¶C#G¶äØ2b,` àHÀÐä'zï¹çØä¯üàCwYr@ùá’þ—ÿë³Ïÿòo=»Ch p¡MdÔ>¥© S’SÒöá>ÆyÊ°PÛ¬É:¦ª²Äêeݶuဈ·Xþ÷­…ßûüG/K’ºõ,4Ì mÁ$-Å~p `6|$ilÄXäâHЈ›º½ïyß]ÛÿÁ¿ù;¤]Z×­ÿwÿà_=ûÛ_~yׄ¥Óæ]‘ö ì¹0¥aOÀ½cŽL’š°PhVÙ:_,êõ0oŠYÕ^âÍä¼Y à+*µ½Š¸ŸUI§ü]w „8mlêÊy‚}€&쎎) Aöÿè·Ý{Ûòý<ž&faãÛ­ëÖÿëÿýמyìÉ—.°$lŽ®âkJaOÄ.€] aWÑ»@Ÿ°Ï¨y _&lWž‡uŽª^­–MYöšáVÑžOG<÷)áäÇÇ>«`‡ÿ×àj`ð>¦Ç?ýãÒ[j½8`Áƒ'^«q‹rQ”Ãá–„(ÉEºmvê.âLàù‹Óúñó¯TzàŽQ0c02 fßý¾[óeÍ/½°×P0P ISWÞEp3Þ$`ˆj²R…­P¶ud¹Ä~xüÀ3§ßt&ðf¼éï¾ó³J«ØÏ,.J&Y‰¨ˆ¡Ä‘™Æ¶ºÒOM:ŸŸ#@#€}3õÿüŸøÀñ?üàÉÉÁ›E—vö–Íõ³¿úÌË»‹}K38f ¦§4ìQ>uu;?» à G\gJÖí5¥Ÿ_ù4Ø›ïÓ}óã«; šÄ2«Ù†P\©!†’Œ‹6–c‹Ô¸Ëa(aH wlÒýµúλ··ziØÔ À¿xìüîOÿÓÏ^pgwšÜ׆9’Ü=H»sJª“£À¹Úzå¡¿Êâ^ëæbo¸>º‹íþfçG(bÌ®(¿YÎNù]c3n œP8"mÒ|ÄÆßÇpXf£¿öCßyÏßuûèÀ×îzöâ~õðOÿÊ—§óõ\Ђ°ºŠ¾=ˆ»4ßlÀžÑ§Ûoéó -`ZÆèë@¯ª¶W—%›2L㳃wÅWïú‡Šÿ†à\‰ ÜÀŽ… °làÕºPbµZ—[€Óå­ÁÐÉ肹h]_¦y!A\­ýòçž[|à¾cÃQ? ¶yÂÉÛÇå{ï=6|ìw^\×m#`ÔU Í?ÊÕý4€^£Eà.&°ýZ17ôn¾Ñ€À€tyþã·ÙÉ„U³Lê^/EÝt§ùBrUù©1-™&Ž€˜le½ƒÛŽô·þÆ|äwló‹]zeºjÏüô¿<¿X7 s€3û2ìÜ´Gp—ÂiöqO‰%˘b]ÖiµÎД«U»µnãï¿3âÙç…‡vºüþÙïÅ!Ý3ðpö,pö ð±Ggìݧîzv*µª2U¯I½É¡à‰G‡ÄF‚¹êª6åÃìz‰S I`Ó:?ýø «ïxÏã2OÍH’ÄdX&~߉­ßxò¥õ|Õ€°«ž¨MŒQ†ÍŸ&&‰ Q\uÒ–Žõ·µwl(<û©Î%8{o´+ðF€ëOõMõ,,;IöÒL]ßw½+´ß5¦¡ÛùÁÉÁO[;ÿÝ·Ž'gþüwß;êçáZ埯j?ó÷þåÓ{³õüjn_SÑöî ¾GÙ.M{rN•pŸQs-<+–lëªj_Wí =Ò›e—Û_Rþ› ×ÄÎõ±³³ý%˜Ñr=E“˜Ì£b‘»©#¥Ä™IIpÖ÷uçù ‚uñØ“—~ßñIšI#Yd‰}ø}'&¿õÔËÕtQœL0lB Î^tõˆ&£ &k+ÔèÉ×S ö·]S1øI¼‘Fà27(ÿ®¬/…Uì…Šó,ï¥YðºD£,í É5&oP~ijKè }¾åøÑ#?þ±Ÿ,ó$Ôv»„¦ú?ó©g^xe6íh?;åï|´]Q»$v쉘ÒÂ>¡9ˆ…+[©ª*­ë¼^¶½¼Šç³qg½ëxè3ÂOýeu–úòß<ظŸ>ö3ØÙÙÖÞà6  Ø¨.M%ƒÚ&‘™œ hæ‚ €ºZ¾Ž èJø® ò-×>ÿôNõ=ï?±µ¡ É4˜}÷ûOl}á™WêËÓ¥w¯“Q`gHºcŸDWˆ £ˆ 45,D…yà‚À>À)€]‚{’v ¶+×”Ð>=Ì ¸@âK·°.¼­ÖUÖ”YhƒA—Ûß]ß0å÷òß|¸!Sðl‚½Á·nïjA¥Õj“ cZuÔ.9e4Iûwçl¼Û[¬ý© »Í‡ß{|ÌM@€$B ?üÞ[O=¿×ìì-£@#Å«F@”asm ÅH‰¬v…zZ(Ñ탡.§ßp#ð€k&½žÚæ}u/,«ªtÞÕö³-˜°×5èL7‡z4qi³óÛfççx³ó>ø®;ŽýØŸ}èDÆk‚´€„ÿñ=öÂo>ùÒ+º_عG S~Ú® {4ìœ1á–1ú:—ê* õ¿ÿÄÝcdz~HùßR¸ÖÜ…Ëw¯‹ ämDÓBÆTLä£$ÊÈM­Ð†Ê ÖyØ+{Ëø⥹è;®™I‚F~×{Ÿ»8ó_™µÝ«Én|¤m€`:»ºÌd ¢ÌMu¹bЭù.ß=Îõ¯5ßлõ 6›”ÍéÓÄã;vj±c³4$«ÝUš'È‚%E-–ÑÐ7†¡äc3lI˜8u„àÈ-A#yÿ]·þÇæÁ;7(?€Ÿýç¿}ñ“¿ñÌË$æ榲®a‡„«ÊlJÄÌ–néºH«º²ôJŠfggÛqòô ¹ýCåkàÆšì÷—8RGTeª"iÔ"—!ª£æ.X"¹pÕt]ë|#Àç^žÅUÕòý÷Ý:8x§—àÛßsÇèÒÞJÏ^œ6ÂÆ DÛ ,4ƒ$ ÁÔ?»H?:0e¦fŒšƒØ òL«õ iŠÝØUõ][³ý½§q¨üoElŒÀAñµF`•‹Tu¨`01B F)ÆŽûwÀ&¨×íõì‹Ï_n²4Iï?q´<Áƒç?ø®Û†/\Zøs/ï7›¤â¦°¸{Žœ›xƒy¢ !Êé½ð©ÖíTƒNô—Øyü]vãœøFu¾Îïðþ{y©žÙÚö“GÉZU¢h­'C_è"þ0Ž%lá ØèÚ<ƒo}Ç­·üè÷=xnØõ]§~ëÂþÇéÜ – æ‚öAM)îɱbÏÈ©Ӯݓ/®›â»µlúËíö<NmÁñðé7½Ló›_­ð¾Þ÷¸‹Ëu<ê8 \Ø~'—ÛZlí Üëa•-B ÐI`ìÒöå°.žç¦ŽÏûø/{a2(’ïzßñÑu,”Ä~߃w,×ÿæ—.v1Pìö( p.×戲9[x­×¥©Ç[|WJ|¤'R{šßè¹ßËòÿ,,ý¤L{é*cn¶îÁC_ÄТÆ]O7qñ‰#$&’&Ç¢†ïóÄs¯\¦87•‚®„ËF]x™Âžv›’Å…{±,kUÝ ÂM/Éëº ½þ›Ñ7ÀÜô»´ ³½W’* Ó,±ÜÚ®…—È¡d#¶œv„кmh‚®¾tâØpòÈ_øž“EšØµ7Ú]¸°3«ÿúß9ûå¦ÑL]Ë®®ºosƒ%^6j×=Ò÷©t&_/=ôWu­ë•÷éoèþ&Æ•E¹VáÏ}´ûyjçêÿ¿0»^Þî^½Çç¶!üpê÷¸ïçÞ˓¾ÂuÎàaà£;÷ÿ©Œó®µè‰ÍUÙ'¤Ž:"ñ¨ €-†iÊáßü‘Ó÷ßf7Êæºiýá¿ûËçŸ{y¶ pÐà.å»2»$ð²É/_•MÍÀ°ðÄV]K¹Y3ܺåjK¹«²yS€î°ý#g?b§¶wlwRvA¿2‹(aeÏQ%Q>‘qÇQÁŽ:Ba"h äðØVoò7þâGî”YvýGm£ë?ÿŸ?yþùÙˆ}IS‘»”.vIÐe(¿o¨?’ET³.òXUû¨•ÿkÆ•›tEáâ£Â(úkðRÎZð$€éíWåmœCçÏw¿èh½P\½ïׇ Çî<õ>Ø9“Íu®ñÀ6ôsðsðQìœÞ!ðœÝ>Õ•¡!![W!L  íÎ º®ÃÝ,‰#€y„Ò„äÂèÎíáÖó~ïÉä.ÌWuüñ¿ýè—_Þ[îBšÜ±+ñ2á—a¸Dß´”§ï²|µô€U5G]NÊf²»j»æ2:~X¯w_É×3ØQnvÿþ2ä@¨Ò^e•§…[(£Å¾Á†"ƶ›œšš€ûùø‘¿ð=÷ŒE7„û%àã¿tî•Çžxñ€`›&¶+aÏ€]÷9}¿ëïgK÷¸*òX­•7ƒôHפa½{£òâµq%A¾)páO¶óQðØ1X{öŽa—va·nÃf5 ¡6„‰!ìß‚Ä…°j4¡v„¼BØJaUS€UÖÏ`Û=ðüyØÞlk6¼æ@Ȭ‰e;¸öËaB?ƒ ìÒðÙ—`ó]Ç`“»ç¥ünÌf¿Í;vרÚ­L+Õ,B…F‰Œ€wå}#)Y7y˜(f€Âþ²BÕxxß;Žõ¯•R#‘&½ûŽÑ¯~þ²j¢ºíQ ¡Í8É®e™Ñ zÃÖ£‚·-U²QRuìúìl«+~}ë^Gp õŸìÚÉàaÕdIQĬ*Ò,ÈJ&èCaصîÒ[ÄæX¯01ëhÂàÇøÃ÷Ü~tݨüÑ…'/\^ýí_øÜ…M»¦.âOì± Ù®™öÄ0ujä1Y8ëuÁP¤úžm–]Àï¡¿¼éÙlÒ-‡»ÿõ¸NéOü©Â®Uúó;£-„¶ERÂÚä µH½$¤M4­‘¦I‘dI,,GˆBi0a©«Æ!7„Õ6ªí Ç "XDâ-’G"]}dD¨#’¹ö¡¬B Z»¥.^ïZ¤L%LîpGçi†íu öFr¬U“(…€nü(F2Òd"`  _¼°Û¾÷ÛÃÉ°L¯׃²á÷Ü»=xô³Ïν‹*jž‡Ñ!9h˜[ÍÛ^ðBª :Ö_è5*_—Å}½ @—ò;}š8¶c÷Õ½°X­“:/S„4 ¸úA¡kÝ5‘8¡u=ûaCü¥?ý­w½ÿ¾cƒ•š&êoý̯>³¬š«½ú¥)e{‚öŒÜ“…=*μõ9€Eî¾ ±¨Ö˜7å íø ÏúcŽ3 HýoÀõ»ýFé_¸f§/FÒ¯$$mDÒË‘%Ò!u!Kjdm@–™ 9‰ŒDViB¤n]Oþ:AbIR!äHši¯Eb-Rk‘z†D* ƒ!m‰Ì ©$‘Hºë´›ëx‰à BJX °¶……ô²eEÂðÔEÜÕﳪ¼c¸ÄôRO"˜(¢f Å@BæP ,Ô͹§_iþðONntŒä¸Ÿ'Gǽü3O\\nšt¾àõa¯‡¸õ?û¨aÑ·"½˜TóaZä½,´«n=FÇ¢¶:ÚoAvi¿1€ÁyèžÛþíïyçQ¾Z÷÷?÷ò¹ó¯\´éÌ©‘{0v#º€=Jûl–0.‚…¥‡¶Z¯Û¦ Ú­uÛã?¹Éó?²ù ‡Êà™wû‹°á>l8ExaԵήˆ´HF%ÒP!CŠ,ÔÈ*¢ Q¸£ˆ ‹(Q ACC.C"2 d‰wŠMGz` È"­…,2Ï»#O…Ü…¼í&8g9ÕBN K D#²–HD!±!«Š¡v°Â<˜¶G.Ñæ}­Lz¼¼[1ëERÈI†®T°K÷H#B‰€°¬ZNç•}ð]·^uCIœ¼}\ì/+|éùÝj#ÙÝ,šƒpQNÐep.Þx°ž×KÒ‹š=ëð±G_7Wàu0×Pÿ㉠w…•Ò¤ Lƒ˜×––!ľ¤¡F ¶ œH8Bâ ÿ˜ÀðÝw=úŸþ™o»Ã^Cû£ ŸýâÅÅßÿ¥sÏcs´—À”ÀžÈ]÷6‘Ö)3 ÔZÆv]U!¯ó°l/ âß:îšxüä©Ã<ÿõ¸NñÏøöaõÂÁn߉¤^"ÔH=AŽ ¹ŠJP†€-J#zzzJЃÐP (PHA°ˆ,2DdÈdÈÙ"‡#G‚<%Š(é(ÌP:P@(A”$Š¸18 ro‘# R‹H£H! -‚R„‘ƒM» Å:ØÑlÊýzÍ"ÊËÍxÍYoΆhhÒdsH膆 À„D8ÿâ4Þ{ÇVÿؤŸ½yÅûÞqkÿ ç_i^™®Úî‚æ€\‚ ›¹²H¸[‚Ø°ˆ™ËÓ^c¤céí«õzº_¯x ꯤlöÒ5’\i,Ù7ià!´pêH×Î˺&žÐðè¨7þñ}øî, ¯Ò XVÿ­ŸýÔ³UÓÎÎ7ÊߥVˆ]ELš š6‡ÅU´b]{^(šáVÙ>Õîjmÿa…p•æ“7(þÎ3aCó›ˆ)’ªAê9²¼Ay ÆÒÊ@ôôÈ1Ð'Ð7ÀŽ }==wôd(ÙBn… yÒ à-¢0 ŒŽ^Jôàè¹Ð·€ˆ^ zFô@”f(!4&äÆŽ)ÈèH‘ õ€Ä„ÐI’Àš¶ ‰ËlÁd^óÒxŸ½*À›ÊKÔ(„HC‚¨ØÍp WFˆ1H…Ï?½Óü¡ONÒ$¼‰åßyûàSŸ¿°\UM7«M‹FD…$ZT”èž4Þ´P¯Yúywhèut¾Np°ûï;ËP¼’j2Oó,ÉQ@¡Æ- å¾ÕÍé³ ºY}›^~&Á†?þþ疮ӫޅþ‡ŸÿÌ‹_~aºqb›´ŸQ»pì!pJþ»æJÂÂa«ÂÒJIÓöÙkŸÆ—ý]Tñ®=Ë£¯¡ø[G:ůˆ´l‘VY’ †¼‰(”Aè¹£o†>€A†&t£¹ˆ¡º±lC†"›™Œ} ý.Ò†‰@鎂†RŽ’†…ž}n®ß }Å€ÀÀ#úØ9zbw]¥¼c ÈiÈE¤H‘˜8 ƒ­Ú&p˜ WV¬6Z`«ÈѶΤȰÊS@&lô›ÑhJI7H„IÕD{ñò\ßùžãÃWÝpI0{Ï=Ûƒ³Ÿ}fîBס¸3NYì:(2X£!†TÞ*õº\ ‚f»Saçq½,àë07Fýï «Á:)k¦æ*j± @_àîc[€Oh˜pSèCa jø—þô·žxß}Çú¯¥üî¿üü…Ù?ù•'_‚ºé¼`7­‡à®¨=#öà¶oÁfÑÂ"“Ö! ÕjµÛ”+´Ãfé;ëòPù;¼&Õßya¹ƒ0Øø÷="e‹´2䉡PGÏ ½Ð)ÞÀ’Né! )ŒÔ5j“Wþ ²›Ü;ÑÐ#PŠ( ¥%z&ôœè è0P§èZ÷>†²ƒ!W¦÷aèèè Ý5ÉÎHÈÈ, %C€ flƒêÀ`Ë:"Xͬ®1«k†4gjÆJ)Ýi4'a]Ëðdó/¼2÷[ôËÛ£üF`$Fƒ<9:*³Ï<ñâ²;$¸ ¢ä2½cBl":WÀƒ×Cv®Ààù×%+ðµžØ|«‡³¼ï¶ÍVuèÕód•m¥–(³º.‘°Å~×–™#^™Ò‚!Ð îxèÝw=ý­w.,wÐ @§üÓEÿÞ/~î%k°ÑM`a&ú  â\ð…b²J«UÕi¿)ûhÇõ*n¦ôèjÄÿm©ü×ïœû(xjÄã°ó Øä(,é¦j†yD²®X‚¤%R²èÈ ä1 §S({ç—r  ÈIÈ€MÔ~ÓWÿ ý%‡A´Z- -„ƒÃ7kd  &!¨{}èÚ÷€ìÚ/ØÕÚÃ)DSw=5•€Š@#ÖÁ°raíÀÚˆµ¬C«­ò¼jÂ|'N²&ÖY‚Ý8ˆëZµ÷hkXî©#MAÏ@ds¹ØÙßûÅϽôž{¶{ã~®ô«Øȶ‘8ý­wãÉ‹Gÿõ^h6‚ZH5ŒÄšf5ä5j‹jÛ­ >W®_°SÒ@.!£!…’Œ2’YZK›ÈÚZöÕ"¬¨6Ö‘t Œy@ˆÝ°P€+]JS`Ò¶.î.ð]ï½ê ¼øüsèõ03HÀûßqlpösÏΫºíâݸÁh`7A2Â<ˆ¡n<æÃØ´rNçê „¾>Wàk1]ào{Ç®üleIÑz´Îk õ7 áÜ´ñÖÀÄ„-tóûF ÿòÞ<Ê®ëªþí}îø¦ªW¥ÒPšgY²ä)ƒí8–ìƤC˜ì,>Bhº¡Ó4t§i {AÓë³Í×Àׄo5S¸;0d%8¤!€88ãÙ-É’,k–ª¤šÞtï=gïïs_•dˉ•8æh½UUzU÷Ýáìñ÷Û{Wþ}oY1zú/ý6_y ˆ"ŠÃ§¦²?»×Wù1ùæ(y*™I"ñY‘–sA'× e½…û‡.ÒÍ矕õŸó? ðümfõ ‡@æe‚oÎ|õ‚_A U a¾/cÁÔ9ûO^表Îo6†®\·|ñW]¶jÓÊ%£3.O¶:¦³C‘–ßW¡^˜µ/à:«fCˆR)y¡/• ´Ì)¨O ’G‰&ïô=f„j`˜`K,l)„À„˜ ꥑ¹ÆŒ€HÕ3Œž !”B& „'ζåšõ Õ8 "Tku<øåû°rõZÆ0-_0>¼ãØŒ¯X$a…xö:9ò‘e+.Òž,š7EæÓy¡=›ôI~!@ómÂFšh¦\tl.BvIÄ”'¤œB¹ ÖTûCëJä‡u¥ï|ÓŠW¬™_éu×ög†Ñìø.f§îß3NÐLI;üàNð H[iA¥­ÄetÃÀölNyžX›"t‡'Ƈ¿¢Ø†ŽÂÿ w¿yŒ1Ðè¸UÀdA¥@àb„…"2ðJ‚˜áÝz(R-…Š)y(ï|ËšŠÿ>"Fé!Z:¸±qå’¡å‹FjKçWë•$<ÿ$¯Z¿bÑÝŸ»ÿ…ÇOÏ@áˆàTýD_”@Þ{a8 ‚`ÉÈPºxþPuáÐ@jŒaèÜšîôò#'Ç[/›Ér›` ÈÈd ôtAè@Ñ BŒ)b"„Û4T{ÆÎŒgˆ]>ÌF&¨R$V´è¹fæîŠå¶aŠ…‘&ih Ò˜€èS÷ïÿå»~103ˆÏïÚË.ßÄ+Ö̯Üò¦ ¾ôÔa«Ð\‰r@s2@2ePÎH%gHQhRˆé¹¢º<š'›&º²{l÷È}Ùð%m”KUžÀø¾„uÔÎG8·Ó¦ 6Q˜Äª«’R ZºŒBubøŒÐêÂázãß{ùHÿ vŸþ‹ãWÿßßàcÿÇ'zÛœž„RÄmRm)K ¢-0·à¸­L]QÓ“LòŠDºU[t~TÊ¡ wö·Ó?‹E}Áßyq~Þö,¾Á“0íA##O²aE*b1HØ"±„T)Äcù*ZB|}áW A BBŠä…ñü¡kÖ¯œ·yõÒ¡‘ÁF|á©]øâ00ÿæûo^÷G}ÿ±OœiC!ˆ–¿¸px0ºfýŠÁ%ó‡+ †’¡F5z5‚ØË—¨êøäLvr|¢½çðñsOï=tÆ:éAÐE©´LD !eA,¥òbÑ&!19·8êœ) ¸ ©  ÐY ÇApÒÉI"$ÂðÆ((QÕxûÓ“ûž^³¸™0n¼ùVüßÿåCø•ÿç7‘¦Þþ}à{/ÙõâøÌɳÓ@B®B“fô@šY•œÉä[Šl›[qíꈠ¹H±»Ið ÅKÊ\b º‹°g#¯õ˜^h›Ç"6F•™ëª4@ ùMú:j®3Qí¿~ຕÍz: øÿ· oßö¬X½@?öêäØdwª$ýL+tJI'Yil&¡:M V@®Ílz¦³^*s®ÿ‘ã>îŸÃûßèk6»ÿ€pž»?8Ê !„H±Aì‰(RTÈ ju"Ô„Ð0Šê]üAšs3K·ßÿÜ`E}ÁðÀð;®¹|åm7½eõ-oÞ¼x墑z5‰_a\ÎNuðÜÞøÇ'bÉ‚TâaºbͲú ÇNëd«cFšêÖ+7Ì»í¦·Œ¾ëÚ+¬^¼ 62X+qd.å)@Õ$ T6¯Z:ü¶-ëG‡ÕÚ¹™ZÝŒA Õ')J 0 °úââÀ•=Cã P¨Æ„ `UCŽ"vÆ 0PB(QH „ àôD‹ú‰n?Zñ·ý\÷öm³g»aùPõŸ92í „T@ìu ² µP²dÈ:ˆg+ÔE(<7àÅÅKŸ¸ä\À%xçÁ~#cÜ;.¢šILr„Hm‹ËR0U`P…R Ð:”kÌZS¥*A+ï¾nÍ‚ fᑇ¸ûžß_øå;xÆß¾#g»{Ÿ¨Õ®’¶ýøn´AÚ´­ªeî:ej5/êI‘¢ç]ÿö˜|»ê§_‡ëâÙý1ðhÜjxwŸB%j¹1 °w㨨xRO¤±Á+äãlW{¨.!E²bÁ¼Á[Þrù’M+ÎZf»ÝS­¾ºó(žÛû^ÇÙÉÎì{í8‚_ûÙ[1ÔH‘F!ÿ»¸yþÙé–[224·'õÂG×Ë-ŽžšÂ‘S“8rj3† Ì f‚aÂÂá:V/ÂÊÅM$ÑÜ¡ªqÜ°yÝÂ6¯[xèÄ™™¿yäÙÃ/ž' !ºÄä×P¡„gTØ(Øè N9­‡…•Š+º¡8J­3if-zR‡µAD-UMI‘‚(ÙsøìÔžÃãóÖ/N nºå]øä'>†‡¿òeÜ°õf&Z±p ~÷u«|áÑ…‚ &ÍT8iÊ=íÔ#ÑœM’SP&Ï]!ìz§Z‚‘–b÷F-;áµîûתæ`¿MК³]š:Õ2•¤t „ìÒ(€MÈ)@U¨Ö”´F‚Hkª!mT£êmÛ6Ìëo–³ãcøÝßúuüÌÏýçYèÏ0áÓ÷ïêc5P¢m€Z ´¡Ú ˆº…H¦”çi…Nd®²°Vºþ}Èï mù/ü÷´i¨ytv œ À Z˜¶"Ð.´Œó"<7?•ñ½–ÌbïUñB_AIç…úL:€tÝ’…C·\³itÝÒ…Ù³)…uªÕÃc;âáçŽ`סÓ¹ø><>6ƒ_ùÈñë?óN4½ %óšÁËÈ#Ûà™}'ðÒÉ)œ™h½\'¼úÍ!Ââ‘:®Z?Šï½n –.˜Eš±jÑHýC?|Ëæ§öÿüÃÏšéôf¡Koµ’" ÀÄø"BqÐcÓ é…E1hĺÐYZ€²œÃ®CÔfE*NR0·AZ4|úþ=cwýäËŸ ø¿~ü'ñ»þu\¶i3†ç€ˆpÛ¶ óÚqtbªçT‰4ƒ¢§¬](z ê‘ 2‚ˬ‹r¡®­$-›å5 kr`8¥K…_c0ûm똙Ðyj´ãØ°Kˆ¹âTkÄÜ è©6U¹„ü|‰/jü¾+—-_؈û<éÿÚÈò úù_Á‰bס±ÎçÚ ~øâ @S€N‚Êi>À´ž!5mº.9“eE%O]î'õö]ßLñjý_™äK/Ìî\äÝ}%$N‘FŠŠ2jʨ1£î¸ÌØAhÒœ‹?HOÔòá@@}óª%£?úÎk×Þú¦M£ÃµÙøÞ‰âÁgãþiÜý¹§ðÄîã8}îë ëL'ÓÏÇÛ®X†´Œ:½>{ŸøÂsøÿzOî9Žãc3hwóK¾IÓí ûŒãïÙç^8CÀâù ã›Ê.ž7X¹nÓê…VÄ95žùò^ò|ÊÞ  ùùòŽH‰EU"…‰àL¢6¨PÎf£ŠÌ¾° ˆ ÏN÷hÝÒ¡Ú¼ÁJÈDX±j îý»ÿƒ}{÷`Û;nõ•@ÃJüÄž“-@…@DJŽ¡„DÖ©Z&8ç\‹p×ï(|i°àkQt>ßÀ0w¨$ Ê!1¥†]Ô 2@Bƒ xÌ¿„„T»lÅðð¾çò‘¾ð?ýäãøÄÇþ?ýþV­YÀ³¤~ç3Oœœ˜É§xÊ/0 _è3 †§ûBZ ×uÝ°—"ÌÔœµÕyƒîÜóâ§÷¼q]ÿ 1ýÚ{ ÆLÁŒ6aÚçFÂÂùj9µHÉ e‡*5êh¨b´„ðü¨õA”3U1À>0@„ú5ëW,ùÀ­×­ÝzÅúƒµJÔ?™Ü:Ü÷ÕøÍ?_zâNŸk¿f Ý_3í ÷?qŽžÅƒÏ¾„|æ <ºã(N}íÖþµ¬ñɾºë¾øøA4ë V,„ÆðeË5Ôw½x¼-~@@Äe„ªOO"F L¤€p,¹« 3†-'DAÈŽLPPD„У"Ÿ¦[Þ´r6P­Õñ—ŸüS\¶i F/aÙ‚F´ûðx6>ÙÍËÁbG -ód †³N"ëȺŠ)’\‚FM.µNàµ)€;éëŸõ(¤ÐDFLBŒ Õ@Ò€Ò X›¤h*ñ oü:3j¿ôþëVÔÒȬµ¸ë"H IDATWþ3êõ>ô sÖÿÙý§Û_xìàé9ë¯Sê§úL4i ÓÊ4cD;ÆŠzYWÚE’ÅîpxÚEñ‘ŸÑïШõo÷zU«ßOò„0LæŒØ0R¤A€Š8ÔÔcè &4 TÆ û$Ÿ~œ§° 64pý¦5Ë>pËuk¯Û¸z¤žÎAxÝÌâoÚßü³GðÐsGÐîßÔ…e…ÑÓÓ8vfîUB†oÕêfí<†]Ï`ÝÒa T½#³ph ²nÉ‚æÎÃÇ[¹u¾JÇ›%@Á* „I,8Ü‘Ø4T ULD9‡†Ø¨ ”‚("hPh41“ÑÊEƒÕC¾ZpŪÕøÇ/݇gž|ïþ¾3CD±nÉPåKOšV…÷@–T­2,” b)¦È2ÖÅ䊌 —y|Ýûñõ:ký«1Ó‹ç›4ë†ÆHLp(UE¨AJ%߃à~koj¡þîëÖ,zÛæ%õ>áç¯?ó)óð$€)Uß=UXÛjÃŽÓ¢—Ø~w}ì!t{ ŠJÿöÝ7l¹~ãªeªò$š+tR¸0r6€´¹Ám¸NG…¦]ä…—)¸g¸dö÷1¨  GDÙçÚ¶_ùÊ̸õ]ïA§ÛÁßýÑÙs Ãï¿åò…€&¥,•²åå ªUgIbD£(¨UQS5^Qm36p¾ž8|-p÷8oÎck­¡È$ÝH-ÇLHH8%– ˆªT=lÄ)‘¦ š•ú ›—4ú®ÿ¾ç÷àþûî@Øzó;g?èäÙVñâÉÉ6 9@=ºPtAÜa–Ž ºj¤ˆf”ÙBcg%Ÿt©Yêv7÷+°QqÇíŠ;ßÖÿÕqý’Æ[kf¢@å 9¤ä1ýª*ª¤¨1Pš+ÃUEe ="ª¼óªõKo¹fÃhÜß,c“|üïwáÁíG_Æ{Õ“'` š`²Õûß–on ‡j¬Å¬Å¨%þkÕÿÌL8rz ë— Cü¾­Wo™?X7Ÿ{tûDTB$ä€q`g NŠB«4)¢-íõzE¸,c×Sá.í¸•®]zªš¿xr²}òl«X4\ `ëÍïÄ'þøáþûîÅ{ÞûCXÙF&Ü°yIã³<_?=ÑɽL¡¥*˜ªí@¸C@×Zôˆ»¹FAa­µy4O°{Œ{wîÁ×bÂ~ PnA?܃¦Nµ¸’¢$T)"aŽÅP‘Š×JR¸ B¤)€ø¶m—(t6‘úÑßûŸP(ÖnØ€E£‹xé‘Çf@ÈÊHÑI—@ˆtÔ¤gD3KÈ5 ŠJ/¶j\pz§¢{N±íäÅú_”Ã?‹ë—4Þj°+ˆ!‡Äïö;E•5QÔµþ~­¾j Ôȷ誮X8<ü¾¯Z1:<T!¢øü#ð÷íA÷­}0Þqõrüàk±d¤ŽÉV†_ÿó¯b÷‹ãßò›ôZV£cýÒ&Ö/†eÃX·´‰ê…¥_/ƒ¶mY³éäÙÉãï{éiIW±`Ö¡€­D½L$É ’&ŒTzÎqIº`tÒ%¥žz/7dç±™¼qýa¢ÑÅX»a^Ø·ý½ÿ‰ßþÈÝž1ÕÛ¶]6ò‘Ï=Ý"h ¥ @/kÔIG wØiÌ"q˜‡½›w­[³°Fž€’Y}qãøj Àÿöî„fzcl›0$& ØP%Œ !³âþI¡ hUÕs«ÕjçÏN{øÁ°ß^„­7ÍYćv€"'BO”ºê¹Ú]€º¢ÔCfÆ¡ÐÂÙ…®Ò›’KcÁÄFÅ·¿H?³.ŸÔÓçð70`ÚãDš „©°gï©ïÆSÓ²Yyjo]J«O%¹§…µ÷^»yŵ–{BV¹á÷=‡ßû«gñâÉ©K:éZâ_\» ßwýj4ë‰ÿOU V#ü꿺wüÉ£ØõO¤–/hàæ«—áúM£÷ŠÞœ¯îKXŽ§@@"š bˆE!m‹HÏ…EOVΓiN=0u•JoÔU ç½äí8:qÛ¶ CýØzÓ;ñ¾}Ø¿o/~ð¼}ëM0Ìô¶-KŸýʾکs­LUKV&ujiE«]+Ü#ØP#SØÄÚÞ©–C6_0±±Oº(1èU@¿âo+­Yx†§Su ƒ¢$H˜bçò”(H!ZKÕwþE H ¢ø¶më竨§UøÔ'ÿÌ3ˆ°uÛ;fŸÇ¡“Ùésí.™ªf êjø›¦Ò3¬™Í\¡\i‹Ü¸¤UŒ(¶}EýÄ—ïZëÿª.ÿ’†ŸZcr„‘øj½HƒŠXT8@UÎkÅå ÔÕW`ökæ« TߺvÙ¢÷^wùÒzŸuE»[àOþa7þþ‰ÃÐK‘Á ~à†ÕøÞ·¬@: ø¿õc¶“Èà®u=îøø£ØyèÛ£šõÛ®X‚›¯^ŠÕ£ƒç½óÊkŸnçãSí¬ÝËl«—ÙV//¦;½¢ÕÍrQuIr%5CJ£H+q(GÆ'N>±ÿÈA|U¡U…%†ƒs" IaÝ$å6 sš„…ÌØPSΔÐ%¢. eH‹®ªfÊNŸkwŸÌVÆDÀÖmïÀÇîþ¨*>õÉ?ÃÛ·Þä¯B·m[?ÿ÷ÿúé )À)¥ÌQ›À©“"euY4cËy6L†i³&>#ÆFôkU ^LÌYÿuû©wªÉ.ªq‘w2ªPÄЄNà\Ä(U@âcP²p¨V»~ó’zßú?þØ£8tð ÂÆM›1od¤|DŠ‡w& PÕD=…ö@Ô…P,=dRnˆrbÛæÌU{Sr`é*ÁÄ!Åw|7Çþç'ú°ñvЉó]þs0ŠEÈŠØ…ˆI‘¡â| |ŠzÛ×ûÅ9B¨P‡zÁŸ?Pk¾ïÆ+V®ñÍ)JAà¹c¸û ;1ÙÊ^ó ¯\ØÀ߸[¯X‚ÙnåñŽŸÂýÛ_ÀÁgñ3ï¹ ëHBƯþÄu¸ããaÇ·P ¬[ÒÄûß¹׬~Ùy@//äØÙ©îѱÉö‹§ÏµžŸi÷ò¥5 ðµ÷ ¯³„€¯Èóß% ­@ @G.z䪮p!Û ³®W—JK&‘! ˜m®yÎÈÙi&F{ ÕzªšQñðŽ£Ó+GF„y##ظi3vïÚ‰CâñÇÅ[¯»Æ0®ß¼¤þ™¯ì«:ÛÊH‰¤”9T ®B·I%q‚ž° 5/ ÕlïT‹±î¨”y¼‹zQsõþkZC<3o€k®0а. Ž¸„Ô¤`éˆTˆ(…øØÿ‡·n˜ïD=Ÿ Þú÷ùÿ7ÞôôóJ"ÀÃ;Lª"W¢ŒA=uÒchàžÎLas ÄR.VkpÉš`çŒbÛwuæVøw—.ÿ={À›úõú@àj ñ‰>ëDŠÔÏÕ‡ÎõÈcBCÔwÞ9/áW ®ÞzÕúå7mY½04†çµ|~;ž=0öšOöŠÕópûkqÍÚùsÿYï…ã¸û Ø{ìŒ üþß<Œÿž°`°†8`Üõ/¯ÅýãG±ç¥sßÔM›?XÁO|ÏeضeÉܤøò<ºyáv>9ýø¾#ç<;%ë ,QÙvLá„J%@¾ù«o%¦ð=  ¾$ŠÚ`̨¢ EW zd”SD¹Åtî8!뺖’@`#6¶@®a1¤ˆo=îè !ƒ"xç‘É÷ßzùH‹»ñ¦w`Ïî]¼Ì¼ùÚëø<ÙoÝ0ÿ#Ÿ}ªEД˜Ò²ÛQÌ)”S—6qa%ChòZOL– ¸5GOñîžWõ^%¸Xw7Ù<"Ç=΃,{&Ô0 Ó˜ˆÒ2)ÁZŠú¦ ‡kÕ·l\\N€íÏ>}{}ìÏ̸þ†›àÄ«Ü=/Žwf:EO 9™B{zí)Lª™aÊ‹ÈšYkv›Ê±–b¤U ýwõÿš.ÿ™1e„\ Ì};ëXØ÷Ú—Rðµ~UßpR ¿Î6ʬ.iÿØMW­™?Pó¹* +øË_À§¿ò +¯éd«Iˆÿþ×bÃÒ&úÇüóÛþâI|yÇŸœÓÀå7Ó þÏàçàí qÀøÑ›Öá¿}ü«ßÐM«Ä~dÛ:|ÿõ«Œ~nˉê 'Æ;O8:ñÜ‹'ÎY'¹rxKž¡è+0,)œ)y;$?‘‡VKŠBºtHÑbBKmº`d$yá$µašÛ“醩àŠ¼°¶QîD3€{×›ÛßÈ”ÏtŠÞÎCã+çU˜×ßpîþèïCD°oï^<óôÓ¸âªk0Þ²qqý³ì­ž:Ûê‰"e*[¨ U@š‚8qq@AH½"Ìã¬p[» Nˆb>xÑûúrpôפ¼™²Í­5± CF¬}CH¸ÒAEÀ)AÅ·¾uõ<ëÇ)þòS¾ª¾üÊ«Q„S@•ððÎ#Ó -ìñŸèÔSÖÌ€r§®0N-ÂÔJ~N",4O(F?XVüÝùÝdý¿f–?1d‚(J‹¤}©.yx¯¦@ŒF™Ùo@/츫„ê-W¬]ö½W¯[ìÇTùÛóÜÁqüÞÿÙããíK:á÷\»–¢UÅ#Ï¿„ÜyãÓ¯~¬k×/Ã^»©ì ã×¾cß«¶uËbüì÷]ŽÆl‚BTñøþ£ÓÿÔ¾ÓSÝ^@E”Í?ÉÇìðÖ¶Pòñ;V}0”|4Aùž…adNB×*ºFÑ&  EG d"Ó–)’zlVÆ5B— ƒÈN¬3®`¢Ü‘f¿¯û{Ü<)ÞydzÊ‘Š¨ âò+¯ÆŽgŸàeçò+¯8n}ëêyú÷Û[MD9eHÅW¢B0 ˜bFì2-L`1y4Ïa¢+˜¸‡. ¾L”[sl„ÖlîÒ”kqµ M –B§ADD>¡HÁš’J PGAríæ¥ Á)°wÏ.ìÚ±}+ý¶o†+?:·NŸ|þÄ$s¨æÊÈ Ô|Ý3 2QäÆØ‚%.²sy$Á¹®¢{R1z<Ñá»f}Í,ÿùØ>;Ä”!±„J >Ã/Æ'ú”ÐàÒò—½öëð  ÖHãƶ]µvÝè¼ÙXº“ãÿv7¾¼ýø7tÒ»^< k%‘`¤QE=0Ýé]À ¨bÃâ\¹rÖ,Fÿr+øÄ÷â¯yñ’‚5"àïX½iíì±`ï±±ÞçßsæääL‹="ô€òEè•Š ‡² °*rRä Ä(°¬°¢¥(Û‘Q?Ûˆ¢P B/Pô„ÑSAWCtQ ‹y«bl§ÖvÝÃ-­.DEÕuG8-Ôع gB&<··Aš‘ 'âüÉçOLþØ»®\•7øm7ÞŒÏ>ãïýŽíؽ{6l¼ µ›—6>}ÿî$ËmBTˆR¥ ¤PMš8„Q@RÀAìÄäA—×,¬Ñ‡_œ—ü³€¡À¤¨JÄdcQ$>îPßN‘”(4¾aËò!Æ]é]~æ<ëÞtí p8<³ïT;/\¡ œˆ2R͈©Q¯Åfâ8W5…r×ÎJ¥™Ê´µï:èo.Þ•,«ðsí$@‰3e#EMÍ,–?+ü¢³ñ~u˲ ~äí[VÖ’h¶¦þ¹ƒãøÍÏ<‡s3¯=É÷òµëð9ü‡>„Ÿÿ¡-X;êkê×cýèõPçf:8=ÕÆ‚*†ë•¹?ìËÇ&ñ[ŸÝŽ£c­KúÜ(`üÂ]­[FgurbÆ~þ‰ç'Ÿ?66BFÚG‹Ê ;ЃúD–ŠA™2r`öU³í¨ôćß„ÓÖ+r2Ȩ@΄ ™5ȃ­S¶}p‰Ë×4õà€P†ÆlW¢(´¤a¡D9ÉX(ƒ"S¯D»2ò¼pÅ3ûNµ¯^¿¨f˜ñ¦ko@ðÑßµž‡ñ™Oý~ùÎß6|ÖåC_zê@‡@‰o8¢©R¥¢’0IìYÔR`Ǭí¡å^– œ]çS½û`ÓD“Šd€«€qa8²!‰PLðñ?i¿•³úfŠ hëÕ«š¢>›rä¥ÃØþìS^aóUoFZ­ÃwWb|u—Ïþ´€jE¦ê2-ÝqÈY]a ±†àfåã‚愧ý–Ûì’vÕ?ýšåòßy¾Ë_ÒyÃsútÞØy:o¢¨‚Ñà’Îë€!(† †b„a†I}!!¼íúMëê–kÖÖ’0Ö þø¾½ø¥?³3ø¦^‡NMãCø>vßÞÒâûwŠázŠKæa¸ž^ðW…uø“/îÅÏÝý(ŽŒµ.éóªþÇO^‹­[ÍþïSeþüÃcÏkQÙॠÃcí‚wÑõ¼¯Jh3І Í‚¶ Úä³úm&Ì°`F 3D˜†bZ ¦˜ÅÓp˜Vƒ%´5F[#t9@Vaä iöÒDFFÆ$iYi çRgÎbY]!¹²ú½­Î+Õœ ÅWw&bˆiµŽÍW½yVn¶?ûŽ¼tØ$(aëÕ«šŠˆá›¤¤HI5%p"B19²¡ ³  ˜"àMMo-½ŒÏZÎó<€Òýß4BíÇÙqÌÀD™ Ä  "‚M€ êÛ83ǪoX9¿1{O¾|ß?àýÿú§ÃÕpÃÊù½/žéq¬¢>€oDJp‰hu;“Ùš3ŽzÜ6mÆŠå<¹0à¼þkðÝ~›uÊ£ylsk0r Ê!³‹IƒêR€SUõýÖ}_¹èÆ«V gNaØ×û?üàý˜Ãi€5—m†Õ2ûÿÒX׉XBn‘R£k¡\IsA˜«Ë¸¶iº4íéZ÷|÷ÿõlý_ïœ×£o˜ºa#²’À ‘ÀÇúê9üu eŸág\óÕ@¨Ý°~é’ïóú%Q0ïÝ¿ý~÷oŸG7ûöíoã#_x~öçázŒ%óª8;“áØ%&/¶>ôÞXÔL=jáŸ|hgï™OuÑŸúã¿ZNuÎsP8”p¤Ä÷ Úß @7‹HC%‚AœÏXŠ(ò9ÈrØjŠkl”Áµ-:pÑ0dEºûgt·ãm‹€tFGÓ\ÛhºÀåÎ:k•ÂœIrÊÁê=H¢œ…S±{^ënY»¨ÊDXsÙæ dçáïÇড়(n¼jÕðÞÃg&T4Q_,”(\ b‹EM¨¡ªÎX-“'ꄉýîÁ³÷wÝE¡5ñ.&º\3)»Â¹U#q $ï&Ë”q BÔ¬W*V,¨"ÀÓ?ŠöÌ ¨üW¯ 9<ßã-8rr²À*´€R®àLÉÇH —3QÎìÛ"glG¢¼ör÷ÿõ¸^áòoÚvÄWEÐVD $®¬à³Š:;ïò3ÐcˆJ·Ÿâ|å^% ‡~êæ+7Þ~ÝeË¢€Pt³¿ùÙøŸÝùmþ‹­³3¶¿xî["ü·\9Š­—/€Ïò þè‹OgϾxªèCäAˆÙð憃Í΀Â!2 #—#ËŒÿž€È!.ÅÂ2T Ä„pQ +ëìtn²OÁeÑ:@6z6*Fö(–Æå51¶#†È1Áù½L¹ÂùP—(Spæ÷¾ì‘““=q“lÏG½>0+?í™<ýø£ñ}36¬XPmÖ+""Ž•½L (H"ŽbVr¡+LP3)]^ŸaŒxY/T©ÀÀ6ÀΤ䢜­ˆ‰B‚P "bÄM”4!€DoÙ´|ÈJ™>UàÁûïa@„å«×Ͼ§Dxéä¹@ ª9‹æDÈ™ ¸`'Öbë ÍdÔ»Šý'_ÏØÿÅ!¾~ùî˜/ß­ â®Aê¸äï¨sY¯Ï@“CªVÂ0C QÙ­gí¢¡Eÿå½×^±yéÈ`¿dwß±)üô<†/n?ñMÇúßÉ×ÂfŠŸ}÷zô¯ë‹Û¹N»€¬ ‘ï{O 0Råôscø²Ý”Éó&HKøTQãòkÿ%@Uz˜Ù™Z˜@üó {Оº2Cò³ó…îԇϛûOjPï*Ídj\OL –XLð{\4' ‡ªoð²/<×Ñ=s ,_½þzðþ{gß³¼eÓò!ø~ƒ1 ’ d“‹Aä„QÈ1.ÊÙΤ„mÀùeÂsIÀÝ÷NÔ)Z,a•mÑ œåÀ Y5"¢ØÇš”€«¯Ž¶¬_ÜèŸü™3§±w×s¥<ø×ÒUkÏ;yÅ‘Sç:üPDBòÄ •Â¯Ö±Y®ÍäÂf*Nµ#Ë~¯»äßù”ÞÙÚýü,L\ñÖ(@TDHº)U'¨CÐ@Ù“OØ'û´Lô•Ö@Lï¹zÍÚŸ¹åªõÍJB*ŠO?ô"~îcOàĹîwòÚ¿éÅLø¥º•Èªxé̤ÞûÜ!õ=ùàKHˆü~õ“x¡ú.¾1eŒ PN V˜Ðù£ÌÊ™0@‚ñÍOëZ¨àB;EÄ9À$T± Z­~ïíñ_ïœÛ‹‚;ïPŒlÔ§Z6Si3¹¬gXvj\((?Ï{{äÔ¹Ž•“¥«Öâ|Ú»ë9œ9szÖˆnY¿¸ R¯ô.M"ï2¡³Ø¢HXå€Ôg("%Ï÷W)g ®?n¼Oý…z¨„=etÉúDâÊIÁÀ€à …S†«„pÜ7A^Ü w£[5È+s¬¬™§Öµ}èOË=*ÏIá^:5ÑkÔ+!3añŠ¾0·{ðK¸õß?;£hõË~_ëâñ~ŸÒ;†1‚@âÌ"e?q·J~Zn] õM:*’ÜC%¥÷í–,ýþkV/ ¼fEáôŃøÜǾ³Wþ-^qhðæUM@=½÷ÉO{ŠAÕ ¿R?ÞŸ³´^ø ùñ^.2,Ih0ÚYQä…+ÔS{PÉõ§ÙJ¿¢„{ªèHT@ ah‰688_`3.… ÚQÃ+H5¼}º}LƒÖŒšy£bÈ Œ:N=òå¡L ›*}¥dŸžèmZ³¸ƒóPhbfzröГçÎâùÏ`Ãæk6­YÒ¸ï‘ÝQ)‹1˜c¨§ä“P,¤‘s²a£PcUŒmÆ'"ê÷ ðÍûL¢¢;Àµ¨àn˜˜]è(I5’Ê1Cc(b!ŠHŽ.hVëõJÐ÷BŸ{ò‘WÜÅ+Ö`ÎK%óñ¿ó!Yó¹EC«³…ƒR±F´–ëÚÂòwèë€ûQ|?? Óð̾ @˜ "ãç@¨Xïú×µŒO•1ÀÞ£AŠ†5C\ÿ‘ëÖ­yóª…žK«Š±é wÞ³{OL'¯ûÛ²Þ´º‰8`@/œœ@§W 0Œ¡ZLCÕ„†ª 5«I0T1X‰…q`CaÀˆC‘a&ºÐZæÖI«WØ™^^´²¢˜îæÅL/ÏfzyïäD{æà™Ésê s:ð±t@EPœ¬:ÏŒ¹y67°ãmðŠ\¬šJ}‚í:P[¨£i®¦I'pr.`µB¦P§^ÀY.{ý+È;u®£ YyY¼b öîxê‚xîÉG°ör_P¯W‚ÑCÕ§'ÚJˆX=Ò!J1HbVŠ ™Ð 3S .Æg½)êŸm€»î"lÚJ+æÇl:)G”Îp $!Dä8I¬D1 1y—+ܸfñ`aE™™TÏïxúëï/bí,HDõęɮÈ(e«c uX&q*™ÌÔ Õ øøôƒ¯àUñý3>ÞbA˜;Ä"ÎÈ¡@•Ø×ë£A>ù×O@5Ô[ýz³’ þä¶Më–Õ*}ã²ý¥Iüêg÷`²ýÆqùÏ_oß0ýkmVñßo»Žêiô²]ôZÖ…v! ˜‡jq4T‹£‹ýv«WäûOOœ}c;ŽŽ%DX%ä†Ñ© dFÀ)¸Ù˜MøP_q®›nWœ¸[y:Ó„9„(tNŒõ3ÔB¹P(Pd¡p'ÎLv «Êe#Å+ÖbïΧ/8ôó;ž†uZ†¢×,<~fb‡ä“J åHDâ4„ã ry EÊ.íñ ÓæÃ+VààÄÝdÛMrÈر¡F„#B]’JeÖ_#õTÄpýêÅu%&§ÀñÃКš,ö¹5º|í¬FŸ˜)œ«D–J·‡@%³‹ ecÉ©c‚Sµ;T+FG^ñÿ\²ïå%¼ð%¼ dAl$*¨°ñPÔ÷åÅʉ;ªe¼ï=‚ÚºEÍ?þ¶ «jq8ËåÿìãÇñ‡_:ôm–ñ\׬Dÿz볊^ûõ*€^îÐ+z¹ W8Ô’ÍZ„€_]Ôâ ºzÙÈ¢«—,:pzrÙ_>ñÂc3Ýœ|1QÌ‚¡c…q)¸m@£=ÐÄí@i”Î ÎÏ@©}\™+BNŽØXçK“‰ %µ¬¬sbÇ&fŠyCð²óryjMMâèá>G@LëW/®éÑ]¡©F^x Ÿ0 T£]1n&c‹*áÄ~Âè<$Ð$i¦$-bg`)XBQAÃêJ¨AÑ¡F¥RIgÝÿçw<õ ë —¯™M;=Ѩ¬¸R p¡ª–˜ %±â¬38–@`!Ò\,é*HáñËïÈzÕx?‡ç ð.¿Fˆ'+\væBCÕ³ùˆü¬=ïïðÍ:oÙ¸tÙ»7/_ÌLUd…à·¾ðîßuæ;vÑÿTëôdÍÊ…Í:»¹ÃÉÉNOe85Ùó¯© ã3º¹C¯ôr‡náïk@*!†ë†kÑìבFŒkV61ÚLfwÍü¿ø®«ßõçí»oûÑñ•¡rd¸ÀÁ²4ZNL¸~Oàb¹€€`sW[,f¢åŠ¤Î9ëØÀB¹P ¢K ;vz¢70P˜ —¯¹¨L=¿ã),\î+$+•4ªWÆÎM·áC™H=4&‚‘…ÖR…‘°ÂRSÂ÷PkÖhêT‹kiÀjب…qB…”ýPƒ>ñ@¸|ñüº+]ØwPBµÞ„ˆWê'ÏLö´ß|ìçœÿ?{odÙ}Ý÷}Îùýî}ïõ{½MϾp01ÁE"%Zi†ÔFY¦MÓŠK‘»ìªT’JR•¤,/2A;v\I©;U©”+N¥R‘q%Ú,[¢dRÔbZ$€D€Á`ö­§××o¹÷wNþøÝ÷ºg03˜É OUW÷›é~wy÷ûûó=ç|O{dM;¦Õ&æ&3Ét£ô—†øÇ“ß–øÿ–ào5dF7õüš+ûÚ)Еš®zî Еy'ïú.M=?Ì–!Ìýß{üØG–&j\\òÓOý§®|ã•uoû¯ö«ü{'ö°¶Usi-ƒ}}ðW3:°ºU±ºUñòåí{ùðÁ›Ãš}ÿ~fÛÛ•ðeøžÃ»Ž¸§—<ß™Õë\<ûÊÍ÷‰ý÷ÝH^¸r} X#Ô.ž‚†ÚÍR%žhE› =ôÆηO^þŽdߤßZ´ÓRaF38=• þ ÑÇdæ^^fÞÞ¹ÎÒ_ü¾GÚ??Óž¸¼ÿîåþÛÿ÷$÷çŽ[Y”øùg/}ËÞ_;<Ç÷?²Ä÷?²ÄÞ¹Ööî56‡ÕÖo¾xñErK°¡˜'Üs6Âï4@ãN:c×ÍÊ·Ú-£N)"ITëä)áÔê$OÏø•ëC¦>ÅþûŽ±ñÕß½á=/ž}…Õ•ëÌ.ìÂÜÓ{ú¹—""…ã… ¥¹– v‹ ©0 QÜ¡VŸmI½qY8qT"Næøq¤j-¥²à.ÑBH%ª%.¥»—ÒŒ:•¸ïî¶å±‰œ|þµäÀî÷Ý@./¯iš8PÉÒKHP'HAH’‚aÑÖËïu:+§ÞàíÃöºdßÈ(H´b¤Ñqòô]µœâ“ïÏïŒ÷¥‰÷;¼ëÀO|ðØ2šâöŸý­süo¿qæ›:÷lG÷Ìðéìç#/±«»#ÄØqƒ7†Õøë—V¯>w~ùì WN'ó<ËuiV®‚S[$QacÃ[¾ðW¼œ¸ÓI,>èÚ=åëã/,†I°” ‰h ÔFã 4ðòòúp'¸ûÀ}¼øüÓ¯yë“Ï?Ã{?üI@Ø¿ww[T¢» V¥D½Ä¬4´0“(âØÒ¤#­W2Y™ÄÿëjíVÀ=D­£¥-pJuJ„Ò —¸wÏbOcÔÉîþâóÏp«úüvwvê Ç•–;º\/ ù’˜YíjZÖæŠËZåôÅgàÄEç©×¾ÿ·ÈnKö©‰-%zS.²[xLö5cµçÉ…=óäÿ¬ ½}ü¾>ñÈ¡"€;[ãÄßý…—ø͓ߘXæw,Ûñý]~òæûŽïÚ~"w€~uk<~áâÊêœ_¾ò⥵«žÓ€›}S¶ÄXö=Çu®¨ëH R‰u5·0¬Ÿò5g!pâIç œ[D¨\æÕEk«ëhŠ* ŸJ’58p3ÜãÊÛ­R cèVØzñùgxü{? €Æ¨{w/ö._YÙtñB›9„Fö0‰!Ô±6 Á¶*µÅ9a‰Ç6/ÉFûaéÅŽ¢µšKÿ[!¢… …‹—¸âˆÇÃ÷ö&Õu]qúäWoé”ÞŽ 6òE& ñí $Iɨ’ âRŽ=•sò¸sðâxîÙîXܳp0ß}“âžÂ™§ëžY~Íi½i¦ñ’|fÛ1Ìý‡ßûÐñG,ÌOü»3ËþÆ¿8É™å·v-ÿ›Á;<ËO~øz×d6À6è¯nÇxaeõ÷Î^_>}}c•mɰÖÀ¦À†9ëâl_oY`€1§*z<Â|Œ-õñù£øÁ§¦¹µßvò¸Ç£§]ªÂe’‹‹IÜÞ}‚ùUFUEŒfÎáƒ{{—¯^âR¸P ^ RäB)+Ì$NP­µ;ZJ9¶ùŠDŽ#õ‡b!žÆÁÊ2„TÇd©pO%H©"xá"'Ø¿gÆ\pƒKgNQW·Z¥Švw ã„ã.b"nˆ›#É‘„cÁÅ$ºiæV»Ê¬Çn±óÆ~+ãÛ’}Kó„åHì …”´tGqÐ5Ë…=æÌkãò‹7•}MŽïl{ñ/ÿ±ãï›ëLãý/Ÿ\áïþâËlß“v¿]ö£süùâ½÷Íåhîo2÷ß;·¼úo¾~ñÊ…Õ­5gZþ;"kûg91gË…>Y%(O¥6Ø¡þ[À¸®©‹6i+L71à¶}©>ù¯8»Ïµ_¹øÈEƒYRK“JC<‰XžQ€˜¸û`0N3332†nu”ºª¸tæŽ>Œ»p`ÿž~ÿk1WGzR˜[éî%P ÑB ø(H»T•cLjõFG¬BR5T ‚YÅ4ªJĤp(Üò xÔ ÅÒî]mk.óâ™—¹¹ kbÅ ?§ìþ»‰ˆ™›&˜¹`µ»¢I\ª —§[_úV§ÿ§.ÿ禓oJö 2¦,[”uE[”N°F¨S³p‡8óBNñ5îÿD½§w|ßܾÿè{Ž½«[ƘË]áù?û;Þt-o%ûð»øÉäу ß¿6÷gÏ,_ÿ•¯¿xmc´ÁDHš3ͺ8Cƒ°¥žåÃTØ4acS`g+Ô SÉXª`ÀWöàwŒÿŸ>ö%àƒÈÆÈ5ÖNÑq·ÚkÇr_§›¢†»!X4N ÍÃQtz·Å×Å3/³÷þ‡XÚ½«‚–<:¸" i¯Ý$UD *5kKm‰õbKleU–"µF·”(¦bBáPˆg`Ï£2ÙÙ/ÉneE+{îÎ`0Jd%wÄm¦³ˆ ’%òÈÚ®±ë­Kk¹˜Ïú·hØŽ÷ÙïØIöQQ&£%‘¶fýµ®)=aGqLcýy™Ôô ½ÛwäÓï9r$¨äxÿoÿÂ)~û¥Õ;žÔwìövx±Í_ûÔœ84IÄe¤TÉüéW——õ.œ¿>oB# ÊdÞdÞõeÛõJþ>pgK[æl)l¥f,½£Õ¸GݯI3W±ƒ{²ûëøbŸsö<å­K›®íy黇Ô<ßjbb¸›Fö„ qÇñÁ`”&Õ~EëÖdì=6õETöì^ê^ºrm÷ÂÅ / L WbtB-Q Q-Œ´^\hýe™ÒýÐÒVH–¢ ¼@B+È.EÄ=..Îw’mçÿ/Ÿ{ù–1 äÕËš" Ñ¸2²‘¡nžõOÄpŒ„‹ãCsŸi'g|hßò}¿IvÛN¾µd_Õ¢Õ®i̳¸äâ!“}â7’}‚ô>û¾#Ç>üàž=ààÎŵ?õ/^æ•k߉÷߈ ð§Þ¿—ÿø£‡hÊDq\›}åÕkË_øÚ¥ókÃjs"ªÓó .ÍÎoùÿ¥YĈ2$1@&gC©UCUÆPÍ ² Ø…y|å‹y÷ožú»pä.#EÛÃà-•¬¦:áÃÜ ÔL‚Æ•%Ë[q2ö&E¢îÎââ|çÒå«$ žwш§BL¢yŠ…kY?ÌÕ3R÷—%V+•NÔV(Õ܃Çpbîô£@3øÂÜüB»nÀÑ`“õå+¼ž`†‡U­¶<…ÒÐf÷ws4˜xí"-×xèuœá·,w$ûZW‰ó-blSVí0ÎÒ]nô&d_“Ò›·FlÂr…ßœ³2ÎÿÅ=xü¡=³³“Gã÷ÏnðÓ?wŠµoBË;ÑöΖüÔßÏwÝ?‰óaT›ýö+W—íë—.lŽë w†¢lIãÖ»ç6_ò×v€†¢ Í©2œ„ Á%a\9ã¢fìƉz®¢¾¶ž5WÄN<ƒ?yœTè,ºnfq}Ñ‘‹DKŽ¨9æ6ñŽl8¬R2ÜAîä¬/_a«¿I«ÓÃÌ™›_h ‡ˆHt£@½@$¿–Ì=´B©‰±V+µFŸm‰GOcuZR’›¿@ˆêDÏoæç[.Jr¸|öö»¿¨¢E;+˜ 2óÛ¸‰‹åÅ@M’ûØÌ¥4_«7é2Ç wÿŒÜ­½.Ùg‘¢*iU#ZèHÌÅ=f9Þy÷ŸgGªO„9„ÞþÙöÒ_úðƒÇww[­ Oôóp¿ÿ«ç¨ßÆõüßJû¡»ø/?q˜n+0¹§¯,÷û?ûÌ«§¯mŽÖ˜0ùÛN–ß²àϳò‡{Œjg…‘Ã8À¸RªBWdå_‹T³kÔ«=Ò¨ýăØÞèú¿î‡úpXc"ö|<6šŸyÜ]DÌŧ˜pðÑhœ wТ„@¦Ì^k—Ͼ̡‡žQæç[Þ§ˆMˆØÄ{'%XˆêŒ”¢A¢ŠLR¨ãàæ™t¢#—èj÷„™Þl9!ö®œ;ÅíV§²Ý%M+š„Ñxœ|qGÜÅqÃsÔµCtÆ-—²AËâ7µèžÉ>ItôTs¼çԞÂz³4c¹ß?¿ÿ'¾ë¾;E“ÞöøëøÏÞýÎïض-ÌDþ›8Â÷?”‡‘4šþ¯¿vùâN^> àe{€6ß-ÏØÍž0PgX+£"12×Ne0¶Dí­,ûÝOÔD=n“âTí"µ®f À•à¿{ןü s)gœqËÑä¨y"º`†¹+¸‹;à¸øh\Ý·Id¸¸»]Bån}@É2`ÏÀ‰oJð-‹{ºk„8Ÿ'ó¼†ì 9ÞWgvÒŇ³@SÖ;‘éFè}⡽÷êÑý‡Tr3ÏÆ0ñ7áU~÷ÕoøÄ߉ö}ïšã¯üàgâ”Ý?·6üìÓgN]\®¸ä^…M6Ýš4žÒ£ï°E~3†ªÍD^[d,PEË@+'•PkR1CmbÝ’T¯aý1vð0þÂüÄn FW›ŒVQwIE®0³ü¾.Ž;Æh4N;±SvzŒ·î ¹zîÔ6ÎDevv¶½±±‘Ãx±ˆkt„ôÌSPMêR«w6%ú¸¯MˆQ 1MîSí5‹¸Dă;a~q¡³S‚nùÂíwç¢ÝeçïŽFcCÄ wuqG µYˆe¦,y!(K§¿Òüåçù3·ÿÚ¡¨‰Ãd_QÑèx g)OàÄù.Mªimÿ¬ªÌþûï=rì»ï[\‚üTœ¹>â¯ü?¯pnåãz'ÛgÞ¿›ÿâ㇦-vÉÜýÅ«W~ù.1|SòŽ¿éΦ âlºæI?@ß•¾Â–C²Ü×È=ó´`lFm-ê$Ô±"ͶI¾‰u[XRŒvn?q{¡aûOÅ?ŸÌ§x—±œä¿â@~Uö²îm¨sæ4—˜šànâ.ˆFcÛ‰\ pk[¾pêœÍ/.t6Ö7‚!c×¢yîÄ …D­EÕkG%Îu é÷]KÍà×0Q]õè"É®?":;¿Ð®ñ`“ÑÖ&"zË+;=v†-ãñ¸f2@ÜqQwG½rCF@ì7Ÿå7l¯Kö#E«¤$ÑbDÇ„] ÍTg½¡¤w¾™Ö3ÛkÇ…¿øÁ£Ý5ÓõI3Ïé žüÅ3lŽ¾SÜóFìÏÏ^þÒÛϤ?âòÆhôOŸ={úÕÕ­ehvyŸŽòÚÀÙpØTØôÉ(0gàÊ@3Ã?J!O§ªu=C]ŒI´°Hã[áG#vaŒ<Œ_ÚƒÓÿžwýÛZ³¡ZTTÔ÷œÌ0˜ŒQv|<×v“p;œ¶6ö7¼™ÏÎ/´‘sê2v%ª{D%ÁÜC¡¦#-u®#S¬í²-6õXçÝß ±ùž_CèÍÍ·' «×n[·òF“Kriò¡YµMp'DêoV/Ìë’}å<åÈ(GcÚªtbÌC:ʶðZ³ë7Ê=Cs¥¿ð¡£Ç:Å„­à©g¯ñ?é"önž7dÿÉGðãØ3Mk¼º¹ù¿rú¥:Ù†Ðìö°°.°A®ØÛDØ°†øØrcœaRÆRoÜÄöí6i£OÚjc;Øò2>*°£G³›ÏSpüŸïPùyò›\*ÁÜ5º¤ŒŽ4J‰›d4½Ö¸Ö6V¯±Ðî¨ôææÛlc8àße‡àUÐvK%õ‡½*’ººz–C M*!€dFà:ÓëMå•úk׸ÓYŲsÃëªjÆN‹$‘ìú»+5‰-çsŸo?CwŽáž}f”’hš‹{Ü—¿aù…vÿ‰`gïñCó~ü‰#–QÕªäü¿~ž_zþ;ÍõØ®iÏÎó×ÖþgμäîMYî†Àº“xª4åºÆ¦8›ælHbH`¨àCÕ«©û3¤0È»}XÃö‚_ ïö7»ùOñÍ=+ ó­L=2&ÃË‘³Žò·)VËXº=Öúk×XØ€ŒQWàHƒao0íÁÅÕÕÔÇ®T…DkD´+ž*)(XH‚‹G° ¢êîÚjµÂä [k×nK˜Ý˜ó!hª›L 9¢ø—µ7fÓxÒÆ{óîdže»’1Û}sªSyî\Ú«MY¯Ðûøñ½G?ùоƒ"yÆáê æs¿x†çο3Ä;¾ÙUøë?t„ŸŸzNÏœ[¹þÏïÜKä½uÖuwÖÐüsšLõ…~„~ˆ RbèQ[¯'ê§êÏêDŠŠ¶°Qq#©°s·ç[Ûor[™„â!„ü}³úŽXÛZ»6ý¹Á¨Šˆ‚D£@0Á ¤ &¦ ñЗèu'©A½¶€ªªYÈRH®9J·PEÔdû ËÜ Àuu£xzˆQR]ç?ÈJ×·+q~£v{²ï:±ÕhôOÈ>sf4ÒµDO‰.šz~™äø97fcÙ?ûÄ‘ãOœ_„LLŸº6äoü«\Z¿÷ ¸E>öÐõ­uñ߈¹‹äé¥921Þ€ŒŒ¥;-ËÓŸ5)Š"VU2qàêÔ ®ªÁCP'…(Ñ[AÄT¼N9À³Û0‰! h§;ÓÚyÐþëxé¦ ƨcnºwɳŒ¬4Vãõè, n Ÿô IDAT7”õÞŠì+Êf ·7àwº˜ugV`Þt»¡˜÷¦¬w¾çò»Ž>rx¡33yR~ûÔ:ç_ep=º;Y¯øïâ3ïÛÍ®ÜðÅÂLÁÏþîÛ_ÿo§ý•8ÌwKòë/^¹ü+_¿üJCî­;¬«"¬¹³¦ÊM§ž…)Ó?ˆÊ0DÆ¡™ê»1$µ”t'à?™ùÿ;ð½‰PŸõDNÙ×2E“úˆ1Þà¤j|G¬õwxîL«Z[Ûž¦ä“>DÔkTŠ ®¡ñêZc0Mâ*¨"¨O¦­š« ÚéܨÜx¯@Ã$|,´”ç¾IîŽÌÅSîB`‘)iú:v×dŸŒi£t,«õvŦ2Ýóbô0U=¸ÐÙóï¿ï¡ùvQL\Ôÿëé«üãß¹|ÏÊ={zú½K|ê±]ÌäVï©Û»þ’øÌû–øȱ¹éõÿò]ºðåS×Έ°ážw|U&_šÁºzÓ¦ ýqbCIŒG5Õì˜z¥GjHÓüýñ¤Þ“ßÎÝ~ƒ'¨ÅÕE@Îìcûçü©Ç=P†m²ïg~í<¿öõ{ëä;º«Ågß¿‡?ûþݼ{“u¿åxòÊ€¿ù‹¯2~‡€_Eøk?x˜]3wgcT×ÿì÷ϾäΦM̃ëo°õ¤lÐCFŒÆÊxdT­=¤¥uÒd×Cµúß2óüì.~[¼(Œk(£xÄñ€ˆ›Òøæî"M^âMY€×ã c²ìô Õ™‰"“ù‰î® Šä ØE¥]$z ‚j.E¶Zi~i⚀‹»iÙ*£höaëñˆ×Káe™°æV¸b¡..â*¹!UW%ˆP7‹N‘³ >.„ÒaåÔ͹é_<….Ÿ»‰ìkßšì³f8Ç„ìsci¦óÂìl+.ü¹÷Ý÷ðÁ¹ÎÌÔ=}yÿþ çÕw&ûŠ |âáþÌûvsx¡l®¹¹æ\ëØ?»­mpúúˆ¿úó¯²5¾wñ­jþC{yÏ¡.îÜýÿþƒs§6Gõš(ëk.ÛàXÃXweÃœ-ƒ‘3jGÆ]£ºÖ']›ßƒñEìͳëßd+§„ÅN~¦Û b¯L(1© »q%/b¡;ÃíÛIîí´ŒIU)[et7Íé¶m,»7Çј_§ ‘ø¨ª’'ÑJNÿy&=»ë1lûåuýú+RÚɸbâÓ UÄÕ#g4Ji"#C¼³%i0”›A^Ãô·ÖÐÖ qç@ÎdšÇryvùçv} è¶l×Á…ΞϾçÈCsí¢˜@òŸ<}•ÿó+Wîø$u[}l‘?ùž¥Ü¸Â¶XܨN<}v…——7ùìG¦ÿ~qmÌ_ý¹Ól ß9åÂ::Ëg?°{z~ýÅ+O¯ô¯’ãþ „5œ5…U÷Ìö‹çJ¿ÊsÌ_ ç•qª¨G[ÔåÖãMµëßÚÒ`E¼Ó¯:ÒR$…¼ÙŠéNð+¸Š»†˜;J'Kw·zÞ2V›{òþÓ={^›H¢§ , ÄÉpdqš^%Éß5n×¼^ü`fT£E«ƒƒ·;xãI5¬¤§yASm´]E©ÙÇ ÖM€'ÅÁoUÙW+Åd §EÚw~÷ÜÆÛˆuÎ5SyÜ›‘ÜÙíïØ7ðG=p´ªîθv~æ×Ïó/Ý~ïî^Á§ß³‹9±H§Èëâ¤`cTó•3Ë<{n…v ü…>@;*îÎr¿æ§~î4Ë[ïÖ¿ŒÂöÑÓPèëW6Öó•kçú"¹´×-“æ¬ ¬[`#›}O BÁpF_¨¨¶¨'í¹'žÊÂOæC½)Á¸Æ>f«¾xHj ¿&âͳpút³mw:Ñó¢&Õh€™ñzxÛr7Xr HF~>† ^‹Ç žfÛªŠ¨YŽÔD²Hi^zvV'ÝÍŠÐ_½Æ¾#ˆˆÌt{Ť°H5Ó*Ãf 5®Iµ%Vײ9ÓWžÿÿ'ýÏ‚ß\Ù×wŠîŽœÁ™Inpz¶ƒì& =2È ½½kï~`÷)Ùׯù[¿|–¯ÞZ‰èþ]->óÞ%>úÐ6£?!ö®õGüÛW—yþÒÉœ^+ò¸Ÿn1‡µAâ§~îU.o¼=§üÞÎþÄc»XœÉ÷`uPU?÷üùSHÓ¹—»ùÖE³»°î°!5›IØjYÞùgš þg^£Êó&ÿç…«ÈèñŠ]Zµdµ8µ’ãiu,€ErŒëL·WHãÿ÷Wï\o3±÷«Ž¨¸à*–ÙwÉ`• ‘H ‚(jÁ$³ò„í¸ÁEuۨ€¦F9/t{½DÞÔ& ¬)Tˆ7”(F±ñXN ÈÆžá8W‘dßÈ(%­¸s §1‹2‹6£·‡yÙ¡ÑTæ~ìÄ¡cì[$'óâÕû_åzÿµ»ócføÌ{wó]÷÷¦ëð$Æ?·ºÅZ̋׶{ÿ;EàϽï~Ú%îÐ?ý‹¯rnõÕ"Ü-•?ó¾ÝÓ{õ/_¹0JÖèOjü'‹Êz6j£ ¶*gXd™®jâöOÁÏ›üO_ü(0Àg[bŽˆê´ä>aÁ'%÷ÍϹ^_¤Ûë“øÿõ î&¶3ë–±ê¹ÎÆE åú›àB*!IöÜj‰YèCmfs€ ’ öB¸1¸ylñ­lgr{f&f)iܦ%Š¨ªï(QôºV«KÙ½Þ×õ_÷G/ ÿ.za ÝIö•‰’]~œžzsi{_.ò™¤ø`v®>óž#ïŸmÏLÜö/¿¼ÎßÿâÆõö³$ß{t–ϼw‰‡÷¾–ÑñÚÿöÕe?·6¸áF”Aùñ'Ž°{¦ÄÝÕÆ“ÿò,/]ûö 9üvÙ§ŸX¢WæðçÊæhøÜ…µ+¢YÌË|ê¬'a3$6=Òg«®zbÔWª®QÝAïÍþmûÔ§³ì¾kº¡ä^U•iÉý®¸k»™ Cwƒµ!@ƪ¢BÃŽ¸¨#$$"bV R½ aÇÜ$·'ä ¥\§è¢º=lý®C€J1F-Ê2Vu¥äΤ(‚3 ñ`>â)Њ2+×tpõ²î¾tÑåì,imE‚í¢ŠÖÍdŸ6dŸ7•}ÓQÜÊœ4dßùΞ?õØá‡z­Xùéù'¿{•öìöBUáãÇçùôKšÏŒþ„¼Jæ¼pyÍ¿ræ:×û¯ÝÍ£ ŸyÏöÍu0ràïüÊ9þðêÞ.6߉üØ㻦÷î7^¹zÉÜ}„M6ÍÙ””ûùl%c@‘óü£†í/—H;cþæ-ßÜàçspáIÝ__DÜZB•ÔܲêVÓi‹kpIQ€ EQÆ¥Àýµå{´ÉêåvÛ\u+“š¶3Œ^sJH;\_%?õ;Ž{Ãpi‰éÉï°Þìlkeùz%ˆk0±,>j  ‰Z‰h»¿RtöÚ:£°" qAËzµeQg¥ÞIeßMdßRo3¦k˜}tßüÁ|xÿѨ¹²oTÿß\à·_É®{· üð£ Ó˜¶Ýüqmüþ…{úÜußÕ“ ŸF€¨ò±ÃY˜ÉZ'ÿÃÎóÌÙwf§àgß·D+*îp~m°õâ•«’ÕyûdMw6D2Ù"[)妞–25»Huû­þk|\‹ÕµJ,³êVð@Ê:}`Q]âÁЛ½©çæÎ%÷K;Òî;±:é/Øûle Œ¸qê±Ûm$;Þ4§_÷œnѤÐm­\_Éj% âÉ:„Á¬Ž½€VjºÄ0«—­(V¤w} ”u¥Tt*aF +:­ì›oȾyÉùýyqæ fÕé~äÁ=~è¾¥ÙúkýŠ¿ó¯ÏsjyÈR7ò'ßÅ<²ðF¿?®ýÙs+é÷.¬Ø¨¶i­6MJrúBàGÞ}€wuñ&LøŸ¾tqº¸¼ÓlO¯à‡Þ½0½¿qêÊyσ:¶&€8›}ƒ¾„ ~UF-Ëå½+=ÒÁuÒük‡p¼ÉÁ¿ÃVNIj¥7lë Ã¶ê–@ž¢‹n{Á"ÚévoÑt÷ú‡Ú™܉Õ×ØM^@^ÆãLGˆúŽ±g·µ»‰I«˜%Tfæ3Ý^ È+^VnVA¬.\4¤TÅ¢#!™©\Æ=ƒëd¤^Õeᔕ0¡—„Y1ææ5»ûS²Ï3ù7[¨Ì~êуÇíž]œ°õ_¿2àïýêyfÛÿü£øþwÍnbôWc{úÜõÑ —ÖR2WòG ì¸sÍÂ*|â¡ý<¼gnú÷ÿëï\æ '×îê½íGN,f²ÙáÕ•þ晕­ÉÓyÚh÷™ÐDzl·Ã F†•3–D½•rS‡s‘Ï›=ÏÿZû¼À£Û¼$kìïDiÍs7 à)"¬¶±ÀÂL·WšeÙ=³Ä`ãîzOî“ãdÌ—¥*ý57"šU š÷µ”,4±I,[wõQ¸ƒõº »AfºÝ‚&þ×,6Z˜KD³vyËR´”BË·BH[!ΙÑQˆZ[ËRÓУô‚0GbÁaÁiD;ͲxÇ\»Xøôc‡ß½§ÛšVö}é¥u¾øâÿéGöóþ#¯eô/m «§Ï-N^ÝÈòDÛ_;k³' y`¯<¾aúÿô™küâóïœæž[Ù‡îÛîñÿ­Ó×.#‡¡Â–)[2‘ílYÊã·L‹SÕ#êÖÔß—Ë{ïmÏ›Âr€=RŸî‹kK’T4¨§‚§˜Éw/0‰*^àD#Þ™év iЕËÍ Ì/.¶s@£SQÄ 1‰ ÑLC(¡²(UUX=ˆuUÉÛFãú;³;T{,×íÒTöÍuö|êуtËXXsž/]²®à§øps_¶ýWWúÃgÎ^_?»º5òm€‡_Ó{©y½p€Þ·$8²Èä?ÿÕëüóß»1ìy§Ù}‹-öÎE gcTWÖkäáœ&㺲¦ÿg”aRFUpªåÒ»Z¤ùÙìú?™ïõ[üÛvá¤Àql43T:u0<ˆÇ Xãýzá'úzÎÌ/.¶' +—¹ÛKß¹¤”¼‰R›GÕ0Ä1õ©ÁäïD£×V»¨º¸»3íÄ­‘/Oi»k%­{Zï`+^aÿyŽj·×+bŒE]×÷èH!ªÑ¡H4õXZŠÉ%¨b™Æbº¥E¡…Zj6£žº–ûõçd"ØÉveߣûæüñcûÞUtç)¾kw.+žü›¹ûKË››OŸ]^¹º9Ú¦ôsùóD16Ÿ@“ A'àÏùðý»eò~_øúÿûWî<d¾x`©Ísçû¯é |»ØwÝß›ÞãÓËý ‡± Cš¡œÍ00e„Q"«ö¨Â€F¤ KyþÞSoEàO À—Ϩ±W­6‰Ûƒws^‘ÇwY!xbŒ±Èõ2ÙV.¼r×8‹ÅÍ €a.h³ç&\wõÚ̃FÇ!’c5"…‹¤,Ð9™R‚x#Yèu½-Ë6·îq{­Ý0;@„Å¥¥™«—/¯#¢˜à%Râ^&ñb†A©…X¬kÕ±hJu+&o‹Óè ô€^£æ3ëBè~ß{Ž½ÿЮûàö÷­Jf_»º¾ú̹ë×Ö‡ÕhBzh¾`mÔSr|Öœun­œæFô‘½sò±wí›.0¿õÊÿËo]ºí=Ø?[ðcïâ7`_~yð¥‹wuÿÞjöÁû¶€—¯m®HVÀÑÌåSe`ÎPŒ!0Š‘±Cµ5&u[Øш½Ðäû›y0oÁE€†ìH¯¬t`-Ó(&QÝóÔm—ýØ®)ßp+¯ Jί½¸ö}²ïlï?Ò^ó+×7×j §raLŽ÷Ç *Ê ên ®*¬Y¯ÏoÈù¿ÅlgüQ®¦Ú‚à!‰¦q- ÜK Ä)„œËØÈvýü)îc“À³·ž¦ ßæ»uo„s0€8 ¥·tÔôá¸LR–W \\¼®kŸô(eû®=€|1/óàû> dyð¹ùùÎúúÚ¦¸.Zˆ{)x‰SbÞdX¨*ErQM´å. 2IL ðýîýžVÔÎÎ8éâú`å÷/¬œ{u¥¿ìN-0a,06¡Â¨RÃm(2»‰"îS™â)û¼ÔmÅ~ä`/†<ÅáÜê˜ÿî çTFTá#ïšåÇ[äÀ\#ÒœOeÆ]Z§×Š<¸Ôà·N¯³ñ6vh¾œ^ûé•þ:B ÛàÂ8yÓ%P•F½QcE'Oê¹~â-—óŸZvÿ_x”+§äZ{^zå@·ªÚE“Ä(F‰{‰P¥‹âV¸çææ;;åÀ¯Ÿùž0VL8wêºvA²ô. žÅi¦sç©\Ciy”hθpQ³<¾ÛLܲ@MX xJµyS`|/i@€å /Mvw—vw7ÖÖV¼ñ\¤t—–+…[(Ä¥¨½¨…覥ºj RXí"˜˜â•9ÉÜÝήm]|öüõÓ—7†ä\G%BEÎAOvŸÊ…Ú·ó›Qœh;¨8…ßX %óí²øÔ#‡Ê âW6*þö¯œ§2çS'ùÔ»^S:<¬Ï_\õç/­ym&?ù§Ù‚_ùÚÛs÷ÉYw¨Í}P¥‘Hö¶€Z„ªY+ÔµŠŠfHi3é:xø­|kø<œØ#ýÓ§5iKCèhËÇ!%A¬H!¨•¸´¦‹Rˆ{\\ÚÝÝ©”1s/@kržRmLFÉd[·¼xp7L]JóH¬}\‹G.Ô–²"ˆ‰¹¹`ˆ˜»{]×iû`÷æl­^c¸¹F»7´4óê+§¢@áXŽ‡”R<”‚c-c¤ÔQkFǃí"ºXH†ÔbZ±‘ Ãùµó¿pßB÷sk[k«ƒF§,{µ“É'`D³áÔ"Ó.‡ Ùk‹ î”®7®Ò-cñ#ï>´»ƒNÄAÿá—/ñƒÌóÉã ;ä½ólŽkÿêÅûÚ•5¯,k,ß;Gs8µ<|Ûvηc¾ùýq]É„P Ôžï}%FR¥6#yÀÊ>Þká~ÿ³ßî ùFì…G…Å“R÷%-Ž´¶Vv+˜UQ,{º¸–ˆ—î”`¥ˆŽÄÅ¥¥™°2Ü\ckõÞêInÌÔÉG¤ÙùÝPÌ ˜9ÑÇÔNtlDsIîIÅ@Í0SqCòBê”&J±l!*w]¥píìI¿û»UÙ½G zŽ} wo´’[ ÷Rb(4uB•ZuŠ·QÛê«¢cõ44aKŒ°6ª®<iuÝ¡lf«8¹æ©‚†|FbT¢Tî¹fGµÌ=$Ô…l“}2S„ð©G˜)BœÔ´_Zó7>yh"]8­u_Œí¹K«ã—®mX2WÉ]Ôâ‚>´4+“ßûÕ¯¿}˃'bŸýq]&²5{É…dJrÅÔ1‚­üÕD£þô–,ü™ºÿ¬,JÕêìæ.Ýj§@=ŽQc!ÁÊ”¼ÞiáÞÊà÷B¸{ÿþΤ†ÿÚÙ“ÜË-Õ© ‚¤:å  †Í3¶fÁÜ%˜û–%&/¬Šµ… 1CHžµó"€Ûp«_m(fæw³yýò]Ÿä…Ÿåð»¿È­Á»÷î›»zùÒB)"%BK%´„¢[,Š‹¶vªJ:i 3S§Šq4²¶.Mn³JBrõ’ ÔžI¦±7_äØ?5r'Úü~sJ!§'çÚŠ~øáCG{­íé¿ÀT`[hX=wqupzu³ÂsÊP2§à@h‡`{{`Ù-æ+g6ßrOöÝÚDõ ?ª«ÆÈ\’`b$Ï•– Wøl—2ƒþ­[@½íþ=}Z7u—Öµ‡²E-Ì«R¥¨–¸µZ %™÷*vïÛ7·³ø‹ÏNÓ»±îün&Ó"Êp«_åPÌq! $Ü «bmjÁˆ•GÕ.%ù‚É Ì@L ˆmllÜà»ö÷±yýöÅ/7Û¥—ŸÃR†ˆ™ù¾Cg¯^¾´ŒSŠ{K¡t(Ý¥%E²VÜ’Ymóu»½^dÍà”–˜×f±Ú͇ Ñ…`¹ÆØÌIMªoì áäNMvXD„èJ«Y- Ujò.åîx¡ª?ôðÁãóârq§]\ ¾zqeýüÆ`Ô?× 8±Éœ€û» Ûøü¥-ocàÅNœÆO[U]IÃ>û6™l!—A8ŠÅFÿjèžöÁ‰íôß[É2ò^xTXœ•ºßÍî·ÄB4Ó"˜•8-ð6J ÷9]žCg§ @©æÒËÏq/@oqß ¯3VóÆ-H“îvÃ3ÆÅÝ$˜3¨=Jl»˜˜Ôî“$ašÏà—üö××Ç;IŠÙ]û¸ôÒÝŸd=på•Øì TUö8Ø{ÞŸ‰ˆ”.R&¼¥BÛUË*Å–Æ™PÐ Ã4OßV¬­sÉâتºJ…K…2ÔDËò tm> Ï J%žcO˜æˆH-ÇýÖìÔmÏ|A’,PÈ'xlW§5{óìg×úëÏ]XY¾¶5à¹x¨¢ÒôvO³¸#÷-t§Õ‚OŸé¿mwÈÀäZ7ÇuՀߥI'‹àÖôQdW 7‡V|Ü–©ûJªö¼Î†Jûí(Ä5æR¢Ò¦ñ¼È}IÐD á©Áº±í1t ó4v!˜x²„’â™Ä$‚™{ ‡u†…÷ IDATV÷…Þ®}Ük¨váä3ì?öEYêÒÞ½sËW¯n!ÒÂi»IÛ”¶ª–&¡ZWÛq㸙¶ŠAòzPϧA•J…T ]RÄ“Šæ ¦Jr§N‰Z•¤N"å‰ä.R¨WJÆ– Rjê{îßs|w·½{§Ûoîvzesù«—V/¯ ƃÉCÝTjÓÔTˆSX“ÂrGbPÝ?ÛQkÊ0ž=÷ö)‚LC€qò;º:š½$ Åßú*‰ÍEŸøbãþ·´NÊ:D/baî¥hj¶6F¡Ð½XÚ»w®(Ëm÷ÿä3Ü+®z;€ÑpX›{s¦øM†$ó> Ûšú] µ&šÃ´»s%æÝ”ä¼0 ^A}õa5pê:Õˆ(nªý¿2æ"³fæM#y¶±xM(L¸ !d1 ñÞƒ×ÝÿõógPϾó<Šï$AÑÉhTh¸’‘ts…@YJç5O½ZžåÊ©—¦†X’ðK¢>d$xÜÉx\-øʳvÖ&pÍ»sâ.Ÿyƒ£$IjzKËÅöææJ•SV¤j8G—ˆâ.["ÝiÔ[‡ÆB%!£T¥ÖJ-¤`h]C q• 4-@“lM¨þSƒÊ”zÔĘ+;³³WvfWHæíÀshuž¶Ân\â~EB€ÓëÀŸU‚cìkçÑÂ?wé½O º5s»WjE˜)9z¼%ðâ?c@†@•MkÐVy¥Í‚!¸þÝצòÊû¸ÅNKGløXÉ'Äœ8$ƒ"i Õ ¸·´\$IºÛœù̳ïú©m” k÷ Ñd<®¡Åâ ä!Á À7Ê „g‰Ú¸Q[‹’U!‰gñ€q ï ì˜Ä àç@u}L1¡èïÁöÕsïê†/yüÔß@=tìh{s}Hĉ‚R(RIà%¶©§$ªë®“·¤Im<¤r›ër5‘È:¦m­d¤0ÐYM†¡¸¤0‚ÒØ0¤ jÀ ÁZA¤Œ„ˆ <'LqJ(AˆHaæ@Ÿ’B„vûˆ†1‚—o(Tv%¤ð{;i-ʆ‡5®}ƒEÐekæw³IÄBƒƒaT`± ÕÐdžwtÀ «=ñý¯ü¸~úƒ0nq³RrËçfK‘UŽ=û„ @»^U‰;Ú_¸ÿ€âÒ™§f÷.¤èïÁõ a2U˜»ÿLêDÙ)‹ŒÄa!ÛˆÖ¤;±¨¥I£ÜMˆ#x<È{y„<”súñÛSKûÞµ˜ílbíì·08vÌLn½üÒK‰kšX )12dg,&ñŠÄ'd©!ëËoäuæ´_÷šµª–|6–{ëm¹Š¼1´ë gp @³TíÀÄE•ÂA…=0* 2P(¼(* u‘†Í ¨R™¬¡Ò0¸þáe*K„DG Q‚ìéÂ~îâ{ÿô‚°xæØK¡Õ’ÀRàVˆÖÊ2ÃxÀ˜\ €ª tú$Oý (ÿ§ÿ«3ÚiØP2µQi"¢Ø³K&% ÅÜ(i¦@ ÕØFqràÐá]÷íì·0ÛÙ|×7²pÿ2×QO¡áÍ©ƒ{%ïžÔ¥,4¬Ôšv"Ty%C^¼²ä!ì@â äAù ðålV{ïu1$¤µ´ï]ðú3ˆÁ±a%™éð±cKo¾úÊ”@¡Õ—)ƒJ&Ä™ITm\z‰êlÙd>âITyOZcçѯ÷*]¿|î¼&“˜,‡Ëev@˜@¯€2(jÇ`çÀ6¸¦ð!AÆŒ‘,óü%sxÛªª@,8;¯7ðꃔº¿}}Âð7.O vôíʤÔNBbŒÕÀ«`4Ëp"UDpˆ,`ÄÂd%LÕÛøò²uÝýÿ>×ú´Õ³¯É¸ÆXb©oE ©¦ ÍÉÊH 8|ìØÒ콯?ó‡·¤K7ï½–³YÀB¯#ÀÔAØÕ“÷–¼V^M;¦´Rã"a±óŠ!òÂêDÕ ©SRGsO€ŸM&»þl{y¾»ëò™gv­áÈ]Ç{PÄPM ÈD5˜LÀY£š $ã(‹"Ü÷`½ÞG[GŽê׸__ºÿ§üìàAÿʳÐÏÂ/®­ã ( I¾]ñ¢ÉkTÖ tŠ)ÆPì¨Å¶alÂc“ë$Ø Á† 6Åc À¶F&¢(ç=îNCóô.I/Ó42VT±9u8·ùƒŸèú^emÒ@TAIK-Ú¾X ‘"bXÁ¸)̤˜ãOà&<àûMæ÷ö) ¿Å“bµShÇÄì#£3SBÒd¢š‰"åLTs2¨¦PÄGî:Þ[„Ó³M\>ó nE—‚™ë¦W„“_)è°¨:auòDð,VŒ‹„ÒJ-ºÊ¹Š–a&ïEëÀÚ@Ô¨Q%RG ?ª¢ÝŽ‰ˇï¿%«¥êñƳ„ÕO„Ö¬(ìʾ}õk×f1R¦ÀüÃÈHm",I“ ‚V­50u±‚]ÝÒÙlCƒmý,>¥OáÉPˆàÉϧVqA“.|bAõ”Ð"Þ†NµR€HÂœ$¦€ + C,$A`LµL»¥­ ÄKªàƒÝb—˜äùËÓïdý½#/¾5ÃÞ9¥Ý¡n«¿6©ÖDÀÒ„„€X‰Äˆ¸‚͘­1ÌÙ ²z‚§Â®¢ïK/`÷ô§Uôi«Ÿ…Ô_b-ii¤±M œ‚$‡"G˜c‘HA¯ìÝÛÉŠb7ûùƳ•[K/¾?Ü•*Æ£QE€W‚SeG¤ T˜qÞ;gO¾Ê áQ¬l²Zyæ´J¬4ê„Až¼÷ªpÄÔ¨¡¡øáÖæn™RÑ ï.ãV,×›ßøHUqôøÝKPMæL?))eDÈ!È=!8eæÄ’âˆm¡s)êð·ö¶é©ûÐgñ8€'›E Vjõ³ÐShÕ…$CøÊÁ±Cã€*£˜*aBŒ1;0Ø`‡ÃÈ`‡(Œ­‚bBE )¼È4 F‘(ïmgíÅS¾ðÖû#þ_ÈÓ&»ox'ë Ê·¹òR%¤P$0H¼o`VÂv2˜xŒ5ð©ÇAO^÷¾Ÿ¼Ê~¯ŸþyemDMdDc†¤§~¾w‰“R†‹•jrôøÝ»õ&âÞüÆçq+:”w—Qô»77Üڜ̎ b mØÞ{ùFT‰ž95Y­Ì[3µ’P€$T w¡QP£ÁýwJê¸õkWÇ×3Ààè‰yrìÝ]Õxˆ‹§¿V•+{÷æYžçJ%3æʉ(‡P.ÌSg(q¢q“°m‰˜:^áÕ­>aítèÈš§éç«„OÍÀö F  94dQÕ Ju˜)…)5\cÌŒ1€‰SL!˜±G)@­ §2g"0Ï+inµ’(ï¤Q*ª˜5‚—¯½7[¿“œÝªpm€Ěh¥H»P$DH ÈDA!cA A™˜©ía0÷ߟ Ù€ï3域þk§iu«Ou¼Â-Ó$lhì %N…9»qï‚4)4Íò<_Ù»7_èÐÅÓ_E5Þ’ Žž¸~sDX¿vuŒ¹®êî×pˆ « !~âI¬ŒZ‰ðÖLÙ¥I£ì+a"ï:–÷“RC šT„r£áδ©¯wµ0ïÝ[/@ñÆÓÿö¦>rüø0/œPd äP äŸ"9CR#GÔDuemíÄLŠã¾ý„S'èíãbzêf#Ð,ÁYAS Q§ Ê´F))fc¦³Æ¢¤&ÐVK§Âe)í6þDóŒAÀí·Vï繋4^oå½þ@_Ï^œîþÿÁNÞÇuÎ…Üá…#ó©âˆ8†w`’!xõ$èÔœÓ ß/†@1Gþ÷Ó¤píÄÔo?ýDršïÝ°‡‘…b M¿.AnM@:4u-£áΔ@ †TçºK QÐioÔ1‘g_ MµÅ@Ù¶gÊI€@ß5ðÞÆ€…64>Ði xò›kkÓ… 38vªzK×ú…3Ø~ëMs0ðøÝÝ$I2…¦ó{âÐB9SG’:Ѹ6u”—c;óLð„'oò̽@O=Ý:Y>q¾šÂ5Kp[@ãu1D]1ê,Pˆ5”¢1>vD,_±ÌK: ˆUh}Ñø÷çÞûÕï$Ï\ 3D½í¼‹P&ª"§9›3€Bœ|ð¬Aj±#D&‚­º0aVO‚>Š®€¿X#öÔ“OÖ´ºÕ§™¿`òrlkSGN4v$)S!䀀aï"…Ó?I’ìÈñ»»‹Óû­7±~áÌ-ëϨ*6×Ö¦˜Çÿ¤á享€oã†xö 'Ý¿2VÓ¤ÂùLx^À¢Ž•‚†‹w;€ ·þ ïæqÈ­Y±W¿ú¯vW™™éøý +ƒjP~¥ `MA9SfJbŽ£:‰l#+fÜ×^ÀõP`ñövÀêgƒ!XC0©á”p cÐià&3x[Ã×%Ô´¶`ãÈ¡ñ'´w×6QB¼·•õËÑbøè«ëå-®Èöu~;>©*Ãö`§̹2½nZ ´(ÈÌy¤"H¦‚8±ßnž˜—ã/Äè.ð‡ûÚ4L#+¦N"s1Ó¼ÚòùïY- š‡½Œäøý nœÜöþ­­tÀß®ÇÿA')p0:¼z¡¿¬Ü°¨cÀ3Ás>Ó¤’\+Ÿ`²ZÇ>’Šà‚gV'Ò8Ù%Õ P ¥P·~íêè&àØ­áPÅùÿ;k/àð]Ç;Yš´ëhXHÌT“"sðiÃUâ(‰òzl›´Ë÷$׫ ê>rq PpaðT<¹HV]HwÞå’p¨$$¶.ÿ8E¢Œ”t~aîÚÉ¡n1X¼ž¯½Ç[ÿ4ùÊœøDœØÛÛg˜HxEK-R´‰ÑV e¹rg‘Â#‰ÝÍF ÿXŸø‹ æa姀ÕÝ“\ã&ír^­£$j¸J|JŠ ‚ü¦½ -@ÈšfiZ¾ëxg¡7;kqþÅ?¾e½¹ÑýŸÇÿ#@æaûBo¦i³º†à+‚ûHLVë)Œþ–òÖ,à©cÕ9Gà†! Õ]‡j@5Óɸ¬ÊëímƒcâV-™ªàÔ>sãÃÐÝ'N  š*æaP€¤¬ª ä,ÈØsbEâ&µ¶Î†vzö‚Á©5Æ©Ï.BÅÛ6ËÂ<±0sà@:§¤ÂÔF ØY˜!òó¶1HD‘jˆe3>4"NWŠ¬³p}¿zþ~¼>ÿÚ×Æ¡202Æ>°§wX4œþ¤h¡-s#@m KÈ­E*.>%9l½ÓŒ“à?goàºë계Sk<={ÁÔÙÐ6©µV$fÏ Ë¯‚„“Ÿ¤…àéÌOMï>qb@7œš§¾ð„²ß[[å sAª²ôÓɸ$Ph£:ËB5C7NÄ«ŽS+4i”·fŠþ–2ܧ¶˜„ª _…0À¨Ãj]\ÐF«np³qíÚ 8Àê-[2¨âÒ©¯`ëòëaʼnpðÈÑNÑj·Hu XUE Ì “qdRvœXÑ8/­­û̱bÂèoñu@P¿Ó*ÞÀ*€I ¢8‹C[ D0H<#SœƒAÊÈU‘Htò=L`UÅë%Ö'îïÃ,i¼âÿ~qs7V=Ô-–zY¼¤A!ZãÛ;tUÐ… C‚¶(ZNQxB^7H‡ Ljò¶ˆaßî <ñgn4ìŒyÚïX1áz°Ï䥵V4fÇ G&%c‚§Êö(¨P@5'Õ¬hµ[Ý=ý·.¿ŽK§¾r[:38æmª*6®]›Ü(÷_¡ÍBo…P‹á†:&xö•˜v"¶˜(ܧŒSkj¯ŽÔ4©Xîx.Ṇ'‘€P°$DT¨Ðê6Ö¯í¢\ywå‚[»¾ùùß¹iáï=qb€PoWÑi ªað.wð©K’¸æ$r"fæ¹çBB'¿k.YàI´ùòtv¶%x<õ„¨QÄ>¤«2CÈ”K˜MX!C¸ÒCÝÖÊîéþý þ½]¾u­Ä3§»-”'ö,žƒ€-%´tt1ÒÂŽQ´("BžDHÕ!I€ØÉuo`õ4ÌåÇ`N<z|îèÍÆàN„ðO‚°: {.T<óÆ8Ss¹$‰| ïr8™P:ߣh!D€4ìeÝýà°×o]WZK{‘wWv?/è¢:ù'ª‚΢få†D×ð\Â[îxÓ¤b¯Ž§ÖÂlÀ×Zûô@Vë¸vb žŒ8ë£Æ“4ªAR#xup3¨Y¿zmDD»uˆ{Ž?‚ñÆ­¼¼úêsX?{ +ÇVAÄØwà`«Ýé´w†;5…²Û’™%ˆKJ@*²ZSåšÈgLâkI¤l¥‚Sk |VñÄià ÕÀOqÃ[˜Ç“OX= Ú¸ît`&Š¨"'H¬"…Á¼œ3(>pU„xÙN±§ˆ£T˜6‚çÞ'µÿß‹üÁ©m|hOŠÔ2Zq”íµžÝyè¼\uÎH€"P†™@ÝÆTÁ8†ñÕh& ·'»œÂØ€œ.AýÇ hC?9€>þÙÀ+ŸÂ·YwóZ®çüO \¶öq#-“ZDì},Oi€¿pò+´Í@Kî¿"ët;í}¶C?×ÏžÂÕWŸ»­uÝsü‘ë7J„õ«×F5%©‰p­Æ¨iœiœ*yçg’gV_kíS`¬žÖÀÍ< °±s¦rL¨IPqEÊUàBS•³òƾ€#ýèm¹4PÅKø[»¦î{ðÁ- ƒBꨠHª Ê96iÃH?Ž3SFps(psaî>~!®Ü¸SuaL[¢iŒÄY¤`dŒ^q8¹èuµÙñ~{ÿÂÕýük;¨Üê¿Êã_~k¸ _îìYÊ“-"´•ÐÐS  Ï@Ÿ€žz`t¼AŠ+ fdIŒÔ·äñ4A46ˆ¡Áêéà$øÄã O><ƒÅ¥ïì%¼“Çp³òÏ]ÿ 0™)£Æ㆑plRåÐùÉicžÝ@0AÓû|ppãŽxéë¶õäÈC?ºûy³É¤©ÊY©s*|"šë*W$¨™P;S9¶±c_ 7¡ý-Åêi58y’ðìc´çn¦J¹”dCË<ůϧ`N¡”º@¿ã¬hÝ~/%"Ê»¼ùÜ¡©nÝž×°tè^´—€ˆP´ZÑhgÇMF㊂C(‡VGVeïÅ{R'Ʊ8륈#™ùF÷_Ù¯›î*ð_*~ü ÂO’â‹ €N=êgàz-(?€H1{¤dLhƒæ±jØœ}(z·}¸Ó:´·÷u¾ÙûùÍ]j $È…÷RtS"¢=EÞ–u=s¾Y($-(ÃL ¦ûg&°a0 X¬5˜=X˜:§ÚÁ|ƒÛ tå"øÊ5ðx 䎀Ú÷ƒp ôé€>» Z]­ž}ÀI€n¼¾€ð)zêSON¯ú[|ô•Ø”mcò$‹Jr‰iLr…Wmq¬]Uê“r„> =u´ö<°r÷ý,-bÿ·^y/é÷nk=óîþÌ/DP½|áâÎƵ«M@Øx„!Äï€ÌŽ‚ÆÑÔx*Y’ZRïZK™ÛüÖ>Á±sjðÔ€n˜ |îå¼¹UßÖ³¿Wå嵫{3ä/Œ@o§¬ëÒyGctÎD@ yUžÏnÀ@`ŒQ ã̸"˜¸†IR˜©W/ Ød NÀE N-xã"¨u|t ´¶î€GÀ c 'Ï-'ßä¥çÞâÕ25ÛV—ºÑÌ'±ñ”ªjA±- Ô!Ò. õˆÑWPŸ‡ƒjÛFQû#?òñÃl T<¾ò™„j¼}[ky÷Çþ2öÞý(€1;ýÂóWª²» ÞÞ÷¶r¯»Í³Í=¬{Jm–Û‘ƒ‰£RSG’GÖž|Ç]%êÒ'¢AûuAÚ¨8ñè#‡zKËÙâô?ó¥ÿç_üâm¯åc?÷ß!)º€ÉhÔ¼zú[—‰h¢1)v@²bHÀŽ2DiÂ*3b©ªêtlÝNKPfŠÓ«èˆWOën:Ð%bÞXqùFTB:u>ÛJh`ÑUÒæÒùsÃE:°³çzûîºíçô¿û]Œ7 HD8tôXgi°ÒSZ(Z*Ú¨­@[ mˆ¶œJÁìr¶&a×$ËÃI¼•mE[/GÙ¬´å©MlDE±b'5bòHê©1È<#g PEk^ Ò¡£@W (:sôºˆ,·uZƒÅ-ÿÛWvà½Þ°ûž¿†3Ou×Æ;‰øÁ=ËÇ»i²Š=UôX°¢‚VXaƲ2–¡X2pOèÆŒtšm´˜Be¡3ÈEVVH›Ò~ y$Þ#1uÀâ:d6ö"îDˆ9YŽÜžQìf6Žì(Ù§“r¸$Ûe¢ejâRJ%k …ñ0 •°E; ä_ùÒ`¥wèè±Ý´ßxó-œþw¿{ÛzÑÛw:{ŽT—Ο*i£aîeÐM RÖJµ¨4ùÆXq†à»žþÃêivùn€›ÓðU o¼8ö¨YP©¢Rh©¤•’VмtþÜMŽ>zò¶w‹o*<û/þÉîgª*úðí7Ì $´@h)¨ÃÐŽm€ÚFѲMHRŽË>!7Mª*>d8ÎzÉ,Ž]Z¦R穉‘åÙüÔo± M„. ºòÓ=(zs#ÐƼ|õÄÊòqËlT«c‡ç.¿¿Ú~oUv*_ûÚÍF`uïò]‡:­#:@ð¶XaE€ „°"ŒV,“bI‚gÖ3Æ@ :Æ£ÏCZ)#w@®9,2c‘Á •Èx‚Œ§Èš¼H“¥2ãÒ¦q¬É@LêKZI•"e9Ïr®ª—u;R×VCmUm[ ­Š -¥þæì¡ÿÐþG{=û/þ |Sávõ"èÖu¹tþÜÖ¼B·Zè¥BKUT,¨Ø£6^ªµ«% IDAT\UÂ[ÆMé¿ð ×xê ÀêiÚÜû0ºÑ·xâ:lMªjl‰t>¾‹¥J@š¨"ñ®‰–VV:YžGD„¼·¯þÉ«u2Ù¼‚¢¿½ýw‡y^QĆ ƒDŠ0(ÌŸS"Vµ"¼Ú¦Ö¸,ш¢é&[Ý6KÛQSUqd}ÔŠë¤v&#õáä'´už›f  €=„« }ïrïøR‘v@#ŠþÜæ{žóÿNJå/^-ñÀžyÂ^–´ûYÚ×MÓ¢ËGÐ’†Þ %D‹ñqX…Vı¯aÒbÄ– !&ƒXb²ˆ½GÏ©É!‚GÜPÙD"(jœ‰cPTÅIâ«$Ñ$KI3ÏQ¡0-lÊ]!ê1™¾€ú ê)¡ Àß}'V®ìÛ·Ûî{ö¹?«_þƒÛ^7"ÂÿÂßC”„!›ëk³óo¼qMA"Œ´C !+ ÝÑHÀcÍÈ$U©h’ô’?×ý˜G¹¥øôßVàI½>“aõ´ÏÆR3{£OÆ8©µ bC¥z*).­ZT_:~{y&gfe Ž?Œk¯?ÛýÂç~ûïÿ(’¢ "±{ïí½uñÂÎh8tJÔ(|jŸµaE£ä=7êÙ;ULÙbP‡r&eíÍ„]ëÀ‘á&1™‡ÏÈhCÑ£«@@ ?oíï‡ö´ò¥…mûýoqaøð÷neTyü³¯¯ã¿ø¡eê¡Veì[yàòh²v~8:¯¢1©†ªË) r%̘ 0a¦Š@E„Šš5)"8Èœ¸Eá8Pd ¼'ˆ6c…#¯h"%‚æ© 6 451yŽ¤´ÆN#’÷ë'øíÉàø#È:Ë‚1¸tþü6@õ\çî¿” .a¨¯«o8R§2ñJ‰ëXpl¤8pZ1Â8ç$’pò$á©s´RjÒ}ÙŠ«&5QD–‘‰¡ƒc@æ OF#»çž•ÝN'U\>ýïoû¡}Sa¶³C~|÷gË{ùÅsçƪ¢PV" Jð Uµê”¨¢È•deb"7¶™nÛ–'Æ• 5ef¤É‰šœDZ€v Qª=æëyéÝ“ŸÐ%E»Ÿ&{ïYêÆ€Wþä rû5ñÃ+obéðh¯!Šb“Ezõò¥)ÂLR%R1â`ЀÅQŒ)§~bR¿ÅnœØf”$¾Ê,M +®`H›H:†µ#ª=&ôTÑ#ºÁí'tHÑN­]º°tœ‰X¼²Qá÷Nmß»þþEh~þÊ ƒÂb)3P–Ùòl)±¶('˜ƒâù<†s#BÊsÅÇÜÈ4pddJÈxþ3r"dÊÄ cÏ9ŒÉa8÷Ìqœ{Ø¢1q Bq[MÜ®)m Ù±í Û.À=uÔQû¡Ç>r¸¿²²‹ú¿uæi¼øoþÏ;²N&JðÑ¿ö÷Á6‚Šè[/Ž®^º¼¦1ºCÀ”‡ÄºÃÂ#Ï:QÑ’¸ªª¨_§ãmwvïÃlwÝ ÄY×eØ:d¢8s(Ñ´jj‚ !aÅ Ð’T+êËçÏmÍ«‹8Z¾oy阡úmL~÷Åí ~î lÎ<~ó¹M<º?ÃÏÞ×A…äÔJžõWò¬?ªëÉút¶¾>­)´ ˆ7j‹ÊÔJ†m[(‰E †Ó02Þ1Ã{Èè@XÅ’¨¤&‚W±ÂŠc¯ŽYªˆÅÅ1W™÷I!”´Y\G­i“j‹HŠÃwÝ;Øøpkñ<åx OÿÞ¯Þ6¶»ûIØ$3]>n@Mª•JÌC"hÏ•\m4jX©Abºéý¿Ùýn2óFÙSkZ Y:[.ùiêœo"µ•g©ZBxÖ”sõªõõr6Û|ÿ}¯ís·Ìvz£”£ |õ3ÿ?þ‹ÿÄas<ðð#ƒ­r´µ%P…WW£ ö5WÅVšùº&rŽáÂhtR ¡¤ TSe Í…PP`­)t^ó¿’g{ö»‡˜ˆ@åÿ× [(Ýû‰ë÷ÏOžk†WÖ+üåû:xt¶ûó"Ž‹"Ž‹Cö¡ÍY¹}m<½6mš%¯Aו‹KáÀh4L¸v:•e˜¼*JB€Ì›•T ¡¤€Õ)“'RÇF5®Š ׉e—{4-FÔ1-b.ºý¥îý?ºÛ™£"øêgþ!ÊÑÆYbƒû쓻ߗ³™ÛX_ß!¢J ¤3Í)Uµ2b«†\k\>6Þ›¡eúcV+ŸÍMmž X›¥áˆ‰CË*‘‚bbŠJ HJŠ ˆŒátie⬅ñÚE ßzãŽ,Ädó Áž»?¼»6ƒ}û‹KçÎNàkeñ`x2Z‘õ¥‰eÅnÇn”&2É#?+¬T-#uÇÀu ¾KÐ.“vA»(„u÷:GvÚûh¾Z£Jð[ÏoâÊøýÝêûg-(N¯•x}³†eÂrf°àÐ!"Σ(ù —%ËJ+ïI yxNÒ2çjÈ ¡ƒ“¼J@-µj)QKÁá+ÙBa O¶lá9ny·JZÂQË#n Ù–²)8N[ü©#6ŠxáúŸúÃŽ³Ïþwl=Ž>ú¸ë#? ¤ÃϾúÊæöÆæ†‚Æ Úa¨ŠVŒ„xÂjf,¾šÕ•Ku¯/Šnpÿ·‡@žúqMšIueì-Á#¶Te#F+ö! ÐŒ Sf¤Z*´:÷ÚkëwÝ{ß’‚÷ÀÉ¿Žsßø£;æ þüï`åèƒØwÿG@DHÒÌ>ü‘záËŸ?G1êaÔ±¥Æu “«Þœph<3 X‚F4#Eçtͳ=]J“•ƒöÁØ»¸íK£¿ûâ6vªÒ}^rv»ÆÙí™e<¼/Åȱ¯u}»f6Êw£ƒ‡»ƒ•÷õ¬ifÓÆM&u3™ÖõÈAk<)<æY€y%©(‘€v‰"”‰æ~DEÔ[‚A@pdÔƒ&†¸„ŒË>#õÙêG?~0I³]ÐïÊ™§qúó¿sÇö;ˆðÀÉ¿¾û­wNνöÚºB+¥ÍAZ¸Tã+ã}ÃIÚ v^Ÿîk ^(N~ñ&÷x»°ÈL lŽkêõ3šŠ2Ä°’Uc #"å>(æT­µ6í-‡È´ÕÇÖ¥3­]¸3 Å[g¾Ž#þ¢´!oµ#õÞ ×¯ÔFŠ´fãf6Ò™Mü$‰d’ÄnšY?+¬Ô-#M‹Ô·I¥ÃР-Šn’ì=ÒíÛS&æEùÅóWføÌKÛ˜}àöÿ…ˆÅ¥O_šâÌzQ`)³0L»%2†Ø$Ö¦­8n/eÙòÞVkÿR–ZqÜ­iQ6ŸGfó͹%Ê@‹¯œ*q 6™IA6ñlSá$uœdž“L9É<ŹÍ®~tÏ{N´Ê?ݾ†/þÿ#|sç¨àœøpßÇÀüôíÕ­k×ÖL4b¢!CR‚u¨ª#+cêÕT%I“¶wn}*(¦‚=kX ÿ y» xâSÀ.ÿºÆõDj^ò‘‡Ò6BU-Æ–P”D2ƒÒÀ”¡3Ï’½ùÚkkGï¹·¿˜ø¡Oü \>uû)Á…Ô“!¾òÛOâÿí?›pûw?ôá~=6›g¿IPÏ$>bõ1Ô7F¼P˜ÙI€‚Z ³êb&dƒ,[^ÎÒåĘx±Ð@¦>÷ê^Ûü Ïÿý"—F .ü›×F81HðО;ò9hx£ÄÆ$±1I7Izoÿ7QQ¯*¢¯êEáÈ{%çàýº4ÃËŠ@†ˆUhDxåèý½cl÷sÅ;|å·ŸD=ÞÑçýÐ'þÆõ{ѳ¯½¶¦@ðŒ!3€¦PLA2Sp©¥‘¦fM$âÔ±ëuA¿Vø¯O@ožàB€ø ÔfKjVÌ.H‚ÚŠV¢4SÃSò:0U¦)‰JT6u3»ðæãwßÓ#",]ÅàøÃX{ã…;¶0çNá…Ïý>üWþûÝŸ=ð±Ûsz¶­³Ë/³!±¤>b•!U0”ŒªíFèöŒí¶£¸“Y“.büE#ÅŸœŸâËç'pt÷|_Jã/\)ñ•pÚöRƒíÚvþ5Bb¿)1Ó£9ß.Šf{·g­WK…#Ô’ˆm¼«u÷ø3+7þö Ÿû5lœ»½.Ø·Ëàø#X>zöë›o ›º™¨$Õ2è‚þžÂcfH+G¨‘]ðÏ®³MÅ'Nö›Å|ÛOº^Р?nÓÄ&dµaf6JdEÅ8i Öó2L% ˆFÃ9zÏ=K Ä´½ŒsÏÝ9`6ÎFÞ è¾ÝŸ­¾·Øyëu'ã -MägÆJEZÆ{hÔ¤˜}hРˆ¸1E¸–_>?Áïk¯nVtöýIékS‡7¶j¼pµÄ—ÏOðÒµw +'ŠÊ) Ä$Ì´ èù®ò¦ïŒ+“³3yä8ÌžûŠ»~êW˜Íî¼ñµ…—þß߸ãÏôØ/ü}´WTUŸÿÚ×/xïÆJ40c !”‡yÊ 9©JDMZ‰?»·ñà# ðïÛþÎ;xPt#ØlÅÎÍ4[YÖJTJ‚ LA4eÅT¬ªÊÙ¥sgw;Ö%bìà£è¼[—^½£‹ôÌïÿ ’VW?"Œ¡{â?Ùóú¿þ߯acX*‘sè>[ `nÄg¯8»ÝàWf8³QÝ1ìæù‹—©ÇÆÔ㥫ïüï– ‰%$†ÐÏ ~þCd6„ àÕºØ,)f±&ÓÝüKÿÍ2™ëÊéÔ—ñÌïÿ îD©ïÒ?x/ö?ðÑp/*¸tîìNU•3€JVL•h è®î)¤4¬•©®Éb8ÔæÀ¿o»ÑwòQ öÁø«÷wÐMB¢xµJ¶.I»j87M”)Æû~îY6Y‹‰‚‘X{ã|ù7ÿ§Ý·wR~è¯þ]t÷ݵøVŸÿú×.ºÆ”hX(¤þ”‡ÊØ1D;"2afäʪœq“ºÊyð¥ùéÿÎ.Ï·£'ó? `Î0Òt_K,à£(uVšÆÚ¨RÑ ÞÏ°ëЊ))f•³élrùÂù‘J@Ï?ò ¬Üõît£¹w¾ô›¿Œá•Po@Ä°yÇú™¿³BQ+ñdcá8º,íúÕ*ÝZ°Ôf–ñŸ>ÔÃRölàòž—NÂøÏîc[¨£ðZ ¯H^{NÙ™ÔHÔ‹ÿñÿÜ·yoWù‡WÞÀ—~ó—áÝí·ø¾ýZ¹ë!~$ õ*‚ËÎfÓÙ ’³u ÀÞÏT´²6ª¬4M¥Î.RF»}ÿønÊw2¸‘'àµ+cÊ-™¦Î5bßP-àÒ0Í”u~C~ ¢ S%Ì”oœ9síÆ`뱟ÿ%ñ^34Ó1¾øk¿„ÉÖ•pçĈ{{ížÿè——$éFŽcë(6oi«z¥Œ¶ªŠ*: ã?{¸‡½Å;EBÈ{Y´#üWôÑO¢ §ª¯ÎÌÎ5ÉjG×&5>]¶ýŸ{²õ˜Eêdë ¾øk¿„f:¾ãû˜ˆñØÏÿÒõ›$ÂgÎ\P*aBÐ1õS€&Ê:5L3—¾¡ºÛLSç¢rK^»2¾©ïÿ;Éw?þžúðÉßöœÀÞ$GC5Åž¸ϱõFÕ²dA)iPÄŠDQSW\­¼Õé$D„´½„j¼…Íó§ßÕËú^ÄUS¼õòWqôÃ? § "˜¬ÃÙ]K¦o|µáf ‚p¥i㛞‘„Š˜pbâêÄaóƒ¾þ÷…¬üÂ$ó˜_}£²£ äuÃ99Ûb×:hº¿ð+Ýhù¨¥@ûˆz2Ä>ý?`²yëô÷ßMîý‘ŸÇ]ýYùëÂ…‹çÞ¼ â1+v@4tàm0 ái‡@cÃnZ{)«µ¦Ò´—nóhW°çÅŸº©òïíò]<àÛ¼—É4m;›ÙÆû¸Úõ S@&M(0Õy(ðò7_|Ë»ëU4þôßB’wq»ôHït®žÃÿÆ?€«C±íí7KíW{¾w4jLÆÉh¹;SEÃ:ô#6Àã':ø‘Ãùí¾Ãäû\~ühŽ¿r_†…¢V•WJ;Ú@Ö4œSm[Ütï²ÝOþo]Û;hÊïª)þø7þFWÏý™ìÝ$ïâÁŸþ[»÷é“—¿ùâ[•ª˜)0t®c2QÜpúû¸²™m¦iÛE.ûžOàOó)ÁO®{Ö°¼q™üÎÅ § bP”RrÿOÄåùçœÎvR8îx©Ûäciq¤aOañúVýA·ß{L"Cø¹û;xdoºëuOîÕ:ÞSîn¡6-òƒ™Îãÿk›ó>/”¿šlã©úwþL<×…üÐÏÿ=¬Üõðî÷/ó¥k[ë@þ˜x›ˆ·‚xÀ©Ž¥‰q®4ªµŽŠ&3—ýö}=öü¬â‰“úN©¿å{@Àž$œ$©ÛML½¾ÒÌÖ„*f±’3æÿgïÍã亪sÑo­}†:§ªzîÖ¬–d ¶%Yò$y’-Ì`l0ؘ!$Œ—ð„ ÜÄvîc÷Bò^ .\’!@ƒ<Ï“,˲­Á’zÔR5Wiïuÿ8U¥–-K–Z’Ÿ¿ß¯­®j>çÔYß^ë[k¯ÅJ‰H³…S QÚÈA`— =0gN§ãº¡{Þrì{ò^4Jã/í®=¥ ìÛzæ­¾¢Y2Ì`e“{ækÝ`ïÚÔ †ÐBRŒf(Qn³=Z¯§°¼ÇÅžRŒÆ+C>^èÎ(¼ý¬,lv€’F¸+ÎTö%V>b+ÇzÎyvÇ ŸÏ+7Û6þzñ nûâ‡QÜ·ó„_ϳqÁ[ÿ°Ùë_P)•Â'6=: ¢2ˆJ” (¦!ÊŠ¹bD×D[ ­9 ³qäz¢Gʤ1^7ø·VÙï >Ä/F?Ì ˜iÄ9.qDŠ`X)‹Ø@,¶ˆ``‹MizÐ`•‹E3Ñ¢´]ºæ-Å®ûÿãWß1#¬1²ù6Ì]yiÚF™PeÎ|ØaLeÜ¡¬)‚$ðYlàY„Uý. ¡ÆDã]àtÆ9.®_‘GÞáöÊ áú¨öëûˆU±•g¼Øî¼þ/sd»Ô2þÊø0ný‡P=q'H„Ë>ðø]í—6Ýÿhe•!T¢´è§PIXÊÌ\щª憫’H*&Îweš±ÿ‹[ýG˜îLU#êPO ÙHY`aÃ0J‘$1YFÄ‘M Kˆì0h뺙Ž®î Áï@½0†Âèñi™t$Ä*†ý9f¯X¯£/­ &wÅ«¸^•xb—„º¡$ÐFçØ8D Å„½.º3 Ãåú•½@§<‹ðÆ¥y¬›ë5ÛF #±ªNH6L”ÈÊRdå˜W½ÅÍ¿þO³ÄµR}…Ñm¸õ Fp‚¼Ô–¬¿Ë.{+´K~G‡ö¢*¡Õê‹Š)R{âU†Ò5Eh°Ña$±${ª‘9šÕxÑp¸0 ˜âjDŽo("›`4[ %Š˜ +a± b§ºÙ XX…©©dÁ¢EÝÊJå׾ū±ë¾4[&Ÿè¨áGnAÿ’5ÈöÌR€»ä"[òsT8úXœŽ¥dD ]M$Ê’¶[º@¿oaeŸ‹‰ºF1x…N,ê²ñ¶³:0{Zz70HöÄN¥¬rIÌÙÔøÝÊ\ù‡Ùìúweˆ­”õø3›pûß~Q½|BÏÓñ;°áƒÿ–“6?‰£H?úÀýÃƘ*ˆÊ*§}þ¤¦".ÂBY jZLÝ°¸VIˆ$7;§võ^<à0/Àžž @uÉ‘­©(aQ†Å"¤z1YHÓ@A,Ãaò¬¹só`9ü®Œn¾õèïÞQÀ$†½]s—¡cÖ`ûu»o±²–\æCi‰"DÐ`)i •$”!±©€tVŸ ÏfŒ”“WÂS6._èãÊEYØÓ¶ OhnŒj¿ÚPi¼©%‹­Ü[>—wžoƒ%Ãön¹w}å÷‘D~¾ëÞùßлhuûç­›;P.¦TA\&Jã~b.ÀP‘ J"¦Jdj*¦+7 B'öt”Vý ï•£Yý£"ª xd S’UÄšÀšÀŠ„‰™MJÂX,Øiy0Y¬J©¬{ú;2žgºæ.Cub/Š{·Õ©-DkŒ<úsKÏKÙžìu²»ò'ßcLù ´æTÖ„ã@›Ä'c·¦§ÌÎZXÞã`²aP_ñN%,ívpýŠ<; }±ÀŒ&veM—¿ï_ætÜð¹œÊõµÅ>ˆ`ëOÿëÓ'¤¼÷ÙX´îXuõ›ZPœšl<ýø–Q5¤+™E0 0©úOJʆ¨ p]C‚0Q‘«¬$[´õÔc°îWçýŸ£¬ƒ½™pÛmÀ¢©YÐ/Ðv@NdÛ šÅ°"E,Š„•á怂•z°JSSfá’%]­}³Ï\áM?GT?¾û©Ÿ ÁÁcjä)ÌYy”í¦i¦8˜S<ötbˆ0b°)Š,Ñì,±g÷¹èõƪ ÂWÜ“ŠîŒÂÕKrX7׃£­úe¡pÄxÕû:QÙv¼o_ø~/ÿÚù¤ì¶Ø5*¸ç}ÏÜó]œ(Qz:rý pùoýØj“•™i­¾ÀLˆS$¿X¾$ÊGœÆúÄç¼ÍëxÓÿ“UÝóUjøéÿ«Â_þ]Œl:¾ª~2ù¼ê#_‚•É¶_{ôþûFkåJÍáž ”\B Å4ÿOU…Òu ;ˆ€ÈA1É»šÂ_+íGÇ„¾„Íð7n¿)m2ëú…‘°O®Ž)2 Û ‹ @PDPÖ¿I5êuÀîíï÷Àr}tÍYŠ=ý3Ù¢§^Ã3÷|ÊvлèœVÑHYä.^oóü ì`ìim¢B !…˜”®Šˆ1°a,4?…ŒÅXÒecy·ƒØ&ƒWŽ-ˆÒÿªÅYœÝëÀ¶â'€)‰Õ8H~µ®|)Ÿ+K±ÊsÒs†•}çrþšk]RÓW}ƒm¿ü:îù‡?@íDVö=ÏÅ\ö›ŸC÷‚3Û/í|ê©É}#É© B™ÐªöC€¢Eb)+AUÃÔq®D‘(•d©žìê)¤Âßß®<&׿…—Ø £)/D1v¨g@£QõáØL*ŽYlN—uMLE 6Â!}@&'¢î¾Þ|kÄx~`!’ Š‰Ý/}ÂðÑ@L‚±§îÅþ'ïFß’µÈä{šï8×Ç™Õor´ò(ßmŒ²HˆÀJjà@DĆQ-¡ÐU„Åvû.GÑ+YƒDÖfœ;àⵃ>Vô8ȨC·L V}‚ýZ•ý8¶šî¾Ê"Éô)kÝû3ù«þÔW³MËí—öïÄ_úvÝ÷}Èq˜Wy´8óÕïÁ²+Þ íÑÞOlzt e€KE¨ÈD%*SD5›ÛN&Väõ6’áIÇ ðLÚ¶o#ŽÅõoáØ©£ýÿ¸ ÜÎK'ªRTV 'Çér™_É ¸ƒ.!ôÐ  —À½Ó   ‚NÇuº.{õkÎhMW£q×—ÿ+ö>~ûK<Åc[6V]óÛ8ûª¢+£a¢ºÔø§ yü»Jjbé@l Á&‚e"ê0›“ØSÏÚn-†Ë ž˜Œ0T‰_éA8 óVõ:XÔqHØkA¦Jv£Ì™0aG ;HÈE¬2”XY²Ï¹!ã¯w†Ÿ0í³£ñä-_Á?þLÏð¥˜wÎFlø­ÿÄ "‚8ŠôÝ¿üÅ3Q[é>O Ì$€I&I0e€"Á”É¢ª1™zÃŒê‹ò]:ÙÙëi`£IÛ|ðr—Ç¡ÖtA°U ÍP@§*€XÄ ‘f7fRH5¶p¹XÔs.ìl¹ßó×¼cOß‹Fñy:;ž@ˆÑ8°íì}ü6ô.Y¯£Ù šd¹ä ^`Ûg_íFº$…QcÈ‚@AH!`+©‘hˆXé‚v€Öé2–uÛ8³ÇĆÿ?ÝuØa¬ìupåB«û\teTkIˆAºLvcJe«5å' {hîÜ£ÈÊ2ùF7ÿ¦Oæ2Ë®pÈvizE_aôiÜñ…ßÆÐCÇg>å± wÑj\ñá¿[NûµM÷Ý7Z+—‹ ª(§q>Šœnô)B¤ÄÌeU›ºÖ&pE"q2iλ †ÍKþ¦ã¥zÍcpÓÍ„­g5Pu'g;NÕe ìù’c:‰M·!î!A/Äô‰P‘é!¡N!t,]±bÁÒ³Îîm<¬Lá–ϾsÆ2G¼@VXvůaõ5‚Û jDH&vëÚÝ_nÈÐ}±eBQ:€%‘(A™ž _b7#Æ9Òñ‹¡ÁîRŒ]åë/oáp–¯°¸ÓÆâ]î‘ûÑÄQì àL¬Ù†f ;d8ƒ˜]¢Á‹íìe¿åY}‹Uë3h!¬LaËÿ;îøדfø@ZìsÕÇÿå°çeçSONîܶm„e!)‰ð‘LxB“,fJ ˆ¹Ä  L£nQ” ý¨€ÆÊ'›/mõŽ4óìP` UÞv,v91žhÉ !/¬:Ù$=îÐ+@/Hz Ô ’NˆéX»nýâYsçåZc—*‡ñ³ÏþÂÊÔq:ÝcƒÉâ¬×}g¾æýíqÍ1i­À¾'’ð‘o‡z×]‘¥Q‚u ±(Á21gMàfŒÎ(ÈŸþZ,ØSŽ±»”`-9í Œs²wZXÔa÷üÈi Ø ªì†šm£Ù&šÒä"QRK68îùow¹«¬Ö=o! xú_ÃS?û*â 6S—wD¸ù¼îãÿŠüÀBiÜ`ßÞêc>°Äe•@R€ÐM×ßL¶¦Èè *¤¨j,nD‰ ]]‰ó]}ÇÕõoáx¡¶Ð]àE‘£jµ¬åvÀ Bå²bZrbé6ªS=DÔA¯Àô¨›Dºè$æü…—\º¤»¯Ïk‘ÀäîÍøåçß‹$:~³×Ž™Ž>¬¾ö#XzÙÛÓÄ3tù i<ö½0ÙúÃP…Q&e"aAI % \8ž‰Çh‡ŸçsÐŒ×5Æê Ô5Ô4‚S\DÌ(¬¬Âl߬¬B¿§ žç)3€‰XÅ ¶£Pe"M4Ù0íß%ív“µòZ×[{½«:¸u[£±óîï`Ë¿€ <1CWùü°œ ^ýû_Gïâ5Rã/LL4º÷ž]bL…€’R ð$“"2E‚)úD‰*CQÕhSϸ: ˈ²ÙZ²Ç‰4 ÝfÚê\„ãHÏ º—ðüÂ.+È»–›h'€a†/å-át±P·I¿@/ ÝhŠ‚JqÇú˯Xœïìt[$°wó­¸óï>rR]»éÈÏZ„µ× Î{Ýsßѱ[o‰¢Íß 1µK[Ò&aCI 619&²2:v1Ž’#{-Cƒ±ºÆ®RŒÑSdTùüœ…%6fûêyÝú ‘ ‰£PÙQÈNbØM6 ÙÐlS*ð9„ž%ÊYsƒ›Yy•CÊ>,¾oaäÑŸá±ï}•{NЕˆ.ÿÐ0oÍ•ÐîîóÀwìÖÚ”[¢@“™bð¤!)0PLÈ”IœŠ1¨g¡¥¢L%LF»—$(ì2ÇÓõoŸóñ8ÈáÇk’ÀíWðÊþq.t{VPS–ãT]†“$YÊ‹¤™c¤‡ˆ{ L/‘ôˆP7:t8ŽÓyñ—d|ßj‘ÀŽ;þ}óÆã|Ú/ }KÖâì« óÎyèYª bF8ôhlúNh†Œ• ¡t$ 1”‰…$’$ 9&Vn:¶Û£ðŸÑÁºÆ££“FósÎp0à¿°–œ'1qZN±£…,ÑdCÈ‚f›RWß&Í.xáz;sî[]wð<;]í?¶ˆÁÞÇoÓ?ù2&vÍlšøWáÂ߸ù°t_P¯'÷Ýqû®(ŠJÊ"(IA„¦<)b¦˜)UüÉ”RY5ƒ(ˆ¢\˜ÉꤻÐH¶Ž÷l¼ÃoãŽ?´bãíßa¬ì§Ãô€°á²ëy òÈ‹‘Nº I5‰@ =€tCRð|¿óâ¯ZÜJÀæï}[üwÇÿÔ_"rý °üÊ÷àŒKß ÛËö^J ¦Q’pÇQ´ýÖXö>–’Aê#›lb°$`hrtdY&±m£-%iFáÙw¬®ñóáÆŒíLtáµ =Ì>‚á š(‰Y% [q¬œDC‰a †l¶``Á°MšR£§ykmgù•¶»ìr‡½NjÝ«éˆU‘qÿtœ x®°(ª¨*—í ¹v a†µñ œèEÒi@©&@ÒCD=dLºDÐÉ@¾»¯¯÷‚K/[ÀÓ†:?uËW°é;Ÿ9A—p@„¹«.NJ׼sÎÞ€çT¸`e wÞÇ»ï‹ôècšÃ¢a‰EI ebpó»2±‰`IÂNel#nëXûj?iœ°ŽEŠ€×.ð07{Èøc¦0² !Ëv ÙIcz¶¡ÙF3¾'ãv±š¿VÙ‹/vÜ¥—Ùœéx^£‡ö?y¶ýâ±ï‰;g´$ühqîÛþg]u¨Ÿ¿1F¾çî‘ÂÄĤ*D(‘ (ÌS"’®ú$†µP‰ Ê S3Šrƒ@Â8g:â=N^Ÿ¨¸:N4ÝL Þ~3¯ì?›K€ª;‘í)ßNEÁÐTb:ØH§(ÕE"ÝDÔ#=-QP€†äûçÎ8wÝúy4ÍvÞõ-<øO@äÔîГí›Á Þ€Á ߀žÁUGü¶Aˆ )ŽšxtsbönŽõÈCš«cÚ2!,B™ÊD`‘“„ŽëöD“¡J‚ÛöÇýI!¯š—Á`þÁ†¶ªG–vD+WbrÈ°‹„]˜Ül¥\¨xÞÛž¿Æ²ºæ3ˆð¼F`jè =ô# =ü£™¯×?J1Ö½ç“Xºáí×D›|`ïø¾} ¨’–ù¦¢1¦DdJˆ ¤uÑ0•@\tÍ·žA4t=ö#'îôÖñ' 6Þx\S~G¼ŽqÐCôø7pû퇉‚™¬vÈD™0"FrQ^XuÑÝÌÔM=Bènê]B耘üüÁÁÙ«Î=ötzèG¸÷+‘VNǹþ…XxÁ5/H@“HD0Õq“ŒlJdä¡Älý^hÇUX&€Ò¹IàÚ±n&<2á‰É踞óª^ç÷ªcŠmÕ-/ÐÊEÂ$v´òz—\hY ε8×ψ~^ƒýðÃ?Fu|ø¸žó‰+ —|ð¯1xáÚ¯‰žØôÈØèÐЈ+$(7÷õHPÆ”1R`R2º”ˆTÀTu© ;APSÑá¢_+ßœ׿…LÀaz@S¬‡Z5аÝ8ç0¢LDäÛÚä„7‰îbKuIê t‘ºÐôHL~öÜyýç®[7—Rön¹·ñ·¡O:£A~`]x /|#zžý‚¿›CO ëàGZWc›K×a™€Ü°ž±€Ä?ØSG5>>ÏMÎ&¼y‘æ(=$– B×ÎH¢|èÙk¬Ì>å«ž…Jļ ÁÀÔð“zè?±ç¡£rpÏq9Ç™‚r2Øø;_¼Õ¤†/"²éÁ÷íÛ;.Äí•?ÝÛŸÆûDT0‰.²¥Š$¨ÄŠ«ŽHÝÀ B»yðbßUº-úÀ¸:f€ž­x©»€[™W‹£È΀á‹69Ý!$LÔe©8é&‘nu ÐÁ"¹Þþ¾Þ /½dþtaðÀö‡ðËÏ¿Q£2—uüáuöaÖòu˜µbf­Xîg>'­hÖAÔñÙº<úÍÐÖ5XºA^£–#“v+­iܺ÷øá•ó2˜ßŒû…)ixÙj¢<‰UtÞo¸þk>ž† |„¬€FžÆmàÀ¶q`ûƒh”N~ÁαÀñòxõï ³–_àà÷Ð=÷ŽNŽOL¢*eˆ”„¨@ B*ö¡`DŠ$Tb¨2)®Â ®%BEQ[ñG̳D?à7)œ hþÃI`Q䨆ž«BU·]å'Dä‹’œåEs'ºÚHwê H—€ò,’ïèìè¾äŠ œi)ÂÉ¡­øégß5£mžN?YË.Ĭë0{Åzô/>gz#IÀh”þé¿ThßÉ£k°ãšrêŽÖÛwì1ükæ,\1§­3"ò½rlgu¤²¹Xïþ_ùé†o’ã»Ó+¶=ˆ;BT?= y:2ù^¼þãÿ½ƒ+ ÝÑçÞ;î)—ÊCT!HH7÷2~.A‘”)‘H…4UáH]×­ rMèj?öÔ>}Ř¥3EÍ¿õ\¨Õ²VdǶëçNŒ‡˜|¨æ¾#Dè ë04=È„œçù—_¹aПV,TÛŸýÕo¢0zb[Ï4,ÇÃeïÿ$–_þ6im.0ů¾£bÆNjpe£8õDðC $ǨZ ¼iЃßìÂc;½ŽFlegú¸ëßÊ«ŽYíÖÚÛïüîþÚ'f´›ÓL {þr¼î÷þ³Ó”®ˆ ^¯'wÞz×P£Q/AµÕÑgºñPiæùhªÂ–º±¸Ö«‘ÛÏ—îf¢=1ŽËvà£A³¡h«‰HîL ¨Q˜ ƒÈ¸`‚§í÷ &˜YDÔ‡(ͨ (Žc324ZŸ;g çe\‹‰àå»±âò·¡2>‚©‘§göO ŒŽ±ç‘[àwô`öÒµ`f(×'äfq¸ó®„Å šãÈYLˆ pð'Õec~N¥Ûs™Lìçj‰ò+Ÿ²¯ÿ3ß[¸ÖRJ xòçÿˆ;¿úG0úäì»?QXvÉu¸ú¾†\טÒi’•R)¼ýÖ»öéü¾*Ê•@Rl}‰ "Rj¿¢*XêqnäĦiü«5 ÁI1~`Æ n"ܾ±IV“,˜| ‘å '!Ï&&!Í(¡ÉZkÚ=\èïÍæ²¾ÍD°,K×_ƒL® #[vã3…ýO?€37¼™l˜™ÙËU}ä ­‹{5Á€E űÝ.aGùèwZ \6Ûioä1^¦ÛY+¼xƒÓûºzÌ & 6¹?ùüoÂ$Ç7óp2Áʆ÷ÜŒKã°,»müãÛo»{Oœ$åÖž~¢t/:¿ L(¡"1Êmã—¦ñ{~èxõ8KýÉgÁ³rýÀKÝß´8 p37·mž\ <ò*ÚY P¿§„°°c¤MP&£!ÌÂ$BD"Dhr†1‚¡¡‘ZWgÞïîîp˜&`βs±ðœË±gÓ­ˆÕ™¿ÜD(؃³6\Ÿ>˜b`÷/V•Í?ŠX4˜Ds:0Š‰ÆÒ 8³Ó¼VÁb­½|=i6ãè{Ë'³v¾—™ÓŽ¯·üÍÿÉá—§•ë™ëþô›XvÑÐzŽ˜€‘‘½Õ»îz`6’vñ…” T¦" )â"DJ¤¬+`ÔZÆÖ¹Ù8ÎÖû“=µqƒ‘{ë>,¸q£¤£üf2"Oqn&Ü|p€¡;€'ÇQ´³è©èï×ÔK“b€8Ë€b (-’hö„ÁððÞ*A¬Ù³ú|BÚ»¿£o.κüØõ8ÊO½:òcAqÿ. ®Ù€Îþy`&8ý\ßû´ÖÅMÐ Ñ‚(v@—ÃØQyñ^€ÅÀ%TÓÍ2ž×Hœ¬Ž­,ì¥ÞKßå2§aؾmâîo~êD^êŒbÁªKñÖ?ÿzç-mn¦Ãâ¶<þÔäƒo‘ˆÊ"Í•ŸP 4Å>¢‹*%UÄTk¹ý®B„Žþȯëá¸n0þ¤ÁFܸQš5þé›aœ$‘ÀF`h#ðä¿¡œíGO½Ž°âY¾DIª NžX ADˆ„…DB i}R-þ>-X0;g)ÅŠ ®çcå7@Ç!ö>ý`³¼ôôþª`ÕÆÚ^€;p†*núˆMfh ƒÃ¼€‰9ÏpE‡…¹­>k“í¨'ì!¶²4ç­ŸÊÚ¹îöêÿó/ý!Šûwô{ñ’¿XýïàšÿúÿÂõs` ‚(Žôm·ß7ºs×ÈU* *Q‰@€ ,TF‘!E&*‘¡*‰Ô`¡¡žç‡Çîx]­*¿“lüÀI%àpO`c›¨_êN ^Í—Ø…pd„lKb1¢ Â",ÂÄ"D"’†©G‘T*µä™]õY}]~G.k3ÌŒÅk.ÇÀâ•Øõèm't,ùL ¸7V¬¿ ùžY©ïåÚØ3:ž2Œ -FŽèp;*¿º=9pQ¿ Õì͇¬ßÐŽ¯c•¥ÌY¯±.~‡Û2þñÝ[pÛ?þʼn¿Ð ×ïÀ›ÿàK¸à @K× Œ7~ò³»÷ å"˜*-ã¨$"E0§>".‘ œU-V5$ºa\x5¼zœ­÷'‡Vþ nÄI7~à¤p$hiõ ϸˆ…„™DˆK‰ !6 €ˆ„ $q";v W¢æÍéó‰fBÿ‚eX}åÛPÂäÈŽ“|ý/ aµ€•ÞÔ|h ¼Yg¨©G~±h(0pD„-&ÔA1zágm0§°0—>¤X#×Yo ÜXøŽOeí\ç¡Õÿï?‰ám3q™' +.¾ï¸ñ›˜·üܶÐG<úØS“wÞûèpë*Uˆ¤¢’@J*0¸ dŠRb’rbP…¨š5´¦0“¨¨7¿Úø)güÀ)AÀI``µ ¨QÔBÖc‰ŒÛ&Ñ€³0HdRB`!¡4x€dÿÉÆØØx´hÁ윭˜2~«.¿³¯ÄðÖûO[prtV]þfä:{ÁDprÝ\;°ÇÄã» ‹†’&ŠmÈZ„gª/ÜMi]¯ ·ùT°ïÚÍéDùä¯|½3ë¢ÜÖ¨§©½;ðÓ/ý Nòó{ÌÈ÷ÌÆu¿ÿlüõ?D¦íò„a¤ú‹ûF·?3r €Ê.¨Hi;¯"DŠLT$AYeÃ\Ps,jh£CßFTÂ$Mõ-йGN5ãNž# ¶S„£hè UE®$v,lŒ&a# £ˆ !õH`@" 2  ¤T«'Ûw Õfõuy]ùœÝRv,Ãù¯‚j ûwnÆ©¼õôˆAÔpö%×´½ö2uð¡ïGlbX´©7«“¡ ö9yëÝ'‰©¨ ¢2„JDÜ,ímVøAŠ’öö+S…5jFœFÆVaP®Ç®ª'ù./Ù…‚9¬ÈçPªï”0~à”# ]'Ð&…˜²`NnŽÀVÔ”xIb)á„ ±Š"Ä“a`„aX`š¥„²l²±sÏÞƬÞÎLWÞ·©¹ tÌǺkÞ bÆÐÖ‡`N‘棿 b $‰qÖú׶ãØÜÜåjìþïEJb°“ÔêŽ(kFÏNämÂêÎfÕATwWC[¾Ä*OËßõ—¾íM[ý¿öI ?ýðI¹Öc²\ùïýñ—Ñ;{Íu`ö˜lüà–{GžÙ³ï RïP–´‚¯¢"§Ý|Š)¨ÈDeJL•W¡¹n, 2& ƒÐ‰½•är9ýÜ=À©füÀ)Ip3¯LI`ÖH¤O#I†BIàŠRÊ0Å&N`,e ‘­ ¢SãO=ëVX"ÓhDñÖíÕr¥fÌéõ-K13A)…3Ö^†Õ®ÅÁám(Œ ì›ñ¢0¶{+.¼ú7àeó©ÍSub¯ûwJB6‰V ˆ0Ö8œVvZètÒÕßòܘüÎ8fŸ:ϻ֙»þN+ï_žÜoÿß9 3öŽg¬Ý€÷ý÷obÍo†R D€ˆ #ý‹»;pÛ}[FATQ³¸'ÓÇ’®ú )TdB‘É*%•$AÕ²¥åÖ58Èp# ÙŽÝ ´?¢ôÎìÿ”2üNQR8|ïÀT©SrIt¤8øaVbG ö‘DLZ*ÄZ` ˆ4k!h€ A AL“ 21U 6?¹§ê¹–=w ;ò]}¸ðªwâŒs.ÆäÞ](<µ;ÔÀ$!V^ôº4®Ÿ»\í½ÿßc–6‹Äµ†y‹°§fÐ’\&œ×mµëÐÜ®®Fbg%²ótö{þÒw§­þ?ùê_`hëƒ'ç"‹W­Ç;ÿ苸꽌|W_ÛÝg6?¹«ô½ŸÞ?Y ¿ó“ûG7?µç Ö¦   P2ˆŠ©Ø‡¢P:¢@‰%f.© «ÂR7 æ0 G&“œ=?ÉÇu³u¼ß \NÖÆžcÁi@@›¶® -Âø´4¡èXâ¤ËxIbÄ°‚a('‰‘Všš©ñ k€R¯€`˜¸õž©ÕñcOí.×Íëó3©¦ð501.¹ö=<ë<ÞŽòäØɾ)Ï1 LbÕ%Ó¼€ù+ÔÈ=ÿ³$d)#q­a@§Mª(Îï¶ÚcŒÝž®@Û9‰­óþÏz®—£–ø÷Ÿ3vŸ¢«ÿ‚kñëô7¸îC7c`þâ¶ÑCI’˜ŸßýøøïØ4R­e!ª2Q™ˆË’*ýEN‡s2Rw(’¡’&©-U‹­šnhm‚ ™0LüÈA1ñÝPïqèñ‘{ßgðÅ•‚WmÄé`üÀÉØ}pì öý|Vw¡z8¢¢d¶ÊPhGu׶2âÄ’dÈÀ#ey€ÉB8 £ó Î !ÏFòB”ƒ ˜€O„Œç9Ù×_zÎœsV,è0Þ™çñ»Œÿüê§0ºãñ¿/ËvñßÞŒ®þ¹é "xü[ŸÆø·ÄÕ5Dc£^Æ vÕR`I6½6˵µ3{~#RYô­«uÎ;þ$Óêd\߇?û$§XõäüeçàøSœsÙ5‡½nŒáñm#åŸÞóøþF#ª‰ PO¿¸ B•Dª†©AZé'¦ V©\4„Ñ°É ’€"Çã@Üرƴï.8mľçÃiâ´p„ Á`§,6¢#0„Ä^+$£,66l 5‘VM „DÒÂHšaBp‚Ô+ÐÒq¬õS»öWžØ1Rõ3¶5«¯Ó„ZbØìÁeØpÝû1é*L £8¾÷dß© “«/½ ÔT»;¬P{îù~BF“R¸Vµ€Ô 販ݭÜíë´“—ØÉÓyÿå3žãe‰š×ûƒ/ß„]ORùÎOydëÐ8ÖU€* T@T8÷E0 l¨H­:~FYƒ*JPQ JêZ›€Y‡¡âHª&ÎDZwžÞ6Ø™Žê>M8½<€éH½›n&àFàö›ËßH‹¢-ª¡•ŠMŸòT`GìÚ¶1N„8ÃPâć&|r0’s‚Ô+äÀȲHÖ²Dð âAéêð²W\¸¢ÿ‚•‹:ˆˆ¦'ìÛõîüÁ×pÿOþµrá$Ý––íâÓÿ¾å0/àÑo}.ÚÿÀw;©K²OF7ꇑ¿ò|mÍYÄ–OsÖß`÷Ž9ÓWÿ?yË꓾úg;ºqÑÕïÄåo~?æ.9ë°÷ŒI›s>¼uOùŽ‡¶Ëˆ"h0¡fˆj0¨Z›zP…1U0UA¨"AJêb¬ºØAÌ9&Œ:Û<¡=¥Óâžíÿ™Vöáfœ.ñþ‘pšyÓÑÔ«¬aÎØ!U•¨»×Dµq±lßh!£¬Ø”Vl­I”’@(Kê0–¦7Ñ$B¤ ¤ƒ(Ž·í«>üÄHÍèr™©­töôãœK^‹×ýÚï`îâ3Q«0±rž‰–°ö²«Ú±p÷Âå¼ýîiˆ@e<1µ’2Ú¤-l[¬9‹£ÄÎRbwÐ%¿ù®ëùíØÿßÿîFìÜrrV"™\Ž>t#Þÿßþk7\ΞþÃb|£Üÿø3¥þÏFß¾w<ˆ’ ˆªBTaA@™ˆ‹”„‘Æú$E*R¸,Ë’ HÕ tݧÁJ…aTŒÑ1y•b’-vé]=ÍâžVeßiï §«ÐÂsuñ~ZÙ?εlZ©(ÈX¾gY-o f¸ !#”xH8 ‚¯YrJ( 19Ê% P,><x€drýŒí]qÁÒþKÏ=£Ë±-æ#Lý98º wüàë¸ë‡ÿ„âÄÌŠ†–ãâ~ÿ ô Ìæ¾øÎã¡ûþC[:'u˜É}tB<°06–De0xñµÖú·}¤Ý_qêà^üÁu«D3»úwõÍƆkß+Þü^ Ì_òœ÷¢81÷lz¦xÇÃ;ÇëAÜ$( BC êÕ©ÁPHª ®j’š2T… ËÔ ©!L ØakÕ¯7’Äɉ§´ÎÖ²GPùÓÙøÓŸZ8<$Øú¶@ŒU9È»VbŒÊPlÇŽ%G©È¡2d$ “Á›Ôð¹C Y|ò@ÆP \ÇVÞç/í»dÍ’®Î¼gicD=+>Ð:ÁcwýwÿøŸ±åþ[Ѩ•gä¦\°ñZüÞç¾Ýþ¹Vš’ûäoÇ*® #7+ +ØжOoýÄ—ìlgOûüÿêcoÇ÷ÿpFÎ×Ëv`õEWâ²k~k7\ ¥Ÿ/к·¥J#¹wó®â윈bÝBjøÂu!i@PQ­M„jJ\K ¿%ðQÆ´v„‚ÈVˆ±c‹Yg*a’™3‡„¾·Ééîò?/^„7›>ågØŠ(´--NbÄa&—¡2‰1HûdàƒÈ‡˜,zm"|@|4IÀx\¹"â.ìë¸pÕ`×Úóò¶m‘ˆàÙžÖ ¶?v6ÝõS |@šDÐ$Iß3€ @‚dDÄÁ±;çž9¿ë¢Õ »–/ì÷ÒÎÆG¾Õ“c£Øt÷-ØtÏ-xüþ[Ôï¶äî¾Ùøël†Ÿël¿öݯüµݾÕ04€’ùËWò üh[ªWKøè›× pœC—ŒŸÃ9]‰s/½ ç^vzgÏ?âï“îËß><Þ¸ËpqÓÓ£ÅX›‚(5~ ÔH=uù›Æ/Í@êÔ…¨a15L¬£LhEÅŒÈÑN\w“Įʼn èL5gv.xù®úÓñr$à¼pvŽê¡VQbTT-Ïí¶ŠmË'1ŽÃhd˜WKì‘Q”xdÈtê|2éw¾£ƒæ±3Ù<þ¯ý±ò|­Õÿ öAÜú½¯¿ä¿íeó\¾Ë׬Çù^³Î»–íñw[÷fl²ß¿e¨tß–¡B©G"Bi?Bh0Ð8§†‘: ÒŸ55„uC‘Ý0& ¼Àâ(J˜#Kì¸;“K‹µï*íŽUåå¾êOÇË•ZH‰@Ü|h>áÊ­ã\ËösC4ªG–•m["Ž²ÅI¸ “Ñdš‚¡j:|x ø øFàŒÀ;ä PWˆq…`“½pvgîÒs»V/ëëÊÚ"ÓÜ{p$ˆîží[°gÛfìÙ¶CÛ·`ÿÈ®£nsþžßÿ$Þò?hm091)ßûöw®û ÔÛ×KJ¥AÿþÕÿo|þGw£™1gÁ ._E+VcÑŠ5X´|5æ ¶IåÙÐFš;óÅZ¼eçXõžÇ‡ŠÃc¥ªÄ$ˆ…($‘ H‚ÖÊ !à:Òx_PJÉ@ hݦ† 8°,„:¦(!Š,‰cÛI’zkºû t¶6n¶®ì7í¹|7ÞxÊíÝ?x¹@ G¨˜C-‘0Îtr”¬lG‡Šê¡msb'lÛJEnbl§Mà 3`ñÈÄžñ Æ'â¦Ï4‰€ !$.Žˆ8D°EÄîÊ{™•Kòg-ÈžµhÀïÊg”‘”¬ž/\h!hÔ0´}+ölÛ‚ÝÛÇÁ½C(ÆQšGijâˆ^á÷>ýU\yÝ»¤•r­êÆéÿ¾õûÿ„¿ú“@ŽÐÅËæÑÙӇΞ~tv÷c`Þ ¯8‹V¬Æàò•ÈxÙƒýÇd„L’ů%½Ú™í®ÊŒxþ‘ݳ³Ü]P”DÙÐÎUÝè]ý^¼ˆŒŠ,ÙZËÒ¦“¹]?yÓæ~¯ûÇySäûð“?‰uýoŠ×E€W¤xÿœïÍŸÛþÓQèà'ûRÚä¥îBP5õÞf3Αœ ¶ç†:]ØBØ´%¸®@·jœAÌÒ‚!¨€*€ ¨¼õàló“¿|ûüG?¼8ù«^œœm'ËÃ﯄¯b]ö7b0Žãquù÷÷ÿ€Óóû/ýÜõåcüó?ý#ÎÎï’ß<é§yó¾Çíïµ[ó?ñðéüâáÓŸþ×g—Ÿ?ºÚ^ 4€D“Є‘ãCXHÝÊõ¹7b'häÄn¤ÚˆoûDîÝ´dj)¥.Ѹ–l­mK«kô§›ÞË#Ä Ä þ[‰/õºØý—áu€#^‚.ˆŸ=´÷õöhg7Bм G5Xª›Í&Maœ‰Üdp&¸!°rC³MfnÈçR‚ІÄ,`Ž5T•@T@PùÁÛ÷·?y÷í³wßy°ýþŽùâÁiëا'SHIåU¹Ã=Rv«;ri¡‡®Û¯>Y>úøÑî§}võ«ÏïáGÁ& h€ÀB`•Ëz·@Ø™Ù^™{ÀöÂs-‚í=µ$¹Fæâê­¤·–¥ÕúŒøõÁ6OfŸz%ܶû¯!ñøñú?­´x©”õ~‚}‰ZJµZ¥º–)T'³>38§q&¢GQÐäFÀA8K˜Án@6 šT€Ž€BU ®Ñ±évo;ýùÅùæ7Ïçï½q6¿sqoúÞgõ;ç›r\nŒLèÐ~8Òk~cqDJPêXE ú1mð›Ë}ÿäË«öñÃ'ë'_^-q¹ü÷ÃËýÿ<Ù­‚@ã^ Š]ĈøbÔ4‚«˜‡¨o ‰E:4ô 4¢¿ ½Ù »¤ÅR‹\KfYœm Ϋ£·–­yë½ÖÍ ~AÔþ ⯕Ý^g8âÅeÃ[Bp;58 x[K‰êeêV:{õÔôêÂÖgS™Âb¶ÄLŽÈ/bfrNŽ×(ÌbÎ&Á& ¡IÐ$ G¢P|©pBvxnÅÜß¹8›ÿâ­{óßÙNÛ©Øf*¾™‹mªÛf®v2ÛÎÕ6S±yrNµp*£ê×zäÒº–5´_{î––O—žû¥å¾Eî—žûµÇníùåovë/?²|üðjé Ñ™/%¾:„> =¸ŠX‰\A¬”-:ß„E¦…' iIÃâéK²¯že buE ãZTÚZ²{‹^§Þ¯ñœÕŽøÚËzßwð _+msßZ†Ÿ)}Ÿ–ЩGÙ—ºŒô 0kЫ'¦€&dŸÒ0›0%13s¦ù$åLÙ”¦‰Â$`…BL„ªÈJ @¨d‰¤ *:a CF0 cMàø—çað-äð )Þ\}al 0ǨoÉ1WM‡w%d ¦‡Äç =Á)HuÉ:ˆ& Sj¹þ*`%°ŠX-Àe¬2[LX’X-±ˆXSZ\Ã黬¹zk³7ï›î¼Ži9éW´¨æqSÜ»#þ×âN^Ä‹©ÁWŠ…m³·¨÷­gúiÊ»àGW…ÅרÁ¨I¯æšRm2ÎÕ¤)…‰Ò$;D}a¢0%4Ñ0 ¬«Ê˜TÑ uŠ§X8©[®€&É 4DÊ0îä%†@c82•epN^‡5Ãã­?‡ýOH 4!0æ™Æ„,‰LˆIe¦€©@rÔ8F¯EdvƒD„—ÎÖ€è^»ÙF„í Kœ‡ìòY¤ß¤oWMëY>»ÔÏð׉w.uíÿp¸€ß_q8 ÿøÛ¿Ám1Î`¤ ¹Tf]ìÌ·Öó‰§Ï–û>Aðôj)yÅâb±<Ø|™[\LKÈA7GwÑLtC¤‰2QæI‚iÒ¡@$,ž‘?“Ï)€Ùa­_Bº@ ™"%È2L¢˜nIER™P¤AYv"˜‘$€ z6ÌadX´4¿)i±d±{q»´6§ÍMG{_N¯õéÿå_‹¥¨w¸wÄÿq'8¼\ €gðÁ£x¬äƒ-oÜÁåb÷O+»o-Ÿ6‹Ò,}6Y·Ùºi,´sY51¬öq”¥‰ÅÔÓTÈÊ4u™|¢b58©$‘FxŒUEQ¤ñxç}K¡c¬4¢á‚¥hB¢OÉXÅÂl²d—X,©žLKʳ•–”'³¥sœÖ\²$sÝ{M;©Yb—¯›üüY”·G;ÝäôÞÕ ‘¸#ý·€;øvðj1¸U3X®>åÑÜ„£C¸·VFYM­2J3ÍÎÜwÃT8/ÝäaªÎ\ÃP £¦4"SiTŒ6@#²˜ ì/½î´"`¬ˆ}4òÓSk¯‚…h)Ê“žB ÙäÉbx.sI¬]¶)É%ä½&k“÷)ŸLMÏ"ü3£ü|ö]=—Ów¤ÿ#áN¾}<ƒcÍàX@žX¾{Æ~¹e¿~x# y>1ÿÚ´NÔiå½µ2·O©V¨Meìš©;5/TwbZ œ@ݨX¹Ý ?ô ¯ã¶…—bøt ~h·è“XRÀS`Äâ2‹%äÛšÜ7±vÙîDO¦&^7qZå÷¿Ÿv¹êÙ/TÎwš?½¼HøC!ï&§?ütw¤ÿVñ¿­R»>*Ž¾IEND®B`‚darkplaces/crypto-keygen-standalone.c0000664000175000017500000005400413067716216017265 0ustar kalevkalev#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include // BEGIN stuff shared with crypto.c #define FOURCC_D0PK (('d' << 0) | ('0' << 8) | ('p' << 16) | ('k' << 24)) #define FOURCC_D0SK (('d' << 0) | ('0' << 8) | ('s' << 16) | ('k' << 24)) #define FOURCC_D0PI (('d' << 0) | ('0' << 8) | ('p' << 16) | ('i' << 24)) #define FOURCC_D0SI (('d' << 0) | ('0' << 8) | ('s' << 16) | ('i' << 24)) #define FOURCC_D0IQ (('d' << 0) | ('0' << 8) | ('i' << 16) | ('q' << 24)) #define FOURCC_D0IR (('d' << 0) | ('0' << 8) | ('i' << 16) | ('r' << 24)) #define FOURCC_D0ER (('d' << 0) | ('0' << 8) | ('e' << 16) | ('r' << 24)) #define FOURCC_D0IC (('d' << 0) | ('0' << 8) | ('i' << 16) | ('c' << 24)) static unsigned long Crypto_LittleLong(const char *data) { return ((unsigned char) data[0]) | (((unsigned char) data[1]) << 8) | (((unsigned char) data[2]) << 16) | (((unsigned char) data[3]) << 24); } static void Crypto_UnLittleLong(char *data, unsigned long l) { data[0] = l & 0xFF; data[1] = (l >> 8) & 0xFF; data[2] = (l >> 16) & 0xFF; data[3] = (l >> 24) & 0xFF; } static size_t Crypto_ParsePack(const char *buf, size_t len, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) { size_t i; size_t pos; pos = 0; if(header) { if(len < 4) return 0; if(Crypto_LittleLong(buf) != header) return 0; pos += 4; } for(i = 0; i < nlumps; ++i) { if(pos + 4 > len) return 0; lumpsize[i] = Crypto_LittleLong(&buf[pos]); pos += 4; if(pos + lumpsize[i] > len) return 0; lumps[i] = &buf[pos]; pos += lumpsize[i]; } return pos; } static size_t Crypto_UnParsePack(char *buf, size_t len, unsigned long header, const char *const *lumps, const size_t *lumpsize, size_t nlumps) { size_t i; size_t pos; pos = 0; if(header) { if(len < 4) return 0; Crypto_UnLittleLong(buf, header); pos += 4; } for(i = 0; i < nlumps; ++i) { if(pos + 4 + lumpsize[i] > len) return 0; Crypto_UnLittleLong(&buf[pos], lumpsize[i]); pos += 4; memcpy(&buf[pos], lumps[i], lumpsize[i]); pos += lumpsize[i]; } return pos; } void file2buf(const char *fn, char **data, size_t *datasize) { FILE *f; *data = NULL; *datasize = 0; size_t n = 0, dn = 0; if(!strncmp(fn, "/dev/fd/", 8)) f = fdopen(atoi(fn + 8), "rb"); else f = fopen(fn, "rb"); if(!f) { return; } for(;;) { *data = realloc(*data, *datasize += 8192); if(!*data) { *datasize = 0; fclose(f); return; } dn = fread(*data + n, 1, *datasize - n, f); if(!dn) break; n += dn; } fclose(f); *datasize = n; } int buf2file(const char *fn, const char *data, size_t n) { FILE *f; if(!strncmp(fn, "/dev/fd/", 8)) f = fdopen(atoi(fn + 8), "wb"); else f = fopen(fn, "wb"); if(!f) return 0; n = fwrite(data, n, 1, f); if(fclose(f) || !n) return 0; return 1; } void file2lumps(const char *fn, unsigned long header, const char **lumps, size_t *lumpsize, size_t nlumps) { char *buf; size_t n; file2buf(fn, &buf, &n); if(!buf) { fprintf(stderr, "could not open %s\n", fn); exit(1); } if(!Crypto_ParsePack(buf, n, header, lumps, lumpsize, nlumps)) { fprintf(stderr, "could not parse %s as %c%c%c%c (%d lumps expected)\n", fn, (int) header & 0xFF, (int) (header >> 8) & 0xFF, (int) (header >> 16) & 0xFF, (int) (header >> 24) & 0xFF, (int) nlumps); free(buf); exit(1); } free(buf); } mode_t umask_save; void lumps2file(const char *fn, unsigned long header, const char *const *lumps, size_t *lumpsize, size_t nlumps, D0_BOOL private) { char buf[65536]; size_t n; if(private) umask(umask_save | 0077); else umask(umask_save); if(!(n = Crypto_UnParsePack(buf, sizeof(buf), header, lumps, lumpsize, nlumps))) { fprintf(stderr, "could not unparse for %s\n", fn); exit(1); } if(!buf2file(fn, buf, n)) { fprintf(stderr, "could not write %s\n", fn); exit(1); } } void USAGE(const char *me) { printf("Usage:\n" "%s [-F] [-b bits] [-n progress-denominator] [-x prefix] [-X infix] [-C] -o private.d0sk\n" "%s -P private.d0sk -o public.d0pk\n" "%s [-n progress-denominator] [-x prefix] [-X infix] [-C] -p public.d0pk -o idkey-unsigned.d0si\n" "%s -p public.d0pk -I idkey-unsigned.d0si -o request.d0iq -O camouflage.d0ic\n" "%s -P private.d0sk -j request.d0iq -o response.d0ir\n" "%s -p public.d0pk -I idkey-unsigned.d0si -c camouflage.d0ic -J response.d0ir -o idkey.d0si\n" "%s -P private.d0sk -I idkey-unsigned.d0si -o idkey.d0si\n" "%s -I idkey.d0si -o id.d0pi\n" "%s -p public.d0pk\n" "%s -P private.d0sk\n" "%s -p public.d0pk -i id.d0pi\n" "%s -p public.d0pk -I idkey.d0si\n" "%s -0 -p public.d0pk -I idkey.d0si\n" "%s -0 -p public.d0pk\n" "%s -p public.d0pk -I idkey.d0si -d file-to-sign.dat -o file-signed.dat\n" "%s -p public.d0pk -s file-signed.dat -o file-content.dat [-O id.d0pi]\n" "%s -p public.d0pk -I idkey.d0si -d file-to-sign.dat -O signature.dat\n" "%s -p public.d0pk -d file-to-sign.dat -s signature.dat [-O id.d0pi]\n", me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me, me ); } unsigned int seconds; unsigned int generated; unsigned int ntasks = 1; double generated_offset; double guesscount; double guessfactor; void print_generated(int signo) { (void) signo; ++seconds; if(generated >= 1000000000) { generated_offset += generated; generated = 0; } fprintf(stderr, "Generated: %.0f (about %.0f, %.1f/s, about %.2f hours for %.0f)\n", // nasty and dishonest hack: // we are adjusting the values "back", so the total count is // divided by guessfactor (as the check function is called // guessfactor as often as it would be if no fastreject were // done) // so the values indicate the relative speed of fastreject vs // normal! (generated + generated_offset) / guessfactor, (generated + generated_offset) * ntasks / guessfactor, (generated + generated_offset) * ntasks / (guessfactor * seconds), guesscount * ((guessfactor * seconds) / (generated + generated_offset) / ntasks) / 3600.0, guesscount); alarm(1); } #define CHECK(x) if(!(x)) { fprintf(stderr, "error exit: error returned by %s\n", #x); exit(2); } const char *prefix = NULL, *infix = NULL; size_t prefixlen = 0; int ignorecase; typedef D0_BOOL (*fingerprint_func) (const d0_blind_id_t *ctx, char *outbuf, size_t *outbuflen); D0_BOOL fastreject(const d0_blind_id_t *ctx, void *pass) { static char fp64[513]; size_t fp64size = 512; CHECK(((fingerprint_func) pass)(ctx, fp64, &fp64size)); ++generated; if(ignorecase) { if(prefixlen) if(strncasecmp(fp64, prefix, prefixlen)) return 1; if(infix) { fp64[fp64size] = 0; if(!strcasestr(fp64, infix)) return 1; } } else { if(prefixlen) if(memcmp(fp64, prefix, prefixlen)) return 1; if(infix) { fp64[fp64size] = 0; if(!strstr(fp64, infix)) return 1; } } return 0; } int main(int argc, char **argv) { int opt; size_t lumpsize[2]; const char *lumps[2]; char *databuf_in; size_t databufsize_in; char *databuf_out; size_t databufsize_out; char *databuf_sig; size_t databufsize_sig; char lumps_w0[65536]; char lumps_w1[65536]; const char *pubkeyfile = NULL, *privkeyfile = NULL, *pubidfile = NULL, *prividfile = NULL, *idreqfile = NULL, *idresfile = NULL, *outfile = NULL, *outfile2 = NULL, *camouflagefile = NULL, *datafile = NULL, *sigfile = NULL; char fp64[513]; size_t fp64size = 512; int mask = 0; int bits = 1024; int i; D0_BOOL do_fastreject = 1; d0_blind_id_t *ctx; if(!d0_blind_id_INITIALIZE()) { fprintf(stderr, "could not initialize\n"); exit(1); } umask_save = umask(0022); ctx = d0_blind_id_new(); while((opt = getopt(argc, argv, "d:s:p:P:i:I:j:J:o:O:c:b:x:X:y:Fn:C0")) != -1) { switch(opt) { case 'C': ignorecase = 1; break; case 'n': ntasks = atoi(optarg); break; case 'b': bits = atoi(optarg); break; case 'p': // d0pk = pubkeyfile = optarg; mask |= 1; break; case 'P': // d0sk = privkeyfile = optarg; mask |= 2; break; case 'i': // d0pi = pubidfile = optarg; mask |= 4; break; case 'I': // d0si = prividfile = optarg; mask |= 8; break; case 'j': // d0iq = idreqfile = optarg; mask |= 0x10; break; case 'J': // d0ir = idresfile = optarg; mask |= 0x20; break; case 'o': outfile = optarg; mask |= 0x40; break; case 'O': outfile2 = optarg; mask |= 0x80; break; case 'c': camouflagefile = optarg; mask |= 0x100; break; case 'x': prefix = optarg; prefixlen = strlen(prefix); break; case '0': // test mode mask |= 0x200; break; case 'd': datafile = optarg; mask |= 0x400; break; case 's': sigfile = optarg; mask |= 0x800; break; case 'X': infix = optarg; break; case 'F': do_fastreject = 0; break; default: USAGE(*argv); return 1; } } // fastreject is a slight slowdown when rejecting nothing at all if(!infix && !prefixlen) do_fastreject = 0; guesscount = pow(64.0, prefixlen); if(infix) guesscount /= (1 - pow(1 - pow(1/64.0, strlen(infix)), 44 - prefixlen - strlen(infix))); // 44 chars; prefix is assumed to not match the infix (although it theoretically could) // 43'th char however is always '=' and does not count if(ignorecase) { if(infix) for(i = 0; infix[i]; ++i) if(toupper(infix[i]) != tolower(infix[i])) guesscount /= 2; for(i = 0; i < (int)prefixlen; ++i) if(toupper(prefix[i]) != tolower(prefix[i])) guesscount /= 2; } if(do_fastreject) { // fastreject: reject function gets called about log(2^bits) times more often guessfactor = bits * log(2) / 2; // so guess function gets called guesscount * guessfactor times, and it tests as many valid keys as guesscount } if(mask & 1) { file2lumps(pubkeyfile, FOURCC_D0PK, lumps, lumpsize, 2); if(!d0_blind_id_read_public_key(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not decode public key\n"); exit(1); } if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) { fprintf(stderr, "could not decode modulus\n"); exit(1); } } else if(mask & 2) { file2lumps(privkeyfile, FOURCC_D0SK, lumps, lumpsize, 2); if(!d0_blind_id_read_private_key(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not decode private key\n"); exit(1); } if(!d0_blind_id_read_private_id_modulus(ctx, lumps[1], lumpsize[1])) { fprintf(stderr, "could not decode modulus\n"); exit(1); } } if(mask & 4) { file2lumps(pubidfile, FOURCC_D0PI, lumps, lumpsize, 1); if(!d0_blind_id_read_public_id(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not decode public ID\n"); exit(1); } } if(mask & 8) { file2lumps(prividfile, FOURCC_D0SI, lumps, lumpsize, 1); if(!d0_blind_id_read_private_id(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not decode private ID\n"); exit(1); } } if(mask & 0x10) { file2lumps(idreqfile, FOURCC_D0IQ, lumps, lumpsize, 1); lumpsize[1] = sizeof(lumps_w1); lumps[1] = lumps_w1; if(!d0_blind_id_answer_private_id_request(ctx, lumps[0], lumpsize[0], lumps_w1, &lumpsize[1])) { fprintf(stderr, "could not answer private ID request\n"); exit(1); } } else if((mask & 0x120) == 0x120) { file2lumps(camouflagefile, FOURCC_D0IC, lumps, lumpsize, 1); if(!d0_blind_id_read_private_id_request_camouflage(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not decode camouflage\n"); exit(1); } file2lumps(idresfile, FOURCC_D0IR, lumps, lumpsize, 1); if(!d0_blind_id_finish_private_id_request(ctx, lumps[0], lumpsize[0])) { fprintf(stderr, "could not finish private ID request\n"); exit(1); } } if(mask & 0x400) { file2buf(datafile, &databuf_in, &databufsize_in); if(!databuf_in) { fprintf(stderr, "could not decode data\n"); exit(1); } } if(mask & 0x800) { file2buf(sigfile, &databuf_sig, &databufsize_sig); if(!databuf_sig) { fprintf(stderr, "could not decode signature\n"); exit(1); } } switch(mask) { // modes of operation: case 0x40: // nothing -> private key file (incl modulus), print fingerprint generated = 0; generated_offset = 0; seconds = 0; signal(SIGALRM, print_generated); alarm(1); if(do_fastreject) { CHECK(d0_blind_id_generate_private_key_fastreject(ctx, bits, fastreject, d0_blind_id_fingerprint64_public_key)); } else { guessfactor = 1; // no fastreject here do { CHECK(d0_blind_id_generate_private_key(ctx, bits)); } while(fastreject(ctx, d0_blind_id_fingerprint64_public_key)); } alarm(0); signal(SIGALRM, NULL); CHECK(d0_blind_id_generate_private_id_modulus(ctx)); lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); lumps[1] = lumps_w1; lumpsize[1] = sizeof(lumps_w1); CHECK(d0_blind_id_write_private_key(ctx, lumps_w0, &lumpsize[0])); CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); lumps2file(outfile, FOURCC_D0SK, lumps, lumpsize, 2, 1); break; case 0x42: // private key file -> public key file (incl modulus) lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); lumps[1] = lumps_w1; lumpsize[1] = sizeof(lumps_w1); CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); lumps2file(outfile, FOURCC_D0PK, lumps, lumpsize, 2, 0); break; case 0x41: // public key file -> unsigned private ID file generated = 0; generated_offset = 0; seconds = 0; signal(SIGALRM, print_generated); alarm(1); guessfactor = 1; // no fastreject here do { CHECK(d0_blind_id_generate_private_id_start(ctx)); } while(fastreject(ctx, d0_blind_id_fingerprint64_public_id)); alarm(0); signal(SIGALRM, 0); lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); break; case 0xC9: // public key file, unsigned private ID file -> ID request file and camouflage file lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_generate_private_id_request(ctx, lumps_w0, &lumpsize[0])); lumps2file(outfile, FOURCC_D0IQ, lumps, lumpsize, 1, 0); lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_write_private_id_request_camouflage(ctx, lumps_w0, &lumpsize[0])); lumps2file(outfile2, FOURCC_D0IC, lumps, lumpsize, 1, 1); break; case 0x52: // private key file, ID request file -> ID response file lumps2file(outfile, FOURCC_D0IR, lumps+1, lumpsize+1, 1, 0); break; case 0x169: // public key file, ID response file, private ID file -> signed private ID file lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_write_private_id(ctx, lumps_w0, &lumpsize[0])); lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); break; case 0x4A: // private key file, private ID file -> signed private ID file { char buf[65536]; size_t bufsize; char buf2[65536]; size_t buf2size; D0_BOOL status; d0_blind_id_t *ctx2 = d0_blind_id_new(); CHECK(d0_blind_id_copy(ctx2, ctx)); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); buf2size = sizeof(buf2); CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); buf2size = sizeof(buf2); CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); CHECK(status == 0); CHECK(d0_blind_id_authenticate_with_private_id_generate_missing_signature(ctx2)); lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_write_private_id(ctx2, lumps_w0, &lumpsize[0])); lumps2file(outfile, FOURCC_D0SI, lumps, lumpsize, 1, 1); } break; case 0x48: // private ID file -> public ID file lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); CHECK(d0_blind_id_write_public_id(ctx, lumps_w0, &lumpsize[0])); lumps2file(outfile, FOURCC_D0PI, lumps, lumpsize, 1, 0); break; case 0x01: case 0x02: // public/private key file -> fingerprint CHECK(d0_blind_id_fingerprint64_public_key(ctx, fp64, &fp64size)); printf("%.*s\n", (int)fp64size, fp64); break; case 0x05: case 0x09: // public/private ID file -> fingerprint CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); printf("%.*s\n", (int)fp64size, fp64); break; case 0x449: // public key, private ID, data -> signed data databufsize_out = databufsize_in + 8192; databuf_out = malloc(databufsize_out); CHECK(d0_blind_id_sign_with_private_id_sign(ctx, 1, 0, databuf_in, databufsize_in, databuf_out, &databufsize_out)); buf2file(outfile, databuf_out, databufsize_out); break; case 0x489: // public key, private ID, data -> signature databufsize_out = databufsize_in + 8192; databuf_out = malloc(databufsize_out); CHECK(d0_blind_id_sign_with_private_id_sign_detached(ctx, 1, 0, databuf_in, databufsize_in, databuf_out, &databufsize_out)); buf2file(outfile2, databuf_out, databufsize_out); break; case 0x841: case 0x8C1: // public key, signed data -> data, optional public ID { D0_BOOL status; databufsize_out = databufsize_sig; databuf_out = malloc(databufsize_out); CHECK(d0_blind_id_sign_with_private_id_verify(ctx, 1, 0, databuf_sig, databufsize_sig, databuf_out, &databufsize_out, &status)); CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); printf("%d\n", (int)status); printf("%.*s\n", (int)fp64size, fp64); buf2file(outfile, databuf_out, databufsize_out); if(outfile2) { lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); lumps[1] = lumps_w1; lumpsize[1] = sizeof(lumps_w1); CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); lumps2file(outfile2, FOURCC_D0PK, lumps, lumpsize, 2, 0); } } break; case 0xC01: case 0xC81: // public key, signature, signed data -> optional public ID { D0_BOOL status; CHECK(d0_blind_id_sign_with_private_id_verify_detached(ctx, 1, 0, databuf_sig, databufsize_sig, databuf_in, databufsize_in, &status)); CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); printf("%d\n", (int)status); printf("%.*s\n", (int)fp64size, fp64); if(outfile2) { lumps[0] = lumps_w0; lumpsize[0] = sizeof(lumps_w0); lumps[1] = lumps_w1; lumpsize[1] = sizeof(lumps_w1); CHECK(d0_blind_id_write_public_key(ctx, lumps_w0, &lumpsize[0])); CHECK(d0_blind_id_write_private_id_modulus(ctx, lumps_w1, &lumpsize[1])); lumps2file(outfile2, FOURCC_D0PK, lumps, lumpsize, 2, 0); } } break; /* case 0x09: // public key, private ID file -> test whether key is properly signed { char buf[65536]; size_t bufsize; char buf2[65536]; size_t buf2size; D0_BOOL status; d0_blind_id_t *ctx2 = d0_blind_id_new(); CHECK(d0_blind_id_copy(ctx2, ctx)); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); buf2size = sizeof(buf2); CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx2, 1, 1, buf, bufsize, buf2, &buf2size, &status)); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); buf2size = sizeof(buf2); CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx2, buf, bufsize, buf2, &buf2size, &status)); if(status) printf("OK\n"); else printf("EPIC FAIL\n"); } break; */ case 0x209: // protocol client { char hexbuf[131073]; const char hex[] = "0123456789abcdef"; char buf[65536]; size_t bufsize; char buf2[65536]; size_t buf2size; bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_start(ctx, 1, 1, "hello world", 11, buf, &bufsize)); for(i = 0; i < (int)bufsize; ++i) sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); printf("%s\n", hexbuf); fgets(hexbuf, sizeof(hexbuf), stdin); buf2size = strlen(hexbuf) / 2; for(i = 0; i < (int)buf2size; ++i) buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_response(ctx, buf2, buf2size, buf, &bufsize)); for(i = 0; i < (int)bufsize; ++i) sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); printf("%s\n", hexbuf); } break; case 0x201: // protocol server { char hexbuf[131073]; const char hex[] = "0123456789abcdef"; char buf[65536]; size_t bufsize; char buf2[65536]; size_t buf2size; D0_BOOL status; fgets(hexbuf, sizeof(hexbuf), stdin); buf2size = strlen(hexbuf) / 2; for(i = 0; i < (int)buf2size; ++i) buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_challenge(ctx, 1, 1, buf2, buf2size, buf, &bufsize, &status)); for(i = 0; i < (int)bufsize; ++i) sprintf(&hexbuf[2*i], "%02x", (unsigned char)buf[i]); printf("%s\n", hexbuf); fgets(hexbuf, sizeof(hexbuf), stdin); buf2size = strlen(hexbuf) / 2; for(i = 0; i < (int)buf2size; ++i) buf2[i] = ((strchr(hex, hexbuf[2*i]) - hex) << 4) | (strchr(hex, hexbuf[2*i+1]) - hex); bufsize = sizeof(buf); CHECK(d0_blind_id_authenticate_with_private_id_verify(ctx, buf2, buf2size, buf, &bufsize, &status)); printf("verify status: %d\n", status); CHECK(d0_blind_id_fingerprint64_public_id(ctx, fp64, &fp64size)); printf("%.*s\n", (int)fp64size, fp64); } break; default: USAGE(*argv); exit(1); break; } d0_blind_id_SHUTDOWN(); return 0; } darkplaces/mod_skeletal_animatevertices_generic.c0000664000175000017500000001143513067716220021735 0ustar kalevkalev#include "mod_skeletal_animatevertices_generic.h" void Mod_Skeletal_AnimateVertices_Generic(const dp_model_t * RESTRICT model, const frameblend_t * RESTRICT frameblend, const skeleton_t *skeleton, float * RESTRICT vertex3f, float * RESTRICT normal3f, float * RESTRICT svector3f, float * RESTRICT tvector3f) { // vertex weighted skeletal int i, k; float *bonepose; float *boneposerelative; const blendweights_t * RESTRICT weights; //unsigned long long ts = rdtsc(); bonepose = (float *) Mod_Skeletal_AnimateVertices_AllocBuffers(sizeof(float[12]) * (model->num_bones*2 + model->surfmesh.num_blends)); boneposerelative = bonepose + model->num_bones * 12; Mod_Skeletal_BuildTransforms(model, frameblend, skeleton, bonepose, boneposerelative); // generate matrices for all blend combinations weights = model->surfmesh.data_blendweights; for (i = 0;i < model->surfmesh.num_blends;i++, weights++) { float * RESTRICT b = boneposerelative + 12 * (model->num_bones + i); const float * RESTRICT m = boneposerelative + 12 * (unsigned int)weights->index[0]; float f = weights->influence[0] * (1.0f / 255.0f); b[ 0] = f*m[ 0]; b[ 1] = f*m[ 1]; b[ 2] = f*m[ 2]; b[ 3] = f*m[ 3]; b[ 4] = f*m[ 4]; b[ 5] = f*m[ 5]; b[ 6] = f*m[ 6]; b[ 7] = f*m[ 7]; b[ 8] = f*m[ 8]; b[ 9] = f*m[ 9]; b[10] = f*m[10]; b[11] = f*m[11]; for (k = 1;k < 4 && weights->influence[k];k++) { m = boneposerelative + 12 * (unsigned int)weights->index[k]; f = weights->influence[k] * (1.0f / 255.0f); b[ 0] += f*m[ 0]; b[ 1] += f*m[ 1]; b[ 2] += f*m[ 2]; b[ 3] += f*m[ 3]; b[ 4] += f*m[ 4]; b[ 5] += f*m[ 5]; b[ 6] += f*m[ 6]; b[ 7] += f*m[ 7]; b[ 8] += f*m[ 8]; b[ 9] += f*m[ 9]; b[10] += f*m[10]; b[11] += f*m[11]; } } #define LOAD_MATRIX_SCALAR() const float * RESTRICT m = boneposerelative + 12 * (unsigned int)*b #define LOAD_MATRIX3() \ LOAD_MATRIX_SCALAR() #define LOAD_MATRIX4() \ LOAD_MATRIX_SCALAR() #define TRANSFORM_POSITION_SCALAR(in, out) \ (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2] + m[3]); \ (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6] + m[7]); \ (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10] + m[11]); #define TRANSFORM_VECTOR_SCALAR(in, out) \ (out)[0] = ((in)[0] * m[0] + (in)[1] * m[1] + (in)[2] * m[ 2]); \ (out)[1] = ((in)[0] * m[4] + (in)[1] * m[5] + (in)[2] * m[ 6]); \ (out)[2] = ((in)[0] * m[8] + (in)[1] * m[9] + (in)[2] * m[10]); #define TRANSFORM_POSITION(in, out) \ TRANSFORM_POSITION_SCALAR(in, out) #define TRANSFORM_VECTOR(in, out) \ TRANSFORM_VECTOR_SCALAR(in, out) // transform vertex attributes by blended matrices if (vertex3f) { const float * RESTRICT v = model->surfmesh.data_vertex3f; const unsigned short * RESTRICT b = model->surfmesh.blends; // special case common combinations of attributes to avoid repeated loading of matrices if (normal3f) { const float * RESTRICT n = model->surfmesh.data_normal3f; if (svector3f && tvector3f) { const float * RESTRICT svec = model->surfmesh.data_svector3f; const float * RESTRICT tvec = model->surfmesh.data_tvector3f; // Note that for SSE each iteration stores one element past end, so we break one vertex short // and handle that with scalars in that case for (i = 0; i < model->surfmesh.num_vertices; i++, v += 3, n += 3, svec += 3, tvec += 3, b++, vertex3f += 3, normal3f += 3, svector3f += 3, tvector3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); TRANSFORM_VECTOR(n, normal3f); TRANSFORM_VECTOR(svec, svector3f); TRANSFORM_VECTOR(tvec, tvector3f); } return; } for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, n += 3, b++, vertex3f += 3, normal3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); TRANSFORM_VECTOR(n, normal3f); } } else { for (i = 0;i < model->surfmesh.num_vertices; i++, v += 3, b++, vertex3f += 3) { LOAD_MATRIX4(); TRANSFORM_POSITION(v, vertex3f); } } } else if (normal3f) { const float * RESTRICT n = model->surfmesh.data_normal3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < model->surfmesh.num_vertices; i++, n += 3, b++, normal3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(n, normal3f); } } if (svector3f) { const float * RESTRICT svec = model->surfmesh.data_svector3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < model->surfmesh.num_vertices; i++, svec += 3, b++, svector3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(svec, svector3f); } } if (tvector3f) { const float * RESTRICT tvec = model->surfmesh.data_tvector3f; const unsigned short * RESTRICT b = model->surfmesh.blends; for (i = 0; i < model->surfmesh.num_vertices; i++, tvec += 3, b++, tvector3f += 3) { LOAD_MATRIX3(); TRANSFORM_VECTOR(tvec, tvector3f); } } } darkplaces/r_shadow.c0000664000175000017500000113604413067716222014150 0ustar kalevkalev /* Terminology: Stencil Shadow Volume (sometimes called Stencil Shadows) An extrusion of the lit faces, beginning at the original geometry and ending further from the light source than the original geometry (presumably at least as far as the light's radius, if the light has a radius at all), capped at both front and back to avoid any problems (extrusion from dark faces also works but has a different set of problems) This is normally rendered using Carmack's Reverse technique, in which backfaces behind zbuffer (zfail) increment the stencil, and frontfaces behind zbuffer (zfail) decrement the stencil, the result is a stencil value of zero where shadows did not intersect the visible geometry, suitable as a stencil mask for rendering lighting everywhere but shadow. In our case to hopefully avoid the Creative Labs patent, we draw the backfaces as decrement and the frontfaces as increment, and we redefine the DepthFunc to GL_LESS (the patent uses GL_GEQUAL) which causes zfail when behind surfaces and zpass when infront (the patent draws where zpass with a GL_GEQUAL test), additionally we clear stencil to 128 to avoid the need for the unclamped incr/decr extension (not related to patent). Patent warning: This algorithm may be covered by Creative's patent (US Patent #6384822), however that patent is quite specific about increment on backfaces and decrement on frontfaces where zpass with GL_GEQUAL depth test, which is opposite this implementation and partially opposite Carmack's Reverse paper (which uses GL_LESS, but increments on backfaces and decrements on frontfaces). Terminology: Stencil Light Volume (sometimes called Light Volumes) Similar to a Stencil Shadow Volume, but inverted; rather than containing the areas in shadow it contains the areas in light, this can only be built quickly for certain limited cases (such as portal visibility from a point), but is quite useful for some effects (sunlight coming from sky polygons is one possible example, translucent occluders is another example). Terminology: Optimized Stencil Shadow Volume A Stencil Shadow Volume that has been processed sufficiently to ensure it has no duplicate coverage of areas (no need to shadow an area twice), often this greatly improves performance but is an operation too costly to use on moving lights (however completely optimal Stencil Light Volumes can be constructed in some ideal cases). Terminology: Per Pixel Lighting (sometimes abbreviated PPL) Per pixel evaluation of lighting equations, at a bare minimum this involves DOT3 shading of diffuse lighting (per pixel dotproduct of negated incidence vector and surface normal, using a texture of the surface bumps, called a NormalMap) if supported by hardware; in our case there is support for cards which are incapable of DOT3, the quality is quite poor however. Additionally it is desirable to have specular evaluation per pixel, per vertex normalization of specular halfangle vectors causes noticable distortion but is unavoidable on hardware without GL_ARB_fragment_program or GL_ARB_fragment_shader. Terminology: Normalization CubeMap A cubemap containing normalized dot3-encoded (vectors of length 1 or less encoded as RGB colors) for any possible direction, this technique allows per pixel calculation of incidence vector for per pixel lighting purposes, which would not otherwise be possible per pixel without GL_ARB_fragment_program or GL_ARB_fragment_shader. Terminology: 2D+1D Attenuation Texturing A very crude approximation of light attenuation with distance which results in cylindrical light shapes which fade vertically as a streak (some games such as Doom3 allow this to be rotated to be less noticable in specific cases), the technique is simply modulating lighting by two 2D textures (which can be the same) on different axes of projection (XY and Z, typically), this is the second best technique available without 3D Attenuation Texturing, GL_ARB_fragment_program or GL_ARB_fragment_shader technology. Terminology: 2D+1D Inverse Attenuation Texturing A clever method described in papers on the Abducted engine, this has a squared distance texture (bright on the outside, black in the middle), which is used twice using GL_ADD blending, the result of this is used in an inverse modulate (GL_ONE_MINUS_DST_ALPHA, GL_ZERO) to implement the equation lighting*=(1-((X*X+Y*Y)+(Z*Z))) which is spherical (unlike 2D+1D attenuation texturing). Terminology: 3D Attenuation Texturing A slightly crude approximation of light attenuation with distance, its flaws are limited radius and resolution (performance tradeoffs). Terminology: 3D Attenuation-Normalization Texturing A 3D Attenuation Texture merged with a Normalization CubeMap, by making the vectors shorter the lighting becomes darker, a very effective optimization of diffuse lighting if 3D Attenuation Textures are already used. Terminology: Light Cubemap Filtering A technique for modeling non-uniform light distribution according to direction, for example a lantern may use a cubemap to describe the light emission pattern of the cage around the lantern (as well as soot buildup discoloring the light in certain areas), often also used for softened grate shadows and light shining through a stained glass window (done crudely by texturing the lighting with a cubemap), another good example would be a disco light. This technique is used heavily in many games (Doom3 does not support this however). Terminology: Light Projection Filtering A technique for modeling shadowing of light passing through translucent surfaces, allowing stained glass windows and other effects to be done more elegantly than possible with Light Cubemap Filtering by applying an occluder texture to the lighting combined with a stencil light volume to limit the lit area, this technique is used by Doom3 for spotlights and flashlights, among other things, this can also be used more generally to render light passing through multiple translucent occluders in a scene (using a light volume to describe the area beyond the occluder, and thus mask off rendering of all other areas). Terminology: Doom3 Lighting A combination of Stencil Shadow Volume, Per Pixel Lighting, Normalization CubeMap, 2D+1D Attenuation Texturing, and Light Projection Filtering, as demonstrated by the game Doom3. */ #include "quakedef.h" #include "r_shadow.h" #include "cl_collision.h" #include "portals.h" #include "image.h" #include "dpsoftrast.h" #ifdef SUPPORTD3D #include extern LPDIRECT3DDEVICE9 vid_d3d9dev; #endif static void R_Shadow_EditLights_Init(void); typedef enum r_shadow_rendermode_e { R_SHADOW_RENDERMODE_NONE, R_SHADOW_RENDERMODE_ZPASS_STENCIL, R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL, R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE, R_SHADOW_RENDERMODE_ZFAIL_STENCIL, R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL, R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE, R_SHADOW_RENDERMODE_LIGHT_VERTEX, R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN, R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN, R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN, R_SHADOW_RENDERMODE_LIGHT_GLSL, R_SHADOW_RENDERMODE_VISIBLEVOLUMES, R_SHADOW_RENDERMODE_VISIBLELIGHTING, R_SHADOW_RENDERMODE_SHADOWMAP2D } r_shadow_rendermode_t; typedef enum r_shadow_shadowmode_e { R_SHADOW_SHADOWMODE_STENCIL, R_SHADOW_SHADOWMODE_SHADOWMAP2D } r_shadow_shadowmode_t; r_shadow_rendermode_t r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; r_shadow_rendermode_t r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_NONE; r_shadow_rendermode_t r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_NONE; r_shadow_rendermode_t r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_NONE; qboolean r_shadow_usingshadowmap2d; qboolean r_shadow_usingshadowmaportho; int r_shadow_shadowmapside; float r_shadow_shadowmap_texturescale[2]; float r_shadow_shadowmap_parameters[4]; #if 0 int r_shadow_drawbuffer; int r_shadow_readbuffer; #endif int r_shadow_cullface_front, r_shadow_cullface_back; GLuint r_shadow_fbo2d; r_shadow_shadowmode_t r_shadow_shadowmode; int r_shadow_shadowmapfilterquality; int r_shadow_shadowmapdepthbits; int r_shadow_shadowmapmaxsize; qboolean r_shadow_shadowmapvsdct; qboolean r_shadow_shadowmapsampler; qboolean r_shadow_shadowmapshadowsampler; int r_shadow_shadowmappcf; int r_shadow_shadowmapborder; matrix4x4_t r_shadow_shadowmapmatrix; int r_shadow_lightscissor[4]; qboolean r_shadow_usingdeferredprepass; qboolean r_shadow_shadowmapdepthtexture; int maxshadowtriangles; int *shadowelements; int maxshadowvertices; float *shadowvertex3f; int maxshadowmark; int numshadowmark; int *shadowmark; int *shadowmarklist; int shadowmarkcount; int maxshadowsides; int numshadowsides; unsigned char *shadowsides; int *shadowsideslist; int maxvertexupdate; int *vertexupdate; int *vertexremap; int vertexupdatenum; int r_shadow_buffer_numleafpvsbytes; unsigned char *r_shadow_buffer_visitingleafpvs; unsigned char *r_shadow_buffer_leafpvs; int *r_shadow_buffer_leaflist; int r_shadow_buffer_numsurfacepvsbytes; unsigned char *r_shadow_buffer_surfacepvs; int *r_shadow_buffer_surfacelist; unsigned char *r_shadow_buffer_surfacesides; int r_shadow_buffer_numshadowtrispvsbytes; unsigned char *r_shadow_buffer_shadowtrispvs; int r_shadow_buffer_numlighttrispvsbytes; unsigned char *r_shadow_buffer_lighttrispvs; rtexturepool_t *r_shadow_texturepool; rtexture_t *r_shadow_attenuationgradienttexture; rtexture_t *r_shadow_attenuation2dtexture; rtexture_t *r_shadow_attenuation3dtexture; skinframe_t *r_shadow_lightcorona; rtexture_t *r_shadow_shadowmap2ddepthbuffer; rtexture_t *r_shadow_shadowmap2ddepthtexture; rtexture_t *r_shadow_shadowmapvsdcttexture; int r_shadow_shadowmapsize; // changes for each light based on distance GLuint r_shadow_prepassgeometryfbo; GLuint r_shadow_prepasslightingdiffusespecularfbo; GLuint r_shadow_prepasslightingdiffusefbo; int r_shadow_prepass_width; int r_shadow_prepass_height; rtexture_t *r_shadow_prepassgeometrydepthbuffer; rtexture_t *r_shadow_prepassgeometrynormalmaptexture; rtexture_t *r_shadow_prepasslightingdiffusetexture; rtexture_t *r_shadow_prepasslightingspeculartexture; // keep track of the provided framebuffer info static int r_shadow_fb_fbo; static rtexture_t *r_shadow_fb_depthtexture; static rtexture_t *r_shadow_fb_colortexture; // lights are reloaded when this changes char r_shadow_mapname[MAX_QPATH]; // buffer for doing corona fading unsigned int r_shadow_occlusion_buf = 0; // used only for light filters (cubemaps) rtexturepool_t *r_shadow_filters_texturepool; cvar_t r_shadow_bumpscale_basetexture = {0, "r_shadow_bumpscale_basetexture", "0", "generate fake bumpmaps from diffuse textures at this bumpyness, try 4 to match tenebrae, higher values increase depth, requires r_restart to take effect"}; cvar_t r_shadow_bumpscale_bumpmap = {0, "r_shadow_bumpscale_bumpmap", "4", "what magnitude to interpret _bump.tga textures as, higher values increase depth, requires r_restart to take effect"}; cvar_t r_shadow_debuglight = {0, "r_shadow_debuglight", "-1", "renders only one light, for level design purposes or debugging"}; cvar_t r_shadow_deferred = {CVAR_SAVE, "r_shadow_deferred", "0", "uses image-based lighting instead of geometry-based lighting, the method used renders a depth image and a normalmap image, renders lights into separate diffuse and specular images, and then combines this into the normal rendering, requires r_shadow_shadowmapping"}; cvar_t r_shadow_usebihculling = {0, "r_shadow_usebihculling", "1", "use BIH (Bounding Interval Hierarchy) for culling lit surfaces instead of BSP (Binary Space Partitioning)"}; cvar_t r_shadow_usenormalmap = {CVAR_SAVE, "r_shadow_usenormalmap", "1", "enables use of directional shading on lights"}; cvar_t r_shadow_gloss = {CVAR_SAVE, "r_shadow_gloss", "1", "0 disables gloss (specularity) rendering, 1 uses gloss if textures are found, 2 forces a flat metallic specular effect on everything without textures (similar to tenebrae)"}; cvar_t r_shadow_gloss2intensity = {0, "r_shadow_gloss2intensity", "0.125", "how bright the forced flat gloss should look if r_shadow_gloss is 2"}; cvar_t r_shadow_glossintensity = {0, "r_shadow_glossintensity", "1", "how bright textured glossmaps should look if r_shadow_gloss is 1 or 2"}; cvar_t r_shadow_glossexponent = {0, "r_shadow_glossexponent", "32", "how 'sharp' the gloss should appear (specular power)"}; cvar_t r_shadow_gloss2exponent = {0, "r_shadow_gloss2exponent", "32", "same as r_shadow_glossexponent but for forced gloss (gloss 2) surfaces"}; cvar_t r_shadow_glossexact = {0, "r_shadow_glossexact", "0", "use exact reflection math for gloss (slightly slower, but should look a tad better)"}; cvar_t r_shadow_lightattenuationdividebias = {0, "r_shadow_lightattenuationdividebias", "1", "changes attenuation texture generation"}; cvar_t r_shadow_lightattenuationlinearscale = {0, "r_shadow_lightattenuationlinearscale", "2", "changes attenuation texture generation"}; cvar_t r_shadow_lightintensityscale = {0, "r_shadow_lightintensityscale", "1", "renders all world lights brighter or darker"}; cvar_t r_shadow_lightradiusscale = {0, "r_shadow_lightradiusscale", "1", "renders all world lights larger or smaller"}; cvar_t r_shadow_projectdistance = {0, "r_shadow_projectdistance", "0", "how far to cast shadows"}; cvar_t r_shadow_frontsidecasting = {0, "r_shadow_frontsidecasting", "1", "whether to cast shadows from illuminated triangles (front side of model) or unlit triangles (back side of model)"}; cvar_t r_shadow_realtime_dlight = {CVAR_SAVE, "r_shadow_realtime_dlight", "1", "enables rendering of dynamic lights such as explosions and rocket light"}; cvar_t r_shadow_realtime_dlight_shadows = {CVAR_SAVE, "r_shadow_realtime_dlight_shadows", "1", "enables rendering of shadows from dynamic lights"}; cvar_t r_shadow_realtime_dlight_svbspculling = {0, "r_shadow_realtime_dlight_svbspculling", "0", "enables svbsp optimization on dynamic lights (very slow!)"}; cvar_t r_shadow_realtime_dlight_portalculling = {0, "r_shadow_realtime_dlight_portalculling", "0", "enables portal optimization on dynamic lights (slow!)"}; cvar_t r_shadow_realtime_world = {CVAR_SAVE, "r_shadow_realtime_world", "0", "enables rendering of full world lighting (whether loaded from the map, or a .rtlights file, or a .ent file, or a .lights file produced by hlight)"}; cvar_t r_shadow_realtime_world_importlightentitiesfrommap = {0, "r_shadow_realtime_world_importlightentitiesfrommap", "1", "load lights from .ent file or map entities at startup if no .rtlights or .lights file is present (if set to 2, always use the .ent or map entities)"}; cvar_t r_shadow_realtime_world_lightmaps = {CVAR_SAVE, "r_shadow_realtime_world_lightmaps", "0", "brightness to render lightmaps when using full world lighting, try 0.5 for a tenebrae-like appearance"}; cvar_t r_shadow_realtime_world_shadows = {CVAR_SAVE, "r_shadow_realtime_world_shadows", "1", "enables rendering of shadows from world lights"}; cvar_t r_shadow_realtime_world_compile = {0, "r_shadow_realtime_world_compile", "1", "enables compilation of world lights for higher performance rendering"}; cvar_t r_shadow_realtime_world_compileshadow = {0, "r_shadow_realtime_world_compileshadow", "1", "enables compilation of shadows from world lights for higher performance rendering"}; cvar_t r_shadow_realtime_world_compilesvbsp = {0, "r_shadow_realtime_world_compilesvbsp", "1", "enables svbsp optimization during compilation (slower than compileportalculling but more exact)"}; cvar_t r_shadow_realtime_world_compileportalculling = {0, "r_shadow_realtime_world_compileportalculling", "1", "enables portal-based culling optimization during compilation (overrides compilesvbsp)"}; cvar_t r_shadow_scissor = {0, "r_shadow_scissor", "1", "use scissor optimization of light rendering (restricts rendering to the portion of the screen affected by the light)"}; cvar_t r_shadow_shadowmapping = {CVAR_SAVE, "r_shadow_shadowmapping", "1", "enables use of shadowmapping (depth texture sampling) instead of stencil shadow volumes"}; cvar_t r_shadow_shadowmapping_filterquality = {CVAR_SAVE, "r_shadow_shadowmapping_filterquality", "-1", "shadowmap filter modes: -1 = auto-select, 0 = no filtering, 1 = bilinear, 2 = bilinear 2x2 blur (fast), 3 = 3x3 blur (moderate), 4 = 4x4 blur (slow)"}; cvar_t r_shadow_shadowmapping_useshadowsampler = {CVAR_SAVE, "r_shadow_shadowmapping_useshadowsampler", "1", "whether to use sampler2DShadow if available"}; cvar_t r_shadow_shadowmapping_depthbits = {CVAR_SAVE, "r_shadow_shadowmapping_depthbits", "24", "requested minimum shadowmap texture depth bits"}; cvar_t r_shadow_shadowmapping_vsdct = {CVAR_SAVE, "r_shadow_shadowmapping_vsdct", "1", "enables use of virtual shadow depth cube texture"}; cvar_t r_shadow_shadowmapping_minsize = {CVAR_SAVE, "r_shadow_shadowmapping_minsize", "32", "shadowmap size limit"}; cvar_t r_shadow_shadowmapping_maxsize = {CVAR_SAVE, "r_shadow_shadowmapping_maxsize", "512", "shadowmap size limit"}; cvar_t r_shadow_shadowmapping_precision = {CVAR_SAVE, "r_shadow_shadowmapping_precision", "1", "makes shadowmaps have a maximum resolution of this number of pixels per light source radius unit such that, for example, at precision 0.5 a light with radius 200 will have a maximum resolution of 100 pixels"}; //cvar_t r_shadow_shadowmapping_lod_bias = {CVAR_SAVE, "r_shadow_shadowmapping_lod_bias", "16", "shadowmap size bias"}; //cvar_t r_shadow_shadowmapping_lod_scale = {CVAR_SAVE, "r_shadow_shadowmapping_lod_scale", "128", "shadowmap size scaling parameter"}; cvar_t r_shadow_shadowmapping_bordersize = {CVAR_SAVE, "r_shadow_shadowmapping_bordersize", "4", "shadowmap size bias for filtering"}; cvar_t r_shadow_shadowmapping_nearclip = {CVAR_SAVE, "r_shadow_shadowmapping_nearclip", "1", "shadowmap nearclip in world units"}; cvar_t r_shadow_shadowmapping_bias = {CVAR_SAVE, "r_shadow_shadowmapping_bias", "0.03", "shadowmap bias parameter (this is multiplied by nearclip * 1024 / lodsize)"}; cvar_t r_shadow_shadowmapping_polygonfactor = {CVAR_SAVE, "r_shadow_shadowmapping_polygonfactor", "2", "slope-dependent shadowmapping bias"}; cvar_t r_shadow_shadowmapping_polygonoffset = {CVAR_SAVE, "r_shadow_shadowmapping_polygonoffset", "0", "constant shadowmapping bias"}; cvar_t r_shadow_sortsurfaces = {0, "r_shadow_sortsurfaces", "1", "improve performance by sorting illuminated surfaces by texture"}; cvar_t r_shadow_polygonfactor = {0, "r_shadow_polygonfactor", "0", "how much to enlarge shadow volume polygons when rendering (should be 0!)"}; cvar_t r_shadow_polygonoffset = {0, "r_shadow_polygonoffset", "1", "how much to push shadow volumes into the distance when rendering, to reduce chances of zfighting artifacts (should not be less than 0)"}; cvar_t r_shadow_texture3d = {0, "r_shadow_texture3d", "1", "use 3D voxel textures for spherical attenuation rather than cylindrical (does not affect OpenGL 2.0 render path)"}; cvar_t r_shadow_bouncegrid = {CVAR_SAVE, "r_shadow_bouncegrid", "0", "perform particle tracing for indirect lighting (Global Illumination / radiosity) using a 3D texture covering the scene, only active on levels with realtime lights active (r_shadow_realtime_world is usually required for these)"}; cvar_t r_shadow_bouncegrid_blur = {CVAR_SAVE, "r_shadow_bouncegrid_blur", "1", "apply a 1-radius blur on bouncegrid to denoise it and deal with boundary issues with surfaces"}; cvar_t r_shadow_bouncegrid_bounceanglediffuse = {CVAR_SAVE, "r_shadow_bouncegrid_bounceanglediffuse", "0", "use random bounce direction rather than true reflection, makes some corner areas dark"}; cvar_t r_shadow_bouncegrid_dynamic_culllightpaths = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_culllightpaths", "1", "skip accumulating light in the bouncegrid texture where the light paths are out of view (dynamic mode only)"}; cvar_t r_shadow_bouncegrid_dynamic_dlightparticlemultiplier = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_dlightparticlemultiplier", "1", "if set to a high value like 16 this can make dlights look great, but 0 is recommended for performance reasons"}; cvar_t r_shadow_bouncegrid_dynamic_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_directionalshading", "0", "use diffuse shading rather than ambient, 3D texture becomes 8x as many pixels to hold the additional data"}; cvar_t r_shadow_bouncegrid_dynamic_hitmodels = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_hitmodels", "0", "enables hitting character model geometry (SLOW)"}; cvar_t r_shadow_bouncegrid_dynamic_energyperphoton = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_energyperphoton", "10000", "amount of light that one photon should represent"}; cvar_t r_shadow_bouncegrid_dynamic_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_lightradiusscale", "10", "particles stop at this fraction of light radius (can be more than 1)"}; cvar_t r_shadow_bouncegrid_dynamic_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_maxbounce", "5", "maximum number of bounces for a particle (minimum is 0)"}; cvar_t r_shadow_bouncegrid_dynamic_maxphotons = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_maxphotons", "25000", "upper bound on photons to shoot per update, divided proportionately between lights - normally the number of photons is calculated by energyperphoton"}; cvar_t r_shadow_bouncegrid_dynamic_spacing = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_spacing", "64", "unit size of bouncegrid pixel"}; cvar_t r_shadow_bouncegrid_dynamic_stablerandom = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_stablerandom", "1", "make particle distribution consistent from frame to frame"}; cvar_t r_shadow_bouncegrid_dynamic_updateinterval = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_updateinterval", "0", "update bouncegrid texture once per this many seconds, useful values are 0, 0.05, or 1000000"}; cvar_t r_shadow_bouncegrid_dynamic_x = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_x", "64", "maximum texture size of bouncegrid on X axis"}; cvar_t r_shadow_bouncegrid_dynamic_y = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_y", "64", "maximum texture size of bouncegrid on Y axis"}; cvar_t r_shadow_bouncegrid_dynamic_z = {CVAR_SAVE, "r_shadow_bouncegrid_dynamic_z", "32", "maximum texture size of bouncegrid on Z axis"}; cvar_t r_shadow_bouncegrid_floatcolors = {CVAR_SAVE, "r_shadow_bouncegrid_floatcolors", "1", "upload texture as RGBA16F (or RGBA32F when set to 2) rather than RGBA8 format - this gives more dynamic range and accuracy"}; cvar_t r_shadow_bouncegrid_includedirectlighting = {CVAR_SAVE, "r_shadow_bouncegrid_includedirectlighting", "0", "allows direct lighting to be recorded, not just indirect (gives an effect somewhat like r_shadow_realtime_world_lightmaps)"}; cvar_t r_shadow_bouncegrid_intensity = {CVAR_SAVE, "r_shadow_bouncegrid_intensity", "4", "overall brightness of bouncegrid texture"}; cvar_t r_shadow_bouncegrid_particlebounceintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particlebounceintensity", "2", "amount of energy carried over after each bounce, this is a multiplier of texture color and the result is clamped to 1 or less, to prevent adding energy on each bounce"}; cvar_t r_shadow_bouncegrid_particleintensity = {CVAR_SAVE, "r_shadow_bouncegrid_particleintensity", "0.25", "brightness of particles contributing to bouncegrid texture"}; cvar_t r_shadow_bouncegrid_sortlightpaths = {CVAR_SAVE, "r_shadow_bouncegrid_sortlightpaths", "1", "sort light paths before accumulating them into the bouncegrid texture, this reduces cpu cache misses"}; cvar_t r_shadow_bouncegrid_lightpathsize = {CVAR_SAVE, "r_shadow_bouncegrid_lightpathsize", "1", "width of the light path for accumulation of light in the bouncegrid texture"}; cvar_t r_shadow_bouncegrid_static = {CVAR_SAVE, "r_shadow_bouncegrid_static", "1", "use static radiosity solution (high quality) rather than dynamic (splotchy)"}; cvar_t r_shadow_bouncegrid_static_directionalshading = {CVAR_SAVE, "r_shadow_bouncegrid_static_directionalshading", "1", "whether to use directionalshading when in static mode"}; cvar_t r_shadow_bouncegrid_static_energyperphoton = {CVAR_SAVE, "r_shadow_bouncegrid_static_energyperphoton", "10000", "amount of light that one photon should represent in static mode"}; cvar_t r_shadow_bouncegrid_static_lightradiusscale = {CVAR_SAVE, "r_shadow_bouncegrid_static_lightradiusscale", "10", "particles stop at this fraction of light radius (can be more than 1) when in static mode"}; cvar_t r_shadow_bouncegrid_static_maxbounce = {CVAR_SAVE, "r_shadow_bouncegrid_static_maxbounce", "5", "maximum number of bounces for a particle (minimum is 0) in static mode"}; cvar_t r_shadow_bouncegrid_static_maxphotons = {CVAR_SAVE, "r_shadow_bouncegrid_static_maxphotons", "250000", "upper bound on photons in static mode"}; cvar_t r_shadow_bouncegrid_static_spacing = {CVAR_SAVE, "r_shadow_bouncegrid_static_spacing", "64", "unit size of bouncegrid pixel when in static mode"}; cvar_t r_coronas = {CVAR_SAVE, "r_coronas", "0", "brightness of corona flare effects around certain lights, 0 disables corona effects"}; cvar_t r_coronas_occlusionsizescale = {CVAR_SAVE, "r_coronas_occlusionsizescale", "0.1", "size of light source for corona occlusion checksum the proportion of hidden pixels controls corona intensity"}; cvar_t r_coronas_occlusionquery = {CVAR_SAVE, "r_coronas_occlusionquery", "0", "use GL_ARB_occlusion_query extension if supported (fades coronas according to visibility) - bad performance (synchronous rendering) - worse on multi-gpu!"}; cvar_t gl_flashblend = {CVAR_SAVE, "gl_flashblend", "0", "render bright coronas for dynamic lights instead of actual lighting, fast but ugly"}; cvar_t gl_ext_separatestencil = {0, "gl_ext_separatestencil", "1", "make use of OpenGL 2.0 glStencilOpSeparate or GL_ATI_separate_stencil extension"}; cvar_t gl_ext_stenciltwoside = {0, "gl_ext_stenciltwoside", "1", "make use of GL_EXT_stenciltwoside extension (NVIDIA only)"}; cvar_t r_editlights = {0, "r_editlights", "0", "enables .rtlights file editing mode"}; cvar_t r_editlights_cursordistance = {0, "r_editlights_cursordistance", "1024", "maximum distance of cursor from eye"}; cvar_t r_editlights_cursorpushback = {0, "r_editlights_cursorpushback", "0", "how far to pull the cursor back toward the eye"}; cvar_t r_editlights_cursorpushoff = {0, "r_editlights_cursorpushoff", "4", "how far to push the cursor off the impacted surface"}; cvar_t r_editlights_cursorgrid = {0, "r_editlights_cursorgrid", "4", "snaps cursor to this grid size"}; cvar_t r_editlights_quakelightsizescale = {CVAR_SAVE, "r_editlights_quakelightsizescale", "1", "changes size of light entities loaded from a map"}; cvar_t r_editlights_drawproperties = {0, "r_editlights_drawproperties", "1", "draw properties of currently selected light"}; cvar_t r_editlights_current_origin = {0, "r_editlights_current_origin", "0 0 0", "origin of selected light"}; cvar_t r_editlights_current_angles = {0, "r_editlights_current_angles", "0 0 0", "angles of selected light"}; cvar_t r_editlights_current_color = {0, "r_editlights_current_color", "1 1 1", "color of selected light"}; cvar_t r_editlights_current_radius = {0, "r_editlights_current_radius", "0", "radius of selected light"}; cvar_t r_editlights_current_corona = {0, "r_editlights_current_corona", "0", "corona intensity of selected light"}; cvar_t r_editlights_current_coronasize = {0, "r_editlights_current_coronasize", "0", "corona size of selected light"}; cvar_t r_editlights_current_style = {0, "r_editlights_current_style", "0", "style of selected light"}; cvar_t r_editlights_current_shadows = {0, "r_editlights_current_shadows", "0", "shadows flag of selected light"}; cvar_t r_editlights_current_cubemap = {0, "r_editlights_current_cubemap", "0", "cubemap of selected light"}; cvar_t r_editlights_current_ambient = {0, "r_editlights_current_ambient", "0", "ambient intensity of selected light"}; cvar_t r_editlights_current_diffuse = {0, "r_editlights_current_diffuse", "1", "diffuse intensity of selected light"}; cvar_t r_editlights_current_specular = {0, "r_editlights_current_specular", "1", "specular intensity of selected light"}; cvar_t r_editlights_current_normalmode = {0, "r_editlights_current_normalmode", "0", "normalmode flag of selected light"}; cvar_t r_editlights_current_realtimemode = {0, "r_editlights_current_realtimemode", "0", "realtimemode flag of selected light"}; r_shadow_bouncegrid_state_t r_shadow_bouncegrid_state; // note the table actually includes one more value, just to avoid the need to clamp the distance index due to minor math error #define ATTENTABLESIZE 256 // 1D gradient, 2D circle and 3D sphere attenuation textures #define ATTEN1DSIZE 32 #define ATTEN2DSIZE 64 #define ATTEN3DSIZE 32 static float r_shadow_attendividebias; // r_shadow_lightattenuationdividebias static float r_shadow_attenlinearscale; // r_shadow_lightattenuationlinearscale static float r_shadow_attentable[ATTENTABLESIZE+1]; rtlight_t *r_shadow_compilingrtlight; static memexpandablearray_t r_shadow_worldlightsarray; dlight_t *r_shadow_selectedlight; dlight_t r_shadow_bufferlight; vec3_t r_editlights_cursorlocation; qboolean r_editlights_lockcursor; extern int con_vislines; void R_Shadow_UncompileWorldLights(void); void R_Shadow_ClearWorldLights(void); void R_Shadow_SaveWorldLights(void); void R_Shadow_LoadWorldLights(void); void R_Shadow_LoadLightsFile(void); void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void); void R_Shadow_EditLights_Reload_f(void); void R_Shadow_ValidateCvars(void); static void R_Shadow_MakeTextures(void); #define EDLIGHTSPRSIZE 8 skinframe_t *r_editlights_sprcursor; skinframe_t *r_editlights_sprlight; skinframe_t *r_editlights_sprnoshadowlight; skinframe_t *r_editlights_sprcubemaplight; skinframe_t *r_editlights_sprcubemapnoshadowlight; skinframe_t *r_editlights_sprselection; static void R_Shadow_SetShadowMode(void) { r_shadow_shadowmapmaxsize = bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4); r_shadow_shadowmapvsdct = r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20; r_shadow_shadowmapfilterquality = r_shadow_shadowmapping_filterquality.integer; r_shadow_shadowmapshadowsampler = r_shadow_shadowmapping_useshadowsampler.integer != 0; r_shadow_shadowmapdepthbits = r_shadow_shadowmapping_depthbits.integer; r_shadow_shadowmapborder = bound(0, r_shadow_shadowmapping_bordersize.integer, 16); r_shadow_shadowmapsize = 0; r_shadow_shadowmapsampler = false; r_shadow_shadowmappcf = 0; r_shadow_shadowmapdepthtexture = r_fb.usedepthtextures; r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; if ((r_shadow_shadowmapping.integer || r_shadow_deferred.integer) && vid.support.ext_framebuffer_object) { switch(vid.renderpath) { case RENDERPATH_GL20: if(r_shadow_shadowmapfilterquality < 0) { if (!r_fb.usedepthtextures) r_shadow_shadowmappcf = 1; else if((strstr(gl_vendor, "NVIDIA") || strstr(gl_renderer, "Radeon HD")) && vid.support.arb_shadow && r_shadow_shadowmapshadowsampler) { r_shadow_shadowmapsampler = true; r_shadow_shadowmappcf = 1; } else if(vid.support.amd_texture_texture4 || vid.support.arb_texture_gather) r_shadow_shadowmappcf = 1; else if((strstr(gl_vendor, "ATI") || strstr(gl_vendor, "Advanced Micro Devices")) && !strstr(gl_renderer, "Mesa") && !strstr(gl_version, "Mesa")) r_shadow_shadowmappcf = 1; else r_shadow_shadowmapsampler = vid.support.arb_shadow && r_shadow_shadowmapshadowsampler; } else { r_shadow_shadowmapsampler = vid.support.arb_shadow && r_shadow_shadowmapshadowsampler; switch (r_shadow_shadowmapfilterquality) { case 1: break; case 2: r_shadow_shadowmappcf = 1; break; case 3: r_shadow_shadowmappcf = 1; break; case 4: r_shadow_shadowmappcf = 2; break; } } if (!r_fb.usedepthtextures) r_shadow_shadowmapsampler = false; r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: r_shadow_shadowmapsampler = false; r_shadow_shadowmappcf = 1; r_shadow_shadowmode = R_SHADOW_SHADOWMODE_SHADOWMAP2D; break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; } } if(R_CompileShader_CheckStaticParms()) R_GLSL_Restart_f(); } qboolean R_Shadow_ShadowMappingEnabled(void) { switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: return true; default: return false; } } static void R_Shadow_FreeShadowMaps(void) { R_Shadow_SetShadowMode(); R_Mesh_DestroyFramebufferObject(r_shadow_fbo2d); r_shadow_fbo2d = 0; if (r_shadow_shadowmap2ddepthtexture) R_FreeTexture(r_shadow_shadowmap2ddepthtexture); r_shadow_shadowmap2ddepthtexture = NULL; if (r_shadow_shadowmap2ddepthbuffer) R_FreeTexture(r_shadow_shadowmap2ddepthbuffer); r_shadow_shadowmap2ddepthbuffer = NULL; if (r_shadow_shadowmapvsdcttexture) R_FreeTexture(r_shadow_shadowmapvsdcttexture); r_shadow_shadowmapvsdcttexture = NULL; } static void r_shadow_start(void) { // allocate vertex processing arrays memset(&r_shadow_bouncegrid_state, 0, sizeof(r_shadow_bouncegrid_state)); r_shadow_bouncegrid_state.maxsplatpaths = 16384; r_shadow_attenuationgradienttexture = NULL; r_shadow_attenuation2dtexture = NULL; r_shadow_attenuation3dtexture = NULL; r_shadow_shadowmode = R_SHADOW_SHADOWMODE_STENCIL; r_shadow_shadowmap2ddepthtexture = NULL; r_shadow_shadowmap2ddepthbuffer = NULL; r_shadow_shadowmapvsdcttexture = NULL; r_shadow_shadowmapmaxsize = 0; r_shadow_shadowmapsize = 0; r_shadow_shadowmapfilterquality = -1; r_shadow_shadowmapdepthbits = 0; r_shadow_shadowmapvsdct = false; r_shadow_shadowmapsampler = false; r_shadow_shadowmappcf = 0; r_shadow_fbo2d = 0; R_Shadow_FreeShadowMaps(); r_shadow_texturepool = NULL; r_shadow_filters_texturepool = NULL; R_Shadow_ValidateCvars(); R_Shadow_MakeTextures(); maxshadowtriangles = 0; shadowelements = NULL; maxshadowvertices = 0; shadowvertex3f = NULL; maxvertexupdate = 0; vertexupdate = NULL; vertexremap = NULL; vertexupdatenum = 0; maxshadowmark = 0; numshadowmark = 0; shadowmark = NULL; shadowmarklist = NULL; shadowmarkcount = 0; maxshadowsides = 0; numshadowsides = 0; shadowsides = NULL; shadowsideslist = NULL; r_shadow_buffer_numleafpvsbytes = 0; r_shadow_buffer_visitingleafpvs = NULL; r_shadow_buffer_leafpvs = NULL; r_shadow_buffer_leaflist = NULL; r_shadow_buffer_numsurfacepvsbytes = 0; r_shadow_buffer_surfacepvs = NULL; r_shadow_buffer_surfacelist = NULL; r_shadow_buffer_surfacesides = NULL; r_shadow_buffer_numshadowtrispvsbytes = 0; r_shadow_buffer_shadowtrispvs = NULL; r_shadow_buffer_numlighttrispvsbytes = 0; r_shadow_buffer_lighttrispvs = NULL; r_shadow_usingdeferredprepass = false; r_shadow_prepass_width = r_shadow_prepass_height = 0; // determine renderpath specific capabilities, we don't need to figure // these out per frame... switch(vid.renderpath) { case RENDERPATH_GL20: r_shadow_bouncegrid_state.allowdirectionalshading = true; r_shadow_bouncegrid_state.capable = vid.support.ext_texture_3d; break; case RENDERPATH_GLES2: // for performance reasons, do not use directional shading on GLES devices r_shadow_bouncegrid_state.capable = vid.support.ext_texture_3d; break; // these renderpaths do not currently have the code to display the bouncegrid, so disable it on them... case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: case RENDERPATH_SOFT: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: break; } } static void R_Shadow_FreeDeferred(void); static void r_shadow_shutdown(void) { CHECKGLERROR R_Shadow_UncompileWorldLights(); R_Shadow_FreeShadowMaps(); r_shadow_usingdeferredprepass = false; if (r_shadow_prepass_width) R_Shadow_FreeDeferred(); r_shadow_prepass_width = r_shadow_prepass_height = 0; CHECKGLERROR memset(&r_shadow_bouncegrid_state, 0, sizeof(r_shadow_bouncegrid_state)); r_shadow_attenuationgradienttexture = NULL; r_shadow_attenuation2dtexture = NULL; r_shadow_attenuation3dtexture = NULL; R_FreeTexturePool(&r_shadow_texturepool); R_FreeTexturePool(&r_shadow_filters_texturepool); maxshadowtriangles = 0; if (shadowelements) Mem_Free(shadowelements); shadowelements = NULL; if (shadowvertex3f) Mem_Free(shadowvertex3f); shadowvertex3f = NULL; maxvertexupdate = 0; if (vertexupdate) Mem_Free(vertexupdate); vertexupdate = NULL; if (vertexremap) Mem_Free(vertexremap); vertexremap = NULL; vertexupdatenum = 0; maxshadowmark = 0; numshadowmark = 0; if (shadowmark) Mem_Free(shadowmark); shadowmark = NULL; if (shadowmarklist) Mem_Free(shadowmarklist); shadowmarklist = NULL; shadowmarkcount = 0; maxshadowsides = 0; numshadowsides = 0; if (shadowsides) Mem_Free(shadowsides); shadowsides = NULL; if (shadowsideslist) Mem_Free(shadowsideslist); shadowsideslist = NULL; r_shadow_buffer_numleafpvsbytes = 0; if (r_shadow_buffer_visitingleafpvs) Mem_Free(r_shadow_buffer_visitingleafpvs); r_shadow_buffer_visitingleafpvs = NULL; if (r_shadow_buffer_leafpvs) Mem_Free(r_shadow_buffer_leafpvs); r_shadow_buffer_leafpvs = NULL; if (r_shadow_buffer_leaflist) Mem_Free(r_shadow_buffer_leaflist); r_shadow_buffer_leaflist = NULL; r_shadow_buffer_numsurfacepvsbytes = 0; if (r_shadow_buffer_surfacepvs) Mem_Free(r_shadow_buffer_surfacepvs); r_shadow_buffer_surfacepvs = NULL; if (r_shadow_buffer_surfacelist) Mem_Free(r_shadow_buffer_surfacelist); r_shadow_buffer_surfacelist = NULL; if (r_shadow_buffer_surfacesides) Mem_Free(r_shadow_buffer_surfacesides); r_shadow_buffer_surfacesides = NULL; r_shadow_buffer_numshadowtrispvsbytes = 0; if (r_shadow_buffer_shadowtrispvs) Mem_Free(r_shadow_buffer_shadowtrispvs); r_shadow_buffer_numlighttrispvsbytes = 0; if (r_shadow_buffer_lighttrispvs) Mem_Free(r_shadow_buffer_lighttrispvs); } static void r_shadow_newmap(void) { if (r_shadow_bouncegrid_state.texture) R_FreeTexture(r_shadow_bouncegrid_state.texture);r_shadow_bouncegrid_state.texture = NULL; if (r_shadow_lightcorona) R_SkinFrame_MarkUsed(r_shadow_lightcorona); if (r_editlights_sprcursor) R_SkinFrame_MarkUsed(r_editlights_sprcursor); if (r_editlights_sprlight) R_SkinFrame_MarkUsed(r_editlights_sprlight); if (r_editlights_sprnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprnoshadowlight); if (r_editlights_sprcubemaplight) R_SkinFrame_MarkUsed(r_editlights_sprcubemaplight); if (r_editlights_sprcubemapnoshadowlight) R_SkinFrame_MarkUsed(r_editlights_sprcubemapnoshadowlight); if (r_editlights_sprselection) R_SkinFrame_MarkUsed(r_editlights_sprselection); if (strncmp(cl.worldname, r_shadow_mapname, sizeof(r_shadow_mapname))) R_Shadow_EditLights_Reload_f(); } void R_Shadow_Init(void) { Cvar_RegisterVariable(&r_shadow_bumpscale_basetexture); Cvar_RegisterVariable(&r_shadow_bumpscale_bumpmap); Cvar_RegisterVariable(&r_shadow_usebihculling); Cvar_RegisterVariable(&r_shadow_usenormalmap); Cvar_RegisterVariable(&r_shadow_debuglight); Cvar_RegisterVariable(&r_shadow_deferred); Cvar_RegisterVariable(&r_shadow_gloss); Cvar_RegisterVariable(&r_shadow_gloss2intensity); Cvar_RegisterVariable(&r_shadow_glossintensity); Cvar_RegisterVariable(&r_shadow_glossexponent); Cvar_RegisterVariable(&r_shadow_gloss2exponent); Cvar_RegisterVariable(&r_shadow_glossexact); Cvar_RegisterVariable(&r_shadow_lightattenuationdividebias); Cvar_RegisterVariable(&r_shadow_lightattenuationlinearscale); Cvar_RegisterVariable(&r_shadow_lightintensityscale); Cvar_RegisterVariable(&r_shadow_lightradiusscale); Cvar_RegisterVariable(&r_shadow_projectdistance); Cvar_RegisterVariable(&r_shadow_frontsidecasting); Cvar_RegisterVariable(&r_shadow_realtime_world_importlightentitiesfrommap); Cvar_RegisterVariable(&r_shadow_realtime_dlight); Cvar_RegisterVariable(&r_shadow_realtime_dlight_shadows); Cvar_RegisterVariable(&r_shadow_realtime_dlight_svbspculling); Cvar_RegisterVariable(&r_shadow_realtime_dlight_portalculling); Cvar_RegisterVariable(&r_shadow_realtime_world); Cvar_RegisterVariable(&r_shadow_realtime_world_lightmaps); Cvar_RegisterVariable(&r_shadow_realtime_world_shadows); Cvar_RegisterVariable(&r_shadow_realtime_world_compile); Cvar_RegisterVariable(&r_shadow_realtime_world_compileshadow); Cvar_RegisterVariable(&r_shadow_realtime_world_compilesvbsp); Cvar_RegisterVariable(&r_shadow_realtime_world_compileportalculling); Cvar_RegisterVariable(&r_shadow_scissor); Cvar_RegisterVariable(&r_shadow_shadowmapping); Cvar_RegisterVariable(&r_shadow_shadowmapping_vsdct); Cvar_RegisterVariable(&r_shadow_shadowmapping_filterquality); Cvar_RegisterVariable(&r_shadow_shadowmapping_useshadowsampler); Cvar_RegisterVariable(&r_shadow_shadowmapping_depthbits); Cvar_RegisterVariable(&r_shadow_shadowmapping_precision); Cvar_RegisterVariable(&r_shadow_shadowmapping_maxsize); Cvar_RegisterVariable(&r_shadow_shadowmapping_minsize); // Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_bias); // Cvar_RegisterVariable(&r_shadow_shadowmapping_lod_scale); Cvar_RegisterVariable(&r_shadow_shadowmapping_bordersize); Cvar_RegisterVariable(&r_shadow_shadowmapping_nearclip); Cvar_RegisterVariable(&r_shadow_shadowmapping_bias); Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonfactor); Cvar_RegisterVariable(&r_shadow_shadowmapping_polygonoffset); Cvar_RegisterVariable(&r_shadow_sortsurfaces); Cvar_RegisterVariable(&r_shadow_polygonfactor); Cvar_RegisterVariable(&r_shadow_polygonoffset); Cvar_RegisterVariable(&r_shadow_texture3d); Cvar_RegisterVariable(&r_shadow_bouncegrid); Cvar_RegisterVariable(&r_shadow_bouncegrid_blur); Cvar_RegisterVariable(&r_shadow_bouncegrid_bounceanglediffuse); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_culllightpaths); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_directionalshading); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_dlightparticlemultiplier); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_hitmodels); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_energyperphoton); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_lightradiusscale); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_maxbounce); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_maxphotons); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_spacing); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_stablerandom); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_updateinterval); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_x); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_y); Cvar_RegisterVariable(&r_shadow_bouncegrid_dynamic_z); Cvar_RegisterVariable(&r_shadow_bouncegrid_floatcolors); Cvar_RegisterVariable(&r_shadow_bouncegrid_includedirectlighting); Cvar_RegisterVariable(&r_shadow_bouncegrid_intensity); Cvar_RegisterVariable(&r_shadow_bouncegrid_lightpathsize); Cvar_RegisterVariable(&r_shadow_bouncegrid_particlebounceintensity); Cvar_RegisterVariable(&r_shadow_bouncegrid_particleintensity); Cvar_RegisterVariable(&r_shadow_bouncegrid_sortlightpaths); Cvar_RegisterVariable(&r_shadow_bouncegrid_static); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_spacing); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_directionalshading); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_lightradiusscale); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_maxbounce); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_maxphotons); Cvar_RegisterVariable(&r_shadow_bouncegrid_static_energyperphoton); Cvar_RegisterVariable(&r_coronas); Cvar_RegisterVariable(&r_coronas_occlusionsizescale); Cvar_RegisterVariable(&r_coronas_occlusionquery); Cvar_RegisterVariable(&gl_flashblend); Cvar_RegisterVariable(&gl_ext_separatestencil); Cvar_RegisterVariable(&gl_ext_stenciltwoside); R_Shadow_EditLights_Init(); Mem_ExpandableArray_NewArray(&r_shadow_worldlightsarray, r_main_mempool, sizeof(dlight_t), 128); maxshadowtriangles = 0; shadowelements = NULL; maxshadowvertices = 0; shadowvertex3f = NULL; maxvertexupdate = 0; vertexupdate = NULL; vertexremap = NULL; vertexupdatenum = 0; maxshadowmark = 0; numshadowmark = 0; shadowmark = NULL; shadowmarklist = NULL; shadowmarkcount = 0; maxshadowsides = 0; numshadowsides = 0; shadowsides = NULL; shadowsideslist = NULL; r_shadow_buffer_numleafpvsbytes = 0; r_shadow_buffer_visitingleafpvs = NULL; r_shadow_buffer_leafpvs = NULL; r_shadow_buffer_leaflist = NULL; r_shadow_buffer_numsurfacepvsbytes = 0; r_shadow_buffer_surfacepvs = NULL; r_shadow_buffer_surfacelist = NULL; r_shadow_buffer_surfacesides = NULL; r_shadow_buffer_shadowtrispvs = NULL; r_shadow_buffer_lighttrispvs = NULL; R_RegisterModule("R_Shadow", r_shadow_start, r_shadow_shutdown, r_shadow_newmap, NULL, NULL); } matrix4x4_t matrix_attenuationxyz = { { {0.5, 0.0, 0.0, 0.5}, {0.0, 0.5, 0.0, 0.5}, {0.0, 0.0, 0.5, 0.5}, {0.0, 0.0, 0.0, 1.0} } }; matrix4x4_t matrix_attenuationz = { { {0.0, 0.0, 0.5, 0.5}, {0.0, 0.0, 0.0, 0.5}, {0.0, 0.0, 0.0, 0.5}, {0.0, 0.0, 0.0, 1.0} } }; static void R_Shadow_ResizeShadowArrays(int numvertices, int numtriangles, int vertscale, int triscale) { numvertices = ((numvertices + 255) & ~255) * vertscale; numtriangles = ((numtriangles + 255) & ~255) * triscale; // make sure shadowelements is big enough for this volume if (maxshadowtriangles < numtriangles) { maxshadowtriangles = numtriangles; if (shadowelements) Mem_Free(shadowelements); shadowelements = (int *)Mem_Alloc(r_main_mempool, maxshadowtriangles * sizeof(int[3])); } // make sure shadowvertex3f is big enough for this volume if (maxshadowvertices < numvertices) { maxshadowvertices = numvertices; if (shadowvertex3f) Mem_Free(shadowvertex3f); shadowvertex3f = (float *)Mem_Alloc(r_main_mempool, maxshadowvertices * sizeof(float[3])); } } static void R_Shadow_EnlargeLeafSurfaceTrisBuffer(int numleafs, int numsurfaces, int numshadowtriangles, int numlighttriangles) { int numleafpvsbytes = (((numleafs + 7) >> 3) + 255) & ~255; int numsurfacepvsbytes = (((numsurfaces + 7) >> 3) + 255) & ~255; int numshadowtrispvsbytes = (((numshadowtriangles + 7) >> 3) + 255) & ~255; int numlighttrispvsbytes = (((numlighttriangles + 7) >> 3) + 255) & ~255; if (r_shadow_buffer_numleafpvsbytes < numleafpvsbytes) { if (r_shadow_buffer_visitingleafpvs) Mem_Free(r_shadow_buffer_visitingleafpvs); if (r_shadow_buffer_leafpvs) Mem_Free(r_shadow_buffer_leafpvs); if (r_shadow_buffer_leaflist) Mem_Free(r_shadow_buffer_leaflist); r_shadow_buffer_numleafpvsbytes = numleafpvsbytes; r_shadow_buffer_visitingleafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); r_shadow_buffer_leafpvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes); r_shadow_buffer_leaflist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numleafpvsbytes * 8 * sizeof(*r_shadow_buffer_leaflist)); } if (r_shadow_buffer_numsurfacepvsbytes < numsurfacepvsbytes) { if (r_shadow_buffer_surfacepvs) Mem_Free(r_shadow_buffer_surfacepvs); if (r_shadow_buffer_surfacelist) Mem_Free(r_shadow_buffer_surfacelist); if (r_shadow_buffer_surfacesides) Mem_Free(r_shadow_buffer_surfacesides); r_shadow_buffer_numsurfacepvsbytes = numsurfacepvsbytes; r_shadow_buffer_surfacepvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes); r_shadow_buffer_surfacelist = (int *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); r_shadow_buffer_surfacesides = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numsurfacepvsbytes * 8 * sizeof(*r_shadow_buffer_surfacelist)); } if (r_shadow_buffer_numshadowtrispvsbytes < numshadowtrispvsbytes) { if (r_shadow_buffer_shadowtrispvs) Mem_Free(r_shadow_buffer_shadowtrispvs); r_shadow_buffer_numshadowtrispvsbytes = numshadowtrispvsbytes; r_shadow_buffer_shadowtrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numshadowtrispvsbytes); } if (r_shadow_buffer_numlighttrispvsbytes < numlighttrispvsbytes) { if (r_shadow_buffer_lighttrispvs) Mem_Free(r_shadow_buffer_lighttrispvs); r_shadow_buffer_numlighttrispvsbytes = numlighttrispvsbytes; r_shadow_buffer_lighttrispvs = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_buffer_numlighttrispvsbytes); } } void R_Shadow_PrepareShadowMark(int numtris) { // make sure shadowmark is big enough for this volume if (maxshadowmark < numtris) { maxshadowmark = numtris; if (shadowmark) Mem_Free(shadowmark); if (shadowmarklist) Mem_Free(shadowmarklist); shadowmark = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmark)); shadowmarklist = (int *)Mem_Alloc(r_main_mempool, maxshadowmark * sizeof(*shadowmarklist)); shadowmarkcount = 0; } shadowmarkcount++; // if shadowmarkcount wrapped we clear the array and adjust accordingly if (shadowmarkcount == 0) { shadowmarkcount = 1; memset(shadowmark, 0, maxshadowmark * sizeof(*shadowmark)); } numshadowmark = 0; } void R_Shadow_PrepareShadowSides(int numtris) { if (maxshadowsides < numtris) { maxshadowsides = numtris; if (shadowsides) Mem_Free(shadowsides); if (shadowsideslist) Mem_Free(shadowsideslist); shadowsides = (unsigned char *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsides)); shadowsideslist = (int *)Mem_Alloc(r_main_mempool, maxshadowsides * sizeof(*shadowsideslist)); } numshadowsides = 0; } static int R_Shadow_ConstructShadowVolume_ZFail(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) { int i, j; int outtriangles = 0, outvertices = 0; const int *element; const float *vertex; float ratio, direction[3], projectvector[3]; if (projectdirection) VectorScale(projectdirection, projectdistance, projectvector); else VectorClear(projectvector); // create the vertices if (projectdirection) { for (i = 0;i < numshadowmarktris;i++) { element = inelement3i + shadowmarktris[i] * 3; for (j = 0;j < 3;j++) { if (vertexupdate[element[j]] != vertexupdatenum) { vertexupdate[element[j]] = vertexupdatenum; vertexremap[element[j]] = outvertices; vertex = invertex3f + element[j] * 3; // project one copy of the vertex according to projectvector VectorCopy(vertex, outvertex3f); VectorAdd(vertex, projectvector, (outvertex3f + 3)); outvertex3f += 6; outvertices += 2; } } } } else { for (i = 0;i < numshadowmarktris;i++) { element = inelement3i + shadowmarktris[i] * 3; for (j = 0;j < 3;j++) { if (vertexupdate[element[j]] != vertexupdatenum) { vertexupdate[element[j]] = vertexupdatenum; vertexremap[element[j]] = outvertices; vertex = invertex3f + element[j] * 3; // project one copy of the vertex to the sphere radius of the light // (FIXME: would projecting it to the light box be better?) VectorSubtract(vertex, projectorigin, direction); ratio = projectdistance / VectorLength(direction); VectorCopy(vertex, outvertex3f); VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); outvertex3f += 6; outvertices += 2; } } } } if (r_shadow_frontsidecasting.integer) { for (i = 0;i < numshadowmarktris;i++) { int remappedelement[3]; int markindex; const int *neighbortriangle; markindex = shadowmarktris[i] * 3; element = inelement3i + markindex; neighbortriangle = inneighbor3i + markindex; // output the front and back triangles outelement3i[0] = vertexremap[element[0]]; outelement3i[1] = vertexremap[element[1]]; outelement3i[2] = vertexremap[element[2]]; outelement3i[3] = vertexremap[element[2]] + 1; outelement3i[4] = vertexremap[element[1]] + 1; outelement3i[5] = vertexremap[element[0]] + 1; outelement3i += 6; outtriangles += 2; // output the sides (facing outward from this triangle) if (shadowmark[neighbortriangle[0]] != shadowmarkcount) { remappedelement[0] = vertexremap[element[0]]; remappedelement[1] = vertexremap[element[1]]; outelement3i[0] = remappedelement[1]; outelement3i[1] = remappedelement[0]; outelement3i[2] = remappedelement[0] + 1; outelement3i[3] = remappedelement[1]; outelement3i[4] = remappedelement[0] + 1; outelement3i[5] = remappedelement[1] + 1; outelement3i += 6; outtriangles += 2; } if (shadowmark[neighbortriangle[1]] != shadowmarkcount) { remappedelement[1] = vertexremap[element[1]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[2]; outelement3i[1] = remappedelement[1]; outelement3i[2] = remappedelement[1] + 1; outelement3i[3] = remappedelement[2]; outelement3i[4] = remappedelement[1] + 1; outelement3i[5] = remappedelement[2] + 1; outelement3i += 6; outtriangles += 2; } if (shadowmark[neighbortriangle[2]] != shadowmarkcount) { remappedelement[0] = vertexremap[element[0]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[0]; outelement3i[1] = remappedelement[2]; outelement3i[2] = remappedelement[2] + 1; outelement3i[3] = remappedelement[0]; outelement3i[4] = remappedelement[2] + 1; outelement3i[5] = remappedelement[0] + 1; outelement3i += 6; outtriangles += 2; } } } else { for (i = 0;i < numshadowmarktris;i++) { int remappedelement[3]; int markindex; const int *neighbortriangle; markindex = shadowmarktris[i] * 3; element = inelement3i + markindex; neighbortriangle = inneighbor3i + markindex; // output the front and back triangles outelement3i[0] = vertexremap[element[2]]; outelement3i[1] = vertexremap[element[1]]; outelement3i[2] = vertexremap[element[0]]; outelement3i[3] = vertexremap[element[0]] + 1; outelement3i[4] = vertexremap[element[1]] + 1; outelement3i[5] = vertexremap[element[2]] + 1; outelement3i += 6; outtriangles += 2; // output the sides (facing outward from this triangle) if (shadowmark[neighbortriangle[0]] != shadowmarkcount) { remappedelement[0] = vertexremap[element[0]]; remappedelement[1] = vertexremap[element[1]]; outelement3i[0] = remappedelement[0]; outelement3i[1] = remappedelement[1]; outelement3i[2] = remappedelement[1] + 1; outelement3i[3] = remappedelement[0]; outelement3i[4] = remappedelement[1] + 1; outelement3i[5] = remappedelement[0] + 1; outelement3i += 6; outtriangles += 2; } if (shadowmark[neighbortriangle[1]] != shadowmarkcount) { remappedelement[1] = vertexremap[element[1]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[1]; outelement3i[1] = remappedelement[2]; outelement3i[2] = remappedelement[2] + 1; outelement3i[3] = remappedelement[1]; outelement3i[4] = remappedelement[2] + 1; outelement3i[5] = remappedelement[1] + 1; outelement3i += 6; outtriangles += 2; } if (shadowmark[neighbortriangle[2]] != shadowmarkcount) { remappedelement[0] = vertexremap[element[0]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[2]; outelement3i[1] = remappedelement[0]; outelement3i[2] = remappedelement[0] + 1; outelement3i[3] = remappedelement[2]; outelement3i[4] = remappedelement[0] + 1; outelement3i[5] = remappedelement[2] + 1; outelement3i += 6; outtriangles += 2; } } } if (outnumvertices) *outnumvertices = outvertices; return outtriangles; } static int R_Shadow_ConstructShadowVolume_ZPass(int innumvertices, int innumtris, const int *inelement3i, const int *inneighbor3i, const float *invertex3f, int *outnumvertices, int *outelement3i, float *outvertex3f, const float *projectorigin, const float *projectdirection, float projectdistance, int numshadowmarktris, const int *shadowmarktris) { int i, j, k; int outtriangles = 0, outvertices = 0; const int *element; const float *vertex; float ratio, direction[3], projectvector[3]; qboolean side[4]; if (projectdirection) VectorScale(projectdirection, projectdistance, projectvector); else VectorClear(projectvector); for (i = 0;i < numshadowmarktris;i++) { int remappedelement[3]; int markindex; const int *neighbortriangle; markindex = shadowmarktris[i] * 3; neighbortriangle = inneighbor3i + markindex; side[0] = shadowmark[neighbortriangle[0]] == shadowmarkcount; side[1] = shadowmark[neighbortriangle[1]] == shadowmarkcount; side[2] = shadowmark[neighbortriangle[2]] == shadowmarkcount; if (side[0] + side[1] + side[2] == 0) continue; side[3] = side[0]; element = inelement3i + markindex; // create the vertices for (j = 0;j < 3;j++) { if (side[j] + side[j+1] == 0) continue; k = element[j]; if (vertexupdate[k] != vertexupdatenum) { vertexupdate[k] = vertexupdatenum; vertexremap[k] = outvertices; vertex = invertex3f + k * 3; VectorCopy(vertex, outvertex3f); if (projectdirection) { // project one copy of the vertex according to projectvector VectorAdd(vertex, projectvector, (outvertex3f + 3)); } else { // project one copy of the vertex to the sphere radius of the light // (FIXME: would projecting it to the light box be better?) VectorSubtract(vertex, projectorigin, direction); ratio = projectdistance / VectorLength(direction); VectorMA(projectorigin, ratio, direction, (outvertex3f + 3)); } outvertex3f += 6; outvertices += 2; } } // output the sides (facing outward from this triangle) if (!side[0]) { remappedelement[0] = vertexremap[element[0]]; remappedelement[1] = vertexremap[element[1]]; outelement3i[0] = remappedelement[1]; outelement3i[1] = remappedelement[0]; outelement3i[2] = remappedelement[0] + 1; outelement3i[3] = remappedelement[1]; outelement3i[4] = remappedelement[0] + 1; outelement3i[5] = remappedelement[1] + 1; outelement3i += 6; outtriangles += 2; } if (!side[1]) { remappedelement[1] = vertexremap[element[1]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[2]; outelement3i[1] = remappedelement[1]; outelement3i[2] = remappedelement[1] + 1; outelement3i[3] = remappedelement[2]; outelement3i[4] = remappedelement[1] + 1; outelement3i[5] = remappedelement[2] + 1; outelement3i += 6; outtriangles += 2; } if (!side[2]) { remappedelement[0] = vertexremap[element[0]]; remappedelement[2] = vertexremap[element[2]]; outelement3i[0] = remappedelement[0]; outelement3i[1] = remappedelement[2]; outelement3i[2] = remappedelement[2] + 1; outelement3i[3] = remappedelement[0]; outelement3i[4] = remappedelement[2] + 1; outelement3i[5] = remappedelement[0] + 1; outelement3i += 6; outtriangles += 2; } } if (outnumvertices) *outnumvertices = outvertices; return outtriangles; } void R_Shadow_MarkVolumeFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs) { int t, tend; const int *e; const float *v[3]; float normal[3]; if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) return; tend = firsttriangle + numtris; if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) { // surface box entirely inside light box, no box cull if (projectdirection) { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { TriangleNormal(invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3, normal); if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) shadowmarklist[numshadowmark++] = t; } } else { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, invertex3f + e[0] * 3, invertex3f + e[1] * 3, invertex3f + e[2] * 3)) shadowmarklist[numshadowmark++] = t; } } else { // surface box not entirely inside light box, cull each triangle if (projectdirection) { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3; v[1] = invertex3f + e[1] * 3; v[2] = invertex3f + e[2] * 3; TriangleNormal(v[0], v[1], v[2], normal); if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) shadowmarklist[numshadowmark++] = t; } } else { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3; v[1] = invertex3f + e[1] * 3; v[2] = invertex3f + e[2] * 3; if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) shadowmarklist[numshadowmark++] = t; } } } } static qboolean R_Shadow_UseZPass(vec3_t mins, vec3_t maxs) { #if 1 return false; #else if (r_shadow_compilingrtlight || !r_shadow_frontsidecasting.integer || !r_shadow_usezpassifpossible.integer) return false; // check if the shadow volume intersects the near plane // // a ray between the eye and light origin may intersect the caster, // indicating that the shadow may touch the eye location, however we must // test the near plane (a polygon), not merely the eye location, so it is // easiest to enlarge the caster bounding shape slightly for this. // TODO return true; #endif } void R_Shadow_VolumeFromList(int numverts, int numtris, const float *invertex3f, const int *elements, const int *neighbors, const vec3_t projectorigin, const vec3_t projectdirection, float projectdistance, int nummarktris, const int *marktris, vec3_t trismins, vec3_t trismaxs) { int i, tris, outverts; if (projectdistance < 0.1) { Con_Printf("R_Shadow_Volume: projectdistance %f\n", projectdistance); return; } if (!numverts || !nummarktris) return; // make sure shadowelements is big enough for this volume if (maxshadowtriangles < nummarktris*8 || maxshadowvertices < numverts*2) R_Shadow_ResizeShadowArrays(numverts, nummarktris, 2, 8); if (maxvertexupdate < numverts) { maxvertexupdate = numverts; if (vertexupdate) Mem_Free(vertexupdate); if (vertexremap) Mem_Free(vertexremap); vertexupdate = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); vertexremap = (int *)Mem_Alloc(r_main_mempool, maxvertexupdate * sizeof(int)); vertexupdatenum = 0; } vertexupdatenum++; if (vertexupdatenum == 0) { vertexupdatenum = 1; memset(vertexupdate, 0, maxvertexupdate * sizeof(int)); memset(vertexremap, 0, maxvertexupdate * sizeof(int)); } for (i = 0;i < nummarktris;i++) shadowmark[marktris[i]] = shadowmarkcount; if (r_shadow_compilingrtlight) { // if we're compiling an rtlight, capture the mesh //tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); //Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zpass, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_zfail, NULL, NULL, NULL, shadowvertex3f, NULL, NULL, NULL, NULL, tris, shadowelements); } else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_VISIBLEVOLUMES) { tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL, 0); R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); } else { // decide which type of shadow to generate and set stencil mode R_Shadow_RenderMode_StencilShadowVolumes(R_Shadow_UseZPass(trismins, trismaxs)); // generate the sides or a solid volume, depending on type if (r_shadow_rendermode >= R_SHADOW_RENDERMODE_ZPASS_STENCIL && r_shadow_rendermode <= R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE) tris = R_Shadow_ConstructShadowVolume_ZPass(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); else tris = R_Shadow_ConstructShadowVolume_ZFail(numverts, numtris, elements, neighbors, invertex3f, &outverts, shadowelements, shadowvertex3f, projectorigin, projectdirection, projectdistance, nummarktris, marktris); r_refdef.stats[r_stat_lights_dynamicshadowtriangles] += tris; r_refdef.stats[r_stat_lights_shadowtriangles] += tris; if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) { // increment stencil if frontface is infront of depthbuffer GL_CullFace(r_refdef.view.cullface_front); R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); // decrement stencil if backface is infront of depthbuffer GL_CullFace(r_refdef.view.cullface_back); R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); } else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) { // decrement stencil if backface is behind depthbuffer GL_CullFace(r_refdef.view.cullface_front); R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); // increment stencil if frontface is behind depthbuffer GL_CullFace(r_refdef.view.cullface_back); R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); } R_Mesh_PrepareVertices_Vertex3f(outverts, shadowvertex3f, NULL, 0); R_Mesh_Draw(0, outverts, 0, tris, shadowelements, NULL, 0, NULL, NULL, 0); } } int R_Shadow_CalcTriangleSideMask(const vec3_t p1, const vec3_t p2, const vec3_t p3, float bias) { // p1, p2, p3 are in the cubemap's local coordinate system // bias = border/(size - border) int mask = 0x3F; float dp1 = p1[0] + p1[1], dn1 = p1[0] - p1[1], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = p2[0] + p2[1], dn2 = p2[0] - p2[1], ap2 = fabs(dp2), an2 = fabs(dn2), dp3 = p3[0] + p3[1], dn3 = p3[0] - p3[1], ap3 = fabs(dp3), an3 = fabs(dn3); if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) mask &= (3<<4) | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) | (dp3 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) mask &= (3<<4) | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) | (dn3 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); dp1 = p1[1] + p1[2], dn1 = p1[1] - p1[2], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = p2[1] + p2[2], dn2 = p2[1] - p2[2], ap2 = fabs(dp2), an2 = fabs(dn2), dp3 = p3[1] + p3[2], dn3 = p3[1] - p3[2], ap3 = fabs(dp3), an3 = fabs(dn3); if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) mask &= (3<<0) | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) | (dp3 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) mask &= (3<<0) | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) | (dn3 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); dp1 = p1[2] + p1[0], dn1 = p1[2] - p1[0], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = p2[2] + p2[0], dn2 = p2[2] - p2[0], ap2 = fabs(dp2), an2 = fabs(dn2), dp3 = p3[2] + p3[0], dn3 = p3[2] - p3[0], ap3 = fabs(dp3), an3 = fabs(dn3); if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3) mask &= (3<<2) | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) | (dp3 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3) mask &= (3<<2) | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) | (dn3 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); return mask; } static int R_Shadow_CalcBBoxSideMask(const vec3_t mins, const vec3_t maxs, const matrix4x4_t *worldtolight, const matrix4x4_t *radiustolight, float bias) { vec3_t center, radius, lightcenter, lightradius, pmin, pmax; float dp1, dn1, ap1, an1, dp2, dn2, ap2, an2; int mask = 0x3F; VectorSubtract(maxs, mins, radius); VectorScale(radius, 0.5f, radius); VectorAdd(mins, radius, center); Matrix4x4_Transform(worldtolight, center, lightcenter); Matrix4x4_Transform3x3(radiustolight, radius, lightradius); VectorSubtract(lightcenter, lightradius, pmin); VectorAdd(lightcenter, lightradius, pmax); dp1 = pmax[0] + pmax[1], dn1 = pmax[0] - pmin[1], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = pmin[0] + pmin[1], dn2 = pmin[0] - pmax[1], ap2 = fabs(dp2), an2 = fabs(dn2); if(ap1 > bias*an1 && ap2 > bias*an2) mask &= (3<<4) | (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)) | (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); if(an1 > bias*ap1 && an2 > bias*ap2) mask &= (3<<4) | (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)) | (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); dp1 = pmax[1] + pmax[2], dn1 = pmax[1] - pmin[2], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = pmin[1] + pmin[2], dn2 = pmin[1] - pmax[2], ap2 = fabs(dp2), an2 = fabs(dn2); if(ap1 > bias*an1 && ap2 > bias*an2) mask &= (3<<0) | (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)) | (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); if(an1 > bias*ap1 && an2 > bias*ap2) mask &= (3<<0) | (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)) | (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); dp1 = pmax[2] + pmax[0], dn1 = pmax[2] - pmin[0], ap1 = fabs(dp1), an1 = fabs(dn1), dp2 = pmin[2] + pmin[0], dn2 = pmin[2] - pmax[0], ap2 = fabs(dp2), an2 = fabs(dn2); if(ap1 > bias*an1 && ap2 > bias*an2) mask &= (3<<2) | (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)) | (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); if(an1 > bias*ap1 && an2 > bias*ap2) mask &= (3<<2) | (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)) | (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); return mask; } #define R_Shadow_CalcEntitySideMask(ent, worldtolight, radiustolight, bias) R_Shadow_CalcBBoxSideMask((ent)->mins, (ent)->maxs, worldtolight, radiustolight, bias) int R_Shadow_CalcSphereSideMask(const vec3_t p, float radius, float bias) { // p is in the cubemap's local coordinate system // bias = border/(size - border) float dxyp = p[0] + p[1], dxyn = p[0] - p[1], axyp = fabs(dxyp), axyn = fabs(dxyn); float dyzp = p[1] + p[2], dyzn = p[1] - p[2], ayzp = fabs(dyzp), ayzn = fabs(dyzn); float dzxp = p[2] + p[0], dzxn = p[2] - p[0], azxp = fabs(dzxp), azxn = fabs(dzxn); int mask = 0x3F; if(axyp > bias*axyn + radius) mask &= dxyp < 0 ? ~((1<<0)|(1<<2)) : ~((2<<0)|(2<<2)); if(axyn > bias*axyp + radius) mask &= dxyn < 0 ? ~((1<<0)|(2<<2)) : ~((2<<0)|(1<<2)); if(ayzp > bias*ayzn + radius) mask &= dyzp < 0 ? ~((1<<2)|(1<<4)) : ~((2<<2)|(2<<4)); if(ayzn > bias*ayzp + radius) mask &= dyzn < 0 ? ~((1<<2)|(2<<4)) : ~((2<<2)|(1<<4)); if(azxp > bias*azxn + radius) mask &= dzxp < 0 ? ~((1<<4)|(1<<0)) : ~((2<<4)|(2<<0)); if(azxn > bias*azxp + radius) mask &= dzxn < 0 ? ~((1<<4)|(2<<0)) : ~((2<<4)|(1<<0)); return mask; } static int R_Shadow_CullFrustumSides(rtlight_t *rtlight, float size, float border) { int i; vec3_t o, p, n; int sides = 0x3F, masks[6] = { 3<<4, 3<<4, 3<<0, 3<<0, 3<<2, 3<<2 }; float scale = (size - 2*border)/size, len; float bias = border / (float)(size - border), dp, dn, ap, an; // check if cone enclosing side would cross frustum plane scale = 2 / (scale*scale + 2); Matrix4x4_OriginFromMatrix(&rtlight->matrix_lighttoworld, o); for (i = 0;i < 5;i++) { if (PlaneDiff(o, &r_refdef.view.frustum[i]) > -0.03125) continue; Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[i].normal, n); len = scale*VectorLength2(n); if(n[0]*n[0] > len) sides &= n[0] < 0 ? ~(1<<0) : ~(2 << 0); if(n[1]*n[1] > len) sides &= n[1] < 0 ? ~(1<<2) : ~(2 << 2); if(n[2]*n[2] > len) sides &= n[2] < 0 ? ~(1<<4) : ~(2 << 4); } if (PlaneDiff(o, &r_refdef.view.frustum[4]) >= r_refdef.farclip - r_refdef.nearclip + 0.03125) { Matrix4x4_Transform3x3(&rtlight->matrix_worldtolight, r_refdef.view.frustum[4].normal, n); len = scale*VectorLength2(n); if(n[0]*n[0] > len) sides &= n[0] >= 0 ? ~(1<<0) : ~(2 << 0); if(n[1]*n[1] > len) sides &= n[1] >= 0 ? ~(1<<2) : ~(2 << 2); if(n[2]*n[2] > len) sides &= n[2] >= 0 ? ~(1<<4) : ~(2 << 4); } // this next test usually clips off more sides than the former, but occasionally clips fewer/different ones, so do both and combine results // check if frustum corners/origin cross plane sides #if 1 // infinite version, assumes frustum corners merely give direction and extend to infinite distance Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.origin, p); dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); for (i = 0;i < 4;i++) { Matrix4x4_Transform(&rtlight->matrix_worldtolight, r_refdef.view.frustumcorner[i], n); VectorSubtract(n, p, n); dp = n[0] + n[1], dn = n[0] - n[1], ap = fabs(dp), an = fabs(dn); if(ap > 0) masks[0] |= dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2); if(an > 0) masks[1] |= dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2); dp = n[1] + n[2], dn = n[1] - n[2], ap = fabs(dp), an = fabs(dn); if(ap > 0) masks[2] |= dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4); if(an > 0) masks[3] |= dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4); dp = n[2] + n[0], dn = n[2] - n[0], ap = fabs(dp), an = fabs(dn); if(ap > 0) masks[4] |= dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0); if(an > 0) masks[5] |= dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0); } #else // finite version, assumes corners are a finite distance from origin dependent on far plane for (i = 0;i < 5;i++) { Matrix4x4_Transform(&rtlight->matrix_worldtolight, !i ? r_refdef.view.origin : r_refdef.view.frustumcorner[i-1], p); dp = p[0] + p[1], dn = p[0] - p[1], ap = fabs(dp), an = fabs(dn); masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2)); masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2)); dp = p[1] + p[2], dn = p[1] - p[2], ap = fabs(dp), an = fabs(dn); masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4)); masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4)); dp = p[2] + p[0], dn = p[2] - p[0], ap = fabs(dp), an = fabs(dn); masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0)); masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0)); } #endif return sides & masks[0] & masks[1] & masks[2] & masks[3] & masks[4] & masks[5]; } int R_Shadow_ChooseSidesFromBox(int firsttriangle, int numtris, const float *invertex3f, const int *elements, const matrix4x4_t *worldtolight, const vec3_t projectorigin, const vec3_t projectdirection, const vec3_t lightmins, const vec3_t lightmaxs, const vec3_t surfacemins, const vec3_t surfacemaxs, int *totals) { int t, tend; const int *e; const float *v[3]; float normal[3]; vec3_t p[3]; float bias; int mask, surfacemask = 0; if (!BoxesOverlap(lightmins, lightmaxs, surfacemins, surfacemaxs)) return 0; bias = r_shadow_shadowmapborder / (float)(r_shadow_shadowmapmaxsize - r_shadow_shadowmapborder); tend = firsttriangle + numtris; if (BoxInsideBox(surfacemins, surfacemaxs, lightmins, lightmaxs)) { // surface box entirely inside light box, no box cull if (projectdirection) { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; TriangleNormal(v[0], v[1], v[2], normal); if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0)) { Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); surfacemask |= mask; if(totals) { totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; shadowsides[numshadowsides] = mask; shadowsideslist[numshadowsides++] = t; } } } } else { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2])) { Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); surfacemask |= mask; if(totals) { totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; shadowsides[numshadowsides] = mask; shadowsideslist[numshadowsides++] = t; } } } } } else { // surface box not entirely inside light box, cull each triangle if (projectdirection) { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; TriangleNormal(v[0], v[1], v[2], normal); if (r_shadow_frontsidecasting.integer == (DotProduct(normal, projectdirection) < 0) && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) { Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); surfacemask |= mask; if(totals) { totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; shadowsides[numshadowsides] = mask; shadowsideslist[numshadowsides++] = t; } } } } else { for (t = firsttriangle, e = elements + t * 3;t < tend;t++, e += 3) { v[0] = invertex3f + e[0] * 3, v[1] = invertex3f + e[1] * 3, v[2] = invertex3f + e[2] * 3; if (r_shadow_frontsidecasting.integer == PointInfrontOfTriangle(projectorigin, v[0], v[1], v[2]) && TriangleBBoxOverlapsBox(v[0], v[1], v[2], lightmins, lightmaxs)) { Matrix4x4_Transform(worldtolight, v[0], p[0]), Matrix4x4_Transform(worldtolight, v[1], p[1]), Matrix4x4_Transform(worldtolight, v[2], p[2]); mask = R_Shadow_CalcTriangleSideMask(p[0], p[1], p[2], bias); surfacemask |= mask; if(totals) { totals[0] += mask&1, totals[1] += (mask>>1)&1, totals[2] += (mask>>2)&1, totals[3] += (mask>>3)&1, totals[4] += (mask>>4)&1, totals[5] += mask>>5; shadowsides[numshadowsides] = mask; shadowsideslist[numshadowsides++] = t; } } } } } return surfacemask; } void R_Shadow_ShadowMapFromList(int numverts, int numtris, const float *vertex3f, const int *elements, int numsidetris, const int *sidetotals, const unsigned char *sides, const int *sidetris) { int i, j, outtriangles = 0; int *outelement3i[6]; if (!numverts || !numsidetris || !r_shadow_compilingrtlight) return; outtriangles = sidetotals[0] + sidetotals[1] + sidetotals[2] + sidetotals[3] + sidetotals[4] + sidetotals[5]; // make sure shadowelements is big enough for this mesh if (maxshadowtriangles < outtriangles) R_Shadow_ResizeShadowArrays(0, outtriangles, 0, 1); // compute the offset and size of the separate index lists for each cubemap side outtriangles = 0; for (i = 0;i < 6;i++) { outelement3i[i] = shadowelements + outtriangles * 3; r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sideoffsets[i] = outtriangles; r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap->sidetotals[i] = sidetotals[i]; outtriangles += sidetotals[i]; } // gather up the (sparse) triangles into separate index lists for each cubemap side for (i = 0;i < numsidetris;i++) { const int *element = elements + sidetris[i] * 3; for (j = 0;j < 6;j++) { if (sides[i] & (1 << j)) { outelement3i[j][0] = element[0]; outelement3i[j][1] = element[1]; outelement3i[j][2] = element[2]; outelement3i[j] += 3; } } } Mod_ShadowMesh_AddMesh(r_main_mempool, r_shadow_compilingrtlight->static_meshchain_shadow_shadowmap, NULL, NULL, NULL, vertex3f, NULL, NULL, NULL, NULL, outtriangles, shadowelements); } static void R_Shadow_MakeTextures_MakeCorona(void) { float dx, dy; int x, y, a; unsigned char pixels[32][32][4]; for (y = 0;y < 32;y++) { dy = (y - 15.5f) * (1.0f / 16.0f); for (x = 0;x < 32;x++) { dx = (x - 15.5f) * (1.0f / 16.0f); a = (int)(((1.0f / (dx * dx + dy * dy + 0.2f)) - (1.0f / (1.0f + 0.2))) * 32.0f / (1.0f / (1.0f + 0.2))); a = bound(0, a, 255); pixels[y][x][0] = a; pixels[y][x][1] = a; pixels[y][x][2] = a; pixels[y][x][3] = 255; } } r_shadow_lightcorona = R_SkinFrame_LoadInternalBGRA("lightcorona", TEXF_FORCELINEAR, &pixels[0][0][0], 32, 32, false); } static unsigned int R_Shadow_MakeTextures_SamplePoint(float x, float y, float z) { float dist = sqrt(x*x+y*y+z*z); float intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; // note this code could suffer byte order issues except that it is multiplying by an integer that reads the same both ways return (unsigned char)bound(0, intensity * 256.0f, 255) * 0x01010101; } static void R_Shadow_MakeTextures(void) { int x, y, z; float intensity, dist; unsigned int *data; R_Shadow_FreeShadowMaps(); R_FreeTexturePool(&r_shadow_texturepool); r_shadow_texturepool = R_AllocTexturePool(); r_shadow_attenlinearscale = r_shadow_lightattenuationlinearscale.value; r_shadow_attendividebias = r_shadow_lightattenuationdividebias.value; data = (unsigned int *)Mem_Alloc(tempmempool, max(max(ATTEN3DSIZE*ATTEN3DSIZE*ATTEN3DSIZE, ATTEN2DSIZE*ATTEN2DSIZE), ATTEN1DSIZE) * 4); // the table includes one additional value to avoid the need to clamp indexing due to minor math errors for (x = 0;x <= ATTENTABLESIZE;x++) { dist = (x + 0.5f) * (1.0f / ATTENTABLESIZE) * (1.0f / 0.9375); intensity = dist < 1 ? ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) : 0; r_shadow_attentable[x] = bound(0, intensity, 1); } // 1D gradient texture for (x = 0;x < ATTEN1DSIZE;x++) data[x] = R_Shadow_MakeTextures_SamplePoint((x + 0.5f) * (1.0f / ATTEN1DSIZE) * (1.0f / 0.9375), 0, 0); r_shadow_attenuationgradienttexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation1d", ATTEN1DSIZE, 1, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); // 2D circle texture for (y = 0;y < ATTEN2DSIZE;y++) for (x = 0;x < ATTEN2DSIZE;x++) data[y*ATTEN2DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN2DSIZE) - 1.0f) * (1.0f / 0.9375), 0); r_shadow_attenuation2dtexture = R_LoadTexture2D(r_shadow_texturepool, "attenuation2d", ATTEN2DSIZE, ATTEN2DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); // 3D sphere texture if (r_shadow_texture3d.integer && vid.support.ext_texture_3d) { for (z = 0;z < ATTEN3DSIZE;z++) for (y = 0;y < ATTEN3DSIZE;y++) for (x = 0;x < ATTEN3DSIZE;x++) data[(z*ATTEN3DSIZE+y)*ATTEN3DSIZE+x] = R_Shadow_MakeTextures_SamplePoint(((x + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((y + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375), ((z + 0.5f) * (2.0f / ATTEN3DSIZE) - 1.0f) * (1.0f / 0.9375)); r_shadow_attenuation3dtexture = R_LoadTexture3D(r_shadow_texturepool, "attenuation3d", ATTEN3DSIZE, ATTEN3DSIZE, ATTEN3DSIZE, (unsigned char *)data, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, -1, NULL); } else r_shadow_attenuation3dtexture = NULL; Mem_Free(data); R_Shadow_MakeTextures_MakeCorona(); // Editor light sprites r_editlights_sprcursor = R_SkinFrame_LoadInternal8bit("gfx/editlights/cursor", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) "................" ".3............3." "..5...2332...5.." "...7.3....3.7..." "....7......7...." "...3.7....7.3..." "..2...7..7...2.." "..3..........3.." "..3..........3.." "..2...7..7...2.." "...3.7....7.3..." "....7......7...." "...7.3....3.7..." "..5...2332...5.." ".3............3." "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); r_editlights_sprlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/light", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) "................" "................" "......1111......" "....11233211...." "...1234554321..." "...1356776531..." "..124677776421.." "..135777777531.." "..135777777531.." "..124677776421.." "...1356776531..." "...1234554321..." "....11233211...." "......1111......" "................" "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); r_editlights_sprnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/noshadow", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) "................" "................" "......1111......" "....11233211...." "...1234554321..." "...1356226531..." "..12462..26421.." "..1352....2531.." "..1352....2531.." "..12462..26421.." "...1356226531..." "...1234554321..." "....11233211...." "......1111......" "................" "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); r_editlights_sprcubemaplight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemaplight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) "................" "................" "......2772......" "....27755772...." "..277533335772.." "..753333333357.." "..777533335777.." "..735775577537.." "..733357753337.." "..733337733337.." "..753337733357.." "..277537735772.." "....27777772...." "......2772......" "................" "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); r_editlights_sprcubemapnoshadowlight = R_SkinFrame_LoadInternal8bit("gfx/editlights/cubemapnoshadowlight", TEXF_ALPHA | TEXF_CLAMP, (const unsigned char *) "................" "................" "......2772......" "....27722772...." "..2772....2772.." "..72........27.." "..7772....2777.." "..7.27722772.7.." "..7...2772...7.." "..7....77....7.." "..72...77...27.." "..2772.77.2772.." "....27777772...." "......2772......" "................" "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); r_editlights_sprselection = R_SkinFrame_LoadInternal8bit("gfx/editlights/selection", TEXF_ALPHA | TEXF_CLAMP, (unsigned char *) "................" ".777752..257777." ".742........247." ".72..........27." ".7............7." ".5............5." ".2............2." "................" "................" ".2............2." ".5............5." ".7............7." ".72..........27." ".742........247." ".777752..257777." "................" , 16, 16, palette_bgra_embeddedpic, palette_bgra_embeddedpic); } void R_Shadow_ValidateCvars(void) { if (r_shadow_texture3d.integer && !vid.support.ext_texture_3d) Cvar_SetValueQuick(&r_shadow_texture3d, 0); if (gl_ext_separatestencil.integer && !vid.support.ati_separate_stencil) Cvar_SetValueQuick(&gl_ext_separatestencil, 0); if (gl_ext_stenciltwoside.integer && !vid.support.ext_stencil_two_side) Cvar_SetValueQuick(&gl_ext_stenciltwoside, 0); } void R_Shadow_RenderMode_Begin(void) { #if 0 GLint drawbuffer; GLint readbuffer; #endif R_Shadow_ValidateCvars(); if (!r_shadow_attenuation2dtexture || (!r_shadow_attenuation3dtexture && r_shadow_texture3d.integer) || r_shadow_lightattenuationdividebias.value != r_shadow_attendividebias || r_shadow_lightattenuationlinearscale.value != r_shadow_attenlinearscale) R_Shadow_MakeTextures(); CHECKGLERROR R_Mesh_ResetTextureState(); GL_BlendFunc(GL_ONE, GL_ZERO); GL_DepthRange(0, 1); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset); GL_DepthTest(true); GL_DepthMask(false); GL_Color(0, 0, 0, 1); GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; if (gl_ext_separatestencil.integer && vid.support.ati_separate_stencil) { r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL; r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL; } else if (gl_ext_stenciltwoside.integer && vid.support.ext_stencil_two_side) { r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE; r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE; } else { r_shadow_shadowingrendermode_zpass = R_SHADOW_RENDERMODE_ZPASS_STENCIL; r_shadow_shadowingrendermode_zfail = R_SHADOW_RENDERMODE_ZFAIL_STENCIL; } switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: case RENDERPATH_GLES2: r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_GLSL; break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: if (r_textureunits.integer >= 2 && vid.texunits >= 2 && r_shadow_texture3d.integer && r_shadow_attenuation3dtexture) r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN; else if (r_textureunits.integer >= 3 && vid.texunits >= 3) r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN; else if (r_textureunits.integer >= 2 && vid.texunits >= 2) r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN; else r_shadow_lightingrendermode = R_SHADOW_RENDERMODE_LIGHT_VERTEX; break; } CHECKGLERROR #if 0 qglGetIntegerv(GL_DRAW_BUFFER, &drawbuffer);CHECKGLERROR qglGetIntegerv(GL_READ_BUFFER, &readbuffer);CHECKGLERROR r_shadow_drawbuffer = drawbuffer; r_shadow_readbuffer = readbuffer; #endif r_shadow_cullface_front = r_refdef.view.cullface_front; r_shadow_cullface_back = r_refdef.view.cullface_back; } void R_Shadow_RenderMode_ActiveLight(const rtlight_t *rtlight) { rsurface.rtlight = rtlight; } void R_Shadow_RenderMode_Reset(void) { R_Mesh_ResetTextureState(); R_Mesh_SetRenderTargets(r_shadow_fb_fbo, r_shadow_fb_depthtexture, r_shadow_fb_colortexture, NULL, NULL, NULL); R_SetViewport(&r_refdef.view.viewport); GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); GL_DepthRange(0, 1); GL_DepthTest(true); GL_DepthMask(false); GL_DepthFunc(GL_LEQUAL); GL_PolygonOffset(r_refdef.polygonfactor, r_refdef.polygonoffset);CHECKGLERROR r_refdef.view.cullface_front = r_shadow_cullface_front; r_refdef.view.cullface_back = r_shadow_cullface_back; GL_CullFace(r_refdef.view.cullface_back); GL_Color(1, 1, 1, 1); GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); GL_BlendFunc(GL_ONE, GL_ZERO); R_SetupShader_Generic_NoTexture(false, false); r_shadow_usingshadowmap2d = false; r_shadow_usingshadowmaportho = false; R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); } void R_Shadow_ClearStencil(void) { GL_Clear(GL_STENCIL_BUFFER_BIT, NULL, 1.0f, 128); r_refdef.stats[r_stat_lights_clears]++; } void R_Shadow_RenderMode_StencilShadowVolumes(qboolean zpass) { r_shadow_rendermode_t mode = zpass ? r_shadow_shadowingrendermode_zpass : r_shadow_shadowingrendermode_zfail; if (r_shadow_rendermode == mode) return; R_Shadow_RenderMode_Reset(); GL_DepthFunc(GL_LESS); GL_ColorMask(0, 0, 0, 0); GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR GL_CullFace(GL_NONE); R_SetupShader_DepthOrShadow(false, false, false); // FIXME test if we have a skeletal model? r_shadow_rendermode = mode; switch(mode) { default: break; case R_SHADOW_RENDERMODE_ZPASS_STENCILTWOSIDE: case R_SHADOW_RENDERMODE_ZPASS_SEPARATESTENCIL: R_SetStencilSeparate(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, GL_ALWAYS, 128, 255); break; case R_SHADOW_RENDERMODE_ZFAIL_STENCILTWOSIDE: case R_SHADOW_RENDERMODE_ZFAIL_SEPARATESTENCIL: R_SetStencilSeparate(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, GL_ALWAYS, 128, 255); break; } } static void R_Shadow_MakeVSDCT(void) { // maps to a 2x3 texture rectangle with normalized coordinates // +- // XX // YY // ZZ // stores abs(dir.xy), offset.xy/2.5 unsigned char data[4*6] = { 255, 0, 0x33, 0x33, // +X: <1, 0>, <0.5, 0.5> 255, 0, 0x99, 0x33, // -X: <1, 0>, <1.5, 0.5> 0, 255, 0x33, 0x99, // +Y: <0, 1>, <0.5, 1.5> 0, 255, 0x99, 0x99, // -Y: <0, 1>, <1.5, 1.5> 0, 0, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> 0, 0, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> }; r_shadow_shadowmapvsdcttexture = R_LoadTextureCubeMap(r_shadow_texturepool, "shadowmapvsdct", 1, data, TEXTYPE_RGBA, TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); } static void R_Shadow_MakeShadowMap(int side, int size) { switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: if (r_shadow_shadowmap2ddepthtexture) return; if (r_fb.usedepthtextures) { r_shadow_shadowmap2ddepthtexture = R_LoadTextureShadowMap2D(r_shadow_texturepool, "shadowmap", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), r_shadow_shadowmapdepthbits >= 24 ? (r_shadow_shadowmapsampler ? TEXTYPE_SHADOWMAP24_COMP : TEXTYPE_SHADOWMAP24_RAW) : (r_shadow_shadowmapsampler ? TEXTYPE_SHADOWMAP16_COMP : TEXTYPE_SHADOWMAP16_RAW), r_shadow_shadowmapsampler); r_shadow_shadowmap2ddepthbuffer = NULL; r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); } else { r_shadow_shadowmap2ddepthtexture = R_LoadTexture2D(r_shadow_texturepool, "shadowmaprendertarget", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), NULL, TEXTYPE_COLORBUFFER, TEXF_RENDERTARGET | TEXF_FORCENEAREST | TEXF_CLAMP | TEXF_ALPHA, -1, NULL); r_shadow_shadowmap2ddepthbuffer = R_LoadTextureRenderBuffer(r_shadow_texturepool, "shadowmap", size*2, size*(vid.support.arb_texture_non_power_of_two ? 3 : 4), r_shadow_shadowmapdepthbits >= 24 ? TEXTYPE_DEPTHBUFFER24 : TEXTYPE_DEPTHBUFFER16); r_shadow_fbo2d = R_Mesh_CreateFramebufferObject(r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); } break; default: return; } } static void R_Shadow_RenderMode_ShadowMap(int side, int clear, int size) { float nearclip, farclip, bias; r_viewport_t viewport; int flipped; GLuint fbo2d = 0; float clearcolor[4]; nearclip = r_shadow_shadowmapping_nearclip.value / rsurface.rtlight->radius; farclip = 1.0f; bias = r_shadow_shadowmapping_bias.value * nearclip * (1024.0f / size);// * rsurface.rtlight->radius; r_shadow_shadowmap_parameters[1] = -nearclip * farclip / (farclip - nearclip) - 0.5f * bias; r_shadow_shadowmap_parameters[3] = 0.5f + 0.5f * (farclip + nearclip) / (farclip - nearclip); r_shadow_shadowmapside = side; r_shadow_shadowmapsize = size; r_shadow_shadowmap_parameters[0] = 0.5f * (size - r_shadow_shadowmapborder); r_shadow_shadowmap_parameters[2] = r_shadow_shadowmapvsdct ? 2.5f*size : size; R_Viewport_InitRectSideView(&viewport, &rsurface.rtlight->matrix_lighttoworld, side, size, r_shadow_shadowmapborder, nearclip, farclip, NULL); if (r_shadow_rendermode == R_SHADOW_RENDERMODE_SHADOWMAP2D) goto init_done; // complex unrolled cube approach (more flexible) if (r_shadow_shadowmapvsdct && !r_shadow_shadowmapvsdcttexture) R_Shadow_MakeVSDCT(); if (!r_shadow_shadowmap2ddepthtexture) R_Shadow_MakeShadowMap(side, r_shadow_shadowmapmaxsize); fbo2d = r_shadow_fbo2d; r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2ddepthtexture); r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2ddepthtexture); r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; R_Mesh_ResetTextureState(); R_Shadow_RenderMode_Reset(); if (r_shadow_shadowmap2ddepthbuffer) R_Mesh_SetRenderTargets(fbo2d, r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); else R_Mesh_SetRenderTargets(fbo2d, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); R_SetupShader_DepthOrShadow(true, r_shadow_shadowmap2ddepthbuffer != NULL, false); // FIXME test if we have a skeletal model? GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); GL_DepthMask(true); GL_DepthTest(true); init_done: R_SetViewport(&viewport); flipped = (side & 1) ^ (side >> 2); r_refdef.view.cullface_front = flipped ? r_shadow_cullface_back : r_shadow_cullface_front; r_refdef.view.cullface_back = flipped ? r_shadow_cullface_front : r_shadow_cullface_back; if (r_shadow_shadowmap2ddepthbuffer) { // completely different meaning than in depthtexture approach r_shadow_shadowmap_parameters[1] = 0; r_shadow_shadowmap_parameters[3] = -bias; } Vector4Set(clearcolor, 1,1,1,1); if (r_shadow_shadowmap2ddepthbuffer) GL_ColorMask(1,1,1,1); else GL_ColorMask(0,0,0,0); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: GL_CullFace(r_refdef.view.cullface_back); // OpenGL lets us scissor larger than the viewport, so go ahead and clear all views at once if ((clear & ((2 << side) - 1)) == (1 << side)) // only clear if the side is the first in the mask { // get tightest scissor rectangle that encloses all viewports in the clear mask int x1 = clear & 0x15 ? 0 : size; int x2 = clear & 0x2A ? 2 * size : size; int y1 = clear & 0x03 ? 0 : (clear & 0xC ? size : 2 * size); int y2 = clear & 0x30 ? 3 * size : (clear & 0xC ? 2 * size : size); GL_Scissor(x1, y1, x2 - x1, y2 - y1); if (clear) { if (r_shadow_shadowmap2ddepthbuffer) GL_Clear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); else GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); } } GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: // we invert the cull mode because we flip the projection matrix // NOTE: this actually does nothing because the DrawShadowMap code sets it to doublesided... GL_CullFace(r_refdef.view.cullface_front); // D3D considers it an error to use a scissor larger than the viewport... clear just this view GL_Scissor(viewport.x, viewport.y, viewport.width, viewport.height); if (clear) { if (r_shadow_shadowmap2ddepthbuffer) GL_Clear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); else GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); } break; } } void R_Shadow_RenderMode_Lighting(qboolean stenciltest, qboolean transparent, qboolean shadowmapping) { R_Mesh_ResetTextureState(); if (transparent) { r_shadow_lightscissor[0] = r_refdef.view.viewport.x; r_shadow_lightscissor[1] = r_refdef.view.viewport.y; r_shadow_lightscissor[2] = r_refdef.view.viewport.width; r_shadow_lightscissor[3] = r_refdef.view.viewport.height; } R_Shadow_RenderMode_Reset(); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); if (!transparent) GL_DepthFunc(GL_EQUAL); // do global setup needed for the chosen lighting mode if (r_shadow_rendermode == R_SHADOW_RENDERMODE_LIGHT_GLSL) GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 0); r_shadow_usingshadowmap2d = shadowmapping; r_shadow_rendermode = r_shadow_lightingrendermode; // only draw light where this geometry was already rendered AND the // stencil is 128 (values other than this mean shadow) if (stenciltest) R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); else R_SetStencil(false, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_ALWAYS, 128, 255); } static const unsigned short bboxelements[36] = { 5, 1, 3, 5, 3, 7, 6, 2, 0, 6, 0, 4, 7, 3, 2, 7, 2, 6, 4, 0, 1, 4, 1, 5, 4, 5, 7, 4, 7, 6, 1, 0, 2, 1, 2, 3, }; static const float bboxpoints[8][3] = { {-1,-1,-1}, { 1,-1,-1}, {-1, 1,-1}, { 1, 1,-1}, {-1,-1, 1}, { 1,-1, 1}, {-1, 1, 1}, { 1, 1, 1}, }; void R_Shadow_RenderMode_DrawDeferredLight(qboolean stenciltest, qboolean shadowmapping) { int i; float vertex3f[8*3]; const matrix4x4_t *matrix = &rsurface.rtlight->matrix_lighttoworld; // do global setup needed for the chosen lighting mode R_Shadow_RenderMode_Reset(); r_shadow_rendermode = r_shadow_lightingrendermode; R_EntityMatrix(&identitymatrix); GL_BlendFunc(GL_SRC_ALPHA, GL_ONE); // only draw light where this geometry was already rendered AND the // stencil is 128 (values other than this mean shadow) R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); if (rsurface.rtlight->specularscale > 0 && r_shadow_gloss.integer > 0) R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); else R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); r_shadow_usingshadowmap2d = shadowmapping; // render the lighting R_SetupShader_DeferredLight(rsurface.rtlight); for (i = 0;i < 8;i++) Matrix4x4_Transform(matrix, bboxpoints[i], vertex3f + i*3); GL_ColorMask(1,1,1,1); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(true); GL_DepthFunc(GL_GREATER); GL_CullFace(r_refdef.view.cullface_back); R_Mesh_PrepareVertices_Vertex3f(8, vertex3f, NULL, 0); R_Mesh_Draw(0, 8, 0, 12, NULL, NULL, 0, bboxelements, NULL, 0); } #define MAXBOUNCEGRIDSPLATSIZE 7 #define MAXBOUNCEGRIDSPLATSIZE1 (MAXBOUNCEGRIDSPLATSIZE+1) // these are temporary data per-frame, sorted and performed in a more // cache-friendly order than the original photons typedef struct r_shadow_bouncegrid_splatpath_s { vec3_t point; vec3_t step; vec3_t splatcolor; vec3_t splatdir; vec_t splatintensity; int remainingsplats; } r_shadow_bouncegrid_splatpath_t; static void R_Shadow_BounceGrid_AddSplatPath(vec3_t originalstart, vec3_t originalend, vec3_t color) { int bestaxis; int numsplats; float len; float ilen; vec3_t start; vec3_t end; vec3_t diff; vec3_t originaldir; r_shadow_bouncegrid_splatpath_t *path; // cull paths that fail R_CullBox in dynamic mode if (!r_shadow_bouncegrid_state.settings.staticmode && r_shadow_bouncegrid_dynamic_culllightpaths.integer) { vec3_t cullmins, cullmaxs; cullmins[0] = min(originalstart[0], originalend[0]) - r_shadow_bouncegrid_state.settings.spacing[0]; cullmins[1] = min(originalstart[1], originalend[1]) - r_shadow_bouncegrid_state.settings.spacing[1]; cullmins[2] = min(originalstart[2], originalend[2]) - r_shadow_bouncegrid_state.settings.spacing[2]; cullmaxs[0] = max(originalstart[0], originalend[0]) + r_shadow_bouncegrid_state.settings.spacing[0]; cullmaxs[1] = max(originalstart[1], originalend[1]) + r_shadow_bouncegrid_state.settings.spacing[1]; cullmaxs[2] = max(originalstart[2], originalend[2]) + r_shadow_bouncegrid_state.settings.spacing[2]; if (R_CullBox(cullmins, cullmaxs)) return; } // if the light path is going upward, reverse it - we always draw down. if (originalend[2] < originalstart[2]) { VectorCopy(originalend, start); VectorCopy(originalstart, end); } else { VectorCopy(originalstart, start); VectorCopy(originalend, end); } // transform to texture pixels start[0] = (start[0] - r_shadow_bouncegrid_state.mins[0]) * r_shadow_bouncegrid_state.ispacing[0]; start[1] = (start[1] - r_shadow_bouncegrid_state.mins[1]) * r_shadow_bouncegrid_state.ispacing[1]; start[2] = (start[2] - r_shadow_bouncegrid_state.mins[2]) * r_shadow_bouncegrid_state.ispacing[2]; end[0] = (end[0] - r_shadow_bouncegrid_state.mins[0]) * r_shadow_bouncegrid_state.ispacing[0]; end[1] = (end[1] - r_shadow_bouncegrid_state.mins[1]) * r_shadow_bouncegrid_state.ispacing[1]; end[2] = (end[2] - r_shadow_bouncegrid_state.mins[2]) * r_shadow_bouncegrid_state.ispacing[2]; // check if we need to grow the splatpaths array if (r_shadow_bouncegrid_state.maxsplatpaths <= r_shadow_bouncegrid_state.numsplatpaths) { // double the limit, this will persist from frame to frame so we don't // make the same mistake each time r_shadow_bouncegrid_splatpath_t *newpaths; r_shadow_bouncegrid_state.maxsplatpaths *= 2; newpaths = (r_shadow_bouncegrid_splatpath_t *)R_FrameData_Alloc(sizeof(r_shadow_bouncegrid_splatpath_t) * r_shadow_bouncegrid_state.maxsplatpaths); if (r_shadow_bouncegrid_state.splatpaths) memcpy(newpaths, r_shadow_bouncegrid_state.splatpaths, r_shadow_bouncegrid_state.numsplatpaths * sizeof(r_shadow_bouncegrid_splatpath_t)); r_shadow_bouncegrid_state.splatpaths = newpaths; } // divide a series of splats along the length using the maximum axis VectorSubtract(end, start, diff); // pick the best axis to trace along bestaxis = 0; if (diff[1]*diff[1] > diff[bestaxis]*diff[bestaxis]) bestaxis = 1; if (diff[2]*diff[2] > diff[bestaxis]*diff[bestaxis]) bestaxis = 2; len = fabs(diff[bestaxis]); ilen = 1.0f / len; numsplats = (int)(floor(len + 0.5f)); // sanity limits numsplats = bound(0, numsplats, 1024); VectorSubtract(originalstart, originalend, originaldir); VectorNormalize(originaldir); path = r_shadow_bouncegrid_state.splatpaths + r_shadow_bouncegrid_state.numsplatpaths++; VectorCopy(start, path->point); VectorScale(diff, ilen, path->step); VectorCopy(color, path->splatcolor); VectorCopy(originaldir, path->splatdir); path->splatintensity = VectorLength(color); path->remainingsplats = numsplats; } static qboolean R_Shadow_BounceGrid_CheckEnable(int flag) { qboolean enable = r_shadow_bouncegrid_state.capable && r_shadow_bouncegrid.integer != 0 && r_refdef.scene.worldmodel; int lightindex; int range; dlight_t *light; rtlight_t *rtlight; vec3_t lightcolor; // see if there are really any lights to render... if (enable && r_shadow_bouncegrid_static.integer) { enable = false; range = (unsigned int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light || !(light->flags & flag)) continue; rtlight = &light->rtlight; // when static, we skip styled lights because they tend to change... if (rtlight->style > 0) continue; VectorScale(rtlight->color, (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale), lightcolor); if (!VectorLength2(lightcolor)) continue; enable = true; break; } } return enable; } static void R_Shadow_BounceGrid_GenerateSettings(r_shadow_bouncegrid_settings_t *settings) { qboolean s = r_shadow_bouncegrid_static.integer != 0; float spacing = s ? r_shadow_bouncegrid_static_spacing.value : r_shadow_bouncegrid_dynamic_spacing.value; // prevent any garbage in alignment padded areas as we'll be using memcmp memset(settings, 0, sizeof(*settings)); // build up a complete collection of the desired settings, so that memcmp can be used to compare parameters settings->staticmode = s; settings->blur = r_shadow_bouncegrid_blur.integer != 0; settings->floatcolors = bound(0, r_shadow_bouncegrid_floatcolors.integer, 2); settings->lightpathsize = bound(1, r_shadow_bouncegrid_lightpathsize.integer, MAXBOUNCEGRIDSPLATSIZE); settings->bounceanglediffuse = r_shadow_bouncegrid_bounceanglediffuse.integer != 0; settings->directionalshading = (s ? r_shadow_bouncegrid_static_directionalshading.integer != 0 : r_shadow_bouncegrid_dynamic_directionalshading.integer != 0) && r_shadow_bouncegrid_state.allowdirectionalshading; settings->dlightparticlemultiplier = s ? 0 : r_shadow_bouncegrid_dynamic_dlightparticlemultiplier.value; settings->hitmodels = s ? false : r_shadow_bouncegrid_dynamic_hitmodels.integer != 0; settings->includedirectlighting = r_shadow_bouncegrid_includedirectlighting.integer != 0 || r_shadow_bouncegrid.integer == 2; settings->lightradiusscale = (s ? r_shadow_bouncegrid_static_lightradiusscale.value : r_shadow_bouncegrid_dynamic_lightradiusscale.value); settings->maxbounce = (s ? r_shadow_bouncegrid_static_maxbounce.integer : r_shadow_bouncegrid_dynamic_maxbounce.integer); settings->particlebounceintensity = r_shadow_bouncegrid_particlebounceintensity.value; settings->particleintensity = r_shadow_bouncegrid_particleintensity.value * 16384.0f * (settings->directionalshading ? 4.0f : 1.0f) / (spacing * spacing); settings->maxphotons = s ? r_shadow_bouncegrid_static_maxphotons.integer : r_shadow_bouncegrid_dynamic_maxphotons.integer; settings->energyperphoton = s ? r_shadow_bouncegrid_static_energyperphoton.integer : r_shadow_bouncegrid_dynamic_energyperphoton.integer; settings->spacing[0] = spacing; settings->spacing[1] = spacing; settings->spacing[2] = spacing; settings->stablerandom = s ? 1 : r_shadow_bouncegrid_dynamic_stablerandom.integer; // bound the values for sanity settings->maxphotons = bound(1, settings->maxphotons, 25000000); settings->lightradiusscale = bound(0.0001f, settings->lightradiusscale, 1024.0f); settings->maxbounce = bound(0, settings->maxbounce, 16); settings->spacing[0] = bound(1, settings->spacing[0], 512); settings->spacing[1] = bound(1, settings->spacing[1], 512); settings->spacing[2] = bound(1, settings->spacing[2], 512); } static void R_Shadow_BounceGrid_UpdateSpacing(void) { float m[16]; int c[4]; int resolution[3]; int numpixels; vec3_t ispacing; vec3_t maxs; vec3_t mins; vec3_t size; vec3_t spacing; r_shadow_bouncegrid_settings_t *settings = &r_shadow_bouncegrid_state.settings; // get the spacing values spacing[0] = settings->spacing[0]; spacing[1] = settings->spacing[1]; spacing[2] = settings->spacing[2]; ispacing[0] = 1.0f / spacing[0]; ispacing[1] = 1.0f / spacing[1]; ispacing[2] = 1.0f / spacing[2]; // calculate texture size enclosing entire world bounds at the spacing if (r_refdef.scene.worldmodel) { VectorMA(r_refdef.scene.worldmodel->normalmins, -2.0f, spacing, mins); VectorMA(r_refdef.scene.worldmodel->normalmaxs, 2.0f, spacing, maxs); } else { VectorSet(mins, -1048576.0f, -1048576.0f, -1048576.0f); VectorSet(maxs, 1048576.0f, 1048576.0f, 1048576.0f); } VectorSubtract(maxs, mins, size); // now we can calculate the resolution we want c[0] = (int)floor(size[0] / spacing[0] + 0.5f); c[1] = (int)floor(size[1] / spacing[1] + 0.5f); c[2] = (int)floor(size[2] / spacing[2] + 0.5f); // figure out the exact texture size (honoring power of 2 if required) c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); if (vid.support.arb_texture_non_power_of_two) { resolution[0] = c[0]; resolution[1] = c[1]; resolution[2] = c[2]; } else { for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; } size[0] = spacing[0] * resolution[0]; size[1] = spacing[1] * resolution[1]; size[2] = spacing[2] * resolution[2]; // if dynamic we may or may not want to use the world bounds // if the dynamic size is smaller than the world bounds, use it instead if (!settings->staticmode && (r_shadow_bouncegrid_dynamic_x.integer * r_shadow_bouncegrid_dynamic_y.integer * r_shadow_bouncegrid_dynamic_z.integer < resolution[0] * resolution[1] * resolution[2])) { // we know the resolution we want c[0] = r_shadow_bouncegrid_dynamic_x.integer; c[1] = r_shadow_bouncegrid_dynamic_y.integer; c[2] = r_shadow_bouncegrid_dynamic_z.integer; // now we can calculate the texture size (power of 2 if required) c[0] = bound(4, c[0], (int)vid.maxtexturesize_3d); c[1] = bound(4, c[1], (int)vid.maxtexturesize_3d); c[2] = bound(4, c[2], (int)vid.maxtexturesize_3d); if (vid.support.arb_texture_non_power_of_two) { resolution[0] = c[0]; resolution[1] = c[1]; resolution[2] = c[2]; } else { for (resolution[0] = 4;resolution[0] < c[0];resolution[0]*=2) ; for (resolution[1] = 4;resolution[1] < c[1];resolution[1]*=2) ; for (resolution[2] = 4;resolution[2] < c[2];resolution[2]*=2) ; } size[0] = spacing[0] * resolution[0]; size[1] = spacing[1] * resolution[1]; size[2] = spacing[2] * resolution[2]; // center the rendering on the view mins[0] = floor(r_refdef.view.origin[0] * ispacing[0] + 0.5f) * spacing[0] - 0.5f * size[0]; mins[1] = floor(r_refdef.view.origin[1] * ispacing[1] + 0.5f) * spacing[1] - 0.5f * size[1]; mins[2] = floor(r_refdef.view.origin[2] * ispacing[2] + 0.5f) * spacing[2] - 0.5f * size[2]; } // recalculate the maxs in case the resolution was not satisfactory VectorAdd(mins, size, maxs); // check if this changed the texture size r_shadow_bouncegrid_state.createtexture = !(r_shadow_bouncegrid_state.texture && r_shadow_bouncegrid_state.resolution[0] == resolution[0] && r_shadow_bouncegrid_state.resolution[1] == resolution[1] && r_shadow_bouncegrid_state.resolution[2] == resolution[2] && r_shadow_bouncegrid_state.directional == r_shadow_bouncegrid_state.settings.directionalshading); r_shadow_bouncegrid_state.directional = r_shadow_bouncegrid_state.settings.directionalshading; VectorCopy(mins, r_shadow_bouncegrid_state.mins); VectorCopy(maxs, r_shadow_bouncegrid_state.maxs); VectorCopy(size, r_shadow_bouncegrid_state.size); VectorCopy(spacing, r_shadow_bouncegrid_state.spacing); VectorCopy(ispacing, r_shadow_bouncegrid_state.ispacing); VectorCopy(resolution, r_shadow_bouncegrid_state.resolution); // reallocate pixels for this update if needed... r_shadow_bouncegrid_state.pixelbands = settings->directionalshading ? 8 : 1; r_shadow_bouncegrid_state.pixelsperband = resolution[0]*resolution[1]*resolution[2]; r_shadow_bouncegrid_state.bytesperband = r_shadow_bouncegrid_state.pixelsperband*4; numpixels = r_shadow_bouncegrid_state.pixelsperband*r_shadow_bouncegrid_state.pixelbands; if (r_shadow_bouncegrid_state.numpixels != numpixels) { if (r_shadow_bouncegrid_state.texture) { R_FreeTexture(r_shadow_bouncegrid_state.texture); r_shadow_bouncegrid_state.texture = NULL; } r_shadow_bouncegrid_state.numpixels = numpixels; } // update the bouncegrid matrix to put it in the world properly memset(m, 0, sizeof(m)); m[0] = 1.0f / r_shadow_bouncegrid_state.size[0]; m[3] = -r_shadow_bouncegrid_state.mins[0] * m[0]; m[5] = 1.0f / r_shadow_bouncegrid_state.size[1]; m[7] = -r_shadow_bouncegrid_state.mins[1] * m[5]; m[10] = 1.0f / r_shadow_bouncegrid_state.size[2]; m[11] = -r_shadow_bouncegrid_state.mins[2] * m[10]; m[15] = 1.0f; Matrix4x4_FromArrayFloatD3D(&r_shadow_bouncegrid_state.matrix, m); } #define MAXBOUNCEGRIDPARTICLESPERLIGHT 1048576 // enumerate world rtlights and sum the overall amount of light in the world, // from that we can calculate a scaling factor to fairly distribute photons // to all the lights // // this modifies rtlight->photoncolor and rtlight->photons static void R_Shadow_BounceGrid_AssignPhotons(r_shadow_bouncegrid_settings_t *settings, unsigned int range, unsigned int range1, unsigned int range2, int flag, float *photonscaling) { float normalphotonscaling; float maxphotonscaling; float photoncount = 0.0f; float lightintensity; float radius; float s; float w; vec3_t cullmins; vec3_t cullmaxs; unsigned int lightindex; dlight_t *light; rtlight_t *rtlight; for (lightindex = 0;lightindex < range2;lightindex++) { if (lightindex < range) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; rtlight = &light->rtlight; VectorClear(rtlight->photoncolor); rtlight->photons = 0; if (!(light->flags & flag)) continue; if (settings->staticmode) { // when static, we skip styled lights because they tend to change... if (rtlight->style > 0 && r_shadow_bouncegrid.integer != 2) continue; } } else { rtlight = r_refdef.scene.lights[lightindex - range]; VectorClear(rtlight->photoncolor); rtlight->photons = 0; } // draw only visible lights (major speedup) radius = rtlight->radius * settings->lightradiusscale; cullmins[0] = rtlight->shadoworigin[0] - radius; cullmins[1] = rtlight->shadoworigin[1] - radius; cullmins[2] = rtlight->shadoworigin[2] - radius; cullmaxs[0] = rtlight->shadoworigin[0] + radius; cullmaxs[1] = rtlight->shadoworigin[1] + radius; cullmaxs[2] = rtlight->shadoworigin[2] + radius; w = r_shadow_lightintensityscale.value * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); if (!settings->staticmode) { if (R_CullBox(cullmins, cullmaxs)) continue; if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs && !r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, cullmins, cullmaxs)) continue; if (w * VectorLength2(rtlight->color) == 0.0f) continue; } // a light that does not emit any light before style is applied, can be // skipped entirely (it may just be a corona) if (rtlight->radius == 0.0f || VectorLength2(rtlight->color) == 0.0f) continue; w *= ((rtlight->style >= 0 && rtlight->style < MAX_LIGHTSTYLES) ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1); VectorScale(rtlight->color, w, rtlight->photoncolor); // skip lights that will emit no photons if (!VectorLength2(rtlight->photoncolor)) continue; // shoot particles from this light // use a calculation for the number of particles that will not // vary with lightstyle, otherwise we get randomized particle // distribution, the seeded random is only consistent for a // consistent number of particles on this light... s = rtlight->radius; lightintensity = VectorLength(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale); if (lightindex >= range) lightintensity *= settings->dlightparticlemultiplier; rtlight->photons = bound(0.0f, lightintensity * s * s, MAXBOUNCEGRIDPARTICLESPERLIGHT); photoncount += rtlight->photons; // if the lightstyle happens to be off right now, we can skip actually // firing the photons, but we did have to count them in the total. //if (VectorLength2(rtlight->photoncolor) == 0.0f) // rtlight->photons = 0; } // the user provided an energyperphoton value which we try to use // if that results in too many photons to shoot this frame, then we cap it // which causes photons to appear/disappear from frame to frame, so we don't // like doing that in the typical case normalphotonscaling = 1.0f / max(0.0001f, settings->energyperphoton); maxphotonscaling = (float)settings->maxphotons / max(1, photoncount); *photonscaling = min(normalphotonscaling, maxphotonscaling); } static int R_Shadow_BounceGrid_SplatPathCompare(const void *pa, const void *pb) { r_shadow_bouncegrid_splatpath_t *a = (r_shadow_bouncegrid_splatpath_t *)pa; r_shadow_bouncegrid_splatpath_t *b = (r_shadow_bouncegrid_splatpath_t *)pb; // we only really care about sorting by Z if (a->point[2] < b->point[2]) return -1; if (a->point[2] > b->point[2]) return 1; return 0; } static void R_Shadow_BounceGrid_ClearPixels(void) { // clear the highpixels array we'll be accumulating into r_shadow_bouncegrid_state.highpixels = (float *)R_FrameData_Alloc(r_shadow_bouncegrid_state.numpixels * sizeof(float[4])); memset(r_shadow_bouncegrid_state.highpixels, 0, r_shadow_bouncegrid_state.numpixels * sizeof(float[4])); } static void R_Shadow_BounceGrid_PerformSplats(void) { int splatsize = r_shadow_bouncegrid_state.settings.lightpathsize; int splatsize1 = splatsize + 1; r_shadow_bouncegrid_splatpath_t *splatpaths = r_shadow_bouncegrid_state.splatpaths; r_shadow_bouncegrid_splatpath_t *splatpath; float *highpixels = r_shadow_bouncegrid_state.highpixels; int numsplatpaths = r_shadow_bouncegrid_state.numsplatpaths; int splatindex; vec3_t steppos; vec3_t stepdelta; vec3_t dir; float texcorner[3]; float texlerp[MAXBOUNCEGRIDSPLATSIZE1][3]; float splatcolor[32]; float boxweight = 1.0f / (splatsize * splatsize * splatsize); int resolution[3]; int tex[3]; int pixelsperband = r_shadow_bouncegrid_state.pixelsperband; int pixelbands = r_shadow_bouncegrid_state.pixelbands; int numsteps; int step; // hush warnings about uninitialized data - pixelbands doesn't change but... memset(splatcolor, 0, sizeof(splatcolor)); // we use this a lot, so get a local copy VectorCopy(r_shadow_bouncegrid_state.resolution, resolution); // sort the splats before we execute them, to reduce cache misses if (r_shadow_bouncegrid_sortlightpaths.integer) qsort(splatpaths, numsplatpaths, sizeof(*splatpaths), R_Shadow_BounceGrid_SplatPathCompare); // the middle row/column/layer of each splat are full intensity for (step = 1;step < splatsize;step++) VectorSet(texlerp[step], 1.0f, 1.0f, 1.0f); splatpath = splatpaths; for (splatindex = 0;splatindex < numsplatpaths;splatindex++, splatpath++) { // calculate second order spherical harmonics values (average, slopeX, slopeY, slopeZ) // accumulate average shotcolor VectorCopy(splatpath->splatdir, dir); splatcolor[ 0] = splatpath->splatcolor[0]; splatcolor[ 1] = splatpath->splatcolor[1]; splatcolor[ 2] = splatpath->splatcolor[2]; splatcolor[ 3] = 0.0f; if (pixelbands > 1) { // store bentnormal in case the shader has a use for it, // bentnormal is an intensity-weighted average of the directions, // and will be normalized on conversion to texture pixels. splatcolor[ 4] = dir[0] * splatpath->splatintensity; splatcolor[ 5] = dir[1] * splatpath->splatintensity; splatcolor[ 6] = dir[2] * splatpath->splatintensity; splatcolor[ 7] = splatpath->splatintensity; // for each color component (R, G, B) calculate the amount that a // direction contributes splatcolor[ 8] = splatcolor[0] * max(0.0f, dir[0]); splatcolor[ 9] = splatcolor[0] * max(0.0f, dir[1]); splatcolor[10] = splatcolor[0] * max(0.0f, dir[2]); splatcolor[11] = 0.0f; splatcolor[12] = splatcolor[1] * max(0.0f, dir[0]); splatcolor[13] = splatcolor[1] * max(0.0f, dir[1]); splatcolor[14] = splatcolor[1] * max(0.0f, dir[2]); splatcolor[15] = 0.0f; splatcolor[16] = splatcolor[2] * max(0.0f, dir[0]); splatcolor[17] = splatcolor[2] * max(0.0f, dir[1]); splatcolor[18] = splatcolor[2] * max(0.0f, dir[2]); splatcolor[19] = 0.0f; // and do the same for negative directions splatcolor[20] = splatcolor[0] * max(0.0f, -dir[0]); splatcolor[21] = splatcolor[0] * max(0.0f, -dir[1]); splatcolor[22] = splatcolor[0] * max(0.0f, -dir[2]); splatcolor[23] = 0.0f; splatcolor[24] = splatcolor[1] * max(0.0f, -dir[0]); splatcolor[25] = splatcolor[1] * max(0.0f, -dir[1]); splatcolor[26] = splatcolor[1] * max(0.0f, -dir[2]); splatcolor[27] = 0.0f; splatcolor[28] = splatcolor[2] * max(0.0f, -dir[0]); splatcolor[29] = splatcolor[2] * max(0.0f, -dir[1]); splatcolor[30] = splatcolor[2] * max(0.0f, -dir[2]); splatcolor[31] = 0.0f; } // calculate the number of steps we need to traverse this distance VectorCopy(splatpath->point, steppos); VectorCopy(splatpath->step, stepdelta); numsteps = splatpath->remainingsplats; for (step = 0;step < numsteps;step++) { r_refdef.stats[r_stat_bouncegrid_splats]++; // figure out the min corner of the pixels we'll need to update texcorner[0] = steppos[0] - (splatsize1 * 0.5f); texcorner[1] = steppos[1] - (splatsize1 * 0.5f); texcorner[2] = steppos[2] - (splatsize1 * 0.5f); tex[0] = (int)floor(texcorner[0]); tex[1] = (int)floor(texcorner[1]); tex[2] = (int)floor(texcorner[2]); // only update if it is within reasonable bounds if (tex[0] >= 1 && tex[1] >= 1 && tex[2] >= 1 && tex[0] < resolution[0] - splatsize1 && tex[1] < resolution[1] - splatsize1 && tex[2] < resolution[2] - splatsize1) { // it is within bounds... do the real work now int xi, yi, zi; // calculate the antialiased box edges texlerp[splatsize][0] = texcorner[0] - tex[0]; texlerp[splatsize][1] = texcorner[1] - tex[1]; texlerp[splatsize][2] = texcorner[2] - tex[2]; texlerp[0][0] = 1.0f - texlerp[splatsize][0]; texlerp[0][1] = 1.0f - texlerp[splatsize][1]; texlerp[0][2] = 1.0f - texlerp[splatsize][2]; // accumulate light onto the pixels for (zi = 0;zi < splatsize1;zi++) { for (yi = 0;yi < splatsize1;yi++) { int index = ((tex[2]+zi)*resolution[1]+tex[1]+yi)*resolution[0]+tex[0]; for (xi = 0;xi < splatsize1;xi++, index++) { float w = texlerp[xi][0]*texlerp[yi][1]*texlerp[zi][2] * boxweight; int band = 0; float *p = highpixels + 4 * index + band * pixelsperband * 4; for (;band < pixelbands;band++, p += pixelsperband * 4) { // add to the pixel color p[0] += splatcolor[band*4+0] * w; p[1] += splatcolor[band*4+1] * w; p[2] += splatcolor[band*4+2] * w; p[3] += splatcolor[band*4+3] * w; } } } } } VectorAdd(steppos, stepdelta, steppos); } } } static void R_Shadow_BounceGrid_BlurPixelsInDirection(const float *inpixels, float *outpixels, int off) { const float *inpixel; float *outpixel; int pixelbands = r_shadow_bouncegrid_state.pixelbands; int pixelband; unsigned int index; unsigned int x, y, z; unsigned int resolution[3]; VectorCopy(r_shadow_bouncegrid_state.resolution, resolution); for (pixelband = 0;pixelband < pixelbands;pixelband++) { for (z = 1;z < resolution[2]-1;z++) { for (y = 1;y < resolution[1]-1;y++) { x = 1; index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x; inpixel = inpixels + 4*index; outpixel = outpixels + 4*index; for (;x < resolution[0]-1;x++, inpixel += 4, outpixel += 4) { outpixel[0] = (inpixel[0] + inpixel[ off] + inpixel[0-off]) * (1.0f / 3.0); outpixel[1] = (inpixel[1] + inpixel[1+off] + inpixel[1-off]) * (1.0f / 3.0); outpixel[2] = (inpixel[2] + inpixel[2+off] + inpixel[2-off]) * (1.0f / 3.0); outpixel[3] = (inpixel[3] + inpixel[3+off] + inpixel[3-off]) * (1.0f / 3.0); } } } } } static void R_Shadow_BounceGrid_BlurPixels(void) { float *highpixels = r_shadow_bouncegrid_state.highpixels; float *temppixels1 = (float *)R_FrameData_Alloc(r_shadow_bouncegrid_state.numpixels * sizeof(float[4])); float *temppixels2 = (float *)R_FrameData_Alloc(r_shadow_bouncegrid_state.numpixels * sizeof(float[4])); unsigned int resolution[3]; if (!r_shadow_bouncegrid_blur.integer) return; VectorCopy(r_shadow_bouncegrid_state.resolution, resolution); // blur on X R_Shadow_BounceGrid_BlurPixelsInDirection(highpixels, temppixels1, 4); // blur on Y R_Shadow_BounceGrid_BlurPixelsInDirection(temppixels1, temppixels2, resolution[0] * 4); // blur on Z R_Shadow_BounceGrid_BlurPixelsInDirection(temppixels2, highpixels, resolution[0] * resolution[1] * 4); } static void R_Shadow_BounceGrid_ConvertPixelsAndUpload(void) { int floatcolors = r_shadow_bouncegrid_state.settings.floatcolors; unsigned char *pixelsbgra8 = NULL; unsigned char *pixelbgra8; unsigned short *pixelsrgba16f = NULL; unsigned short *pixelrgba16f; float *pixelsrgba32f = NULL; float *highpixels = r_shadow_bouncegrid_state.highpixels; float *highpixel; float *bandpixel; unsigned int pixelsperband = r_shadow_bouncegrid_state.pixelsperband; unsigned int pixelbands = r_shadow_bouncegrid_state.pixelbands; unsigned int pixelband; unsigned int x, y, z; unsigned int index, bandindex; unsigned int resolution[3]; int c[4]; VectorCopy(r_shadow_bouncegrid_state.resolution, resolution); if (r_shadow_bouncegrid_state.createtexture && r_shadow_bouncegrid_state.texture) { R_FreeTexture(r_shadow_bouncegrid_state.texture); r_shadow_bouncegrid_state.texture = NULL; } // if bentnormals exist, we need to normalize and bias them for the shader if (pixelbands > 1) { pixelband = 1; for (z = 0;z < resolution[2]-1;z++) { for (y = 0;y < resolution[1]-1;y++) { x = 1; index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x; highpixel = highpixels + 4*index; for (;x < resolution[0]-1;x++, index++, highpixel += 4) { // only convert pixels that were hit by photons if (highpixel[3] != 0.0f) VectorNormalize(highpixel); VectorSet(highpixel, highpixel[0] * 0.5f + 0.5f, highpixel[1] * 0.5f + 0.5f, highpixel[2] * 0.5f + 0.5f); highpixel[pixelsperband * 4 + 3] = 1.0f; } } } } // start by clearing the pixels array - we won't be writing to all of it // // then process only the pixels that have at least some color, skipping // the higher bands for speed on pixels that are black switch (floatcolors) { case 0: pixelsbgra8 = (unsigned char *)R_FrameData_Alloc(r_shadow_bouncegrid_state.numpixels * sizeof(unsigned char[4])); for (pixelband = 0;pixelband < pixelbands;pixelband++) { if (pixelband == 1) memset(pixelsbgra8 + pixelband * r_shadow_bouncegrid_state.bytesperband, 128, r_shadow_bouncegrid_state.bytesperband); else memset(pixelsbgra8 + pixelband * r_shadow_bouncegrid_state.bytesperband, 0, r_shadow_bouncegrid_state.bytesperband); } for (z = 1;z < resolution[2]-1;z++) { for (y = 1;y < resolution[1]-1;y++) { x = 1; pixelband = 0; index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x; highpixel = highpixels + 4*index; for (;x < resolution[0]-1;x++, index++, highpixel += 4) { // only convert pixels that were hit by photons if (VectorLength2(highpixel)) { // normalize the bentnormal now if (pixelbands > 1) { VectorNormalize(highpixel + pixelsperband * 4); highpixel[pixelsperband * 4 + 3] = 1.0f; } // process all of the pixelbands for this pixel for (pixelband = 0, bandindex = index;pixelband < pixelbands;pixelband++, bandindex += pixelsperband) { pixelbgra8 = pixelsbgra8 + 4*bandindex; bandpixel = highpixels + 4*bandindex; c[0] = (int)(bandpixel[0]*256.0f); c[1] = (int)(bandpixel[1]*256.0f); c[2] = (int)(bandpixel[2]*256.0f); c[3] = (int)(bandpixel[3]*256.0f); pixelbgra8[2] = (unsigned char)bound(0, c[0], 255); pixelbgra8[1] = (unsigned char)bound(0, c[1], 255); pixelbgra8[0] = (unsigned char)bound(0, c[2], 255); pixelbgra8[3] = (unsigned char)bound(0, c[3], 255); } } } } } if (!r_shadow_bouncegrid_state.createtexture) R_UpdateTexture(r_shadow_bouncegrid_state.texture, pixelsbgra8, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands); else r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, pixelsbgra8, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL); break; case 1: pixelsrgba16f = (unsigned short *)R_FrameData_Alloc(r_shadow_bouncegrid_state.numpixels * sizeof(unsigned short[4])); memset(pixelsrgba16f, 0, r_shadow_bouncegrid_state.numpixels * sizeof(unsigned short[4])); for (z = 1;z < resolution[2]-1;z++) { for (y = 1;y < resolution[1]-1;y++) { x = 1; pixelband = 0; index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x; highpixel = highpixels + 4*index; for (;x < resolution[0]-1;x++, index++, highpixel += 4) { // only convert pixels that were hit by photons if (VectorLength2(highpixel)) { // process all of the pixelbands for this pixel for (pixelband = 0, bandindex = index;pixelband < pixelbands;pixelband++, bandindex += pixelsperband) { // time to have fun with IEEE 754 bit hacking... union { float f[4]; unsigned int raw[4]; } u; pixelrgba16f = pixelsrgba16f + 4*bandindex; bandpixel = highpixels + 4*bandindex; VectorCopy4(bandpixel, u.f); VectorCopy4(u.raw, c); // this math supports negative numbers, snaps denormals to zero //pixelrgba16f[0] = (unsigned short)(((c[0] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[0] - 0x38000000) >> 13) & 0x7FFF) | ((c[0] >> 16) & 0x8000)); //pixelrgba16f[1] = (unsigned short)(((c[1] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[1] - 0x38000000) >> 13) & 0x7FFF) | ((c[1] >> 16) & 0x8000)); //pixelrgba16f[2] = (unsigned short)(((c[2] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[2] - 0x38000000) >> 13) & 0x7FFF) | ((c[2] >> 16) & 0x8000)); //pixelrgba16f[3] = (unsigned short)(((c[3] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[3] - 0x38000000) >> 13) & 0x7FFF) | ((c[3] >> 16) & 0x8000)); // this math does not support negative pixelrgba16f[0] = (unsigned short)((c[0] < 0x38000000) ? 0 : ((c[0] - 0x38000000) >> 13)); pixelrgba16f[1] = (unsigned short)((c[1] < 0x38000000) ? 0 : ((c[1] - 0x38000000) >> 13)); pixelrgba16f[2] = (unsigned short)((c[2] < 0x38000000) ? 0 : ((c[2] - 0x38000000) >> 13)); pixelrgba16f[3] = (unsigned short)((c[3] < 0x38000000) ? 0 : ((c[3] - 0x38000000) >> 13)); } } } } } if (!r_shadow_bouncegrid_state.createtexture) R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba16f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands); else r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba16f, TEXTYPE_COLORBUFFER16F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL); break; case 2: // our native format happens to match, so this is easy. pixelsrgba32f = highpixels; if (!r_shadow_bouncegrid_state.createtexture) R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba32f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands); else r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba32f, TEXTYPE_COLORBUFFER32F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL); break; } r_shadow_bouncegrid_state.lastupdatetime = realtime; } static void R_Shadow_BounceGrid_TracePhotons(r_shadow_bouncegrid_settings_t settings, unsigned int range, unsigned int range1, unsigned int range2, float photonscaling, int flag) { vec3_t bouncerandom[10]; dlight_t *light; int bouncecount; int hitsupercontentsmask; int skipsupercontentsmask; int maxbounce; int shootparticles; int shotparticles; trace_t cliptrace; //trace_t cliptrace2; //trace_t cliptrace3; unsigned int lightindex; unsigned int seed = (unsigned int)(realtime * 1000.0f); randomseed_t randomseed; vec3_t shotcolor; vec3_t baseshotcolor; vec3_t surfcolor; vec3_t clipend; vec3_t clipstart; vec3_t clipdiff; vec_t radius; vec_t s; rtlight_t *rtlight; Math_RandomSeed_FromInt(&randomseed, seed); r_shadow_bouncegrid_state.numsplatpaths = 0; r_shadow_bouncegrid_state.splatpaths = (r_shadow_bouncegrid_splatpath_t *)R_FrameData_Alloc(sizeof(r_shadow_bouncegrid_splatpath_t) * r_shadow_bouncegrid_state.maxsplatpaths); // figure out what we want to interact with if (settings.hitmodels) hitsupercontentsmask = SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;// | SUPERCONTENTS_LIQUIDSMASK; else hitsupercontentsmask = SUPERCONTENTS_SOLID;// | SUPERCONTENTS_LIQUIDSMASK; skipsupercontentsmask = SUPERCONTENTS_SKY; // this allows the e1m5 sky shadow to work by ignoring the sky surfaces maxbounce = settings.maxbounce; for (lightindex = 0;lightindex < range2;lightindex++) { if (lightindex < range) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; rtlight = &light->rtlight; } else rtlight = r_refdef.scene.lights[lightindex - range]; // note that this code used to keep track of residual photons and // distribute them evenly to achieve exactly a desired photon count, // but that caused unwanted flickering in dynamic mode shootparticles = (int)floor(rtlight->photons * photonscaling); // skip if we won't be shooting any photons if (!shootparticles) continue; radius = rtlight->radius * settings.lightradiusscale; s = settings.particleintensity / shootparticles; VectorScale(rtlight->photoncolor, s, baseshotcolor); r_refdef.stats[r_stat_bouncegrid_lights]++; r_refdef.stats[r_stat_bouncegrid_particles] += shootparticles; switch (settings.stablerandom) { default: break; case 1: Math_RandomSeed_FromInt(&randomseed, lightindex * 11937); // prime the random number generator a bit Math_crandomf(&randomseed); break; case 2: seed = lightindex * 11937; // prime the random number generator a bit lhcheeserand(seed); break; } for (shotparticles = 0;shotparticles < shootparticles;shotparticles++) { VectorCopy(baseshotcolor, shotcolor); VectorCopy(rtlight->shadoworigin, clipstart); switch (settings.stablerandom) { default: case 0: VectorRandom(clipend); if (settings.bounceanglediffuse) { // we want random to be stable, so we still have to do all the random we would have done for (bouncecount = 0; bouncecount < maxbounce; bouncecount++) VectorRandom(bouncerandom[bouncecount]); } break; case -1: case 1: VectorLehmerRandom(&randomseed, clipend); if (settings.bounceanglediffuse) { // we want random to be stable, so we still have to do all the random we would have done for (bouncecount = 0; bouncecount < maxbounce; bouncecount++) VectorLehmerRandom(&randomseed, bouncerandom[bouncecount]); } break; case -2: case 2: VectorCheeseRandom(seed, clipend); if (settings.bounceanglediffuse) { // we want random to be stable, so we still have to do all the random we would have done for (bouncecount = 0; bouncecount < maxbounce; bouncecount++) VectorCheeseRandom(seed, bouncerandom[bouncecount]); } break; } VectorMA(clipstart, radius, clipend, clipend); for (bouncecount = 0;;bouncecount++) { r_refdef.stats[r_stat_bouncegrid_traces]++; //r_refdef.scene.worldmodel->TraceLineAgainstSurfaces(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace, clipstart, clipend, hitsupercontentsmask); //r_refdef.scene.worldmodel->TraceLine(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace2, clipstart, clipend, hitsupercontentsmask); if (settings.staticmode || settings.stablerandom <= 0) { // static mode fires a LOT of rays but none of them are identical, so they are not cached // non-stable random in dynamic mode also never reuses a direction, so there's no reason to cache it cliptrace = CL_TraceLine(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), NULL, hitsupercontentsmask, skipsupercontentsmask, collision_extendmovelength.value, true, false, NULL, true, true); } else { // dynamic mode fires many rays and most will match the cache from the previous frame cliptrace = CL_Cache_TraceLineSurfaces(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), hitsupercontentsmask, skipsupercontentsmask); } if (bouncecount > 0 || settings.includedirectlighting) { vec3_t hitpos; VectorCopy(cliptrace.endpos, hitpos); R_Shadow_BounceGrid_AddSplatPath(clipstart, hitpos, shotcolor); } if (cliptrace.fraction >= 1.0f) break; r_refdef.stats[r_stat_bouncegrid_hits]++; if (bouncecount >= maxbounce) break; // scale down shot color by bounce intensity and texture color (or 50% if no texture reported) // also clamp the resulting color to never add energy, even if the user requests extreme values if (cliptrace.hittexture && cliptrace.hittexture->currentskinframe) VectorCopy(cliptrace.hittexture->currentskinframe->avgcolor, surfcolor); else VectorSet(surfcolor, 0.5f, 0.5f, 0.5f); VectorScale(surfcolor, settings.particlebounceintensity, surfcolor); surfcolor[0] = min(surfcolor[0], 1.0f); surfcolor[1] = min(surfcolor[1], 1.0f); surfcolor[2] = min(surfcolor[2], 1.0f); VectorMultiply(shotcolor, surfcolor, shotcolor); if (VectorLength2(baseshotcolor) == 0.0f) break; r_refdef.stats[r_stat_bouncegrid_bounces]++; if (settings.bounceanglediffuse) { // random direction, primarily along plane normal s = VectorDistance(cliptrace.endpos, clipend); VectorMA(cliptrace.plane.normal, 0.95f, bouncerandom[bouncecount], clipend); VectorNormalize(clipend); VectorScale(clipend, s, clipend); } else { // reflect the remaining portion of the line across plane normal VectorSubtract(clipend, cliptrace.endpos, clipdiff); VectorReflect(clipdiff, 1.0, cliptrace.plane.normal, clipend); } // calculate the new line start and end VectorCopy(cliptrace.endpos, clipstart); VectorAdd(clipstart, clipend, clipend); } } } } void R_Shadow_UpdateBounceGridTexture(void) { int flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; r_shadow_bouncegrid_settings_t settings; qboolean enable = false; qboolean settingschanged; unsigned int range; // number of world lights unsigned int range1; // number of dynamic lights (or zero if disabled) unsigned int range2; // range+range1 float photonscaling; enable = R_Shadow_BounceGrid_CheckEnable(flag); R_Shadow_BounceGrid_GenerateSettings(&settings); // changing intensity does not require an update r_shadow_bouncegrid_state.intensity = r_shadow_bouncegrid_intensity.value; settingschanged = memcmp(&r_shadow_bouncegrid_state.settings, &settings, sizeof(settings)) != 0; // when settings change, we free everything as it is just simpler that way. if (settingschanged || !enable) { // not enabled, make sure we free anything we don't need anymore. if (r_shadow_bouncegrid_state.texture) { R_FreeTexture(r_shadow_bouncegrid_state.texture); r_shadow_bouncegrid_state.texture = NULL; } r_shadow_bouncegrid_state.numpixels = 0; r_shadow_bouncegrid_state.directional = false; if (!enable) return; } // if all the settings seem identical to the previous update, return if (r_shadow_bouncegrid_state.texture && (settings.staticmode || realtime < r_shadow_bouncegrid_state.lastupdatetime + r_shadow_bouncegrid_dynamic_updateinterval.value) && !settingschanged) return; // store the new settings r_shadow_bouncegrid_state.settings = settings; R_Shadow_BounceGrid_UpdateSpacing(); // get the range of light numbers we'll be looping over: // range = static lights // range1 = dynamic lights (optional) // range2 = range + range1 range = (unsigned int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked range1 = settings.staticmode ? 0 : r_refdef.scene.numlights; range2 = range + range1; // calculate weighting factors for distributing photons among the lights R_Shadow_BounceGrid_AssignPhotons(&settings, range, range1, range2, flag, &photonscaling); // trace the photons from lights and accumulate illumination R_Shadow_BounceGrid_TracePhotons(settings, range, range1, range2, photonscaling, flag); // clear the texture R_Shadow_BounceGrid_ClearPixels(); // accumulate the light splatting into texture R_Shadow_BounceGrid_PerformSplats(); // apply a mild blur filter to the texture R_Shadow_BounceGrid_BlurPixels(); // convert the pixels to lower precision and upload the texture R_Shadow_BounceGrid_ConvertPixelsAndUpload(); } void R_Shadow_RenderMode_VisibleShadowVolumes(void) { R_Shadow_RenderMode_Reset(); GL_BlendFunc(GL_ONE, GL_ONE); GL_DepthRange(0, 1); GL_DepthTest(r_showshadowvolumes.integer < 2); GL_Color(0.0, 0.0125 * r_refdef.view.colorscale, 0.1 * r_refdef.view.colorscale, 1); GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR GL_CullFace(GL_NONE); r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLEVOLUMES; } void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent) { R_Shadow_RenderMode_Reset(); GL_BlendFunc(GL_ONE, GL_ONE); GL_DepthRange(0, 1); GL_DepthTest(r_showlighting.integer < 2); GL_Color(0.1 * r_refdef.view.colorscale, 0.0125 * r_refdef.view.colorscale, 0, 1); if (!transparent) GL_DepthFunc(GL_EQUAL); R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255); r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLELIGHTING; } void R_Shadow_RenderMode_End(void) { R_Shadow_RenderMode_Reset(); R_Shadow_RenderMode_ActiveLight(NULL); GL_DepthMask(true); GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE; } int bboxedges[12][2] = { // top {0, 1}, // +X {0, 2}, // +Y {1, 3}, // Y, +X {2, 3}, // X, +Y // bottom {4, 5}, // +X {4, 6}, // +Y {5, 7}, // Y, +X {6, 7}, // X, +Y // verticals {0, 4}, // +Z {1, 5}, // X, +Z {2, 6}, // Y, +Z {3, 7}, // XY, +Z }; qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs) { if (!r_shadow_scissor.integer || r_shadow_usingdeferredprepass || r_trippy.integer) { r_shadow_lightscissor[0] = r_refdef.view.viewport.x; r_shadow_lightscissor[1] = r_refdef.view.viewport.y; r_shadow_lightscissor[2] = r_refdef.view.viewport.width; r_shadow_lightscissor[3] = r_refdef.view.viewport.height; return false; } if(R_ScissorForBBox(mins, maxs, r_shadow_lightscissor)) return true; // invisible if(r_shadow_lightscissor[0] != r_refdef.view.viewport.x || r_shadow_lightscissor[1] != r_refdef.view.viewport.y || r_shadow_lightscissor[2] != r_refdef.view.viewport.width || r_shadow_lightscissor[3] != r_refdef.view.viewport.height) r_refdef.stats[r_stat_lights_scissored]++; return false; } static void R_Shadow_RenderLighting_Light_Vertex_Shading(int firstvertex, int numverts, const float *diffusecolor, const float *ambientcolor) { int i; const float *vertex3f; const float *normal3f; float *color4f; float dist, dot, distintensity, shadeintensity, v[3], n[3]; switch (r_shadow_rendermode) { case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: if (VectorLength2(diffusecolor) > 0) { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) { Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); if ((dot = DotProduct(n, v)) < 0) { shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); VectorMA(ambientcolor, shadeintensity, diffusecolor, color4f); } else VectorCopy(ambientcolor, color4f); if (r_refdef.fogenabled) { float f; f = RSurf_FogVertex(vertex3f); VectorScale(color4f, f, color4f); } color4f[3] = 1; } } else { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) { VectorCopy(ambientcolor, color4f); if (r_refdef.fogenabled) { float f; Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); f = RSurf_FogVertex(vertex3f); VectorScale(color4f + 4*i, f, color4f); } color4f[3] = 1; } } break; case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: if (VectorLength2(diffusecolor) > 0) { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) { Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) { Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); if ((dot = DotProduct(n, v)) < 0) { shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; } else { color4f[0] = ambientcolor[0] * distintensity; color4f[1] = ambientcolor[1] * distintensity; color4f[2] = ambientcolor[2] * distintensity; } if (r_refdef.fogenabled) { float f; f = RSurf_FogVertex(vertex3f); VectorScale(color4f, f, color4f); } } else VectorClear(color4f); color4f[3] = 1; } } else { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) { Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); if ((dist = fabs(v[2])) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) { color4f[0] = ambientcolor[0] * distintensity; color4f[1] = ambientcolor[1] * distintensity; color4f[2] = ambientcolor[2] * distintensity; if (r_refdef.fogenabled) { float f; f = RSurf_FogVertex(vertex3f); VectorScale(color4f, f, color4f); } } else VectorClear(color4f); color4f[3] = 1; } } break; case R_SHADOW_RENDERMODE_LIGHT_VERTEX: if (VectorLength2(diffusecolor) > 0) { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4) { Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) { distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n); if ((dot = DotProduct(n, v)) < 0) { shadeintensity = -dot / sqrt(VectorLength2(v) * VectorLength2(n)); color4f[0] = (ambientcolor[0] + shadeintensity * diffusecolor[0]) * distintensity; color4f[1] = (ambientcolor[1] + shadeintensity * diffusecolor[1]) * distintensity; color4f[2] = (ambientcolor[2] + shadeintensity * diffusecolor[2]) * distintensity; } else { color4f[0] = ambientcolor[0] * distintensity; color4f[1] = ambientcolor[1] * distintensity; color4f[2] = ambientcolor[2] * distintensity; } if (r_refdef.fogenabled) { float f; f = RSurf_FogVertex(vertex3f); VectorScale(color4f, f, color4f); } } else VectorClear(color4f); color4f[3] = 1; } } else { for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, color4f += 4) { Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v); if ((dist = VectorLength(v)) < 1 && (distintensity = r_shadow_attentable[(int)(dist * ATTENTABLESIZE)])) { distintensity = (1 - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist); color4f[0] = ambientcolor[0] * distintensity; color4f[1] = ambientcolor[1] * distintensity; color4f[2] = ambientcolor[2] * distintensity; if (r_refdef.fogenabled) { float f; f = RSurf_FogVertex(vertex3f); VectorScale(color4f, f, color4f); } } else VectorClear(color4f); color4f[3] = 1; } } break; default: break; } } static void R_Shadow_RenderLighting_VisibleLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) { // used to display how many times a surface is lit for level design purposes RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); R_Mesh_PrepareVertices_Generic_Arrays(rsurface.batchnumvertices, rsurface.batchvertex3f, NULL, NULL); RSurf_DrawBatch(); } static void R_Shadow_RenderLighting_Light_GLSL(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale, float specularscale) { // ARB2 GLSL shader path (GFFX5200, Radeon 9500) R_SetupShader_Surface(lightcolor, false, ambientscale, diffusescale, specularscale, RSURFPASS_RTLIGHT, texturenumsurfaces, texturesurfacelist, NULL, false); RSurf_DrawBatch(); } static void R_Shadow_RenderLighting_Light_Vertex_Pass(int firstvertex, int numvertices, int numtriangles, const int *element3i, vec3_t diffusecolor2, vec3_t ambientcolor2) { int renders; int i; int stop; int newfirstvertex; int newlastvertex; int newnumtriangles; int *newe; const int *e; float *c; int maxtriangles = 1024; int newelements[1024*3]; R_Shadow_RenderLighting_Light_Vertex_Shading(firstvertex, numvertices, diffusecolor2, ambientcolor2); for (renders = 0;renders < 4;renders++) { stop = true; newfirstvertex = 0; newlastvertex = 0; newnumtriangles = 0; newe = newelements; // due to low fillrate on the cards this vertex lighting path is // designed for, we manually cull all triangles that do not // contain a lit vertex // this builds batches of triangles from multiple surfaces and // renders them at once for (i = 0, e = element3i;i < numtriangles;i++, e += 3) { if (VectorLength2(rsurface.passcolor4f + e[0] * 4) + VectorLength2(rsurface.passcolor4f + e[1] * 4) + VectorLength2(rsurface.passcolor4f + e[2] * 4) >= 0.01) { if (newnumtriangles) { newfirstvertex = min(newfirstvertex, e[0]); newlastvertex = max(newlastvertex, e[0]); } else { newfirstvertex = e[0]; newlastvertex = e[0]; } newfirstvertex = min(newfirstvertex, e[1]); newlastvertex = max(newlastvertex, e[1]); newfirstvertex = min(newfirstvertex, e[2]); newlastvertex = max(newlastvertex, e[2]); newe[0] = e[0]; newe[1] = e[1]; newe[2] = e[2]; newnumtriangles++; newe += 3; if (newnumtriangles >= maxtriangles) { R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); newnumtriangles = 0; newe = newelements; stop = false; } } } if (newnumtriangles >= 1) { R_Mesh_Draw(newfirstvertex, newlastvertex - newfirstvertex + 1, 0, newnumtriangles, newelements, NULL, 0, NULL, NULL, 0); stop = false; } // if we couldn't find any lit triangles, exit early if (stop) break; // now reduce the intensity for the next overbright pass // we have to clamp to 0 here incase the drivers have improper // handling of negative colors // (some old drivers even have improper handling of >1 color) stop = true; for (i = 0, c = rsurface.passcolor4f + 4 * firstvertex;i < numvertices;i++, c += 4) { if (c[0] > 1 || c[1] > 1 || c[2] > 1) { c[0] = max(0, c[0] - 1); c[1] = max(0, c[1] - 1); c[2] = max(0, c[2] - 1); stop = false; } else VectorClear(c); } // another check... if (stop) break; } } static void R_Shadow_RenderLighting_Light_Vertex(int texturenumsurfaces, const msurface_t **texturesurfacelist, const vec3_t lightcolor, float ambientscale, float diffusescale) { // OpenGL 1.1 path (anything) float ambientcolorbase[3], diffusecolorbase[3]; float ambientcolorpants[3], diffusecolorpants[3]; float ambientcolorshirt[3], diffusecolorshirt[3]; const float *surfacecolor = rsurface.texture->dlightcolor; const float *surfacepants = rsurface.colormap_pantscolor; const float *surfaceshirt = rsurface.colormap_shirtcolor; rtexture_t *basetexture = rsurface.texture->basetexture; rtexture_t *pantstexture = rsurface.texture->pantstexture; rtexture_t *shirttexture = rsurface.texture->shirttexture; qboolean dopants = pantstexture && VectorLength2(surfacepants) >= (1.0f / 1048576.0f); qboolean doshirt = shirttexture && VectorLength2(surfaceshirt) >= (1.0f / 1048576.0f); ambientscale *= 2 * r_refdef.view.colorscale; diffusescale *= 2 * r_refdef.view.colorscale; ambientcolorbase[0] = lightcolor[0] * ambientscale * surfacecolor[0];ambientcolorbase[1] = lightcolor[1] * ambientscale * surfacecolor[1];ambientcolorbase[2] = lightcolor[2] * ambientscale * surfacecolor[2]; diffusecolorbase[0] = lightcolor[0] * diffusescale * surfacecolor[0];diffusecolorbase[1] = lightcolor[1] * diffusescale * surfacecolor[1];diffusecolorbase[2] = lightcolor[2] * diffusescale * surfacecolor[2]; ambientcolorpants[0] = ambientcolorbase[0] * surfacepants[0];ambientcolorpants[1] = ambientcolorbase[1] * surfacepants[1];ambientcolorpants[2] = ambientcolorbase[2] * surfacepants[2]; diffusecolorpants[0] = diffusecolorbase[0] * surfacepants[0];diffusecolorpants[1] = diffusecolorbase[1] * surfacepants[1];diffusecolorpants[2] = diffusecolorbase[2] * surfacepants[2]; ambientcolorshirt[0] = ambientcolorbase[0] * surfaceshirt[0];ambientcolorshirt[1] = ambientcolorbase[1] * surfaceshirt[1];ambientcolorshirt[2] = ambientcolorbase[2] * surfaceshirt[2]; diffusecolorshirt[0] = diffusecolorbase[0] * surfaceshirt[0];diffusecolorshirt[1] = diffusecolorbase[1] * surfaceshirt[1];diffusecolorshirt[2] = diffusecolorbase[2] * surfaceshirt[2]; RSurf_PrepareVerticesForBatch(BATCHNEED_ARRAY_VERTEX | (diffusescale > 0 ? BATCHNEED_ARRAY_NORMAL : 0) | BATCHNEED_ARRAY_TEXCOORD | BATCHNEED_NOGAPS, texturenumsurfaces, texturesurfacelist); rsurface.passcolor4f = (float *)R_FrameData_Alloc((rsurface.batchfirstvertex + rsurface.batchnumvertices) * sizeof(float[4])); R_Mesh_VertexPointer(3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); R_Mesh_ColorPointer(4, GL_FLOAT, sizeof(float[4]), rsurface.passcolor4f, 0, 0); R_Mesh_TexCoordPointer(0, 2, GL_FLOAT, sizeof(float[2]), rsurface.batchtexcoordtexture2f, rsurface.batchtexcoordtexture2f_vertexbuffer, rsurface.batchtexcoordtexture2f_bufferoffset); R_Mesh_TexBind(0, basetexture); R_Mesh_TexMatrix(0, &rsurface.texture->currenttexmatrix); R_Mesh_TexCombine(0, GL_MODULATE, GL_MODULATE, 1, 1); switch(r_shadow_rendermode) { case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: R_Mesh_TexBind(1, r_shadow_attenuation3dtexture); R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); break; case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: R_Mesh_TexBind(2, r_shadow_attenuation2dtexture); R_Mesh_TexMatrix(2, &rsurface.entitytoattenuationz); R_Mesh_TexCombine(2, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(2, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); // fall through case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: R_Mesh_TexBind(1, r_shadow_attenuation2dtexture); R_Mesh_TexMatrix(1, &rsurface.entitytoattenuationxyz); R_Mesh_TexCombine(1, GL_MODULATE, GL_MODULATE, 1, 1); R_Mesh_TexCoordPointer(1, 3, GL_FLOAT, sizeof(float[3]), rsurface.batchvertex3f, rsurface.batchvertex3f_vertexbuffer, rsurface.batchvertex3f_bufferoffset); break; case R_SHADOW_RENDERMODE_LIGHT_VERTEX: break; default: break; } //R_Mesh_TexBind(0, basetexture); R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorbase, ambientcolorbase); if (dopants) { R_Mesh_TexBind(0, pantstexture); R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorpants, ambientcolorpants); } if (doshirt) { R_Mesh_TexBind(0, shirttexture); R_Shadow_RenderLighting_Light_Vertex_Pass(rsurface.batchfirstvertex, rsurface.batchnumvertices, rsurface.batchnumtriangles, rsurface.batchelement3i + 3*rsurface.batchfirsttriangle, diffusecolorshirt, ambientcolorshirt); } } extern cvar_t gl_lightmaps; void R_Shadow_RenderLighting(int texturenumsurfaces, const msurface_t **texturesurfacelist) { float ambientscale, diffusescale, specularscale; qboolean negated; float lightcolor[3]; VectorCopy(rsurface.rtlight->currentcolor, lightcolor); ambientscale = rsurface.rtlight->ambientscale + rsurface.texture->rtlightambient; diffusescale = rsurface.rtlight->diffusescale * max(0, 1.0 - rsurface.texture->rtlightambient); specularscale = rsurface.rtlight->specularscale * rsurface.texture->specularscale; if (!r_shadow_usenormalmap.integer) { ambientscale += 1.0f * diffusescale; diffusescale = 0; specularscale = 0; } if ((ambientscale + diffusescale) * VectorLength2(lightcolor) + specularscale * VectorLength2(lightcolor) < (1.0f / 1048576.0f)) return; negated = (lightcolor[0] + lightcolor[1] + lightcolor[2] < 0) && vid.support.ext_blend_subtract; if(negated) { VectorNegate(lightcolor, lightcolor); GL_BlendEquationSubtract(true); } RSurf_SetupDepthAndCulling(); switch (r_shadow_rendermode) { case R_SHADOW_RENDERMODE_VISIBLELIGHTING: GL_DepthTest(!(rsurface.texture->currentmaterialflags & MATERIALFLAG_NODEPTHTEST) && !r_showdisabledepthtest.integer); R_Shadow_RenderLighting_VisibleLighting(texturenumsurfaces, texturesurfacelist); break; case R_SHADOW_RENDERMODE_LIGHT_GLSL: R_Shadow_RenderLighting_Light_GLSL(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale, specularscale); break; case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN: case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN: case R_SHADOW_RENDERMODE_LIGHT_VERTEX2DATTEN: case R_SHADOW_RENDERMODE_LIGHT_VERTEX: R_Shadow_RenderLighting_Light_Vertex(texturenumsurfaces, texturesurfacelist, lightcolor, ambientscale, diffusescale); break; default: Con_Printf("R_Shadow_RenderLighting: unknown r_shadow_rendermode %i\n", r_shadow_rendermode); break; } if(negated) GL_BlendEquationSubtract(false); } void R_RTLight_Update(rtlight_t *rtlight, int isstatic, matrix4x4_t *matrix, vec3_t color, int style, const char *cubemapname, int shadow, vec_t corona, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) { matrix4x4_t tempmatrix = *matrix; Matrix4x4_Scale(&tempmatrix, r_shadow_lightradiusscale.value, 1); // if this light has been compiled before, free the associated data R_RTLight_Uncompile(rtlight); // clear it completely to avoid any lingering data memset(rtlight, 0, sizeof(*rtlight)); // copy the properties rtlight->matrix_lighttoworld = tempmatrix; Matrix4x4_Invert_Simple(&rtlight->matrix_worldtolight, &tempmatrix); Matrix4x4_OriginFromMatrix(&tempmatrix, rtlight->shadoworigin); rtlight->radius = Matrix4x4_ScaleFromMatrix(&tempmatrix); VectorCopy(color, rtlight->color); rtlight->cubemapname[0] = 0; if (cubemapname && cubemapname[0]) strlcpy(rtlight->cubemapname, cubemapname, sizeof(rtlight->cubemapname)); rtlight->shadow = shadow; rtlight->corona = corona; rtlight->style = style; rtlight->isstatic = isstatic; rtlight->coronasizescale = coronasizescale; rtlight->ambientscale = ambientscale; rtlight->diffusescale = diffusescale; rtlight->specularscale = specularscale; rtlight->flags = flags; // compute derived data //rtlight->cullradius = rtlight->radius; //rtlight->cullradius2 = rtlight->radius * rtlight->radius; rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; } // compiles rtlight geometry // (undone by R_FreeCompiledRTLight, which R_UpdateLight calls) void R_RTLight_Compile(rtlight_t *rtlight) { int i; int numsurfaces, numleafs, numleafpvsbytes, numshadowtrispvsbytes, numlighttrispvsbytes; int lighttris, shadowtris, shadowzpasstris, shadowzfailtris; entity_render_t *ent = r_refdef.scene.worldentity; dp_model_t *model = r_refdef.scene.worldmodel; unsigned char *data; shadowmesh_t *mesh; // compile the light rtlight->compiled = true; rtlight->shadowmode = rtlight->shadow ? (int)r_shadow_shadowmode : -1; rtlight->static_numleafs = 0; rtlight->static_numleafpvsbytes = 0; rtlight->static_leaflist = NULL; rtlight->static_leafpvs = NULL; rtlight->static_numsurfaces = 0; rtlight->static_surfacelist = NULL; rtlight->static_shadowmap_receivers = 0x3F; rtlight->static_shadowmap_casters = 0x3F; rtlight->cullmins[0] = rtlight->shadoworigin[0] - rtlight->radius; rtlight->cullmins[1] = rtlight->shadoworigin[1] - rtlight->radius; rtlight->cullmins[2] = rtlight->shadoworigin[2] - rtlight->radius; rtlight->cullmaxs[0] = rtlight->shadoworigin[0] + rtlight->radius; rtlight->cullmaxs[1] = rtlight->shadoworigin[1] + rtlight->radius; rtlight->cullmaxs[2] = rtlight->shadoworigin[2] + rtlight->radius; if (model && model->GetLightInfo) { // this variable must be set for the CompileShadowVolume/CompileShadowMap code r_shadow_compilingrtlight = rtlight; R_FrameData_SetMark(); model->GetLightInfo(ent, rtlight->shadoworigin, rtlight->radius, rtlight->cullmins, rtlight->cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, 0, NULL); R_FrameData_ReturnToMark(); numleafpvsbytes = (model->brush.num_leafs + 7) >> 3; numshadowtrispvsbytes = ((model->brush.shadowmesh ? model->brush.shadowmesh->numtriangles : model->surfmesh.num_triangles) + 7) >> 3; numlighttrispvsbytes = (model->surfmesh.num_triangles + 7) >> 3; data = (unsigned char *)Mem_Alloc(r_main_mempool, sizeof(int) * numsurfaces + sizeof(int) * numleafs + numleafpvsbytes + numshadowtrispvsbytes + numlighttrispvsbytes); rtlight->static_numsurfaces = numsurfaces; rtlight->static_surfacelist = (int *)data;data += sizeof(int) * numsurfaces; rtlight->static_numleafs = numleafs; rtlight->static_leaflist = (int *)data;data += sizeof(int) * numleafs; rtlight->static_numleafpvsbytes = numleafpvsbytes; rtlight->static_leafpvs = (unsigned char *)data;data += numleafpvsbytes; rtlight->static_numshadowtrispvsbytes = numshadowtrispvsbytes; rtlight->static_shadowtrispvs = (unsigned char *)data;data += numshadowtrispvsbytes; rtlight->static_numlighttrispvsbytes = numlighttrispvsbytes; rtlight->static_lighttrispvs = (unsigned char *)data;data += numlighttrispvsbytes; if (rtlight->static_numsurfaces) memcpy(rtlight->static_surfacelist, r_shadow_buffer_surfacelist, rtlight->static_numsurfaces * sizeof(*rtlight->static_surfacelist)); if (rtlight->static_numleafs) memcpy(rtlight->static_leaflist, r_shadow_buffer_leaflist, rtlight->static_numleafs * sizeof(*rtlight->static_leaflist)); if (rtlight->static_numleafpvsbytes) memcpy(rtlight->static_leafpvs, r_shadow_buffer_leafpvs, rtlight->static_numleafpvsbytes); if (rtlight->static_numshadowtrispvsbytes) memcpy(rtlight->static_shadowtrispvs, r_shadow_buffer_shadowtrispvs, rtlight->static_numshadowtrispvsbytes); if (rtlight->static_numlighttrispvsbytes) memcpy(rtlight->static_lighttrispvs, r_shadow_buffer_lighttrispvs, rtlight->static_numlighttrispvsbytes); R_FrameData_SetMark(); switch (rtlight->shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: if (model->CompileShadowMap && rtlight->shadow) model->CompileShadowMap(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); break; default: if (model->CompileShadowVolume && rtlight->shadow) model->CompileShadowVolume(ent, rtlight->shadoworigin, NULL, rtlight->radius, numsurfaces, r_shadow_buffer_surfacelist); break; } R_FrameData_ReturnToMark(); // now we're done compiling the rtlight r_shadow_compilingrtlight = NULL; } // use smallest available cullradius - box radius or light radius //rtlight->cullradius = RadiusFromBoundsAndOrigin(rtlight->cullmins, rtlight->cullmaxs, rtlight->shadoworigin); //rtlight->cullradius = min(rtlight->cullradius, rtlight->radius); shadowzpasstris = 0; if (rtlight->static_meshchain_shadow_zpass) for (mesh = rtlight->static_meshchain_shadow_zpass;mesh;mesh = mesh->next) shadowzpasstris += mesh->numtriangles; shadowzfailtris = 0; if (rtlight->static_meshchain_shadow_zfail) for (mesh = rtlight->static_meshchain_shadow_zfail;mesh;mesh = mesh->next) shadowzfailtris += mesh->numtriangles; lighttris = 0; if (rtlight->static_numlighttrispvsbytes) for (i = 0;i < rtlight->static_numlighttrispvsbytes*8;i++) if (CHECKPVSBIT(rtlight->static_lighttrispvs, i)) lighttris++; shadowtris = 0; if (rtlight->static_numshadowtrispvsbytes) for (i = 0;i < rtlight->static_numshadowtrispvsbytes*8;i++) if (CHECKPVSBIT(rtlight->static_shadowtrispvs, i)) shadowtris++; if (developer_extra.integer) Con_DPrintf("static light built: %f %f %f : %f %f %f box, %i light triangles, %i shadow triangles, %i zpass/%i zfail compiled shadow volume triangles\n", rtlight->cullmins[0], rtlight->cullmins[1], rtlight->cullmins[2], rtlight->cullmaxs[0], rtlight->cullmaxs[1], rtlight->cullmaxs[2], lighttris, shadowtris, shadowzpasstris, shadowzfailtris); } void R_RTLight_Uncompile(rtlight_t *rtlight) { if (rtlight->compiled) { if (rtlight->static_meshchain_shadow_zpass) Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zpass); rtlight->static_meshchain_shadow_zpass = NULL; if (rtlight->static_meshchain_shadow_zfail) Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_zfail); rtlight->static_meshchain_shadow_zfail = NULL; if (rtlight->static_meshchain_shadow_shadowmap) Mod_ShadowMesh_Free(rtlight->static_meshchain_shadow_shadowmap); rtlight->static_meshchain_shadow_shadowmap = NULL; // these allocations are grouped if (rtlight->static_surfacelist) Mem_Free(rtlight->static_surfacelist); rtlight->static_numleafs = 0; rtlight->static_numleafpvsbytes = 0; rtlight->static_leaflist = NULL; rtlight->static_leafpvs = NULL; rtlight->static_numsurfaces = 0; rtlight->static_surfacelist = NULL; rtlight->static_numshadowtrispvsbytes = 0; rtlight->static_shadowtrispvs = NULL; rtlight->static_numlighttrispvsbytes = 0; rtlight->static_lighttrispvs = NULL; rtlight->compiled = false; } } void R_Shadow_UncompileWorldLights(void) { size_t lightindex; dlight_t *light; size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; R_RTLight_Uncompile(&light->rtlight); } } static void R_Shadow_ComputeShadowCasterCullingPlanes(rtlight_t *rtlight) { int i, j; mplane_t plane; // reset the count of frustum planes // see rtlight->cached_frustumplanes definition for how much this array // can hold rtlight->cached_numfrustumplanes = 0; if (r_trippy.integer) return; // haven't implemented a culling path for ortho rendering if (!r_refdef.view.useperspective) { // check if the light is on screen and copy the 4 planes if it is for (i = 0;i < 4;i++) if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) break; if (i == 4) for (i = 0;i < 4;i++) rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; return; } #if 1 // generate a deformed frustum that includes the light origin, this is // used to cull shadow casting surfaces that can not possibly cast a // shadow onto the visible light-receiving surfaces, which can be a // performance gain // // if the light origin is onscreen the result will be 4 planes exactly // if the light origin is offscreen on only one axis the result will // be exactly 5 planes (split-side case) // if the light origin is offscreen on two axes the result will be // exactly 4 planes (stretched corner case) for (i = 0;i < 4;i++) { // quickly reject standard frustum planes that put the light // origin outside the frustum if (PlaneDiff(rtlight->shadoworigin, &r_refdef.view.frustum[i]) < -0.03125) continue; // copy the plane rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = r_refdef.view.frustum[i]; } // if all the standard frustum planes were accepted, the light is onscreen // otherwise we need to generate some more planes below... if (rtlight->cached_numfrustumplanes < 4) { // at least one of the stock frustum planes failed, so we need to // create one or two custom planes to enclose the light origin for (i = 0;i < 4;i++) { // create a plane using the view origin and light origin, and a // single point from the frustum corner set TriangleNormal(r_refdef.view.origin, r_refdef.view.frustumcorner[i], rtlight->shadoworigin, plane.normal); VectorNormalize(plane.normal); plane.dist = DotProduct(r_refdef.view.origin, plane.normal); // see if this plane is backwards and flip it if so for (j = 0;j < 4;j++) if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) break; if (j < 4) { VectorNegate(plane.normal, plane.normal); plane.dist *= -1; // flipped plane, test again to see if it is now valid for (j = 0;j < 4;j++) if (j != i && DotProduct(r_refdef.view.frustumcorner[j], plane.normal) - plane.dist < -0.03125) break; // if the plane is still not valid, then it is dividing the // frustum and has to be rejected if (j < 4) continue; } // we have created a valid plane, compute extra info PlaneClassify(&plane); // copy the plane rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; #if 1 // if we've found 5 frustum planes then we have constructed a // proper split-side case and do not need to keep searching for // planes to enclose the light origin if (rtlight->cached_numfrustumplanes == 5) break; #endif } } #endif #if 0 for (i = 0;i < rtlight->cached_numfrustumplanes;i++) { plane = rtlight->cached_frustumplanes[i]; Con_Printf("light %p plane #%i %f %f %f : %f (%f %f %f %f %f)\n", rtlight, i, plane.normal[0], plane.normal[1], plane.normal[2], plane.dist, PlaneDiff(r_refdef.view.frustumcorner[0], &plane), PlaneDiff(r_refdef.view.frustumcorner[1], &plane), PlaneDiff(r_refdef.view.frustumcorner[2], &plane), PlaneDiff(r_refdef.view.frustumcorner[3], &plane), PlaneDiff(rtlight->shadoworigin, &plane)); } #endif #if 0 // now add the light-space box planes if the light box is rotated, as any // caster outside the oriented light box is irrelevant (even if it passed // the worldspace light box, which is axial) if (rtlight->matrix_lighttoworld.m[0][0] != 1 || rtlight->matrix_lighttoworld.m[1][1] != 1 || rtlight->matrix_lighttoworld.m[2][2] != 1) { for (i = 0;i < 6;i++) { vec3_t v; VectorClear(v); v[i >> 1] = (i & 1) ? -1 : 1; Matrix4x4_Transform(&rtlight->matrix_lighttoworld, v, plane.normal); VectorSubtract(plane.normal, rtlight->shadoworigin, plane.normal); plane.dist = VectorNormalizeLength(plane.normal); plane.dist += DotProduct(plane.normal, rtlight->shadoworigin); rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; } } #endif #if 0 // add the world-space reduced box planes for (i = 0;i < 6;i++) { VectorClear(plane.normal); plane.normal[i >> 1] = (i & 1) ? -1 : 1; plane.dist = (i & 1) ? -rtlight->cached_cullmaxs[i >> 1] : rtlight->cached_cullmins[i >> 1]; rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = plane; } #endif #if 0 { int j, oldnum; vec3_t points[8]; vec_t bestdist; // reduce all plane distances to tightly fit the rtlight cull box, which // is in worldspace VectorSet(points[0], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); VectorSet(points[1], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmins[2]); VectorSet(points[2], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); VectorSet(points[3], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmins[2]); VectorSet(points[4], rtlight->cached_cullmins[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); VectorSet(points[5], rtlight->cached_cullmaxs[0], rtlight->cached_cullmins[1], rtlight->cached_cullmaxs[2]); VectorSet(points[6], rtlight->cached_cullmins[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); VectorSet(points[7], rtlight->cached_cullmaxs[0], rtlight->cached_cullmaxs[1], rtlight->cached_cullmaxs[2]); oldnum = rtlight->cached_numfrustumplanes; rtlight->cached_numfrustumplanes = 0; for (j = 0;j < oldnum;j++) { // find the nearest point on the box to this plane bestdist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[0]); for (i = 1;i < 8;i++) { dist = DotProduct(rtlight->cached_frustumplanes[j].normal, points[i]); if (bestdist > dist) bestdist = dist; } Con_Printf("light %p %splane #%i %f %f %f : %f < %f\n", rtlight, rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125 ? "^2" : "^1", j, rtlight->cached_frustumplanes[j].normal[0], rtlight->cached_frustumplanes[j].normal[1], rtlight->cached_frustumplanes[j].normal[2], rtlight->cached_frustumplanes[j].dist, bestdist); // if the nearest point is near or behind the plane, we want this // plane, otherwise the plane is useless as it won't cull anything if (rtlight->cached_frustumplanes[j].dist < bestdist + 0.03125) { PlaneClassify(&rtlight->cached_frustumplanes[j]); rtlight->cached_frustumplanes[rtlight->cached_numfrustumplanes++] = rtlight->cached_frustumplanes[j]; } } } #endif } static void R_Shadow_DrawWorldShadow_ShadowMap(int numsurfaces, int *surfacelist, const unsigned char *trispvs, const unsigned char *surfacesides) { shadowmesh_t *mesh; RSurf_ActiveWorldEntity(); if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) { CHECKGLERROR GL_CullFace(GL_NONE); mesh = rsurface.rtlight->static_meshchain_shadow_shadowmap; for (;mesh;mesh = mesh->next) { if (!mesh->sidetotals[r_shadow_shadowmapside]) continue; r_refdef.stats[r_stat_lights_shadowtriangles] += mesh->sidetotals[r_shadow_shadowmapside]; R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer, mesh->vbooffset_vertex3f); R_Mesh_Draw(0, mesh->numverts, mesh->sideoffsets[r_shadow_shadowmapside], mesh->sidetotals[r_shadow_shadowmapside], mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); } CHECKGLERROR } else if (r_refdef.scene.worldentity->model) r_refdef.scene.worldmodel->DrawShadowMap(r_shadow_shadowmapside, r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, surfacesides, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } static void R_Shadow_DrawWorldShadow_ShadowVolume(int numsurfaces, int *surfacelist, const unsigned char *trispvs) { qboolean zpass = false; shadowmesh_t *mesh; int t, tend; int surfacelistindex; msurface_t *surface; // if triangle neighbors are disabled, shadowvolumes are disabled if (r_refdef.scene.worldmodel->brush.shadowmesh ? !r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i : !r_refdef.scene.worldmodel->surfmesh.data_neighbor3i) return; RSurf_ActiveWorldEntity(); if (rsurface.rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) { CHECKGLERROR if (r_shadow_rendermode != R_SHADOW_RENDERMODE_VISIBLEVOLUMES) { zpass = R_Shadow_UseZPass(r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); R_Shadow_RenderMode_StencilShadowVolumes(zpass); } mesh = zpass ? rsurface.rtlight->static_meshchain_shadow_zpass : rsurface.rtlight->static_meshchain_shadow_zfail; for (;mesh;mesh = mesh->next) { r_refdef.stats[r_stat_lights_shadowtriangles] += mesh->numtriangles; R_Mesh_PrepareVertices_Vertex3f(mesh->numverts, mesh->vertex3f, mesh->vbo_vertexbuffer, mesh->vbooffset_vertex3f); if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZPASS_STENCIL) { // increment stencil if frontface is infront of depthbuffer GL_CullFace(r_refdef.view.cullface_back); R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_INCR, GL_ALWAYS, 128, 255); R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); // decrement stencil if backface is infront of depthbuffer GL_CullFace(r_refdef.view.cullface_front); R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_DECR, GL_ALWAYS, 128, 255); } else if (r_shadow_rendermode == R_SHADOW_RENDERMODE_ZFAIL_STENCIL) { // decrement stencil if backface is behind depthbuffer GL_CullFace(r_refdef.view.cullface_front); R_SetStencil(true, 255, GL_KEEP, GL_DECR, GL_KEEP, GL_ALWAYS, 128, 255); R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); // increment stencil if frontface is behind depthbuffer GL_CullFace(r_refdef.view.cullface_back); R_SetStencil(true, 255, GL_KEEP, GL_INCR, GL_KEEP, GL_ALWAYS, 128, 255); } R_Mesh_Draw(0, mesh->numverts, 0, mesh->numtriangles, mesh->element3i, mesh->element3i_indexbuffer, mesh->element3i_bufferoffset, mesh->element3s, mesh->element3s_indexbuffer, mesh->element3s_bufferoffset); } CHECKGLERROR } else if (numsurfaces && r_refdef.scene.worldmodel->brush.shadowmesh) { // use the shadow trispvs calculated earlier by GetLightInfo to cull world triangles on this dynamic light R_Shadow_PrepareShadowMark(r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles); for (surfacelistindex = 0;surfacelistindex < numsurfaces;surfacelistindex++) { surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[surfacelistindex]; for (t = surface->num_firstshadowmeshtriangle, tend = t + surface->num_triangles;t < tend;t++) if (CHECKPVSBIT(trispvs, t)) shadowmarklist[numshadowmark++] = t; } R_Shadow_VolumeFromList(r_refdef.scene.worldmodel->brush.shadowmesh->numverts, r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles, r_refdef.scene.worldmodel->brush.shadowmesh->vertex3f, r_refdef.scene.worldmodel->brush.shadowmesh->element3i, r_refdef.scene.worldmodel->brush.shadowmesh->neighbor3i, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius + r_refdef.scene.worldmodel->radius*2 + r_shadow_projectdistance.value, numshadowmark, shadowmarklist, r_refdef.scene.worldmodel->normalmins, r_refdef.scene.worldmodel->normalmaxs); } else if (numsurfaces) { r_refdef.scene.worldmodel->DrawShadowVolume(r_refdef.scene.worldentity, rsurface.rtlight->shadoworigin, NULL, rsurface.rtlight->radius, numsurfaces, surfacelist, rsurface.rtlight->cached_cullmins, rsurface.rtlight->cached_cullmaxs); } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } static void R_Shadow_DrawEntityShadow(entity_render_t *ent) { vec3_t relativeshadoworigin, relativeshadowmins, relativeshadowmaxs; vec_t relativeshadowradius; RSurf_ActiveModelEntity(ent, false, false, false); Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, relativeshadoworigin); // we need to re-init the shader for each entity because the matrix changed relativeshadowradius = rsurface.rtlight->radius / ent->scale; relativeshadowmins[0] = relativeshadoworigin[0] - relativeshadowradius; relativeshadowmins[1] = relativeshadoworigin[1] - relativeshadowradius; relativeshadowmins[2] = relativeshadoworigin[2] - relativeshadowradius; relativeshadowmaxs[0] = relativeshadoworigin[0] + relativeshadowradius; relativeshadowmaxs[1] = relativeshadoworigin[1] + relativeshadowradius; relativeshadowmaxs[2] = relativeshadoworigin[2] + relativeshadowradius; switch (r_shadow_rendermode) { case R_SHADOW_RENDERMODE_SHADOWMAP2D: ent->model->DrawShadowMap(r_shadow_shadowmapside, ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); break; default: ent->model->DrawShadowVolume(ent, relativeshadoworigin, NULL, relativeshadowradius, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); break; } rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } void R_Shadow_SetupEntityLight(const entity_render_t *ent) { // set up properties for rendering light onto this entity RSurf_ActiveModelEntity(ent, true, true, false); Matrix4x4_Concat(&rsurface.entitytolight, &rsurface.rtlight->matrix_worldtolight, &ent->matrix); Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); Matrix4x4_Transform(&ent->inversematrix, rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); } static void R_Shadow_DrawWorldLight(int numsurfaces, int *surfacelist, const unsigned char *lighttrispvs) { if (!r_refdef.scene.worldmodel->DrawLight) return; // set up properties for rendering light onto this entity RSurf_ActiveWorldEntity(); rsurface.entitytolight = rsurface.rtlight->matrix_worldtolight; Matrix4x4_Concat(&rsurface.entitytoattenuationxyz, &matrix_attenuationxyz, &rsurface.entitytolight); Matrix4x4_Concat(&rsurface.entitytoattenuationz, &matrix_attenuationz, &rsurface.entitytolight); VectorCopy(rsurface.rtlight->shadoworigin, rsurface.entitylightorigin); r_refdef.scene.worldmodel->DrawLight(r_refdef.scene.worldentity, numsurfaces, surfacelist, lighttrispvs); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } static void R_Shadow_DrawEntityLight(entity_render_t *ent) { dp_model_t *model = ent->model; if (!model->DrawLight) return; R_Shadow_SetupEntityLight(ent); model->DrawLight(ent, model->nummodelsurfaces, model->sortedmodelsurfaces, NULL); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } static void R_Shadow_PrepareLight(rtlight_t *rtlight) { int i; float f; int numleafs, numsurfaces; int *leaflist, *surfacelist; unsigned char *leafpvs; unsigned char *shadowtrispvs; unsigned char *lighttrispvs; //unsigned char *surfacesides; int numlightentities; int numlightentities_noselfshadow; int numshadowentities; int numshadowentities_noselfshadow; static entity_render_t *lightentities[MAX_EDICTS]; static entity_render_t *lightentities_noselfshadow[MAX_EDICTS]; static entity_render_t *shadowentities[MAX_EDICTS]; static entity_render_t *shadowentities_noselfshadow[MAX_EDICTS]; qboolean nolight; qboolean castshadows; rtlight->draw = false; rtlight->cached_numlightentities = 0; rtlight->cached_numlightentities_noselfshadow = 0; rtlight->cached_numshadowentities = 0; rtlight->cached_numshadowentities_noselfshadow = 0; rtlight->cached_numsurfaces = 0; rtlight->cached_lightentities = NULL; rtlight->cached_lightentities_noselfshadow = NULL; rtlight->cached_shadowentities = NULL; rtlight->cached_shadowentities_noselfshadow = NULL; rtlight->cached_shadowtrispvs = NULL; rtlight->cached_lighttrispvs = NULL; rtlight->cached_surfacelist = NULL; // skip lights that don't light because of ambientscale+diffusescale+specularscale being 0 (corona only lights) // skip lights that are basically invisible (color 0 0 0) nolight = VectorLength2(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale) < (1.0f / 1048576.0f); // loading is done before visibility checks because loading should happen // all at once at the start of a level, not when it stalls gameplay. // (especially important to benchmarks) // compile light if (rtlight->isstatic && !nolight && (!rtlight->compiled || (rtlight->shadow && rtlight->shadowmode != (int)r_shadow_shadowmode)) && r_shadow_realtime_world_compile.integer) { if (rtlight->compiled) R_RTLight_Uncompile(rtlight); R_RTLight_Compile(rtlight); } // load cubemap rtlight->currentcubemap = rtlight->cubemapname[0] ? R_GetCubemap(rtlight->cubemapname) : r_texture_whitecube; // look up the light style value at this time f = ((rtlight->style >= 0 && rtlight->style < MAX_LIGHTSTYLES) ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; VectorScale(rtlight->color, f, rtlight->currentcolor); /* if (rtlight->selected) { f = 2 + sin(realtime * M_PI * 4.0); VectorScale(rtlight->currentcolor, f, rtlight->currentcolor); } */ // if lightstyle is currently off, don't draw the light if (VectorLength2(rtlight->currentcolor) < (1.0f / 1048576.0f)) return; // skip processing on corona-only lights if (nolight) return; // if the light box is offscreen, skip it if (R_CullBox(rtlight->cullmins, rtlight->cullmaxs)) return; VectorCopy(rtlight->cullmins, rtlight->cached_cullmins); VectorCopy(rtlight->cullmaxs, rtlight->cached_cullmaxs); R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); // don't allow lights to be drawn if using r_shadow_bouncegrid 2, except if we're using static bouncegrid where dynamic lights still need to draw if (r_shadow_bouncegrid.integer == 2 && (rtlight->isstatic || !r_shadow_bouncegrid_static.integer)) return; if (rtlight->compiled && r_shadow_realtime_world_compile.integer) { // compiled light, world available and can receive realtime lighting // retrieve leaf information numleafs = rtlight->static_numleafs; leaflist = rtlight->static_leaflist; leafpvs = rtlight->static_leafpvs; numsurfaces = rtlight->static_numsurfaces; surfacelist = rtlight->static_surfacelist; //surfacesides = NULL; shadowtrispvs = rtlight->static_shadowtrispvs; lighttrispvs = rtlight->static_lighttrispvs; } else if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->GetLightInfo) { // dynamic light, world available and can receive realtime lighting // calculate lit surfaces and leafs r_refdef.scene.worldmodel->GetLightInfo(r_refdef.scene.worldentity, rtlight->shadoworigin, rtlight->radius, rtlight->cached_cullmins, rtlight->cached_cullmaxs, r_shadow_buffer_leaflist, r_shadow_buffer_leafpvs, &numleafs, r_shadow_buffer_surfacelist, r_shadow_buffer_surfacepvs, &numsurfaces, r_shadow_buffer_shadowtrispvs, r_shadow_buffer_lighttrispvs, r_shadow_buffer_visitingleafpvs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes); R_Shadow_ComputeShadowCasterCullingPlanes(rtlight); leaflist = r_shadow_buffer_leaflist; leafpvs = r_shadow_buffer_leafpvs; surfacelist = r_shadow_buffer_surfacelist; //surfacesides = r_shadow_buffer_surfacesides; shadowtrispvs = r_shadow_buffer_shadowtrispvs; lighttrispvs = r_shadow_buffer_lighttrispvs; // if the reduced leaf bounds are offscreen, skip it if (R_CullBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) return; } else { // no world numleafs = 0; leaflist = NULL; leafpvs = NULL; numsurfaces = 0; surfacelist = NULL; //surfacesides = NULL; shadowtrispvs = NULL; lighttrispvs = NULL; } // check if light is illuminating any visible leafs if (numleafs) { for (i = 0;i < numleafs;i++) if (r_refdef.viewcache.world_leafvisible[leaflist[i]]) break; if (i == numleafs) return; } // make a list of lit entities and shadow casting entities numlightentities = 0; numlightentities_noselfshadow = 0; numshadowentities = 0; numshadowentities_noselfshadow = 0; // add dynamic entities that are lit by the light for (i = 0;i < r_refdef.scene.numentities;i++) { dp_model_t *model; entity_render_t *ent = r_refdef.scene.entities[i]; vec3_t org; if (!BoxesOverlap(ent->mins, ent->maxs, rtlight->cached_cullmins, rtlight->cached_cullmaxs)) continue; // skip the object entirely if it is not within the valid // shadow-casting region (which includes the lit region) if (R_CullBoxCustomPlanes(ent->mins, ent->maxs, rtlight->cached_numfrustumplanes, rtlight->cached_frustumplanes)) continue; if (!(model = ent->model)) continue; if (r_refdef.viewcache.entityvisible[i] && model->DrawLight && (ent->flags & RENDER_LIGHT)) { // this entity wants to receive light, is visible, and is // inside the light box // TODO: check if the surfaces in the model can receive light // so now check if it's in a leaf seen by the light if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) continue; if (ent->flags & RENDER_NOSELFSHADOW) lightentities_noselfshadow[numlightentities_noselfshadow++] = ent; else lightentities[numlightentities++] = ent; // since it is lit, it probably also casts a shadow... // about the VectorDistance2 - light emitting entities should not cast their own shadow Matrix4x4_OriginFromMatrix(&ent->matrix, org); if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) { // note: exterior models without the RENDER_NOSELFSHADOW // flag still create a RENDER_NOSELFSHADOW shadow but // are lit normally, this means that they are // self-shadowing but do not shadow other // RENDER_NOSELFSHADOW entities such as the gun // (very weird, but keeps the player shadow off the gun) if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; else shadowentities[numshadowentities++] = ent; } } else if (ent->flags & RENDER_SHADOW) { // this entity is not receiving light, but may still need to // cast a shadow... // TODO: check if the surfaces in the model can cast shadow // now check if it is in a leaf seen by the light if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS && !r_refdef.scene.worldmodel->brush.BoxTouchingLeafPVS(r_refdef.scene.worldmodel, leafpvs, ent->mins, ent->maxs)) continue; // about the VectorDistance2 - light emitting entities should not cast their own shadow Matrix4x4_OriginFromMatrix(&ent->matrix, org); if ((ent->flags & RENDER_SHADOW) && model->DrawShadowVolume && VectorDistance2(org, rtlight->shadoworigin) > 0.1) { if (ent->flags & (RENDER_NOSELFSHADOW | RENDER_EXTERIORMODEL)) shadowentities_noselfshadow[numshadowentities_noselfshadow++] = ent; else shadowentities[numshadowentities++] = ent; } } } // return if there's nothing at all to light if (numsurfaces + numlightentities + numlightentities_noselfshadow == 0) return; // count this light in the r_speeds r_refdef.stats[r_stat_lights]++; // flag it as worth drawing later rtlight->draw = true; // if we have shadows disabled, don't count the shadow entities, this way we don't do the R_AnimCache_GetEntity on each one castshadows = numsurfaces + numshadowentities + numshadowentities_noselfshadow > 0 && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows); if (!castshadows) numshadowentities = numshadowentities_noselfshadow = 0; // cache all the animated entities that cast a shadow but are not visible for (i = 0;i < numshadowentities;i++) R_AnimCache_GetEntity(shadowentities[i], false, false); for (i = 0;i < numshadowentities_noselfshadow;i++) R_AnimCache_GetEntity(shadowentities_noselfshadow[i], false, false); // allocate some temporary memory for rendering this light later in the frame // reusable buffers need to be copied, static data can be used as-is rtlight->cached_numlightentities = numlightentities; rtlight->cached_numlightentities_noselfshadow = numlightentities_noselfshadow; rtlight->cached_numshadowentities = numshadowentities; rtlight->cached_numshadowentities_noselfshadow = numshadowentities_noselfshadow; rtlight->cached_numsurfaces = numsurfaces; rtlight->cached_lightentities = (entity_render_t**)R_FrameData_Store(numlightentities*sizeof(entity_render_t*), (void*)lightentities); rtlight->cached_lightentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numlightentities_noselfshadow*sizeof(entity_render_t*), (void*)lightentities_noselfshadow); rtlight->cached_shadowentities = (entity_render_t**)R_FrameData_Store(numshadowentities*sizeof(entity_render_t*), (void*)shadowentities); rtlight->cached_shadowentities_noselfshadow = (entity_render_t**)R_FrameData_Store(numshadowentities_noselfshadow*sizeof(entity_render_t *), (void*)shadowentities_noselfshadow); if (shadowtrispvs == r_shadow_buffer_shadowtrispvs) { int numshadowtrispvsbytes = (((r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles) + 7) >> 3); int numlighttrispvsbytes = ((r_refdef.scene.worldmodel->surfmesh.num_triangles + 7) >> 3); rtlight->cached_shadowtrispvs = (unsigned char *)R_FrameData_Store(numshadowtrispvsbytes, shadowtrispvs); rtlight->cached_lighttrispvs = (unsigned char *)R_FrameData_Store(numlighttrispvsbytes, lighttrispvs); rtlight->cached_surfacelist = (int*)R_FrameData_Store(numsurfaces*sizeof(int), (void*)surfacelist); } else { // compiled light data rtlight->cached_shadowtrispvs = shadowtrispvs; rtlight->cached_lighttrispvs = lighttrispvs; rtlight->cached_surfacelist = surfacelist; } } static void R_Shadow_DrawLight(rtlight_t *rtlight) { int i; int numsurfaces; unsigned char *shadowtrispvs, *lighttrispvs, *surfacesides; int numlightentities; int numlightentities_noselfshadow; int numshadowentities; int numshadowentities_noselfshadow; entity_render_t **lightentities; entity_render_t **lightentities_noselfshadow; entity_render_t **shadowentities; entity_render_t **shadowentities_noselfshadow; int *surfacelist; static unsigned char entitysides[MAX_EDICTS]; static unsigned char entitysides_noselfshadow[MAX_EDICTS]; vec3_t nearestpoint; vec_t distance; qboolean castshadows; int lodlinear; // check if we cached this light this frame (meaning it is worth drawing) if (!rtlight->draw) return; numlightentities = rtlight->cached_numlightentities; numlightentities_noselfshadow = rtlight->cached_numlightentities_noselfshadow; numshadowentities = rtlight->cached_numshadowentities; numshadowentities_noselfshadow = rtlight->cached_numshadowentities_noselfshadow; numsurfaces = rtlight->cached_numsurfaces; lightentities = rtlight->cached_lightentities; lightentities_noselfshadow = rtlight->cached_lightentities_noselfshadow; shadowentities = rtlight->cached_shadowentities; shadowentities_noselfshadow = rtlight->cached_shadowentities_noselfshadow; shadowtrispvs = rtlight->cached_shadowtrispvs; lighttrispvs = rtlight->cached_lighttrispvs; surfacelist = rtlight->cached_surfacelist; // set up a scissor rectangle for this light if (R_Shadow_ScissorForBBox(rtlight->cached_cullmins, rtlight->cached_cullmaxs)) return; // don't let sound skip if going slow if (r_refdef.scene.extraupdate) S_ExtraUpdate (); // make this the active rtlight for rendering purposes R_Shadow_RenderMode_ActiveLight(rtlight); if (r_showshadowvolumes.integer && r_refdef.view.showdebug && numsurfaces + numshadowentities + numshadowentities_noselfshadow && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows)) { // optionally draw visible shape of the shadow volumes // for performance analysis by level designers R_Shadow_RenderMode_VisibleShadowVolumes(); if (numsurfaces) R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); for (i = 0;i < numshadowentities;i++) R_Shadow_DrawEntityShadow(shadowentities[i]); for (i = 0;i < numshadowentities_noselfshadow;i++) R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); R_Shadow_RenderMode_VisibleLighting(false, false); } if (r_showlighting.integer && r_refdef.view.showdebug && numsurfaces + numlightentities + numlightentities_noselfshadow) { // optionally draw the illuminated areas // for performance analysis by level designers R_Shadow_RenderMode_VisibleLighting(false, false); if (numsurfaces) R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); for (i = 0;i < numlightentities;i++) R_Shadow_DrawEntityLight(lightentities[i]); for (i = 0;i < numlightentities_noselfshadow;i++) R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); } castshadows = numsurfaces + numshadowentities + numshadowentities_noselfshadow > 0 && rtlight->shadow && (rtlight->isstatic ? r_refdef.scene.rtworldshadows : r_refdef.scene.rtdlightshadows); nearestpoint[0] = bound(rtlight->cullmins[0], r_refdef.view.origin[0], rtlight->cullmaxs[0]); nearestpoint[1] = bound(rtlight->cullmins[1], r_refdef.view.origin[1], rtlight->cullmaxs[1]); nearestpoint[2] = bound(rtlight->cullmins[2], r_refdef.view.origin[2], rtlight->cullmaxs[2]); distance = VectorDistance(nearestpoint, r_refdef.view.origin); lodlinear = (rtlight->radius * r_shadow_shadowmapping_precision.value) / sqrt(max(1.0f, distance/rtlight->radius)); //lodlinear = (int)(r_shadow_shadowmapping_lod_bias.value + r_shadow_shadowmapping_lod_scale.value * rtlight->radius / max(1.0f, distance)); lodlinear = bound(r_shadow_shadowmapping_minsize.integer, lodlinear, r_shadow_shadowmapmaxsize); if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) { float borderbias; int side; int size; int castermask = 0; int receivermask = 0; matrix4x4_t radiustolight = rtlight->matrix_worldtolight; Matrix4x4_Abs(&radiustolight); size = bound(r_shadow_shadowmapborder, lodlinear, r_shadow_shadowmapmaxsize); borderbias = r_shadow_shadowmapborder / (float)(size - r_shadow_shadowmapborder); surfacesides = NULL; if (numsurfaces) { if (rtlight->compiled && r_shadow_realtime_world_compile.integer && r_shadow_realtime_world_compileshadow.integer) { castermask = rtlight->static_shadowmap_casters; receivermask = rtlight->static_shadowmap_receivers; } else { surfacesides = r_shadow_buffer_surfacesides; for(i = 0;i < numsurfaces;i++) { msurface_t *surface = r_refdef.scene.worldmodel->data_surfaces + surfacelist[i]; surfacesides[i] = R_Shadow_CalcBBoxSideMask(surface->mins, surface->maxs, &rtlight->matrix_worldtolight, &radiustolight, borderbias); castermask |= surfacesides[i]; receivermask |= surfacesides[i]; } } } if (receivermask < 0x3F) { for (i = 0;i < numlightentities;i++) receivermask |= R_Shadow_CalcEntitySideMask(lightentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); if (receivermask < 0x3F) for(i = 0; i < numlightentities_noselfshadow;i++) receivermask |= R_Shadow_CalcEntitySideMask(lightentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias); } receivermask &= R_Shadow_CullFrustumSides(rtlight, size, r_shadow_shadowmapborder); if (receivermask) { for (i = 0;i < numshadowentities;i++) castermask |= (entitysides[i] = R_Shadow_CalcEntitySideMask(shadowentities[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); for (i = 0;i < numshadowentities_noselfshadow;i++) castermask |= (entitysides_noselfshadow[i] = R_Shadow_CalcEntitySideMask(shadowentities_noselfshadow[i], &rtlight->matrix_worldtolight, &radiustolight, borderbias)); } //Con_Printf("distance %f lodlinear %i size %i\n", distance, lodlinear, size); // render shadow casters into 6 sided depth texture for (side = 0;side < 6;side++) if (receivermask & (1 << side)) { R_Shadow_RenderMode_ShadowMap(side, receivermask, size); if (! (castermask & (1 << side))) continue; if (numsurfaces) R_Shadow_DrawWorldShadow_ShadowMap(numsurfaces, surfacelist, shadowtrispvs, surfacesides); for (i = 0;i < numshadowentities;i++) if (entitysides[i] & (1 << side)) R_Shadow_DrawEntityShadow(shadowentities[i]); } if (numlightentities_noselfshadow) { // render lighting using the depth texture as shadowmap // draw lighting in the unmasked areas R_Shadow_RenderMode_Lighting(false, false, true); for (i = 0;i < numlightentities_noselfshadow;i++) R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); } // render shadow casters into 6 sided depth texture if (numshadowentities_noselfshadow) { for (side = 0;side < 6;side++) if ((receivermask & castermask) & (1 << side)) { R_Shadow_RenderMode_ShadowMap(side, 0, size); for (i = 0;i < numshadowentities_noselfshadow;i++) if (entitysides_noselfshadow[i] & (1 << side)) R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); } } // render lighting using the depth texture as shadowmap // draw lighting in the unmasked areas R_Shadow_RenderMode_Lighting(false, false, true); // draw lighting in the unmasked areas if (numsurfaces) R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); for (i = 0;i < numlightentities;i++) R_Shadow_DrawEntityLight(lightentities[i]); } else if (castshadows && vid.stencil) { // draw stencil shadow volumes to mask off pixels that are in shadow // so that they won't receive lighting GL_Scissor(r_shadow_lightscissor[0], r_shadow_lightscissor[1], r_shadow_lightscissor[2], r_shadow_lightscissor[3]); R_Shadow_ClearStencil(); if (numsurfaces) R_Shadow_DrawWorldShadow_ShadowVolume(numsurfaces, surfacelist, shadowtrispvs); for (i = 0;i < numshadowentities;i++) R_Shadow_DrawEntityShadow(shadowentities[i]); // draw lighting in the unmasked areas R_Shadow_RenderMode_Lighting(true, false, false); for (i = 0;i < numlightentities_noselfshadow;i++) R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); for (i = 0;i < numshadowentities_noselfshadow;i++) R_Shadow_DrawEntityShadow(shadowentities_noselfshadow[i]); // draw lighting in the unmasked areas R_Shadow_RenderMode_Lighting(true, false, false); if (numsurfaces) R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); for (i = 0;i < numlightentities;i++) R_Shadow_DrawEntityLight(lightentities[i]); } else { // draw lighting in the unmasked areas R_Shadow_RenderMode_Lighting(false, false, false); if (numsurfaces) R_Shadow_DrawWorldLight(numsurfaces, surfacelist, lighttrispvs); for (i = 0;i < numlightentities;i++) R_Shadow_DrawEntityLight(lightentities[i]); for (i = 0;i < numlightentities_noselfshadow;i++) R_Shadow_DrawEntityLight(lightentities_noselfshadow[i]); } if (r_shadow_usingdeferredprepass) { // when rendering deferred lighting, we simply rasterize the box if (castshadows && r_shadow_shadowmode == R_SHADOW_SHADOWMODE_SHADOWMAP2D) R_Shadow_RenderMode_DrawDeferredLight(false, true); else if (castshadows && vid.stencil) R_Shadow_RenderMode_DrawDeferredLight(true, false); else R_Shadow_RenderMode_DrawDeferredLight(false, false); } } static void R_Shadow_FreeDeferred(void) { R_Mesh_DestroyFramebufferObject(r_shadow_prepassgeometryfbo); r_shadow_prepassgeometryfbo = 0; R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusespecularfbo); r_shadow_prepasslightingdiffusespecularfbo = 0; R_Mesh_DestroyFramebufferObject(r_shadow_prepasslightingdiffusefbo); r_shadow_prepasslightingdiffusefbo = 0; if (r_shadow_prepassgeometrydepthbuffer) R_FreeTexture(r_shadow_prepassgeometrydepthbuffer); r_shadow_prepassgeometrydepthbuffer = NULL; if (r_shadow_prepassgeometrynormalmaptexture) R_FreeTexture(r_shadow_prepassgeometrynormalmaptexture); r_shadow_prepassgeometrynormalmaptexture = NULL; if (r_shadow_prepasslightingdiffusetexture) R_FreeTexture(r_shadow_prepasslightingdiffusetexture); r_shadow_prepasslightingdiffusetexture = NULL; if (r_shadow_prepasslightingspeculartexture) R_FreeTexture(r_shadow_prepasslightingspeculartexture); r_shadow_prepasslightingspeculartexture = NULL; } void R_Shadow_DrawPrepass(void) { int i; int flag; int lnum; size_t lightindex; dlight_t *light; size_t range; entity_render_t *ent; float clearcolor[4]; R_Mesh_ResetTextureState(); GL_DepthMask(true); GL_ColorMask(1,1,1,1); GL_BlendFunc(GL_ONE, GL_ZERO); GL_Color(1,1,1,1); GL_DepthTest(true); R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); Vector4Set(clearcolor, 0.5f,0.5f,0.5f,1.0f); GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); if (r_timereport_active) R_TimeReport("prepasscleargeom"); if (cl.csqc_vidvars.drawworld && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->DrawPrepass) r_refdef.scene.worldmodel->DrawPrepass(r_refdef.scene.worldentity); if (r_timereport_active) R_TimeReport("prepassworld"); for (i = 0;i < r_refdef.scene.numentities;i++) { if (!r_refdef.viewcache.entityvisible[i]) continue; ent = r_refdef.scene.entities[i]; if (ent->model && ent->model->DrawPrepass != NULL) ent->model->DrawPrepass(ent); } if (r_timereport_active) R_TimeReport("prepassmodels"); GL_DepthMask(false); GL_ColorMask(1,1,1,1); GL_Color(1,1,1,1); GL_DepthTest(true); R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); Vector4Set(clearcolor, 0, 0, 0, 0); GL_Clear(GL_COLOR_BUFFER_BIT, clearcolor, 1.0f, 0); if (r_timereport_active) R_TimeReport("prepassclearlit"); R_Shadow_RenderMode_Begin(); flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; if (r_shadow_debuglight.integer >= 0) { lightindex = r_shadow_debuglight.integer; light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light && (light->flags & flag) && light->rtlight.draw) R_Shadow_DrawLight(&light->rtlight); } else { range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light && (light->flags & flag) && light->rtlight.draw) R_Shadow_DrawLight(&light->rtlight); } } if (r_refdef.scene.rtdlight) for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) if (r_refdef.scene.lights[lnum]->draw) R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); R_Shadow_RenderMode_End(); if (r_timereport_active) R_TimeReport("prepasslights"); } void R_Shadow_DrawLightSprites(void); void R_Shadow_PrepareLights(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { int flag; int lnum; size_t lightindex; dlight_t *light; size_t range; float f; if (r_shadow_shadowmapmaxsize != bound(1, r_shadow_shadowmapping_maxsize.integer, (int)vid.maxtexturesize_2d / 4) || (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL) != (r_shadow_shadowmapping.integer || r_shadow_deferred.integer) || r_shadow_shadowmapvsdct != (r_shadow_shadowmapping_vsdct.integer != 0 && vid.renderpath == RENDERPATH_GL20) || r_shadow_shadowmapfilterquality != r_shadow_shadowmapping_filterquality.integer || r_shadow_shadowmapshadowsampler != (vid.support.arb_shadow && r_shadow_shadowmapping_useshadowsampler.integer) || r_shadow_shadowmapdepthbits != r_shadow_shadowmapping_depthbits.integer || r_shadow_shadowmapborder != bound(0, r_shadow_shadowmapping_bordersize.integer, 16) || r_shadow_shadowmapdepthtexture != r_fb.usedepthtextures) R_Shadow_FreeShadowMaps(); r_shadow_fb_fbo = fbo; r_shadow_fb_depthtexture = depthtexture; r_shadow_fb_colortexture = colortexture; r_shadow_usingshadowmaportho = false; switch (vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: case RENDERPATH_SOFT: #ifndef USE_GLES2 if (!r_shadow_deferred.integer || r_shadow_shadowmode == R_SHADOW_SHADOWMODE_STENCIL || !vid.support.ext_framebuffer_object || vid.maxdrawbuffers < 2) { r_shadow_usingdeferredprepass = false; if (r_shadow_prepass_width) R_Shadow_FreeDeferred(); r_shadow_prepass_width = r_shadow_prepass_height = 0; break; } if (r_shadow_prepass_width != vid.width || r_shadow_prepass_height != vid.height) { R_Shadow_FreeDeferred(); r_shadow_usingdeferredprepass = true; r_shadow_prepass_width = vid.width; r_shadow_prepass_height = vid.height; r_shadow_prepassgeometrydepthbuffer = R_LoadTextureRenderBuffer(r_shadow_texturepool, "prepassgeometrydepthbuffer", vid.width, vid.height, TEXTYPE_DEPTHBUFFER24); r_shadow_prepassgeometrynormalmaptexture = R_LoadTexture2D(r_shadow_texturepool, "prepassgeometrynormalmap", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER32F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); r_shadow_prepasslightingdiffusetexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingdiffuse", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER16F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); r_shadow_prepasslightingspeculartexture = R_LoadTexture2D(r_shadow_texturepool, "prepasslightingspecular", vid.width, vid.height, NULL, TEXTYPE_COLORBUFFER16F, TEXF_RENDERTARGET | TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCENEAREST, -1, NULL); // set up the geometry pass fbo (depth + normalmap) r_shadow_prepassgeometryfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); R_Mesh_SetRenderTargets(r_shadow_prepassgeometryfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepassgeometrynormalmaptexture, NULL, NULL, NULL); // render depth into a renderbuffer and other important properties into the normalmap texture // set up the lighting pass fbo (diffuse + specular) r_shadow_prepasslightingdiffusespecularfbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusespecularfbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, r_shadow_prepasslightingspeculartexture, NULL, NULL); // render diffuse into one texture and specular into another, // with depth and normalmap bound as textures, // with depth bound as attachment as well // set up the lighting pass fbo (diffuse) r_shadow_prepasslightingdiffusefbo = R_Mesh_CreateFramebufferObject(r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); R_Mesh_SetRenderTargets(r_shadow_prepasslightingdiffusefbo, r_shadow_prepassgeometrydepthbuffer, r_shadow_prepasslightingdiffusetexture, NULL, NULL, NULL); // render diffuse into one texture, // with depth and normalmap bound as textures, // with depth bound as attachment as well } #endif break; case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GLES1: case RENDERPATH_GLES2: r_shadow_usingdeferredprepass = false; break; } R_Shadow_EnlargeLeafSurfaceTrisBuffer(r_refdef.scene.worldmodel->brush.num_leafs, r_refdef.scene.worldmodel->num_surfaces, r_refdef.scene.worldmodel->brush.shadowmesh ? r_refdef.scene.worldmodel->brush.shadowmesh->numtriangles : r_refdef.scene.worldmodel->surfmesh.num_triangles, r_refdef.scene.worldmodel->surfmesh.num_triangles); flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; if (r_shadow_debuglight.integer >= 0) { lightindex = r_shadow_debuglight.integer; light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light) R_Shadow_PrepareLight(&light->rtlight); } else { range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light && (light->flags & flag)) R_Shadow_PrepareLight(&light->rtlight); } } if (r_refdef.scene.rtdlight) { for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) R_Shadow_PrepareLight(r_refdef.scene.lights[lnum]); } else if(gl_flashblend.integer) { for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) { rtlight_t *rtlight = r_refdef.scene.lights[lnum]; f = ((rtlight->style >= 0 && rtlight->style < MAX_LIGHTSTYLES) ? r_refdef.scene.lightstylevalue[rtlight->style] : 1) * r_shadow_lightintensityscale.value; VectorScale(rtlight->color, f, rtlight->currentcolor); } } if (r_editlights.integer) R_Shadow_DrawLightSprites(); } void R_Shadow_DrawLights(void) { int flag; int lnum; size_t lightindex; dlight_t *light; size_t range; R_Shadow_RenderMode_Begin(); flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; if (r_shadow_debuglight.integer >= 0) { lightindex = r_shadow_debuglight.integer; light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light) R_Shadow_DrawLight(&light->rtlight); } else { range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light && (light->flags & flag)) R_Shadow_DrawLight(&light->rtlight); } } if (r_refdef.scene.rtdlight) for (lnum = 0;lnum < r_refdef.scene.numlights;lnum++) R_Shadow_DrawLight(r_refdef.scene.lights[lnum]); R_Shadow_RenderMode_End(); } #define MAX_MODELSHADOWS 1024 static int r_shadow_nummodelshadows; static entity_render_t *r_shadow_modelshadows[MAX_MODELSHADOWS]; void R_Shadow_PrepareModelShadows(void) { int i; float scale, size, radius, dot1, dot2; prvm_vec3_t prvmshadowdir, prvmshadowfocus; vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus, shadowmins, shadowmaxs; entity_render_t *ent; r_shadow_nummodelshadows = 0; if (!r_refdef.scene.numentities) return; switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: if (r_shadows.integer >= 2) break; // fall through case R_SHADOW_SHADOWMODE_STENCIL: if (!vid.stencil) return; for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; if (ent->model && ent->model->DrawShadowVolume != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) { if (r_shadow_nummodelshadows >= MAX_MODELSHADOWS) break; r_shadow_modelshadows[r_shadow_nummodelshadows++] = ent; R_AnimCache_GetEntity(ent, false, false); } } return; default: return; } size = 2*r_shadow_shadowmapmaxsize; scale = r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value; radius = 0.5f * size / scale; Math_atov(r_shadows_throwdirection.string, prvmshadowdir); VectorCopy(prvmshadowdir, shadowdir); VectorNormalize(shadowdir); dot1 = DotProduct(r_refdef.view.forward, shadowdir); dot2 = DotProduct(r_refdef.view.up, shadowdir); if (fabs(dot1) <= fabs(dot2)) VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); else VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); VectorNormalize(shadowforward); CrossProduct(shadowdir, shadowforward, shadowright); Math_atov(r_shadows_focus.string, prvmshadowfocus); VectorCopy(prvmshadowfocus, shadowfocus); VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) dot1 = 1; VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); shadowmins[0] = shadoworigin[0] - r_shadows_throwdistance.value * fabs(shadowdir[0]) - radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); shadowmins[1] = shadoworigin[1] - r_shadows_throwdistance.value * fabs(shadowdir[1]) - radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); shadowmins[2] = shadoworigin[2] - r_shadows_throwdistance.value * fabs(shadowdir[2]) - radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); shadowmaxs[0] = shadoworigin[0] + r_shadows_throwdistance.value * fabs(shadowdir[0]) + radius * (fabs(shadowforward[0]) + fabs(shadowright[0])); shadowmaxs[1] = shadoworigin[1] + r_shadows_throwdistance.value * fabs(shadowdir[1]) + radius * (fabs(shadowforward[1]) + fabs(shadowright[1])); shadowmaxs[2] = shadoworigin[2] + r_shadows_throwdistance.value * fabs(shadowdir[2]) + radius * (fabs(shadowforward[2]) + fabs(shadowright[2])); for (i = 0;i < r_refdef.scene.numentities;i++) { ent = r_refdef.scene.entities[i]; if (!BoxesOverlap(ent->mins, ent->maxs, shadowmins, shadowmaxs)) continue; // cast shadows from anything of the map (submodels are optional) if (ent->model && ent->model->DrawShadowMap != NULL && (!ent->model->brush.submodel || r_shadows_castfrombmodels.integer) && (ent->flags & RENDER_SHADOW)) { if (r_shadow_nummodelshadows >= MAX_MODELSHADOWS) break; r_shadow_modelshadows[r_shadow_nummodelshadows++] = ent; R_AnimCache_GetEntity(ent, false, false); } } } void R_DrawModelShadowMaps(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { int i; float relativethrowdistance, scale, size, radius, nearclip, farclip, bias, dot1, dot2; entity_render_t *ent; vec3_t relativelightorigin; vec3_t relativelightdirection, relativeforward, relativeright; vec3_t relativeshadowmins, relativeshadowmaxs; vec3_t shadowdir, shadowforward, shadowright, shadoworigin, shadowfocus; prvm_vec3_t prvmshadowdir, prvmshadowfocus; float m[12]; matrix4x4_t shadowmatrix, cameramatrix, mvpmatrix, invmvpmatrix, scalematrix, texmatrix; r_viewport_t viewport; GLuint shadowfbo = 0; float clearcolor[4]; if (!r_shadow_nummodelshadows) return; switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: break; default: return; } r_shadow_fb_fbo = fbo; r_shadow_fb_depthtexture = depthtexture; r_shadow_fb_colortexture = colortexture; R_ResetViewRendering3D(fbo, depthtexture, colortexture); R_Shadow_RenderMode_Begin(); R_Shadow_RenderMode_ActiveLight(NULL); switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: if (!r_shadow_shadowmap2ddepthtexture) R_Shadow_MakeShadowMap(0, r_shadow_shadowmapmaxsize); shadowfbo = r_shadow_fbo2d; r_shadow_shadowmap_texturescale[0] = 1.0f / R_TextureWidth(r_shadow_shadowmap2ddepthtexture); r_shadow_shadowmap_texturescale[1] = 1.0f / R_TextureHeight(r_shadow_shadowmap2ddepthtexture); r_shadow_rendermode = R_SHADOW_RENDERMODE_SHADOWMAP2D; break; default: break; } size = 2*r_shadow_shadowmapmaxsize; scale = (r_shadow_shadowmapping_precision.value * r_shadows_shadowmapscale.value) / size; radius = 0.5f / scale; nearclip = -r_shadows_throwdistance.value; farclip = r_shadows_throwdistance.value; bias = (r_shadows_shadowmapbias.value < 0) ? r_shadow_shadowmapping_bias.value : r_shadows_shadowmapbias.value * r_shadow_shadowmapping_nearclip.value / (2 * r_shadows_throwdistance.value) * (1024.0f / size); r_shadow_shadowmap_parameters[0] = size; r_shadow_shadowmap_parameters[1] = size; r_shadow_shadowmap_parameters[2] = 1.0; r_shadow_shadowmap_parameters[3] = bound(0.0f, 1.0f - r_shadows_darken.value, 1.0f); Math_atov(r_shadows_throwdirection.string, prvmshadowdir); VectorCopy(prvmshadowdir, shadowdir); VectorNormalize(shadowdir); Math_atov(r_shadows_focus.string, prvmshadowfocus); VectorCopy(prvmshadowfocus, shadowfocus); VectorM(shadowfocus[0], r_refdef.view.right, shadoworigin); VectorMA(shadoworigin, shadowfocus[1], r_refdef.view.up, shadoworigin); VectorMA(shadoworigin, -shadowfocus[2], r_refdef.view.forward, shadoworigin); VectorAdd(shadoworigin, r_refdef.view.origin, shadoworigin); dot1 = DotProduct(r_refdef.view.forward, shadowdir); dot2 = DotProduct(r_refdef.view.up, shadowdir); if (fabs(dot1) <= fabs(dot2)) VectorMA(r_refdef.view.forward, -dot1, shadowdir, shadowforward); else VectorMA(r_refdef.view.up, -dot2, shadowdir, shadowforward); VectorNormalize(shadowforward); VectorM(scale, shadowforward, &m[0]); if (shadowfocus[0] || shadowfocus[1] || shadowfocus[2]) dot1 = 1; m[3] = fabs(dot1) * 0.5f - DotProduct(shadoworigin, &m[0]); CrossProduct(shadowdir, shadowforward, shadowright); VectorM(scale, shadowright, &m[4]); m[7] = 0.5f - DotProduct(shadoworigin, &m[4]); VectorM(1.0f / (farclip - nearclip), shadowdir, &m[8]); m[11] = 0.5f - DotProduct(shadoworigin, &m[8]); Matrix4x4_FromArray12FloatD3D(&shadowmatrix, m); Matrix4x4_Invert_Full(&cameramatrix, &shadowmatrix); R_Viewport_InitOrtho(&viewport, &cameramatrix, 0, 0, size, size, 0, 0, 1, 1, 0, -1, NULL); VectorMA(shadoworigin, (1.0f - fabs(dot1)) * radius, shadowforward, shadoworigin); if (r_shadow_shadowmap2ddepthbuffer) R_Mesh_SetRenderTargets(shadowfbo, r_shadow_shadowmap2ddepthbuffer, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL); else R_Mesh_SetRenderTargets(shadowfbo, r_shadow_shadowmap2ddepthtexture, NULL, NULL, NULL, NULL); R_SetupShader_DepthOrShadow(true, r_shadow_shadowmap2ddepthbuffer != NULL, false); // FIXME test if we have a skeletal model? GL_PolygonOffset(r_shadow_shadowmapping_polygonfactor.value, r_shadow_shadowmapping_polygonoffset.value); GL_DepthMask(true); GL_DepthTest(true); R_SetViewport(&viewport); GL_Scissor(viewport.x, viewport.y, min(viewport.width + r_shadow_shadowmapborder, 2*r_shadow_shadowmapmaxsize), viewport.height + r_shadow_shadowmapborder); Vector4Set(clearcolor, 1,1,1,1); // in D3D9 we have to render to a color texture shadowmap // in GL we render directly to a depth texture only if (r_shadow_shadowmap2ddepthbuffer) GL_Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); else GL_Clear(GL_DEPTH_BUFFER_BIT, clearcolor, 1.0f, 0); // render into a slightly restricted region so that the borders of the // shadowmap area fade away, rather than streaking across everything // outside the usable area GL_Scissor(viewport.x + r_shadow_shadowmapborder, viewport.y + r_shadow_shadowmapborder, viewport.width - 2*r_shadow_shadowmapborder, viewport.height - 2*r_shadow_shadowmapborder); for (i = 0;i < r_shadow_nummodelshadows;i++) { ent = r_shadow_modelshadows[i]; relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); Matrix4x4_Transform(&ent->inversematrix, shadoworigin, relativelightorigin); Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); Matrix4x4_Transform3x3(&ent->inversematrix, shadowforward, relativeforward); Matrix4x4_Transform3x3(&ent->inversematrix, shadowright, relativeright); relativeshadowmins[0] = relativelightorigin[0] - r_shadows_throwdistance.value * fabs(relativelightdirection[0]) - radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); relativeshadowmins[1] = relativelightorigin[1] - r_shadows_throwdistance.value * fabs(relativelightdirection[1]) - radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); relativeshadowmins[2] = relativelightorigin[2] - r_shadows_throwdistance.value * fabs(relativelightdirection[2]) - radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); relativeshadowmaxs[0] = relativelightorigin[0] + r_shadows_throwdistance.value * fabs(relativelightdirection[0]) + radius * (fabs(relativeforward[0]) + fabs(relativeright[0])); relativeshadowmaxs[1] = relativelightorigin[1] + r_shadows_throwdistance.value * fabs(relativelightdirection[1]) + radius * (fabs(relativeforward[1]) + fabs(relativeright[1])); relativeshadowmaxs[2] = relativelightorigin[2] + r_shadows_throwdistance.value * fabs(relativelightdirection[2]) + radius * (fabs(relativeforward[2]) + fabs(relativeright[2])); RSurf_ActiveModelEntity(ent, false, false, false); ent->model->DrawShadowMap(0, ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, NULL, relativeshadowmins, relativeshadowmaxs); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } #if 0 if (r_test.integer) { unsigned char *rawpixels = Z_Malloc(viewport.width*viewport.height*4); CHECKGLERROR qglReadPixels(viewport.x, viewport.y, viewport.width, viewport.height, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, rawpixels); CHECKGLERROR Image_WriteTGABGRA("r_shadows_2.tga", viewport.width, viewport.height, rawpixels); Cvar_SetValueQuick(&r_test, 0); Z_Free(rawpixels); } #endif R_Shadow_RenderMode_End(); Matrix4x4_Concat(&mvpmatrix, &r_refdef.view.viewport.projectmatrix, &r_refdef.view.viewport.viewmatrix); Matrix4x4_Invert_Full(&invmvpmatrix, &mvpmatrix); Matrix4x4_CreateScale3(&scalematrix, size, -size, 1); Matrix4x4_AdjustOrigin(&scalematrix, 0, size, -0.5f * bias); Matrix4x4_Concat(&texmatrix, &scalematrix, &shadowmatrix); Matrix4x4_Concat(&r_shadow_shadowmapmatrix, &texmatrix, &invmvpmatrix); switch (vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_SOFT: case RENDERPATH_GLES1: case RENDERPATH_GLES2: break; case RENDERPATH_D3D9: case RENDERPATH_D3D10: case RENDERPATH_D3D11: #ifdef MATRIX4x4_OPENGLORIENTATION r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; r_shadow_shadowmapmatrix.m[0][1] *= -1.0f; r_shadow_shadowmapmatrix.m[0][2] *= -1.0f; r_shadow_shadowmapmatrix.m[0][3] *= -1.0f; #else r_shadow_shadowmapmatrix.m[0][0] *= -1.0f; r_shadow_shadowmapmatrix.m[1][0] *= -1.0f; r_shadow_shadowmapmatrix.m[2][0] *= -1.0f; r_shadow_shadowmapmatrix.m[3][0] *= -1.0f; #endif break; } r_shadow_usingshadowmaportho = true; switch (r_shadow_shadowmode) { case R_SHADOW_SHADOWMODE_SHADOWMAP2D: r_shadow_usingshadowmap2d = true; break; default: break; } } void R_DrawModelShadows(int fbo, rtexture_t *depthtexture, rtexture_t *colortexture) { int i; float relativethrowdistance; entity_render_t *ent; vec3_t relativelightorigin; vec3_t relativelightdirection; vec3_t relativeshadowmins, relativeshadowmaxs; vec3_t tmp, shadowdir; prvm_vec3_t prvmshadowdir; if (!r_shadow_nummodelshadows || (r_shadow_shadowmode != R_SHADOW_SHADOWMODE_STENCIL && r_shadows.integer != 1)) return; r_shadow_fb_fbo = fbo; r_shadow_fb_depthtexture = depthtexture; r_shadow_fb_colortexture = colortexture; R_ResetViewRendering3D(fbo, depthtexture, colortexture); //GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height); //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); R_Shadow_RenderMode_Begin(); R_Shadow_RenderMode_ActiveLight(NULL); r_shadow_lightscissor[0] = r_refdef.view.x; r_shadow_lightscissor[1] = vid.height - r_refdef.view.y - r_refdef.view.height; r_shadow_lightscissor[2] = r_refdef.view.width; r_shadow_lightscissor[3] = r_refdef.view.height; R_Shadow_RenderMode_StencilShadowVolumes(false); // get shadow dir if (r_shadows.integer == 2) { Math_atov(r_shadows_throwdirection.string, prvmshadowdir); VectorCopy(prvmshadowdir, shadowdir); VectorNormalize(shadowdir); } R_Shadow_ClearStencil(); for (i = 0;i < r_shadow_nummodelshadows;i++) { ent = r_shadow_modelshadows[i]; // cast shadows from anything of the map (submodels are optional) relativethrowdistance = r_shadows_throwdistance.value * Matrix4x4_ScaleFromMatrix(&ent->inversematrix); VectorSet(relativeshadowmins, -relativethrowdistance, -relativethrowdistance, -relativethrowdistance); VectorSet(relativeshadowmaxs, relativethrowdistance, relativethrowdistance, relativethrowdistance); if (r_shadows.integer == 2) // 2: simpler mode, throw shadows always in same direction Matrix4x4_Transform3x3(&ent->inversematrix, shadowdir, relativelightdirection); else { if(ent->entitynumber != 0) { if(ent->entitynumber >= MAX_EDICTS) // csqc entity { // FIXME handle this VectorNegate(ent->modellight_lightdir, relativelightdirection); } else { // networked entity - might be attached in some way (then we should use the parent's light direction, to not tear apart attached entities) int entnum, entnum2, recursion; entnum = entnum2 = ent->entitynumber; for(recursion = 32; recursion > 0; --recursion) { entnum2 = cl.entities[entnum].state_current.tagentity; if(entnum2 >= 1 && entnum2 < cl.num_entities && cl.entities_active[entnum2]) entnum = entnum2; else break; } if(recursion && recursion != 32) // if we followed a valid non-empty attachment chain { VectorNegate(cl.entities[entnum].render.modellight_lightdir, relativelightdirection); // transform into modelspace of OUR entity Matrix4x4_Transform3x3(&cl.entities[entnum].render.matrix, relativelightdirection, tmp); Matrix4x4_Transform3x3(&ent->inversematrix, tmp, relativelightdirection); } else VectorNegate(ent->modellight_lightdir, relativelightdirection); } } else VectorNegate(ent->modellight_lightdir, relativelightdirection); } VectorScale(relativelightdirection, -relativethrowdistance, relativelightorigin); RSurf_ActiveModelEntity(ent, false, false, false); ent->model->DrawShadowVolume(ent, relativelightorigin, relativelightdirection, relativethrowdistance, ent->model->nummodelsurfaces, ent->model->sortedmodelsurfaces, relativeshadowmins, relativeshadowmaxs); rsurface.entity = NULL; // used only by R_GetCurrentTexture and RSurf_ActiveWorldEntity/RSurf_ActiveModelEntity } // not really the right mode, but this will disable any silly stencil features R_Shadow_RenderMode_End(); // set up ortho view for rendering this pass //GL_Scissor(r_refdef.view.x, vid.height - r_refdef.view.height - r_refdef.view.y, r_refdef.view.width, r_refdef.view.height); //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); //GL_ScissorTest(true); //R_EntityMatrix(&identitymatrix); //R_Mesh_ResetTextureState(); R_ResetViewRendering2D(fbo, depthtexture, colortexture); // set up a darkening blend on shadowed areas GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //GL_DepthRange(0, 1); //GL_DepthTest(false); //GL_DepthMask(false); //GL_PolygonOffset(0, 0);CHECKGLERROR GL_Color(0, 0, 0, r_shadows_darken.value); //GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); //GL_DepthFunc(GL_ALWAYS); R_SetStencil(true, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_NOTEQUAL, 128, 255); // apply the blend to the shadowed areas R_Mesh_PrepareVertices_Generic_Arrays(4, r_screenvertex3f, NULL, NULL); R_SetupShader_Generic_NoTexture(false, true); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); // restore the viewport R_SetViewport(&r_refdef.view.viewport); // restore other state to normal //R_Shadow_RenderMode_End(); } static void R_BeginCoronaQuery(rtlight_t *rtlight, float scale, qboolean usequery) { float zdist; vec3_t centerorigin; #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) float vertex3f[12]; #endif // if it's too close, skip it if (VectorLength(rtlight->currentcolor) < (1.0f / 256.0f)) return; zdist = (DotProduct(rtlight->shadoworigin, r_refdef.view.forward) - DotProduct(r_refdef.view.origin, r_refdef.view.forward)); if (zdist < 32) return; if (usequery && r_numqueries + 2 <= r_maxqueries) { rtlight->corona_queryindex_allpixels = r_queries[r_numqueries++]; rtlight->corona_queryindex_visiblepixels = r_queries[r_numqueries++]; // we count potential samples in the middle of the screen, we count actual samples at the light location, this allows counting potential samples of off-screen lights VectorMA(r_refdef.view.origin, zdist, r_refdef.view.forward, centerorigin); switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) CHECKGLERROR // NOTE: GL_DEPTH_TEST must be enabled or ATI won't count samples, so use GL_DepthFunc instead qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_allpixels); GL_DepthFunc(GL_ALWAYS); R_CalcSprite_Vertex3f(vertex3f, centerorigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL, 0); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); qglEndQueryARB(GL_SAMPLES_PASSED_ARB); GL_DepthFunc(GL_LEQUAL); qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, rtlight->corona_queryindex_visiblepixels); R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); R_Mesh_PrepareVertices_Vertex3f(4, vertex3f, NULL, 0); R_Mesh_Draw(0, 4, 0, 2, polygonelement3i, NULL, 0, polygonelement3s, NULL, 0); qglEndQueryARB(GL_SAMPLES_PASSED_ARB); CHECKGLERROR #endif break; case RENDERPATH_D3D9: Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } } rtlight->corona_visibility = bound(0, (zdist - 32) / 32, 1); } static float spritetexcoord2f[4*2] = {0, 1, 0, 0, 1, 0, 1, 1}; static void R_DrawCorona(rtlight_t *rtlight, float cscale, float scale) { vec3_t color; unsigned int occlude = 0; GLint allpixels = 0, visiblepixels = 0; // now we have to check the query result if (rtlight->corona_queryindex_visiblepixels) { switch(vid.renderpath) { case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) // See if we can use the GPU-side method to prevent implicit sync if (vid.support.arb_query_buffer_object) { #define BUFFER_OFFSET(i) ((GLint *)((unsigned char*)NULL + (i))) if (!r_shadow_occlusion_buf) { qglGenBuffersARB(1, &r_shadow_occlusion_buf); qglBindBufferARB(GL_QUERY_BUFFER_ARB, r_shadow_occlusion_buf); qglBufferDataARB(GL_QUERY_BUFFER_ARB, 8, NULL, GL_DYNAMIC_COPY); } else { qglBindBufferARB(GL_QUERY_BUFFER_ARB, r_shadow_occlusion_buf); } qglGetQueryObjectivARB(rtlight->corona_queryindex_visiblepixels, GL_QUERY_RESULT_ARB, BUFFER_OFFSET(0)); qglGetQueryObjectivARB(rtlight->corona_queryindex_allpixels, GL_QUERY_RESULT_ARB, BUFFER_OFFSET(4)); qglBindBufferBase(GL_UNIFORM_BUFFER, 0, r_shadow_occlusion_buf); occlude = MATERIALFLAG_OCCLUDE; cscale *= rtlight->corona_visibility; CHECKGLERROR break; } // fallthrough #else return; #endif case RENDERPATH_GL11: case RENDERPATH_GL13: #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) CHECKGLERROR qglGetQueryObjectivARB(rtlight->corona_queryindex_visiblepixels, GL_QUERY_RESULT_ARB, &visiblepixels); qglGetQueryObjectivARB(rtlight->corona_queryindex_allpixels, GL_QUERY_RESULT_ARB, &allpixels); if (visiblepixels < 1 || allpixels < 1) return; rtlight->corona_visibility *= bound(0, (float)visiblepixels / (float)allpixels, 1); cscale *= rtlight->corona_visibility; CHECKGLERROR break; #else return; #endif case RENDERPATH_D3D9: Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; case RENDERPATH_SOFT: //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); return; default: return; } } else { // FIXME: these traces should scan all render entities instead of cl.world if (CL_TraceLine(r_refdef.view.origin, rtlight->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction < 1) return; } VectorScale(rtlight->currentcolor, cscale, color); if (VectorLength(color) > (1.0f / 256.0f)) { float vertex3f[12]; qboolean negated = (color[0] + color[1] + color[2] < 0) && vid.support.ext_blend_subtract; if(negated) { VectorNegate(color, color); GL_BlendEquationSubtract(true); } R_CalcSprite_Vertex3f(vertex3f, rtlight->shadoworigin, r_refdef.view.right, r_refdef.view.up, scale, -scale, -scale, scale); RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, RENDER_NODEPTHTEST, 0, color[0], color[1], color[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); R_DrawCustomSurface(r_shadow_lightcorona, &identitymatrix, MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE | MATERIALFLAG_NODEPTHTEST | occlude, 0, 4, 0, 2, false, false); if(negated) GL_BlendEquationSubtract(false); } } void R_Shadow_DrawCoronas(void) { int i, flag; qboolean usequery = false; size_t lightindex; dlight_t *light; rtlight_t *rtlight; size_t range; if (r_coronas.value < (1.0f / 256.0f) && !gl_flashblend.integer) return; if (r_fb.water.renderingscene) return; flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; R_EntityMatrix(&identitymatrix); range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked // check occlusion of coronas // use GL_ARB_occlusion_query if available // otherwise use raytraces r_numqueries = 0; switch (vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: usequery = vid.support.arb_occlusion_query && r_coronas_occlusionquery.integer; #if defined(GL_SAMPLES_PASSED_ARB) && !defined(USE_GLES2) if (usequery) { GL_ColorMask(0,0,0,0); if (r_maxqueries < ((unsigned int)range + r_refdef.scene.numlights) * 2) if (r_maxqueries < MAX_OCCLUSION_QUERIES) { i = r_maxqueries; r_maxqueries = ((unsigned int)range + r_refdef.scene.numlights) * 4; r_maxqueries = min(r_maxqueries, MAX_OCCLUSION_QUERIES); CHECKGLERROR qglGenQueriesARB(r_maxqueries - i, r_queries + i); CHECKGLERROR } RSurf_ActiveWorldEntity(); GL_BlendFunc(GL_ONE, GL_ZERO); GL_CullFace(GL_NONE); GL_DepthMask(false); GL_DepthRange(0, 1); GL_PolygonOffset(0, 0); GL_DepthTest(true); R_Mesh_ResetTextureState(); R_SetupShader_Generic_NoTexture(false, false); } #endif break; case RENDERPATH_D3D9: usequery = false; //Con_DPrintf("FIXME D3D9 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D10: Con_DPrintf("FIXME D3D10 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_D3D11: Con_DPrintf("FIXME D3D11 %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; case RENDERPATH_SOFT: usequery = false; //Con_DPrintf("FIXME SOFT %s:%i %s\n", __FILE__, __LINE__, __FUNCTION__); break; } for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; rtlight = &light->rtlight; rtlight->corona_visibility = 0; rtlight->corona_queryindex_visiblepixels = 0; rtlight->corona_queryindex_allpixels = 0; if (!(rtlight->flags & flag)) continue; if (rtlight->corona <= 0) continue; if (r_shadow_debuglight.integer >= 0 && r_shadow_debuglight.integer != (int)lightindex) continue; R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); } for (i = 0;i < r_refdef.scene.numlights;i++) { rtlight = r_refdef.scene.lights[i]; rtlight->corona_visibility = 0; rtlight->corona_queryindex_visiblepixels = 0; rtlight->corona_queryindex_allpixels = 0; if (!(rtlight->flags & flag)) continue; if (rtlight->corona <= 0) continue; R_BeginCoronaQuery(rtlight, rtlight->radius * rtlight->coronasizescale * r_coronas_occlusionsizescale.value, usequery); } if (usequery) GL_ColorMask(r_refdef.view.colormask[0], r_refdef.view.colormask[1], r_refdef.view.colormask[2], 1); // now draw the coronas using the query data for intensity info for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; rtlight = &light->rtlight; if (rtlight->corona_visibility <= 0) continue; R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); } for (i = 0;i < r_refdef.scene.numlights;i++) { rtlight = r_refdef.scene.lights[i]; if (rtlight->corona_visibility <= 0) continue; if (gl_flashblend.integer) R_DrawCorona(rtlight, rtlight->corona, rtlight->radius * rtlight->coronasizescale * 2.0f); else R_DrawCorona(rtlight, rtlight->corona * r_coronas.value * 0.25f, rtlight->radius * rtlight->coronasizescale); } } static dlight_t *R_Shadow_NewWorldLight(void) { return (dlight_t *)Mem_ExpandableArray_AllocRecord(&r_shadow_worldlightsarray); } static void R_Shadow_UpdateWorldLight(dlight_t *light, vec3_t origin, vec3_t angles, vec3_t color, vec_t radius, vec_t corona, int style, int shadowenable, const char *cubemapname, vec_t coronasizescale, vec_t ambientscale, vec_t diffusescale, vec_t specularscale, int flags) { matrix4x4_t matrix; // note that style is no longer validated here, -1 is used for unstyled lights and >= MAX_LIGHTSTYLES is accepted for sake of editing rtlights files that might be out of bounds but perfectly formatted // validate parameters if (!cubemapname) cubemapname = ""; // copy to light properties VectorCopy(origin, light->origin); light->angles[0] = angles[0] - 360 * floor(angles[0] / 360); light->angles[1] = angles[1] - 360 * floor(angles[1] / 360); light->angles[2] = angles[2] - 360 * floor(angles[2] / 360); /* light->color[0] = max(color[0], 0); light->color[1] = max(color[1], 0); light->color[2] = max(color[2], 0); */ light->color[0] = color[0]; light->color[1] = color[1]; light->color[2] = color[2]; light->radius = max(radius, 0); light->style = style; light->shadow = shadowenable; light->corona = corona; strlcpy(light->cubemapname, cubemapname, sizeof(light->cubemapname)); light->coronasizescale = coronasizescale; light->ambientscale = ambientscale; light->diffusescale = diffusescale; light->specularscale = specularscale; light->flags = flags; // update renderable light data Matrix4x4_CreateFromQuakeEntity(&matrix, light->origin[0], light->origin[1], light->origin[2], light->angles[0], light->angles[1], light->angles[2], light->radius); R_RTLight_Update(&light->rtlight, true, &matrix, light->color, light->style, light->cubemapname[0] ? light->cubemapname : NULL, light->shadow, light->corona, light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); } static void R_Shadow_FreeWorldLight(dlight_t *light) { if (r_shadow_selectedlight == light) r_shadow_selectedlight = NULL; R_RTLight_Uncompile(&light->rtlight); Mem_ExpandableArray_FreeRecord(&r_shadow_worldlightsarray, light); } void R_Shadow_ClearWorldLights(void) { size_t lightindex; dlight_t *light; size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light) R_Shadow_FreeWorldLight(light); } r_shadow_selectedlight = NULL; } static void R_Shadow_SelectLight(dlight_t *light) { if (r_shadow_selectedlight) r_shadow_selectedlight->selected = false; r_shadow_selectedlight = light; if (r_shadow_selectedlight) r_shadow_selectedlight->selected = true; } static void R_Shadow_DrawCursor_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { // this is never batched (there can be only one) float vertex3f[12]; R_CalcSprite_Vertex3f(vertex3f, r_editlights_cursorlocation, r_refdef.view.right, r_refdef.view.up, EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, -EDLIGHTSPRSIZE, EDLIGHTSPRSIZE); RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); R_DrawCustomSurface(r_editlights_sprcursor, &identitymatrix, MATERIALFLAG_NODEPTHTEST | MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); } static void R_Shadow_DrawLightSprite_TransparentCallback(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist) { float intensity; float s; vec3_t spritecolor; skinframe_t *skinframe; float vertex3f[12]; // this is never batched (due to the ent parameter changing every time) // so numsurfaces == 1 and surfacelist[0] == lightnumber const dlight_t *light = (dlight_t *)ent; s = EDLIGHTSPRSIZE; R_CalcSprite_Vertex3f(vertex3f, light->origin, r_refdef.view.right, r_refdef.view.up, s, -s, -s, s); intensity = 0.5f; VectorScale(light->color, intensity, spritecolor); if (VectorLength(spritecolor) < 0.1732f) VectorSet(spritecolor, 0.1f, 0.1f, 0.1f); if (VectorLength(spritecolor) > 1.0f) VectorNormalize(spritecolor); // draw light sprite if (light->cubemapname[0] && !light->shadow) skinframe = r_editlights_sprcubemapnoshadowlight; else if (light->cubemapname[0]) skinframe = r_editlights_sprcubemaplight; else if (!light->shadow) skinframe = r_editlights_sprnoshadowlight; else skinframe = r_editlights_sprlight; RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, spritecolor[0], spritecolor[1], spritecolor[2], 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); R_DrawCustomSurface(skinframe, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); // draw selection sprite if light is selected if (light->selected) { RSurf_ActiveCustomEntity(&identitymatrix, &identitymatrix, 0, 0, 1, 1, 1, 1, 4, vertex3f, spritetexcoord2f, NULL, NULL, NULL, NULL, 2, polygonelement3i, polygonelement3s, false, false); R_DrawCustomSurface(r_editlights_sprselection, &identitymatrix, MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_FULLBRIGHT | MATERIALFLAG_NOCULLFACE, 0, 4, 0, 2, false, false); // VorteX todo: add normalmode/realtime mode light overlay sprites? } } void R_Shadow_DrawLightSprites(void) { size_t lightindex; dlight_t *light; size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (light) R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, light->origin, R_Shadow_DrawLightSprite_TransparentCallback, (entity_render_t *)light, 5, &light->rtlight); } if (!r_editlights_lockcursor) R_MeshQueue_AddTransparent(TRANSPARENTSORT_DISTANCE, r_editlights_cursorlocation, R_Shadow_DrawCursor_TransparentCallback, NULL, 0, NULL); } int R_Shadow_GetRTLightInfo(unsigned int lightindex, float *origin, float *radius, float *color) { unsigned int range; dlight_t *light; rtlight_t *rtlight; range = (unsigned int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); if (lightindex >= range) return -1; light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) return 0; rtlight = &light->rtlight; //if (!(rtlight->flags & flag)) // return 0; VectorCopy(rtlight->shadoworigin, origin); *radius = rtlight->radius; VectorCopy(rtlight->color, color); return 1; } static void R_Shadow_SelectLightInView(void) { float bestrating, rating, temp[3]; dlight_t *best; size_t lightindex; dlight_t *light; size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked best = NULL; bestrating = 0; if (r_editlights_lockcursor) return; for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; VectorSubtract(light->origin, r_refdef.view.origin, temp); rating = (DotProduct(temp, r_refdef.view.forward) / sqrt(DotProduct(temp, temp))); if (rating >= 0.95) { rating /= (1 + 0.0625f * sqrt(DotProduct(temp, temp))); if (bestrating < rating && CL_TraceLine(light->origin, r_refdef.view.origin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction == 1.0f) { bestrating = rating; best = light; } } } R_Shadow_SelectLight(best); } void R_Shadow_LoadWorldLights(void) { int n, a, style, shadow, flags; char tempchar, *lightsstring, *s, *t, name[MAX_QPATH], cubemapname[MAX_QPATH]; float origin[3], radius, color[3], angles[3], corona, coronasizescale, ambientscale, diffusescale, specularscale; if (cl.worldmodel == NULL) { Con_Print("No map loaded.\n"); return; } dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); if (lightsstring) { s = lightsstring; n = 0; while (*s) { /* t = s; shadow = true; for (;COM_Parse(t, true) && strcmp( if (COM_Parse(t, true)) { if (com_token[0] == '!') { shadow = false; origin[0] = atof(com_token+1); } else origin[0] = atof(com_token); if (Com_Parse(t } */ t = s; while (*s && *s != '\n' && *s != '\r') s++; if (!*s) break; tempchar = *s; shadow = true; // check for modifier flags if (*t == '!') { shadow = false; t++; } *s = 0; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif cubemapname[sizeof(cubemapname)-1] = 0; #if MAX_QPATH != 128 #error update this code if MAX_QPATH changes #endif a = sscanf(t, "%f %f %f %f %f %f %f %d %127s %f %f %f %f %f %f %f %f %i", &origin[0], &origin[1], &origin[2], &radius, &color[0], &color[1], &color[2], &style, cubemapname #if _MSC_VER >= 1400 , sizeof(cubemapname) #endif , &corona, &angles[0], &angles[1], &angles[2], &coronasizescale, &ambientscale, &diffusescale, &specularscale, &flags); *s = tempchar; if (a < 18) flags = LIGHTFLAG_REALTIMEMODE; if (a < 17) specularscale = 1; if (a < 16) diffusescale = 1; if (a < 15) ambientscale = 0; if (a < 14) coronasizescale = 0.25f; if (a < 13) VectorClear(angles); if (a < 10) corona = 0; if (a < 9 || !strcmp(cubemapname, "\"\"")) cubemapname[0] = 0; // remove quotes on cubemapname if (cubemapname[0] == '"' && cubemapname[strlen(cubemapname) - 1] == '"') { size_t namelen; namelen = strlen(cubemapname) - 2; memmove(cubemapname, cubemapname + 1, namelen); cubemapname[namelen] = '\0'; } if (a < 8) { Con_Printf("found %d parameters on line %i, should be 8 or more parameters (origin[0] origin[1] origin[2] radius color[0] color[1] color[2] style \"cubemapname\" corona angles[0] angles[1] angles[2] coronasizescale ambientscale diffusescale specularscale flags)\n", a, n + 1); break; } R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, corona, style, shadow, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); if (*s == '\r') s++; if (*s == '\n') s++; n++; } if (*s) Con_Printf("invalid rtlights file \"%s\"\n", name); Mem_Free(lightsstring); } } void R_Shadow_SaveWorldLights(void) { size_t lightindex; dlight_t *light; size_t bufchars, bufmaxchars; char *buf, *oldbuf; char name[MAX_QPATH]; char line[MAX_INPUTLINE]; size_t range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked, assuming the dpsnprintf mess doesn't screw it up... // I hate lines which are 3 times my screen size :( --blub if (!range) return; if (cl.worldmodel == NULL) { Con_Print("No map loaded.\n"); return; } dpsnprintf(name, sizeof(name), "%s.rtlights", cl.worldnamenoextension); bufchars = bufmaxchars = 0; buf = NULL; for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; if (light->coronasizescale != 0.25f || light->ambientscale != 0 || light->diffusescale != 1 || light->specularscale != 1 || light->flags != LIGHTFLAG_REALTIMEMODE) dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f %f %f %f %f %i\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2], light->coronasizescale, light->ambientscale, light->diffusescale, light->specularscale, light->flags); else if (light->cubemapname[0] || light->corona || light->angles[0] || light->angles[1] || light->angles[2]) dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d \"%s\" %f %f %f %f\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style, light->cubemapname, light->corona, light->angles[0], light->angles[1], light->angles[2]); else dpsnprintf(line, sizeof(line), "%s%f %f %f %f %f %f %f %d\n", light->shadow ? "" : "!", light->origin[0], light->origin[1], light->origin[2], light->radius, light->color[0], light->color[1], light->color[2], light->style); if (bufchars + strlen(line) > bufmaxchars) { bufmaxchars = bufchars + strlen(line) + 2048; oldbuf = buf; buf = (char *)Mem_Alloc(tempmempool, bufmaxchars); if (oldbuf) { if (bufchars) memcpy(buf, oldbuf, bufchars); Mem_Free(oldbuf); } } if (strlen(line)) { memcpy(buf + bufchars, line, strlen(line)); bufchars += strlen(line); } } if (bufchars) FS_WriteFile(name, buf, (fs_offset_t)bufchars); if (buf) Mem_Free(buf); } void R_Shadow_LoadLightsFile(void) { int n, a, style; char tempchar, *lightsstring, *s, *t, name[MAX_QPATH]; float origin[3], radius, color[3], subtract, spotdir[3], spotcone, falloff, distbias; if (cl.worldmodel == NULL) { Con_Print("No map loaded.\n"); return; } dpsnprintf(name, sizeof(name), "%s.lights", cl.worldnamenoextension); lightsstring = (char *)FS_LoadFile(name, tempmempool, false, NULL); if (lightsstring) { s = lightsstring; n = 0; while (*s) { t = s; while (*s && *s != '\n' && *s != '\r') s++; if (!*s) break; tempchar = *s; *s = 0; a = sscanf(t, "%f %f %f %f %f %f %f %f %f %f %f %f %f %d", &origin[0], &origin[1], &origin[2], &falloff, &color[0], &color[1], &color[2], &subtract, &spotdir[0], &spotdir[1], &spotdir[2], &spotcone, &distbias, &style); *s = tempchar; if (a < 14) { Con_Printf("invalid lights file, found %d parameters on line %i, should be 14 parameters (origin[0] origin[1] origin[2] falloff light[0] light[1] light[2] subtract spotdir[0] spotdir[1] spotdir[2] spotcone distancebias style)\n", a, n + 1); break; } radius = sqrt(DotProduct(color, color) / (falloff * falloff * 8192.0f * 8192.0f)); radius = bound(15, radius, 4096); VectorScale(color, (2.0f / (8388608.0f)), color); R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, vec3_origin, color, radius, 0, style, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); if (*s == '\r') s++; if (*s == '\n') s++; n++; } if (*s) Con_Printf("invalid lights file \"%s\"\n", name); Mem_Free(lightsstring); } } // tyrlite/hmap2 light types in the delay field typedef enum lighttype_e {LIGHTTYPE_MINUSX, LIGHTTYPE_RECIPX, LIGHTTYPE_RECIPXX, LIGHTTYPE_NONE, LIGHTTYPE_SUN, LIGHTTYPE_MINUSXX} lighttype_t; void R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(void) { int entnum; int style; int islight; int skin; int pflags; //int effects; int type; int n; char *entfiledata; const char *data; float origin[3], angles[3], radius, color[3], light[4], fadescale, lightscale, originhack[3], overridecolor[3], vec[4]; char key[256], value[MAX_INPUTLINE]; char vabuf[1024]; if (cl.worldmodel == NULL) { Con_Print("No map loaded.\n"); return; } // try to load a .ent file first dpsnprintf(key, sizeof(key), "%s.ent", cl.worldnamenoextension); data = entfiledata = (char *)FS_LoadFile(key, tempmempool, true, NULL); // and if that is not found, fall back to the bsp file entity string if (!data) data = cl.worldmodel->brush.entities; if (!data) return; for (entnum = 0;COM_ParseToken_Simple(&data, false, false, true) && com_token[0] == '{';entnum++) { type = LIGHTTYPE_MINUSX; origin[0] = origin[1] = origin[2] = 0; originhack[0] = originhack[1] = originhack[2] = 0; angles[0] = angles[1] = angles[2] = 0; color[0] = color[1] = color[2] = 1; light[0] = light[1] = light[2] = 1;light[3] = 300; overridecolor[0] = overridecolor[1] = overridecolor[2] = 1; fadescale = 1; lightscale = 1; style = 0; skin = 0; pflags = 0; //effects = 0; islight = false; while (1) { if (!COM_ParseToken_Simple(&data, false, false, true)) break; // error if (com_token[0] == '}') break; // end of entity if (com_token[0] == '_') strlcpy(key, com_token + 1, sizeof(key)); else strlcpy(key, com_token, sizeof(key)); while (key[strlen(key)-1] == ' ') // remove trailing spaces key[strlen(key)-1] = 0; if (!COM_ParseToken_Simple(&data, false, false, true)) break; // error strlcpy(value, com_token, sizeof(value)); // now that we have the key pair worked out... if (!strcmp("light", key)) { n = sscanf(value, "%f %f %f %f", &vec[0], &vec[1], &vec[2], &vec[3]); if (n == 1) { // quake light[0] = vec[0] * (1.0f / 256.0f); light[1] = vec[0] * (1.0f / 256.0f); light[2] = vec[0] * (1.0f / 256.0f); light[3] = vec[0]; } else if (n == 4) { // halflife light[0] = vec[0] * (1.0f / 255.0f); light[1] = vec[1] * (1.0f / 255.0f); light[2] = vec[2] * (1.0f / 255.0f); light[3] = vec[3]; } } else if (!strcmp("delay", key)) type = atoi(value); else if (!strcmp("origin", key)) sscanf(value, "%f %f %f", &origin[0], &origin[1], &origin[2]); else if (!strcmp("angle", key)) angles[0] = 0, angles[1] = atof(value), angles[2] = 0; else if (!strcmp("angles", key)) sscanf(value, "%f %f %f", &angles[0], &angles[1], &angles[2]); else if (!strcmp("color", key)) sscanf(value, "%f %f %f", &color[0], &color[1], &color[2]); else if (!strcmp("wait", key)) fadescale = atof(value); else if (!strcmp("classname", key)) { if (!strncmp(value, "light", 5)) { islight = true; if (!strcmp(value, "light_fluoro")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 1; overridecolor[2] = 1; } if (!strcmp(value, "light_fluorospark")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 1; overridecolor[2] = 1; } if (!strcmp(value, "light_globe")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 0.8; overridecolor[2] = 0.4; } if (!strcmp(value, "light_flame_large_yellow")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 0.5; overridecolor[2] = 0.1; } if (!strcmp(value, "light_flame_small_yellow")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 0.5; overridecolor[2] = 0.1; } if (!strcmp(value, "light_torch_small_white")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 0.5; overridecolor[2] = 0.1; } if (!strcmp(value, "light_torch_small_walltorch")) { originhack[0] = 0; originhack[1] = 0; originhack[2] = 0; overridecolor[0] = 1; overridecolor[1] = 0.5; overridecolor[2] = 0.1; } } } else if (!strcmp("style", key)) style = atoi(value); else if (!strcmp("skin", key)) skin = (int)atof(value); else if (!strcmp("pflags", key)) pflags = (int)atof(value); //else if (!strcmp("effects", key)) // effects = (int)atof(value); else if (cl.worldmodel->type == mod_brushq3) { if (!strcmp("scale", key)) lightscale = atof(value); if (!strcmp("fade", key)) fadescale = atof(value); } } if (!islight) continue; if (lightscale <= 0) lightscale = 1; if (fadescale <= 0) fadescale = 1; if (color[0] == color[1] && color[0] == color[2]) { color[0] *= overridecolor[0]; color[1] *= overridecolor[1]; color[2] *= overridecolor[2]; } radius = light[3] * r_editlights_quakelightsizescale.value * lightscale / fadescale; color[0] = color[0] * light[0]; color[1] = color[1] * light[1]; color[2] = color[2] * light[2]; switch (type) { case LIGHTTYPE_MINUSX: break; case LIGHTTYPE_RECIPX: radius *= 2; VectorScale(color, (1.0f / 16.0f), color); break; case LIGHTTYPE_RECIPXX: radius *= 2; VectorScale(color, (1.0f / 16.0f), color); break; default: case LIGHTTYPE_NONE: break; case LIGHTTYPE_SUN: break; case LIGHTTYPE_MINUSXX: break; } VectorAdd(origin, originhack, origin); if (radius >= 1) R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), origin, angles, color, radius, (pflags & PFLAGS_CORONA) != 0, style, (pflags & PFLAGS_NOSHADOW) == 0, skin >= 16 ? va(vabuf, sizeof(vabuf), "cubemaps/%i", skin) : NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); } if (entfiledata) Mem_Free(entfiledata); } static void R_Shadow_SetCursorLocationForView(void) { vec_t dist, push; vec3_t dest, endpos; trace_t trace; VectorMA(r_refdef.view.origin, r_editlights_cursordistance.value, r_refdef.view.forward, dest); trace = CL_TraceLine(r_refdef.view.origin, dest, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true); if (trace.fraction < 1) { dist = trace.fraction * r_editlights_cursordistance.value; push = r_editlights_cursorpushback.value; if (push > dist) push = dist; push = -push; VectorMA(trace.endpos, push, r_refdef.view.forward, endpos); VectorMA(endpos, r_editlights_cursorpushoff.value, trace.plane.normal, endpos); } else { VectorClear( endpos ); } r_editlights_cursorlocation[0] = floor(endpos[0] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; r_editlights_cursorlocation[1] = floor(endpos[1] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; r_editlights_cursorlocation[2] = floor(endpos[2] / r_editlights_cursorgrid.value + 0.5f) * r_editlights_cursorgrid.value; } void R_Shadow_UpdateWorldLightSelection(void) { if (r_editlights.integer) { R_Shadow_SetCursorLocationForView(); R_Shadow_SelectLightInView(); } else R_Shadow_SelectLight(NULL); } static void R_Shadow_EditLights_Clear_f(void) { R_Shadow_ClearWorldLights(); } void R_Shadow_EditLights_Reload_f(void) { if (!cl.worldmodel) return; strlcpy(r_shadow_mapname, cl.worldname, sizeof(r_shadow_mapname)); R_Shadow_ClearWorldLights(); if (r_shadow_realtime_world_importlightentitiesfrommap.integer <= 1) { R_Shadow_LoadWorldLights(); if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) R_Shadow_LoadLightsFile(); } if (r_shadow_realtime_world_importlightentitiesfrommap.integer >= 1) { if (!Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)) R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); } } static void R_Shadow_EditLights_Save_f(void) { if (!cl.worldmodel) return; R_Shadow_SaveWorldLights(); } static void R_Shadow_EditLights_ImportLightEntitiesFromMap_f(void) { R_Shadow_ClearWorldLights(); R_Shadow_LoadWorldLightsFromMap_LightArghliteTyrlite(); } static void R_Shadow_EditLights_ImportLightsFile_f(void) { R_Shadow_ClearWorldLights(); R_Shadow_LoadLightsFile(); } static void R_Shadow_EditLights_Spawn_f(void) { vec3_t color; if (!r_editlights.integer) { Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); return; } if (Cmd_Argc() != 1) { Con_Print("r_editlights_spawn does not take parameters\n"); return; } color[0] = color[1] = color[2] = 1; R_Shadow_UpdateWorldLight(R_Shadow_NewWorldLight(), r_editlights_cursorlocation, vec3_origin, color, 200, 0, 0, true, NULL, 0.25, 0, 1, 1, LIGHTFLAG_REALTIMEMODE); } static void R_Shadow_EditLights_Edit_f(void) { vec3_t origin, angles, color; vec_t radius, corona, coronasizescale, ambientscale, diffusescale, specularscale; int style, shadows, flags, normalmode, realtimemode; char cubemapname[MAX_INPUTLINE]; if (!r_editlights.integer) { Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } VectorCopy(r_shadow_selectedlight->origin, origin); VectorCopy(r_shadow_selectedlight->angles, angles); VectorCopy(r_shadow_selectedlight->color, color); radius = r_shadow_selectedlight->radius; style = r_shadow_selectedlight->style; if (r_shadow_selectedlight->cubemapname) strlcpy(cubemapname, r_shadow_selectedlight->cubemapname, sizeof(cubemapname)); else cubemapname[0] = 0; shadows = r_shadow_selectedlight->shadow; corona = r_shadow_selectedlight->corona; coronasizescale = r_shadow_selectedlight->coronasizescale; ambientscale = r_shadow_selectedlight->ambientscale; diffusescale = r_shadow_selectedlight->diffusescale; specularscale = r_shadow_selectedlight->specularscale; flags = r_shadow_selectedlight->flags; normalmode = (flags & LIGHTFLAG_NORMALMODE) != 0; realtimemode = (flags & LIGHTFLAG_REALTIMEMODE) != 0; if (!strcmp(Cmd_Argv(1), "origin")) { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); return; } origin[0] = atof(Cmd_Argv(2)); origin[1] = atof(Cmd_Argv(3)); origin[2] = atof(Cmd_Argv(4)); } else if (!strcmp(Cmd_Argv(1), "originscale")) { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); return; } origin[0] *= atof(Cmd_Argv(2)); origin[1] *= atof(Cmd_Argv(3)); origin[2] *= atof(Cmd_Argv(4)); } else if (!strcmp(Cmd_Argv(1), "originx")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[0] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "originy")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[1] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "originz")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[2] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "move")) { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); return; } origin[0] += atof(Cmd_Argv(2)); origin[1] += atof(Cmd_Argv(3)); origin[2] += atof(Cmd_Argv(4)); } else if (!strcmp(Cmd_Argv(1), "movex")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[0] += atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "movey")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[1] += atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "movez")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } origin[2] += atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "angles")) { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s x y z\n", Cmd_Argv(1)); return; } angles[0] = atof(Cmd_Argv(2)); angles[1] = atof(Cmd_Argv(3)); angles[2] = atof(Cmd_Argv(4)); } else if (!strcmp(Cmd_Argv(1), "anglesx")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } angles[0] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "anglesy")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } angles[1] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "anglesz")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } angles[2] = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "color")) { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s red green blue\n", Cmd_Argv(1)); return; } color[0] = atof(Cmd_Argv(2)); color[1] = atof(Cmd_Argv(3)); color[2] = atof(Cmd_Argv(4)); } else if (!strcmp(Cmd_Argv(1), "radius")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } radius = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "colorscale")) { if (Cmd_Argc() == 3) { double scale = atof(Cmd_Argv(2)); color[0] *= scale; color[1] *= scale; color[2] *= scale; } else { if (Cmd_Argc() != 5) { Con_Printf("usage: r_editlights_edit %s red green blue (OR grey instead of red green blue)\n", Cmd_Argv(1)); return; } color[0] *= atof(Cmd_Argv(2)); color[1] *= atof(Cmd_Argv(3)); color[2] *= atof(Cmd_Argv(4)); } } else if (!strcmp(Cmd_Argv(1), "radiusscale") || !strcmp(Cmd_Argv(1), "sizescale")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } radius *= atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "style")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } style = atoi(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "cubemap")) { if (Cmd_Argc() > 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } if (Cmd_Argc() == 3) strlcpy(cubemapname, Cmd_Argv(2), sizeof(cubemapname)); else cubemapname[0] = 0; } else if (!strcmp(Cmd_Argv(1), "shadows")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } shadows = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "corona")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } corona = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "coronasize")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } coronasizescale = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "ambient")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } ambientscale = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "diffuse")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } diffusescale = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "specular")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } specularscale = atof(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "normalmode")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } normalmode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); } else if (!strcmp(Cmd_Argv(1), "realtimemode")) { if (Cmd_Argc() != 3) { Con_Printf("usage: r_editlights_edit %s value\n", Cmd_Argv(1)); return; } realtimemode = Cmd_Argv(2)[0] == 'y' || Cmd_Argv(2)[0] == 'Y' || Cmd_Argv(2)[0] == 't' || atoi(Cmd_Argv(2)); } else { Con_Print("usage: r_editlights_edit [property] [value]\n"); Con_Print("Selected light's properties:\n"); Con_Printf("Origin : %f %f %f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]); Con_Printf("Angles : %f %f %f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]); Con_Printf("Color : %f %f %f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]); Con_Printf("Radius : %f\n", r_shadow_selectedlight->radius); Con_Printf("Corona : %f\n", r_shadow_selectedlight->corona); Con_Printf("Style : %i\n", r_shadow_selectedlight->style); Con_Printf("Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no"); Con_Printf("Cubemap : %s\n", r_shadow_selectedlight->cubemapname); Con_Printf("CoronaSize : %f\n", r_shadow_selectedlight->coronasizescale); Con_Printf("Ambient : %f\n", r_shadow_selectedlight->ambientscale); Con_Printf("Diffuse : %f\n", r_shadow_selectedlight->diffusescale); Con_Printf("Specular : %f\n", r_shadow_selectedlight->specularscale); Con_Printf("NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no"); Con_Printf("RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no"); return; } flags = (normalmode ? LIGHTFLAG_NORMALMODE : 0) | (realtimemode ? LIGHTFLAG_REALTIMEMODE : 0); R_Shadow_UpdateWorldLight(r_shadow_selectedlight, origin, angles, color, radius, corona, style, shadows, cubemapname, coronasizescale, ambientscale, diffusescale, specularscale, flags); } static void R_Shadow_EditLights_EditAll_f(void) { size_t lightindex; dlight_t *light, *oldselected; size_t range; if (!r_editlights.integer) { Con_Print("Cannot edit lights when not in editing mode. Set r_editlights to 1.\n"); return; } oldselected = r_shadow_selectedlight; // EditLights doesn't seem to have a "remove" command or something so: range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; R_Shadow_SelectLight(light); R_Shadow_EditLights_Edit_f(); } // return to old selected (to not mess editing once selection is locked) R_Shadow_SelectLight(oldselected); } void R_Shadow_EditLights_DrawSelectedLightProperties(void) { int lightnumber, lightcount; size_t lightindex, range; dlight_t *light; char temp[256]; float x, y; if (!r_editlights.integer) return; // update cvars so QC can query them if (r_shadow_selectedlight) { dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]); Cvar_SetQuick(&r_editlights_current_origin, temp); dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]); Cvar_SetQuick(&r_editlights_current_angles, temp); dpsnprintf(temp, sizeof(temp), "%f %f %f", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]); Cvar_SetQuick(&r_editlights_current_color, temp); Cvar_SetValueQuick(&r_editlights_current_radius, r_shadow_selectedlight->radius); Cvar_SetValueQuick(&r_editlights_current_corona, r_shadow_selectedlight->corona); Cvar_SetValueQuick(&r_editlights_current_coronasize, r_shadow_selectedlight->coronasizescale); Cvar_SetValueQuick(&r_editlights_current_style, r_shadow_selectedlight->style); Cvar_SetValueQuick(&r_editlights_current_shadows, r_shadow_selectedlight->shadow); Cvar_SetQuick(&r_editlights_current_cubemap, r_shadow_selectedlight->cubemapname); Cvar_SetValueQuick(&r_editlights_current_ambient, r_shadow_selectedlight->ambientscale); Cvar_SetValueQuick(&r_editlights_current_diffuse, r_shadow_selectedlight->diffusescale); Cvar_SetValueQuick(&r_editlights_current_specular, r_shadow_selectedlight->specularscale); Cvar_SetValueQuick(&r_editlights_current_normalmode, (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? 1 : 0); Cvar_SetValueQuick(&r_editlights_current_realtimemode, (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? 1 : 0); } // draw properties on screen if (!r_editlights_drawproperties.integer) return; x = vid_conwidth.value - 240; y = 5; DrawQ_Pic(x-5, y-5, NULL, 250, 155, 0, 0, 0, 0.75, 0); lightnumber = -1; lightcount = 0; range = Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked for (lightindex = 0;lightindex < range;lightindex++) { light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex); if (!light) continue; if (light == r_shadow_selectedlight) lightnumber = (int)lightindex; lightcount++; } dpsnprintf(temp, sizeof(temp), "Cursor origin: %.0f %.0f %.0f", r_editlights_cursorlocation[0], r_editlights_cursorlocation[1], r_editlights_cursorlocation[2]); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Total lights : %i active (%i total)", lightcount, (int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray)); DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, false, FONT_DEFAULT);y += 8; y += 8; if (r_shadow_selectedlight == NULL) return; dpsnprintf(temp, sizeof(temp), "Light #%i properties:", lightnumber);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Origin : %.0f %.0f %.0f\n", r_shadow_selectedlight->origin[0], r_shadow_selectedlight->origin[1], r_shadow_selectedlight->origin[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Angles : %.0f %.0f %.0f\n", r_shadow_selectedlight->angles[0], r_shadow_selectedlight->angles[1], r_shadow_selectedlight->angles[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Color : %.2f %.2f %.2f\n", r_shadow_selectedlight->color[0], r_shadow_selectedlight->color[1], r_shadow_selectedlight->color[2]);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Radius : %.0f\n", r_shadow_selectedlight->radius);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Corona : %.0f\n", r_shadow_selectedlight->corona);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Style : %i\n", r_shadow_selectedlight->style);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Shadows : %s\n", r_shadow_selectedlight->shadow ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Cubemap : %s\n", r_shadow_selectedlight->cubemapname);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "CoronaSize : %.2f\n", r_shadow_selectedlight->coronasizescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Ambient : %.2f\n", r_shadow_selectedlight->ambientscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Diffuse : %.2f\n", r_shadow_selectedlight->diffusescale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "Specular : %.2f\n", r_shadow_selectedlight->specularscale);DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "NormalMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_NORMALMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; dpsnprintf(temp, sizeof(temp), "RealTimeMode : %s\n", (r_shadow_selectedlight->flags & LIGHTFLAG_REALTIMEMODE) ? "yes" : "no");DrawQ_String(x, y, temp, 0, 8, 8, 1, 1, 1, 1, 0, NULL, true, FONT_DEFAULT);y += 8; } static void R_Shadow_EditLights_ToggleShadow_f(void) { if (!r_editlights.integer) { Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, r_shadow_selectedlight->corona, r_shadow_selectedlight->style, !r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); } static void R_Shadow_EditLights_ToggleCorona_f(void) { if (!r_editlights.integer) { Con_Print("Cannot spawn light when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_selectedlight->angles, r_shadow_selectedlight->color, r_shadow_selectedlight->radius, !r_shadow_selectedlight->corona, r_shadow_selectedlight->style, r_shadow_selectedlight->shadow, r_shadow_selectedlight->cubemapname, r_shadow_selectedlight->coronasizescale, r_shadow_selectedlight->ambientscale, r_shadow_selectedlight->diffusescale, r_shadow_selectedlight->specularscale, r_shadow_selectedlight->flags); } static void R_Shadow_EditLights_Remove_f(void) { if (!r_editlights.integer) { Con_Print("Cannot remove light when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } R_Shadow_FreeWorldLight(r_shadow_selectedlight); r_shadow_selectedlight = NULL; } static void R_Shadow_EditLights_Help_f(void) { Con_Print( "Documentation on r_editlights system:\n" "Settings:\n" "r_editlights : enable/disable editing mode\n" "r_editlights_cursordistance : maximum distance of cursor from eye\n" "r_editlights_cursorpushback : push back cursor this far from surface\n" "r_editlights_cursorpushoff : push cursor off surface this far\n" "r_editlights_cursorgrid : snap cursor to grid of this size\n" "r_editlights_quakelightsizescale : imported quake light entity size scaling\n" "Commands:\n" "r_editlights_help : this help\n" "r_editlights_clear : remove all lights\n" "r_editlights_reload : reload .rtlights, .lights file, or entities\n" "r_editlights_lock : lock selection to current light, if already locked - unlock\n" "r_editlights_save : save to .rtlights file\n" "r_editlights_spawn : create a light with default settings\n" "r_editlights_edit command : edit selected light - more documentation below\n" "r_editlights_remove : remove selected light\n" "r_editlights_toggleshadow : toggles on/off selected light's shadow property\n" "r_editlights_importlightentitiesfrommap : reload light entities\n" "r_editlights_importlightsfile : reload .light file (produced by hlight)\n" "Edit commands:\n" "origin x y z : set light location\n" "originx x: set x component of light location\n" "originy y: set y component of light location\n" "originz z: set z component of light location\n" "move x y z : adjust light location\n" "movex x: adjust x component of light location\n" "movey y: adjust y component of light location\n" "movez z: adjust z component of light location\n" "angles x y z : set light angles\n" "anglesx x: set x component of light angles\n" "anglesy y: set y component of light angles\n" "anglesz z: set z component of light angles\n" "color r g b : set color of light (can be brighter than 1 1 1)\n" "radius radius : set radius (size) of light\n" "colorscale grey : multiply color of light (1 does nothing)\n" "colorscale r g b : multiply color of light (1 1 1 does nothing)\n" "radiusscale scale : multiply radius (size) of light (1 does nothing)\n" "sizescale scale : multiply radius (size) of light (1 does nothing)\n" "originscale x y z : multiply origin of light (1 1 1 does nothing)\n" "style style : set lightstyle of light (flickering patterns, switches, etc)\n" "cubemap basename : set filter cubemap of light\n" "shadows 1/0 : turn on/off shadows\n" "corona n : set corona intensity\n" "coronasize n : set corona size (0-1)\n" "ambient n : set ambient intensity (0-1)\n" "diffuse n : set diffuse intensity (0-1)\n" "specular n : set specular intensity (0-1)\n" "normalmode 1/0 : turn on/off rendering of this light in rtworld 0 mode\n" "realtimemode 1/0 : turn on/off rendering of this light in rtworld 1 mode\n" " : print light properties to console\n" ); } static void R_Shadow_EditLights_CopyInfo_f(void) { if (!r_editlights.integer) { Con_Print("Cannot copy light info when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } VectorCopy(r_shadow_selectedlight->angles, r_shadow_bufferlight.angles); VectorCopy(r_shadow_selectedlight->color, r_shadow_bufferlight.color); r_shadow_bufferlight.radius = r_shadow_selectedlight->radius; r_shadow_bufferlight.style = r_shadow_selectedlight->style; if (r_shadow_selectedlight->cubemapname) strlcpy(r_shadow_bufferlight.cubemapname, r_shadow_selectedlight->cubemapname, sizeof(r_shadow_bufferlight.cubemapname)); else r_shadow_bufferlight.cubemapname[0] = 0; r_shadow_bufferlight.shadow = r_shadow_selectedlight->shadow; r_shadow_bufferlight.corona = r_shadow_selectedlight->corona; r_shadow_bufferlight.coronasizescale = r_shadow_selectedlight->coronasizescale; r_shadow_bufferlight.ambientscale = r_shadow_selectedlight->ambientscale; r_shadow_bufferlight.diffusescale = r_shadow_selectedlight->diffusescale; r_shadow_bufferlight.specularscale = r_shadow_selectedlight->specularscale; r_shadow_bufferlight.flags = r_shadow_selectedlight->flags; } static void R_Shadow_EditLights_PasteInfo_f(void) { if (!r_editlights.integer) { Con_Print("Cannot paste light info when not in editing mode. Set r_editlights to 1.\n"); return; } if (!r_shadow_selectedlight) { Con_Print("No selected light.\n"); return; } R_Shadow_UpdateWorldLight(r_shadow_selectedlight, r_shadow_selectedlight->origin, r_shadow_bufferlight.angles, r_shadow_bufferlight.color, r_shadow_bufferlight.radius, r_shadow_bufferlight.corona, r_shadow_bufferlight.style, r_shadow_bufferlight.shadow, r_shadow_bufferlight.cubemapname, r_shadow_bufferlight.coronasizescale, r_shadow_bufferlight.ambientscale, r_shadow_bufferlight.diffusescale, r_shadow_bufferlight.specularscale, r_shadow_bufferlight.flags); } static void R_Shadow_EditLights_Lock_f(void) { if (!r_editlights.integer) { Con_Print("Cannot lock on light when not in editing mode. Set r_editlights to 1.\n"); return; } if (r_editlights_lockcursor) { r_editlights_lockcursor = false; return; } if (!r_shadow_selectedlight) { Con_Print("No selected light to lock on.\n"); return; } r_editlights_lockcursor = true; } static void R_Shadow_EditLights_Init(void) { Cvar_RegisterVariable(&r_editlights); Cvar_RegisterVariable(&r_editlights_cursordistance); Cvar_RegisterVariable(&r_editlights_cursorpushback); Cvar_RegisterVariable(&r_editlights_cursorpushoff); Cvar_RegisterVariable(&r_editlights_cursorgrid); Cvar_RegisterVariable(&r_editlights_quakelightsizescale); Cvar_RegisterVariable(&r_editlights_drawproperties); Cvar_RegisterVariable(&r_editlights_current_origin); Cvar_RegisterVariable(&r_editlights_current_angles); Cvar_RegisterVariable(&r_editlights_current_color); Cvar_RegisterVariable(&r_editlights_current_radius); Cvar_RegisterVariable(&r_editlights_current_corona); Cvar_RegisterVariable(&r_editlights_current_coronasize); Cvar_RegisterVariable(&r_editlights_current_style); Cvar_RegisterVariable(&r_editlights_current_shadows); Cvar_RegisterVariable(&r_editlights_current_cubemap); Cvar_RegisterVariable(&r_editlights_current_ambient); Cvar_RegisterVariable(&r_editlights_current_diffuse); Cvar_RegisterVariable(&r_editlights_current_specular); Cvar_RegisterVariable(&r_editlights_current_normalmode); Cvar_RegisterVariable(&r_editlights_current_realtimemode); Cmd_AddCommand("r_editlights_help", R_Shadow_EditLights_Help_f, "prints documentation on console commands and variables in rtlight editing system"); Cmd_AddCommand("r_editlights_clear", R_Shadow_EditLights_Clear_f, "removes all world lights (let there be darkness!)"); Cmd_AddCommand("r_editlights_reload", R_Shadow_EditLights_Reload_f, "reloads rtlights file (or imports from .lights file or .ent file or the map itself)"); Cmd_AddCommand("r_editlights_save", R_Shadow_EditLights_Save_f, "save .rtlights file for current level"); Cmd_AddCommand("r_editlights_spawn", R_Shadow_EditLights_Spawn_f, "creates a light with default properties (let there be light!)"); Cmd_AddCommand("r_editlights_edit", R_Shadow_EditLights_Edit_f, "changes a property on the selected light"); Cmd_AddCommand("r_editlights_editall", R_Shadow_EditLights_EditAll_f, "changes a property on ALL lights at once (tip: use radiusscale and colorscale to alter these properties)"); Cmd_AddCommand("r_editlights_remove", R_Shadow_EditLights_Remove_f, "remove selected light"); Cmd_AddCommand("r_editlights_toggleshadow", R_Shadow_EditLights_ToggleShadow_f, "toggle on/off the shadow option on the selected light"); Cmd_AddCommand("r_editlights_togglecorona", R_Shadow_EditLights_ToggleCorona_f, "toggle on/off the corona option on the selected light"); Cmd_AddCommand("r_editlights_importlightentitiesfrommap", R_Shadow_EditLights_ImportLightEntitiesFromMap_f, "load lights from .ent file or map entities (ignoring .rtlights or .lights file)"); Cmd_AddCommand("r_editlights_importlightsfile", R_Shadow_EditLights_ImportLightsFile_f, "load lights from .lights file (ignoring .rtlights or .ent files and map entities)"); Cmd_AddCommand("r_editlights_copyinfo", R_Shadow_EditLights_CopyInfo_f, "store a copy of all properties (except origin) of the selected light"); Cmd_AddCommand("r_editlights_pasteinfo", R_Shadow_EditLights_PasteInfo_f, "apply the stored properties onto the selected light (making it exactly identical except for origin)"); Cmd_AddCommand("r_editlights_lock", R_Shadow_EditLights_Lock_f, "lock selection to current light, if already locked - unlock"); } /* ============================================================================= LIGHT SAMPLING ============================================================================= */ void R_LightPoint(float *color, const vec3_t p, const int flags) { int i, numlights, flag; float f, relativepoint[3], dist, dist2, lightradius2; vec3_t diffuse, n; rtlight_t *light; dlight_t *dlight; if (r_fullbright.integer) { VectorSet(color, 1, 1, 1); return; } VectorClear(color); if (flags & LP_LIGHTMAP) { if (!r_fullbright.integer && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) { VectorClear(diffuse); r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, color, diffuse, n); VectorAdd(color, diffuse, color); } else VectorSet(color, 1, 1, 1); color[0] += r_refdef.scene.ambient; color[1] += r_refdef.scene.ambient; color[2] += r_refdef.scene.ambient; } if (flags & LP_RTWORLD) { flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; numlights = (int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); for (i = 0; i < numlights; i++) { dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); if (!dlight) continue; light = &dlight->rtlight; if (!(light->flags & flag)) continue; // sample lightradius2 = light->radius * light->radius; VectorSubtract(light->shadoworigin, p, relativepoint); dist2 = VectorLength2(relativepoint); if (dist2 >= lightradius2) continue; dist = sqrt(dist2) / light->radius; f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; if (f <= 0) continue; // todo: add to both ambient and diffuse if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction == 1) VectorMA(color, f, light->currentcolor, color); } } if (flags & LP_DYNLIGHT) { // sample dlights for (i = 0;i < r_refdef.scene.numlights;i++) { light = r_refdef.scene.lights[i]; // sample lightradius2 = light->radius * light->radius; VectorSubtract(light->shadoworigin, p, relativepoint); dist2 = VectorLength2(relativepoint); if (dist2 >= lightradius2) continue; dist = sqrt(dist2) / light->radius; f = dist < 1 ? (r_shadow_lightintensityscale.value * ((1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist))) : 0; if (f <= 0) continue; // todo: add to both ambient and diffuse if (!light->shadow || CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction == 1) VectorMA(color, f, light->color, color); } } } void R_CompleteLightPoint(vec3_t ambient, vec3_t diffuse, vec3_t lightdir, const vec3_t p, const int flags) { int i, numlights, flag; rtlight_t *light; dlight_t *dlight; float relativepoint[3]; float color[3]; float dir[3]; float dist; float dist2; float intensity; float sample[5*3]; float lightradius2; if (r_fullbright.integer) { VectorSet(ambient, 1, 1, 1); VectorClear(diffuse); VectorClear(lightdir); return; } if (flags == LP_LIGHTMAP) { VectorSet(ambient, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); VectorClear(diffuse); VectorClear(lightdir); if (r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, ambient, diffuse, lightdir); else VectorSet(ambient, 1, 1, 1); return; } memset(sample, 0, sizeof(sample)); VectorSet(sample, r_refdef.scene.ambient, r_refdef.scene.ambient, r_refdef.scene.ambient); if ((flags & LP_LIGHTMAP) && r_refdef.scene.worldmodel && r_refdef.scene.worldmodel->lit && r_refdef.scene.worldmodel->brush.LightPoint) { vec3_t tempambient; VectorClear(tempambient); VectorClear(color); VectorClear(relativepoint); r_refdef.scene.worldmodel->brush.LightPoint(r_refdef.scene.worldmodel, p, tempambient, color, relativepoint); VectorScale(tempambient, r_refdef.lightmapintensity, tempambient); VectorScale(color, r_refdef.lightmapintensity, color); VectorAdd(sample, tempambient, sample); VectorMA(sample , 0.5f , color, sample ); VectorMA(sample + 3, relativepoint[0], color, sample + 3); VectorMA(sample + 6, relativepoint[1], color, sample + 6); VectorMA(sample + 9, relativepoint[2], color, sample + 9); // calculate a weighted average light direction as well intensity = VectorLength(color); VectorMA(sample + 12, intensity, relativepoint, sample + 12); } if (flags & LP_RTWORLD) { flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE; numlights = (int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); for (i = 0; i < numlights; i++) { dlight = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, i); if (!dlight) continue; light = &dlight->rtlight; if (!(light->flags & flag)) continue; // sample lightradius2 = light->radius * light->radius; VectorSubtract(light->shadoworigin, p, relativepoint); dist2 = VectorLength2(relativepoint); if (dist2 >= lightradius2) continue; dist = sqrt(dist2) / light->radius; intensity = min(1.0f, (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist)) * r_shadow_lightintensityscale.value; if (intensity <= 0.0f) continue; if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction < 1) continue; // scale down intensity to add to both ambient and diffuse //intensity *= 0.5f; VectorNormalize(relativepoint); VectorScale(light->currentcolor, intensity, color); VectorMA(sample , 0.5f , color, sample ); VectorMA(sample + 3, relativepoint[0], color, sample + 3); VectorMA(sample + 6, relativepoint[1], color, sample + 6); VectorMA(sample + 9, relativepoint[2], color, sample + 9); // calculate a weighted average light direction as well intensity *= VectorLength(color); VectorMA(sample + 12, intensity, relativepoint, sample + 12); } // FIXME: sample bouncegrid too! } if (flags & LP_DYNLIGHT) { // sample dlights for (i = 0;i < r_refdef.scene.numlights;i++) { light = r_refdef.scene.lights[i]; // sample lightradius2 = light->radius * light->radius; VectorSubtract(light->shadoworigin, p, relativepoint); dist2 = VectorLength2(relativepoint); if (dist2 >= lightradius2) continue; dist = sqrt(dist2) / light->radius; intensity = (1.0f - dist) * r_shadow_lightattenuationlinearscale.value / (r_shadow_lightattenuationdividebias.value + dist*dist) * r_shadow_lightintensityscale.value; if (intensity <= 0.0f) continue; if (light->shadow && CL_TraceLine(p, light->shadoworigin, MOVE_NOMONSTERS, NULL, SUPERCONTENTS_SOLID, SUPERCONTENTS_SKY, collision_extendmovelength.value, true, false, NULL, false, true).fraction < 1) continue; // scale down intensity to add to both ambient and diffuse //intensity *= 0.5f; VectorNormalize(relativepoint); VectorScale(light->currentcolor, intensity, color); VectorMA(sample , 0.5f , color, sample ); VectorMA(sample + 3, relativepoint[0], color, sample + 3); VectorMA(sample + 6, relativepoint[1], color, sample + 6); VectorMA(sample + 9, relativepoint[2], color, sample + 9); // calculate a weighted average light direction as well intensity *= VectorLength(color); VectorMA(sample + 12, intensity, relativepoint, sample + 12); } } // calculate the direction we'll use to reduce the sample to a directional light source VectorCopy(sample + 12, dir); //VectorSet(dir, sample[3] + sample[4] + sample[5], sample[6] + sample[7] + sample[8], sample[9] + sample[10] + sample[11]); VectorNormalize(dir); // extract the diffuse color along the chosen direction and scale it diffuse[0] = (dir[0]*sample[3] + dir[1]*sample[6] + dir[2]*sample[ 9] + sample[ 0]); diffuse[1] = (dir[0]*sample[4] + dir[1]*sample[7] + dir[2]*sample[10] + sample[ 1]); diffuse[2] = (dir[0]*sample[5] + dir[1]*sample[8] + dir[2]*sample[11] + sample[ 2]); // subtract some of diffuse from ambient VectorMA(sample, -0.333f, diffuse, ambient); // store the normalized lightdir VectorCopy(dir, lightdir); } darkplaces/model_iqm.h0000664000175000017500000000461713067716222014314 0ustar kalevkalev#ifndef __MODEL_IQM_H__ #define __MODEL_IQM_H__ typedef struct iqmheader_s { char id[16]; unsigned int version; unsigned int filesize; unsigned int flags; unsigned int num_text, ofs_text; unsigned int num_meshes, ofs_meshes; unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; unsigned int num_triangles, ofs_triangles, ofs_neighbors; unsigned int num_joints, ofs_joints; unsigned int num_poses, ofs_poses; unsigned int num_anims, ofs_anims; unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; unsigned int num_comment, ofs_comment; unsigned int num_extensions, ofs_extensions; } iqmheader_t; typedef struct iqmmesh_s { unsigned int name; unsigned int material; unsigned int first_vertex, num_vertexes; unsigned int first_triangle, num_triangles; } iqmmesh_t; #define IQM_POSITION 0 #define IQM_TEXCOORD 1 #define IQM_NORMAL 2 #define IQM_TANGENT 3 #define IQM_BLENDINDEXES 4 #define IQM_BLENDWEIGHTS 5 #define IQM_COLOR 6 #define IQM_CUSTOM 0x10 #define IQM_BYTE 0 #define IQM_UBYTE 1 #define IQM_SHORT 2 #define IQM_USHORT 3 #define IQM_INT 4 #define IQM_UINT 5 #define IQM_HALF 6 #define IQM_FLOAT 7 #define IQM_DOUBLE 8 // animflags #define IQM_LOOP 1 typedef struct iqmtriangle_s { unsigned int vertex[3]; } iqmtriangle_t; typedef struct iqmjoint1_s { unsigned int name; signed int parent; float origin[3], rotation[3], scale[3]; } iqmjoint1_t; typedef struct iqmjoint_s { unsigned int name; signed int parent; float origin[3], rotation[4], scale[3]; } iqmjoint_t; typedef struct iqmpose1_s { signed int parent; unsigned int channelmask; float channeloffset[9], channelscale[9]; } iqmpose1_t; typedef struct iqmpose_s { signed int parent; unsigned int channelmask; float channeloffset[10], channelscale[10]; } iqmpose_t; typedef struct iqmanim_s { unsigned int name; unsigned int first_frame, num_frames; float framerate; unsigned int flags; } iqmanim_t; typedef struct iqmvertexarray_s { unsigned int type; unsigned int flags; unsigned int format; unsigned int size; unsigned int offset; } iqmvertexarray_t; typedef struct iqmextension_s { unsigned int name; unsigned int num_data, ofs_data; unsigned int ofs_extensions; // pointer to next extension } iqmextension_t; typedef struct iqmbounds_s { float mins[3], maxs[3]; float xyradius, radius; } iqmbounds_t; #endif darkplaces/thread_pthread.c0000664000175000017500000001410113067716222015304 0ustar kalevkalev#include "quakedef.h" #include "thread.h" #ifdef THREADRECURSIVE #define __USE_UNIX98 #include #endif #include int Thread_Init(void) { return 0; } void Thread_Shutdown(void) { } qboolean Thread_HasThreads(void) { return true; } void *_Thread_CreateMutex(const char *filename, int fileline) { #ifdef THREADRECURSIVE pthread_mutexattr_t attr; #endif pthread_mutex_t *mutexp = (pthread_mutex_t *) Z_Malloc(sizeof(pthread_mutex_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex create %s:%i\n" , mutexp, filename, fileline); #endif #ifdef THREADRECURSIVE pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(mutexp, &attr); pthread_mutexattr_destroy(&attr); #else pthread_mutex_init(mutexp, NULL); #endif return mutexp; } void _Thread_DestroyMutex(void *mutex, const char *filename, int fileline) { pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex destroy %s:%i\n", mutex, filename, fileline); #endif pthread_mutex_destroy(mutexp); Z_Free(mutexp); } int _Thread_LockMutex(void *mutex, const char *filename, int fileline) { pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex lock %s:%i\n" , mutex, filename, fileline); #endif return pthread_mutex_lock(mutexp); } int _Thread_UnlockMutex(void *mutex, const char *filename, int fileline) { pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p mutex unlock %s:%i\n" , mutex, filename, fileline); #endif return pthread_mutex_unlock(mutexp); } void *_Thread_CreateCond(const char *filename, int fileline) { pthread_cond_t *condp = (pthread_cond_t *) Z_Malloc(sizeof(pthread_cond_t)); pthread_cond_init(condp, NULL); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond create %s:%i\n" , condp, filename, fileline); #endif return condp; } void _Thread_DestroyCond(void *cond, const char *filename, int fileline) { pthread_cond_t *condp = (pthread_cond_t *) cond; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond destroy %s:%i\n" , cond, filename, fileline); #endif pthread_cond_destroy(condp); Z_Free(condp); } int _Thread_CondSignal(void *cond, const char *filename, int fileline) { pthread_cond_t *condp = (pthread_cond_t *) cond; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond signal %s:%i\n" , cond, filename, fileline); #endif return pthread_cond_signal(condp); } int _Thread_CondBroadcast(void *cond, const char *filename, int fileline) { pthread_cond_t *condp = (pthread_cond_t *) cond; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond broadcast %s:%i\n" , cond, filename, fileline); #endif return pthread_cond_broadcast(condp); } int _Thread_CondWait(void *cond, void *mutex, const char *filename, int fileline) { pthread_cond_t *condp = (pthread_cond_t *) cond; pthread_mutex_t *mutexp = (pthread_mutex_t *) mutex; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p cond wait %s:%i\n" , cond, filename, fileline); #endif return pthread_cond_wait(condp, mutexp); } void *_Thread_CreateThread(int (*fn)(void *), void *data, const char *filename, int fileline) { pthread_t *threadp = (pthread_t *) Z_Malloc(sizeof(pthread_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread create %s:%i\n" , threadp, filename, fileline); #endif int r = pthread_create(threadp, NULL, (void * (*) (void *)) fn, data); if(r) { Z_Free(threadp); return NULL; } return threadp; } int _Thread_WaitThread(void *thread, int retval, const char *filename, int fileline) { pthread_t *threadp = (pthread_t *) thread; void *status = (void *) (intptr_t) retval; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p thread wait %s:%i\n" , thread, filename, fileline); #endif pthread_join(*threadp, &status); Z_Free(threadp); return (int) (intptr_t) status; } #ifdef PTHREAD_BARRIER_SERIAL_THREAD void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) { pthread_barrier_t *b = (pthread_barrier_t *) Z_Malloc(sizeof(pthread_barrier_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); #endif pthread_barrier_init(b, NULL, count); return (void *) b; } void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) { pthread_barrier_t *b = (pthread_barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); #endif pthread_barrier_destroy(b); } void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) { pthread_barrier_t *b = (pthread_barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); #endif pthread_barrier_wait(b); } #else // standard barrier implementation using conds and mutexes // see: http://www.howforge.com/implementing-barrier-in-pthreads typedef struct { unsigned int needed; unsigned int called; void *mutex; void *cond; } barrier_t; void *_Thread_CreateBarrier(unsigned int count, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) Z_Malloc(sizeof(barrier_t)); #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier create(%d) %s:%i\n", b, count, filename, fileline); #endif b->needed = count; b->called = 0; b->mutex = Thread_CreateMutex(); b->cond = Thread_CreateCond(); return (void *) b; } void _Thread_DestroyBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier destroy %s:%i\n", b, filename, fileline); #endif Thread_DestroyMutex(b->mutex); Thread_DestroyCond(b->cond); } void _Thread_WaitBarrier(void *barrier, const char *filename, int fileline) { volatile barrier_t *b = (volatile barrier_t *) barrier; #ifdef THREADDEBUG Sys_PrintfToTerminal("%p barrier wait %s:%i\n", b, filename, fileline); #endif Thread_LockMutex(b->mutex); b->called++; if (b->called == b->needed) { b->called = 0; Thread_CondBroadcast(b->cond); } else { do { Thread_CondWait(b->cond, b->mutex); } while(b->called); } Thread_UnlockMutex(b->mutex); } #endif darkplaces/darkplaces32x32.png0000664000175000017500000000402413067716220015504 0ustar kalevkalev‰PNG  IHDR szzôÛIDATxœ—}LUçÇ?¸/À„^P¢ˆÚ¢¢¶r˜¦Vݦ³f-Y³˜¸®›éìâœ,fë²Î½9Ó «ëL»d‰Ë¦¶ÊšlÃM…Æ7ÔŠPQ|¹¼]îUÞ/÷…ûÎÝÏs­uêINÈ%Ïù}Ïïûý}Ÿß“Ì#<š¦¥VWWïëììlTU5SUÕ„ªª1·Ûx”8ŸäGY¬ªª-Ò}ù°ÛîÆ€RU5ñ¸I$=âzË»ÿ¨x˜Ø3 <8@ÊÃ.Ô4M¬O3gÎ,t83€›À- ¢iš±TÆ«ëú+óÐ Ê’sgt¶7‘——7Íáp¸| sñâÅéV«Õž››»¸££ã\cccBš¦…€0Õu}ìÞ B©×é¬ Ú&ñú‹« é@Aiii^zzú“II‰‚[­Me jÞÓótýê4 ŸÌŸ?°P&š¡išYVrüyhªªj‡|¯t{%“gÏãRss èïëë»ÙÑÑÑÞÙÙu »/e©×ªê?yAæÕ±Î^ù£%---=j"@\UÕ¸!ÚŠGÓ´À X€'¦ådþ*-§ð›þ¾Ÿòòòa 8\b@.POyªº:;m¡D‚òòòŸMÀe`éº>v_ hšf,eeeSšj¾Ø7ò €üø«ÏA,B?é@ªAð> ¸€[™vûH¸³ó‡uû~¡¬\¹ò§'Ož|ˆ-@\Ó´ÈçÐ4Í d¾LëåÃïé“?*++ëNg–šnú׺3y ~–Ë“¸££à|2‰~ÀSUUÅÊìØšH$ÒÖ®][Ù××ç O¾|ùògµ#GjÚN¹c¶¯løÖu“Å:ìõzÓ·/˜üËM¯Ú==+¥æø©Ð |tIà¨ÜaDø=âñxÖ¬üØìÊ~j’Óéúd‚Á¤ à)@fuõÞ³Wë?bMå‹}­×ndÈ]]‰xlä•_ÿÎø$‚l/„+}—”%HóÖ­[ïÚ}‚={ö˜€ùÀ„^,ÛÐÚÖÖv¡æ·ù[v9»M’×k@#Ð0·hZ{Ø{ø˜–` Ãtt]7 ƒn·ûŸ5wp²"µÖ$c÷eeey~¿¿ø?yŸÚÚÚ  Ü´Ê 8]>€ÉdJBt¡Eƹ·«Æ€Ñ””W¹Q¶HÄL²‹!BsÅ—Š÷§´ªÆ‡& Þ´.\¸pèСC‡æM2~gÁ.þëƒäüÙó²n x¬@¢l@XÓ´˜“zˆ&+JÄl6›ˆ¶NÌFÖw¾»}Yíå#TT¬3Êë•åïinnv•””¼‡ÍŸÁBÌäÜðŒϘQTT\ü¼»pÊôüÉ1»Ý^ØÕÕuêÌ™3’ÿÜít­¾SEÆWÅHÀR9p”Œ ›rû6a™À0ÄûÐ äJ{G‡§®¾~㊪ß[¶l©mhhð"ü`¡kƹS›®|/™];~î—xq„x†Íž«xEQü{'‰nàðp¨ά~~õþ#À¶mÛü ³B SRblø9Ž Gù侀ŠŒ7‚ht¼ .®Ï Nž¥’––f§Q lôµW&Ñ œ.ͱx"P]]m“ZH—2™*£-fàÍ»¢^¯7pI7¬ÂF‘?Y¾|6uÊ æø¯%#)3wŸ–qI…OÒÒô¨}ÖXmRÍ,x×[;3·oÙDûP$(itm’Ê¡HK{÷_B°*²`%Ùƒg-ÃÃÃÆnŒ‰Ç.Æ`¼å"þϘEĈÊÄ3€¬®Ÿ¾îЭM³ó}µ M†W´Wv„‡&úxǼü|Gÿ‰|ÝÔoBÅTɧéžj¦£Ô¨ûZPÅdÏÍKÑu}…®ëoÍkö²:«éÆpÒ,ÁuYÿD „WjAÁÎ?lóÿ¾”VZZ:˜ÌD˜†YÓ´{˜äéöŒÄ¾}ûpÞtˆÅ’ÖWT¸4M»Ú£imN÷íbl;'ë’¾7(ˆ#DÖVþÆá~xs}o«-{Ù²—NŸ>Ý+w0¬iZP®H²L³õ640uóæÐàð!Þ>ùzd©»€ Gr?ŠÑDÊr†ä¢F›¦ýþS·Ç·h÷î슢Ô7–.]ú}Ä$<1^MB .š”â fç"!Z¬UîöphÿsÉÒ‡ùpü8VUÕ°Î %[{.¨ªeD±%†?ž³<#¼K–®©(+++âv»½€2CÍM{­bÙš{ÿu¹\a„ÀÎ#°tP‡€ØÄáô®ÃCrœ C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL2-2.0\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x86;C:\dev\SDL2-2.0\lib\x86;$(LibraryPath) <_PropertySheetDisplayName>vs2010_win32 darkplaces/darkplaces-sdl.dsp0000664000175000017500000003425413067716220015574 0ustar kalevkalev# Microsoft Developer Studio Project File - Name="darkplaces-sdl" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Application" 0x0101 CFG=darkplaces-sdl - Win32 Debug !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE !MESSAGE NMAKE /f "darkplaces-sdl.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE !MESSAGE NMAKE /f "darkplaces-sdl.mak" CFG="darkplaces-sdl - Win32 Debug" !MESSAGE !MESSAGE Possible choices for configuration are: !MESSAGE !MESSAGE "darkplaces-sdl - Win32 Release" (based on "Win32 (x86) Application") !MESSAGE "darkplaces-sdl - Win32 Debug" (based on "Win32 (x86) Application") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 # PROP Scc_ProjName "" # PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe !IF "$(CFG)" == "darkplaces-sdl - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 # PROP BASE Output_Dir "Release" # PROP BASE Intermediate_Dir "Release" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 # PROP Output_Dir "Release-SDL" # PROP Intermediate_Dir "Release-SDL" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD CPP /nologo /MD /W3 /GX /Ox /Ot /Og /Oi /Op /I "SDL/include" /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "NDEBUG" # ADD RSC /l 0x40c /d "NDEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /machine:I386 # ADD LINK32 ws2_32.lib winmm.lib sdl.lib sdlmain.lib user32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /machine:I386 /libpath:"SDL/lib" # SUBTRACT LINK32 /pdb:none !ELSEIF "$(CFG)" == "darkplaces-sdl - Win32 Debug" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 1 # PROP BASE Output_Dir "Debug" # PROP BASE Intermediate_Dir "Debug" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 # PROP Output_Dir "Debug-SDL" # PROP Intermediate_Dir "Debug-SDL" # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "SDL/include" /D "WIN32" /D "WIN32_LEAN_AND_MEAN" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /D "_FILE_OFFSET_BITS=64" /D "__KERNEL_STRICT_NAMES" /YX /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x40c /d "_DEBUG" # ADD RSC /l 0x40c /d "_DEBUG" BSC32=bscmake.exe # ADD BASE BSC32 /nologo # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept # ADD LINK32 ws2_32.lib winmm.lib sdl.lib sdlmain.lib user32.lib /nologo /subsystem:console /LARGEADDRESSAWARE /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"Debug-SDL/darkplaces-sdl-debug.exe" /pdbtype:sept /libpath:"SDL/lib" # SUBTRACT LINK32 /pdb:none !ENDIF # Begin Target # Name "darkplaces-sdl - Win32 Release" # Name "darkplaces-sdl - Win32 Debug" # Begin Group "Source Files" # PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" # Begin Source File SOURCE=.\builddate.c # End Source File # Begin Source File SOURCE=.\cd_sdl.c # End Source File # Begin Source File SOURCE=.\cd_shared.c # End Source File # Begin Source File SOURCE=.\cl_collision.c # End Source File # Begin Source File SOURCE=.\cl_demo.c # End Source File # Begin Source File SOURCE=.\cl_dyntexture.c # End Source File # Begin Source File SOURCE=.\cl_gecko.c # End Source File # Begin Source File SOURCE=.\cl_input.c # End Source File # Begin Source File SOURCE=.\cl_main.c # End Source File # Begin Source File SOURCE=.\cl_parse.c # End Source File # Begin Source File SOURCE=.\cl_particles.c # End Source File # Begin Source File SOURCE=.\cl_screen.c # End Source File # Begin Source File SOURCE=.\cl_video.c # End Source File # Begin Source File SOURCE=.\clvm_cmds.c # End Source File # Begin Source File SOURCE=.\cmd.c # End Source File # Begin Source File SOURCE=.\collision.c # End Source File # Begin Source File SOURCE=.\common.c # End Source File # Begin Source File SOURCE=.\console.c # End Source File # Begin Source File SOURCE=.\csprogs.c # End Source File # Begin Source File SOURCE=.\curves.c # End Source File # Begin Source File SOURCE=.\cvar.c # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.c # End Source File # Begin Source File SOURCE=.\filematch.c # End Source File # Begin Source File SOURCE=.\fractalnoise.c # End Source File # Begin Source File SOURCE=.\fs.c # End Source File # Begin Source File SOURCE=.\gl_backend.c # End Source File # Begin Source File SOURCE=.\gl_draw.c # End Source File # Begin Source File SOURCE=.\gl_rmain.c # End Source File # Begin Source File SOURCE=.\gl_rsurf.c # End Source File # Begin Source File SOURCE=.\gl_textures.c # End Source File # Begin Source File SOURCE=.\host.c # End Source File # Begin Source File SOURCE=.\host_cmd.c # End Source File # Begin Source File SOURCE=.\image.c # End Source File # Begin Source File SOURCE=.\image_png.c # End Source File # Begin Source File SOURCE=.\jpeg.c # End Source File # Begin Source File SOURCE=.\keys.c # End Source File # Begin Source File SOURCE=.\lhnet.c # End Source File # Begin Source File SOURCE=.\libcurl.c # End Source File # Begin Source File SOURCE=.\mathlib.c # End Source File # Begin Source File SOURCE=.\matrixlib.c # End Source File # Begin Source File SOURCE=.\mdfour.c # End Source File # Begin Source File SOURCE=.\menu.c # End Source File # Begin Source File SOURCE=.\meshqueue.c # End Source File # Begin Source File SOURCE=.\model_alias.c # End Source File # Begin Source File SOURCE=.\model_brush.c # End Source File # Begin Source File SOURCE=.\model_shared.c # End Source File # Begin Source File SOURCE=.\model_sprite.c # End Source File # Begin Source File SOURCE=.\mvm_cmds.c # End Source File # Begin Source File SOURCE=.\netconn.c # End Source File # Begin Source File SOURCE=.\palette.c # End Source File # Begin Source File SOURCE=.\polygon.c # End Source File # Begin Source File SOURCE=.\portals.c # End Source File # Begin Source File SOURCE=.\protocol.c # End Source File # Begin Source File SOURCE=.\prvm_cmds.c # End Source File # Begin Source File SOURCE=.\prvm_edict.c # End Source File # Begin Source File SOURCE=.\prvm_exec.c # End Source File # Begin Source File SOURCE=.\r_explosion.c # End Source File # Begin Source File SOURCE=.\r_lerpanim.c # End Source File # Begin Source File SOURCE=.\r_lightning.c # End Source File # Begin Source File SOURCE=.\r_modules.c # End Source File # Begin Source File SOURCE=.\r_shadow.c # End Source File # Begin Source File SOURCE=.\r_sky.c # End Source File # Begin Source File SOURCE=.\r_sprites.c # End Source File # Begin Source File SOURCE=.\sbar.c # End Source File # Begin Source File SOURCE=.\snd_main.c # End Source File # Begin Source File SOURCE=.\snd_mem.c # End Source File # Begin Source File SOURCE=.\snd_mix.c # End Source File # Begin Source File SOURCE=.\snd_ogg.c # End Source File # Begin Source File SOURCE=.\snd_sdl.c # End Source File # Begin Source File SOURCE=.\snd_wav.c # End Source File # Begin Source File SOURCE=.\sv_demo.c # End Source File # Begin Source File SOURCE=.\sv_main.c # End Source File # Begin Source File SOURCE=.\sv_move.c # End Source File # Begin Source File SOURCE=.\sv_phys.c # End Source File # Begin Source File SOURCE=.\sv_user.c # End Source File # Begin Source File SOURCE=.\svbsp.c # End Source File # Begin Source File SOURCE=.\svvm_cmds.c # End Source File # Begin Source File SOURCE=.\sys_sdl.c # End Source File # Begin Source File SOURCE=.\sys_shared.c # End Source File # Begin Source File SOURCE=.\vid_sdl.c # End Source File # Begin Source File SOURCE=.\vid_shared.c # End Source File # Begin Source File SOURCE=.\view.c # End Source File # Begin Source File SOURCE=.\wad.c # End Source File # Begin Source File SOURCE=.\world.c # End Source File # Begin Source File SOURCE=.\zone.c # End Source File # End Group # Begin Group "Header Files" # PROP Default_Filter "h;hpp;hxx;hm;inl" # Begin Source File SOURCE=.\bspfile.h # End Source File # Begin Source File SOURCE=.\cdaudio.h # End Source File # Begin Source File SOURCE=.\cl_collision.h # End Source File # Begin Source File SOURCE=.\cl_dyntexture.h # End Source File # Begin Source File SOURCE=.\cl_gecko.h # End Source File # Begin Source File SOURCE=.\cl_screen.h # End Source File # Begin Source File SOURCE=.\cl_video.h # End Source File # Begin Source File SOURCE=.\client.h # End Source File # Begin Source File SOURCE=.\clprogdefs.h # End Source File # Begin Source File SOURCE=.\cmd.h # End Source File # Begin Source File SOURCE=.\collision.h # End Source File # Begin Source File SOURCE=.\common.h # End Source File # Begin Source File SOURCE=.\conproc.h # End Source File # Begin Source File SOURCE=.\console.h # End Source File # Begin Source File SOURCE=.\csprogs.h # End Source File # Begin Source File SOURCE=.\curves.h # End Source File # Begin Source File SOURCE=.\cvar.h # End Source File # Begin Source File SOURCE=.\dpvsimpledecode.h # End Source File # Begin Source File SOURCE=.\draw.h # End Source File # Begin Source File SOURCE=.\fs.h # End Source File # Begin Source File SOURCE=.\gl_backend.h # End Source File # Begin Source File SOURCE=.\glquake.h # End Source File # Begin Source File SOURCE=.\image.h # End Source File # Begin Source File SOURCE=.\image_png.h # End Source File # Begin Source File SOURCE=.\input.h # End Source File # Begin Source File SOURCE=.\jpeg.h # End Source File # Begin Source File SOURCE=.\keys.h # End Source File # Begin Source File SOURCE=.\lhfont.h # End Source File # Begin Source File SOURCE=.\lhnet.h # End Source File # Begin Source File SOURCE=.\libcurl.h # End Source File # Begin Source File SOURCE=.\mathlib.h # End Source File # Begin Source File SOURCE=.\matrixlib.h # End Source File # Begin Source File SOURCE=.\mdfour.h # End Source File # Begin Source File SOURCE=.\menu.h # End Source File # Begin Source File SOURCE=.\meshqueue.h # End Source File # Begin Source File SOURCE=.\model_alias.h # End Source File # Begin Source File SOURCE=.\model_brush.h # End Source File # Begin Source File SOURCE=.\model_dpmodel.h # End Source File # Begin Source File SOURCE=.\model_psk.h # End Source File # Begin Source File SOURCE=.\model_shared.h # End Source File # Begin Source File SOURCE=.\model_sprite.h # End Source File # Begin Source File SOURCE=.\model_zymotic.h # End Source File # Begin Source File SOURCE=.\modelgen.h # End Source File # Begin Source File SOURCE=.\mprogdefs.h # End Source File # Begin Source File SOURCE=.\netconn.h # End Source File # Begin Source File SOURCE=.\palette.h # End Source File # Begin Source File SOURCE=.\polygon.h # End Source File # Begin Source File SOURCE=.\portals.h # End Source File # Begin Source File SOURCE=.\pr_comp.h # End Source File # Begin Source File SOURCE=.\pr_execprogram.h # End Source File # Begin Source File SOURCE=.\progdefs.h # End Source File # Begin Source File SOURCE=.\progs.h # End Source File # Begin Source File SOURCE=.\progsvm.h # End Source File # Begin Source File SOURCE=.\protocol.h # End Source File # Begin Source File SOURCE=.\prvm_cmds.h # End Source File # Begin Source File SOURCE=.\prvm_execprogram.h # End Source File # Begin Source File SOURCE=.\qtypes.h # End Source File # Begin Source File SOURCE=.\quakedef.h # End Source File # Begin Source File SOURCE=.\r_lerpanim.h # End Source File # Begin Source File SOURCE=.\r_modules.h # End Source File # Begin Source File SOURCE=.\r_shadow.h # End Source File # Begin Source File SOURCE=.\r_textures.h # End Source File # Begin Source File SOURCE=.\render.h # End Source File # Begin Source File SOURCE=.\sbar.h # End Source File # Begin Source File SOURCE=.\screen.h # End Source File # Begin Source File SOURCE=.\server.h # End Source File # Begin Source File SOURCE=.\snd_main.h # End Source File # Begin Source File SOURCE=.\snd_ogg.h # End Source File # Begin Source File SOURCE=.\snd_wav.h # End Source File # Begin Source File SOURCE=.\sound.h # End Source File # Begin Source File SOURCE=.\spritegn.h # End Source File # Begin Source File SOURCE=.\sv_demo.h # End Source File # Begin Source File SOURCE=.\svbsp.h # End Source File # Begin Source File SOURCE=.\sys.h # End Source File # Begin Source File SOURCE=.\vid.h # End Source File # Begin Source File SOURCE=.\wad.h # End Source File # Begin Source File SOURCE=.\world.h # End Source File # Begin Source File SOURCE=.\zone.h # End Source File # End Group # Begin Group "Resource Files" # PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" # Begin Source File SOURCE=.\darkplaces.ico # End Source File # Begin Source File SOURCE=.\darkplaces.rc # End Source File # Begin Source File SOURCE=.\resource.h # End Source File # End Group # End Target # End Project darkplaces/cl_parse.c0000664000175000017500000043200413067716216014127 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // cl_parse.c -- parse a message received from the server #include "quakedef.h" #ifdef CONFIG_CD #include "cdaudio.h" #endif #include "cl_collision.h" #include "csprogs.h" #include "libcurl.h" #include "utf8lib.h" #ifdef CONFIG_MENU #include "menu.h" #endif #include "cl_video.h" const char *svc_strings[128] = { "svc_bad", "svc_nop", "svc_disconnect", "svc_updatestat", "svc_version", // [int] server version "svc_setview", // [short] entity number "svc_sound", // "svc_time", // [float] server time "svc_print", // [string] null terminated string "svc_stufftext", // [string] stuffed into client's console buffer // the string should be \n terminated "svc_setangle", // [vec3] set the view angle to this absolute value "svc_serverinfo", // [int] version // [string] signon string // [string]..[0]model cache [string]...[0]sounds cache // [string]..[0]item cache "svc_lightstyle", // [byte] [string] "svc_updatename", // [byte] [string] "svc_updatefrags", // [byte] [short] "svc_clientdata", // "svc_stopsound", // "svc_updatecolors", // [byte] [byte] "svc_particle", // [vec3] "svc_damage", // [byte] impact [byte] blood [vec3] from "svc_spawnstatic", "OBSOLETE svc_spawnbinary", "svc_spawnbaseline", "svc_temp_entity", // "svc_setpause", "svc_signonnum", "svc_centerprint", "svc_killedmonster", "svc_foundsecret", "svc_spawnstaticsound", "svc_intermission", "svc_finale", // [string] music [string] text "svc_cdtrack", // [byte] track [byte] looptrack "svc_sellscreen", "svc_cutscene", "svc_showlmp", // [string] iconlabel [string] lmpfile [short] x [short] y "svc_hidelmp", // [string] iconlabel "svc_skybox", // [string] skyname "", // 38 "", // 39 "", // 40 "", // 41 "", // 42 "", // 43 "", // 44 "", // 45 "", // 46 "", // 47 "", // 48 "", // 49 "svc_downloaddata", // 50 // [int] start [short] size [variable length] data "svc_updatestatubyte", // 51 // [byte] stat [byte] value "svc_effect", // 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate "svc_effect2", // 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate "svc_sound2", // 54 // short soundindex instead of byte "svc_spawnbaseline2", // 55 // short modelindex instead of byte "svc_spawnstatic2", // 56 // short modelindex instead of byte "svc_entities", // 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata "svc_csqcentities", // 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 "svc_spawnstaticsound2", // 59 // [coord3] [short] samp [byte] vol [byte] aten "svc_trailparticles", // 60 // [short] entnum [short] effectnum [vector] start [vector] end "svc_pointparticles", // 61 // [short] effectnum [vector] start [vector] velocity [short] count "svc_pointparticles1", // 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 }; const char *qw_svc_strings[128] = { "qw_svc_bad", // 0 "qw_svc_nop", // 1 "qw_svc_disconnect", // 2 "qw_svc_updatestat", // 3 // [byte] [byte] "", // 4 "qw_svc_setview", // 5 // [short] entity number "qw_svc_sound", // 6 // "", // 7 "qw_svc_print", // 8 // [byte] id [string] null terminated string "qw_svc_stufftext", // 9 // [string] stuffed into client's console buffer "qw_svc_setangle", // 10 // [angle3] set the view angle to this absolute value "qw_svc_serverdata", // 11 // [long] protocol ... "qw_svc_lightstyle", // 12 // [byte] [string] "", // 13 "qw_svc_updatefrags", // 14 // [byte] [short] "", // 15 "qw_svc_stopsound", // 16 // "", // 17 "", // 18 "qw_svc_damage", // 19 "qw_svc_spawnstatic", // 20 "", // 21 "qw_svc_spawnbaseline", // 22 "qw_svc_temp_entity", // 23 // variable "qw_svc_setpause", // 24 // [byte] on / off "", // 25 "qw_svc_centerprint", // 26 // [string] to put in center of the screen "qw_svc_killedmonster", // 27 "qw_svc_foundsecret", // 28 "qw_svc_spawnstaticsound", // 29 // [coord3] [byte] samp [byte] vol [byte] aten "qw_svc_intermission", // 30 // [vec3_t] origin [vec3_t] angle "qw_svc_finale", // 31 // [string] text "qw_svc_cdtrack", // 32 // [byte] track "qw_svc_sellscreen", // 33 "qw_svc_smallkick", // 34 // set client punchangle to 2 "qw_svc_bigkick", // 35 // set client punchangle to 4 "qw_svc_updateping", // 36 // [byte] [short] "qw_svc_updateentertime", // 37 // [byte] [float] "qw_svc_updatestatlong", // 38 // [byte] [long] "qw_svc_muzzleflash", // 39 // [short] entity "qw_svc_updateuserinfo", // 40 // [byte] slot [long] uid "qw_svc_download", // 41 // [short] size [size bytes] "qw_svc_playerinfo", // 42 // variable "qw_svc_nails", // 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 "qw_svc_chokecount", // 44 // [byte] packets choked "qw_svc_modellist", // 45 // [strings] "qw_svc_soundlist", // 46 // [strings] "qw_svc_packetentities", // 47 // [...] "qw_svc_deltapacketentities", // 48 // [...] "qw_svc_maxspeed", // 49 // maxspeed change, for prediction "qw_svc_entgravity", // 50 // gravity change, for prediction "qw_svc_setinfo", // 51 // setinfo on a client "qw_svc_serverinfo", // 52 // serverinfo "qw_svc_updatepl", // 53 // [byte] [byte] }; //============================================================================= cvar_t cl_worldmessage = {CVAR_READONLY, "cl_worldmessage", "", "title of current level"}; cvar_t cl_worldname = {CVAR_READONLY, "cl_worldname", "", "name of current worldmodel"}; cvar_t cl_worldnamenoextension = {CVAR_READONLY, "cl_worldnamenoextension", "", "name of current worldmodel without extension"}; cvar_t cl_worldbasename = {CVAR_READONLY, "cl_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; cvar_t developer_networkentities = {0, "developer_networkentities", "0", "prints received entities, value is 0-10 (higher for more info, 10 being the most verbose)"}; cvar_t cl_gameplayfix_soundsmovewithentities = {0, "cl_gameplayfix_soundsmovewithentities", "1", "causes sounds made by lifts, players, projectiles, and any other entities, to move with the entity, so for example a rocket noise follows the rocket rather than staying at the starting position"}; cvar_t cl_sound_wizardhit = {0, "cl_sound_wizardhit", "wizard/hit.wav", "sound to play during TE_WIZSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_hknighthit = {0, "cl_sound_hknighthit", "hknight/hit.wav", "sound to play during TE_KNIGHTSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_tink1 = {0, "cl_sound_tink1", "weapons/tink1.wav", "sound to play with 80% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric1 = {0, "cl_sound_ric1", "weapons/ric1.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric2 = {0, "cl_sound_ric2", "weapons/ric2.wav", "sound to play with 5% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_sound_ric3 = {0, "cl_sound_ric3", "weapons/ric3.wav", "sound to play with 10% chance during TE_SPIKE/TE_SUPERSPIKE (empty cvar disables sound)"}; cvar_t cl_readpicture_force = {0, "cl_readpicture_force", "0", "when enabled, the low quality pictures read by ReadPicture() are preferred over the high quality pictures on the file system"}; #define RIC_GUNSHOT 1 #define RIC_GUNSHOTQUAD 2 cvar_t cl_sound_ric_gunshot = {0, "cl_sound_ric_gunshot", "0", "specifies if and when the related cl_sound_ric and cl_sound_tink sounds apply to TE_GUNSHOT/TE_GUNSHOTQUAD, 0 = no sound, 1 = TE_GUNSHOT, 2 = TE_GUNSHOTQUAD, 3 = TE_GUNSHOT and TE_GUNSHOTQUAD"}; cvar_t cl_sound_r_exp3 = {0, "cl_sound_r_exp3", "weapons/r_exp3.wav", "sound to play during TE_EXPLOSION and related effects (empty cvar disables sound)"}; cvar_t cl_serverextension_download = {0, "cl_serverextension_download", "0", "indicates whether the server supports the download command"}; cvar_t cl_joinbeforedownloadsfinish = {CVAR_SAVE, "cl_joinbeforedownloadsfinish", "1", "if non-zero the game will begin after the map is loaded before other downloads finish"}; cvar_t cl_nettimesyncfactor = {CVAR_SAVE, "cl_nettimesyncfactor", "0", "rate at which client time adapts to match server time, 1 = instantly, 0.125 = slowly, 0 = not at all (bounding still applies)"}; cvar_t cl_nettimesyncboundmode = {CVAR_SAVE, "cl_nettimesyncboundmode", "6", "method of restricting client time to valid values, 0 = no correction, 1 = tight bounding (jerky with packet loss), 2 = loose bounding (corrects it if out of bounds), 3 = leniant bounding (ignores temporary errors due to varying framerate), 4 = slow adjustment method from Quake3, 5 = slighttly nicer version of Quake3 method, 6 = bounding + Quake3"}; cvar_t cl_nettimesyncboundtolerance = {CVAR_SAVE, "cl_nettimesyncboundtolerance", "0.25", "how much error is tolerated by bounding check, as a fraction of frametime, 0.25 = up to 25% margin of error tolerated, 1 = use only new time, 0 = use only old time (same effect as setting cl_nettimesyncfactor to 1)"}; cvar_t cl_iplog_name = {CVAR_SAVE, "cl_iplog_name", "darkplaces_iplog.txt", "name of iplog file containing player addresses for iplog_list command and automatic ip logging when parsing status command"}; static qboolean QW_CL_CheckOrDownloadFile(const char *filename); static void QW_CL_RequestNextDownload(void); static void QW_CL_NextUpload(void); //static qboolean QW_CL_IsUploading(void); static void QW_CL_StopUpload(void); /* ================== CL_ParseStartSoundPacket ================== */ static void CL_ParseStartSoundPacket(int largesoundindex) { vec3_t pos; int channel, ent; int sound_num; int nvolume; int field_mask; float attenuation; float speed; int fflags = CHANNELFLAG_NONE; if (cls.protocol == PROTOCOL_QUAKEWORLD) { channel = MSG_ReadShort(&cl_message); if (channel & (1<<15)) nvolume = MSG_ReadByte(&cl_message); else nvolume = DEFAULT_SOUND_PACKET_VOLUME; if (channel & (1<<14)) attenuation = MSG_ReadByte(&cl_message) / 64.0; else attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; speed = 1.0f; ent = (channel>>3)&1023; channel &= 7; sound_num = MSG_ReadByte(&cl_message); } else { field_mask = MSG_ReadByte(&cl_message); if (field_mask & SND_VOLUME) nvolume = MSG_ReadByte(&cl_message); else nvolume = DEFAULT_SOUND_PACKET_VOLUME; if (field_mask & SND_ATTENUATION) attenuation = MSG_ReadByte(&cl_message) / 64.0; else attenuation = DEFAULT_SOUND_PACKET_ATTENUATION; if (field_mask & SND_SPEEDUSHORT4000) speed = ((unsigned short)MSG_ReadShort(&cl_message)) / 4000.0f; else speed = 1.0f; if (field_mask & SND_LARGEENTITY) { ent = (unsigned short) MSG_ReadShort(&cl_message); channel = MSG_ReadChar(&cl_message); } else { channel = (unsigned short) MSG_ReadShort(&cl_message); ent = channel >> 3; channel &= 7; } if (largesoundindex || (field_mask & SND_LARGESOUND) || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) sound_num = (unsigned short) MSG_ReadShort(&cl_message); else sound_num = MSG_ReadByte(&cl_message); } channel = CHAN_NET2ENGINE(channel); MSG_ReadVector(&cl_message, pos, cls.protocol); if (sound_num < 0 || sound_num >= MAX_SOUNDS) { Con_Printf("CL_ParseStartSoundPacket: sound_num (%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS); return; } if (ent >= MAX_EDICTS) { Con_Printf("CL_ParseStartSoundPacket: ent = %i", ent); return; } if (ent >= cl.max_entities) CL_ExpandEntities(ent); if( !CL_VM_Event_Sound(sound_num, nvolume / 255.0f, channel, attenuation, ent, pos, fflags, speed) ) S_StartSound_StartPosition_Flags (ent, channel, cl.sound_precache[sound_num], pos, nvolume/255.0f, attenuation, 0, fflags, speed); } /* ================== CL_KeepaliveMessage When the client is taking a long time to load stuff, send keepalive messages so the server doesn't disconnect. ================== */ static unsigned char olddata[NET_MAXMESSAGE]; void CL_KeepaliveMessage (qboolean readmessages) { static double lastdirtytime = 0; static qboolean recursive = false; double dirtytime; double deltatime; static double countdownmsg = 0; static double countdownupdate = 0; sizebuf_t old; qboolean thisrecursive; thisrecursive = recursive; recursive = true; dirtytime = Sys_DirtyTime(); deltatime = dirtytime - lastdirtytime; lastdirtytime = dirtytime; if (deltatime <= 0 || deltatime >= 1800.0) return; countdownmsg -= deltatime; countdownupdate -= deltatime; if(!thisrecursive) { if(cls.state != ca_dedicated) { if(countdownupdate <= 0) // check if time stepped backwards { SCR_UpdateLoadingScreenIfShown(); countdownupdate = 2; } } } // no need if server is local and definitely not if this is a demo if (sv.active || !cls.netcon || cls.protocol == PROTOCOL_QUAKEWORLD || cls.signon >= SIGNONS) { recursive = thisrecursive; return; } if (readmessages) { // read messages from server, should just be nops old = cl_message; memcpy(olddata, cl_message.data, cl_message.cursize); NetConn_ClientFrame(); cl_message = old; memcpy(cl_message.data, olddata, cl_message.cursize); } if (cls.netcon && countdownmsg <= 0) // check if time stepped backwards { sizebuf_t msg; unsigned char buf[4]; countdownmsg = 5; // write out a nop // LordHavoc: must use unreliable because reliable could kill the sigon message! Con_Print("--> client to server keepalive\n"); memset(&msg, 0, sizeof(msg)); msg.data = buf; msg.maxsize = sizeof(buf); MSG_WriteChar(&msg, clc_nop); NetConn_SendUnreliableMessage(cls.netcon, &msg, cls.protocol, 10000, 0, false); } recursive = thisrecursive; } void CL_ParseEntityLump(char *entdata) { qboolean loadedsky = false; const char *data; char key[128], value[MAX_INPUTLINE]; FOG_clear(); // LordHavoc: no fog until set // LordHavoc: default to the map's sky (q3 shader parsing sets this) R_SetSkyBox(cl.worldmodel->brush.skybox); data = entdata; if (!data) return; if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] != '{') return; // error while (1) { if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error if (com_token[0] == '}') break; // end of worldspawn if (com_token[0] == '_') strlcpy (key, com_token + 1, sizeof (key)); else strlcpy (key, com_token, sizeof (key)); while (key[strlen(key)-1] == ' ') // remove trailing spaces key[strlen(key)-1] = 0; if (!COM_ParseToken_Simple(&data, false, false, true)) return; // error strlcpy (value, com_token, sizeof (value)); if (!strcmp("sky", key)) { loadedsky = true; R_SetSkyBox(value); } else if (!strcmp("skyname", key)) // non-standard, introduced by QuakeForge... sigh. { loadedsky = true; R_SetSkyBox(value); } else if (!strcmp("qlsky", key)) // non-standard, introduced by QuakeLives (EEK) { loadedsky = true; R_SetSkyBox(value); } else if (!strcmp("fog", key)) { FOG_clear(); // so missing values get good defaults r_refdef.fog_start = 0; r_refdef.fog_alpha = 1; r_refdef.fog_end = 16384; r_refdef.fog_height = 1<<30; r_refdef.fog_fadedepth = 128; #if _MSC_VER >= 1400 #define sscanf sscanf_s #endif sscanf(value, "%f %f %f %f %f %f %f %f %f", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth); } else if (!strcmp("fog_density", key)) r_refdef.fog_density = atof(value); else if (!strcmp("fog_red", key)) r_refdef.fog_red = atof(value); else if (!strcmp("fog_green", key)) r_refdef.fog_green = atof(value); else if (!strcmp("fog_blue", key)) r_refdef.fog_blue = atof(value); else if (!strcmp("fog_alpha", key)) r_refdef.fog_alpha = atof(value); else if (!strcmp("fog_start", key)) r_refdef.fog_start = atof(value); else if (!strcmp("fog_end", key)) r_refdef.fog_end = atof(value); else if (!strcmp("fog_height", key)) r_refdef.fog_height = atof(value); else if (!strcmp("fog_fadedepth", key)) r_refdef.fog_fadedepth = atof(value); else if (!strcmp("fog_heighttexture", key)) { FOG_clear(); // so missing values get good defaults #if _MSC_VER >= 1400 sscanf_s(value, "%f %f %f %f %f %f %f %f %f %s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename, (unsigned int)sizeof(r_refdef.fog_height_texturename)); #else sscanf(value, "%f %f %f %f %f %f %f %f %f %63s", &r_refdef.fog_density, &r_refdef.fog_red, &r_refdef.fog_green, &r_refdef.fog_blue, &r_refdef.fog_alpha, &r_refdef.fog_start, &r_refdef.fog_end, &r_refdef.fog_height, &r_refdef.fog_fadedepth, r_refdef.fog_height_texturename); #endif r_refdef.fog_height_texturename[63] = 0; } } if (!loadedsky && cl.worldmodel->brush.isq2bsp) R_SetSkyBox("unit1_"); } static const vec3_t defaultmins = {-4096, -4096, -4096}; static const vec3_t defaultmaxs = {4096, 4096, 4096}; static void CL_SetupWorldModel(void) { prvm_prog_t *prog = CLVM_prog; // update the world model cl.entities[0].render.model = cl.worldmodel = CL_GetModelByIndex(1); CL_UpdateRenderEntity(&cl.entities[0].render); // make sure the cl.worldname and related cvars are set up now that we know the world model name // set up csqc world for collision culling if (cl.worldmodel) { strlcpy(cl.worldname, cl.worldmodel->name, sizeof(cl.worldname)); FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); Cvar_SetQuick(&cl_worldname, cl.worldname); Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); World_SetSize(&cl.world, cl.worldname, cl.worldmodel->normalmins, cl.worldmodel->normalmaxs, prog); } else { Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); Cvar_SetQuick(&cl_worldnamenoextension, ""); Cvar_SetQuick(&cl_worldbasename, ""); World_SetSize(&cl.world, "", defaultmins, defaultmaxs, prog); } World_Start(&cl.world); // load or reload .loc file for team chat messages CL_Locs_Reload_f(); // make sure we send enough keepalives CL_KeepaliveMessage(false); // reset particles and other per-level things R_Modules_NewMap(); // make sure we send enough keepalives CL_KeepaliveMessage(false); // load the team chat beep if possible cl.foundtalk2wav = FS_FileExists("sound/misc/talk2.wav"); // check memory integrity Mem_CheckSentinelsGlobal(); #ifdef CONFIG_MENU // make menu know MR_NewMap(); #endif // load the csqc now if (cl.loadcsqc) { cl.loadcsqc = false; CL_VM_Init(); } } static qboolean QW_CL_CheckOrDownloadFile(const char *filename) { qfile_t *file; char vabuf[1024]; // see if the file already exists file = FS_OpenVirtualFile(filename, true); if (file) { FS_Close(file); return true; } // download messages in a demo would be bad if (cls.demorecording) { Con_Printf("Unable to download \"%s\" when recording.\n", filename); return true; } // don't try to download when playing a demo if (!cls.netcon) return true; strlcpy(cls.qw_downloadname, filename, sizeof(cls.qw_downloadname)); Con_Printf("Downloading %s\n", filename); if (!cls.qw_downloadmemory) { cls.qw_downloadmemory = NULL; cls.qw_downloadmemorycursize = 0; cls.qw_downloadmemorymaxsize = 1024*1024; // start out with a 1MB buffer } MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "download %s", filename)); cls.qw_downloadnumber++; cls.qw_downloadpercent = 0; cls.qw_downloadmemorycursize = 0; return false; } static void QW_CL_ProcessUserInfo(int slot); static void QW_CL_RequestNextDownload(void) { int i; char vabuf[1024]; // clear name of file that just finished cls.qw_downloadname[0] = 0; // skip the download fragment if playing a demo if (!cls.netcon) { return; } switch (cls.qw_downloadtype) { case dl_single: break; case dl_skin: if (cls.qw_downloadnumber == 0) Con_Printf("Checking skins...\n"); for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++) { if (!cl.scores[cls.qw_downloadnumber].name[0]) continue; // check if we need to download the file, and return if so if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin))) return; } cls.qw_downloadtype = dl_none; // load any newly downloaded skins for (i = 0;i < cl.maxclients;i++) QW_CL_ProcessUserInfo(i); // if we're still in signon stages, request the next one if (cls.signon != SIGNONS) { cls.signon = SIGNONS-1; // we'll go to SIGNONS when the first entity update is received MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "begin %i", cl.qw_servercount)); } break; case dl_model: if (cls.qw_downloadnumber == 0) { Con_Printf("Checking models...\n"); cls.qw_downloadnumber = 1; } for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) { // skip submodels if (cl.model_name[cls.qw_downloadnumber][0] == '*') continue; if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/spike.mdl")) cl.qw_modelindex_spike = cls.qw_downloadnumber; if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/player.mdl")) cl.qw_modelindex_player = cls.qw_downloadnumber; if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/flag.mdl")) cl.qw_modelindex_flag = cls.qw_downloadnumber; if (!strcmp(cl.model_name[cls.qw_downloadnumber], "progs/s_explod.spr")) cl.qw_modelindex_s_explod = cls.qw_downloadnumber; // check if we need to download the file, and return if so if (!QW_CL_CheckOrDownloadFile(cl.model_name[cls.qw_downloadnumber])) return; } cls.qw_downloadtype = dl_none; // touch all of the precached models that are still loaded so we can free // anything that isn't needed if (!sv.active) Mod_ClearUsed(); for (i = 1;i < MAX_MODELS && cl.model_name[i][0];i++) Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); // precache any models used by the client (this also marks them used) cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); // we purge the models and sounds later in CL_SignonReply //Mod_PurgeUnused(); // now we try to load everything that is new // world model cl.model_precache[1] = Mod_ForName(cl.model_name[1], false, false, NULL); if (cl.model_precache[1]->Draw == NULL) Con_Printf("Map %s could not be found or downloaded\n", cl.model_name[1]); // normal models for (i = 2;i < MAX_MODELS && cl.model_name[i][0];i++) if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL))->Draw == NULL) Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]); // check memory integrity Mem_CheckSentinelsGlobal(); // now that we have a world model, set up the world entity, renderer // modules and csqc CL_SetupWorldModel(); // add pmodel/emodel CRCs to userinfo CL_SetInfo("pmodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/player.mdl", NULL)), true, true, true, true); CL_SetInfo("emodel", va(vabuf, sizeof(vabuf), "%i", FS_CRCFile("progs/eyes.mdl", NULL)), true, true, true, true); // done checking sounds and models, send a prespawn command now MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2)); if (cls.qw_downloadmemory) { Mem_Free(cls.qw_downloadmemory); cls.qw_downloadmemory = NULL; } // done loading cl.loadfinished = true; break; case dl_sound: if (cls.qw_downloadnumber == 0) { Con_Printf("Checking sounds...\n"); cls.qw_downloadnumber = 1; } for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++) { // check if we need to download the file, and return if so if (!QW_CL_CheckOrDownloadFile(va(vabuf, sizeof(vabuf), "sound/%s", cl.sound_name[cls.qw_downloadnumber]))) return; } cls.qw_downloadtype = dl_none; // clear sound usage flags for purging of unused sounds S_ClearUsed(); // precache any sounds used by the client cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); // sounds used by the game for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); // we purge the models and sounds later in CL_SignonReply //S_PurgeUnused(); // check memory integrity Mem_CheckSentinelsGlobal(); // done with sound downloads, next we check models MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, 0)); break; case dl_none: default: Con_Printf("Unknown download type.\n"); } } static void QW_CL_ParseDownload(void) { int size = (signed short)MSG_ReadShort(&cl_message); int percent = MSG_ReadByte(&cl_message); //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize); // skip the download fragment if playing a demo if (!cls.netcon) { if (size > 0) cl_message.readcount += size; return; } if (size == -1) { Con_Printf("File not found.\n"); QW_CL_RequestNextDownload(); return; } if (cl_message.readcount + (unsigned short)size > cl_message.cursize) Host_Error("corrupt download message\n"); // make sure the buffer is big enough to include this new fragment if (!cls.qw_downloadmemory || cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) { unsigned char *old; while (cls.qw_downloadmemorymaxsize < cls.qw_downloadmemorycursize + size) cls.qw_downloadmemorymaxsize *= 2; old = cls.qw_downloadmemory; cls.qw_downloadmemory = (unsigned char *)Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); if (old) { memcpy(cls.qw_downloadmemory, old, cls.qw_downloadmemorycursize); Mem_Free(old); } } // read the fragment out of the packet MSG_ReadBytes(&cl_message, size, cls.qw_downloadmemory + cls.qw_downloadmemorycursize); cls.qw_downloadmemorycursize += size; cls.qw_downloadspeedcount += size; cls.qw_downloadpercent = percent; if (percent != 100) { // request next fragment MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, "nextdl"); } else { // finished file Con_Printf("Downloaded \"%s\"\n", cls.qw_downloadname); FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); cls.qw_downloadpercent = 0; // start downloading the next file (or join the game) QW_CL_RequestNextDownload(); } } static void QW_CL_ParseModelList(void) { int n; int nummodels = MSG_ReadByte(&cl_message); char *str; char vabuf[1024]; // parse model precache list for (;;) { str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (!str[0]) break; nummodels++; if (nummodels==MAX_MODELS) Host_Error("Server sent too many model precaches"); if (strlen(str) >= MAX_QPATH) Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); strlcpy(cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); } n = MSG_ReadByte(&cl_message); if (n) { MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "modellist %i %i", cl.qw_servercount, n)); return; } cls.signon = 2; cls.qw_downloadnumber = 0; cls.qw_downloadtype = dl_model; QW_CL_RequestNextDownload(); } static void QW_CL_ParseSoundList(void) { int n; int numsounds = MSG_ReadByte(&cl_message); char *str; char vabuf[1024]; // parse sound precache list for (;;) { str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (!str[0]) break; numsounds++; if (numsounds==MAX_SOUNDS) Host_Error("Server sent too many sound precaches"); if (strlen(str) >= MAX_QPATH) Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); strlcpy(cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); } n = MSG_ReadByte(&cl_message); if (n) { MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "soundlist %i %i", cl.qw_servercount, n)); return; } cls.signon = 2; cls.qw_downloadnumber = 0; cls.qw_downloadtype = dl_sound; QW_CL_RequestNextDownload(); } static void QW_CL_Skins_f(void) { cls.qw_downloadnumber = 0; cls.qw_downloadtype = dl_skin; QW_CL_RequestNextDownload(); } static void QW_CL_Changing_f(void) { if (cls.qw_downloadmemory) // don't change when downloading return; S_StopAllSounds(); cl.intermission = 0; cls.signon = 1; // not active anymore, but not disconnected Con_Printf("\nChanging map...\n"); } void QW_CL_NextUpload(void) { int r, percent, size; if (!cls.qw_uploaddata) return; r = cls.qw_uploadsize - cls.qw_uploadpos; if (r > 768) r = 768; size = min(1, cls.qw_uploadsize); percent = (cls.qw_uploadpos+r)*100/size; MSG_WriteByte(&cls.netcon->message, qw_clc_upload); MSG_WriteShort(&cls.netcon->message, r); MSG_WriteByte(&cls.netcon->message, percent); SZ_Write(&cls.netcon->message, cls.qw_uploaddata + cls.qw_uploadpos, r); Con_DPrintf("UPLOAD: %6d: %d written\n", cls.qw_uploadpos, r); cls.qw_uploadpos += r; if (cls.qw_uploadpos < cls.qw_uploadsize) return; Con_Printf("Upload completed\n"); QW_CL_StopUpload(); } void QW_CL_StartUpload(unsigned char *data, int size) { // do nothing in demos or if not connected if (!cls.netcon) return; // abort existing upload if in progress QW_CL_StopUpload(); Con_DPrintf("Starting upload of %d bytes...\n", size); cls.qw_uploaddata = (unsigned char *)Mem_Alloc(cls.permanentmempool, size); memcpy(cls.qw_uploaddata, data, size); cls.qw_uploadsize = size; cls.qw_uploadpos = 0; QW_CL_NextUpload(); } #if 0 qboolean QW_CL_IsUploading(void) { return cls.qw_uploaddata != NULL; } #endif void QW_CL_StopUpload(void) { if (cls.qw_uploaddata) Mem_Free(cls.qw_uploaddata); cls.qw_uploaddata = NULL; cls.qw_uploadsize = 0; cls.qw_uploadpos = 0; } static void QW_CL_ProcessUserInfo(int slot) { int topcolor, bottomcolor; char temp[2048]; InfoString_GetValue(cl.scores[slot].qw_userinfo, "name", cl.scores[slot].name, sizeof(cl.scores[slot].name)); InfoString_GetValue(cl.scores[slot].qw_userinfo, "topcolor", temp, sizeof(temp));topcolor = atoi(temp); InfoString_GetValue(cl.scores[slot].qw_userinfo, "bottomcolor", temp, sizeof(temp));bottomcolor = atoi(temp); cl.scores[slot].colors = topcolor * 16 + bottomcolor; InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp)); cl.scores[slot].qw_spectator = temp[0] != 0; InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team)); InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin)); if (!cl.scores[slot].qw_skin[0]) strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin)); // TODO: skin cache } static void QW_CL_UpdateUserInfo(void) { int slot; slot = MSG_ReadByte(&cl_message); if (slot >= cl.maxclients) { Con_Printf("svc_updateuserinfo >= cl.maxclients\n"); MSG_ReadLong(&cl_message); MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); return; } cl.scores[slot].qw_userid = MSG_ReadLong(&cl_message); strlcpy(cl.scores[slot].qw_userinfo, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(cl.scores[slot].qw_userinfo)); QW_CL_ProcessUserInfo(slot); } static void QW_CL_SetInfo(void) { int slot; char key[2048]; char value[2048]; slot = MSG_ReadByte(&cl_message); strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key)); strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value)); if (slot >= cl.maxclients) { Con_Printf("svc_setinfo >= cl.maxclients\n"); return; } InfoString_SetValue(cl.scores[slot].qw_userinfo, sizeof(cl.scores[slot].qw_userinfo), key, value); QW_CL_ProcessUserInfo(slot); } static void QW_CL_ServerInfo(void) { char key[2048]; char value[2048]; char temp[32]; strlcpy(key, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(key)); strlcpy(value, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof(value)); Con_DPrintf("SERVERINFO: %s=%s\n", key, value); InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value); InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp)); cl.qw_teamplay = atoi(temp); } static void QW_CL_ParseNails(void) { int i, j; int numnails = MSG_ReadByte(&cl_message); vec_t *v; unsigned char bits[6]; for (i = 0;i < numnails;i++) { for (j = 0;j < 6;j++) bits[j] = MSG_ReadByte(&cl_message); if (cl.qw_num_nails >= 255) continue; v = cl.qw_nails[cl.qw_num_nails++]; v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096; v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096; v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096; v[3] = -360*(bits[4]>>4)/16; v[4] = 360*bits[5]/256; v[5] = 0; } } static void CL_UpdateItemsAndWeapon(void) { int j; // check for important changes // set flash times if (cl.olditems != cl.stats[STAT_ITEMS]) for (j = 0;j < 32;j++) if ((cl.stats[STAT_ITEMS] & (1<= 0 && cl_serverextension_download.integer && (FS_CRCFile(csqc_progname.string, &progsize) != csqc_progcrc.integer || ((int)progsize != csqc_progsize.integer && csqc_progsize.integer != -1)) && !FS_FileExists(va(vabuf, sizeof(vabuf), "dlcache/%s.%i.%i", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer))) { Con_Printf("Downloading new CSQC code to dlcache/%s.%i.%i\n", csqc_progname.string, csqc_progsize.integer, csqc_progcrc.integer); if(cl_serverextension_download.integer == 2 && FS_HasZlib()) Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s deflate", csqc_progname.string)); else Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", csqc_progname.string)); return; } } if (cl.loadmodel_current < cl.loadmodel_total) { // loading models if(cl.loadmodel_current == 1) { // worldmodel counts as 16 models (15 + world model setup), for better progress bar SCR_PushLoadingScreen(false, "Loading precached models", ( (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + LOADPROGRESSWEIGHT_WORLDMODEL + LOADPROGRESSWEIGHT_WORLDMODEL_INIT ) / ( (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + LOADPROGRESSWEIGHT_WORLDMODEL + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND ) ); SCR_BeginLoadingPlaque(false); } for (;cl.loadmodel_current < cl.loadmodel_total;cl.loadmodel_current++) { SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], ( (cl.loadmodel_current == 1) ? LOADPROGRESSWEIGHT_WORLDMODEL : LOADPROGRESSWEIGHT_MODEL ) / ( (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + LOADPROGRESSWEIGHT_WORLDMODEL + LOADPROGRESSWEIGHT_WORLDMODEL_INIT ) ); if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw) { SCR_PopLoadingScreen(false); if(cl.loadmodel_current == 1) { SCR_PushLoadingScreen(false, cl.model_name[cl.loadmodel_current], 1.0 / cl.loadmodel_total); SCR_PopLoadingScreen(false); } continue; } CL_KeepaliveMessage(true); // if running a local game, calling Mod_ForName is a completely wasted effort... if (sv.active) cl.model_precache[cl.loadmodel_current] = sv.models[cl.loadmodel_current]; else { if(cl.loadmodel_current == 1) { // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 Mod_FreeQ3Shaders(); } cl.model_precache[cl.loadmodel_current] = Mod_ForName(cl.model_name[cl.loadmodel_current], false, false, cl.model_name[cl.loadmodel_current][0] == '*' ? cl.model_name[1] : NULL); } SCR_PopLoadingScreen(false); if (cl.model_precache[cl.loadmodel_current] && cl.model_precache[cl.loadmodel_current]->Draw && cl.loadmodel_current == 1) { // we now have the worldmodel so we can set up the game world SCR_PushLoadingScreen(true, "world model setup", ( LOADPROGRESSWEIGHT_WORLDMODEL_INIT ) / ( (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + LOADPROGRESSWEIGHT_WORLDMODEL + LOADPROGRESSWEIGHT_WORLDMODEL_INIT ) ); CL_SetupWorldModel(); SCR_PopLoadingScreen(true); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { cl.loadfinished = true; // now issue the spawn to move on to signon 2 like normal if (cls.netcon) Cmd_ForwardStringToServer("prespawn"); } } } SCR_PopLoadingScreen(false); // finished loading models } if (cl.loadsound_current < cl.loadsound_total) { // loading sounds if(cl.loadsound_current == 1) SCR_PushLoadingScreen(false, "Loading precached sounds", ( cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND ) / ( (cl.loadmodel_total - 1) * LOADPROGRESSWEIGHT_MODEL + LOADPROGRESSWEIGHT_WORLDMODEL + LOADPROGRESSWEIGHT_WORLDMODEL_INIT + cl.loadsound_total * LOADPROGRESSWEIGHT_SOUND ) ); for (;cl.loadsound_current < cl.loadsound_total;cl.loadsound_current++) { SCR_PushLoadingScreen(false, cl.sound_name[cl.loadsound_current], 1.0 / cl.loadsound_total); if (cl.sound_precache[cl.loadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.loadsound_current])) { SCR_PopLoadingScreen(false); continue; } CL_KeepaliveMessage(true); cl.sound_precache[cl.loadsound_current] = S_PrecacheSound(cl.sound_name[cl.loadsound_current], false, true); SCR_PopLoadingScreen(false); } SCR_PopLoadingScreen(false); // finished loading sounds } if(IS_NEXUIZ_DERIVED(gamemode)) Cvar_SetValueQuick(&cl_serverextension_download, false); // in Nexuiz/Xonotic, the built in download protocol is kinda broken (misses lots // of dependencies) anyway, and can mess around with the game directory; // until this is fixed, only support pk3 downloads via curl, and turn off // individual file downloads other than for CSQC // on the other end of the download protocol, GAME_NEXUIZ/GAME_XONOTIC enforces writing // to dlcache only // idea: support download of pk3 files using this protocol later // note: the reason these loops skip already-loaded things is that it // enables this command to be issued during the game if desired if (cl.downloadmodel_current < cl.loadmodel_total) { // loading models for (;cl.downloadmodel_current < cl.loadmodel_total;cl.downloadmodel_current++) { if (aborteddownload) { if (cl.downloadmodel_current == 1) { // the worldmodel failed, but we need to set up anyway Mod_FreeQ3Shaders(); CL_SetupWorldModel(); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { cl.loadfinished = true; // now issue the spawn to move on to signon 2 like normal if (cls.netcon) Cmd_ForwardStringToServer("prespawn"); } } aborteddownload = false; continue; } if (cl.model_precache[cl.downloadmodel_current] && cl.model_precache[cl.downloadmodel_current]->Draw) continue; CL_KeepaliveMessage(true); if (cl.model_name[cl.downloadmodel_current][0] != '*' && strcmp(cl.model_name[cl.downloadmodel_current], "null") && !FS_FileExists(cl.model_name[cl.downloadmodel_current])) { if (cl.downloadmodel_current == 1) Con_Printf("Map %s not found\n", cl.model_name[cl.downloadmodel_current]); else Con_Printf("Model %s not found\n", cl.model_name[cl.downloadmodel_current]); // regarding the * check: don't try to download submodels if (cl_serverextension_download.integer && cls.netcon && cl.model_name[cl.downloadmodel_current][0] != '*' && !sv.active) { Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", cl.model_name[cl.downloadmodel_current])); // we'll try loading again when the download finishes return; } } if(cl.downloadmodel_current == 1) { // they'll be soon loaded, but make sure we apply freshly downloaded shaders from a curled pk3 Mod_FreeQ3Shaders(); } cl.model_precache[cl.downloadmodel_current] = Mod_ForName(cl.model_name[cl.downloadmodel_current], false, true, cl.model_name[cl.downloadmodel_current][0] == '*' ? cl.model_name[1] : NULL); if (cl.downloadmodel_current == 1) { // we now have the worldmodel so we can set up the game world // or maybe we do not have it (cl_serverextension_download 0) // then we need to continue loading ANYWAY! CL_SetupWorldModel(); if (!cl.loadfinished && cl_joinbeforedownloadsfinish.integer) { cl.loadfinished = true; // now issue the spawn to move on to signon 2 like normal if (cls.netcon) Cmd_ForwardStringToServer("prespawn"); } } } // finished loading models } if (cl.downloadsound_current < cl.loadsound_total) { // loading sounds for (;cl.downloadsound_current < cl.loadsound_total;cl.downloadsound_current++) { char soundname[MAX_QPATH]; if (aborteddownload) { aborteddownload = false; continue; } if (cl.sound_precache[cl.downloadsound_current] && S_IsSoundPrecached(cl.sound_precache[cl.downloadsound_current])) continue; CL_KeepaliveMessage(true); dpsnprintf(soundname, sizeof(soundname), "sound/%s", cl.sound_name[cl.downloadsound_current]); if (!FS_FileExists(soundname) && !FS_FileExists(cl.sound_name[cl.downloadsound_current])) { Con_Printf("Sound %s not found\n", soundname); if (cl_serverextension_download.integer && cls.netcon && !sv.active) { Cmd_ForwardStringToServer(va(vabuf, sizeof(vabuf), "download %s", soundname)); // we'll try loading again when the download finishes return; } } cl.sound_precache[cl.downloadsound_current] = S_PrecacheSound(cl.sound_name[cl.downloadsound_current], false, true); } // finished loading sounds } SCR_PopLoadingScreen(false); if (!cl.loadfinished) { cl.loadfinished = true; // check memory integrity Mem_CheckSentinelsGlobal(); // now issue the spawn to move on to signon 2 like normal if (cls.netcon) Cmd_ForwardStringToServer("prespawn"); } } static void CL_BeginDownloads_f(void) { // prevent cl_begindownloads from being issued multiple times in one match // to prevent accidentally cancelled downloads if(cl.loadbegun) Con_Printf("cl_begindownloads is only valid once per match\n"); else CL_BeginDownloads(false); } static void CL_StopDownload(int size, int crc) { if (cls.qw_downloadmemory && cls.qw_downloadmemorycursize == size && CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize) == crc) { int existingcrc; size_t existingsize; const char *extension; if(cls.qw_download_deflate) { unsigned char *out; size_t inflated_size; out = FS_Inflate(cls.qw_downloadmemory, cls.qw_downloadmemorycursize, &inflated_size, tempmempool); Mem_Free(cls.qw_downloadmemory); if(out) { Con_Printf("Inflated download: new size: %u (%g%%)\n", (unsigned)inflated_size, 100.0 - 100.0*(cls.qw_downloadmemorycursize / (float)inflated_size)); cls.qw_downloadmemory = out; cls.qw_downloadmemorycursize = (int)inflated_size; } else { cls.qw_downloadmemory = NULL; cls.qw_downloadmemorycursize = 0; Con_Printf("Cannot inflate download, possibly corrupt or zlib not present\n"); } } if(!cls.qw_downloadmemory) { Con_Printf("Download \"%s\" is corrupt (see above!)\n", cls.qw_downloadname); } else { crc = CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize); size = cls.qw_downloadmemorycursize; // finished file // save to disk only if we don't already have it // (this is mainly for playing back demos) existingcrc = FS_CRCFile(cls.qw_downloadname, &existingsize); if (existingsize || IS_NEXUIZ_DERIVED(gamemode) || !strcmp(cls.qw_downloadname, csqc_progname.string)) // let csprogs ALWAYS go to dlcache, to prevent "viral csprogs"; also, never put files outside dlcache for Nexuiz/Xonotic { if ((int)existingsize != size || existingcrc != crc) { // we have a mismatching file, pick another name for it char name[MAX_QPATH*2]; dpsnprintf(name, sizeof(name), "dlcache/%s.%i.%i", cls.qw_downloadname, size, crc); if (!FS_FileExists(name)) { Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", name, size, crc); FS_WriteFile(name, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); if(!strcmp(cls.qw_downloadname, csqc_progname.string)) { if(cls.caughtcsprogsdata) Mem_Free(cls.caughtcsprogsdata); cls.caughtcsprogsdata = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorycursize); memcpy(cls.caughtcsprogsdata, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); cls.caughtcsprogsdatasize = cls.qw_downloadmemorycursize; Con_DPrintf("Buffered \"%s\"\n", name); } } } } else { // we either don't have it or have a mismatching file... // so it's time to accept the file // but if we already have a mismatching file we need to rename // this new one, and if we already have this file in renamed form, // we do nothing Con_Printf("Downloaded \"%s\" (%i bytes, %i CRC)\n", cls.qw_downloadname, size, crc); FS_WriteFile(cls.qw_downloadname, cls.qw_downloadmemory, cls.qw_downloadmemorycursize); extension = FS_FileExtension(cls.qw_downloadname); if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) FS_Rescan(); } } } else if (cls.qw_downloadmemory && size) { Con_Printf("Download \"%s\" is corrupt (%i bytes, %i CRC, should be %i bytes, %i CRC), discarding\n", cls.qw_downloadname, size, crc, (int)cls.qw_downloadmemorycursize, (int)CRC_Block(cls.qw_downloadmemory, cls.qw_downloadmemorycursize)); CL_BeginDownloads(true); } if (cls.qw_downloadmemory) Mem_Free(cls.qw_downloadmemory); cls.qw_downloadmemory = NULL; cls.qw_downloadname[0] = 0; cls.qw_downloadmemorymaxsize = 0; cls.qw_downloadmemorycursize = 0; cls.qw_downloadpercent = 0; } static void CL_ParseDownload(void) { int i, start, size; static unsigned char data[NET_MAXMESSAGE]; start = MSG_ReadLong(&cl_message); size = (unsigned short)MSG_ReadShort(&cl_message); // record the start/size information to ack in the next input packet for (i = 0;i < CL_MAX_DOWNLOADACKS;i++) { if (!cls.dp_downloadack[i].start && !cls.dp_downloadack[i].size) { cls.dp_downloadack[i].start = start; cls.dp_downloadack[i].size = size; break; } } MSG_ReadBytes(&cl_message, size, data); if (!cls.qw_downloadname[0]) { if (size > 0) Con_Printf("CL_ParseDownload: received %i bytes with no download active\n", size); return; } if (start + size > cls.qw_downloadmemorymaxsize) Host_Error("corrupt download message\n"); // only advance cursize if the data is at the expected position // (gaps are unacceptable) memcpy(cls.qw_downloadmemory + start, data, size); cls.qw_downloadmemorycursize = start + size; cls.qw_downloadpercent = (int)floor((start+size) * 100.0 / cls.qw_downloadmemorymaxsize); cls.qw_downloadpercent = bound(0, cls.qw_downloadpercent, 100); cls.qw_downloadspeedcount += size; } static void CL_DownloadBegin_f(void) { int size = atoi(Cmd_Argv(1)); if (size < 0 || size > 1<<30 || FS_CheckNastyPath(Cmd_Argv(2), false)) { Con_Printf("cl_downloadbegin: received bogus information\n"); CL_StopDownload(0, 0); return; } if (cls.qw_downloadname[0]) Con_Printf("Download of %s aborted\n", cls.qw_downloadname); CL_StopDownload(0, 0); // we're really beginning a download now, so initialize stuff strlcpy(cls.qw_downloadname, Cmd_Argv(2), sizeof(cls.qw_downloadname)); cls.qw_downloadmemorymaxsize = size; cls.qw_downloadmemory = (unsigned char *) Mem_Alloc(cls.permanentmempool, cls.qw_downloadmemorymaxsize); cls.qw_downloadnumber++; cls.qw_download_deflate = false; if(Cmd_Argc() >= 4) { if(!strcmp(Cmd_Argv(3), "deflate")) cls.qw_download_deflate = true; // check further encodings here } Cmd_ForwardStringToServer("sv_startdownload"); } static void CL_StopDownload_f(void) { Curl_CancelAll(); if (cls.qw_downloadname[0]) { Con_Printf("Download of %s aborted\n", cls.qw_downloadname); CL_StopDownload(0, 0); } CL_BeginDownloads(true); } static void CL_DownloadFinished_f(void) { if (Cmd_Argc() < 3) { Con_Printf("Malformed cl_downloadfinished command\n"); return; } CL_StopDownload(atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2))); CL_BeginDownloads(false); } static void CL_SendPlayerInfo(void) { char vabuf[1024]; MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "name \"%s\"", cl_name.string)); MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "color %i %i", cl_color.integer >> 4, cl_color.integer & 15)); MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate %i", cl_rate.integer)); MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "rate_burstsize %i", cl_rate_burstsize.integer)); if (cl_pmodel.integer) { MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "pmodel %i", cl_pmodel.integer)); } if (*cl_playermodel.string) { MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "playermodel %s", cl_playermodel.string)); } if (*cl_playerskin.string) { MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, va(vabuf, sizeof(vabuf), "playerskin %s", cl_playerskin.string)); } } /* ===================== CL_SignonReply An svc_signonnum has been received, perform a client side setup ===================== */ static void CL_SignonReply (void) { Con_DPrintf("CL_SignonReply: %i\n", cls.signon); switch (cls.signon) { case 1: if (cls.netcon) { // send player info before we begin downloads // (so that the server can see the player name while downloading) CL_SendPlayerInfo(); // execute cl_begindownloads next frame // (after any commands added by svc_stufftext have been executed) // when done with downloads the "prespawn" will be sent Cbuf_AddText("\ncl_begindownloads\n"); //MSG_WriteByte (&cls.netcon->message, clc_stringcmd); //MSG_WriteString (&cls.netcon->message, "prespawn"); } else // playing a demo... make sure loading occurs as soon as possible CL_BeginDownloads(false); break; case 2: if (cls.netcon) { // LordHavoc: quake sent the player info here but due to downloads // it is sent earlier instead // CL_SendPlayerInfo(); // LordHavoc: changed to begin a loading stage and issue this when done MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, "spawn"); } break; case 3: if (cls.netcon) { MSG_WriteByte (&cls.netcon->message, clc_stringcmd); MSG_WriteString (&cls.netcon->message, "begin"); } break; case 4: // after the level has been loaded, we shouldn't need the shaders, and // if they are needed again they will be automatically loaded... // we also don't need the unused models or sounds from the last level Mod_FreeQ3Shaders(); Mod_PurgeUnused(); S_PurgeUnused(); Con_ClearNotify(); if (COM_CheckParm("-profilegameonly")) Sys_AllowProfiling(true); break; } } /* ================== CL_ParseServerInfo ================== */ static void CL_ParseServerInfo (void) { char *str; int i; protocolversion_t protocol; int nummodels, numsounds; char vabuf[1024]; // if we start loading a level and a video is still playing, stop it CL_VideoStop(); Con_DPrint("Serverinfo packet received.\n"); Collision_Cache_Reset(true); // if server is active, we already began a loading plaque if (!sv.active) { SCR_BeginLoadingPlaque(false); S_StopAllSounds(); // free q3 shaders so that any newly downloaded shaders will be active Mod_FreeQ3Shaders(); } // check memory integrity Mem_CheckSentinelsGlobal(); // clear cl_serverextension cvars Cvar_SetValueQuick(&cl_serverextension_download, 0); // // wipe the client_state_t struct // CL_ClearState (); // parse protocol version number i = MSG_ReadLong(&cl_message); protocol = Protocol_EnumForNumber(i); if (protocol == PROTOCOL_UNKNOWN) { Host_Error("CL_ParseServerInfo: Server is unrecognized protocol number (%i)", i); return; } // hack for unmarked Nehahra movie demos which had a custom protocol if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) protocol = PROTOCOL_NEHAHRAMOVIE; cls.protocol = protocol; Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol)); cl.num_entities = 1; if (protocol == PROTOCOL_QUAKEWORLD) { char gamedir[1][MAX_QPATH]; cl.qw_servercount = MSG_ReadLong(&cl_message); str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); Con_Printf("server gamedir is %s\n", str); strlcpy(gamedir[0], str, sizeof(gamedir[0])); // change gamedir if needed if (!FS_ChangeGameDirs(1, gamedir, true, false)) Host_Error("CL_ParseServerInfo: unable to switch to server specified gamedir"); cl.gametype = GAME_DEATHMATCH; cl.maxclients = 32; // parse player number i = MSG_ReadByte(&cl_message); // cl.qw_spectator is an unneeded flag, cl.scores[cl.playerentity].qw_spectator works better (it can be updated by the server during the game) //cl.qw_spectator = (i & 128) != 0; cl.realplayerentity = cl.playerentity = cl.viewentity = (i & 127) + 1; cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); // get the full level name str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); // get the movevars that are defined in the qw protocol cl.movevars_gravity = MSG_ReadFloat(&cl_message); cl.movevars_stopspeed = MSG_ReadFloat(&cl_message); cl.movevars_maxspeed = MSG_ReadFloat(&cl_message); cl.movevars_spectatormaxspeed = MSG_ReadFloat(&cl_message); cl.movevars_accelerate = MSG_ReadFloat(&cl_message); cl.movevars_airaccelerate = MSG_ReadFloat(&cl_message); cl.movevars_wateraccelerate = MSG_ReadFloat(&cl_message); cl.movevars_friction = MSG_ReadFloat(&cl_message); cl.movevars_waterfriction = MSG_ReadFloat(&cl_message); cl.movevars_entgravity = MSG_ReadFloat(&cl_message); // other movevars not in the protocol... cl.movevars_wallfriction = 0; cl.movevars_timescale = 1; cl.movevars_jumpvelocity = 270; cl.movevars_edgefriction = 1; cl.movevars_maxairspeed = 30; cl.movevars_stepheight = 18; cl.movevars_airaccel_qw = 1; cl.movevars_airaccel_sideways_friction = 0; // seperate the printfs so the server message can have a color Con_Printf("\n\n<===================================>\n\n\2%s\n", str); // check memory integrity Mem_CheckSentinelsGlobal(); if (cls.netcon) { MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd); MSG_WriteString(&cls.netcon->message, va(vabuf, sizeof(vabuf), "soundlist %i %i", cl.qw_servercount, 0)); } cl.loadbegun = false; cl.loadfinished = false; cls.state = ca_connected; cls.signon = 1; // note: on QW protocol we can't set up the gameworld until after // downloads finish... // (we don't even know the name of the map yet) // this also means cl_autodemo does not work on QW protocol... strlcpy(cl.worldname, "", sizeof(cl.worldname)); strlcpy(cl.worldnamenoextension, "", sizeof(cl.worldnamenoextension)); strlcpy(cl.worldbasename, "qw", sizeof(cl.worldbasename)); Cvar_SetQuick(&cl_worldname, cl.worldname); Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); // check memory integrity Mem_CheckSentinelsGlobal(); } else { // parse maxclients cl.maxclients = MSG_ReadByte(&cl_message); if (cl.maxclients < 1 || cl.maxclients > MAX_SCOREBOARD) { Host_Error("Bad maxclients (%u) from server", cl.maxclients); return; } cl.scores = (scoreboard_t *)Mem_Alloc(cls.levelmempool, cl.maxclients*sizeof(*cl.scores)); // parse gametype cl.gametype = MSG_ReadByte(&cl_message); // the original id singleplayer demos are bugged and contain // GAME_DEATHMATCH even for singleplayer if (cl.maxclients == 1 && cls.protocol == PROTOCOL_QUAKE) cl.gametype = GAME_COOP; // parse signon message str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); strlcpy (cl.worldmessage, str, sizeof(cl.worldmessage)); // seperate the printfs so the server message can have a color if (cls.protocol != PROTOCOL_NEHAHRAMOVIE) // no messages when playing the Nehahra movie Con_Printf("\n<===================================>\n\n\2%s\n", str); // check memory integrity Mem_CheckSentinelsGlobal(); // parse model precache list for (nummodels=1 ; ; nummodels++) { str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (!str[0]) break; if (nummodels==MAX_MODELS) Host_Error ("Server sent too many model precaches"); if (strlen(str) >= MAX_QPATH) Host_Error ("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); strlcpy (cl.model_name[nummodels], str, sizeof (cl.model_name[nummodels])); } // parse sound precache list for (numsounds=1 ; ; numsounds++) { str = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (!str[0]) break; if (numsounds==MAX_SOUNDS) Host_Error("Server sent too many sound precaches"); if (strlen(str) >= MAX_QPATH) Host_Error("Server sent a precache name of %i characters (max %i)", (int)strlen(str), MAX_QPATH - 1); strlcpy (cl.sound_name[numsounds], str, sizeof (cl.sound_name[numsounds])); } // set the base name for level-specific things... this gets updated again by CL_SetupWorldModel later strlcpy(cl.worldname, cl.model_name[1], sizeof(cl.worldname)); FS_StripExtension(cl.worldname, cl.worldnamenoextension, sizeof(cl.worldnamenoextension)); strlcpy(cl.worldbasename, !strncmp(cl.worldnamenoextension, "maps/", 5) ? cl.worldnamenoextension + 5 : cl.worldnamenoextension, sizeof(cl.worldbasename)); Cvar_SetQuick(&cl_worldmessage, cl.worldmessage); Cvar_SetQuick(&cl_worldname, cl.worldname); Cvar_SetQuick(&cl_worldnamenoextension, cl.worldnamenoextension); Cvar_SetQuick(&cl_worldbasename, cl.worldbasename); // touch all of the precached models that are still loaded so we can free // anything that isn't needed if (!sv.active) Mod_ClearUsed(); for (i = 1;i < nummodels;i++) Mod_FindName(cl.model_name[i], cl.model_name[i][0] == '*' ? cl.model_name[1] : NULL); // precache any models used by the client (this also marks them used) cl.model_bolt = Mod_ForName("progs/bolt.mdl", false, false, NULL); cl.model_bolt2 = Mod_ForName("progs/bolt2.mdl", false, false, NULL); cl.model_bolt3 = Mod_ForName("progs/bolt3.mdl", false, false, NULL); cl.model_beam = Mod_ForName("progs/beam.mdl", false, false, NULL); // we purge the models and sounds later in CL_SignonReply //Mod_PurgeUnused(); //S_PurgeUnused(); // clear sound usage flags for purging of unused sounds S_ClearUsed(); // precache any sounds used by the client cl.sfx_wizhit = S_PrecacheSound(cl_sound_wizardhit.string, false, true); cl.sfx_knighthit = S_PrecacheSound(cl_sound_hknighthit.string, false, true); cl.sfx_tink1 = S_PrecacheSound(cl_sound_tink1.string, false, true); cl.sfx_ric1 = S_PrecacheSound(cl_sound_ric1.string, false, true); cl.sfx_ric2 = S_PrecacheSound(cl_sound_ric2.string, false, true); cl.sfx_ric3 = S_PrecacheSound(cl_sound_ric3.string, false, true); cl.sfx_r_exp3 = S_PrecacheSound(cl_sound_r_exp3.string, false, true); // sounds used by the game for (i = 1;i < MAX_SOUNDS && cl.sound_name[i][0];i++) cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, true); // now we try to load everything that is new cl.loadmodel_current = 1; cl.downloadmodel_current = 1; cl.loadmodel_total = nummodels; cl.loadsound_current = 1; cl.downloadsound_current = 1; cl.loadsound_total = numsounds; cl.downloadcsqc = true; cl.loadbegun = false; cl.loadfinished = false; cl.loadcsqc = true; // check memory integrity Mem_CheckSentinelsGlobal(); // if cl_autodemo is set, automatically start recording a demo if one isn't being recorded already if (cl_autodemo.integer && cls.netcon && cls.protocol != PROTOCOL_QUAKEWORLD) { char demofile[MAX_OSPATH]; if (cls.demorecording) { // finish the previous level's demo file CL_Stop_f(); } // start a new demo file dpsnprintf (demofile, sizeof(demofile), "%s_%s.dem", Sys_TimeString (cl_autodemo_nameformat.string), cl.worldbasename); Con_Printf ("Auto-recording to %s.\n", demofile); // Reset bit 0 for every new demo Cvar_SetValueQuick(&cl_autodemo_delete, (cl_autodemo_delete.integer & ~0x1) | ((cl_autodemo_delete.integer & 0x2) ? 0x1 : 0) ); cls.demofile = FS_OpenRealFile(demofile, "wb", false); if (cls.demofile) { cls.forcetrack = -1; FS_Printf (cls.demofile, "%i\n", cls.forcetrack); cls.demorecording = true; strlcpy(cls.demoname, demofile, sizeof(cls.demoname)); cls.demo_lastcsprogssize = -1; cls.demo_lastcsprogscrc = -1; } else Con_Print ("ERROR: couldn't open.\n"); } } cl.islocalgame = NetConn_IsLocalGame(); } void CL_ValidateState(entity_state_t *s) { dp_model_t *model; if (!s->active) return; if (s->modelindex >= MAX_MODELS) Host_Error("CL_ValidateState: modelindex (%i) >= MAX_MODELS (%i)\n", s->modelindex, MAX_MODELS); // these warnings are only warnings, no corrections are made to the state // because states are often copied for decoding, which otherwise would // propogate some of the corrections accidentally // (this used to happen, sometimes affecting skin and frame) // colormap is client index + 1 if (!(s->flags & RENDER_COLORMAPPED) && s->colormap > cl.maxclients) Con_DPrintf("CL_ValidateState: colormap (%i) > cl.maxclients (%i)\n", s->colormap, cl.maxclients); if (developer_extra.integer) { model = CL_GetModelByIndex(s->modelindex); if (model && model->type && s->frame >= model->numframes) Con_DPrintf("CL_ValidateState: no such frame %i in \"%s\" (which has %i frames)\n", s->frame, model->name, model->numframes); if (model && model->type && s->skin > 0 && s->skin >= model->numskins && !(s->lightpflags & PFLAGS_FULLDYNAMIC)) Con_DPrintf("CL_ValidateState: no such skin %i in \"%s\" (which has %i skins)\n", s->skin, model->name, model->numskins); } } void CL_MoveLerpEntityStates(entity_t *ent) { float odelta[3], adelta[3]; VectorSubtract(ent->state_current.origin, ent->persistent.neworigin, odelta); VectorSubtract(ent->state_current.angles, ent->persistent.newangles, adelta); if (!ent->state_previous.active || ent->state_previous.modelindex != ent->state_current.modelindex) { // reset all persistent stuff if this is a new entity ent->persistent.lerpdeltatime = 0; ent->persistent.lerpstarttime = cl.mtime[1]; VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); VectorCopy(ent->state_current.angles, ent->persistent.oldangles); VectorCopy(ent->state_current.origin, ent->persistent.neworigin); VectorCopy(ent->state_current.angles, ent->persistent.newangles); // reset animation interpolation as well ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; ent->render.shadertime = cl.time; // reset various persistent stuff ent->persistent.muzzleflash = 0; ent->persistent.trail_allowed = false; } else if ((ent->state_previous.effects & EF_TELEPORT_BIT) != (ent->state_current.effects & EF_TELEPORT_BIT)) { // don't interpolate the move ent->persistent.lerpdeltatime = 0; ent->persistent.lerpstarttime = cl.mtime[1]; VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); VectorCopy(ent->state_current.angles, ent->persistent.oldangles); VectorCopy(ent->state_current.origin, ent->persistent.neworigin); VectorCopy(ent->state_current.angles, ent->persistent.newangles); ent->persistent.trail_allowed = false; // if(ent->state_current.frame != ent->state_previous.frame) // do this even if we did change the frame // teleport bit is only used if an animation restart, or a jump, is necessary // so it should be always harmless to do this { ent->render.framegroupblend[0].frame = ent->render.framegroupblend[1].frame = ent->state_current.frame; ent->render.framegroupblend[0].start = ent->render.framegroupblend[1].start = cl.time; ent->render.framegroupblend[0].lerp = 1;ent->render.framegroupblend[1].lerp = 0; } // note that this case must do everything the following case does too } else if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) { ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; ent->render.framegroupblend[1].lerp = 1; ent->render.framegroupblend[0].frame = ent->state_current.frame; ent->render.framegroupblend[0].start = cl.time; ent->render.framegroupblend[0].lerp = 0; } else if (DotProduct(odelta, odelta) > 1000*1000 || (cl.fixangle[0] && !cl.fixangle[1]) || (ent->state_previous.tagindex != ent->state_current.tagindex) || (ent->state_previous.tagentity != ent->state_current.tagentity)) { // don't interpolate the move // (the fixangle[] check detects teleports, but not constant fixangles // such as when spectating) ent->persistent.lerpdeltatime = 0; ent->persistent.lerpstarttime = cl.mtime[1]; VectorCopy(ent->state_current.origin, ent->persistent.oldorigin); VectorCopy(ent->state_current.angles, ent->persistent.oldangles); VectorCopy(ent->state_current.origin, ent->persistent.neworigin); VectorCopy(ent->state_current.angles, ent->persistent.newangles); ent->persistent.trail_allowed = false; } else if (ent->state_current.flags & RENDER_STEP) { // monster interpolation if (DotProduct(odelta, odelta) + DotProduct(adelta, adelta) > 0.01) { ent->persistent.lerpdeltatime = bound(0, cl.mtime[1] - ent->persistent.lerpstarttime, 0.1); ent->persistent.lerpstarttime = cl.mtime[1]; VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); VectorCopy(ent->state_current.origin, ent->persistent.neworigin); VectorCopy(ent->state_current.angles, ent->persistent.newangles); } } else { // not a monster ent->persistent.lerpstarttime = ent->state_previous.time; // no lerp if it's singleplayer if (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) ent->persistent.lerpdeltatime = 0; else ent->persistent.lerpdeltatime = bound(0, ent->state_current.time - ent->state_previous.time, 0.1); VectorCopy(ent->persistent.neworigin, ent->persistent.oldorigin); VectorCopy(ent->persistent.newangles, ent->persistent.oldangles); VectorCopy(ent->state_current.origin, ent->persistent.neworigin); VectorCopy(ent->state_current.angles, ent->persistent.newangles); } // trigger muzzleflash effect if necessary if (ent->state_current.effects & EF_MUZZLEFLASH) ent->persistent.muzzleflash = 1; // restart animation bit if ((ent->state_previous.effects & EF_RESTARTANIM_BIT) != (ent->state_current.effects & EF_RESTARTANIM_BIT)) { ent->render.framegroupblend[1] = ent->render.framegroupblend[0]; ent->render.framegroupblend[1].lerp = 1; ent->render.framegroupblend[0].frame = ent->state_current.frame; ent->render.framegroupblend[0].start = cl.time; ent->render.framegroupblend[0].lerp = 0; } } /* ================== CL_ParseBaseline ================== */ static void CL_ParseBaseline (entity_t *ent, int large) { int i; ent->state_baseline = defaultstate; // FIXME: set ent->state_baseline.number? ent->state_baseline.active = true; if (large) { ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort(&cl_message); ent->state_baseline.frame = (unsigned short) MSG_ReadShort(&cl_message); } else if (cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3) { ent->state_baseline.modelindex = (unsigned short) MSG_ReadShort(&cl_message); ent->state_baseline.frame = MSG_ReadByte(&cl_message); } else { ent->state_baseline.modelindex = MSG_ReadByte(&cl_message); ent->state_baseline.frame = MSG_ReadByte(&cl_message); } ent->state_baseline.colormap = MSG_ReadByte(&cl_message); ent->state_baseline.skin = MSG_ReadByte(&cl_message); for (i = 0;i < 3;i++) { ent->state_baseline.origin[i] = MSG_ReadCoord(&cl_message, cls.protocol); ent->state_baseline.angles[i] = MSG_ReadAngle(&cl_message, cls.protocol); } ent->state_previous = ent->state_current = ent->state_baseline; } /* ================== CL_ParseClientdata Server information pertaining to this client only ================== */ static void CL_ParseClientdata (void) { int i, bits; VectorCopy (cl.mpunchangle[0], cl.mpunchangle[1]); VectorCopy (cl.mpunchvector[0], cl.mpunchvector[1]); VectorCopy (cl.mvelocity[0], cl.mvelocity[1]); cl.mviewzoom[1] = cl.mviewzoom[0]; if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE || cls.protocol == PROTOCOL_NEHAHRABJP || cls.protocol == PROTOCOL_NEHAHRABJP2 || cls.protocol == PROTOCOL_NEHAHRABJP3 || cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3 || cls.protocol == PROTOCOL_DARKPLACES4 || cls.protocol == PROTOCOL_DARKPLACES5) { cl.stats[STAT_VIEWHEIGHT] = DEFAULT_VIEWHEIGHT; cl.stats[STAT_ITEMS] = 0; cl.stats[STAT_VIEWZOOM] = 255; } cl.idealpitch = 0; cl.mpunchangle[0][0] = 0; cl.mpunchangle[0][1] = 0; cl.mpunchangle[0][2] = 0; cl.mpunchvector[0][0] = 0; cl.mpunchvector[0][1] = 0; cl.mpunchvector[0][2] = 0; cl.mvelocity[0][0] = 0; cl.mvelocity[0][1] = 0; cl.mvelocity[0][2] = 0; cl.mviewzoom[0] = 1; bits = (unsigned short) MSG_ReadShort(&cl_message); if (bits & SU_EXTEND1) bits |= (MSG_ReadByte(&cl_message) << 16); if (bits & SU_EXTEND2) bits |= (MSG_ReadByte(&cl_message) << 24); if (bits & SU_VIEWHEIGHT) cl.stats[STAT_VIEWHEIGHT] = MSG_ReadChar(&cl_message); if (bits & SU_IDEALPITCH) cl.idealpitch = MSG_ReadChar(&cl_message); for (i = 0;i < 3;i++) { if (bits & (SU_PUNCH1<= cl.max_static_entities) Host_Error ("Too many static entities"); ent = &cl.static_entities[cl.num_static_entities++]; CL_ParseBaseline (ent, large); if (ent->state_baseline.modelindex == 0) { Con_DPrintf("svc_parsestatic: static entity without model at %f %f %f\n", ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2]); cl.num_static_entities--; // This is definitely a cheesy way to conserve resources... return; } // copy it to the current state ent->render.model = CL_GetModelByIndex(ent->state_baseline.modelindex); ent->render.framegroupblend[0].frame = ent->state_baseline.frame; ent->render.framegroupblend[0].lerp = 1; // make torchs play out of sync ent->render.framegroupblend[0].start = lhrandom(-10, -1); ent->render.skinnum = ent->state_baseline.skin; ent->render.effects = ent->state_baseline.effects; ent->render.alpha = 1; //VectorCopy (ent->state_baseline.origin, ent->render.origin); //VectorCopy (ent->state_baseline.angles, ent->render.angles); Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, ent->state_baseline.origin[0], ent->state_baseline.origin[1], ent->state_baseline.origin[2], ent->state_baseline.angles[0], ent->state_baseline.angles[1], ent->state_baseline.angles[2], 1); ent->render.allowdecals = true; CL_UpdateRenderEntity(&ent->render); } /* =================== CL_ParseStaticSound =================== */ static void CL_ParseStaticSound (int large) { vec3_t org; int sound_num, vol, atten; MSG_ReadVector(&cl_message, org, cls.protocol); if (large || cls.protocol == PROTOCOL_NEHAHRABJP2) sound_num = (unsigned short) MSG_ReadShort(&cl_message); else sound_num = MSG_ReadByte(&cl_message); if (sound_num < 0 || sound_num >= MAX_SOUNDS) { Con_Printf("CL_ParseStaticSound: sound_num(%i) >= MAX_SOUNDS (%i)\n", sound_num, MAX_SOUNDS); return; } vol = MSG_ReadByte(&cl_message); atten = MSG_ReadByte(&cl_message); S_StaticSound (cl.sound_precache[sound_num], org, vol/255.0f, atten); } static void CL_ParseEffect (void) { vec3_t org; int modelindex, startframe, framecount, framerate; MSG_ReadVector(&cl_message, org, cls.protocol); modelindex = MSG_ReadByte(&cl_message); startframe = MSG_ReadByte(&cl_message); framecount = MSG_ReadByte(&cl_message); framerate = MSG_ReadByte(&cl_message); CL_Effect(org, modelindex, startframe, framecount, framerate); } static void CL_ParseEffect2 (void) { vec3_t org; int modelindex, startframe, framecount, framerate; MSG_ReadVector(&cl_message, org, cls.protocol); modelindex = (unsigned short) MSG_ReadShort(&cl_message); startframe = (unsigned short) MSG_ReadShort(&cl_message); framecount = MSG_ReadByte(&cl_message); framerate = MSG_ReadByte(&cl_message); CL_Effect(org, modelindex, startframe, framecount, framerate); } void CL_NewBeam (int ent, vec3_t start, vec3_t end, dp_model_t *m, int lightning) { int i; beam_t *b = NULL; if (ent >= MAX_EDICTS) { Con_Printf("CL_NewBeam: invalid entity number %i\n", ent); ent = 0; } if (ent >= cl.max_entities) CL_ExpandEntities(ent); // override any beam with the same entity i = cl.max_beams; if (ent) for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) if (b->entity == ent) break; // if the entity was not found then just replace an unused beam if (i == cl.max_beams) for (i = 0, b = cl.beams;i < cl.max_beams;i++, b++) if (!b->model) break; if (i < cl.max_beams) { cl.num_beams = max(cl.num_beams, i + 1); b->entity = ent; b->lightning = lightning; b->model = m; b->endtime = cl.mtime[0] + 0.2; VectorCopy (start, b->start); VectorCopy (end, b->end); } else Con_Print("beam list overflow!\n"); } static void CL_ParseBeam (dp_model_t *m, int lightning) { int ent; vec3_t start, end; ent = (unsigned short) MSG_ReadShort(&cl_message); MSG_ReadVector(&cl_message, start, cls.protocol); MSG_ReadVector(&cl_message, end, cls.protocol); if (ent >= MAX_EDICTS) { Con_Printf("CL_ParseBeam: invalid entity number %i\n", ent); ent = 0; } CL_NewBeam(ent, start, end, m, lightning); } static void CL_ParseTempEntity(void) { int type; vec3_t pos, pos2; vec3_t vel1, vel2; vec3_t dir; vec3_t color; int rnd; int colorStart, colorLength, count; float velspeed, radius; unsigned char *tempcolor; matrix4x4_t tempmatrix; if (cls.protocol == PROTOCOL_QUAKEWORLD) { type = MSG_ReadByte(&cl_message); switch (type) { case QW_TE_WIZSPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); break; case QW_TE_KNIGHTSPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); break; case QW_TE_SPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; case QW_TE_SUPERSPIKE: // super spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; case QW_TE_EXPLOSION: // rocket explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); CL_Effect(pos, cl.qw_modelindex_s_explod, 0, 6, 10); break; case QW_TE_TAREXPLOSION: // tarbaby explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case QW_TE_LIGHTNING1: // lightning bolts CL_ParseBeam(cl.model_bolt, true); break; case QW_TE_LIGHTNING2: // lightning bolts CL_ParseBeam(cl.model_bolt2, true); break; case QW_TE_LIGHTNING3: // lightning bolts CL_ParseBeam(cl.model_bolt3, false); break; case QW_TE_LAVASPLASH: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case QW_TE_TELEPORT: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case QW_TE_GUNSHOT: // bullet hitting wall radius = MSG_ReadByte(&cl_message); MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); VectorSet(pos2, pos[0] + radius, pos[1] + radius, pos[2] + radius); VectorSet(pos, pos[0] - radius, pos[1] - radius, pos[2] - radius); CL_ParticleEffect(EFFECT_TE_GUNSHOT, radius, pos, pos2, vec3_origin, vec3_origin, NULL, 0); if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) { if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } } break; case QW_TE_BLOOD: count = MSG_ReadByte(&cl_message); MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case QW_TE_LIGHTNINGBLOOD: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_BLOOD, 2.5, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; default: Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); } } else { type = MSG_ReadByte(&cl_message); switch (type) { case TE_WIZSPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_WIZSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_wizhit, pos, 1, 1); break; case TE_KNIGHTSPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_KNIGHTSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_knighthit, pos, 1, 1); break; case TE_SPIKE: // spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; case TE_SPIKEQUAD: // quad spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; case TE_SUPERSPIKE: // super spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SUPERSPIKE, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; case TE_SUPERSPIKEQUAD: // quad super spike hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_SUPERSPIKEQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } break; // LordHavoc: added for improved blood splatters case TE_BLOOD: // blood puff MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); dir[0] = MSG_ReadChar(&cl_message); dir[1] = MSG_ReadChar(&cl_message); dir[2] = MSG_ReadChar(&cl_message); count = MSG_ReadByte(&cl_message); CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos, dir, dir, NULL, 0); break; case TE_SPARK: // spark shower MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); dir[0] = MSG_ReadChar(&cl_message); dir[1] = MSG_ReadChar(&cl_message); dir[2] = MSG_ReadChar(&cl_message); count = MSG_ReadByte(&cl_message); CL_ParticleEffect(EFFECT_TE_SPARK, count, pos, pos, dir, dir, NULL, 0); break; case TE_PLASMABURN: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_PLASMABURN, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; // LordHavoc: added for improved gore case TE_BLOODSHOWER: // vaporized body MSG_ReadVector(&cl_message, pos, cls.protocol); // mins MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs velspeed = MSG_ReadCoord(&cl_message, cls.protocol); // speed count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles vel1[0] = -velspeed; vel1[1] = -velspeed; vel1[2] = -velspeed; vel2[0] = velspeed; vel2[1] = velspeed; vel2[2] = velspeed; CL_ParticleEffect(EFFECT_TE_BLOOD, count, pos, pos2, vel1, vel2, NULL, 0); break; case TE_PARTICLECUBE: // general purpose particle effect MSG_ReadVector(&cl_message, pos, cls.protocol); // mins MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs MSG_ReadVector(&cl_message, dir, cls.protocol); // dir count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles colorStart = MSG_ReadByte(&cl_message); // color colorLength = MSG_ReadByte(&cl_message); // gravity (1 or 0) velspeed = MSG_ReadCoord(&cl_message, cls.protocol); // randomvel CL_ParticleCube(pos, pos2, dir, count, colorStart, colorLength != 0, velspeed); break; case TE_PARTICLERAIN: // general purpose particle effect MSG_ReadVector(&cl_message, pos, cls.protocol); // mins MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs MSG_ReadVector(&cl_message, dir, cls.protocol); // dir count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles colorStart = MSG_ReadByte(&cl_message); // color CL_ParticleRain(pos, pos2, dir, count, colorStart, 0); break; case TE_PARTICLESNOW: // general purpose particle effect MSG_ReadVector(&cl_message, pos, cls.protocol); // mins MSG_ReadVector(&cl_message, pos2, cls.protocol); // maxs MSG_ReadVector(&cl_message, dir, cls.protocol); // dir count = (unsigned short) MSG_ReadShort(&cl_message); // number of particles colorStart = MSG_ReadByte(&cl_message); // color CL_ParticleRain(pos, pos2, dir, count, colorStart, 1); break; case TE_GUNSHOT: // bullet hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if(cl_sound_ric_gunshot.integer & RIC_GUNSHOT) { if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } } break; case TE_GUNSHOTQUAD: // quad bullet hitting wall MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_GUNSHOTQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); if(cl_sound_ric_gunshot.integer & RIC_GUNSHOTQUAD) { if (rand() % 5) S_StartSound(-1, 0, cl.sfx_tink1, pos, 1, 1); else { rnd = rand() & 3; if (rnd == 1) S_StartSound(-1, 0, cl.sfx_ric1, pos, 1, 1); else if (rnd == 2) S_StartSound(-1, 0, cl.sfx_ric2, pos, 1, 1); else S_StartSound(-1, 0, cl.sfx_ric3, pos, 1, 1); } } break; case TE_EXPLOSION: // rocket explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_EXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_EXPLOSIONQUAD: // quad rocket explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_EXPLOSIONQUAD, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_EXPLOSION3: // Nehahra movie colored lighting explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); color[0] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); color[1] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); color[2] = MSG_ReadCoord(&cl_message, cls.protocol) * (2.0f / 1.0f); CL_ParticleExplosion(pos); Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_EXPLOSIONRGB: // colored lighting explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleExplosion(pos); color[0] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); color[1] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); color[2] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_TAREXPLOSION: // tarbaby explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_TAREXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_SMALLFLASH: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_SMALLFLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case TE_CUSTOMFLASH: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 4); radius = (MSG_ReadByte(&cl_message) + 1) * 8; velspeed = (MSG_ReadByte(&cl_message) + 1) * (1.0 / 256.0); color[0] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); color[1] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); color[2] = MSG_ReadByte(&cl_message) * (2.0f / 255.0f); Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); CL_AllocLightFlash(NULL, &tempmatrix, radius, color[0], color[1], color[2], radius / velspeed, velspeed, 0, -1, true, 1, 0.25, 1, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); break; case TE_FLAMEJET: MSG_ReadVector(&cl_message, pos, cls.protocol); MSG_ReadVector(&cl_message, dir, cls.protocol); count = MSG_ReadByte(&cl_message); CL_ParticleEffect(EFFECT_TE_FLAMEJET, count, pos, pos, dir, dir, NULL, 0); break; case TE_LIGHTNING1: // lightning bolts CL_ParseBeam(cl.model_bolt, true); break; case TE_LIGHTNING2: // lightning bolts CL_ParseBeam(cl.model_bolt2, true); break; case TE_LIGHTNING3: // lightning bolts CL_ParseBeam(cl.model_bolt3, false); break; // PGM 01/21/97 case TE_BEAM: // grappling hook beam CL_ParseBeam(cl.model_beam, false); break; // PGM 01/21/97 // LordHavoc: for compatibility with the Nehahra movie... case TE_LIGHTNING4NEH: CL_ParseBeam(Mod_ForName(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), true, false, NULL), false); break; case TE_LAVASPLASH: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_ParticleEffect(EFFECT_TE_LAVASPLASH, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case TE_TELEPORT: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_ParticleEffect(EFFECT_TE_TELEPORT, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case TE_EXPLOSION2: // color mapped explosion MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); colorStart = MSG_ReadByte(&cl_message); colorLength = MSG_ReadByte(&cl_message); if (colorLength == 0) colorLength = 1; CL_ParticleExplosion2(pos, colorStart, colorLength); tempcolor = palette_rgb[(rand()%colorLength) + colorStart]; color[0] = tempcolor[0] * (2.0f / 255.0f); color[1] = tempcolor[1] * (2.0f / 255.0f); color[2] = tempcolor[2] * (2.0f / 255.0f); Matrix4x4_CreateTranslate(&tempmatrix, pos[0], pos[1], pos[2]); CL_AllocLightFlash(NULL, &tempmatrix, 350, color[0], color[1], color[2], 700, 0.5, 0, -1, true, 1, 0.25, 0.25, 1, 1, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_TEI_G3: MSG_ReadVector(&cl_message, pos, cls.protocol); MSG_ReadVector(&cl_message, pos2, cls.protocol); MSG_ReadVector(&cl_message, dir, cls.protocol); CL_ParticleTrail(EFFECT_TE_TEI_G3, 1, pos, pos2, dir, dir, NULL, 0, true, true, NULL, NULL, 1); break; case TE_TEI_SMOKE: MSG_ReadVector(&cl_message, pos, cls.protocol); MSG_ReadVector(&cl_message, dir, cls.protocol); count = MSG_ReadByte(&cl_message); CL_FindNonSolidLocation(pos, pos, 4); CL_ParticleEffect(EFFECT_TE_TEI_SMOKE, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; case TE_TEI_BIGEXPLOSION: MSG_ReadVector(&cl_message, pos, cls.protocol); CL_FindNonSolidLocation(pos, pos, 10); CL_ParticleEffect(EFFECT_TE_TEI_BIGEXPLOSION, 1, pos, pos, vec3_origin, vec3_origin, NULL, 0); S_StartSound(-1, 0, cl.sfx_r_exp3, pos, 1, 1); break; case TE_TEI_PLASMAHIT: MSG_ReadVector(&cl_message, pos, cls.protocol); MSG_ReadVector(&cl_message, dir, cls.protocol); count = MSG_ReadByte(&cl_message); CL_FindNonSolidLocation(pos, pos, 5); CL_ParticleEffect(EFFECT_TE_TEI_PLASMAHIT, count, pos, pos, vec3_origin, vec3_origin, NULL, 0); break; default: Host_Error("CL_ParseTempEntity: bad type %d (hex %02X)", type, type); } } } static void CL_ParseTrailParticles(void) { int entityindex; int effectindex; vec3_t start, end; entityindex = (unsigned short)MSG_ReadShort(&cl_message); if (entityindex >= MAX_EDICTS) entityindex = 0; if (entityindex >= cl.max_entities) CL_ExpandEntities(entityindex); effectindex = (unsigned short)MSG_ReadShort(&cl_message); MSG_ReadVector(&cl_message, start, cls.protocol); MSG_ReadVector(&cl_message, end, cls.protocol); CL_ParticleTrail(effectindex, 1, start, end, vec3_origin, vec3_origin, entityindex > 0 ? cl.entities + entityindex : NULL, 0, true, true, NULL, NULL, 1); } static void CL_ParsePointParticles(void) { int effectindex, count; vec3_t origin, velocity; effectindex = (unsigned short)MSG_ReadShort(&cl_message); MSG_ReadVector(&cl_message, origin, cls.protocol); MSG_ReadVector(&cl_message, velocity, cls.protocol); count = (unsigned short)MSG_ReadShort(&cl_message); CL_ParticleEffect(effectindex, count, origin, origin, velocity, velocity, NULL, 0); } static void CL_ParsePointParticles1(void) { int effectindex; vec3_t origin; effectindex = (unsigned short)MSG_ReadShort(&cl_message); MSG_ReadVector(&cl_message, origin, cls.protocol); CL_ParticleEffect(effectindex, 1, origin, origin, vec3_origin, vec3_origin, NULL, 0); } typedef struct cl_iplog_item_s { char *address; char *name; } cl_iplog_item_t; static qboolean cl_iplog_loaded = false; static int cl_iplog_numitems = 0; static int cl_iplog_maxitems = 0; static cl_iplog_item_t *cl_iplog_items; static void CL_IPLog_Load(void); static void CL_IPLog_Add(const char *address, const char *name, qboolean checkexisting, qboolean addtofile) { int i; size_t sz_name, sz_address; if (!address || !address[0] || !name || !name[0]) return; if (!cl_iplog_loaded) CL_IPLog_Load(); if (developer_extra.integer) Con_DPrintf("CL_IPLog_Add(\"%s\", \"%s\", %i, %i);\n", address, name, checkexisting, addtofile); // see if it already exists if (checkexisting) { for (i = 0;i < cl_iplog_numitems;i++) { if (!strcmp(cl_iplog_items[i].address, address) && !strcmp(cl_iplog_items[i].name, name)) { if (developer_extra.integer) Con_DPrintf("... found existing \"%s\" \"%s\"\n", cl_iplog_items[i].address, cl_iplog_items[i].name); return; } } } // it does not already exist in the iplog, so add it if (cl_iplog_maxitems <= cl_iplog_numitems || !cl_iplog_items) { cl_iplog_item_t *olditems = cl_iplog_items; cl_iplog_maxitems = max(1024, cl_iplog_maxitems + 256); cl_iplog_items = (cl_iplog_item_t *) Mem_Alloc(cls.permanentmempool, cl_iplog_maxitems * sizeof(cl_iplog_item_t)); if (olditems) { if (cl_iplog_numitems) memcpy(cl_iplog_items, olditems, cl_iplog_numitems * sizeof(cl_iplog_item_t)); Mem_Free(olditems); } } sz_address = strlen(address) + 1; sz_name = strlen(name) + 1; cl_iplog_items[cl_iplog_numitems].address = (char *) Mem_Alloc(cls.permanentmempool, sz_address); cl_iplog_items[cl_iplog_numitems].name = (char *) Mem_Alloc(cls.permanentmempool, sz_name); strlcpy(cl_iplog_items[cl_iplog_numitems].address, address, sz_address); // TODO: maybe it would be better to strip weird characters from name when // copying it here rather than using a straight strcpy? strlcpy(cl_iplog_items[cl_iplog_numitems].name, name, sz_name); cl_iplog_numitems++; if (addtofile) { // add it to the iplog.txt file // TODO: this ought to open the one in the userpath version of the base // gamedir, not the current gamedir // not necessary for mobile #ifndef DP_MOBILETOUCH Log_Printf(cl_iplog_name.string, "%s %s\n", address, name); if (developer_extra.integer) Con_DPrintf("CL_IPLog_Add: appending this line to %s: %s %s\n", cl_iplog_name.string, address, name); #endif } } static void CL_IPLog_Load(void) { int i, len, linenumber; char *text, *textend; unsigned char *filedata; fs_offset_t filesize; char line[MAX_INPUTLINE]; char address[MAX_INPUTLINE]; cl_iplog_loaded = true; // TODO: this ought to open the one in the userpath version of the base // gamedir, not the current gamedir // not necessary for mobile #ifndef DP_MOBILETOUCH filedata = FS_LoadFile(cl_iplog_name.string, tempmempool, true, &filesize); #else filedata = NULL; #endif if (!filedata) return; text = (char *)filedata; textend = text + filesize; for (linenumber = 1;text < textend;linenumber++) { for (len = 0;text < textend && *text != '\r' && *text != '\n';text++) if (len < (int)sizeof(line) - 1) line[len++] = *text; line[len] = 0; if (text < textend && *text == '\r' && text[1] == '\n') text++; if (text < textend && *text == '\n') text++; if (line[0] == '/' && line[1] == '/') continue; // skip comments if anyone happens to add them for (i = 0;i < len && !ISWHITESPACE(line[i]);i++) address[i] = line[i]; address[i] = 0; // skip exactly one space character i++; // address contains the address with termination, // line + i contains the name with termination if (address[0] && line[i]) CL_IPLog_Add(address, line + i, false, false); else Con_Printf("%s:%i: could not parse address and name:\n%s\n", cl_iplog_name.string, linenumber, line); } } static void CL_IPLog_List_f(void) { int i, j; const char *addressprefix; if (Cmd_Argc() > 2) { Con_Printf("usage: %s 123.456.789.\n", Cmd_Argv(0)); return; } addressprefix = ""; if (Cmd_Argc() >= 2) addressprefix = Cmd_Argv(1); if (!cl_iplog_loaded) CL_IPLog_Load(); if (addressprefix && addressprefix[0]) Con_Printf("Listing iplog addresses beginning with %s\n", addressprefix); else Con_Printf("Listing all iplog entries\n"); Con_Printf("address name\n"); for (i = 0;i < cl_iplog_numitems;i++) { if (addressprefix && addressprefix[0]) { for (j = 0;addressprefix[j];j++) if (addressprefix[j] != cl_iplog_items[i].address[j]) break; // if this address does not begin with the addressprefix string // simply omit it from the output if (addressprefix[j]) continue; } // if name is less than 15 characters, left justify it and pad // if name is more than 15 characters, print all of it, not worrying // about the fact it will misalign the columns if (strlen(cl_iplog_items[i].address) < 15) Con_Printf("%-15s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); else Con_Printf("%5s %s\n", cl_iplog_items[i].address, cl_iplog_items[i].name); } } // look for anything interesting like player IP addresses or ping reports static qboolean CL_ExaminePrintString(const char *text) { int len; const char *t; char temp[MAX_INPUTLINE]; if (!strcmp(text, "Client ping times:\n")) { cl.parsingtextmode = CL_PARSETEXTMODE_PING; // hide ping reports in demos if (cls.demoplayback) cl.parsingtextexpectingpingforscores = 1; for(cl.parsingtextplayerindex = 0; cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0]; cl.parsingtextplayerindex++) ; if (cl.parsingtextplayerindex >= cl.maxclients) // should never happen, since the client itself should be in cl.scores { Con_Printf("ping reply but empty scoreboard?!?\n"); cl.parsingtextmode = CL_PARSETEXTMODE_NONE; cl.parsingtextexpectingpingforscores = 0; } cl.parsingtextexpectingpingforscores = cl.parsingtextexpectingpingforscores ? 2 : 0; return !cl.parsingtextexpectingpingforscores; } if (!strncmp(text, "host: ", 9)) { // cl.parsingtextexpectingpingforscores = false; // really? cl.parsingtextmode = CL_PARSETEXTMODE_STATUS; cl.parsingtextplayerindex = 0; return true; } if (cl.parsingtextmode == CL_PARSETEXTMODE_PING) { // if anything goes wrong, we'll assume this is not a ping report qboolean expected = cl.parsingtextexpectingpingforscores != 0; cl.parsingtextexpectingpingforscores = 0; cl.parsingtextmode = CL_PARSETEXTMODE_NONE; t = text; while (*t == ' ') t++; if ((*t >= '0' && *t <= '9') || *t == '-') { int ping = atoi(t); while ((*t >= '0' && *t <= '9') || *t == '-') t++; if (*t == ' ') { int charindex = 0; t++; if(cl.parsingtextplayerindex < cl.maxclients) { for (charindex = 0;cl.scores[cl.parsingtextplayerindex].name[charindex] == t[charindex];charindex++) ; // note: the matching algorithm stops at the end of the player name because some servers append text such as " READY" after the player name in the scoreboard but not in the ping report //if (cl.scores[cl.parsingtextplayerindex].name[charindex] == 0 && t[charindex] == '\n') if (t[charindex] == '\n') { cl.scores[cl.parsingtextplayerindex].qw_ping = bound(0, ping, 9999); for (cl.parsingtextplayerindex++;cl.parsingtextplayerindex < cl.maxclients && !cl.scores[cl.parsingtextplayerindex].name[0];cl.parsingtextplayerindex++) ; //if (cl.parsingtextplayerindex < cl.maxclients) // we could still get unconnecteds! { // we parsed a valid ping entry, so expect another to follow cl.parsingtextmode = CL_PARSETEXTMODE_PING; cl.parsingtextexpectingpingforscores = expected; } return !expected; } } if (!strncmp(t, "unconnected\n", 12)) { // just ignore cl.parsingtextmode = CL_PARSETEXTMODE_PING; cl.parsingtextexpectingpingforscores = expected; return !expected; } else Con_DPrintf("player names '%s' and '%s' didn't match\n", cl.scores[cl.parsingtextplayerindex].name, t); } } } if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS) { if (!strncmp(text, "players: ", 9)) { cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; cl.parsingtextplayerindex = 0; return true; } else if (!strstr(text, ": ")) { cl.parsingtextmode = CL_PARSETEXTMODE_NONE; // status report ended return true; } } if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERID) { // if anything goes wrong, we'll assume this is not a status report cl.parsingtextmode = CL_PARSETEXTMODE_NONE; if (text[0] == '#' && text[1] >= '0' && text[1] <= '9') { t = text + 1; cl.parsingtextplayerindex = atoi(t) - 1; while (*t >= '0' && *t <= '9') t++; if (*t == ' ') { cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERIP; return true; } // the player name follows here, along with frags and time } } if (cl.parsingtextmode == CL_PARSETEXTMODE_STATUS_PLAYERIP) { // if anything goes wrong, we'll assume this is not a status report cl.parsingtextmode = CL_PARSETEXTMODE_NONE; if (text[0] == ' ') { t = text; while (*t == ' ') t++; for (len = 0;*t && *t != '\n';t++) if (len < (int)sizeof(temp) - 1) temp[len++] = *t; temp[len] = 0; // botclient is perfectly valid, but we don't care about bots // also don't try to look up the name of an invalid player index if (strcmp(temp, "botclient") && cl.parsingtextplayerindex >= 0 && cl.parsingtextplayerindex < cl.maxclients && cl.scores[cl.parsingtextplayerindex].name[0]) { // log the player name and IP address string // (this operates entirely on strings to avoid issues with the // nature of a network address) CL_IPLog_Add(temp, cl.scores[cl.parsingtextplayerindex].name, true, true); } cl.parsingtextmode = CL_PARSETEXTMODE_STATUS_PLAYERID; return true; } } return true; } extern cvar_t slowmo; extern cvar_t cl_lerpexcess; static void CL_NetworkTimeReceived(double newtime) { double timehigh; cl.mtime[1] = cl.mtime[0]; cl.mtime[0] = newtime; if (cl_nolerp.integer || cls.timedemo || (cl.islocalgame && !sv_fixedframeratesingleplayer.integer) || cl.mtime[1] == cl.mtime[0] || cls.signon < SIGNONS) cl.time = cl.mtime[1] = newtime; else if (cls.demoplayback) { // when time falls behind during demo playback it means the cl.mtime[1] was altered // due to a large time gap, so treat it as an instant change in time // (this can also happen during heavy packet loss in the demo) if (cl.time < newtime - 0.1) cl.mtime[1] = cl.time = newtime; } else if (cls.protocol != PROTOCOL_QUAKEWORLD) { cl.mtime[1] = max(cl.mtime[1], cl.mtime[0] - 0.1); if (developer_extra.integer && vid_activewindow) { if (cl.time < cl.mtime[1] - (cl.mtime[0] - cl.mtime[1])) Con_DPrintf("--- cl.time < cl.mtime[1] (%f < %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); else if (cl.time > cl.mtime[0] + (cl.mtime[0] - cl.mtime[1])) Con_DPrintf("--- cl.time > cl.mtime[0] (%f > %f ... %f)\n", cl.time, cl.mtime[1], cl.mtime[0]); } cl.time += (cl.mtime[1] - cl.time) * bound(0, cl_nettimesyncfactor.value, 1); timehigh = cl.mtime[1] + (cl.mtime[0] - cl.mtime[1]) * cl_nettimesyncboundtolerance.value; if (cl_nettimesyncboundmode.integer == 1) cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); else if (cl_nettimesyncboundmode.integer == 2) { if (cl.time < cl.mtime[1] || cl.time > timehigh) cl.time = cl.mtime[1]; } else if (cl_nettimesyncboundmode.integer == 3) { if ((cl.time < cl.mtime[1] && cl.oldtime < cl.mtime[1]) || (cl.time > timehigh && cl.oldtime > timehigh)) cl.time = cl.mtime[1]; } else if (cl_nettimesyncboundmode.integer == 4) { if (fabs(cl.time - cl.mtime[1]) > 0.5) cl.time = cl.mtime[1]; // reset else if (fabs(cl.time - cl.mtime[1]) > 0.1) cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast else if (cl.time > cl.mtime[1]) cl.time -= 0.002 * cl.movevars_timescale; // fall into the past by 2ms else cl.time += 0.001 * cl.movevars_timescale; // creep forward 1ms } else if (cl_nettimesyncboundmode.integer == 5) { if (fabs(cl.time - cl.mtime[1]) > 0.5) cl.time = cl.mtime[1]; // reset else if (fabs(cl.time - cl.mtime[1]) > 0.1) cl.time += 0.5 * (cl.mtime[1] - cl.time); // fast else cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); } else if (cl_nettimesyncboundmode.integer == 6) { cl.time = bound(cl.mtime[1], cl.time, cl.mtime[0]); cl.time = bound(cl.time - 0.002 * cl.movevars_timescale, cl.mtime[1], cl.time + 0.001 * cl.movevars_timescale); } } // this packet probably contains a player entity update, so we will need // to update the prediction cl.movement_replay = true; // this may get updated later in parsing by svc_clientdata cl.onground = false; // if true the cl.viewangles are interpolated from cl.mviewangles[] // during this frame // (makes spectating players much smoother and prevents mouse movement from turning) cl.fixangle[1] = cl.fixangle[0]; cl.fixangle[0] = false; if (!cls.demoplayback) VectorCopy(cl.mviewangles[0], cl.mviewangles[1]); // update the csqc's server timestamps, critical for proper sync CSQC_UpdateNetworkTimes(cl.mtime[0], cl.mtime[1]); if (cl.mtime[0] > cl.mtime[1]) World_Physics_Frame(&cl.world, cl.mtime[0] - cl.mtime[1], cl.movevars_gravity); // only lerp entities that also get an update in this frame, when lerp excess is used if(cl_lerpexcess.value > 0) { int i; for (i = 1;i < cl.num_entities;i++) { if (cl.entities_active[i]) { entity_t *ent = cl.entities + i; ent->persistent.lerpdeltatime = 0; } } } } #define SHOWNET(x) if(cl_shownet.integer==2)Con_Printf("%3i:%s(%i)\n", cl_message.readcount-1, x, cmd); /* ===================== CL_ParseServerMessage ===================== */ int parsingerror = false; void CL_ParseServerMessage(void) { int cmd; int i; protocolversion_t protocol; unsigned char cmdlog[32]; const char *cmdlogname[32], *temp; int cmdindex, cmdcount = 0; qboolean qwplayerupdatereceived; qboolean strip_pqc; char vabuf[1024]; // LordHavoc: moved demo message writing from before the packet parse to // after the packet parse so that CL_Stop_f can be called by cl_autodemo // code in CL_ParseServerinfo //if (cls.demorecording) // CL_WriteDemoMessage (&cl_message); cl.last_received_message = realtime; CL_KeepaliveMessage(false); // // if recording demos, copy the message out // if (cl_shownet.integer == 1) Con_Printf("%f %i\n", realtime, cl_message.cursize); else if (cl_shownet.integer == 2) Con_Print("------------------\n"); // // parse the message // //MSG_BeginReading (); parsingerror = true; if (cls.protocol == PROTOCOL_QUAKEWORLD) { CL_NetworkTimeReceived(realtime); // qw has no clock // kill all qw nails cl.qw_num_nails = 0; // fade weapon view kick cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * bound(0, cl.time - cl.oldtime, 0.1), 0); cls.servermovesequence = cls.netcon->qw.incoming_sequence; qwplayerupdatereceived = false; while (1) { if (cl_message.badread) Host_Error ("CL_ParseServerMessage: Bad QW server message"); cmd = MSG_ReadByte(&cl_message); if (cmd == -1) { SHOWNET("END OF MESSAGE"); break; // end of message } cmdindex = cmdcount & 31; cmdcount++; cmdlog[cmdindex] = cmd; SHOWNET(qw_svc_strings[cmd]); cmdlogname[cmdindex] = qw_svc_strings[cmd]; if (!cmdlogname[cmdindex]) { // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) const char *d = ""; cmdlogname[cmdindex] = d; } // other commands switch (cmd) { default: { char description[32*64], logtemp[64]; int count; strlcpy(description, "packet dump: ", sizeof(description)); i = cmdcount - 32; if (i < 0) i = 0; count = cmdcount - i; i &= 31; while(count > 0) { dpsnprintf(logtemp, sizeof(logtemp), "%3i:%s ", cmdlog[i], cmdlogname[i]); strlcat(description, logtemp, sizeof(description)); count--; i++; i &= 31; } description[strlen(description)-1] = '\n'; // replace the last space with a newline Con_Print(description); Host_Error("CL_ParseServerMessage: Illegible server message"); } break; case qw_svc_nop: //Con_Printf("qw_svc_nop\n"); break; case qw_svc_disconnect: Con_Printf("Server disconnected\n"); if (cls.demonum != -1) CL_NextDemo(); else CL_Disconnect(); return; case qw_svc_print: i = MSG_ReadByte(&cl_message); temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports { if (i == 3) // chat CSQC_AddPrintText(va(vabuf, sizeof(vabuf), "\1%s", temp)); //[515]: csqc else CSQC_AddPrintText(temp); } break; case qw_svc_centerprint: CL_VM_Parse_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc break; case qw_svc_stufftext: CL_VM_Parse_StuffCmd(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc break; case qw_svc_damage: // svc_damage protocol is identical to nq V_ParseDamage (); break; case qw_svc_serverdata: //Cbuf_Execute(); // make sure any stuffed commands are done CL_ParseServerInfo(); break; case qw_svc_setangle: for (i=0 ; i<3 ; i++) cl.viewangles[i] = MSG_ReadAngle(&cl_message, cls.protocol); if (!cls.demoplayback) { cl.fixangle[0] = true; VectorCopy(cl.viewangles, cl.mviewangles[0]); // disable interpolation if this is new if (!cl.fixangle[1]) VectorCopy(cl.viewangles, cl.mviewangles[1]); } break; case qw_svc_lightstyle: i = MSG_ReadByte(&cl_message); if (i >= cl.max_lightstyle) { Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); break; } strlcpy (cl.lightstyle[i].map, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.lightstyle[i].map)); cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); break; case qw_svc_sound: CL_ParseStartSoundPacket(false); break; case qw_svc_stopsound: i = (unsigned short) MSG_ReadShort(&cl_message); S_StopSound(i>>3, i&7); break; case qw_svc_updatefrags: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); cl.scores[i].frags = (signed short) MSG_ReadShort(&cl_message); break; case qw_svc_updateping: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error("CL_ParseServerMessage: svc_updateping >= cl.maxclients"); cl.scores[i].qw_ping = MSG_ReadShort(&cl_message); break; case qw_svc_updatepl: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error("CL_ParseServerMessage: svc_updatepl >= cl.maxclients"); cl.scores[i].qw_packetloss = MSG_ReadByte(&cl_message); break; case qw_svc_updateentertime: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error("CL_ParseServerMessage: svc_updateentertime >= cl.maxclients"); // seconds ago cl.scores[i].qw_entertime = cl.time - MSG_ReadFloat(&cl_message); break; case qw_svc_spawnbaseline: i = (unsigned short) MSG_ReadShort(&cl_message); if (i < 0 || i >= MAX_EDICTS) Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); if (i >= cl.max_entities) CL_ExpandEntities(i); CL_ParseBaseline(cl.entities + i, false); break; case qw_svc_spawnstatic: CL_ParseStatic(false); break; case qw_svc_temp_entity: if(!CL_VM_Parse_TempEntity()) CL_ParseTempEntity (); break; case qw_svc_killedmonster: cl.stats[STAT_MONSTERS]++; break; case qw_svc_foundsecret: cl.stats[STAT_SECRETS]++; break; case qw_svc_updatestat: i = MSG_ReadByte(&cl_message); if (i < 0 || i >= MAX_CL_STATS) Host_Error ("svc_updatestat: %i is invalid", i); cl.stats[i] = MSG_ReadByte(&cl_message); break; case qw_svc_updatestatlong: i = MSG_ReadByte(&cl_message); if (i < 0 || i >= MAX_CL_STATS) Host_Error ("svc_updatestatlong: %i is invalid", i); cl.stats[i] = MSG_ReadLong(&cl_message); break; case qw_svc_spawnstaticsound: CL_ParseStaticSound (false); break; case qw_svc_cdtrack: cl.cdtrack = cl.looptrack = MSG_ReadByte(&cl_message); #ifdef CONFIG_CD if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) CDAudio_Play ((unsigned char)cls.forcetrack, true); else CDAudio_Play ((unsigned char)cl.cdtrack, true); #endif break; case qw_svc_intermission: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 1; MSG_ReadVector(&cl_message, cl.qw_intermission_origin, cls.protocol); for (i = 0;i < 3;i++) cl.qw_intermission_angles[i] = MSG_ReadAngle(&cl_message, cls.protocol); break; case qw_svc_finale: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 2; SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); break; case qw_svc_sellscreen: Cmd_ExecuteString ("help", src_command, true); break; case qw_svc_smallkick: cl.qw_weaponkick = -2; break; case qw_svc_bigkick: cl.qw_weaponkick = -4; break; case qw_svc_muzzleflash: i = (unsigned short) MSG_ReadShort(&cl_message); // NOTE: in QW this only worked on clients if (i < 0 || i >= MAX_EDICTS) Host_Error("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); if (i >= cl.max_entities) CL_ExpandEntities(i); cl.entities[i].persistent.muzzleflash = 1.0f; break; case qw_svc_updateuserinfo: QW_CL_UpdateUserInfo(); break; case qw_svc_setinfo: QW_CL_SetInfo(); break; case qw_svc_serverinfo: QW_CL_ServerInfo(); break; case qw_svc_download: QW_CL_ParseDownload(); break; case qw_svc_playerinfo: // slightly kill qw player entities now that we know there is // an update of player entities this frame... if (!qwplayerupdatereceived) { qwplayerupdatereceived = true; for (i = 1;i < cl.maxclients;i++) cl.entities_active[i] = false; } EntityStateQW_ReadPlayerUpdate(); break; case qw_svc_nails: QW_CL_ParseNails(); break; case qw_svc_chokecount: (void) MSG_ReadByte(&cl_message); // FIXME: apply to netgraph //for (j = 0;j < i;j++) // cl.frames[(cls.netcon->qw.incoming_acknowledged-1-j)&QW_UPDATE_MASK].receivedtime = -2; break; case qw_svc_modellist: QW_CL_ParseModelList(); break; case qw_svc_soundlist: QW_CL_ParseSoundList(); break; case qw_svc_packetentities: EntityFrameQW_CL_ReadFrame(false); // first update is the final signon stage if (cls.signon == SIGNONS - 1) { cls.signon = SIGNONS; CL_SignonReply (); } break; case qw_svc_deltapacketentities: EntityFrameQW_CL_ReadFrame(true); // first update is the final signon stage if (cls.signon == SIGNONS - 1) { cls.signon = SIGNONS; CL_SignonReply (); } break; case qw_svc_maxspeed: cl.movevars_maxspeed = MSG_ReadFloat(&cl_message); break; case qw_svc_entgravity: cl.movevars_entgravity = MSG_ReadFloat(&cl_message); if (!cl.movevars_entgravity) cl.movevars_entgravity = 1.0f; break; case qw_svc_setpause: cl.paused = MSG_ReadByte(&cl_message) != 0; #ifdef CONFIG_CD if (cl.paused) CDAudio_Pause (); else CDAudio_Resume (); #endif S_PauseGameSounds (cl.paused); break; } } if (qwplayerupdatereceived) { // fully kill any player entities that were not updated this frame for (i = 1;i <= cl.maxclients;i++) if (!cl.entities_active[i]) cl.entities[i].state_current.active = false; } } else { while (1) { if (cl_message.badread) Host_Error ("CL_ParseServerMessage: Bad server message"); cmd = MSG_ReadByte(&cl_message); if (cmd == -1) { // R_TimeReport("END OF MESSAGE"); SHOWNET("END OF MESSAGE"); break; // end of message } cmdindex = cmdcount & 31; cmdcount++; cmdlog[cmdindex] = cmd; // if the high bit of the command byte is set, it is a fast update if (cmd & 128) { // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) temp = "entity"; cmdlogname[cmdindex] = temp; SHOWNET("fast update"); if (cls.signon == SIGNONS - 1) { // first update is the final signon stage cls.signon = SIGNONS; CL_SignonReply (); } EntityFrameQuake_ReadEntity (cmd&127); continue; } SHOWNET(svc_strings[cmd]); cmdlogname[cmdindex] = svc_strings[cmd]; if (!cmdlogname[cmdindex]) { // LordHavoc: fix for bizarre problem in MSVC that I do not understand (if I assign the string pointer directly it ends up storing a NULL pointer) const char *d = ""; cmdlogname[cmdindex] = d; } // other commands switch (cmd) { default: { char description[32*64], tempdesc[64]; int count; strlcpy (description, "packet dump: ", sizeof(description)); i = cmdcount - 32; if (i < 0) i = 0; count = cmdcount - i; i &= 31; while(count > 0) { dpsnprintf (tempdesc, sizeof (tempdesc), "%3i:%s ", cmdlog[i], cmdlogname[i]); strlcat (description, tempdesc, sizeof (description)); count--; i++; i &= 31; } description[strlen(description)-1] = '\n'; // replace the last space with a newline Con_Print(description); Host_Error ("CL_ParseServerMessage: Illegible server message"); } break; case svc_nop: if (cls.signon < SIGNONS) Con_Print("<-- server to client keepalive\n"); break; case svc_time: CL_NetworkTimeReceived(MSG_ReadFloat(&cl_message)); break; case svc_clientdata: CL_ParseClientdata(); break; case svc_version: i = MSG_ReadLong(&cl_message); protocol = Protocol_EnumForNumber(i); if (protocol == PROTOCOL_UNKNOWN) Host_Error("CL_ParseServerMessage: Server is unrecognized protocol number (%i)", i); // hack for unmarked Nehahra movie demos which had a custom protocol if (protocol == PROTOCOL_QUAKEDP && cls.demoplayback && gamemode == GAME_NEHAHRA) protocol = PROTOCOL_NEHAHRAMOVIE; cls.protocol = protocol; break; case svc_disconnect: Con_Printf ("Server disconnected\n"); if (cls.demonum != -1) CL_NextDemo (); else CL_Disconnect (); break; case svc_print: temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (CL_ExaminePrintString(temp)) // look for anything interesting like player IP addresses or ping reports CSQC_AddPrintText(temp); //[515]: csqc break; case svc_centerprint: CL_VM_Parse_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); //[515]: csqc break; case svc_stufftext: temp = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); /* if(utf8_enable.integer) { strip_pqc = true; // we can safely strip and even // interpret these in utf8 mode } else */ switch(cls.protocol) { case PROTOCOL_QUAKE: case PROTOCOL_QUAKEDP: // maybe add other protocols if // so desired, but not DP7 strip_pqc = true; break; case PROTOCOL_DARKPLACES7: default: // ProQuake does not support // these protocols strip_pqc = false; break; } if(strip_pqc) { // skip over ProQuake messages, // TODO actually interpret them // (they are sbar team score // updates), see proquake cl_parse.c if(*temp == 0x01) { ++temp; while(*temp >= 0x01 && *temp <= 0x1F) ++temp; } } CL_VM_Parse_StuffCmd(temp); //[515]: csqc break; case svc_damage: V_ParseDamage (); break; case svc_serverinfo: CL_ParseServerInfo (); break; case svc_setangle: for (i=0 ; i<3 ; i++) cl.viewangles[i] = MSG_ReadAngle(&cl_message, cls.protocol); if (!cls.demoplayback) { cl.fixangle[0] = true; VectorCopy(cl.viewangles, cl.mviewangles[0]); // disable interpolation if this is new if (!cl.fixangle[1]) VectorCopy(cl.viewangles, cl.mviewangles[1]); } break; case svc_setview: cl.viewentity = (unsigned short)MSG_ReadShort(&cl_message); if (cl.viewentity >= MAX_EDICTS) Host_Error("svc_setview >= MAX_EDICTS"); if (cl.viewentity >= cl.max_entities) CL_ExpandEntities(cl.viewentity); // LordHavoc: assume first setview recieved is the real player entity if (!cl.realplayerentity) cl.realplayerentity = cl.viewentity; // update cl.playerentity to this one if it is a valid player if (cl.viewentity >= 1 && cl.viewentity <= cl.maxclients) cl.playerentity = cl.viewentity; break; case svc_lightstyle: i = MSG_ReadByte(&cl_message); if (i >= cl.max_lightstyle) { Con_Printf ("svc_lightstyle >= MAX_LIGHTSTYLES"); break; } strlcpy (cl.lightstyle[i].map, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.lightstyle[i].map)); cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0; cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map); break; case svc_sound: CL_ParseStartSoundPacket(false); break; case svc_precache: if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) { // was svc_sound2 in protocols 1, 2, 3, removed in 4, 5, changed to svc_precache in 6 CL_ParseStartSoundPacket(true); } else { char *s; i = (unsigned short)MSG_ReadShort(&cl_message); s = MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); if (i < 32768) { if (i >= 1 && i < MAX_MODELS) { dp_model_t *model = Mod_ForName(s, false, false, s[0] == '*' ? cl.model_name[1] : NULL); if (!model) Con_DPrintf("svc_precache: Mod_ForName(\"%s\") failed\n", s); cl.model_precache[i] = model; } else Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_MODELS); } else { i -= 32768; if (i >= 1 && i < MAX_SOUNDS) { sfx_t *sfx = S_PrecacheSound (s, true, true); if (!sfx && snd_initialized.integer) Con_DPrintf("svc_precache: S_PrecacheSound(\"%s\") failed\n", s); cl.sound_precache[i] = sfx; } else Con_Printf("svc_precache: index %i outside range %i...%i\n", i, 1, MAX_SOUNDS); } } break; case svc_stopsound: i = (unsigned short) MSG_ReadShort(&cl_message); S_StopSound(i>>3, i&7); break; case svc_updatename: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatename >= cl.maxclients"); strlcpy (cl.scores[i].name, MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)), sizeof (cl.scores[i].name)); break; case svc_updatefrags: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatefrags >= cl.maxclients"); cl.scores[i].frags = (signed short) MSG_ReadShort(&cl_message); break; case svc_updatecolors: i = MSG_ReadByte(&cl_message); if (i >= cl.maxclients) Host_Error ("CL_ParseServerMessage: svc_updatecolors >= cl.maxclients"); cl.scores[i].colors = MSG_ReadByte(&cl_message); break; case svc_particle: CL_ParseParticleEffect (); break; case svc_effect: CL_ParseEffect (); break; case svc_effect2: CL_ParseEffect2 (); break; case svc_spawnbaseline: i = (unsigned short) MSG_ReadShort(&cl_message); if (i < 0 || i >= MAX_EDICTS) Host_Error ("CL_ParseServerMessage: svc_spawnbaseline: invalid entity number %i", i); if (i >= cl.max_entities) CL_ExpandEntities(i); CL_ParseBaseline (cl.entities + i, false); break; case svc_spawnbaseline2: i = (unsigned short) MSG_ReadShort(&cl_message); if (i < 0 || i >= MAX_EDICTS) Host_Error ("CL_ParseServerMessage: svc_spawnbaseline2: invalid entity number %i", i); if (i >= cl.max_entities) CL_ExpandEntities(i); CL_ParseBaseline (cl.entities + i, true); break; case svc_spawnstatic: CL_ParseStatic (false); break; case svc_spawnstatic2: CL_ParseStatic (true); break; case svc_temp_entity: if(!CL_VM_Parse_TempEntity()) CL_ParseTempEntity (); break; case svc_setpause: cl.paused = MSG_ReadByte(&cl_message) != 0; #ifdef CONFIG_CD if (cl.paused) CDAudio_Pause (); else CDAudio_Resume (); #endif S_PauseGameSounds (cl.paused); break; case svc_signonnum: i = MSG_ReadByte(&cl_message); // LordHavoc: it's rude to kick off the client if they missed the // reconnect somehow, so allow signon 1 even if at signon 1 if (i <= cls.signon && i != 1) Host_Error ("Received signon %i when at %i", i, cls.signon); cls.signon = i; CL_SignonReply (); break; case svc_killedmonster: cl.stats[STAT_MONSTERS]++; break; case svc_foundsecret: cl.stats[STAT_SECRETS]++; break; case svc_updatestat: i = MSG_ReadByte(&cl_message); if (i < 0 || i >= MAX_CL_STATS) Host_Error ("svc_updatestat: %i is invalid", i); cl.stats[i] = MSG_ReadLong(&cl_message); break; case svc_updatestatubyte: i = MSG_ReadByte(&cl_message); if (i < 0 || i >= MAX_CL_STATS) Host_Error ("svc_updatestat: %i is invalid", i); cl.stats[i] = MSG_ReadByte(&cl_message); break; case svc_spawnstaticsound: CL_ParseStaticSound (false); break; case svc_spawnstaticsound2: CL_ParseStaticSound (true); break; case svc_cdtrack: cl.cdtrack = MSG_ReadByte(&cl_message); cl.looptrack = MSG_ReadByte(&cl_message); #ifdef CONFIG_CD if ( (cls.demoplayback || cls.demorecording) && (cls.forcetrack != -1) ) CDAudio_Play ((unsigned char)cls.forcetrack, true); else CDAudio_Play ((unsigned char)cl.cdtrack, true); #endif break; case svc_intermission: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 1; CL_VM_UpdateIntermissionState(cl.intermission); break; case svc_finale: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 2; CL_VM_UpdateIntermissionState(cl.intermission); SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); break; case svc_cutscene: if(!cl.intermission) cl.completed_time = cl.time; cl.intermission = 3; CL_VM_UpdateIntermissionState(cl.intermission); SCR_CenterPrint(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); break; case svc_sellscreen: Cmd_ExecuteString ("help", src_command, true); break; case svc_hidelmp: if (gamemode == GAME_TENEBRAE) { // repeating particle effect MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); (void) MSG_ReadByte(&cl_message); MSG_ReadLong(&cl_message); MSG_ReadLong(&cl_message); MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); } else SHOWLMP_decodehide(); break; case svc_showlmp: if (gamemode == GAME_TENEBRAE) { // particle effect MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); MSG_ReadCoord(&cl_message, cls.protocol); (void) MSG_ReadByte(&cl_message); MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); } else SHOWLMP_decodeshow(); break; case svc_skybox: R_SetSkyBox(MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring))); break; case svc_entities: if (cls.signon == SIGNONS - 1) { // first update is the final signon stage cls.signon = SIGNONS; CL_SignonReply (); } if (cls.protocol == PROTOCOL_DARKPLACES1 || cls.protocol == PROTOCOL_DARKPLACES2 || cls.protocol == PROTOCOL_DARKPLACES3) EntityFrame_CL_ReadFrame(); else if (cls.protocol == PROTOCOL_DARKPLACES4) EntityFrame4_CL_ReadFrame(); else EntityFrame5_CL_ReadFrame(); break; case svc_csqcentities: CSQC_ReadEntities(); break; case svc_downloaddata: CL_ParseDownload(); break; case svc_trailparticles: CL_ParseTrailParticles(); break; case svc_pointparticles: CL_ParsePointParticles(); break; case svc_pointparticles1: CL_ParsePointParticles1(); break; } // R_TimeReport(svc_strings[cmd]); } } if (cls.signon == SIGNONS) CL_UpdateItemsAndWeapon(); // R_TimeReport("UpdateItems"); EntityFrameQuake_ISeeDeadEntities(); // R_TimeReport("ISeeDeadEntities"); CL_UpdateMoveVars(); // R_TimeReport("UpdateMoveVars"); parsingerror = false; // LordHavoc: this was at the start of the function before cl_autodemo was // implemented if (cls.demorecording) { CL_WriteDemoMessage (&cl_message); // R_TimeReport("WriteDemo"); } } void CL_Parse_DumpPacket(void) { if (!parsingerror) return; Con_Print("Packet dump:\n"); SZ_HexDumpToConsole(&cl_message); parsingerror = false; } void CL_Parse_ErrorCleanUp(void) { CL_StopDownload(0, 0); QW_CL_StopUpload(); } void CL_Parse_Init(void) { Cvar_RegisterVariable(&cl_worldmessage); Cvar_RegisterVariable(&cl_worldname); Cvar_RegisterVariable(&cl_worldnamenoextension); Cvar_RegisterVariable(&cl_worldbasename); Cvar_RegisterVariable(&developer_networkentities); Cvar_RegisterVariable(&cl_gameplayfix_soundsmovewithentities); Cvar_RegisterVariable(&cl_sound_wizardhit); Cvar_RegisterVariable(&cl_sound_hknighthit); Cvar_RegisterVariable(&cl_sound_tink1); Cvar_RegisterVariable(&cl_sound_ric1); Cvar_RegisterVariable(&cl_sound_ric2); Cvar_RegisterVariable(&cl_sound_ric3); Cvar_RegisterVariable(&cl_sound_ric_gunshot); Cvar_RegisterVariable(&cl_sound_r_exp3); Cvar_RegisterVariable(&cl_joinbeforedownloadsfinish); // server extension cvars set by commands issued from the server during connect Cvar_RegisterVariable(&cl_serverextension_download); Cvar_RegisterVariable(&cl_nettimesyncfactor); Cvar_RegisterVariable(&cl_nettimesyncboundmode); Cvar_RegisterVariable(&cl_nettimesyncboundtolerance); Cvar_RegisterVariable(&cl_iplog_name); Cvar_RegisterVariable(&cl_readpicture_force); Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)"); Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)"); Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server"); Cmd_AddCommand("changing", QW_CL_Changing_f, "sent by qw servers to tell client to wait for level change"); Cmd_AddCommand("cl_begindownloads", CL_BeginDownloads_f, "used internally by darkplaces client while connecting (causes loading of models and sounds or triggers downloads for missing ones)"); Cmd_AddCommand("cl_downloadbegin", CL_DownloadBegin_f, "(networking) informs client of download file information, client replies with sv_startsoundload to begin the transfer"); Cmd_AddCommand("stopdownload", CL_StopDownload_f, "terminates a download"); Cmd_AddCommand("cl_downloadfinished", CL_DownloadFinished_f, "signals that a download has finished and provides the client with file size and crc to check its integrity"); Cmd_AddCommand("iplog_list", CL_IPLog_List_f, "lists names of players whose IP address begins with the supplied text (example: iplog_list 123.456.789)"); } void CL_Parse_Shutdown(void) { } darkplaces/nexuiz.rc0000664000175000017500000000123013067716222014031 0ustar kalevkalev#include // include for version info constants A ICON MOVEABLE PURE LOADONCALL DISCARDABLE "nexuiz.ico" 1 VERSIONINFO FILEVERSION 1,0,0,0 PRODUCTVERSION 1,0,0,0 FILETYPE VFT_APP { BLOCK "StringFileInfo" { BLOCK "040904E4" { VALUE "CompanyName", "Forest Hale Digital Services" VALUE "FileVersion", "1.0" VALUE "FileDescription", "Nexuiz" VALUE "InternalName", "nexuiz.exe" VALUE "LegalCopyright", "id Software, Forest Hale, and contributors" VALUE "LegalTrademarks", "" VALUE "OriginalFilename", "nexuiz.exe" VALUE "ProductName", "Nexuiz" VALUE "ProductVersion", "1.0" } } } darkplaces/sv_demo.c0000664000175000017500000000533013067716222013766 0ustar kalevkalev#include "quakedef.h" #include "sv_demo.h" extern cvar_t sv_autodemo_perclient_discardable; void SV_StartDemoRecording(client_t *client, const char *filename, int forcetrack) { prvm_prog_t *prog = SVVM_prog; char name[MAX_QPATH]; if(client->sv_demo_file != NULL) return; // we already have a demo strlcpy(name, filename, sizeof(name)); FS_DefaultExtension(name, ".dem", sizeof(name)); Con_Printf("Recording demo for # %d (%s) to %s\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress, name); // Reset discardable flag for every new demo. PRVM_serveredictfloat(client->edict, discardabledemo) = 0; client->sv_demo_file = FS_OpenRealFile(name, "wb", false); if(!client->sv_demo_file) { Con_Print("ERROR: couldn't open.\n"); return; } FS_Printf(client->sv_demo_file, "%i\n", forcetrack); } void SV_WriteDemoMessage(client_t *client, sizebuf_t *sendbuffer, qboolean clienttoserver) { prvm_prog_t *prog = SVVM_prog; int len, i; float f; int temp; if(client->sv_demo_file == NULL) return; if(sendbuffer->cursize == 0) return; temp = sendbuffer->cursize | (clienttoserver ? DEMOMSG_CLIENT_TO_SERVER : 0); len = LittleLong(temp); FS_Write(client->sv_demo_file, &len, 4); for(i = 0; i < 3; ++i) { f = LittleFloat(PRVM_serveredictvector(client->edict, v_angle)[i]); FS_Write(client->sv_demo_file, &f, 4); } FS_Write(client->sv_demo_file, sendbuffer->data, sendbuffer->cursize); } void SV_StopDemoRecording(client_t *client) { prvm_prog_t *prog = SVVM_prog; sizebuf_t buf; unsigned char bufdata[64]; if(client->sv_demo_file == NULL) return; buf.data = bufdata; buf.maxsize = sizeof(bufdata); SZ_Clear(&buf); MSG_WriteByte(&buf, svc_disconnect); SV_WriteDemoMessage(client, &buf, false); if (sv_autodemo_perclient_discardable.integer && PRVM_serveredictfloat(client->edict, discardabledemo)) { FS_RemoveOnClose(client->sv_demo_file); Con_Printf("Stopped recording discardable demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); } else Con_Printf("Stopped recording demo for # %d (%s)\n", PRVM_NUM_FOR_EDICT(client->edict), client->netaddress); FS_Close(client->sv_demo_file); client->sv_demo_file = NULL; } void SV_WriteNetnameIntoDemo(client_t *client) { // This "pseudo packet" is written so a program can easily find out whose demo this is sizebuf_t buf; unsigned char bufdata[MAX_SCOREBOARDNAME + 64]; if(client->sv_demo_file == NULL) return; buf.data = bufdata; buf.maxsize = sizeof(bufdata); SZ_Clear(&buf); MSG_WriteByte(&buf, svc_stufftext); MSG_WriteUnterminatedString(&buf, "\n// this demo contains the point of view of: "); MSG_WriteUnterminatedString(&buf, client->name); MSG_WriteString(&buf, "\n"); SV_WriteDemoMessage(client, &buf, false); } darkplaces/vid_wgl.c0000664000175000017500000020012213067716222013761 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // vid_wgl.c -- NT GL vid component #ifdef _MSC_VER #pragma comment(lib, "comctl32.lib") #endif #ifdef SUPPORTDIRECTX // Include DX libs #ifdef _MSC_VER #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") #endif #ifndef DIRECTINPUT_VERSION # define DIRECTINPUT_VERSION 0x0500 /* Version 5.0 */ #endif #endif #include "quakedef.h" #include #include #ifdef SUPPORTDIRECTX #include #endif #include "resource.h" #include #ifdef SUPPORTDIRECTX #include #endif #include "dpsoftrast.h" #ifdef SUPPORTD3D #include cvar_t vid_dx9 = {CVAR_SAVE, "vid_dx9", "0", "use Microsoft Direct3D9(r) for rendering"}; cvar_t vid_dx9_hal = {CVAR_SAVE, "vid_dx9_hal", "1", "enables hardware rendering (1), otherwise software reference rasterizer (0 - very slow), note that 0 is necessary when using NVPerfHUD (which renders in hardware but requires this option to enable it)"}; cvar_t vid_dx9_softvertex = {CVAR_SAVE, "vid_dx9_softvertex", "0", "enables software vertex processing (for compatibility testing? or if you have a very fast CPU), usually you want this off"}; cvar_t vid_dx9_triplebuffer = {CVAR_SAVE, "vid_dx9_triplebuffer", "0", "enables triple buffering when using vid_vsync in fullscreen, this options adds some latency and only helps when framerate is below 60 so you usually don't want it"}; //cvar_t vid_dx10 = {CVAR_SAVE, "vid_dx10", "1", "use Microsoft Direct3D10(r) for rendering"}; //cvar_t vid_dx11 = {CVAR_SAVE, "vid_dx11", "1", "use Microsoft Direct3D11(r) for rendering"}; D3DPRESENT_PARAMETERS vid_d3dpresentparameters; // we declare this in vid_shared.c because it is required by dedicated server and all clients when SUPPORTD3D is defined extern LPDIRECT3DDEVICE9 vid_d3d9dev; LPDIRECT3D9 vid_d3d9; D3DCAPS9 vid_d3d9caps; qboolean vid_d3ddevicelost; #endif extern HINSTANCE global_hInstance; static HINSTANCE gldll; #ifndef WM_MOUSEWHEEL #define WM_MOUSEWHEEL 0x020A #endif // Tell startup code that we have a client int cl_available = true; qboolean vid_supportrefreshrate = true; static int (WINAPI *qwglChoosePixelFormat)(HDC, CONST PIXELFORMATDESCRIPTOR *); static int (WINAPI *qwglDescribePixelFormat)(HDC, int, UINT, LPPIXELFORMATDESCRIPTOR); //static int (WINAPI *qwglGetPixelFormat)(HDC); static BOOL (WINAPI *qwglSetPixelFormat)(HDC, int, CONST PIXELFORMATDESCRIPTOR *); static BOOL (WINAPI *qwglSwapBuffers)(HDC); static HGLRC (WINAPI *qwglCreateContext)(HDC); static BOOL (WINAPI *qwglDeleteContext)(HGLRC); static HGLRC (WINAPI *qwglGetCurrentContext)(VOID); static HDC (WINAPI *qwglGetCurrentDC)(VOID); static PROC (WINAPI *qwglGetProcAddress)(LPCSTR); static BOOL (WINAPI *qwglMakeCurrent)(HDC, HGLRC); static BOOL (WINAPI *qwglSwapIntervalEXT)(int interval); static const char *(WINAPI *qwglGetExtensionsStringARB)(HDC hdc); static BOOL (WINAPI *qwglChoosePixelFormatARB)(HDC hdc, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFormats, int *piFormats, UINT *nNumFormats); static BOOL (WINAPI *qwglGetPixelFormatAttribivARB)(HDC hdc, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttributes, int *piValues); static dllfunction_t wglfuncs[] = { {"wglChoosePixelFormat", (void **) &qwglChoosePixelFormat}, {"wglDescribePixelFormat", (void **) &qwglDescribePixelFormat}, // {"wglGetPixelFormat", (void **) &qwglGetPixelFormat}, {"wglSetPixelFormat", (void **) &qwglSetPixelFormat}, {"wglSwapBuffers", (void **) &qwglSwapBuffers}, {"wglCreateContext", (void **) &qwglCreateContext}, {"wglDeleteContext", (void **) &qwglDeleteContext}, {"wglGetProcAddress", (void **) &qwglGetProcAddress}, {"wglMakeCurrent", (void **) &qwglMakeCurrent}, {"wglGetCurrentContext", (void **) &qwglGetCurrentContext}, {"wglGetCurrentDC", (void **) &qwglGetCurrentDC}, {NULL, NULL} }; static dllfunction_t wglswapintervalfuncs[] = { {"wglSwapIntervalEXT", (void **) &qwglSwapIntervalEXT}, {NULL, NULL} }; static dllfunction_t wglpixelformatfuncs[] = { {"wglChoosePixelFormatARB", (void **) &qwglChoosePixelFormatARB}, {"wglGetPixelFormatAttribivARB", (void **) &qwglGetPixelFormatAttribivARB}, {NULL, NULL} }; static DEVMODE gdevmode, initialdevmode; static vid_mode_t desktop_mode; static qboolean vid_initialized = false; static qboolean vid_wassuspended = false; static qboolean vid_usingmouse = false; static qboolean vid_usinghidecursor = false; static qboolean vid_usingvsync = false; static qboolean vid_usevsync = false; static HICON hIcon; // used by cd_win.c and snd_win.c HWND mainwindow; static HDC baseDC; static HGLRC baseRC; static HDC vid_softhdc; static HGDIOBJ vid_softhdc_backup; static BITMAPINFO vid_softbmi; static HBITMAP vid_softdibhandle; //HWND WINAPI InitializeWindow (HINSTANCE hInstance, int nCmdShow); static qboolean vid_isfullscreen; //void VID_MenuDraw (void); //void VID_MenuKey (int key); LONG WINAPI MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void AppActivate(BOOL fActive, BOOL minimize); static void ClearAllStates(void); qboolean VID_InitModeGL(viddef_mode_t *mode); qboolean VID_InitModeSOFT(viddef_mode_t *mode); //==================================== static int window_x, window_y; static qboolean mouseinitialized; #ifdef SUPPORTDIRECTX static qboolean dinput; #define DINPUT_BUFFERSIZE 16 #define iDirectInputCreate(a,b,c,d) pDirectInputCreate(a,b,c,d) static HRESULT (WINAPI *pDirectInputCreate)(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUT * lplpDirectInput, LPUNKNOWN punkOuter); #endif // LordHavoc: thanks to backslash for this support for mouse buttons 4 and 5 /* backslash :: imouse explorer buttons */ /* These are #ifdefed out for non-Win2K in the February 2001 version of MS's platform SDK, but we need them for compilation. . . */ #ifndef WM_XBUTTONDOWN #define WM_XBUTTONDOWN 0x020B #define WM_XBUTTONUP 0x020C #endif #ifndef MK_XBUTTON1 #define MK_XBUTTON1 0x0020 #define MK_XBUTTON2 0x0040 #endif #ifndef MK_XBUTTON3 // LordHavoc: lets hope this allows more buttons in the future... #define MK_XBUTTON3 0x0080 #define MK_XBUTTON4 0x0100 #define MK_XBUTTON5 0x0200 #define MK_XBUTTON6 0x0400 #define MK_XBUTTON7 0x0800 #endif /* :: backslash */ // mouse variables static int mouse_buttons; static int mouse_oldbuttonstate; static unsigned int uiWheelMessage; #ifdef SUPPORTDIRECTX static qboolean dinput_acquired; static unsigned int mstate_di; #endif static cvar_t vid_forcerefreshrate = {0, "vid_forcerefreshrate", "0", "try to set the given vid_refreshrate even if Windows doesn't list it as valid video mode"}; #ifdef SUPPORTDIRECTX static LPDIRECTINPUT g_pdi; static LPDIRECTINPUTDEVICE g_pMouse; static HINSTANCE hInstDI; #endif // forward-referenced functions static void IN_StartupMouse (void); static void AdjustWindowBounds(int fullscreen, int *width, int *height, viddef_mode_t *mode, DWORD WindowStyle, RECT *rect); //==================================== qboolean vid_reallyhidden = true; #ifdef SUPPORTD3D qboolean vid_begunscene = false; #endif void VID_Finish (void) { #ifdef SUPPORTD3D HRESULT hr; #endif vid_hidden = vid_reallyhidden; vid_usevsync = vid_vsync.integer && !cls.timedemo && qwglSwapIntervalEXT; if (!vid_hidden) { switch(vid.renderpath) { case RENDERPATH_GL11: case RENDERPATH_GL13: case RENDERPATH_GL20: case RENDERPATH_GLES1: case RENDERPATH_GLES2: if (vid_usingvsync != vid_usevsync) { vid_usingvsync = vid_usevsync; qwglSwapIntervalEXT (vid_usevsync); } if (r_speeds.integer == 2 || gl_finish.integer) GL_Finish(); SwapBuffers(baseDC); break; case RENDERPATH_D3D9: #ifdef SUPPORTD3D if (vid_begunscene) { IDirect3DDevice9_EndScene(vid_d3d9dev); vid_begunscene = false; } if (!vid_reallyhidden) { if (!vid_d3ddevicelost) { vid_hidden = vid_reallyhidden; hr = IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); if (hr == D3DERR_DEVICELOST) { vid_d3ddevicelost = true; vid_hidden = true; Sleep(100); } } else { hr = IDirect3DDevice9_TestCooperativeLevel(vid_d3d9dev); switch(hr) { case D3DERR_DEVICELOST: vid_d3ddevicelost = true; vid_hidden = true; Sleep(100); break; case D3DERR_DEVICENOTRESET: vid_d3ddevicelost = false; vid_hidden = vid_reallyhidden; R_Modules_DeviceLost(); IDirect3DDevice9_Reset(vid_d3d9dev, &vid_d3dpresentparameters); R_Modules_DeviceRestored(); break; case D3D_OK: vid_hidden = vid_reallyhidden; IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); break; } } if (!vid_begunscene && !vid_hidden) { IDirect3DDevice9_BeginScene(vid_d3d9dev); vid_begunscene = true; } } #endif break; case RENDERPATH_D3D10: break; case RENDERPATH_D3D11: break; case RENDERPATH_SOFT: DPSOFTRAST_Finish(); // baseDC = GetDC(mainwindow); BitBlt(baseDC, 0, 0, vid.width, vid.height, vid_softhdc, 0, 0, SRCCOPY); // ReleaseDC(mainwindow, baseDC); // baseDC = NULL; break; } } // make sure a context switch can happen every frame - Logitech drivers // input drivers sometimes eat cpu time every 3 seconds or lag badly // without this help Sleep(0); VID_UpdateGamma(false, 256); } //========================================================================== static unsigned char scantokey[128] = { // 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 ,K_ESCAPE,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'0' ,'-' ,'=' ,K_BACKSPACE,K_TAB,//0 'q' ,'w' ,'e' ,'r' ,'t' ,'y' ,'u' ,'i' ,'o' ,'p' ,'[' ,']' ,K_ENTER,K_CTRL ,'a' ,'s' ,//1 'd' ,'f' ,'g' ,'h' ,'j' ,'k' ,'l' ,';' ,'\'' ,'`' ,K_SHIFT ,'\\' ,'z' ,'x' ,'c' ,'v' ,//2 'b' ,'n' ,'m' ,',' ,'.' ,'/' ,K_SHIFT ,'*' ,K_ALT ,' ' ,K_CAPSLOCK,K_F1 ,K_F2 ,K_F3 ,K_F4 ,K_F5 ,//3 K_F6 ,K_F7 ,K_F8 ,K_F9 ,K_F10,K_PAUSE,K_SCROLLOCK,K_HOME,K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5 ,K_RIGHTARROW,K_KP_PLUS ,K_END,//4 K_DOWNARROW,K_PGDN ,K_INS,K_DEL,0 ,0 ,0 ,K_F11 ,K_F12 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,//5 0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,//6 0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 //7 }; /* ======= MapKey Map from windows to quake keynums ======= */ static int MapKey (int key, int virtualkey) { int result; int modified = (key >> 16) & 255; qboolean is_extended = false; if (modified < 128 && scantokey[modified]) result = scantokey[modified]; else { result = 0; Con_DPrintf("key 0x%02x (0x%8x, 0x%8x) has no translation\n", modified, key, virtualkey); } if (key & (1 << 24)) is_extended = true; if ( !is_extended ) { if(((GetKeyState(VK_NUMLOCK)) & 0xffff) == 0) return result; switch ( result ) { case K_HOME: return K_KP_HOME; case K_UPARROW: return K_KP_UPARROW; case K_PGUP: return K_KP_PGUP; case K_LEFTARROW: return K_KP_LEFTARROW; case K_RIGHTARROW: return K_KP_RIGHTARROW; case K_END: return K_KP_END; case K_DOWNARROW: return K_KP_DOWNARROW; case K_PGDN: return K_KP_PGDN; case K_INS: return K_KP_INS; case K_DEL: return K_KP_DEL; default: return result; } } else { if(virtualkey == VK_NUMLOCK) return K_NUMLOCK; switch ( result ) { case 0x0D: return K_KP_ENTER; case 0x2F: return K_KP_SLASH; case 0xAF: return K_KP_PLUS; } return result; } } /* =================================================================== MAIN WINDOW =================================================================== */ /* ================ ClearAllStates ================ */ static void ClearAllStates (void) { Key_ClearStates (); if (vid_usingmouse) mouse_oldbuttonstate = 0; } void AppActivate(BOOL fActive, BOOL minimize) /**************************************************************************** * * Function: AppActivate * Parameters: fActive - True if app is activating * * Description: If the application is activating, then swap the system * into SYSPAL_NOSTATIC mode so that our palettes will display * correctly. * ****************************************************************************/ { static qboolean sound_active = false; // initially blocked by Sys_InitConsole() vid_activewindow = fActive != FALSE; vid_reallyhidden = minimize != FALSE; // enable/disable sound on focus gain/loss if ((!vid_reallyhidden && vid_activewindow) || !snd_mutewhenidle.integer) { if (!sound_active) { S_UnblockSound (); sound_active = true; } } else { if (sound_active) { S_BlockSound (); sound_active = false; } } if (fActive) { if (vid_isfullscreen) { if (vid_wassuspended) { vid_wassuspended = false; if (gldll) { ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN); ShowWindow(mainwindow, SW_SHOWNORMAL); } } // LordHavoc: from dabb, fix for alt-tab bug in NVidia drivers if (gldll) MoveWindow(mainwindow,0,0,gdevmode.dmPelsWidth,gdevmode.dmPelsHeight,false); } } if (!fActive) { VID_SetMouse(false, false, false); if (vid_isfullscreen) { if (gldll) ChangeDisplaySettings (NULL, CDS_FULLSCREEN); vid_wassuspended = true; } VID_RestoreSystemGamma(); } } //TODO: move it around in vid_wgl.c since I dont think this is the right position void Sys_SendKeyEvents (void) { MSG msg; while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage (&msg, NULL, 0, 0)) Sys_Quit (1); TranslateMessage (&msg); DispatchMessage (&msg); } } #ifdef CONFIG_CD LONG CDAudio_MessageHandler(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif static keynum_t buttonremap[16] = { K_MOUSE1, K_MOUSE2, K_MOUSE3, K_MOUSE4, K_MOUSE5, K_MOUSE6, K_MOUSE7, K_MOUSE8, K_MOUSE9, K_MOUSE10, K_MOUSE11, K_MOUSE12, K_MOUSE13, K_MOUSE14, K_MOUSE15, K_MOUSE16, }; /* main window procedure */ LONG WINAPI MainWndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { LONG lRet = 1; int fActive, fMinimized, temp; unsigned char state[256]; const unsigned int UNICODE_BUFFER_LENGTH = 4; WCHAR unicode[UNICODE_BUFFER_LENGTH]; int vkey; int charlength; qboolean down = false; if ( uMsg == uiWheelMessage ) uMsg = WM_MOUSEWHEEL; switch (uMsg) { case WM_KILLFOCUS: if (vid_isfullscreen) ShowWindow(mainwindow, SW_SHOWMINNOACTIVE); break; case WM_CREATE: break; case WM_MOVE: window_x = (int) LOWORD(lParam); window_y = (int) HIWORD(lParam); VID_SetMouse(false, false, false); break; case WM_KEYDOWN: case WM_SYSKEYDOWN: down = true; case WM_KEYUP: case WM_SYSKEYUP: vkey = MapKey(lParam, wParam); GetKeyboardState (state); // alt/ctrl/shift tend to produce funky ToAscii values, // and if it's not a single character we don't know care about it charlength = ToUnicode(wParam, lParam >> 16, state, unicode, UNICODE_BUFFER_LENGTH, 0); if(vkey == K_ALT || vkey == K_CTRL || vkey == K_SHIFT || charlength == 0) unicode[0] = 0; else if(charlength == 2) unicode[0] = unicode[1]; if (!VID_JoyBlockEmulatedKeys(vkey)) Key_Event(vkey, unicode[0], down); break; case WM_SYSCHAR: // keep Alt-Space from happening break; case WM_SYSCOMMAND: // prevent screensaver from occuring while the active window // note: password-locked screensavers on Vista still work if (vid_activewindow && ((wParam & 0xFFF0) == SC_SCREENSAVE || (wParam & 0xFFF0) == SC_MONITORPOWER)) lRet = 0; else lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); break; // this is complicated because Win32 seems to pack multiple mouse events into // one update sometimes, so we always check all states and look for events case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_XBUTTONDOWN: // backslash :: imouse explorer buttons case WM_XBUTTONUP: // backslash :: imouse explorer buttons case WM_MOUSEMOVE: temp = 0; if (wParam & MK_LBUTTON) temp |= 1; if (wParam & MK_RBUTTON) temp |= 2; if (wParam & MK_MBUTTON) temp |= 4; /* backslash :: imouse explorer buttons */ if (wParam & MK_XBUTTON1) temp |= 8; if (wParam & MK_XBUTTON2) temp |= 16; /* :: backslash */ // LordHavoc: lets hope this allows more buttons in the future... if (wParam & MK_XBUTTON3) temp |= 32; if (wParam & MK_XBUTTON4) temp |= 64; if (wParam & MK_XBUTTON5) temp |= 128; if (wParam & MK_XBUTTON6) temp |= 256; if (wParam & MK_XBUTTON7) temp |= 512; #ifdef SUPPORTDIRECTX if (!dinput_acquired) #endif { // perform button actions int i; for (i=0 ; i 0) { Key_Event(K_MWHEELUP, 0, true); Key_Event(K_MWHEELUP, 0, false); } else { Key_Event(K_MWHEELDOWN, 0, true); Key_Event(K_MWHEELDOWN, 0, false); } break; case WM_SIZE: break; case WM_CLOSE: if (MessageBox (mainwindow, "Are you sure you want to quit?", "Confirm Exit", MB_YESNO | MB_SETFOREGROUND | MB_ICONQUESTION) == IDYES) Sys_Quit (0); break; case WM_ACTIVATE: fActive = LOWORD(wParam); fMinimized = (BOOL) HIWORD(wParam); AppActivate(!(fActive == WA_INACTIVE), fMinimized); // fix the leftover Alt from any Alt-Tab or the like that switched us away ClearAllStates (); break; //case WM_DESTROY: // PostQuitMessage (0); // break; case MM_MCINOTIFY: #ifdef CONFIG_CD lRet = CDAudio_MessageHandler (hWnd, uMsg, wParam, lParam); #endif break; default: /* pass all unhandled messages to DefWindowProc */ lRet = DefWindowProc (hWnd, uMsg, wParam, lParam); break; } /* return 1 if handled message, 0 if not */ return lRet; } int VID_SetGamma(unsigned short *ramps, int rampsize) { if (qwglMakeCurrent) { HDC hdc = GetDC (NULL); int i = SetDeviceGammaRamp(hdc, ramps); ReleaseDC (NULL, hdc); return i; // return success or failure } else return 0; } int VID_GetGamma(unsigned short *ramps, int rampsize) { if (qwglMakeCurrent) { HDC hdc = GetDC (NULL); int i = GetDeviceGammaRamp(hdc, ramps); ReleaseDC (NULL, hdc); return i; // return success or failure } else return 0; } static void GL_CloseLibrary(void) { if (gldll) { FreeLibrary(gldll); gldll = 0; gl_driver[0] = 0; qwglGetProcAddress = NULL; gl_extensions = ""; gl_platform = ""; gl_platformextensions = ""; } } static int GL_OpenLibrary(const char *name) { Con_Printf("Loading OpenGL driver %s\n", name); GL_CloseLibrary(); if (!(gldll = LoadLibrary(name))) { Con_Printf("Unable to LoadLibrary %s\n", name); return false; } strlcpy(gl_driver, name, sizeof(gl_driver)); return true; } void *GL_GetProcAddress(const char *name) { if (gldll) { void *p = NULL; if (qwglGetProcAddress != NULL) p = (void *) qwglGetProcAddress(name); if (p == NULL) p = (void *) GetProcAddress(gldll, name); return p; } else return NULL; } #ifndef WGL_ARB_pixel_format #define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 #define WGL_DRAW_TO_WINDOW_ARB 0x2001 #define WGL_DRAW_TO_BITMAP_ARB 0x2002 #define WGL_ACCELERATION_ARB 0x2003 #define WGL_NEED_PALETTE_ARB 0x2004 #define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 #define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 #define WGL_SWAP_METHOD_ARB 0x2007 #define WGL_NUMBER_OVERLAYS_ARB 0x2008 #define WGL_NUMBER_UNDERLAYS_ARB 0x2009 #define WGL_TRANSPARENT_ARB 0x200A #define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 #define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 #define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 #define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A #define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B #define WGL_SHARE_DEPTH_ARB 0x200C #define WGL_SHARE_STENCIL_ARB 0x200D #define WGL_SHARE_ACCUM_ARB 0x200E #define WGL_SUPPORT_GDI_ARB 0x200F #define WGL_SUPPORT_OPENGL_ARB 0x2010 #define WGL_DOUBLE_BUFFER_ARB 0x2011 #define WGL_STEREO_ARB 0x2012 #define WGL_PIXEL_TYPE_ARB 0x2013 #define WGL_COLOR_BITS_ARB 0x2014 #define WGL_RED_BITS_ARB 0x2015 #define WGL_RED_SHIFT_ARB 0x2016 #define WGL_GREEN_BITS_ARB 0x2017 #define WGL_GREEN_SHIFT_ARB 0x2018 #define WGL_BLUE_BITS_ARB 0x2019 #define WGL_BLUE_SHIFT_ARB 0x201A #define WGL_ALPHA_BITS_ARB 0x201B #define WGL_ALPHA_SHIFT_ARB 0x201C #define WGL_ACCUM_BITS_ARB 0x201D #define WGL_ACCUM_RED_BITS_ARB 0x201E #define WGL_ACCUM_GREEN_BITS_ARB 0x201F #define WGL_ACCUM_BLUE_BITS_ARB 0x2020 #define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 #define WGL_DEPTH_BITS_ARB 0x2022 #define WGL_STENCIL_BITS_ARB 0x2023 #define WGL_AUX_BUFFERS_ARB 0x2024 #define WGL_NO_ACCELERATION_ARB 0x2025 #define WGL_GENERIC_ACCELERATION_ARB 0x2026 #define WGL_FULL_ACCELERATION_ARB 0x2027 #define WGL_SWAP_EXCHANGE_ARB 0x2028 #define WGL_SWAP_COPY_ARB 0x2029 #define WGL_SWAP_UNDEFINED_ARB 0x202A #define WGL_TYPE_RGBA_ARB 0x202B #define WGL_TYPE_COLORINDEX_ARB 0x202C #endif #ifndef WGL_ARB_multisample #define WGL_SAMPLE_BUFFERS_ARB 0x2041 #define WGL_SAMPLES_ARB 0x2042 #endif static void IN_Init(void); void VID_Init(void) { WNDCLASS wc; #ifdef SUPPORTD3D Cvar_RegisterVariable(&vid_dx9); Cvar_RegisterVariable(&vid_dx9_hal); Cvar_RegisterVariable(&vid_dx9_softvertex); Cvar_RegisterVariable(&vid_dx9_triplebuffer); // Cvar_RegisterVariable(&vid_dx10); // Cvar_RegisterVariable(&vid_dx11); #endif InitCommonControls(); hIcon = LoadIcon (global_hInstance, MAKEINTRESOURCE (IDI_ICON1)); // Register the frame class wc.style = 0; wc.lpfnWndProc = (WNDPROC)MainWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = global_hInstance; wc.hIcon = hIcon; wc.hCursor = LoadCursor (NULL,IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = 0; wc.lpszClassName = "DarkPlacesWindowClass"; if (!RegisterClass (&wc)) Con_Printf ("Couldn't register window class\n"); memset(&initialdevmode, 0, sizeof(initialdevmode)); EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &initialdevmode); desktop_mode.width = initialdevmode.dmPelsWidth; desktop_mode.height = initialdevmode.dmPelsHeight; desktop_mode.bpp = initialdevmode.dmBitsPerPel; desktop_mode.refreshrate = initialdevmode.dmDisplayFrequency; desktop_mode.pixelheight_num = 1; desktop_mode.pixelheight_denom = 1; // Win32 apparently does not provide this (FIXME) IN_Init(); } qboolean VID_InitModeGL(viddef_mode_t *mode) { int i; HDC hdc; RECT rect; MSG msg; PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), // size of this pfd 1, // version number PFD_DRAW_TO_WINDOW // support window | PFD_SUPPORT_OPENGL // support OpenGL | PFD_DOUBLEBUFFER , // double buffered PFD_TYPE_RGBA, // RGBA type 24, // 24-bit color depth 0, 0, 0, 0, 0, 0, // color bits ignored 0, // no alpha buffer 0, // shift bit ignored 0, // no accumulation buffer 0, 0, 0, 0, // accum bits ignored 32, // 32-bit z-buffer 0, // no stencil buffer 0, // no auxiliary buffer PFD_MAIN_PLANE, // main layer 0, // reserved 0, 0, 0 // layer masks ignored }; int windowpass; int pixelformat, newpixelformat; UINT numpixelformats; DWORD WindowStyle, ExWindowStyle; const char *gldrivername; int depth; DEVMODE thismode; qboolean foundmode, foundgoodmode; int *a; float *af; int attribs[128]; float attribsf[16]; int bpp = mode->bitsperpixel; int width = mode->width; int height = mode->height; int refreshrate = (int)floor(mode->refreshrate+0.5); int stereobuffer = mode->stereobuffer; int samples = mode->samples; int fullscreen = mode->fullscreen; if (vid_initialized) Sys_Error("VID_InitMode called when video is already initialised"); // if stencil is enabled, ask for alpha too if (bpp >= 32) { pfd.cRedBits = 8; pfd.cGreenBits = 8; pfd.cBlueBits = 8; pfd.cAlphaBits = 8; pfd.cDepthBits = 24; pfd.cStencilBits = 8; } else { pfd.cRedBits = 5; pfd.cGreenBits = 5; pfd.cBlueBits = 5; pfd.cAlphaBits = 0; pfd.cDepthBits = 16; pfd.cStencilBits = 0; } if (stereobuffer) pfd.dwFlags |= PFD_STEREO; a = attribs; af = attribsf; *a++ = WGL_DRAW_TO_WINDOW_ARB; *a++ = GL_TRUE; *a++ = WGL_ACCELERATION_ARB; *a++ = WGL_FULL_ACCELERATION_ARB; *a++ = WGL_DOUBLE_BUFFER_ARB; *a++ = true; if (bpp >= 32) { *a++ = WGL_RED_BITS_ARB; *a++ = 8; *a++ = WGL_GREEN_BITS_ARB; *a++ = 8; *a++ = WGL_BLUE_BITS_ARB; *a++ = 8; *a++ = WGL_ALPHA_BITS_ARB; *a++ = 8; *a++ = WGL_DEPTH_BITS_ARB; *a++ = 24; *a++ = WGL_STENCIL_BITS_ARB; *a++ = 8; } else { *a++ = WGL_RED_BITS_ARB; *a++ = 1; *a++ = WGL_GREEN_BITS_ARB; *a++ = 1; *a++ = WGL_BLUE_BITS_ARB; *a++ = 1; *a++ = WGL_DEPTH_BITS_ARB; *a++ = 16; } if (stereobuffer) { *a++ = WGL_STEREO_ARB; *a++ = GL_TRUE; } if (samples > 1) { *a++ = WGL_SAMPLE_BUFFERS_ARB; *a++ = 1; *a++ = WGL_SAMPLES_ARB; *a++ = samples; } *a = 0; *af = 0; gldrivername = "opengl32.dll"; // COMMANDLINEOPTION: Windows WGL: -gl_driver selects a GL driver library, default is opengl32.dll, useful only for 3dfxogl.dll or 3dfxvgl.dll, if you don't know what this is for, you don't need it i = COM_CheckParm("-gl_driver"); if (i && i < com_argc - 1) gldrivername = com_argv[i + 1]; if (!GL_OpenLibrary(gldrivername)) { Con_Printf("Unable to load GL driver %s\n", gldrivername); return false; } memset(&gdevmode, 0, sizeof(gdevmode)); vid_isfullscreen = false; if (fullscreen) { if(vid_desktopfullscreen.integer) { foundmode = true; gdevmode = initialdevmode; width = mode->width = gdevmode.dmPelsWidth; height = mode->height = gdevmode.dmPelsHeight; bpp = mode->bitsperpixel = gdevmode.dmBitsPerPel; } else if(vid_forcerefreshrate.integer) { foundmode = true; gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; gdevmode.dmBitsPerPel = bpp; gdevmode.dmPelsWidth = width; gdevmode.dmPelsHeight = height; gdevmode.dmSize = sizeof (gdevmode); if(refreshrate) { gdevmode.dmFields |= DM_DISPLAYFREQUENCY; gdevmode.dmDisplayFrequency = refreshrate; } } else { if(refreshrate == 0) refreshrate = initialdevmode.dmDisplayFrequency; // default vid_refreshrate to the rate of the desktop foundmode = false; foundgoodmode = false; thismode.dmSize = sizeof(thismode); thismode.dmDriverExtra = 0; for(i = 0; EnumDisplaySettings(NULL, i, &thismode); ++i) { if(~thismode.dmFields & (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY)) { Con_DPrintf("enumerating modes yielded a bogus item... please debug this\n"); continue; } if(developer_extra.integer) Con_DPrintf("Found mode %dx%dx%dbpp %dHz... ", (int)thismode.dmPelsWidth, (int)thismode.dmPelsHeight, (int)thismode.dmBitsPerPel, (int)thismode.dmDisplayFrequency); if(thismode.dmBitsPerPel != (DWORD)bpp) { if(developer_extra.integer) Con_DPrintf("wrong bpp\n"); continue; } if(thismode.dmPelsWidth != (DWORD)width) { if(developer_extra.integer) Con_DPrintf("wrong width\n"); continue; } if(thismode.dmPelsHeight != (DWORD)height) { if(developer_extra.integer) Con_DPrintf("wrong height\n"); continue; } if(foundgoodmode) { // if we have a good mode, make sure this mode is better than the previous one, and allowed by the refreshrate if(thismode.dmDisplayFrequency > (DWORD)refreshrate) { if(developer_extra.integer) Con_DPrintf("too high refresh rate\n"); continue; } else if(thismode.dmDisplayFrequency <= gdevmode.dmDisplayFrequency) { if(developer_extra.integer) Con_DPrintf("doesn't beat previous best match (too low)\n"); continue; } } else if(foundmode) { // we do have one, but it isn't good... make sure it has a lower frequency than the previous one if(thismode.dmDisplayFrequency >= gdevmode.dmDisplayFrequency) { if(developer_extra.integer) Con_DPrintf("doesn't beat previous best match (too high)\n"); continue; } } // otherwise, take anything memcpy(&gdevmode, &thismode, sizeof(gdevmode)); if(thismode.dmDisplayFrequency <= (DWORD)refreshrate) foundgoodmode = true; else { if(developer_extra.integer) Con_DPrintf("(out of range)\n"); } foundmode = true; if(developer_extra.integer) Con_DPrintf("accepted\n"); } } if (!foundmode) { VID_Shutdown(); Con_Printf("Unable to find the requested mode %dx%dx%dbpp\n", width, height, bpp); return false; } else if(ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { VID_Shutdown(); Con_Printf("Unable to change to requested mode %dx%dx%dbpp\n", width, height, bpp); return false; } vid_isfullscreen = true; WindowStyle = WS_POPUP; ExWindowStyle = WS_EX_TOPMOST; } else { hdc = GetDC (NULL); i = GetDeviceCaps(hdc, RASTERCAPS); depth = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL); ReleaseDC (NULL, hdc); if (i & RC_PALETTE) { VID_Shutdown(); Con_Print("Can't run in non-RGB mode\n"); return false; } if (bpp > depth) { VID_Shutdown(); Con_Print("A higher desktop depth is required to run this video mode\n"); return false; } WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; ExWindowStyle = 0; } AdjustWindowBounds(fullscreen, &width, &height, mode, WindowStyle, &rect); pixelformat = 0; newpixelformat = 0; // start out at the final windowpass if samples is 1 as it's the only feature we need extended pixel formats for for (windowpass = samples == 1;windowpass < 2;windowpass++) { gl_extensions = ""; gl_platformextensions = ""; mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); if (!mainwindow) { Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, (void *)global_hInstance, (void *)NULL); VID_Shutdown(); return false; } baseDC = GetDC(mainwindow); if (!newpixelformat) newpixelformat = ChoosePixelFormat(baseDC, &pfd); pixelformat = newpixelformat; if (!pixelformat) { VID_Shutdown(); Con_Printf("ChoosePixelFormat(%p, %p) failed\n", (void *)baseDC, (void *)&pfd); return false; } if (SetPixelFormat(baseDC, pixelformat, &pfd) == false) { VID_Shutdown(); Con_Printf("SetPixelFormat(%p, %d, %p) failed\n", (void *)baseDC, pixelformat, (void *)&pfd); return false; } if (!GL_CheckExtension("wgl", wglfuncs, NULL, false)) { VID_Shutdown(); Con_Print("wgl functions not found\n"); return false; } baseRC = qwglCreateContext(baseDC); if (!baseRC) { VID_Shutdown(); Con_Print("Could not initialize GL (wglCreateContext failed).\n\nMake sure you are in 65536 color mode, and try running -window.\n"); return false; } if (!qwglMakeCurrent(baseDC, baseRC)) { VID_Shutdown(); Con_Printf("wglMakeCurrent(%p, %p) failed\n", (void *)baseDC, (void *)baseRC); return false; } if ((qglGetString = (const GLubyte* (GLAPIENTRY *)(GLenum name))GL_GetProcAddress("glGetString")) == NULL) { VID_Shutdown(); Con_Print("glGetString not found\n"); return false; } if ((qwglGetExtensionsStringARB = (const char *(WINAPI *)(HDC hdc))GL_GetProcAddress("wglGetExtensionsStringARB")) == NULL) Con_Print("wglGetExtensionsStringARB not found\n"); gl_extensions = (const char *)qglGetString(GL_EXTENSIONS); gl_platform = "WGL"; gl_platformextensions = ""; if (qwglGetExtensionsStringARB) gl_platformextensions = (const char *)qwglGetExtensionsStringARB(baseDC); if (!gl_extensions) gl_extensions = ""; if (!gl_platformextensions) gl_platformextensions = ""; // now some nice Windows pain: // we have created a window, we needed one to find out if there are // any multisample pixel formats available, the problem is that to // actually use one of those multisample formats we now have to // recreate the window (yes Microsoft OpenGL really is that bad) if (windowpass == 0) { if (!GL_CheckExtension("WGL_ARB_pixel_format", wglpixelformatfuncs, "-noarbpixelformat", false) || !qwglChoosePixelFormatARB(baseDC, attribs, attribsf, 1, &newpixelformat, &numpixelformats) || !newpixelformat) break; // ok we got one - do it all over again with newpixelformat qwglMakeCurrent(NULL, NULL); qwglDeleteContext(baseRC);baseRC = 0; ReleaseDC(mainwindow, baseDC);baseDC = 0; // eat up any messages waiting for us while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } } } /* if (!fullscreen) SetWindowPos (mainwindow, NULL, CenterX, CenterY, 0, 0,SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW | SWP_DRAWFRAME); */ ShowWindow (mainwindow, SW_SHOWDEFAULT); UpdateWindow (mainwindow); // now we try to make sure we get the focus on the mode switch, because // sometimes in some systems we don't. We grab the foreground, then // finish setting up, pump all our messages, and sleep for a little while // to let messages finish bouncing around the system, then we put // ourselves at the top of the z order, then grab the foreground again, // Who knows if it helps, but it probably doesn't hurt SetForegroundWindow (mainwindow); while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } Sleep (100); SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); SetForegroundWindow (mainwindow); // fix the leftover Alt from any Alt-Tab or the like that switched us away ClearAllStates (); // COMMANDLINEOPTION: Windows WGL: -novideosync disables WGL_EXT_swap_control GL_CheckExtension("WGL_EXT_swap_control", wglswapintervalfuncs, "-novideosync", false); GL_Init (); //vid_menudrawfn = VID_MenuDraw; //vid_menukeyfn = VID_MenuKey; vid_usingmouse = false; vid_usinghidecursor = false; vid_usingvsync = false; vid_reallyhidden = vid_hidden = false; vid_initialized = true; IN_StartupMouse (); if (qwglSwapIntervalEXT) { vid_usevsync = vid_vsync.integer != 0; vid_usingvsync = vid_vsync.integer != 0; qwglSwapIntervalEXT (vid_usevsync); } return true; } static void AdjustWindowBounds(int fullscreen, int *width, int *height, viddef_mode_t *mode, DWORD WindowStyle, RECT *rect) { int CenterX, CenterY; rect->top = 0; rect->left = 0; rect->right = *width; rect->bottom = *height; AdjustWindowRectEx(rect, WindowStyle, false, 0); if (fullscreen) { CenterX = 0; CenterY = 0; } else { RECT workArea; SystemParametersInfo(SPI_GETWORKAREA, NULL, &workArea, 0); int workWidth = workArea.right - workArea.left; int workHeight = workArea.bottom - workArea.top; // if height/width matches physical screen height/width, adjust it to available desktop size // and allow 2 pixels on top for the title bar so the window can be moved const int titleBarPixels = 2; if (*width == GetSystemMetrics(SM_CXSCREEN) && (*height == GetSystemMetrics(SM_CYSCREEN) || *height == workHeight - titleBarPixels)) { rect->right -= *width - workWidth; *width = mode->width = workWidth; rect->bottom -= *height - (workHeight - titleBarPixels); *height = mode->height = workHeight - titleBarPixels; CenterX = 0; CenterY = titleBarPixels; } else { CenterX = max(0, (workWidth - *width) / 2); CenterY = max(0, (workHeight - *height) / 2); } } // x and y may be changed by WM_MOVE messages window_x = CenterX; window_y = CenterY; rect->left += CenterX; rect->right += CenterX; rect->top += CenterY; rect->bottom += CenterY; } #ifdef SUPPORTD3D static D3DADAPTER_IDENTIFIER9 d3d9adapteridentifier; extern cvar_t gl_info_extensions; extern cvar_t gl_info_vendor; extern cvar_t gl_info_renderer; extern cvar_t gl_info_version; extern cvar_t gl_info_platform; extern cvar_t gl_info_driver; qboolean VID_InitModeDX(viddef_mode_t *mode, int version) { int deviceindex; RECT rect; MSG msg; DWORD WindowStyle, ExWindowStyle; int bpp = mode->bitsperpixel; int width = mode->width; int height = mode->height; int refreshrate = (int)floor(mode->refreshrate+0.5); // int stereobuffer = mode->stereobuffer; int samples = mode->samples; int fullscreen = mode->fullscreen; int numdevices; if (vid_initialized) Sys_Error("VID_InitMode called when video is already initialised"); vid_isfullscreen = fullscreen != 0; if (fullscreen) { WindowStyle = WS_POPUP; ExWindowStyle = WS_EX_TOPMOST; } else { WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; ExWindowStyle = 0; } AdjustWindowBounds(fullscreen, &width, &height, mode, WindowStyle, &rect); gl_extensions = ""; gl_platformextensions = ""; mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); if (!mainwindow) { Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, global_hInstance, (void *)NULL); VID_Shutdown(); return false; } baseDC = GetDC(mainwindow); vid_d3d9 = Direct3DCreate9(D3D_SDK_VERSION); if (!vid_d3d9) Sys_Error("VID_InitMode: Direct3DCreate9 failed"); numdevices = IDirect3D9_GetAdapterCount(vid_d3d9); vid_d3d9dev = NULL; memset(&d3d9adapteridentifier, 0, sizeof(d3d9adapteridentifier)); for (deviceindex = 0;deviceindex < numdevices && !vid_d3d9dev;deviceindex++) { memset(&vid_d3dpresentparameters, 0, sizeof(vid_d3dpresentparameters)); // vid_d3dpresentparameters.Flags = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL; vid_d3dpresentparameters.Flags = 0; vid_d3dpresentparameters.SwapEffect = D3DSWAPEFFECT_DISCARD; vid_d3dpresentparameters.hDeviceWindow = mainwindow; vid_d3dpresentparameters.BackBufferWidth = width; vid_d3dpresentparameters.BackBufferHeight = height; vid_d3dpresentparameters.MultiSampleType = samples > 1 ? (D3DMULTISAMPLE_TYPE)samples : D3DMULTISAMPLE_NONE; vid_d3dpresentparameters.BackBufferCount = fullscreen ? (vid_dx9_triplebuffer.integer ? 3 : 2) : 1; vid_d3dpresentparameters.FullScreen_RefreshRateInHz = fullscreen ? refreshrate : 0; vid_d3dpresentparameters.Windowed = !fullscreen; vid_d3dpresentparameters.EnableAutoDepthStencil = true; vid_d3dpresentparameters.AutoDepthStencilFormat = bpp > 16 ? D3DFMT_D24S8 : D3DFMT_D16; vid_d3dpresentparameters.BackBufferFormat = fullscreen?D3DFMT_X8R8G8B8:D3DFMT_UNKNOWN; vid_d3dpresentparameters.PresentationInterval = vid_vsync.integer ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; memset(&d3d9adapteridentifier, 0, sizeof(d3d9adapteridentifier)); IDirect3D9_GetAdapterIdentifier(vid_d3d9, deviceindex, 0, &d3d9adapteridentifier); IDirect3D9_CreateDevice(vid_d3d9, deviceindex, vid_dx9_hal.integer ? D3DDEVTYPE_HAL : D3DDEVTYPE_REF, mainwindow, vid_dx9_softvertex.integer ? D3DCREATE_SOFTWARE_VERTEXPROCESSING : D3DCREATE_HARDWARE_VERTEXPROCESSING, &vid_d3dpresentparameters, &vid_d3d9dev); } if (!vid_d3d9dev) { VID_Shutdown(); return false; } IDirect3DDevice9_GetDeviceCaps(vid_d3d9dev, &vid_d3d9caps); Con_Printf("Using D3D9 device: %s\n", d3d9adapteridentifier.Description); gl_extensions = ""; gl_platform = "D3D9"; gl_platformextensions = ""; ShowWindow (mainwindow, SW_SHOWDEFAULT); UpdateWindow (mainwindow); // now we try to make sure we get the focus on the mode switch, because // sometimes in some systems we don't. We grab the foreground, then // finish setting up, pump all our messages, and sleep for a little while // to let messages finish bouncing around the system, then we put // ourselves at the top of the z order, then grab the foreground again, // Who knows if it helps, but it probably doesn't hurt SetForegroundWindow (mainwindow); while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } Sleep (100); SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); SetForegroundWindow (mainwindow); // fix the leftover Alt from any Alt-Tab or the like that switched us away ClearAllStates (); gl_renderer = d3d9adapteridentifier.Description; gl_vendor = d3d9adapteridentifier.Driver; gl_version = ""; gl_extensions = ""; Con_Printf("D3D9 adapter info:\n"); Con_Printf("Description: %s\n", d3d9adapteridentifier.Description); Con_Printf("DeviceId: %x\n", (unsigned int)d3d9adapteridentifier.DeviceId); Con_Printf("DeviceName: %p\n", d3d9adapteridentifier.DeviceName); Con_Printf("Driver: %s\n", d3d9adapteridentifier.Driver); Con_Printf("DriverVersion: %08x%08x\n", (unsigned int)d3d9adapteridentifier.DriverVersion.u.HighPart, (unsigned int)d3d9adapteridentifier.DriverVersion.u.LowPart); Con_DPrintf("GL_EXTENSIONS: %s\n", gl_extensions); Con_DPrintf("%s_EXTENSIONS: %s\n", gl_platform, gl_platformextensions); // clear the extension flags memset(&vid.support, 0, sizeof(vid.support)); Cvar_SetQuick(&gl_info_extensions, ""); // D3D9 requires BGRA vid.forcetextype = TEXTYPE_BGRA; vid.forcevbo = false; vid.support.arb_depth_texture = true; vid.support.arb_draw_buffers = vid_d3d9caps.NumSimultaneousRTs > 1; vid.support.arb_occlusion_query = true; // can't find a cap for this vid.support.arb_query_buffer_object = true; vid.support.arb_shadow = true; vid.support.arb_texture_compression = true; vid.support.arb_texture_cube_map = true; vid.support.arb_texture_non_power_of_two = (vid_d3d9caps.TextureCaps & D3DPTEXTURECAPS_POW2) == 0; vid.support.arb_vertex_buffer_object = true; vid.support.ext_blend_subtract = true; vid.support.ext_draw_range_elements = true; vid.support.ext_framebuffer_object = true; vid.support.ext_texture_3d = true; vid.support.ext_texture_compression_s3tc = true; vid.support.ext_texture_filter_anisotropic = true; vid.support.ati_separate_stencil = (vid_d3d9caps.StencilCaps & D3DSTENCILCAPS_TWOSIDED) != 0; vid.support.ext_texture_srgb = false; // FIXME use D3DSAMP_SRGBTEXTURE if CheckDeviceFormat agrees vid.maxtexturesize_2d = min(vid_d3d9caps.MaxTextureWidth, vid_d3d9caps.MaxTextureHeight); vid.maxtexturesize_3d = vid_d3d9caps.MaxVolumeExtent; vid.maxtexturesize_cubemap = vid.maxtexturesize_2d; vid.texunits = 4; vid.teximageunits = vid_d3d9caps.MaxSimultaneousTextures; vid.texarrayunits = 8; // can't find a caps field for this? vid.max_anisotropy = vid_d3d9caps.MaxAnisotropy; vid.maxdrawbuffers = vid_d3d9caps.NumSimultaneousRTs; vid.texunits = bound(4, vid.texunits, MAX_TEXTUREUNITS); vid.teximageunits = bound(16, vid.teximageunits, MAX_TEXTUREUNITS); vid.texarrayunits = bound(8, vid.texarrayunits, MAX_TEXTUREUNITS); Con_DPrintf("Using D3D9.0 rendering path - %i texture matrix, %i texture images, %i texcoords, shadowmapping supported%s\n", vid.texunits, vid.teximageunits, vid.texarrayunits, vid.maxdrawbuffers > 1 ? ", MRT detected (allows prepass deferred lighting)" : ""); vid.renderpath = RENDERPATH_D3D9; vid.sRGBcapable2D = false; vid.sRGBcapable3D = true; vid.useinterleavedarrays = true; Cvar_SetQuick(&gl_info_vendor, gl_vendor); Cvar_SetQuick(&gl_info_renderer, gl_renderer); Cvar_SetQuick(&gl_info_version, gl_version); Cvar_SetQuick(&gl_info_platform, gl_platform ? gl_platform : ""); Cvar_SetQuick(&gl_info_driver, gl_driver); // LordHavoc: report supported extensions Con_DPrintf("\nQuakeC extensions for server and client: %s\nQuakeC extensions for menu: %s\n", vm_sv_extensions, vm_m_extensions ); // clear to black (loading plaque will be seen over this) IDirect3DDevice9_Clear(vid_d3d9dev, 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); IDirect3DDevice9_BeginScene(vid_d3d9dev); IDirect3DDevice9_EndScene(vid_d3d9dev); IDirect3DDevice9_Present(vid_d3d9dev, NULL, NULL, NULL, NULL); // because the only time we end/begin scene is in VID_Finish, we'd better start a scene now... IDirect3DDevice9_BeginScene(vid_d3d9dev); vid_begunscene = true; //vid_menudrawfn = VID_MenuDraw; //vid_menukeyfn = VID_MenuKey; vid_usingmouse = false; vid_usinghidecursor = false; vid_usingvsync = false; vid_hidden = vid_reallyhidden = false; vid_initialized = true; IN_StartupMouse (); return true; } #endif qboolean VID_InitModeSOFT(viddef_mode_t *mode) { int i; HDC hdc; RECT rect; MSG msg; int pixelformat, newpixelformat; DWORD WindowStyle, ExWindowStyle; int depth; DEVMODE thismode; qboolean foundmode, foundgoodmode; int bpp = mode->bitsperpixel; int width = mode->width; int height = mode->height; int refreshrate = (int)floor(mode->refreshrate+0.5); int fullscreen = mode->fullscreen; if (vid_initialized) Sys_Error("VID_InitMode called when video is already initialised"); memset(&gdevmode, 0, sizeof(gdevmode)); vid_isfullscreen = false; if (fullscreen) { if(vid_forcerefreshrate.integer) { foundmode = true; gdevmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; gdevmode.dmBitsPerPel = bpp; gdevmode.dmPelsWidth = width; gdevmode.dmPelsHeight = height; gdevmode.dmSize = sizeof (gdevmode); if(refreshrate) { gdevmode.dmFields |= DM_DISPLAYFREQUENCY; gdevmode.dmDisplayFrequency = refreshrate; } } else { if(refreshrate == 0) refreshrate = initialdevmode.dmDisplayFrequency; // default vid_refreshrate to the rate of the desktop foundmode = false; foundgoodmode = false; thismode.dmSize = sizeof(thismode); thismode.dmDriverExtra = 0; for(i = 0; EnumDisplaySettings(NULL, i, &thismode); ++i) { if(~thismode.dmFields & (DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY)) { Con_DPrintf("enumerating modes yielded a bogus item... please debug this\n"); continue; } if(developer_extra.integer) Con_DPrintf("Found mode %dx%dx%dbpp %dHz... ", (int)thismode.dmPelsWidth, (int)thismode.dmPelsHeight, (int)thismode.dmBitsPerPel, (int)thismode.dmDisplayFrequency); if(thismode.dmBitsPerPel != (DWORD)bpp) { if(developer_extra.integer) Con_DPrintf("wrong bpp\n"); continue; } if(thismode.dmPelsWidth != (DWORD)width) { if(developer_extra.integer) Con_DPrintf("wrong width\n"); continue; } if(thismode.dmPelsHeight != (DWORD)height) { if(developer_extra.integer) Con_DPrintf("wrong height\n"); continue; } if(foundgoodmode) { // if we have a good mode, make sure this mode is better than the previous one, and allowed by the refreshrate if(thismode.dmDisplayFrequency > (DWORD)refreshrate) { if(developer_extra.integer) Con_DPrintf("too high refresh rate\n"); continue; } else if(thismode.dmDisplayFrequency <= gdevmode.dmDisplayFrequency) { if(developer_extra.integer) Con_DPrintf("doesn't beat previous best match (too low)\n"); continue; } } else if(foundmode) { // we do have one, but it isn't good... make sure it has a lower frequency than the previous one if(thismode.dmDisplayFrequency >= gdevmode.dmDisplayFrequency) { if(developer_extra.integer) Con_DPrintf("doesn't beat previous best match (too high)\n"); continue; } } // otherwise, take anything memcpy(&gdevmode, &thismode, sizeof(gdevmode)); if(thismode.dmDisplayFrequency <= (DWORD)refreshrate) foundgoodmode = true; else { if(developer_extra.integer) Con_DPrintf("(out of range)\n"); } foundmode = true; if(developer_extra.integer) Con_DPrintf("accepted\n"); } } if (!foundmode) { VID_Shutdown(); Con_Printf("Unable to find the requested mode %dx%dx%dbpp\n", width, height, bpp); return false; } else if(ChangeDisplaySettings (&gdevmode, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { VID_Shutdown(); Con_Printf("Unable to change to requested mode %dx%dx%dbpp\n", width, height, bpp); return false; } vid_isfullscreen = true; WindowStyle = WS_POPUP; ExWindowStyle = WS_EX_TOPMOST; } else { hdc = GetDC (NULL); i = GetDeviceCaps(hdc, RASTERCAPS); depth = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL); ReleaseDC (NULL, hdc); if (i & RC_PALETTE) { VID_Shutdown(); Con_Print("Can't run in non-RGB mode\n"); return false; } if (bpp > depth) { VID_Shutdown(); Con_Print("A higher desktop depth is required to run this video mode\n"); return false; } WindowStyle = WS_OVERLAPPED | WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX; ExWindowStyle = 0; } AdjustWindowBounds(fullscreen, &width, &height, mode, WindowStyle, &rect); pixelformat = 0; newpixelformat = 0; gl_extensions = ""; gl_platformextensions = ""; mainwindow = CreateWindowEx (ExWindowStyle, "DarkPlacesWindowClass", gamename, WindowStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, global_hInstance, NULL); if (!mainwindow) { Con_Printf("CreateWindowEx(%d, %s, %s, %d, %d, %d, %d, %d, %p, %p, %p, %p) failed\n", (int)ExWindowStyle, "DarkPlacesWindowClass", gamename, (int)WindowStyle, (int)(rect.left), (int)(rect.top), (int)(rect.right - rect.left), (int)(rect.bottom - rect.top), (void *)NULL, (void *)NULL, (void *)global_hInstance, (void *)NULL); VID_Shutdown(); return false; } baseDC = GetDC(mainwindow); vid.softpixels = NULL; memset(&vid_softbmi, 0, sizeof(vid_softbmi)); vid_softbmi.bmiHeader.biSize = sizeof(vid_softbmi.bmiHeader); vid_softbmi.bmiHeader.biWidth = width; vid_softbmi.bmiHeader.biHeight = -height; // negative to make a top-down bitmap vid_softbmi.bmiHeader.biPlanes = 1; vid_softbmi.bmiHeader.biBitCount = 32; vid_softbmi.bmiHeader.biCompression = BI_RGB; vid_softbmi.bmiHeader.biSizeImage = width*height*4; vid_softbmi.bmiHeader.biClrUsed = 256; vid_softbmi.bmiHeader.biClrImportant = 256; vid_softdibhandle = CreateDIBSection(baseDC, &vid_softbmi, DIB_RGB_COLORS, (void **)&vid.softpixels, NULL, 0); if (!vid_softdibhandle) { Con_Printf("CreateDIBSection failed\n"); VID_Shutdown(); return false; } vid_softhdc = CreateCompatibleDC(baseDC); vid_softhdc_backup = SelectObject(vid_softhdc, vid_softdibhandle); if (!vid_softhdc_backup) { Con_Printf("SelectObject failed\n"); VID_Shutdown(); return false; } // ReleaseDC(mainwindow, baseDC); // baseDC = NULL; vid.softdepthpixels = (unsigned int *)calloc(1, mode->width * mode->height * 4); if (DPSOFTRAST_Init(mode->width, mode->height, vid_soft_threads.integer, vid_soft_interlace.integer, (unsigned int *)vid.softpixels, (unsigned int *)vid.softdepthpixels) < 0) { Con_Printf("Failed to initialize software rasterizer\n"); VID_Shutdown(); return false; } VID_Soft_SharedSetup(); ShowWindow (mainwindow, SW_SHOWDEFAULT); UpdateWindow (mainwindow); // now we try to make sure we get the focus on the mode switch, because // sometimes in some systems we don't. We grab the foreground, then // finish setting up, pump all our messages, and sleep for a little while // to let messages finish bouncing around the system, then we put // ourselves at the top of the z order, then grab the foreground again, // Who knows if it helps, but it probably doesn't hurt SetForegroundWindow (mainwindow); while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage (&msg); DispatchMessage (&msg); } Sleep (100); SetWindowPos (mainwindow, HWND_TOP, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOCOPYBITS); SetForegroundWindow (mainwindow); // fix the leftover Alt from any Alt-Tab or the like that switched us away ClearAllStates (); //vid_menudrawfn = VID_MenuDraw; //vid_menukeyfn = VID_MenuKey; vid_usingmouse = false; vid_usinghidecursor = false; vid_usingvsync = false; vid_reallyhidden = vid_hidden = false; vid_initialized = true; IN_StartupMouse (); return true; } qboolean VID_InitMode(viddef_mode_t *mode) { #ifdef SSE_POSSIBLE if (vid_soft.integer) return VID_InitModeSOFT(mode); #endif #ifdef SUPPORTD3D // if (vid_dx11.integer) // return VID_InitModeDX(mode, 11); // if (vid_dx10.integer) // return VID_InitModeDX(mode, 10); if (vid_dx9.integer) return VID_InitModeDX(mode, 9); #endif return VID_InitModeGL(mode); } static void IN_Shutdown(void); void VID_Shutdown (void) { qboolean isgl; if(vid_initialized == false) return; VID_EnableJoystick(false); VID_SetMouse(false, false, false); VID_RestoreSystemGamma(); vid_initialized = false; isgl = gldll != NULL; IN_Shutdown(); gl_driver[0] = 0; gl_extensions = ""; gl_platform = ""; gl_platformextensions = ""; if (vid_softhdc) { SelectObject(vid_softhdc, vid_softhdc_backup); ReleaseDC(mainwindow, vid_softhdc); } vid_softhdc = NULL; vid_softhdc_backup = NULL; if (vid_softdibhandle) DeleteObject(vid_softdibhandle); vid_softdibhandle = NULL; vid.softpixels = NULL; if (vid.softdepthpixels) free(vid.softdepthpixels); vid.softdepthpixels = NULL; #ifdef SUPPORTD3D if (vid_d3d9dev) { if (vid_begunscene) IDirect3DDevice9_EndScene(vid_d3d9dev); vid_begunscene = false; // Cmd_ExecuteString("r_texturestats", src_command, true); // Cmd_ExecuteString("memlist", src_command, true); IDirect3DDevice9_Release(vid_d3d9dev); } vid_d3d9dev = NULL; if (vid_d3d9) IDirect3D9_Release(vid_d3d9); vid_d3d9 = NULL; #endif if (qwglMakeCurrent) qwglMakeCurrent(NULL, NULL); qwglMakeCurrent = NULL; if (baseRC && qwglDeleteContext) qwglDeleteContext(baseRC); qwglDeleteContext = NULL; // close the library before we get rid of the window GL_CloseLibrary(); if (baseDC && mainwindow) ReleaseDC(mainwindow, baseDC); baseDC = NULL; AppActivate(false, false); if (mainwindow) DestroyWindow(mainwindow); mainwindow = 0; if (vid_isfullscreen && isgl) ChangeDisplaySettings (NULL, CDS_FULLSCREEN); vid_isfullscreen = false; } void VID_SetMouse(qboolean fullscreengrab, qboolean relative, qboolean hidecursor) { static qboolean restore_spi; static int originalmouseparms[3]; if (!mouseinitialized) return; if (relative) { if (!vid_usingmouse) { vid_usingmouse = true; cl_ignoremousemoves = 2; #ifdef SUPPORTDIRECTX if (dinput && g_pMouse) { IDirectInputDevice_Acquire(g_pMouse); dinput_acquired = true; } else #endif { RECT window_rect; window_rect.left = window_x; window_rect.top = window_y; window_rect.right = window_x + vid.width; window_rect.bottom = window_y + vid.height; // change mouse settings to turn off acceleration // COMMANDLINEOPTION: Windows GDI Input: -noforcemparms disables setting of mouse parameters (not used with -dinput, windows only) if (!COM_CheckParm ("-noforcemparms") && SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0)) { int newmouseparms[3]; newmouseparms[0] = 0; // threshold to double movement (only if accel level is >= 1) newmouseparms[1] = 0; // threshold to quadruple movement (only if accel level is >= 2) newmouseparms[2] = 0; // maximum level of acceleration (0 = off) restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0) != FALSE; } else restore_spi = false; SetCursorPos ((window_x + vid.width / 2), (window_y + vid.height / 2)); SetCapture (mainwindow); ClipCursor (&window_rect); } } } else { if (vid_usingmouse) { vid_usingmouse = false; cl_ignoremousemoves = 2; #ifdef SUPPORTDIRECTX if (dinput_acquired) { IDirectInputDevice_Unacquire(g_pMouse); dinput_acquired = false; } else #endif { // restore system mouseparms if we changed them if (restore_spi) SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); restore_spi = false; ClipCursor (NULL); ReleaseCapture (); } } } if (vid_usinghidecursor != hidecursor) { vid_usinghidecursor = hidecursor; ShowCursor (!hidecursor); } } void VID_BuildJoyState(vid_joystate_t *joystate) { VID_Shared_BuildJoyState_Begin(joystate); VID_Shared_BuildJoyState_Finish(joystate); } void VID_EnableJoystick(qboolean enable) { int index = joy_enable.integer > 0 ? joy_index.integer : -1; qboolean success = false; int sharedcount = 0; sharedcount = VID_Shared_SetJoystick(index); if (index >= 0 && index < sharedcount) success = true; // update cvar containing count of XInput joysticks if (joy_detected.integer != sharedcount) Cvar_SetValueQuick(&joy_detected, sharedcount); if (joy_active.integer != (success ? 1 : 0)) Cvar_SetValueQuick(&joy_active, success ? 1 : 0); } #ifdef SUPPORTDIRECTX /* =========== IN_InitDInput =========== */ static qboolean IN_InitDInput (void) { HRESULT hr; DIPROPDWORD dipdw = { { sizeof(DIPROPDWORD), // diph.dwSize sizeof(DIPROPHEADER), // diph.dwHeaderSize 0, // diph.dwObj DIPH_DEVICE, // diph.dwHow }, DINPUT_BUFFERSIZE, // dwData }; if (!hInstDI) { hInstDI = LoadLibrary("dinput.dll"); if (hInstDI == NULL) { Con_Print("Couldn't load dinput.dll\n"); return false; } } if (!pDirectInputCreate) { pDirectInputCreate = (HRESULT (__stdcall *)(HINSTANCE,DWORD,LPDIRECTINPUT *,LPUNKNOWN))GetProcAddress(hInstDI,"DirectInputCreateA"); if (!pDirectInputCreate) { Con_Print("Couldn't get DI proc addr\n"); return false; } } // register with DirectInput and get an IDirectInput to play with. hr = iDirectInputCreate(global_hInstance, DIRECTINPUT_VERSION, &g_pdi, NULL); if (FAILED(hr)) { return false; } // obtain an interface to the system mouse device. #ifdef __cplusplus hr = IDirectInput_CreateDevice(g_pdi, GUID_SysMouse, &g_pMouse, NULL); #else hr = IDirectInput_CreateDevice(g_pdi, &GUID_SysMouse, &g_pMouse, NULL); #endif if (FAILED(hr)) { Con_Print("Couldn't open DI mouse device\n"); return false; } // set the data format to "mouse format". hr = IDirectInputDevice_SetDataFormat(g_pMouse, &c_dfDIMouse); if (FAILED(hr)) { Con_Print("Couldn't set DI mouse format\n"); return false; } // set the cooperativity level. hr = IDirectInputDevice_SetCooperativeLevel(g_pMouse, mainwindow, DISCL_EXCLUSIVE | DISCL_FOREGROUND); if (FAILED(hr)) { Con_Print("Couldn't set DI coop level\n"); return false; } // set the buffer size to DINPUT_BUFFERSIZE elements. // the buffer size is a DWORD property associated with the device hr = IDirectInputDevice_SetProperty(g_pMouse, DIPROP_BUFFERSIZE, &dipdw.diph); if (FAILED(hr)) { Con_Print("Couldn't set DI buffersize\n"); return false; } return true; } #endif /* =========== IN_StartupMouse =========== */ static void IN_StartupMouse (void) { if (COM_CheckParm ("-nomouse")) return; mouseinitialized = true; #ifdef SUPPORTDIRECTX // COMMANDLINEOPTION: Windows Input: -dinput enables DirectInput for mouse input if (COM_CheckParm ("-dinput")) dinput = IN_InitDInput (); if (dinput) Con_Print("DirectInput initialized\n"); else Con_Print("DirectInput not initialized\n"); #endif mouse_buttons = 10; } /* =========== IN_MouseMove =========== */ static void IN_MouseMove (void) { POINT current_pos; GetCursorPos (¤t_pos); in_windowmouse_x = current_pos.x - window_x; in_windowmouse_y = current_pos.y - window_y; if (!vid_usingmouse) return; #ifdef SUPPORTDIRECTX if (dinput_acquired) { int i; DIDEVICEOBJECTDATA od; DWORD dwElements; HRESULT hr; for (;;) { dwElements = 1; hr = IDirectInputDevice_GetDeviceData(g_pMouse, sizeof(DIDEVICEOBJECTDATA), &od, &dwElements, 0); if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED)) { IDirectInputDevice_Acquire(g_pMouse); break; } /* Unable to read data or no data available */ if (FAILED(hr) || dwElements == 0) break; /* Look at the element to see what happened */ if ((int)od.dwOfs == DIMOFS_X) in_mouse_x += (LONG) od.dwData; if ((int)od.dwOfs == DIMOFS_Y) in_mouse_y += (LONG) od.dwData; if ((int)od.dwOfs == DIMOFS_Z) { if((LONG)od.dwData < 0) { Key_Event(K_MWHEELDOWN, 0, true); Key_Event(K_MWHEELDOWN, 0, false); } else if((LONG)od.dwData > 0) { Key_Event(K_MWHEELUP, 0, true); Key_Event(K_MWHEELUP, 0, false); } } if ((int)od.dwOfs == DIMOFS_BUTTON0) mstate_di = (mstate_di & ~1) | ((od.dwData & 0x80) >> 7); if ((int)od.dwOfs == DIMOFS_BUTTON1) mstate_di = (mstate_di & ~2) | ((od.dwData & 0x80) >> 6); if ((int)od.dwOfs == DIMOFS_BUTTON2) mstate_di = (mstate_di & ~4) | ((od.dwData & 0x80) >> 5); if ((int)od.dwOfs == DIMOFS_BUTTON3) mstate_di = (mstate_di & ~8) | ((od.dwData & 0x80) >> 4); } // perform button actions for (i=0 ; i= maxcount) break; modes[k].width = thismode.dmPelsWidth; modes[k].height = thismode.dmPelsHeight; modes[k].bpp = thismode.dmBitsPerPel; modes[k].refreshrate = thismode.dmDisplayFrequency; modes[k].pixelheight_num = 1; modes[k].pixelheight_denom = 1; // Win32 apparently does not provide this (FIXME) ++k; } return k; } darkplaces/console.h0000664000175000017500000001107513067716216014007 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CONSOLE_H #define CONSOLE_H // // console // extern int con_totallines; extern int con_backscroll; extern qboolean con_initialized; void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol); void Con_Rcon_Redirect_End(void); void Con_Rcon_Redirect_Abort(void); /// If the line width has changed, reformat the buffer. void Con_CheckResize (void); void Con_Init (void); void Con_Init_Commands (void); void Con_Shutdown (void); void Con_DrawConsole (int lines); /// Prints to a chosen console target void Con_MaskPrint(int mask, const char *msg); // Prints to a chosen console target void Con_MaskPrintf(int mask, const char *fmt, ...) DP_FUNC_PRINTF(2); /// Prints to all appropriate console targets, and adds timestamps void Con_Print(const char *txt); /// Prints to all appropriate console targets. void Con_Printf(const char *fmt, ...) DP_FUNC_PRINTF(1); /// A Con_Print that only shows up if the "developer" cvar is set. void Con_DPrint(const char *msg); /// A Con_Printf that only shows up if the "developer" cvar is set void Con_DPrintf(const char *fmt, ...) DP_FUNC_PRINTF(1); void Con_Clear_f (void); void Con_DrawNotify (void); /// Clear all notify lines. void Con_ClearNotify (void); void Con_ToggleConsole_f (void); int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos); qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength); /// wrapper function to attempt to either complete the command line /// or to list possible matches grouped by type /// (i.e. will display possible variables, aliases, commands /// that match what they've typed so far) void Con_CompleteCommandLine(void); /// Generic libs/util/console.c function to display a list /// formatted in columns on the console void Con_DisplayList(const char **list); /*! \name log * @{ */ void Log_Init (void); void Log_Close (void); void Log_Start (void); void Log_DestBuffer_Flush (void); ///< call this once per frame to send out replies to rcon streaming clients void Log_Printf(const char *logfilename, const char *fmt, ...) DP_FUNC_PRINTF(2); //@} // CON_MASK_PRINT is the default (Con_Print/Con_Printf) // CON_MASK_DEVELOPER is used by Con_DPrint/Con_DPrintf #define CON_MASK_HIDENOTIFY 128 #define CON_MASK_CHAT 1 #define CON_MASK_INPUT 2 #define CON_MASK_DEVELOPER 4 #define CON_MASK_PRINT 8 typedef struct con_lineinfo_s { char *start; size_t len; int mask; /// used only by console.c double addtime; int height; ///< recalculated line height when needed (-1 to unset) } con_lineinfo_t; typedef struct conbuffer_s { qboolean active; int textsize; char *text; int maxlines; con_lineinfo_t *lines; int lines_first; int lines_count; ///< cyclic buffer } conbuffer_t; #define CONBUFFER_LINES(buf, i) (buf)->lines[((buf)->lines_first + (i)) % (buf)->maxlines] #define CONBUFFER_LINES_COUNT(buf) ((buf)->lines_count) #define CONBUFFER_LINES_LAST(buf) CONBUFFER_LINES(buf, CONBUFFER_LINES_COUNT(buf) - 1) void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool); void ConBuffer_Clear (conbuffer_t *buf); void ConBuffer_Shutdown(conbuffer_t *buf); /*! Notifies the console code about the current time * (and shifts back times of other entries when the time * went backwards) */ void ConBuffer_FixTimes(conbuffer_t *buf); /// Deletes the first line from the console history. void ConBuffer_DeleteLine(conbuffer_t *buf); /// Deletes the last line from the console history. void ConBuffer_DeleteLastLine(conbuffer_t *buf); /// Appends a given string as a new line to the console. void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask); int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); int ConBuffer_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start); const char *ConBuffer_GetLine(conbuffer_t *buf, int i); #endif darkplaces/common.c0000664000175000017500000020161113067716216013625 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // common.c -- misc functions used in client and server #include #include #ifndef WIN32 #include #endif #include "quakedef.h" #include "utf8lib.h" cvar_t registered = {0, "registered","0", "indicates if this is running registered quake (whether gfx/pop.lmp was found)"}; cvar_t cmdline = {0, "cmdline","0", "contains commandline the engine was launched with"}; char com_token[MAX_INPUTLINE]; int com_argc; const char **com_argv; int com_selffd = -1; gamemode_t gamemode; const char *gamename; const char *gamenetworkfiltername; // same as gamename currently but with _ in place of spaces so that "getservers" packets parse correctly (this also means the const char *gamedirname1; const char *gamedirname2; const char *gamescreenshotname; const char *gameuserdirname; char com_modname[MAX_OSPATH] = ""; /* ============================================================================ BYTE ORDER FUNCTIONS ============================================================================ */ float BuffBigFloat (const unsigned char *buffer) { union { float f; unsigned int i; } u; u.i = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; return u.f; } int BuffBigLong (const unsigned char *buffer) { return (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; } short BuffBigShort (const unsigned char *buffer) { return (buffer[0] << 8) | buffer[1]; } float BuffLittleFloat (const unsigned char *buffer) { union { float f; unsigned int i; } u; u.i = (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; return u.f; } int BuffLittleLong (const unsigned char *buffer) { return (buffer[3] << 24) | (buffer[2] << 16) | (buffer[1] << 8) | buffer[0]; } short BuffLittleShort (const unsigned char *buffer) { return (buffer[1] << 8) | buffer[0]; } void StoreBigLong (unsigned char *buffer, unsigned int i) { buffer[0] = (i >> 24) & 0xFF; buffer[1] = (i >> 16) & 0xFF; buffer[2] = (i >> 8) & 0xFF; buffer[3] = i & 0xFF; } void StoreBigShort (unsigned char *buffer, unsigned short i) { buffer[0] = (i >> 8) & 0xFF; buffer[1] = i & 0xFF; } void StoreLittleLong (unsigned char *buffer, unsigned int i) { buffer[0] = i & 0xFF; buffer[1] = (i >> 8) & 0xFF; buffer[2] = (i >> 16) & 0xFF; buffer[3] = (i >> 24) & 0xFF; } void StoreLittleShort (unsigned char *buffer, unsigned short i) { buffer[0] = i & 0xFF; buffer[1] = (i >> 8) & 0xFF; } /* ============================================================================ CRC FUNCTIONS ============================================================================ */ // this is a 16 bit, non-reflected CRC using the polynomial 0x1021 // and the initial and final xor values shown below... in other words, the // CCITT standard CRC used by XMODEM #define CRC_INIT_VALUE 0xffff #define CRC_XOR_VALUE 0x0000 static unsigned short crctable[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; unsigned short CRC_Block(const unsigned char *data, size_t size) { unsigned short crc = CRC_INIT_VALUE; while (size--) crc = (crc << 8) ^ crctable[(crc >> 8) ^ (*data++)]; return crc ^ CRC_XOR_VALUE; } unsigned short CRC_Block_CaseInsensitive(const unsigned char *data, size_t size) { unsigned short crc = CRC_INIT_VALUE; while (size--) crc = (crc << 8) ^ crctable[(crc >> 8) ^ (tolower(*data++))]; return crc ^ CRC_XOR_VALUE; } // QuakeWorld static unsigned char chktbl[1024 + 4] = { 0x78,0xd2,0x94,0xe3,0x41,0xec,0xd6,0xd5,0xcb,0xfc,0xdb,0x8a,0x4b,0xcc,0x85,0x01, 0x23,0xd2,0xe5,0xf2,0x29,0xa7,0x45,0x94,0x4a,0x62,0xe3,0xa5,0x6f,0x3f,0xe1,0x7a, 0x64,0xed,0x5c,0x99,0x29,0x87,0xa8,0x78,0x59,0x0d,0xaa,0x0f,0x25,0x0a,0x5c,0x58, 0xfb,0x00,0xa7,0xa8,0x8a,0x1d,0x86,0x80,0xc5,0x1f,0xd2,0x28,0x69,0x71,0x58,0xc3, 0x51,0x90,0xe1,0xf8,0x6a,0xf3,0x8f,0xb0,0x68,0xdf,0x95,0x40,0x5c,0xe4,0x24,0x6b, 0x29,0x19,0x71,0x3f,0x42,0x63,0x6c,0x48,0xe7,0xad,0xa8,0x4b,0x91,0x8f,0x42,0x36, 0x34,0xe7,0x32,0x55,0x59,0x2d,0x36,0x38,0x38,0x59,0x9b,0x08,0x16,0x4d,0x8d,0xf8, 0x0a,0xa4,0x52,0x01,0xbb,0x52,0xa9,0xfd,0x40,0x18,0x97,0x37,0xff,0xc9,0x82,0x27, 0xb2,0x64,0x60,0xce,0x00,0xd9,0x04,0xf0,0x9e,0x99,0xbd,0xce,0x8f,0x90,0x4a,0xdd, 0xe1,0xec,0x19,0x14,0xb1,0xfb,0xca,0x1e,0x98,0x0f,0xd4,0xcb,0x80,0xd6,0x05,0x63, 0xfd,0xa0,0x74,0xa6,0x86,0xf6,0x19,0x98,0x76,0x27,0x68,0xf7,0xe9,0x09,0x9a,0xf2, 0x2e,0x42,0xe1,0xbe,0x64,0x48,0x2a,0x74,0x30,0xbb,0x07,0xcc,0x1f,0xd4,0x91,0x9d, 0xac,0x55,0x53,0x25,0xb9,0x64,0xf7,0x58,0x4c,0x34,0x16,0xbc,0xf6,0x12,0x2b,0x65, 0x68,0x25,0x2e,0x29,0x1f,0xbb,0xb9,0xee,0x6d,0x0c,0x8e,0xbb,0xd2,0x5f,0x1d,0x8f, 0xc1,0x39,0xf9,0x8d,0xc0,0x39,0x75,0xcf,0x25,0x17,0xbe,0x96,0xaf,0x98,0x9f,0x5f, 0x65,0x15,0xc4,0x62,0xf8,0x55,0xfc,0xab,0x54,0xcf,0xdc,0x14,0x06,0xc8,0xfc,0x42, 0xd3,0xf0,0xad,0x10,0x08,0xcd,0xd4,0x11,0xbb,0xca,0x67,0xc6,0x48,0x5f,0x9d,0x59, 0xe3,0xe8,0x53,0x67,0x27,0x2d,0x34,0x9e,0x9e,0x24,0x29,0xdb,0x69,0x99,0x86,0xf9, 0x20,0xb5,0xbb,0x5b,0xb0,0xf9,0xc3,0x67,0xad,0x1c,0x9c,0xf7,0xcc,0xef,0xce,0x69, 0xe0,0x26,0x8f,0x79,0xbd,0xca,0x10,0x17,0xda,0xa9,0x88,0x57,0x9b,0x15,0x24,0xba, 0x84,0xd0,0xeb,0x4d,0x14,0xf5,0xfc,0xe6,0x51,0x6c,0x6f,0x64,0x6b,0x73,0xec,0x85, 0xf1,0x6f,0xe1,0x67,0x25,0x10,0x77,0x32,0x9e,0x85,0x6e,0x69,0xb1,0x83,0x00,0xe4, 0x13,0xa4,0x45,0x34,0x3b,0x40,0xff,0x41,0x82,0x89,0x79,0x57,0xfd,0xd2,0x8e,0xe8, 0xfc,0x1d,0x19,0x21,0x12,0x00,0xd7,0x66,0xe5,0xc7,0x10,0x1d,0xcb,0x75,0xe8,0xfa, 0xb6,0xee,0x7b,0x2f,0x1a,0x25,0x24,0xb9,0x9f,0x1d,0x78,0xfb,0x84,0xd0,0x17,0x05, 0x71,0xb3,0xc8,0x18,0xff,0x62,0xee,0xed,0x53,0xab,0x78,0xd3,0x65,0x2d,0xbb,0xc7, 0xc1,0xe7,0x70,0xa2,0x43,0x2c,0x7c,0xc7,0x16,0x04,0xd2,0x45,0xd5,0x6b,0x6c,0x7a, 0x5e,0xa1,0x50,0x2e,0x31,0x5b,0xcc,0xe8,0x65,0x8b,0x16,0x85,0xbf,0x82,0x83,0xfb, 0xde,0x9f,0x36,0x48,0x32,0x79,0xd6,0x9b,0xfb,0x52,0x45,0xbf,0x43,0xf7,0x0b,0x0b, 0x19,0x19,0x31,0xc3,0x85,0xec,0x1d,0x8c,0x20,0xf0,0x3a,0xfa,0x80,0x4d,0x2c,0x7d, 0xac,0x60,0x09,0xc0,0x40,0xee,0xb9,0xeb,0x13,0x5b,0xe8,0x2b,0xb1,0x20,0xf0,0xce, 0x4c,0xbd,0xc6,0x04,0x86,0x70,0xc6,0x33,0xc3,0x15,0x0f,0x65,0x19,0xfd,0xc2,0xd3, // map checksum goes here 0x00,0x00,0x00,0x00 }; // QuakeWorld unsigned char COM_BlockSequenceCRCByteQW(unsigned char *base, int length, int sequence) { unsigned char *p; unsigned char chkb[60 + 4]; p = chktbl + (sequence % (sizeof(chktbl) - 8)); if (length > 60) length = 60; memcpy(chkb, base, length); chkb[length] = (sequence & 0xff) ^ p[0]; chkb[length+1] = p[1]; chkb[length+2] = ((sequence>>8) & 0xff) ^ p[2]; chkb[length+3] = p[3]; return CRC_Block(chkb, length + 4) & 0xff; } /* ============================================================================== MESSAGE IO FUNCTIONS Handles byte ordering and avoids alignment errors ============================================================================== */ // // writing functions // void MSG_WriteChar (sizebuf_t *sb, int c) { unsigned char *buf; buf = SZ_GetSpace (sb, 1); buf[0] = c; } void MSG_WriteByte (sizebuf_t *sb, int c) { unsigned char *buf; buf = SZ_GetSpace (sb, 1); buf[0] = c; } void MSG_WriteShort (sizebuf_t *sb, int c) { unsigned char *buf; buf = SZ_GetSpace (sb, 2); buf[0] = c&0xff; buf[1] = c>>8; } void MSG_WriteLong (sizebuf_t *sb, int c) { unsigned char *buf; buf = SZ_GetSpace (sb, 4); buf[0] = c&0xff; buf[1] = (c>>8)&0xff; buf[2] = (c>>16)&0xff; buf[3] = c>>24; } void MSG_WriteFloat (sizebuf_t *sb, float f) { union { float f; int l; } dat; dat.f = f; dat.l = LittleLong (dat.l); SZ_Write (sb, (unsigned char *)&dat.l, 4); } void MSG_WriteString (sizebuf_t *sb, const char *s) { if (!s || !*s) MSG_WriteChar (sb, 0); else SZ_Write (sb, (unsigned char *)s, (int)strlen(s)+1); } void MSG_WriteUnterminatedString (sizebuf_t *sb, const char *s) { if (s && *s) SZ_Write (sb, (unsigned char *)s, (int)strlen(s)); } void MSG_WriteCoord13i (sizebuf_t *sb, float f) { if (f >= 0) MSG_WriteShort (sb, (int)(f * 8.0 + 0.5)); else MSG_WriteShort (sb, (int)(f * 8.0 - 0.5)); } void MSG_WriteCoord16i (sizebuf_t *sb, float f) { if (f >= 0) MSG_WriteShort (sb, (int)(f + 0.5)); else MSG_WriteShort (sb, (int)(f - 0.5)); } void MSG_WriteCoord32f (sizebuf_t *sb, float f) { MSG_WriteFloat (sb, f); } void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol) { if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) MSG_WriteCoord13i (sb, f); else if (protocol == PROTOCOL_DARKPLACES1) MSG_WriteCoord32f (sb, f); else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) MSG_WriteCoord16i (sb, f); else MSG_WriteCoord32f (sb, f); } void MSG_WriteVector (sizebuf_t *sb, const vec3_t v, protocolversion_t protocol) { MSG_WriteCoord (sb, v[0], protocol); MSG_WriteCoord (sb, v[1], protocol); MSG_WriteCoord (sb, v[2], protocol); } // LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem void MSG_WriteAngle8i (sizebuf_t *sb, float f) { if (f >= 0) MSG_WriteByte (sb, (int)(f*(256.0/360.0) + 0.5) & 255); else MSG_WriteByte (sb, (int)(f*(256.0/360.0) - 0.5) & 255); } void MSG_WriteAngle16i (sizebuf_t *sb, float f) { if (f >= 0) MSG_WriteShort (sb, (int)(f*(65536.0/360.0) + 0.5) & 65535); else MSG_WriteShort (sb, (int)(f*(65536.0/360.0) - 0.5) & 65535); } void MSG_WriteAngle32f (sizebuf_t *sb, float f) { MSG_WriteFloat (sb, f); } void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol) { if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) MSG_WriteAngle8i (sb, f); else MSG_WriteAngle16i (sb, f); } // // reading functions // void MSG_InitReadBuffer (sizebuf_t *buf, unsigned char *data, int size) { memset(buf, 0, sizeof(*buf)); buf->data = data; buf->maxsize = buf->cursize = size; MSG_BeginReading(buf); } void MSG_BeginReading(sizebuf_t *sb) { sb->readcount = 0; sb->badread = false; } int MSG_ReadLittleShort(sizebuf_t *sb) { if (sb->readcount+2 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 2; return (short)(sb->data[sb->readcount-2] | (sb->data[sb->readcount-1]<<8)); } int MSG_ReadBigShort (sizebuf_t *sb) { if (sb->readcount+2 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 2; return (short)((sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1]); } int MSG_ReadLittleLong (sizebuf_t *sb) { if (sb->readcount+4 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 4; return sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24); } int MSG_ReadBigLong (sizebuf_t *sb) { if (sb->readcount+4 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 4; return (sb->data[sb->readcount-4]<<24) + (sb->data[sb->readcount-3]<<16) + (sb->data[sb->readcount-2]<<8) + sb->data[sb->readcount-1]; } float MSG_ReadLittleFloat (sizebuf_t *sb) { union { float f; int l; } dat; if (sb->readcount+4 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 4; dat.l = sb->data[sb->readcount-4] | (sb->data[sb->readcount-3]<<8) | (sb->data[sb->readcount-2]<<16) | (sb->data[sb->readcount-1]<<24); return dat.f; } float MSG_ReadBigFloat (sizebuf_t *sb) { union { float f; int l; } dat; if (sb->readcount+4 > sb->cursize) { sb->badread = true; return -1; } sb->readcount += 4; dat.l = (sb->data[sb->readcount-4]<<24) | (sb->data[sb->readcount-3]<<16) | (sb->data[sb->readcount-2]<<8) | sb->data[sb->readcount-1]; return dat.f; } char *MSG_ReadString (sizebuf_t *sb, char *string, size_t maxstring) { int c; size_t l = 0; // read string into sbfer, but only store as many characters as will fit while ((c = MSG_ReadByte(sb)) > 0) if (l < maxstring - 1) string[l++] = c; string[l] = 0; return string; } int MSG_ReadBytes (sizebuf_t *sb, int numbytes, unsigned char *out) { int l, c; for (l = 0;l < numbytes && (c = MSG_ReadByte(sb)) != -1;l++) out[l] = c; return l; } float MSG_ReadCoord13i (sizebuf_t *sb) { return MSG_ReadLittleShort(sb) * (1.0/8.0); } float MSG_ReadCoord16i (sizebuf_t *sb) { return (signed short) MSG_ReadLittleShort(sb); } float MSG_ReadCoord32f (sizebuf_t *sb) { return MSG_ReadLittleFloat(sb); } float MSG_ReadCoord (sizebuf_t *sb, protocolversion_t protocol) { if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_QUAKEWORLD) return MSG_ReadCoord13i(sb); else if (protocol == PROTOCOL_DARKPLACES1) return MSG_ReadCoord32f(sb); else if (protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4) return MSG_ReadCoord16i(sb); else return MSG_ReadCoord32f(sb); } void MSG_ReadVector (sizebuf_t *sb, vec3_t v, protocolversion_t protocol) { v[0] = MSG_ReadCoord(sb, protocol); v[1] = MSG_ReadCoord(sb, protocol); v[2] = MSG_ReadCoord(sb, protocol); } // LordHavoc: round to nearest value, rather than rounding toward zero, fixes crosshair problem float MSG_ReadAngle8i (sizebuf_t *sb) { return (signed char) MSG_ReadByte (sb) * (360.0/256.0); } float MSG_ReadAngle16i (sizebuf_t *sb) { return (signed short)MSG_ReadShort (sb) * (360.0/65536.0); } float MSG_ReadAngle32f (sizebuf_t *sb) { return MSG_ReadFloat (sb); } float MSG_ReadAngle (sizebuf_t *sb, protocolversion_t protocol) { if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_NEHAHRABJP || protocol == PROTOCOL_NEHAHRABJP2 || protocol == PROTOCOL_NEHAHRABJP3 || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD) return MSG_ReadAngle8i (sb); else return MSG_ReadAngle16i (sb); } //=========================================================================== void SZ_Clear (sizebuf_t *buf) { buf->cursize = 0; } unsigned char *SZ_GetSpace (sizebuf_t *buf, int length) { unsigned char *data; if (buf->cursize + length > buf->maxsize) { if (!buf->allowoverflow) Host_Error ("SZ_GetSpace: overflow without allowoverflow set"); if (length > buf->maxsize) Host_Error ("SZ_GetSpace: %i is > full buffer size", length); buf->overflowed = true; Con_Print("SZ_GetSpace: overflow\n"); SZ_Clear (buf); } data = buf->data + buf->cursize; buf->cursize += length; return data; } void SZ_Write (sizebuf_t *buf, const unsigned char *data, int length) { memcpy (SZ_GetSpace(buf,length),data,length); } // LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my // attention, it has been eradicated from here, its only (former) use in // all of darkplaces. static const char *hexchar = "0123456789ABCDEF"; void Com_HexDumpToConsole(const unsigned char *data, int size) { int i, j, n; char text[1024]; char *cur, *flushpointer; const unsigned char *d; cur = text; flushpointer = text + 512; for (i = 0;i < size;) { n = 16; if (n > size - i) n = size - i; d = data + i; // print offset *cur++ = hexchar[(i >> 12) & 15]; *cur++ = hexchar[(i >> 8) & 15]; *cur++ = hexchar[(i >> 4) & 15]; *cur++ = hexchar[(i >> 0) & 15]; *cur++ = ':'; // print hex for (j = 0;j < 16;j++) { if (j < n) { *cur++ = hexchar[(d[j] >> 4) & 15]; *cur++ = hexchar[(d[j] >> 0) & 15]; } else { *cur++ = ' '; *cur++ = ' '; } if ((j & 3) == 3) *cur++ = ' '; } // print text for (j = 0;j < 16;j++) { if (j < n) { // color change prefix character has to be treated specially if (d[j] == STRING_COLOR_TAG) { *cur++ = STRING_COLOR_TAG; *cur++ = STRING_COLOR_TAG; } else if (d[j] >= (unsigned char) ' ') *cur++ = d[j]; else *cur++ = '.'; } else *cur++ = ' '; } *cur++ = '\n'; i += n; if (cur >= flushpointer || i >= size) { *cur++ = 0; Con_Print(text); cur = text; } } } void SZ_HexDumpToConsole(const sizebuf_t *buf) { Com_HexDumpToConsole(buf->data, buf->cursize); } //============================================================================ /* ============== COM_Wordwrap Word wraps a string. The wordWidth function is guaranteed to be called exactly once for each word in the string, so it may be stateful, no idea what that would be good for any more. At the beginning of the string, it will be called for the char 0 to initialize a clean state, and then once with the string " " (a space) so the routine knows how long a space is. In case no single character fits into the given width, the wordWidth function must return the width of exactly one character. Wrapped lines get the isContinuation flag set and are continuationWidth less wide. The sum of the return values of the processLine function will be returned. ============== */ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, float maxWidth, COM_WordWidthFunc_t wordWidth, void *passthroughCW, COM_LineProcessorFunc processLine, void *passthroughPL) { // Logic is as follows: // // For each word or whitespace: // Newline found? Output current line, advance to next line. This is not a continuation. Continue. // Space found? Always add it to the current line, no matter if it fits. // Word found? Check if current line + current word fits. // If it fits, append it. Continue. // If it doesn't fit, output current line, advance to next line. Append the word. This is a continuation. Continue. qboolean isContinuation = false; float spaceWidth; const char *startOfLine = string; const char *cursor = string; const char *end = string + length; float spaceUsedInLine = 0; float spaceUsedForWord; int result = 0; size_t wordLen; size_t dummy; dummy = 0; wordWidth(passthroughCW, NULL, &dummy, -1); dummy = 1; spaceWidth = wordWidth(passthroughCW, " ", &dummy, -1); for(;;) { char ch = (cursor < end) ? *cursor : 0; switch(ch) { case 0: // end of string result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); goto out; case '\n': // end of line result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); isContinuation = false; ++cursor; startOfLine = cursor; break; case ' ': // space ++cursor; spaceUsedInLine += spaceWidth; break; default: // word wordLen = 1; while(cursor + wordLen < end) { switch(cursor[wordLen]) { case 0: case '\n': case ' ': goto out_inner; default: ++wordLen; break; } } out_inner: spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line if(wordLen < 1) // cannot happen according to current spec of wordWidth { wordLen = 1; spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself } if(spaceUsedInLine + spaceUsedForWord <= maxWidth || cursor == startOfLine) { // we can simply append it cursor += wordLen; spaceUsedInLine += spaceUsedForWord; } else { // output current line result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); isContinuation = true; startOfLine = cursor; cursor += wordLen; spaceUsedInLine = continuationWidth + spaceUsedForWord; } } } out: return result; /* qboolean isContinuation = false; float currentWordSpace = 0; const char *currentWord = 0; float minReserve = 0; float spaceUsedInLine = 0; const char *currentLine = 0; const char *currentLineEnd = 0; float currentLineFinalWhitespace = 0; const char *p; int result = 0; minReserve = charWidth(passthroughCW, 0); minReserve += charWidth(passthroughCW, ' '); if(maxWidth < continuationWidth + minReserve) maxWidth = continuationWidth + minReserve; charWidth(passthroughCW, 0); for(p = string; p < string + length; ++p) { char c = *p; float w = charWidth(passthroughCW, c); if(!currentWord) { currentWord = p; currentWordSpace = 0; } if(!currentLine) { currentLine = p; spaceUsedInLine = isContinuation ? continuationWidth : 0; currentLineEnd = 0; } if(c == ' ') { // 1. I can add the word AND a space - then just append it. if(spaceUsedInLine + currentWordSpace + w <= maxWidth) { currentLineEnd = p; // note: space not included here currentLineFinalWhitespace = w; spaceUsedInLine += currentWordSpace + w; } // 2. I can just add the word - then append it, output current line and go to next one. else if(spaceUsedInLine + currentWordSpace <= maxWidth) { result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); currentLine = 0; isContinuation = true; } // 3. Otherwise, output current line and go to next one, where I can add the word. else if(continuationWidth + currentWordSpace + w <= maxWidth) { if(currentLineEnd) result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); currentLine = currentWord; spaceUsedInLine = continuationWidth + currentWordSpace + w; currentLineEnd = p; currentLineFinalWhitespace = w; isContinuation = true; } // 4. We can't even do that? Then output both current and next word as new lines. else { if(currentLineEnd) { result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); isContinuation = true; } result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); currentLine = 0; isContinuation = true; } currentWord = 0; } else if(c == '\n') { // 1. I can add the word - then do it. if(spaceUsedInLine + currentWordSpace <= maxWidth) { result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); } // 2. Otherwise, output current line, next one and make tabula rasa. else { if(currentLineEnd) { processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); isContinuation = true; } result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); } currentWord = 0; currentLine = 0; isContinuation = false; } else { currentWordSpace += w; if( spaceUsedInLine + currentWordSpace > maxWidth // can't join this line... && continuationWidth + currentWordSpace > maxWidth // can't join any other line... ) { // this word cannot join ANY line... // so output the current line... if(currentLineEnd) { result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); isContinuation = true; } // then this word's beginning... if(isContinuation) { // it may not fit, but we know we have to split it into maxWidth - continuationWidth pieces float pieceWidth = maxWidth - continuationWidth; const char *pos = currentWord; currentWordSpace = 0; // reset the char width function to a state where no kerning occurs (start of word) charWidth(passthroughCW, ' '); while(pos <= p) { float w = charWidth(passthroughCW, *pos); if(currentWordSpace + w > pieceWidth) // this piece won't fit any more { // print everything until it result += processLine(passthroughPL, currentWord, pos - currentWord, currentWordSpace, true); // go to here currentWord = pos; currentWordSpace = 0; } currentWordSpace += w; ++pos; } // now we have a currentWord that fits... set up its next line // currentWordSpace has been set // currentWord has been set spaceUsedInLine = continuationWidth; currentLine = currentWord; currentLineEnd = 0; isContinuation = true; } else { // we have a guarantee that it will fix (see if clause) result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace - w, isContinuation); // and use the rest of this word as new start of a line currentWordSpace = w; currentWord = p; spaceUsedInLine = continuationWidth; currentLine = p; currentLineEnd = 0; isContinuation = true; } } } } if(!currentWord) { currentWord = p; currentWordSpace = 0; } if(currentLine) // Same procedure as \n { // Can I append the current word? if(spaceUsedInLine + currentWordSpace <= maxWidth) result += processLine(passthroughPL, currentLine, p - currentLine, spaceUsedInLine + currentWordSpace, isContinuation); else { if(currentLineEnd) { result += processLine(passthroughPL, currentLine, currentLineEnd - currentLine, spaceUsedInLine - currentLineFinalWhitespace, isContinuation); isContinuation = true; } result += processLine(passthroughPL, currentWord, p - currentWord, currentWordSpace, isContinuation); } } return result; */ } /* ============== COM_ParseToken_Simple Parse a token out of a string ============== */ int COM_ParseToken_Simple(const char **datapointer, qboolean returnnewline, qboolean parsebackslash, qboolean parsecomments) { int len; int c; const char *data = *datapointer; len = 0; com_token[0] = 0; if (!data) { *datapointer = NULL; return false; } // skip whitespace skipwhite: // line endings: // UNIX: \n // Mac: \r // Windows: \r\n for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) { if (*data == 0) { // end of file *datapointer = NULL; return false; } } // handle Windows line ending if (data[0] == '\r' && data[1] == '\n') data++; if (parsecomments && data[0] == '/' && data[1] == '/') { // comment while (*data && *data != '\n' && *data != '\r') data++; goto skipwhite; } else if (parsecomments && data[0] == '/' && data[1] == '*') { // comment data++; while (*data && (data[0] != '*' || data[1] != '/')) data++; if (*data) data++; if (*data) data++; goto skipwhite; } else if (*data == '\"') { // quoted string for (data++;*data && *data != '\"';data++) { c = *data; if (*data == '\\' && parsebackslash) { data++; c = *data; if (c == 'n') c = '\n'; else if (c == 't') c = '\t'; } if (len < (int)sizeof(com_token) - 1) com_token[len++] = c; } com_token[len] = 0; if (*data == '\"') data++; *datapointer = data; return true; } else if (*data == '\r') { // translate Mac line ending to UNIX com_token[len++] = '\n';data++; com_token[len] = 0; *datapointer = data; return true; } else if (*data == '\n') { // single character com_token[len++] = *data++; com_token[len] = 0; *datapointer = data; return true; } else { // regular word for (;!ISWHITESPACE(*data);data++) if (len < (int)sizeof(com_token) - 1) com_token[len++] = *data; com_token[len] = 0; *datapointer = data; return true; } } /* ============== COM_ParseToken_QuakeC Parse a token out of a string ============== */ int COM_ParseToken_QuakeC(const char **datapointer, qboolean returnnewline) { int len; int c; const char *data = *datapointer; len = 0; com_token[0] = 0; if (!data) { *datapointer = NULL; return false; } // skip whitespace skipwhite: // line endings: // UNIX: \n // Mac: \r // Windows: \r\n for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) { if (*data == 0) { // end of file *datapointer = NULL; return false; } } // handle Windows line ending if (data[0] == '\r' && data[1] == '\n') data++; if (data[0] == '/' && data[1] == '/') { // comment while (*data && *data != '\n' && *data != '\r') data++; goto skipwhite; } else if (data[0] == '/' && data[1] == '*') { // comment data++; while (*data && (data[0] != '*' || data[1] != '/')) data++; if (*data) data++; if (*data) data++; goto skipwhite; } else if (*data == '\"' || *data == '\'') { // quoted string char quote = *data; for (data++;*data && *data != quote;data++) { c = *data; if (*data == '\\') { data++; c = *data; if (c == 'n') c = '\n'; else if (c == 't') c = '\t'; } if (len < (int)sizeof(com_token) - 1) com_token[len++] = c; } com_token[len] = 0; if (*data == quote) data++; *datapointer = data; return true; } else if (*data == '\r') { // translate Mac line ending to UNIX com_token[len++] = '\n';data++; com_token[len] = 0; *datapointer = data; return true; } else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') { // single character com_token[len++] = *data++; com_token[len] = 0; *datapointer = data; return true; } else { // regular word for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) if (len < (int)sizeof(com_token) - 1) com_token[len++] = *data; com_token[len] = 0; *datapointer = data; return true; } } /* ============== COM_ParseToken_VM_Tokenize Parse a token out of a string ============== */ int COM_ParseToken_VM_Tokenize(const char **datapointer, qboolean returnnewline) { int len; int c; const char *data = *datapointer; len = 0; com_token[0] = 0; if (!data) { *datapointer = NULL; return false; } // skip whitespace skipwhite: // line endings: // UNIX: \n // Mac: \r // Windows: \r\n for (;ISWHITESPACE(*data) && ((*data != '\n' && *data != '\r') || !returnnewline);data++) { if (*data == 0) { // end of file *datapointer = NULL; return false; } } // handle Windows line ending if (data[0] == '\r' && data[1] == '\n') data++; if (data[0] == '/' && data[1] == '/') { // comment while (*data && *data != '\n' && *data != '\r') data++; goto skipwhite; } else if (data[0] == '/' && data[1] == '*') { // comment data++; while (*data && (data[0] != '*' || data[1] != '/')) data++; if (*data) data++; if (*data) data++; goto skipwhite; } else if (*data == '\"' || *data == '\'') { char quote = *data; // quoted string for (data++;*data && *data != quote;data++) { c = *data; if (*data == '\\') { data++; c = *data; if (c == 'n') c = '\n'; else if (c == 't') c = '\t'; } if (len < (int)sizeof(com_token) - 1) com_token[len++] = c; } com_token[len] = 0; if (*data == quote) data++; *datapointer = data; return true; } else if (*data == '\r') { // translate Mac line ending to UNIX com_token[len++] = '\n';data++; com_token[len] = 0; *datapointer = data; return true; } else if (*data == '\n' || *data == '{' || *data == '}' || *data == ')' || *data == '(' || *data == ']' || *data == '[' || *data == ':' || *data == ',' || *data == ';') { // single character com_token[len++] = *data++; com_token[len] = 0; *datapointer = data; return true; } else { // regular word for (;!ISWHITESPACE(*data) && *data != '{' && *data != '}' && *data != ')' && *data != '(' && *data != ']' && *data != '[' && *data != ':' && *data != ',' && *data != ';';data++) if (len < (int)sizeof(com_token) - 1) com_token[len++] = *data; com_token[len] = 0; *datapointer = data; return true; } } /* ============== COM_ParseToken_Console Parse a token out of a string, behaving like the qwcl console ============== */ int COM_ParseToken_Console(const char **datapointer) { int len; const char *data = *datapointer; len = 0; com_token[0] = 0; if (!data) { *datapointer = NULL; return false; } // skip whitespace skipwhite: for (;ISWHITESPACE(*data);data++) { if (*data == 0) { // end of file *datapointer = NULL; return false; } } if (*data == '/' && data[1] == '/') { // comment while (*data && *data != '\n' && *data != '\r') data++; goto skipwhite; } else if (*data == '\"') { // quoted string for (data++;*data && *data != '\"';data++) { // allow escaped " and \ case if (*data == '\\' && (data[1] == '\"' || data[1] == '\\')) data++; if (len < (int)sizeof(com_token) - 1) com_token[len++] = *data; } com_token[len] = 0; if (*data == '\"') data++; *datapointer = data; } else { // regular word for (;!ISWHITESPACE(*data);data++) if (len < (int)sizeof(com_token) - 1) com_token[len++] = *data; com_token[len] = 0; *datapointer = data; } return true; } /* ================ COM_CheckParm Returns the position (1 to argc-1) in the program's argument list where the given parameter apears, or 0 if not present ================ */ int COM_CheckParm (const char *parm) { int i; for (i=1 ; i= 0 && gamemode != gamemode_info[index].mode) COM_SetGameType(index); } static void COM_SetGameType(int index) { static char gamenetworkfilternamebuffer[64]; int i, t; if (index < 0 || index >= (int)(sizeof (gamemode_info) / sizeof (gamemode_info[0]))) index = 0; gamemode = gamemode_info[index].mode; gamename = gamemode_info[index].gamename; gamenetworkfiltername = gamemode_info[index].gamenetworkfiltername; gamedirname1 = gamemode_info[index].gamedirname1; gamedirname2 = gamemode_info[index].gamedirname2; gamescreenshotname = gamemode_info[index].gamescreenshotname; gameuserdirname = gamemode_info[index].gameuserdirname; if (gamemode == com_startupgamemode) { if((t = COM_CheckParm("-customgamename")) && t + 1 < com_argc) gamename = gamenetworkfiltername = com_argv[t+1]; if((t = COM_CheckParm("-customgamenetworkfiltername")) && t + 1 < com_argc) gamenetworkfiltername = com_argv[t+1]; if((t = COM_CheckParm("-customgamedirname1")) && t + 1 < com_argc) gamedirname1 = com_argv[t+1]; if((t = COM_CheckParm("-customgamedirname2")) && t + 1 < com_argc) gamedirname2 = *com_argv[t+1] ? com_argv[t+1] : NULL; if((t = COM_CheckParm("-customgamescreenshotname")) && t + 1 < com_argc) gamescreenshotname = com_argv[t+1]; if((t = COM_CheckParm("-customgameuserdirname")) && t + 1 < com_argc) gameuserdirname = com_argv[t+1]; } if (gamedirname2 && gamedirname2[0]) Con_Printf("Game is %s using base gamedirs %s %s", gamename, gamedirname1, gamedirname2); else Con_Printf("Game is %s using base gamedir %s", gamename, gamedirname1); for (i = 0;i < fs_numgamedirs;i++) { if (i == 0) Con_Printf(", with mod gamedirs"); Con_Printf(" %s", fs_gamedirs[i]); } Con_Printf("\n"); if (strchr(gamenetworkfiltername, ' ')) { char *s; // if there are spaces in the game's network filter name it would // cause parse errors in getservers in dpmaster, so we need to replace // them with _ characters strlcpy(gamenetworkfilternamebuffer, gamenetworkfiltername, sizeof(gamenetworkfilternamebuffer)); while ((s = strchr(gamenetworkfilternamebuffer, ' ')) != NULL) *s = '_'; gamenetworkfiltername = gamenetworkfilternamebuffer; } Con_Printf("gamename for server filtering: %s\n", gamenetworkfiltername); } /* ================ COM_Init ================ */ void COM_Init_Commands (void) { int i, j, n; char com_cmdline[MAX_INPUTLINE]; Cvar_RegisterVariable (®istered); Cvar_RegisterVariable (&cmdline); // reconstitute the command line for the cmdline externally visible cvar n = 0; for (j = 0;(j < MAX_NUM_ARGVS) && (j < com_argc);j++) { i = 0; if (strstr(com_argv[j], " ")) { // arg contains whitespace, store quotes around it // This condition checks whether we can allow to put // in two quote characters. if (n >= ((int)sizeof(com_cmdline) - 2)) break; com_cmdline[n++] = '\"'; // This condition checks whether we can allow one // more character and a quote character. while ((n < ((int)sizeof(com_cmdline) - 2)) && com_argv[j][i]) // FIXME: Doesn't quote special characters. com_cmdline[n++] = com_argv[j][i++]; com_cmdline[n++] = '\"'; } else { // This condition checks whether we can allow one // more character. while ((n < ((int)sizeof(com_cmdline) - 1)) && com_argv[j][i]) com_cmdline[n++] = com_argv[j][i++]; } if (n < ((int)sizeof(com_cmdline) - 1)) com_cmdline[n++] = ' '; else break; } com_cmdline[n] = 0; Cvar_Set ("cmdline", com_cmdline); } /* ============ va varargs print into provided buffer, returns buffer (so that it can be called in-line, unlike dpsnprintf) ============ */ char *va(char *buf, size_t buflen, const char *format, ...) { va_list argptr; va_start (argptr, format); dpvsnprintf (buf, buflen, format,argptr); va_end (argptr); return buf; } //====================================== // snprintf and vsnprintf are NOT portable. Use their DP counterparts instead #undef snprintf #undef vsnprintf #ifdef WIN32 # define snprintf _snprintf # define vsnprintf _vsnprintf #endif int dpsnprintf (char *buffer, size_t buffersize, const char *format, ...) { va_list args; int result; va_start (args, format); result = dpvsnprintf (buffer, buffersize, format, args); va_end (args); return result; } int dpvsnprintf (char *buffer, size_t buffersize, const char *format, va_list args) { int result; #if _MSC_VER >= 1400 result = _vsnprintf_s (buffer, buffersize, _TRUNCATE, format, args); #else result = vsnprintf (buffer, buffersize, format, args); #endif if (result < 0 || (size_t)result >= buffersize) { buffer[buffersize - 1] = '\0'; return -1; } return result; } //====================================== void COM_ToLowerString (const char *in, char *out, size_t size_out) { if (size_out == 0) return; if(utf8_enable.integer) { *out = 0; while(*in && size_out > 1) { int n; Uchar ch = u8_getchar_utf8_enabled(in, &in); ch = u8_tolower(ch); n = u8_fromchar(ch, out, size_out); if(n <= 0) break; out += n; size_out -= n; } return; } while (*in && size_out > 1) { if (*in >= 'A' && *in <= 'Z') *out++ = *in++ + 'a' - 'A'; else *out++ = *in++; size_out--; } *out = '\0'; } void COM_ToUpperString (const char *in, char *out, size_t size_out) { if (size_out == 0) return; if(utf8_enable.integer) { *out = 0; while(*in && size_out > 1) { int n; Uchar ch = u8_getchar_utf8_enabled(in, &in); ch = u8_toupper(ch); n = u8_fromchar(ch, out, size_out); if(n <= 0) break; out += n; size_out -= n; } return; } while (*in && size_out > 1) { if (*in >= 'a' && *in <= 'z') *out++ = *in++ + 'A' - 'a'; else *out++ = *in++; size_out--; } *out = '\0'; } int COM_StringBeginsWith(const char *s, const char *match) { for (;*s && *match;s++, match++) if (*s != *match) return false; return true; } int COM_ReadAndTokenizeLine(const char **text, char **argv, int maxargc, char *tokenbuf, int tokenbufsize, const char *commentprefix) { int argc, commentprefixlength; char *tokenbufend; const char *l; argc = 0; tokenbufend = tokenbuf + tokenbufsize; l = *text; commentprefixlength = 0; if (commentprefix) commentprefixlength = (int)strlen(commentprefix); while (*l && *l != '\n' && *l != '\r') { if (!ISWHITESPACE(*l)) { if (commentprefixlength && !strncmp(l, commentprefix, commentprefixlength)) { while (*l && *l != '\n' && *l != '\r') l++; break; } if (argc >= maxargc) return -1; argv[argc++] = tokenbuf; if (*l == '"') { l++; while (*l && *l != '"') { if (tokenbuf >= tokenbufend) return -1; *tokenbuf++ = *l++; } if (*l == '"') l++; } else { while (!ISWHITESPACE(*l)) { if (tokenbuf >= tokenbufend) return -1; *tokenbuf++ = *l++; } } if (tokenbuf >= tokenbufend) return -1; *tokenbuf++ = 0; } else l++; } // line endings: // UNIX: \n // Mac: \r // Windows: \r\n if (*l == '\r') l++; if (*l == '\n') l++; *text = l; return argc; } /* ============ COM_StringLengthNoColors calculates the visible width of a color coded string. *valid is filled with TRUE if the string is a valid colored string (that is, if it does not end with an unfinished color code). If it gets filled with FALSE, a fix would be adding a STRING_COLOR_TAG at the end of the string. valid can be set to NULL if the caller doesn't care. For size_s, specify the maximum number of characters from s to use, or 0 to use all characters until the zero terminator. ============ */ size_t COM_StringLengthNoColors(const char *s, size_t size_s, qboolean *valid) { const char *end = size_s ? (s + size_s) : NULL; size_t len = 0; for(;;) { switch((s == end) ? 0 : *s) { case 0: if(valid) *valid = TRUE; return len; case STRING_COLOR_TAG: ++s; switch((s == end) ? 0 : *s) { case STRING_COLOR_RGB_TAG_CHAR: if (s+1 != end && isxdigit(s[1]) && s+2 != end && isxdigit(s[2]) && s+3 != end && isxdigit(s[3]) ) { s+=3; break; } ++len; // STRING_COLOR_TAG ++len; // STRING_COLOR_RGB_TAG_CHAR break; case 0: // ends with unfinished color code! ++len; if(valid) *valid = FALSE; return len; case STRING_COLOR_TAG: // escaped ^ ++len; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // color code break; default: // not a color code ++len; // STRING_COLOR_TAG ++len; // the character break; } break; default: ++len; break; } ++s; } // never get here } /* ============ COM_StringDecolorize removes color codes from a string. If escape_carets is true, the resulting string will be safe for printing. If escape_carets is false, the function will just strip color codes (for logging for example). If the output buffer size did not suffice for converting, the function returns FALSE. Generally, if escape_carets is false, the output buffer needs strlen(str)+1 bytes, and if escape_carets is true, it can need strlen(str)*1.5+2 bytes. In any case, the function makes sure that the resulting string is zero terminated. For size_in, specify the maximum number of characters from in to use, or 0 to use all characters until the zero terminator. ============ */ qboolean COM_StringDecolorize(const char *in, size_t size_in, char *out, size_t size_out, qboolean escape_carets) { #define APPEND(ch) do { if(--size_out) { *out++ = (ch); } else { *out++ = 0; return FALSE; } } while(0) const char *end = size_in ? (in + size_in) : NULL; if(size_out < 1) return FALSE; for(;;) { switch((in == end) ? 0 : *in) { case 0: *out++ = 0; return TRUE; case STRING_COLOR_TAG: ++in; switch((in == end) ? 0 : *in) { case STRING_COLOR_RGB_TAG_CHAR: if (in+1 != end && isxdigit(in[1]) && in+2 != end && isxdigit(in[2]) && in+3 != end && isxdigit(in[3]) ) { in+=3; break; } APPEND(STRING_COLOR_TAG); if(escape_carets) APPEND(STRING_COLOR_TAG); APPEND(STRING_COLOR_RGB_TAG_CHAR); break; case 0: // ends with unfinished color code! APPEND(STRING_COLOR_TAG); // finish the code by appending another caret when escaping if(escape_carets) APPEND(STRING_COLOR_TAG); *out++ = 0; return TRUE; case STRING_COLOR_TAG: // escaped ^ APPEND(STRING_COLOR_TAG); // append a ^ twice when escaping if(escape_carets) APPEND(STRING_COLOR_TAG); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // color code break; default: // not a color code APPEND(STRING_COLOR_TAG); APPEND(*in); break; } break; default: APPEND(*in); break; } ++in; } // never get here #undef APPEND } char *InfoString_GetValue(const char *buffer, const char *key, char *value, size_t valuelength) { int pos = 0, j; size_t keylength; if (!key) key = ""; keylength = strlen(key); if (valuelength < 1 || !value) { Con_Printf("InfoString_GetValue: no room in value\n"); return NULL; } value[0] = 0; if (strchr(key, '\\')) { Con_Printf("InfoString_GetValue: key name \"%s\" contains \\ which is not possible in an infostring\n", key); return NULL; } if (strchr(key, '\"')) { Con_Printf("InfoString_SetValue: key name \"%s\" contains \" which is not allowed in an infostring\n", key); return NULL; } if (!key[0]) { Con_Printf("InfoString_GetValue: can not look up a key with no name\n"); return NULL; } while (buffer[pos] == '\\') { if (!memcmp(buffer + pos+1, key, keylength)) { for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); pos++; for (j = 0;buffer[pos+j] && buffer[pos+j] != '\\' && j < (int)valuelength - 1;j++) value[j] = buffer[pos+j]; value[j] = 0; return value; } for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); } // if we reach this point the key was not found return NULL; } void InfoString_SetValue(char *buffer, size_t bufferlength, const char *key, const char *value) { int pos = 0, pos2; size_t keylength; if (!key) key = ""; if (!value) value = ""; keylength = strlen(key); if (strchr(key, '\\') || strchr(value, '\\')) { Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \\ which is not possible to store in an infostring\n", key, value); return; } if (strchr(key, '\"') || strchr(value, '\"')) { Con_Printf("InfoString_SetValue: \"%s\" \"%s\" contains \" which is not allowed in an infostring\n", key, value); return; } if (!key[0]) { Con_Printf("InfoString_SetValue: can not set a key with no name\n"); return; } while (buffer[pos] == '\\') { if (!memcmp(buffer + pos+1, key, keylength)) break; for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); for (pos++;buffer[pos] && buffer[pos] != '\\';pos++); } // if we found the key, find the end of it because we will be replacing it pos2 = pos; if (buffer[pos] == '\\') { for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); for (pos2++;buffer[pos2] && buffer[pos2] != '\\';pos2++); } if (bufferlength <= pos + 1 + strlen(key) + 1 + strlen(value) + strlen(buffer + pos2)) { Con_Printf("InfoString_SetValue: no room for \"%s\" \"%s\" in infostring\n", key, value); return; } if (value[0]) { // set the key/value and append the remaining text char tempbuffer[MAX_INPUTLINE]; strlcpy(tempbuffer, buffer + pos2, sizeof(tempbuffer)); dpsnprintf(buffer + pos, bufferlength - pos, "\\%s\\%s%s", key, value, tempbuffer); } else { // just remove the key from the text strlcpy(buffer + pos, buffer + pos2, bufferlength - pos); } } void InfoString_Print(char *buffer) { int i; char key[MAX_INPUTLINE]; char value[MAX_INPUTLINE]; while (*buffer) { if (*buffer != '\\') { Con_Printf("InfoString_Print: corrupt string\n"); return; } for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) if (i < (int)sizeof(key)-1) key[i++] = *buffer; key[i] = 0; if (*buffer != '\\') { Con_Printf("InfoString_Print: corrupt string\n"); return; } for (buffer++, i = 0;*buffer && *buffer != '\\';buffer++) if (i < (int)sizeof(value)-1) value[i++] = *buffer; value[i] = 0; // empty value is an error case Con_Printf("%20s %s\n", key, value[0] ? value : "NO VALUE"); } } //======================================================== // strlcat and strlcpy, from OpenBSD /* * Copyright (c) 1998 Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* $OpenBSD: strlcat.c,v 1.11 2003/06/17 21:56:24 millert Exp $ */ /* $OpenBSD: strlcpy.c,v 1.8 2003/06/17 21:56:24 millert Exp $ */ #ifndef HAVE_STRLCAT size_t strlcat(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; size_t dlen; /* Find the end of dst and adjust bytes left but don't go past end */ while (n-- != 0 && *d != '\0') d++; dlen = d - dst; n = siz - dlen; if (n == 0) return(dlen + strlen(s)); while (*s != '\0') { if (n != 1) { *d++ = *s; n--; } s++; } *d = '\0'; return(dlen + (s - src)); /* count does not include NUL */ } #endif // #ifndef HAVE_STRLCAT #ifndef HAVE_STRLCPY size_t strlcpy(char *dst, const char *src, size_t siz) { register char *d = dst; register const char *s = src; register size_t n = siz; /* Copy as many bytes as will fit */ if (n != 0 && --n != 0) { do { if ((*d++ = *s++) == 0) break; } while (--n != 0); } /* Not enough room in dst, add NUL and traverse rest of src */ if (n == 0) { if (siz != 0) *d = '\0'; /* NUL-terminate dst */ while (*s++) ; } return(s - src - 1); /* count does not include NUL */ } #endif // #ifndef HAVE_STRLCPY void FindFraction(double val, int *num, int *denom, int denomMax) { int i; double bestdiff; // initialize bestdiff = fabs(val); *num = 0; *denom = 1; for(i = 1; i <= denomMax; ++i) { int inum = (int) floor(0.5 + val * i); double diff = fabs(val - inum / (double)i); if(diff < bestdiff) { bestdiff = diff; *num = inum; *denom = i; } } } // decodes an XPM from C syntax char **XPM_DecodeString(const char *in) { static char *tokens[257]; static char lines[257][512]; size_t line = 0; // skip until "{" token while(COM_ParseToken_QuakeC(&in, false) && strcmp(com_token, "{")); // now, read in succession: string, comma-or-} while(COM_ParseToken_QuakeC(&in, false)) { tokens[line] = lines[line]; strlcpy(lines[line++], com_token, sizeof(lines[0])); if(!COM_ParseToken_QuakeC(&in, false)) return NULL; if(!strcmp(com_token, "}")) break; if(strcmp(com_token, ",")) return NULL; if(line >= sizeof(tokens) / sizeof(tokens[0])) return NULL; } return tokens; } static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static void base64_3to4(const unsigned char *in, unsigned char *out, int bytes) { unsigned char i0 = (bytes > 0) ? in[0] : 0; unsigned char i1 = (bytes > 1) ? in[1] : 0; unsigned char i2 = (bytes > 2) ? in[2] : 0; unsigned char o0 = base64[i0 >> 2]; unsigned char o1 = base64[((i0 << 4) | (i1 >> 4)) & 077]; unsigned char o2 = base64[((i1 << 2) | (i2 >> 6)) & 077]; unsigned char o3 = base64[i2 & 077]; out[0] = (bytes > 0) ? o0 : '?'; out[1] = (bytes > 0) ? o1 : '?'; out[2] = (bytes > 1) ? o2 : '='; out[3] = (bytes > 2) ? o3 : '='; } size_t base64_encode(unsigned char *buf, size_t buflen, size_t outbuflen) { size_t blocks, i; // expand the out-buffer blocks = (buflen + 2) / 3; if(blocks*4 > outbuflen) return 0; for(i = blocks; i > 0; ) { --i; base64_3to4(buf + 3*i, buf + 4*i, (int)(buflen - 3*i)); } return blocks * 4; } darkplaces/darkplaces.xpm0000664000175000017500000000751613067716220015033 0ustar kalevkalev/* XPM */ static char * darkplaces_xpm[] = { "48 48 91 1", " c None", ". c #020300", "+ c #120303", "@ c #180202", "# c #200301", "$ c #340100", "% c #410000", "& c #2D0704", "* c #11120F", "= c #4F0701", "- c #3F0F00", "; c #251615", "> c #780000", ", c #2D1514", "' c #1D1C1A", ") c #680F01", "! c #431A10", "~ c #3B221D", "{ c #2B2725", "] c #601B02", "^ c #9D0A00", "/ c #55220A", "( c #3D2B25", "_ c #53291D", ": c #4E2E1B", "< c #333432", "[ c #4C2F26", "} c #812301", "| c #3F332F", "1 c #6B321A", "2 c #3F3F3D", "3 c #7E320E", "4 c #563B35", "5 c #4B3E39", "6 c #533F39", "7 c #6D3A2F", "8 c #943508", "9 c #514541", "0 c #654132", "a c #474845", "b c #6D442B", "c c #7C4328", "d c #504F4D", "e c #674A43", "f c #644F47", "g c #5E514B", "h c #B64005", "i c #91472F", "j c #575654", "k c #AD4A11", "l c #82533C", "m c #5F5E5B", "n c #745C54", "o c #6C5E5A", "p c #A25329", "q c #825C54", "r c #676664", "s c #816A62", "t c #70706E", "u c #986951", "v c #7D706C", "w c #C66935", "x c #7E7C79", "y c #FA6400", "z c #937B73", "A c #9E796A", "B c #B97557", "C c #A67B5D", "D c #878581", "E c #8F8D8B", "F c #AF8A7D", "G c #A58D83", "H c #9C8F8C", "I c #979693", "J c #B79590", "K c #A59F9C", "L c #DD9472", "M c #B39D97", "N c #EB9462", "O c #C1A391", "P c #D19F8D", "Q c #D1A184", "R c #B6B0AD", "S c #C8C2BF", "T c #DDC0BC", "U c #E1C2B0", "V c #F8BE99", "W c #DDD3CE", "X c #EFE4E0", "Y c #FEE4C7", "Z c #FDFEFA", " ", " ID ", " tvr ", " 59aI ", " 4v2t ", " 4I2r ", " E[Xaj ", " z7Xda RHrqsE ", " f2XjaD Itj93][zH ", " 92Wd2x Ixja2,pQDI ", " 92Xx2t Irjb&FIx ", " 5aZx5r Dj{,ORx ", " xsja,eXt2r t'+XIt ", " Mu/+|<<;[Xr2r j!AXjt ", " Kq3=[222a,5Xt2r 5;SSdI ", " H;0A5222j ~oZr2t t+CWjr ", " z37(2<2ax #include #include #ifdef SUPPORTDIRECTX #include #endif #include "qtypes.h" #include "quakedef.h" #include #include "resource.h" #include "conproc.h" HANDLE hinput, houtput; #ifdef QHOST static HANDLE tevent; static HANDLE hFile; static HANDLE heventParent; static HANDLE heventChild; #endif /* =============================================================================== SYSTEM IO =============================================================================== */ void Sys_Error (const char *error, ...) { va_list argptr; char text[MAX_INPUTLINE]; static int in_sys_error0 = 0; static int in_sys_error1 = 0; static int in_sys_error2 = 0; static int in_sys_error3 = 0; va_start (argptr, error); dpvsnprintf (text, sizeof (text), error, argptr); va_end (argptr); Con_Printf ("Quake Error: %s\n", text); // close video so the message box is visible, unless we already tried that if (!in_sys_error0 && cls.state != ca_dedicated) { in_sys_error0 = 1; VID_Shutdown(); } if (!in_sys_error3 && cls.state != ca_dedicated) { in_sys_error3 = true; MessageBox(NULL, text, "Quake Error", MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); } if (!in_sys_error1) { in_sys_error1 = 1; Host_Shutdown (); } // shut down QHOST hooks if necessary if (!in_sys_error2) { in_sys_error2 = 1; Sys_Shutdown (); } exit (1); } void Sys_Shutdown (void) { #ifdef QHOST if (tevent) CloseHandle (tevent); #endif if (cls.state == ca_dedicated) FreeConsole (); #ifdef QHOST // shut down QHOST hooks if necessary DeinitConProc (); #endif } void Sys_PrintToTerminal(const char *text) { DWORD dummy; extern HANDLE houtput; if ((houtput != 0) && (houtput != INVALID_HANDLE_VALUE)) WriteFile(houtput, text, (DWORD) strlen(text), &dummy, NULL); } char *Sys_ConsoleInput (void) { static char text[MAX_INPUTLINE]; static int len; INPUT_RECORD recs[1024]; int ch; DWORD numread, numevents, dummy; if (cls.state != ca_dedicated) return NULL; for ( ;; ) { if (!GetNumberOfConsoleInputEvents (hinput, &numevents)) { cls.state = ca_disconnected; Sys_Error ("Error getting # of console events (error code %x)", (unsigned int)GetLastError()); } if (numevents <= 0) break; if (!ReadConsoleInput(hinput, recs, 1, &numread)) { cls.state = ca_disconnected; Sys_Error ("Error reading console input (error code %x)", (unsigned int)GetLastError()); } if (numread != 1) { cls.state = ca_disconnected; Sys_Error ("Couldn't read console input (error code %x)", (unsigned int)GetLastError()); } if (recs[0].EventType == KEY_EVENT) { if (!recs[0].Event.KeyEvent.bKeyDown) { ch = recs[0].Event.KeyEvent.uChar.AsciiChar; switch (ch) { case '\r': WriteFile(houtput, "\r\n", 2, &dummy, NULL); if (len) { text[len] = 0; len = 0; return text; } break; case '\b': WriteFile(houtput, "\b \b", 3, &dummy, NULL); if (len) { len--; } break; default: if (ch >= (int) (unsigned char) ' ') { WriteFile(houtput, &ch, 1, &dummy, NULL); text[len] = ch; len = (len + 1) & 0xff; } break; } } } } return NULL; } char *Sys_GetClipboardData (void) { char *data = NULL; char *cliptext; if (OpenClipboard (NULL) != 0) { HANDLE hClipboardData; if ((hClipboardData = GetClipboardData (CF_TEXT)) != 0) { if ((cliptext = (char *)GlobalLock (hClipboardData)) != 0) { size_t allocsize; allocsize = GlobalSize (hClipboardData) + 1; data = (char *)Z_Malloc (allocsize); strlcpy (data, cliptext, allocsize); GlobalUnlock (hClipboardData); } } CloseClipboard (); } return data; } void Sys_InitConsole (void) { #ifdef QHOST int t; // initialize the windows dedicated server console if needed tevent = CreateEvent(NULL, false, false, NULL); if (!tevent) Sys_Error ("Couldn't create event"); #endif houtput = GetStdHandle (STD_OUTPUT_HANDLE); hinput = GetStdHandle (STD_INPUT_HANDLE); // LordHavoc: can't check cls.state because it hasn't been initialized yet // if (cls.state == ca_dedicated) if (COM_CheckParm("-dedicated")) { //if ((houtput == 0) || (houtput == INVALID_HANDLE_VALUE)) // LordHavoc: on Windows XP this is never 0 or invalid, but hinput is invalid { if (!AllocConsole ()) Sys_Error ("Couldn't create dedicated server console (error code %x)", (unsigned int)GetLastError()); houtput = GetStdHandle (STD_OUTPUT_HANDLE); hinput = GetStdHandle (STD_INPUT_HANDLE); } if ((houtput == 0) || (houtput == INVALID_HANDLE_VALUE)) Sys_Error ("Couldn't create dedicated server console"); #ifdef QHOST #ifdef _WIN64 #define atoi _atoi64 #endif // give QHOST a chance to hook into the console if ((t = COM_CheckParm ("-HFILE")) > 0) { if (t < com_argc) hFile = (HANDLE)atoi (com_argv[t+1]); } if ((t = COM_CheckParm ("-HPARENT")) > 0) { if (t < com_argc) heventParent = (HANDLE)atoi (com_argv[t+1]); } if ((t = COM_CheckParm ("-HCHILD")) > 0) { if (t < com_argc) heventChild = (HANDLE)atoi (com_argv[t+1]); } InitConProc (hFile, heventParent, heventChild); #endif } // because sound is off until we become active S_BlockSound (); } /* ============================================================================== WINDOWS CRAP ============================================================================== */ /* ================== WinMain ================== */ HINSTANCE global_hInstance; const char *argv[MAX_NUM_ARGVS]; char program_name[MAX_OSPATH]; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MEMORYSTATUS lpBuffer; /* previous instances do not exist in Win32 */ if (hPrevInstance) return 0; global_hInstance = hInstance; lpBuffer.dwLength = sizeof(MEMORYSTATUS); GlobalMemoryStatus (&lpBuffer); program_name[sizeof(program_name)-1] = 0; GetModuleFileNameA(NULL, program_name, sizeof(program_name) - 1); com_argc = 1; com_argv = argv; argv[0] = program_name; // FIXME: this tokenizer is rather redundent, call a more general one while (*lpCmdLine && (com_argc < MAX_NUM_ARGVS)) { while (*lpCmdLine && ISWHITESPACE(*lpCmdLine)) lpCmdLine++; if (!*lpCmdLine) break; if (*lpCmdLine == '\"') { // quoted string lpCmdLine++; argv[com_argc] = lpCmdLine; com_argc++; while (*lpCmdLine && (*lpCmdLine != '\"')) lpCmdLine++; } else { // unquoted word argv[com_argc] = lpCmdLine; com_argc++; while (*lpCmdLine && !ISWHITESPACE(*lpCmdLine)) lpCmdLine++; } if (*lpCmdLine) { *lpCmdLine = 0; lpCmdLine++; } } Sys_ProvideSelfFD(); Host_Main(); /* return success of application */ return true; } #if 0 // unused, this file is only used when building windows client and vid_wgl provides WinMain() instead int main (int argc, const char* argv[]) { MEMORYSTATUS lpBuffer; global_hInstance = GetModuleHandle (0); lpBuffer.dwLength = sizeof(MEMORYSTATUS); GlobalMemoryStatus (&lpBuffer); program_name[sizeof(program_name)-1] = 0; GetModuleFileNameA(NULL, program_name, sizeof(program_name) - 1); com_argc = argc; com_argv = argv; Host_Main(); return true; } #endif qboolean sys_supportsdlgetticks = false; unsigned int Sys_SDL_GetTicks (void) { Sys_Error("Called Sys_SDL_GetTicks on non-SDL target"); return 0; } void Sys_SDL_Delay (unsigned int milliseconds) { Sys_Error("Called Sys_SDL_Delay on non-SDL target"); } darkplaces/model_sprite.h0000664000175000017500000000210613067716222015023 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef MODEL_SPRITE_H #define MODEL_SPRITE_H /* ============================================================================== SPRITE MODELS ============================================================================== */ #include "spritegn.h" // FIXME: shorten these? typedef struct mspriteframe_s { float up, down, left, right; } mspriteframe_t; #endif darkplaces/SDLMain.m0000664000175000017500000002574113067716216013606 0ustar kalevkalev/* SDLMain.m - main entry point for our Cocoa-ized SDL app Initial Version: Darrell Walisser Non-NIB-Code & other changes: Max Horn Feel free to customize this file to suit your needs */ #include #if SDL_MAJOR_VERSION == 1 #include "SDLMain.h" #include /* for MAXPATHLEN */ #include /* For some reaon, Apple removed setAppleMenu from the headers in 10.4, but the method still is there and works. To avoid warnings, we declare it ourselves here. */ @interface NSApplication(SDL_Missing_Methods) - (void)setAppleMenu:(NSMenu *)menu; @end /* Use this flag to determine whether we use SDLMain.nib or not */ #define SDL_USE_NIB_FILE 0 /* Use this flag to determine whether we use CPS (docking) or not */ #define SDL_USE_CPS 1 #ifdef SDL_USE_CPS /* Portions of CPS.h */ typedef struct CPSProcessSerNum { UInt32 lo; UInt32 hi; } CPSProcessSerNum; extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); #endif /* SDL_USE_CPS */ static int gArgc; static char **gArgv; static BOOL gFinderLaunch; static BOOL gCalledAppMainline = FALSE; static NSString *getApplicationName(void) { const NSDictionary *dict; NSString *appName = 0; /* Determine the application name */ dict = (const NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); if (dict) appName = [dict objectForKey: @"CFBundleName"]; if (![appName length]) appName = [[NSProcessInfo processInfo] processName]; return appName; } #if SDL_USE_NIB_FILE /* A helper category for NSString */ @interface NSString (ReplaceSubString) - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString; @end #endif @interface NSApplication (SDLApplication) @end @implementation NSApplication (SDLApplication) /* Invoked from the Quit menu item */ - (void)terminate:(id)sender { /* Post a SDL_QUIT event */ SDL_Event event; event.type = SDL_QUIT; SDL_PushEvent(&event); } @end /* The main class of the application, the application's delegate */ @implementation SDLMain /* Set the working directory to the .app's parent directory */ - (void) setupWorkingDirectory:(BOOL)shouldChdir { if (shouldChdir) { char parentdir[MAXPATHLEN]; CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); if (CFURLGetFileSystemRepresentation(url2, 1, (UInt8 *)parentdir, MAXPATHLEN)) { chdir(parentdir); /* chdir to the binary app's parent */ } CFRelease(url); CFRelease(url2); } } #if SDL_USE_NIB_FILE /* Fix menu to contain the real app name instead of "SDL App" */ - (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName { NSRange aRange; NSEnumerator *enumerator; NSMenuItem *menuItem; aRange = [[aMenu title] rangeOfString:@"SDL App"]; if (aRange.length != 0) [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]]; enumerator = [[aMenu itemArray] objectEnumerator]; while ((menuItem = [enumerator nextObject])) { aRange = [[menuItem title] rangeOfString:@"SDL App"]; if (aRange.length != 0) [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]]; if ([menuItem hasSubmenu]) [self fixMenu:[menuItem submenu] withAppName:appName]; } } #else static void setApplicationMenu(void) { /* warning: this code is very odd */ NSMenu *appleMenu; NSMenuItem *menuItem; NSString *title; NSString *appName; appName = getApplicationName(); appleMenu = [[NSMenu alloc] initWithTitle:@""]; /* Add menu items */ title = [@"About " stringByAppendingString:appName]; [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; title = [@"Hide " stringByAppendingString:appName]; [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; [appleMenu addItem:[NSMenuItem separatorItem]]; title = [@"Quit " stringByAppendingString:appName]; [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; /* Put menu into the menubar */ menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; [menuItem setSubmenu:appleMenu]; [[NSApp mainMenu] addItem:menuItem]; /* Tell the application object that this is now the application menu */ [NSApp setAppleMenu:appleMenu]; /* Finally give up our references to the objects */ [appleMenu release]; [menuItem release]; } /* Create a window menu */ static void setupWindowMenu(void) { NSMenu *windowMenu; NSMenuItem *windowMenuItem; NSMenuItem *menuItem; windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; /* "Minimize" item */ menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; [windowMenu addItem:menuItem]; [menuItem release]; /* Put menu into the menubar */ windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; [windowMenuItem setSubmenu:windowMenu]; [[NSApp mainMenu] addItem:windowMenuItem]; /* Tell the application object that this is now the window menu */ [NSApp setWindowsMenu:windowMenu]; /* Finally give up our references to the objects */ [windowMenu release]; [windowMenuItem release]; } /* Replacement for NSApplicationMain */ static void CustomApplicationMain (int argc, char **argv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; SDLMain *sdlMain; /* Ensure the application object is initialised */ [NSApplication sharedApplication]; #ifdef SDL_USE_CPS { CPSProcessSerNum PSN; /* Tell the dock about us */ if (!CPSGetCurrentProcess(&PSN)) if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) if (!CPSSetFrontProcess(&PSN)) [NSApplication sharedApplication]; } #endif /* SDL_USE_CPS */ /* Set up the menubar */ [NSApp setMainMenu:[[NSMenu alloc] init]]; setApplicationMenu(); setupWindowMenu(); /* Create SDLMain and make it the app delegate */ sdlMain = [[SDLMain alloc] init]; [NSApp setDelegate:sdlMain]; /* Start the main event loop */ [NSApp run]; [sdlMain release]; [pool release]; } #endif /* * Catch document open requests...this lets us notice files when the app * was launched by double-clicking a document, or when a document was * dragged/dropped on the app's icon. You need to have a * CFBundleDocumentsType section in your Info.plist to get this message, * apparently. * * Files are added to gArgv, so to the app, they'll look like command line * arguments. Previously, apps launched from the finder had nothing but * an argv[0]. * * This message may be received multiple times to open several docs on launch. * * This message is ignored once the app's mainline has been called. */ - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { const char *temparg; size_t arglen; char *arg; char **newargv; if (!gFinderLaunch) /* MacOS is passing command line args. */ return FALSE; if (gCalledAppMainline) /* app has started, ignore this document. */ return FALSE; temparg = [filename UTF8String]; arglen = SDL_strlen(temparg) + 1; arg = (char *) SDL_malloc(arglen); if (arg == NULL) return FALSE; newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); if (newargv == NULL) { SDL_free(arg); return FALSE; } gArgv = newargv; SDL_strlcpy(arg, temparg, arglen); gArgv[gArgc++] = arg; gArgv[gArgc] = NULL; return TRUE; } /* Called when the internal event loop has just started running */ - (void) applicationDidFinishLaunching: (NSNotification *) note { int status; /* Set the working directory to the .app's parent directory */ [self setupWorkingDirectory:gFinderLaunch]; #if SDL_USE_NIB_FILE /* Set the main menu to contain the real app name instead of "SDL App" */ [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()]; #endif /* Hand off to main application code */ gCalledAppMainline = TRUE; status = SDL_main (gArgc, gArgv); /* We're done, thank you for playing */ exit(status); } @end @implementation NSString (ReplaceSubString) - (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString { unsigned int bufferSize; unsigned int selfLen = [self length]; unsigned int aStringLen = [aString length]; unichar *buffer; NSRange localRange; NSString *result; bufferSize = selfLen + aStringLen - aRange.length; buffer = (unichar *)NSAllocateMemoryPages(bufferSize*sizeof(unichar)); /* Get first part into buffer */ localRange.location = 0; localRange.length = aRange.location; [self getCharacters:buffer range:localRange]; /* Get middle part into buffer */ localRange.location = 0; localRange.length = aStringLen; [aString getCharacters:(buffer+aRange.location) range:localRange]; /* Get last part into buffer */ localRange.location = aRange.location + aRange.length; localRange.length = selfLen - localRange.location; [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange]; /* Build output string */ result = [NSString stringWithCharacters:buffer length:bufferSize]; NSDeallocateMemoryPages(buffer, bufferSize); return result; } @end #ifdef main # undef main #endif /* Main entry point to executable - should *not* be SDL_main! */ int main (int argc, char **argv) { /* Copy the arguments into a global variable */ /* This is passed if we are launched by double-clicking */ if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) { gArgv = (char **) SDL_malloc(sizeof (char *) * 2); gArgv[0] = argv[0]; gArgv[1] = NULL; gArgc = 1; gFinderLaunch = YES; } else { int i; gArgc = argc; gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1)); for (i = 0; i <= argc; i++) gArgv[i] = argv[i]; gFinderLaunch = NO; } #if SDL_USE_NIB_FILE NSApplicationMain (argc, argv); #else CustomApplicationMain (argc, argv); #endif return 0; } #endif darkplaces/dpsoftrast.h0000664000175000017500000003705313067716220014535 0ustar kalevkalev #ifndef DPSOFTRAST_H #define DPSOFTRAST_H #include #define DPSOFTRAST_MAXMIPMAPS 16 #define DPSOFTRAST_TEXTURE_MAXSIZE (1<<(DPSOFTRAST_MAXMIPMAPS - 1)) #define DPSOFTRAST_MAXTEXTUREUNITS 16 #define DPSOFTRAST_MAXTEXCOORDARRAYS 8 // type of pixels in texture (some of these are converted to BGRA8 on update) #define DPSOFTRAST_TEXTURE_FORMAT_BGRA8 0 #define DPSOFTRAST_TEXTURE_FORMAT_DEPTH 1 #define DPSOFTRAST_TEXTURE_FORMAT_RGBA8 2 #define DPSOFTRAST_TEXTURE_FORMAT_ALPHA8 3 #define DPSOFTRAST_TEXTURE_FORMAT_RGBA16F 4 #define DPSOFTRAST_TEXTURE_FORMAT_RGBA32F 5 #define DPSOFTRAST_TEXTURE_FORMAT_COMPAREMASK 0x0F // modifier flags for texture (can not be changed after creation) #define DPSOFTRAST_TEXTURE_FLAG_MIPMAP 0x10 #define DPSOFTRAST_TEXTURE_FLAG_CUBEMAP 0x20 #define DPSOFTRAST_TEXTURE_FLAG_USEALPHA 0x40 #define DPSOFTRAST_TEXTURE_FLAG_CLAMPTOEDGE 0x80 typedef enum DPSOFTRAST_TEXTURE_FILTER_e { DPSOFTRAST_TEXTURE_FILTER_NEAREST = 0, DPSOFTRAST_TEXTURE_FILTER_LINEAR = 1, DPSOFTRAST_TEXTURE_FILTER_NEAREST_MIPMAP_TRIANGLE = 2, DPSOFTRAST_TEXTURE_FILTER_LINEAR_MIPMAP_TRIANGLE = 3, } DPSOFTRAST_TEXTURE_FILTER; int DPSOFTRAST_Init(int width, int height, int numthreads, int interlace, unsigned int *colorpixels, unsigned int *depthpixels); void DPSOFTRAST_Shutdown(void); void DPSOFTRAST_Flush(void); void DPSOFTRAST_Finish(void); int DPSOFTRAST_Texture_New(int flags, int width, int height, int depth); void DPSOFTRAST_Texture_Free(int index); void DPSOFTRAST_Texture_UpdatePartial(int index, int mip, const unsigned char *pixels, int blockx, int blocky, int blockwidth, int blockheight); void DPSOFTRAST_Texture_UpdateFull(int index, const unsigned char *pixels); int DPSOFTRAST_Texture_GetWidth(int index, int mip); int DPSOFTRAST_Texture_GetHeight(int index, int mip); int DPSOFTRAST_Texture_GetDepth(int index, int mip); unsigned char *DPSOFTRAST_Texture_GetPixelPointer(int index, int mip); void DPSOFTRAST_Texture_Filter(int index, DPSOFTRAST_TEXTURE_FILTER filter); void DPSOFTRAST_SetRenderTargets(int width, int height, unsigned int *depthpixels, unsigned int *colorpixels0, unsigned int *colorpixels1, unsigned int *colorpixels2, unsigned int *colorpixels3); void DPSOFTRAST_Viewport(int x, int y, int width, int height); void DPSOFTRAST_ClearColor(float r, float g, float b, float a); void DPSOFTRAST_ClearDepth(float d); void DPSOFTRAST_ColorMask(int r, int g, int b, int a); void DPSOFTRAST_DepthTest(int enable); void DPSOFTRAST_ScissorTest(int enable); void DPSOFTRAST_Scissor(float x, float y, float width, float height); void DPSOFTRAST_ClipPlane(float x, float y, float z, float w); void DPSOFTRAST_BlendFunc(int smodulate, int dmodulate); void DPSOFTRAST_BlendSubtract(int enable); void DPSOFTRAST_DepthMask(int enable); void DPSOFTRAST_DepthFunc(int comparemode); void DPSOFTRAST_DepthRange(float range0, float range1); void DPSOFTRAST_PolygonOffset(float alongnormal, float intoview); void DPSOFTRAST_CullFace(int mode); void DPSOFTRAST_Color4f(float r, float g, float b, float a); void DPSOFTRAST_GetPixelsBGRA(int blockx, int blocky, int blockwidth, int blockheight, unsigned char *outpixels); void DPSOFTRAST_CopyRectangleToTexture(int index, int mip, int tx, int ty, int sx, int sy, int width, int height); void DPSOFTRAST_SetTexture(int unitnum, int index); void DPSOFTRAST_SetVertexPointer(const float *vertex3f, size_t stride); void DPSOFTRAST_SetColorPointer(const float *color4f, size_t stride); void DPSOFTRAST_SetColorPointer4ub(const unsigned char *color4ub, size_t stride); void DPSOFTRAST_SetTexCoordPointer(int unitnum, int numcomponents, size_t stride, const float *texcoordf); typedef enum gl20_texunit_e { // postprocess shaders, and generic shaders: GL20TU_FIRST = 0, GL20TU_SECOND = 1, GL20TU_GAMMARAMPS = 2, // standard material properties GL20TU_NORMAL = 0, GL20TU_COLOR = 1, GL20TU_GLOSS = 2, GL20TU_GLOW = 3, // material properties for a second material GL20TU_SECONDARY_NORMAL = 4, GL20TU_SECONDARY_COLOR = 5, GL20TU_SECONDARY_GLOSS = 6, GL20TU_SECONDARY_GLOW = 7, // material properties for a colormapped material // conflicts with secondary material GL20TU_PANTS = 4, GL20TU_SHIRT = 7, // fog fade in the distance GL20TU_FOGMASK = 8, // compiled ambient lightmap and deluxemap GL20TU_LIGHTMAP = 9, GL20TU_DELUXEMAP = 10, // refraction, used by water shaders GL20TU_REFRACTION = 3, // reflection, used by water shaders, also with normal material rendering // conflicts with secondary material GL20TU_REFLECTION = 7, // rtlight attenuation (distance fade) and cubemap filter (projection texturing) // conflicts with lightmap/deluxemap GL20TU_ATTENUATION = 9, GL20TU_CUBE = 10, GL20TU_SHADOWMAP2D = 15, GL20TU_CUBEPROJECTION = 12, // rtlight prepass data (screenspace depth and normalmap) // GL20TU_UNUSED1 = 13, GL20TU_SCREENNORMALMAP = 14, // lightmap prepass data (screenspace diffuse and specular from lights) GL20TU_SCREENDIFFUSE = 11, GL20TU_SCREENSPECULAR = 12, // fake reflections GL20TU_REFLECTMASK = 5, GL20TU_REFLECTCUBE = 6, GL20TU_FOGHEIGHTTEXTURE = 14 } gl20_texunit; typedef enum glsl_attrib_e { GLSLATTRIB_POSITION = 0, GLSLATTRIB_COLOR = 1, GLSLATTRIB_TEXCOORD0 = 2, GLSLATTRIB_TEXCOORD1 = 3, GLSLATTRIB_TEXCOORD2 = 4, GLSLATTRIB_TEXCOORD3 = 5, GLSLATTRIB_TEXCOORD4 = 6, GLSLATTRIB_TEXCOORD5 = 7, GLSLATTRIB_TEXCOORD6 = 8, GLSLATTRIB_TEXCOORD7 = 9, } glsl_attrib; // this enum selects which of the glslshadermodeinfo entries should be used typedef enum shadermode_e { SHADERMODE_GENERIC, ///< (particles/HUD/etc) vertex color, optionally multiplied by one texture SHADERMODE_POSTPROCESS, ///< postprocessing shader (r_glsl_postprocess) SHADERMODE_DEPTH_OR_SHADOW, ///< (depthfirst/shadows) vertex shader only SHADERMODE_FLATCOLOR, ///< (lightmap) modulate texture by uniform color (q1bsp, q3bsp) SHADERMODE_VERTEXCOLOR, ///< (lightmap) modulate texture by vertex colors (q3bsp) SHADERMODE_LIGHTMAP, ///< (lightmap) modulate texture by lightmap texture (q1bsp, q3bsp) SHADERMODE_FAKELIGHT, ///< (fakelight) modulate texture by "fake" lighting (no lightmaps, no nothing) SHADERMODE_LIGHTDIRECTIONMAP_MODELSPACE, ///< (lightmap) use directional pixel shading from texture containing modelspace light directions (q3bsp deluxemap) SHADERMODE_LIGHTDIRECTIONMAP_TANGENTSPACE, ///< (lightmap) use directional pixel shading from texture containing tangentspace light directions (q1bsp deluxemap) SHADERMODE_LIGHTDIRECTIONMAP_FORCED_LIGHTMAP, // forced deluxemapping for lightmapped surfaces SHADERMODE_LIGHTDIRECTIONMAP_FORCED_VERTEXCOLOR, // forced deluxemapping for vertexlit surfaces SHADERMODE_LIGHTDIRECTION, ///< (lightmap) use directional pixel shading from fixed light direction (q3bsp) SHADERMODE_LIGHTSOURCE, ///< (lightsource) use directional pixel shading from light source (rtlight) SHADERMODE_REFRACTION, ///< refract background (the material is rendered normally after this pass) SHADERMODE_WATER, ///< refract background and reflection (the material is rendered normally after this pass) SHADERMODE_DEFERREDGEOMETRY, ///< (deferred) render material properties to screenspace geometry buffers SHADERMODE_DEFERREDLIGHTSOURCE, ///< (deferred) use directional pixel shading from light source (rtlight) on screenspace geometry buffers SHADERMODE_COUNT } shadermode_t; typedef enum shaderpermutation_e { SHADERPERMUTATION_DIFFUSE = 1<<0, ///< (lightsource) whether to use directional shading SHADERPERMUTATION_VERTEXTEXTUREBLEND = 1<<1, ///< indicates this is a two-layer material blend based on vertex alpha (q3bsp) SHADERPERMUTATION_VIEWTINT = 1<<2, ///< view tint (postprocessing only), use vertex colors (generic only) SHADERPERMUTATION_COLORMAPPING = 1<<3, ///< indicates this is a colormapped skin SHADERPERMUTATION_SATURATION = 1<<4, ///< saturation (postprocessing only) SHADERPERMUTATION_FOGINSIDE = 1<<5, ///< tint the color by fog color or black if using additive blend mode SHADERPERMUTATION_FOGOUTSIDE = 1<<6, ///< tint the color by fog color or black if using additive blend mode SHADERPERMUTATION_FOGHEIGHTTEXTURE = 1<<7, ///< fog color and density determined by texture mapped on vertical axis SHADERPERMUTATION_FOGALPHAHACK = 1<<8, ///< fog color and density determined by texture mapped on vertical axis SHADERPERMUTATION_GAMMARAMPS = 1<<9, ///< gamma (postprocessing only) SHADERPERMUTATION_CUBEFILTER = 1<<10, ///< (lightsource) use cubemap light filter SHADERPERMUTATION_GLOW = 1<<11, ///< (lightmap) blend in an additive glow texture SHADERPERMUTATION_BLOOM = 1<<12, ///< bloom (postprocessing only) SHADERPERMUTATION_SPECULAR = 1<<13, ///< (lightsource or deluxemapping) render specular effects SHADERPERMUTATION_POSTPROCESSING = 1<<14, ///< user defined postprocessing (postprocessing only) SHADERPERMUTATION_REFLECTION = 1<<15, ///< normalmap-perturbed reflection of the scene infront of the surface, preformed as an overlay on the surface SHADERPERMUTATION_OFFSETMAPPING = 1<<16, ///< adjust texcoords to roughly simulate a displacement mapped surface SHADERPERMUTATION_OFFSETMAPPING_RELIEFMAPPING = 1<<17, ///< adjust texcoords to accurately simulate a displacement mapped surface (requires OFFSETMAPPING to also be set!) SHADERPERMUTATION_SHADOWMAP2D = 1<<18, ///< (lightsource) use shadowmap texture as light filter SHADERPERMUTATION_SHADOWMAPVSDCT = 1<<19, ///< (lightsource) use virtual shadow depth cube texture for shadowmap indexing SHADERPERMUTATION_SHADOWMAPORTHO = 1<<20, ///< (lightsource) use orthographic shadowmap projection SHADERPERMUTATION_DEFERREDLIGHTMAP = 1<<21, ///< (lightmap) read Texture_ScreenDiffuse/Specular textures and add them on top of lightmapping SHADERPERMUTATION_ALPHAKILL = 1<<22, ///< (deferredgeometry) discard pixel if diffuse texture alpha below 0.5, (generic) apply global alpha SHADERPERMUTATION_REFLECTCUBE = 1<<23, ///< fake reflections using global cubemap (not HDRI light probe) SHADERPERMUTATION_NORMALMAPSCROLLBLEND = 1<<24, ///< (water) counter-direction normalmaps scrolling SHADERPERMUTATION_BOUNCEGRID = 1<<25, ///< (lightmap) use Texture_BounceGrid as an additional source of ambient light SHADERPERMUTATION_BOUNCEGRIDDIRECTIONAL = 1<<26, ///< (lightmap) use 16-component pixels in bouncegrid texture for directional lighting rather than standard 4-component SHADERPERMUTATION_TRIPPY = 1<<27, ///< use trippy vertex shader effect SHADERPERMUTATION_DEPTHRGB = 1<<28, ///< read/write depth values in RGB color coded format for older hardware without depth samplers SHADERPERMUTATION_ALPHAGEN_VERTEX = 1<<29, ///< alphaGen vertex SHADERPERMUTATION_SKELETAL = 1<<30, ///< (skeletal models) use skeletal matrices to deform vertices (gpu-skinning) SHADERPERMUTATION_OCCLUDE = 1<<31, ///< use occlusion buffer for corona SHADERPERMUTATION_COUNT = 32 ///< size of shaderpermutationinfo array } shaderpermutation_t; typedef enum DPSOFTRAST_UNIFORM_e { DPSOFTRAST_UNIFORM_Texture_First, DPSOFTRAST_UNIFORM_Texture_Second, DPSOFTRAST_UNIFORM_Texture_GammaRamps, DPSOFTRAST_UNIFORM_Texture_Normal, DPSOFTRAST_UNIFORM_Texture_Color, DPSOFTRAST_UNIFORM_Texture_Gloss, DPSOFTRAST_UNIFORM_Texture_Glow, DPSOFTRAST_UNIFORM_Texture_SecondaryNormal, DPSOFTRAST_UNIFORM_Texture_SecondaryColor, DPSOFTRAST_UNIFORM_Texture_SecondaryGloss, DPSOFTRAST_UNIFORM_Texture_SecondaryGlow, DPSOFTRAST_UNIFORM_Texture_Pants, DPSOFTRAST_UNIFORM_Texture_Shirt, DPSOFTRAST_UNIFORM_Texture_FogHeightTexture, DPSOFTRAST_UNIFORM_Texture_FogMask, DPSOFTRAST_UNIFORM_Texture_Lightmap, DPSOFTRAST_UNIFORM_Texture_Deluxemap, DPSOFTRAST_UNIFORM_Texture_Attenuation, DPSOFTRAST_UNIFORM_Texture_Cube, DPSOFTRAST_UNIFORM_Texture_Refraction, DPSOFTRAST_UNIFORM_Texture_Reflection, DPSOFTRAST_UNIFORM_Texture_ShadowMap2D, DPSOFTRAST_UNIFORM_Texture_CubeProjection, DPSOFTRAST_UNIFORM_Texture_ScreenNormalMap, DPSOFTRAST_UNIFORM_Texture_ScreenDiffuse, DPSOFTRAST_UNIFORM_Texture_ScreenSpecular, DPSOFTRAST_UNIFORM_Texture_ReflectMask, DPSOFTRAST_UNIFORM_Texture_ReflectCube, DPSOFTRAST_UNIFORM_Alpha, DPSOFTRAST_UNIFORM_BloomBlur_Parameters, DPSOFTRAST_UNIFORM_ClientTime, DPSOFTRAST_UNIFORM_Color_Ambient, DPSOFTRAST_UNIFORM_Color_Diffuse, DPSOFTRAST_UNIFORM_Color_Specular, DPSOFTRAST_UNIFORM_Color_Glow, DPSOFTRAST_UNIFORM_Color_Pants, DPSOFTRAST_UNIFORM_Color_Shirt, DPSOFTRAST_UNIFORM_DeferredColor_Ambient, DPSOFTRAST_UNIFORM_DeferredColor_Diffuse, DPSOFTRAST_UNIFORM_DeferredColor_Specular, DPSOFTRAST_UNIFORM_DeferredMod_Diffuse, DPSOFTRAST_UNIFORM_DeferredMod_Specular, DPSOFTRAST_UNIFORM_DistortScaleRefractReflect, DPSOFTRAST_UNIFORM_EyePosition, DPSOFTRAST_UNIFORM_FogColor, DPSOFTRAST_UNIFORM_FogHeightFade, DPSOFTRAST_UNIFORM_FogPlane, DPSOFTRAST_UNIFORM_FogPlaneViewDist, DPSOFTRAST_UNIFORM_FogRangeRecip, DPSOFTRAST_UNIFORM_LightColor, DPSOFTRAST_UNIFORM_LightDir, DPSOFTRAST_UNIFORM_LightPosition, DPSOFTRAST_UNIFORM_OffsetMapping_ScaleSteps, DPSOFTRAST_UNIFORM_PixelSize, DPSOFTRAST_UNIFORM_ReflectColor, DPSOFTRAST_UNIFORM_ReflectFactor, DPSOFTRAST_UNIFORM_ReflectOffset, DPSOFTRAST_UNIFORM_RefractColor, DPSOFTRAST_UNIFORM_Saturation, DPSOFTRAST_UNIFORM_ScreenCenterRefractReflect, DPSOFTRAST_UNIFORM_ScreenScaleRefractReflect, DPSOFTRAST_UNIFORM_ScreenToDepth, DPSOFTRAST_UNIFORM_ShadowMap_Parameters, DPSOFTRAST_UNIFORM_ShadowMap_TextureScale, DPSOFTRAST_UNIFORM_SpecularPower, DPSOFTRAST_UNIFORM_UserVec1, DPSOFTRAST_UNIFORM_UserVec2, DPSOFTRAST_UNIFORM_UserVec3, DPSOFTRAST_UNIFORM_UserVec4, DPSOFTRAST_UNIFORM_ViewTintColor, DPSOFTRAST_UNIFORM_ViewToLightM1, DPSOFTRAST_UNIFORM_ViewToLightM2, DPSOFTRAST_UNIFORM_ViewToLightM3, DPSOFTRAST_UNIFORM_ViewToLightM4, DPSOFTRAST_UNIFORM_ModelToLightM1, DPSOFTRAST_UNIFORM_ModelToLightM2, DPSOFTRAST_UNIFORM_ModelToLightM3, DPSOFTRAST_UNIFORM_ModelToLightM4, DPSOFTRAST_UNIFORM_TexMatrixM1, DPSOFTRAST_UNIFORM_TexMatrixM2, DPSOFTRAST_UNIFORM_TexMatrixM3, DPSOFTRAST_UNIFORM_TexMatrixM4, DPSOFTRAST_UNIFORM_BackgroundTexMatrixM1, DPSOFTRAST_UNIFORM_BackgroundTexMatrixM2, DPSOFTRAST_UNIFORM_BackgroundTexMatrixM3, DPSOFTRAST_UNIFORM_BackgroundTexMatrixM4, DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM1, DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM2, DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM3, DPSOFTRAST_UNIFORM_ModelViewProjectionMatrixM4, DPSOFTRAST_UNIFORM_ModelViewMatrixM1, DPSOFTRAST_UNIFORM_ModelViewMatrixM2, DPSOFTRAST_UNIFORM_ModelViewMatrixM3, DPSOFTRAST_UNIFORM_ModelViewMatrixM4, DPSOFTRAST_UNIFORM_PixelToScreenTexCoord, DPSOFTRAST_UNIFORM_ModelToReflectCubeM1, DPSOFTRAST_UNIFORM_ModelToReflectCubeM2, DPSOFTRAST_UNIFORM_ModelToReflectCubeM3, DPSOFTRAST_UNIFORM_ModelToReflectCubeM4, DPSOFTRAST_UNIFORM_ShadowMapMatrixM1, DPSOFTRAST_UNIFORM_ShadowMapMatrixM2, DPSOFTRAST_UNIFORM_ShadowMapMatrixM3, DPSOFTRAST_UNIFORM_ShadowMapMatrixM4, DPSOFTRAST_UNIFORM_BloomColorSubtract, DPSOFTRAST_UNIFORM_NormalmapScrollBlend, DPSOFTRAST_UNIFORM_OffsetMapping_LodDistance, DPSOFTRAST_UNIFORM_OffsetMapping_Bias, DPSOFTRAST_UNIFORM_TOTAL } DPSOFTRAST_UNIFORM; void DPSOFTRAST_SetShader(int mode, int permutation, int exactspecularmath); #define DPSOFTRAST_Uniform1f(index, v0) DPSOFTRAST_Uniform4f(index, v0, 0, 0, 0) #define DPSOFTRAST_Uniform2f(index, v0, v1) DPSOFTRAST_Uniform4f(index, v0, v1, 0, 0) #define DPSOFTRAST_Uniform3f(index, v0, v1, v2) DPSOFTRAST_Uniform4f(index, v0, v1, v2, 0) void DPSOFTRAST_Uniform4f(DPSOFTRAST_UNIFORM index, float v0, float v1, float v2, float v3); void DPSOFTRAST_Uniform4fv(DPSOFTRAST_UNIFORM index, const float *v); void DPSOFTRAST_UniformMatrix4fv(DPSOFTRAST_UNIFORM index, int arraysize, int transpose, const float *v); void DPSOFTRAST_Uniform1i(DPSOFTRAST_UNIFORM index, int i0); void DPSOFTRAST_DrawTriangles(int firstvertex, int numvertices, int numtriangles, const int *element3i, const unsigned short *element3s); #endif // DPSOFTRAST_H darkplaces/model_sprite.c0000664000175000017500000004422313067716222015024 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // models.c -- model loading and caching // models are the only shared resource between a client and server running // on the same machine. #include "quakedef.h" #include "image.h" cvar_t r_mipsprites = {CVAR_SAVE, "r_mipsprites", "1", "mipmaps sprites so they render faster in the distance and do not display noise artifacts"}; cvar_t r_labelsprites_scale = {CVAR_SAVE, "r_labelsprites_scale", "1", "global scale to apply to label sprites before conversion to HUD coordinates"}; cvar_t r_labelsprites_roundtopixels = {CVAR_SAVE, "r_labelsprites_roundtopixels", "1", "try to make label sprites sharper by rounding their size to 0.5x or 1x and by rounding their position to whole pixels if possible"}; cvar_t r_overheadsprites_perspective = {CVAR_SAVE, "r_overheadsprites_perspective", "5", "fake perspective effect for SPR_OVERHEAD sprites"}; cvar_t r_overheadsprites_pushback = {CVAR_SAVE, "r_overheadsprites_pushback", "15", "how far to pull the SPR_OVERHEAD sprites toward the eye (used to avoid intersections with 3D models)"}; cvar_t r_overheadsprites_scalex = {CVAR_SAVE, "r_overheadsprites_scalex", "1", "additional scale for overhead sprites for x axis"}; cvar_t r_overheadsprites_scaley = {CVAR_SAVE, "r_overheadsprites_scaley", "1", "additional scale for overhead sprites for y axis"}; cvar_t r_track_sprites = {CVAR_SAVE, "r_track_sprites", "1", "track SPR_LABEL* sprites by putting them as indicator at the screen border to rotate to"}; cvar_t r_track_sprites_flags = {CVAR_SAVE, "r_track_sprites_flags", "1", "1: Rotate sprites accordingly, 2: Make it a continuous rotation"}; cvar_t r_track_sprites_scalew = {CVAR_SAVE, "r_track_sprites_scalew", "1", "width scaling of tracked sprites"}; cvar_t r_track_sprites_scaleh = {CVAR_SAVE, "r_track_sprites_scaleh", "1", "height scaling of tracked sprites"}; /* =============== Mod_SpriteInit =============== */ void Mod_SpriteInit (void) { Cvar_RegisterVariable(&r_mipsprites); Cvar_RegisterVariable(&r_labelsprites_scale); Cvar_RegisterVariable(&r_labelsprites_roundtopixels); Cvar_RegisterVariable(&r_overheadsprites_perspective); Cvar_RegisterVariable(&r_overheadsprites_pushback); Cvar_RegisterVariable(&r_overheadsprites_scalex); Cvar_RegisterVariable(&r_overheadsprites_scaley); Cvar_RegisterVariable(&r_track_sprites); Cvar_RegisterVariable(&r_track_sprites_flags); Cvar_RegisterVariable(&r_track_sprites_scalew); Cvar_RegisterVariable(&r_track_sprites_scaleh); } static void Mod_SpriteSetupTexture(texture_t *texture, skinframe_t *skinframe, qboolean fullbright, qboolean additive) { if (!skinframe) skinframe = R_SkinFrame_LoadMissing(); texture->offsetmapping = OFFSETMAPPING_OFF; texture->offsetscale = 1; texture->offsetbias = 0; texture->specularscalemod = 1; texture->specularpowermod = 1; texture->basematerialflags = MATERIALFLAG_WALL; texture->basealpha = 1.0f; if (fullbright) texture->basematerialflags |= MATERIALFLAG_FULLBRIGHT; if (additive) texture->basematerialflags |= MATERIALFLAG_ADD | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; else if (skinframe->hasalpha) texture->basematerialflags |= MATERIALFLAG_ALPHA | MATERIALFLAG_BLENDED | MATERIALFLAG_NOSHADOW; texture->currentmaterialflags = texture->basematerialflags; texture->numskinframes = 1; texture->currentskinframe = texture->skinframes[0] = skinframe; texture->surfaceflags = 0; texture->supercontents = SUPERCONTENTS_SOLID; if (!(texture->basematerialflags & MATERIALFLAG_BLENDED)) texture->supercontents |= SUPERCONTENTS_OPAQUE; texture->transparentsort = TRANSPARENTSORT_DISTANCE; // WHEN ADDING DEFAULTS HERE, REMEMBER TO PUT DEFAULTS IN ALL LOADERS // JUST GREP FOR "specularscalemod = 1". } extern cvar_t gl_texturecompression_sprites; static void Mod_Sprite_SharedSetup(const unsigned char *datapointer, int version, const unsigned int *palette, qboolean additive) { int i, j, groupframes, realframes, x, y, origin[2], width, height; qboolean fullbright; dspriteframetype_t *pinframetype; dspriteframe_t *pinframe; dspritegroup_t *pingroup; dspriteinterval_t *pinintervals; skinframe_t *skinframe; float modelradius, interval; char name[MAX_QPATH], fogname[MAX_QPATH]; const void *startframes; int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | ((gl_texturecompression.integer && gl_texturecompression_sprites.integer) ? TEXF_COMPRESS : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_ALPHA | TEXF_CLAMP; modelradius = 0; if (loadmodel->numframes < 1) Host_Error ("Mod_Sprite_SharedSetup: Invalid # of frames: %d", loadmodel->numframes); // LordHavoc: hack to allow sprites to be non-fullbright fullbright = true; for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) if (loadmodel->name[i] == '!') fullbright = false; // // load the frames // startframes = datapointer; realframes = 0; for (i = 0;i < loadmodel->numframes;i++) { pinframetype = (dspriteframetype_t *)datapointer; datapointer += sizeof(dspriteframetype_t); if (LittleLong (pinframetype->type) == SPR_SINGLE) groupframes = 1; else { pingroup = (dspritegroup_t *)datapointer; datapointer += sizeof(dspritegroup_t); groupframes = LittleLong(pingroup->numframes); datapointer += sizeof(dspriteinterval_t) * groupframes; } for (j = 0;j < groupframes;j++) { pinframe = (dspriteframe_t *)datapointer; if (version == SPRITE32_VERSION) datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height) * 4; else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) datapointer += sizeof(dspriteframe_t) + LittleLong(pinframe->width) * LittleLong(pinframe->height); } realframes += groupframes; } loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * realframes); loadmodel->num_textures = realframes; loadmodel->num_texturesperskin = 1; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); datapointer = (unsigned char *)startframes; realframes = 0; for (i = 0;i < loadmodel->numframes;i++) { pinframetype = (dspriteframetype_t *)datapointer; datapointer += sizeof(dspriteframetype_t); if (LittleLong (pinframetype->type) == SPR_SINGLE) { groupframes = 1; interval = 0.1f; } else { pingroup = (dspritegroup_t *)datapointer; datapointer += sizeof(dspritegroup_t); groupframes = LittleLong(pingroup->numframes); pinintervals = (dspriteinterval_t *)datapointer; datapointer += sizeof(dspriteinterval_t) * groupframes; interval = LittleFloat(pinintervals[0].interval); if (interval < 0.01f) Host_Error("Mod_Sprite_SharedSetup: invalid interval"); } dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); loadmodel->animscenes[i].firstframe = realframes; loadmodel->animscenes[i].framecount = groupframes; loadmodel->animscenes[i].framerate = 1.0f / interval; loadmodel->animscenes[i].loop = true; for (j = 0;j < groupframes;j++) { pinframe = (dspriteframe_t *)datapointer; datapointer += sizeof(dspriteframe_t); origin[0] = LittleLong (pinframe->origin[0]); origin[1] = LittleLong (pinframe->origin[1]); width = LittleLong (pinframe->width); height = LittleLong (pinframe->height); loadmodel->sprite.sprdata_frames[realframes].left = origin[0]; loadmodel->sprite.sprdata_frames[realframes].right = origin[0] + width; loadmodel->sprite.sprdata_frames[realframes].up = origin[1]; loadmodel->sprite.sprdata_frames[realframes].down = origin[1] - height; x = (int)max(loadmodel->sprite.sprdata_frames[realframes].left * loadmodel->sprite.sprdata_frames[realframes].left, loadmodel->sprite.sprdata_frames[realframes].right * loadmodel->sprite.sprdata_frames[realframes].right); y = (int)max(loadmodel->sprite.sprdata_frames[realframes].up * loadmodel->sprite.sprdata_frames[realframes].up, loadmodel->sprite.sprdata_frames[realframes].down * loadmodel->sprite.sprdata_frames[realframes].down); if (modelradius < x + y) modelradius = x + y; if (cls.state != ca_dedicated) { skinframe = NULL; // note: Nehahra's null.spr has width == 0 and height == 0 if (width > 0 && height > 0) { if (groupframes > 1) { dpsnprintf (name, sizeof(name), "%s_%i_%i", loadmodel->name, i, j); dpsnprintf (fogname, sizeof(fogname), "%s_%i_%ifog", loadmodel->name, i, j); } else { dpsnprintf (name, sizeof(name), "%s_%i", loadmodel->name, i); dpsnprintf (fogname, sizeof(fogname), "%s_%ifog", loadmodel->name, i); } if (!(skinframe = R_SkinFrame_LoadExternal(name, texflags | TEXF_COMPRESS, false))) { unsigned char *pixels = (unsigned char *) Mem_Alloc(loadmodel->mempool, width*height*4); if (version == SPRITE32_VERSION) { for (x = 0;x < width*height;x++) { pixels[x*4+2] = datapointer[x*4+0]; pixels[x*4+1] = datapointer[x*4+1]; pixels[x*4+0] = datapointer[x*4+2]; pixels[x*4+3] = datapointer[x*4+3]; } } else //if (version == SPRITEHL_VERSION || version == SPRITE_VERSION) Image_Copy8bitBGRA(datapointer, pixels, width*height, palette ? palette : palette_bgra_transparent); skinframe = R_SkinFrame_LoadInternalBGRA(name, texflags, pixels, width, height, false); // texflags |= TEXF_COMPRESS; Mem_Free(pixels); } } if (skinframe == NULL) skinframe = R_SkinFrame_LoadMissing(); Mod_SpriteSetupTexture(&loadmodel->data_textures[realframes], skinframe, fullbright, additive); } if (version == SPRITE32_VERSION) datapointer += width * height * 4; else //if (version == SPRITE_VERSION || version == SPRITEHL_VERSION) datapointer += width * height; realframes++; } } modelradius = sqrt(modelradius); for (i = 0;i < 3;i++) { loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; } loadmodel->radius = modelradius; loadmodel->radius2 = modelradius * modelradius; } void Mod_IDSP_Load(dp_model_t *mod, void *buffer, void *bufferend) { int version; const unsigned char *datapointer; datapointer = (unsigned char *)buffer; loadmodel->modeldatatypestring = "SPR1"; loadmodel->type = mod_sprite; loadmodel->DrawSky = NULL; loadmodel->Draw = R_Model_Sprite_Draw; loadmodel->DrawDepth = NULL; loadmodel->CompileShadowVolume = NULL; loadmodel->DrawShadowVolume = NULL; loadmodel->DrawLight = NULL; loadmodel->DrawAddWaterPlanes = NULL; version = LittleLong(((dsprite_t *)buffer)->version); if (version == SPRITE_VERSION || version == SPRITE32_VERSION) { dsprite_t *pinqsprite; pinqsprite = (dsprite_t *)datapointer; datapointer += sizeof(dsprite_t); loadmodel->numframes = LittleLong (pinqsprite->numframes); loadmodel->sprite.sprnum_type = LittleLong (pinqsprite->type); loadmodel->synctype = (synctype_t)LittleLong (pinqsprite->synctype); Mod_Sprite_SharedSetup(datapointer, LittleLong (pinqsprite->version), NULL, false); } else if (version == SPRITEHL_VERSION) { int i, rendermode; unsigned char palette[256][4]; const unsigned char *in; dspritehl_t *pinhlsprite; pinhlsprite = (dspritehl_t *)datapointer; datapointer += sizeof(dspritehl_t); loadmodel->numframes = LittleLong (pinhlsprite->numframes); loadmodel->sprite.sprnum_type = LittleLong (pinhlsprite->type); loadmodel->synctype = (synctype_t)LittleLong (pinhlsprite->synctype); rendermode = pinhlsprite->rendermode; in = datapointer; datapointer += 2; i = in[0] + in[1] * 256; if (i != 256) Host_Error ("Mod_IDSP_Load: unexpected number of palette colors %i (should be 256)", i); in = datapointer; datapointer += 768; switch(rendermode) { case SPRHL_OPAQUE: for (i = 0;i < 256;i++) { palette[i][2] = in[i*3+0]; palette[i][1] = in[i*3+1]; palette[i][0] = in[i*3+2]; palette[i][3] = 255; } break; case SPRHL_ADDITIVE: for (i = 0;i < 256;i++) { palette[i][2] = in[i*3+0]; palette[i][1] = in[i*3+1]; palette[i][0] = in[i*3+2]; palette[i][3] = 255; } // also passes additive == true to Mod_Sprite_SharedSetup break; case SPRHL_INDEXALPHA: for (i = 0;i < 256;i++) { palette[i][2] = in[765]; palette[i][1] = in[766]; palette[i][0] = in[767]; palette[i][3] = i; in += 3; } break; case SPRHL_ALPHATEST: for (i = 0;i < 256;i++) { palette[i][2] = in[i*3+0]; palette[i][1] = in[i*3+1]; palette[i][0] = in[i*3+2]; palette[i][3] = 255; } palette[255][0] = palette[255][1] = palette[255][2] = palette[255][3] = 0; // should this use alpha test or alpha blend? (currently blend) break; default: Host_Error("Mod_IDSP_Load: unknown texFormat (%i, should be 0, 1, 2, or 3)", i); return; } Mod_Sprite_SharedSetup(datapointer, LittleLong (pinhlsprite->version), (unsigned int *)(&palette[0][0]), rendermode == SPRHL_ADDITIVE); } else Host_Error("Mod_IDSP_Load: %s has wrong version number (%i). Only %i (quake), %i (HalfLife), and %i (sprite32) supported", loadmodel->name, version, SPRITE_VERSION, SPRITEHL_VERSION, SPRITE32_VERSION); // TODO: Note that isanimated only means whether vertices change due to // the animation. This may happen due to sprframe parameters changing. // Mere texture chanegs OTOH shouldn't require isanimated to be 1. loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); } void Mod_IDS2_Load(dp_model_t *mod, void *buffer, void *bufferend) { int i, version; qboolean fullbright; const dsprite2_t *pinqsprite; skinframe_t *skinframe; float modelradius; int texflags = (r_mipsprites.integer ? TEXF_MIPMAP : 0) | TEXF_ISSPRITE | TEXF_PICMIP | TEXF_COMPRESS | TEXF_ALPHA | TEXF_CLAMP; loadmodel->modeldatatypestring = "SPR2"; loadmodel->type = mod_sprite; loadmodel->DrawSky = NULL; loadmodel->Draw = R_Model_Sprite_Draw; loadmodel->DrawDepth = NULL; loadmodel->CompileShadowVolume = NULL; loadmodel->DrawShadowVolume = NULL; loadmodel->DrawLight = NULL; loadmodel->DrawAddWaterPlanes = NULL; pinqsprite = (dsprite2_t *)buffer; version = LittleLong(pinqsprite->version); if (version != SPRITE2_VERSION) Host_Error("Mod_IDS2_Load: %s has wrong version number (%i should be 2 (quake 2)", loadmodel->name, version); loadmodel->numframes = LittleLong (pinqsprite->numframes); if (loadmodel->numframes < 1) Host_Error ("Mod_IDS2_Load: Invalid # of frames: %d", loadmodel->numframes); loadmodel->sprite.sprnum_type = SPR_VP_PARALLEL; loadmodel->synctype = ST_SYNC; // LordHavoc: hack to allow sprites to be non-fullbright fullbright = true; for (i = 0;i < MAX_QPATH && loadmodel->name[i];i++) if (loadmodel->name[i] == '!') fullbright = false; loadmodel->animscenes = (animscene_t *)Mem_Alloc(loadmodel->mempool, sizeof(animscene_t) * loadmodel->numframes); loadmodel->sprite.sprdata_frames = (mspriteframe_t *)Mem_Alloc(loadmodel->mempool, sizeof(mspriteframe_t) * loadmodel->numframes); loadmodel->num_textures = loadmodel->numframes; loadmodel->num_texturesperskin = 1; loadmodel->data_textures = (texture_t *)Mem_Alloc(loadmodel->mempool, sizeof(texture_t) * loadmodel->num_textures); modelradius = 0; for (i = 0;i < loadmodel->numframes;i++) { int origin[2], x, y, width, height; const dsprite2frame_t *pinframe; mspriteframe_t *sprframe; dpsnprintf(loadmodel->animscenes[i].name, sizeof(loadmodel->animscenes[i].name), "frame %i", i); loadmodel->animscenes[i].firstframe = i; loadmodel->animscenes[i].framecount = 1; loadmodel->animscenes[i].framerate = 10; loadmodel->animscenes[i].loop = true; pinframe = &pinqsprite->frames[i]; origin[0] = LittleLong (pinframe->origin_x); origin[1] = LittleLong (pinframe->origin_y); width = LittleLong (pinframe->width); height = LittleLong (pinframe->height); sprframe = &loadmodel->sprite.sprdata_frames[i]; // note that sp2 origin[0] is positive, where as it is negative in // spr/spr32/hlspr sprframe->left = -origin[0]; sprframe->right = -origin[0] + width; sprframe->up = origin[1]; sprframe->down = origin[1] - height; x = (int)max(sprframe->left * sprframe->left, sprframe->right * sprframe->right); y = (int)max(sprframe->up * sprframe->up, sprframe->down * sprframe->down); if (modelradius < x + y) modelradius = x + y; } if (cls.state != ca_dedicated) { for (i = 0;i < loadmodel->numframes;i++) { const dsprite2frame_t *pinframe; pinframe = &pinqsprite->frames[i]; if (!(skinframe = R_SkinFrame_LoadExternal(pinframe->name, texflags, false))) { Con_Printf("Mod_IDS2_Load: failed to load %s", pinframe->name); skinframe = R_SkinFrame_LoadMissing(); } Mod_SpriteSetupTexture(&loadmodel->data_textures[i], skinframe, fullbright, false); } } modelradius = sqrt(modelradius); for (i = 0;i < 3;i++) { loadmodel->normalmins[i] = loadmodel->yawmins[i] = loadmodel->rotatedmins[i] = -modelradius; loadmodel->normalmaxs[i] = loadmodel->yawmaxs[i] = loadmodel->rotatedmaxs[i] = modelradius; } loadmodel->radius = modelradius; loadmodel->radius2 = modelradius * modelradius; // TODO: Note that isanimated only means whether vertices change due to // the animation. This may happen due to sprframe parameters changing. // Mere texture chanegs OTOH shouldn't require isanimated to be 1. loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || (loadmodel->animscenes && loadmodel->animscenes[0].framecount > 1); } darkplaces/prvm_cmds.c0000664000175000017500000054437013067716222014340 0ustar kalevkalev// AK // Basically every vm builtin cmd should be in here. // All 3 builtin and extension lists can be found here // cause large (I think they will) parts are from pr_cmds the same copyright like in pr_cmds // also applies here #include "quakedef.h" #include "prvm_cmds.h" #include "libcurl.h" #include #include "cl_collision.h" #include "clvm_cmds.h" #include "csprogs.h" #include "ft2.h" #include "mdfour.h" extern cvar_t prvm_backtraceforwarnings; #ifdef USEODE extern dllhandle_t ode_dll; #endif // LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value void VM_Warning(prvm_prog_t *prog, const char *fmt, ...) { va_list argptr; char msg[MAX_INPUTLINE]; static double recursive = -1; va_start(argptr,fmt); dpvsnprintf(msg,sizeof(msg),fmt,argptr); va_end(argptr); Con_Print(msg); // TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Con_Printf [9/13/2006 Black] if(prvm_backtraceforwarnings.integer && recursive != realtime) // NOTE: this compares to the time, just in case if PRVM_PrintState causes a Host_Error and keeps recursive set { recursive = realtime; PRVM_PrintState(prog, 0); recursive = -1; } } //============================================================================ // Common // TODO DONE: move vm_files and vm_fssearchlist to prvm_prog_t struct // TODO: move vm_files and vm_fssearchlist back [9/13/2006 Black] // TODO: (move vm_files and vm_fssearchlist to prvm_prog_t struct again) [2007-01-23 LordHavoc] // TODO: will this war ever end? [2007-01-23 LordHavoc] void VM_CheckEmptyString(prvm_prog_t *prog, const char *s) { if (ISWHITESPACE(s[0])) prog->error_cmd("%s: Bad string", prog->name); } void VM_GenerateFrameGroupBlend(prvm_prog_t *prog, framegroupblend_t *framegroupblend, const prvm_edict_t *ed) { // self.frame is the interpolation target (new frame) // self.frame1time is the animation base time for the interpolation target // self.frame2 is the interpolation start (previous frame) // self.frame2time is the animation base time for the interpolation start // self.lerpfrac is the interpolation strength for self.frame2 // self.lerpfrac3 is the interpolation strength for self.frame3 // self.lerpfrac4 is the interpolation strength for self.frame4 // pitch angle on a player model where the animator set up 5 sets of // animations and the csqc simply lerps between sets) framegroupblend[0].frame = (int) PRVM_gameedictfloat(ed, frame ); framegroupblend[1].frame = (int) PRVM_gameedictfloat(ed, frame2 ); framegroupblend[2].frame = (int) PRVM_gameedictfloat(ed, frame3 ); framegroupblend[3].frame = (int) PRVM_gameedictfloat(ed, frame4 ); framegroupblend[0].start = PRVM_gameedictfloat(ed, frame1time); framegroupblend[1].start = PRVM_gameedictfloat(ed, frame2time); framegroupblend[2].start = PRVM_gameedictfloat(ed, frame3time); framegroupblend[3].start = PRVM_gameedictfloat(ed, frame4time); framegroupblend[1].lerp = PRVM_gameedictfloat(ed, lerpfrac ); framegroupblend[2].lerp = PRVM_gameedictfloat(ed, lerpfrac3 ); framegroupblend[3].lerp = PRVM_gameedictfloat(ed, lerpfrac4 ); // assume that the (missing) lerpfrac1 is whatever remains after lerpfrac2+lerpfrac3+lerpfrac4 are summed framegroupblend[0].lerp = 1 - framegroupblend[1].lerp - framegroupblend[2].lerp - framegroupblend[3].lerp; } // LordHavoc: quite tempting to break apart this function to reuse the // duplicated code, but I suspect it is better for performance // this way void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model, double curtime) { int sub2, numframes, f, i, k; int isfirstframegroup = true; int nolerp; double sublerp, lerp, d; const animscene_t *scene; const framegroupblend_t *g; frameblend_t *blend = frameblend; memset(blend, 0, MAX_FRAMEBLENDS * sizeof(*blend)); // rpolzer: Not testing isanimated here - a model might have // "animations" that move no vertices (but only bones), thus rendering // may assume it's not animated while processing can't. if (!model) { blend[0].lerp = 1; return; } nolerp = (model->type == mod_sprite) ? !r_lerpsprites.integer : !r_lerpmodels.integer; numframes = model->numframes; for (k = 0, g = framegroupblend;k < MAX_FRAMEGROUPBLENDS;k++, g++) { f = g->frame; if ((unsigned int)f >= (unsigned int)numframes) { if (developer_extra.integer) Con_DPrintf("VM_FrameBlendFromFrameGroupBlend: no such frame %d in model %s\n", f, model->name); f = 0; } d = lerp = g->lerp; if (lerp <= 0) continue; if (nolerp) { if (isfirstframegroup) { d = lerp = 1; isfirstframegroup = false; } else continue; } if (model->animscenes) { scene = model->animscenes + f; f = scene->firstframe; if (scene->framecount > 1) { // this code path is only used on .zym models and torches sublerp = scene->framerate * (curtime - g->start); f = (int) floor(sublerp); sublerp -= f; sub2 = f + 1; if (sublerp < (1.0 / 65536.0f)) sublerp = 0; if (sublerp > (65535.0f / 65536.0f)) sublerp = 1; if (nolerp) sublerp = 0; if (scene->loop) { f = (f % scene->framecount); sub2 = (sub2 % scene->framecount); } f = bound(0, f, (scene->framecount - 1)) + scene->firstframe; sub2 = bound(0, sub2, (scene->framecount - 1)) + scene->firstframe; d = sublerp * lerp; // two framelerps produced from one animation if (d > 0) { for (i = 0;i < MAX_FRAMEBLENDS;i++) { if (blend[i].lerp <= 0 || blend[i].subframe == sub2) { blend[i].subframe = sub2; blend[i].lerp += d; break; } } } d = (1 - sublerp) * lerp; } } if (d > 0) { for (i = 0;i < MAX_FRAMEBLENDS;i++) { if (blend[i].lerp <= 0 || blend[i].subframe == f) { blend[i].subframe = f; blend[i].lerp += d; break; } } } } } void VM_UpdateEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend) { if (ed->priv.server->skeleton.model != edmodel) { VM_RemoveEdictSkeleton(prog, ed); ed->priv.server->skeleton.model = edmodel; } if (!ed->priv.server->skeleton.model || !ed->priv.server->skeleton.model->num_bones) { if(ed->priv.server->skeleton.relativetransforms) Mem_Free(ed->priv.server->skeleton.relativetransforms); ed->priv.server->skeleton.relativetransforms = NULL; return; } { int skeletonindex = -1; skeleton_t *skeleton; skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; if (skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones) { // custom skeleton controlled by the game (FTE_CSQC_SKELETONOBJECTS) if (!ed->priv.server->skeleton.relativetransforms) ed->priv.server->skeleton.relativetransforms = (matrix4x4_t *)Mem_Alloc(prog->progs_mempool, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); memcpy(ed->priv.server->skeleton.relativetransforms, skeleton->relativetransforms, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t)); } else { if(ed->priv.server->skeleton.relativetransforms) Mem_Free(ed->priv.server->skeleton.relativetransforms); ed->priv.server->skeleton.relativetransforms = NULL; } } } void VM_RemoveEdictSkeleton(prvm_prog_t *prog, prvm_edict_t *ed) { if (ed->priv.server->skeleton.relativetransforms) Mem_Free(ed->priv.server->skeleton.relativetransforms); memset(&ed->priv.server->skeleton, 0, sizeof(ed->priv.server->skeleton)); } //============================================================================ //BUILT-IN FUNCTIONS void VM_VarString(prvm_prog_t *prog, int first, char *out, int outlength) { int i; const char *s; char *outend; outend = out + outlength - 1; for (i = first;i < prog->argc && out < outend;i++) { s = PRVM_G_STRING((OFS_PARM0+i*3)); while (out < outend && *s) *out++ = *s++; } *out++ = 0; } /* ================= VM_checkextension returns true if the extension is supported by the server checkextension(extensionname) ================= */ // kind of helper function static qboolean checkextension(prvm_prog_t *prog, const char *name) { int len; const char *e, *start; len = (int)strlen(name); for (e = prog->extensionstring;*e;e++) { while (*e == ' ') e++; if (!*e) break; start = e; while (*e && *e != ' ') e++; if ((e - start) == len && !strncasecmp(start, name, len)) { #ifdef USEODE // special sheck for ODE if (!strncasecmp("DP_PHYSICS_ODE", name, 14)) { #ifndef LINK_TO_LIBODE return ode_dll ? true : false; #else #ifdef LINK_TO_LIBODE return true; #else return false; #endif #endif } #endif // special sheck for d0_blind_id if (!strcasecmp("DP_CRYPTO", name)) return Crypto_Available(); if (!strcasecmp("DP_QC_DIGEST_SHA256", name)) return Crypto_Available(); return true; } } return false; } void VM_checkextension(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_checkextension); PRVM_G_FLOAT(OFS_RETURN) = checkextension(prog, PRVM_G_STRING(OFS_PARM0)); } /* ================= VM_error This is a TERMINAL error, which will kill off the entire prog. Dumps self. error(value) ================= */ void VM_error(prvm_prog_t *prog) { prvm_edict_t *ed; char string[VM_STRINGTEMP_LENGTH]; VM_VarString(prog, 0, string, sizeof(string)); Con_Printf("======%s ERROR in %s:\n%s\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); PRVM_ED_Print(prog, ed, NULL); prog->error_cmd("%s: Program error in function %s:\n%s\nTip: read above for entity information\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); } /* ================= VM_objerror Dumps out self, then an error message. The program is aborted and self is removed, but the level can continue. objerror(value) ================= */ void VM_objerror(prvm_prog_t *prog) { prvm_edict_t *ed; char string[VM_STRINGTEMP_LENGTH]; VM_VarString(prog, 0, string, sizeof(string)); Con_Printf("======OBJECT ERROR======\n"); // , prog->name, PRVM_GetString(prog->xfunction->s_name), string); // or include them? FIXME ed = PRVM_PROG_TO_EDICT(PRVM_allglobaledict(self)); PRVM_ED_Print(prog, ed, NULL); PRVM_ED_Free (prog, ed); Con_Printf("%s OBJECT ERROR in %s:\n%s\nTip: read above for entity information\n", prog->name, PRVM_GetString(prog, prog->xfunction->s_name), string); } /* ================= VM_print print to console print(...[string]) ================= */ void VM_print(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_VarString(prog, 0, string, sizeof(string)); Con_Print(string); } /* ================= VM_bprint broadcast print to everyone on server bprint(...[string]) ================= */ void VM_bprint(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; if(!sv.active) { VM_Warning(prog, "VM_bprint: game is not server(%s) !\n", prog->name); return; } VM_VarString(prog, 0, string, sizeof(string)); SV_BroadcastPrint(string); } /* ================= VM_sprint (menu & client but only if server.active == true) single print to a specific client sprint(float clientnum,...[string]) ================= */ void VM_sprint(prvm_prog_t *prog) { client_t *client; int clientnum; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_sprint); //find client for this entity clientnum = (int)PRVM_G_FLOAT(OFS_PARM0); if (!sv.active || clientnum < 0 || clientnum >= svs.maxclients || !svs.clients[clientnum].active) { VM_Warning(prog, "VM_sprint: %s: invalid client or server is not active !\n", prog->name); return; } client = svs.clients + clientnum; if (!client->netconnection) return; VM_VarString(prog, 1, string, sizeof(string)); MSG_WriteChar(&client->netconnection->message,svc_print); MSG_WriteString(&client->netconnection->message, string); } /* ================= VM_centerprint single print to the screen centerprint(value) ================= */ void VM_centerprint(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_centerprint); VM_VarString(prog, 0, string, sizeof(string)); SCR_CenterPrint(string); } /* ================= VM_normalize vector normalize(vector) ================= */ void VM_normalize(prvm_prog_t *prog) { prvm_vec_t *value1; vec3_t newvalue; double f; VM_SAFEPARMCOUNT(1,VM_normalize); value1 = PRVM_G_VECTOR(OFS_PARM0); f = VectorLength2(value1); if (f) { f = 1.0 / sqrt(f); VectorScale(value1, f, newvalue); } else VectorClear(newvalue); VectorCopy (newvalue, PRVM_G_VECTOR(OFS_RETURN)); } /* ================= VM_vlen scalar vlen(vector) ================= */ void VM_vlen(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_vlen); PRVM_G_FLOAT(OFS_RETURN) = VectorLength(PRVM_G_VECTOR(OFS_PARM0)); } /* ================= VM_vectoyaw float vectoyaw(vector) ================= */ void VM_vectoyaw(prvm_prog_t *prog) { prvm_vec_t *value1; prvm_vec_t yaw; VM_SAFEPARMCOUNT(1,VM_vectoyaw); value1 = PRVM_G_VECTOR(OFS_PARM0); if (value1[1] == 0 && value1[0] == 0) yaw = 0; else { yaw = (int) (atan2(value1[1], value1[0]) * 180 / M_PI); if (yaw < 0) yaw += 360; } PRVM_G_FLOAT(OFS_RETURN) = yaw; } /* ================= VM_vectoangles vector vectoangles(vector[, vector]) ================= */ void VM_vectoangles(prvm_prog_t *prog) { vec3_t result, forward, up; VM_SAFEPARMCOUNTRANGE(1, 2,VM_vectoangles); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), forward); if (prog->argc >= 2) { VectorCopy(PRVM_G_VECTOR(OFS_PARM1), up); AnglesFromVectors(result, forward, up, true); } else AnglesFromVectors(result, forward, NULL, true); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); } /* ================= VM_random Returns a number from 0<= num < 1 float random() ================= */ void VM_random(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_random); PRVM_G_FLOAT(OFS_RETURN) = lhrandom(0, 1); } /* ========= VM_localsound localsound(string sample) ========= */ void VM_localsound(prvm_prog_t *prog) { const char *s; VM_SAFEPARMCOUNT(1,VM_localsound); s = PRVM_G_STRING(OFS_PARM0); if(!S_LocalSound (s)) { PRVM_G_FLOAT(OFS_RETURN) = -4; VM_Warning(prog, "VM_localsound: Failed to play %s for %s !\n", s, prog->name); return; } PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ================= VM_break break() ================= */ void VM_break(prvm_prog_t *prog) { prog->error_cmd("%s: break statement", prog->name); } //============================================================================ /* ================= VM_localcmd Sends text over to the client's execution buffer [localcmd (string, ...) or] cmd (string, ...) ================= */ void VM_localcmd(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_localcmd); VM_VarString(prog, 0, string, sizeof(string)); Cbuf_AddText(string); } static qboolean PRVM_Cvar_ReadOk(const char *string) { cvar_t *cvar; cvar = Cvar_FindVar(string); return ((cvar) && ((cvar->flags & CVAR_PRIVATE) == 0)); } /* ================= VM_cvar float cvar (string) ================= */ void VM_cvar(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); VM_VarString(prog, 0, string, sizeof(string)); VM_CheckEmptyString(prog, string); PRVM_G_FLOAT(OFS_RETURN) = PRVM_Cvar_ReadOk(string) ? Cvar_VariableValue(string) : 0; } /* ================= VM_cvar float cvar_type (string) float CVAR_TYPEFLAG_EXISTS = 1; float CVAR_TYPEFLAG_SAVED = 2; float CVAR_TYPEFLAG_PRIVATE = 4; float CVAR_TYPEFLAG_ENGINE = 8; float CVAR_TYPEFLAG_HASDESCRIPTION = 16; float CVAR_TYPEFLAG_READONLY = 32; ================= */ void VM_cvar_type(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; cvar_t *cvar; int ret; VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar); VM_VarString(prog, 0, string, sizeof(string)); VM_CheckEmptyString(prog, string); cvar = Cvar_FindVar(string); if(!cvar) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; // CVAR_TYPE_NONE } ret = 1; // CVAR_EXISTS if(cvar->flags & CVAR_SAVE) ret |= 2; // CVAR_TYPE_SAVED if(cvar->flags & CVAR_PRIVATE) ret |= 4; // CVAR_TYPE_PRIVATE if(!(cvar->flags & CVAR_ALLOCATED)) ret |= 8; // CVAR_TYPE_ENGINE if(cvar->description != cvar_dummy_description) ret |= 16; // CVAR_TYPE_HASDESCRIPTION if(cvar->flags & CVAR_READONLY) ret |= 32; // CVAR_TYPE_READONLY PRVM_G_FLOAT(OFS_RETURN) = ret; } /* ================= VM_cvar_string const string VM_cvar_string (string, ...) ================= */ void VM_cvar_string(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_string); VM_VarString(prog, 0, string, sizeof(string)); VM_CheckEmptyString(prog, string); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, PRVM_Cvar_ReadOk(string) ? Cvar_VariableString(string) : ""); } /* ======================== VM_cvar_defstring const string VM_cvar_defstring (string, ...) ======================== */ void VM_cvar_defstring(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_defstring); VM_VarString(prog, 0, string, sizeof(string)); VM_CheckEmptyString(prog, string); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Cvar_VariableDefString(string)); } /* ======================== VM_cvar_defstring const string VM_cvar_description (string, ...) ======================== */ void VM_cvar_description(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1,8,VM_cvar_description); VM_VarString(prog, 0, string, sizeof(string)); VM_CheckEmptyString(prog, string); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Cvar_VariableDescription(string)); } /* ================= VM_cvar_set void cvar_set (string,string, ...) ================= */ void VM_cvar_set(prvm_prog_t *prog) { const char *name; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2,8,VM_cvar_set); VM_VarString(prog, 1, string, sizeof(string)); name = PRVM_G_STRING(OFS_PARM0); VM_CheckEmptyString(prog, name); Cvar_Set(name, string); } /* ========= VM_dprint dprint(...[string]) ========= */ void VM_dprint(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_dprint); VM_VarString(prog, 0, string, sizeof(string)); #if 1 Con_DPrintf("%s", string); #else Con_DPrintf("%s: %s", prog->name, string); #endif } /* ========= VM_ftos string ftos(float) ========= */ void VM_ftos(prvm_prog_t *prog) { prvm_vec_t v; char s[128]; VM_SAFEPARMCOUNT(1, VM_ftos); v = PRVM_G_FLOAT(OFS_PARM0); if ((prvm_vec_t)((prvm_int_t)v) == v) dpsnprintf(s, sizeof(s), "%.0f", v); else dpsnprintf(s, sizeof(s), "%f", v); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); } /* ========= VM_fabs float fabs(float) ========= */ void VM_fabs(prvm_prog_t *prog) { prvm_vec_t v; VM_SAFEPARMCOUNT(1,VM_fabs); v = PRVM_G_FLOAT(OFS_PARM0); PRVM_G_FLOAT(OFS_RETURN) = fabs(v); } /* ========= VM_vtos string vtos(vector) ========= */ void VM_vtos(prvm_prog_t *prog) { char s[512]; VM_SAFEPARMCOUNT(1,VM_vtos); dpsnprintf (s, sizeof(s), "'%5.1f %5.1f %5.1f'", PRVM_G_VECTOR(OFS_PARM0)[0], PRVM_G_VECTOR(OFS_PARM0)[1], PRVM_G_VECTOR(OFS_PARM0)[2]); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); } /* ========= VM_etos string etos(entity) ========= */ void VM_etos(prvm_prog_t *prog) { char s[128]; VM_SAFEPARMCOUNT(1, VM_etos); dpsnprintf (s, sizeof(s), "entity %i", PRVM_G_EDICTNUM(OFS_PARM0)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); } /* ========= VM_stof float stof(...[string]) ========= */ void VM_stof(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_stof); VM_VarString(prog, 0, string, sizeof(string)); PRVM_G_FLOAT(OFS_RETURN) = atof(string); } /* ======================== VM_itof float itof(int ent) ======================== */ void VM_itof(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_itof); PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); } /* ======================== VM_ftoe entity ftoe(float num) ======================== */ void VM_ftoe(prvm_prog_t *prog) { prvm_int_t ent; VM_SAFEPARMCOUNT(1, VM_ftoe); ent = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM0); if (ent < 0 || ent >= prog->max_edicts || PRVM_PROG_TO_EDICT(ent)->priv.required->free) ent = 0; // return world instead of a free or invalid entity PRVM_G_INT(OFS_RETURN) = ent; } /* ======================== VM_etof float etof(entity ent) ======================== */ void VM_etof(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_etof); PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICTNUM(OFS_PARM0); } /* ========= VM_strftime string strftime(float uselocaltime, string[, string ...]) ========= */ void VM_strftime(prvm_prog_t *prog) { time_t t; #if _MSC_VER >= 1400 struct tm tm; int tmresult; #else struct tm *tm; #endif char fmt[VM_STRINGTEMP_LENGTH]; char result[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_strftime); VM_VarString(prog, 1, fmt, sizeof(fmt)); t = time(NULL); #if _MSC_VER >= 1400 if (PRVM_G_FLOAT(OFS_PARM0)) tmresult = localtime_s(&tm, &t); else tmresult = gmtime_s(&tm, &t); if (!tmresult) #else if (PRVM_G_FLOAT(OFS_PARM0)) tm = localtime(&t); else tm = gmtime(&t); if (!tm) #endif { PRVM_G_INT(OFS_RETURN) = 0; return; } #if _MSC_VER >= 1400 strftime(result, sizeof(result), fmt, &tm); #else strftime(result, sizeof(result), fmt, tm); #endif PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, result); } /* ========= VM_spawn entity spawn() ========= */ void VM_spawn(prvm_prog_t *prog) { prvm_edict_t *ed; VM_SAFEPARMCOUNT(0, VM_spawn); prog->xfunction->builtinsprofile += 20; ed = PRVM_ED_Alloc(prog); VM_RETURN_EDICT(ed); } /* ========= VM_remove remove(entity e) ========= */ void VM_remove(prvm_prog_t *prog) { prvm_edict_t *ed; prog->xfunction->builtinsprofile += 20; VM_SAFEPARMCOUNT(1, VM_remove); ed = PRVM_G_EDICT(OFS_PARM0); if( PRVM_NUM_FOR_EDICT(ed) <= prog->reserved_edicts ) { if (developer.integer > 0) VM_Warning(prog, "VM_remove: tried to remove the null entity or a reserved entity!\n" ); } else if( ed->priv.required->free ) { if (developer.integer > 0) VM_Warning(prog, "VM_remove: tried to remove an already freed entity!\n" ); } else PRVM_ED_Free (prog, ed); } /* ========= VM_find entity find(entity start, .string field, string match) ========= */ void VM_find(prvm_prog_t *prog) { int e; int f; const char *s, *t; prvm_edict_t *ed; VM_SAFEPARMCOUNT(3,VM_find); e = PRVM_G_EDICTNUM(OFS_PARM0); f = PRVM_G_INT(OFS_PARM1); s = PRVM_G_STRING(OFS_PARM2); // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and // expects it to find all the monsters, so we must be careful to support // searching for "" for (e++ ; e < prog->num_edicts ; e++) { prog->xfunction->builtinsprofile++; ed = PRVM_EDICT_NUM(e); if (ed->priv.required->free) continue; t = PRVM_E_STRING(ed,f); if (!t) t = ""; if (!strcmp(t,s)) { VM_RETURN_EDICT(ed); return; } } VM_RETURN_EDICT(prog->edicts); } /* ========= VM_findfloat entity findfloat(entity start, .float field, float match) entity findentity(entity start, .entity field, entity match) ========= */ // LordHavoc: added this for searching float, int, and entity reference fields void VM_findfloat(prvm_prog_t *prog) { int e; int f; float s; prvm_edict_t *ed; VM_SAFEPARMCOUNT(3,VM_findfloat); e = PRVM_G_EDICTNUM(OFS_PARM0); f = PRVM_G_INT(OFS_PARM1); s = PRVM_G_FLOAT(OFS_PARM2); for (e++ ; e < prog->num_edicts ; e++) { prog->xfunction->builtinsprofile++; ed = PRVM_EDICT_NUM(e); if (ed->priv.required->free) continue; if (PRVM_E_FLOAT(ed,f) == s) { VM_RETURN_EDICT(ed); return; } } VM_RETURN_EDICT(prog->edicts); } /* ========= VM_findchain entity findchain(.string field, string match) ========= */ // chained search for strings in entity fields // entity(.string field, string match) findchain = #402; void VM_findchain(prvm_prog_t *prog) { int i; int f; const char *s, *t; prvm_edict_t *ent, *chain; int chainfield; VM_SAFEPARMCOUNTRANGE(2,3,VM_findchain); if(prog->argc == 3) chainfield = PRVM_G_INT(OFS_PARM2); else chainfield = prog->fieldoffsets.chain; if (chainfield < 0) prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); chain = prog->edicts; f = PRVM_G_INT(OFS_PARM0); s = PRVM_G_STRING(OFS_PARM1); // LordHavoc: apparently BloodMage does a find(world, weaponmodel, "") and // expects it to find all the monsters, so we must be careful to support // searching for "" ent = PRVM_NEXT_EDICT(prog->edicts); for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) { prog->xfunction->builtinsprofile++; if (ent->priv.required->free) continue; t = PRVM_E_STRING(ent,f); if (!t) t = ""; if (strcmp(t,s)) continue; PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_NUM_FOR_EDICT(chain); chain = ent; } VM_RETURN_EDICT(chain); } /* ========= VM_findchainfloat entity findchainfloat(.string field, float match) entity findchainentity(.string field, entity match) ========= */ // LordHavoc: chained search for float, int, and entity reference fields // entity(.string field, float match) findchainfloat = #403; void VM_findchainfloat(prvm_prog_t *prog) { int i; int f; float s; prvm_edict_t *ent, *chain; int chainfield; VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainfloat); if(prog->argc == 3) chainfield = PRVM_G_INT(OFS_PARM2); else chainfield = prog->fieldoffsets.chain; if (chainfield < 0) prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); chain = (prvm_edict_t *)prog->edicts; f = PRVM_G_INT(OFS_PARM0); s = PRVM_G_FLOAT(OFS_PARM1); ent = PRVM_NEXT_EDICT(prog->edicts); for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) { prog->xfunction->builtinsprofile++; if (ent->priv.required->free) continue; if (PRVM_E_FLOAT(ent,f) != s) continue; PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); chain = ent; } VM_RETURN_EDICT(chain); } /* ======================== VM_findflags entity findflags(entity start, .float field, float match) ======================== */ // LordHavoc: search for flags in float fields void VM_findflags(prvm_prog_t *prog) { prvm_int_t e; prvm_int_t f; prvm_int_t s; prvm_edict_t *ed; VM_SAFEPARMCOUNT(3, VM_findflags); e = PRVM_G_EDICTNUM(OFS_PARM0); f = PRVM_G_INT(OFS_PARM1); s = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM2); for (e++ ; e < prog->num_edicts ; e++) { prog->xfunction->builtinsprofile++; ed = PRVM_EDICT_NUM(e); if (ed->priv.required->free) continue; if (!PRVM_E_FLOAT(ed,f)) continue; if ((prvm_int_t)PRVM_E_FLOAT(ed,f) & s) { VM_RETURN_EDICT(ed); return; } } VM_RETURN_EDICT(prog->edicts); } /* ======================== VM_findchainflags entity findchainflags(.float field, float match) ======================== */ // LordHavoc: chained search for flags in float fields void VM_findchainflags(prvm_prog_t *prog) { prvm_int_t i; prvm_int_t f; prvm_int_t s; prvm_edict_t *ent, *chain; int chainfield; VM_SAFEPARMCOUNTRANGE(2, 3, VM_findchainflags); if(prog->argc == 3) chainfield = PRVM_G_INT(OFS_PARM2); else chainfield = prog->fieldoffsets.chain; if (chainfield < 0) prog->error_cmd("VM_findchain: %s doesnt have the specified chain field !", prog->name); chain = (prvm_edict_t *)prog->edicts; f = PRVM_G_INT(OFS_PARM0); s = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM1); ent = PRVM_NEXT_EDICT(prog->edicts); for (i = 1;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) { prog->xfunction->builtinsprofile++; if (ent->priv.required->free) continue; if (!PRVM_E_FLOAT(ent,f)) continue; if (!((prvm_int_t)PRVM_E_FLOAT(ent,f) & s)) continue; PRVM_EDICTFIELDEDICT(ent,chainfield) = PRVM_EDICT_TO_PROG(chain); chain = ent; } VM_RETURN_EDICT(chain); } /* ========= VM_precache_sound string precache_sound (string sample) ========= */ void VM_precache_sound(prvm_prog_t *prog) { const char *s; VM_SAFEPARMCOUNT(1, VM_precache_sound); s = PRVM_G_STRING(OFS_PARM0); PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); //VM_CheckEmptyString(prog, s); if(snd_initialized.integer && !S_PrecacheSound(s, true, true)) { VM_Warning(prog, "VM_precache_sound: Failed to load %s for %s\n", s, prog->name); return; } } /* ================= VM_precache_file returns the same string as output does nothing, only used by qcc to build .pak archives ================= */ void VM_precache_file(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_precache_file); // precache_file is only used to copy files with qcc, it does nothing PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); } /* ========= VM_coredump coredump() ========= */ void VM_coredump(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_coredump); Cbuf_AddText("prvm_edicts "); Cbuf_AddText(prog->name); Cbuf_AddText("\n"); } /* ========= VM_stackdump stackdump() ========= */ void VM_stackdump(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_stackdump); PRVM_StackTrace(prog); } /* ========= VM_crash crash() ========= */ void VM_crash(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_crash); prog->error_cmd("Crash called by %s",prog->name); } /* ========= VM_traceon traceon() ========= */ void VM_traceon(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_traceon); prog->trace = true; } /* ========= VM_traceoff traceoff() ========= */ void VM_traceoff(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_traceoff); prog->trace = false; } /* ========= VM_eprint eprint(entity e) ========= */ void VM_eprint(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_eprint); PRVM_ED_PrintNum (prog, PRVM_G_EDICTNUM(OFS_PARM0), NULL); } /* ========= VM_rint float rint(float) ========= */ void VM_rint(prvm_prog_t *prog) { prvm_vec_t f; VM_SAFEPARMCOUNT(1,VM_rint); f = PRVM_G_FLOAT(OFS_PARM0); if (f > 0) PRVM_G_FLOAT(OFS_RETURN) = floor(f + 0.5); else PRVM_G_FLOAT(OFS_RETURN) = ceil(f - 0.5); } /* ========= VM_floor float floor(float) ========= */ void VM_floor(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_floor); PRVM_G_FLOAT(OFS_RETURN) = floor(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_ceil float ceil(float) ========= */ void VM_ceil(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_ceil); PRVM_G_FLOAT(OFS_RETURN) = ceil(PRVM_G_FLOAT(OFS_PARM0)); } /* ============= VM_nextent entity nextent(entity) ============= */ void VM_nextent(prvm_prog_t *prog) { int i; prvm_edict_t *ent; VM_SAFEPARMCOUNT(1, VM_nextent); i = PRVM_G_EDICTNUM(OFS_PARM0); while (1) { prog->xfunction->builtinsprofile++; i++; if (i == prog->num_edicts) { VM_RETURN_EDICT(prog->edicts); return; } ent = PRVM_EDICT_NUM(i); if (!ent->priv.required->free) { VM_RETURN_EDICT(ent); return; } } } //============================================================================= /* ============== VM_changelevel server and menu changelevel(string map) ============== */ void VM_changelevel(prvm_prog_t *prog) { char vabuf[1024]; VM_SAFEPARMCOUNT(1, VM_changelevel); if(!sv.active) { VM_Warning(prog, "VM_changelevel: game is not server (%s)\n", prog->name); return; } // make sure we don't issue two changelevels if (svs.changelevel_issued) return; svs.changelevel_issued = true; Cbuf_AddText(va(vabuf, sizeof(vabuf), "changelevel %s\n",PRVM_G_STRING(OFS_PARM0))); } /* ========= VM_sin float sin(float) ========= */ void VM_sin(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_sin); PRVM_G_FLOAT(OFS_RETURN) = sin(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_cos float cos(float) ========= */ void VM_cos(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_cos); PRVM_G_FLOAT(OFS_RETURN) = cos(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_sqrt float sqrt(float) ========= */ void VM_sqrt(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_sqrt); PRVM_G_FLOAT(OFS_RETURN) = sqrt(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_asin float asin(float) ========= */ void VM_asin(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_asin); PRVM_G_FLOAT(OFS_RETURN) = asin(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_acos float acos(float) ========= */ void VM_acos(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_acos); PRVM_G_FLOAT(OFS_RETURN) = acos(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_atan float atan(float) ========= */ void VM_atan(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_atan); PRVM_G_FLOAT(OFS_RETURN) = atan(PRVM_G_FLOAT(OFS_PARM0)); } /* ========= VM_atan2 float atan2(float,float) ========= */ void VM_atan2(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2,VM_atan2); PRVM_G_FLOAT(OFS_RETURN) = atan2(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); } /* ========= VM_tan float tan(float) ========= */ void VM_tan(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_tan); PRVM_G_FLOAT(OFS_RETURN) = tan(PRVM_G_FLOAT(OFS_PARM0)); } /* ================= VM_randomvec Returns a vector of length < 1 and > 0 vector randomvec() ================= */ void VM_randomvec(prvm_prog_t *prog) { vec3_t temp; VM_SAFEPARMCOUNT(0, VM_randomvec); VectorRandom(temp); VectorCopy(temp, PRVM_G_VECTOR(OFS_RETURN)); } //============================================================================= /* ========= VM_registercvar float registercvar (string name, string value[, float flags]) ========= */ void VM_registercvar(prvm_prog_t *prog) { const char *name, *value; int flags; VM_SAFEPARMCOUNTRANGE(2, 3, VM_registercvar); name = PRVM_G_STRING(OFS_PARM0); value = PRVM_G_STRING(OFS_PARM1); flags = prog->argc >= 3 ? (int)PRVM_G_FLOAT(OFS_PARM2) : 0; PRVM_G_FLOAT(OFS_RETURN) = 0; if(flags > CVAR_MAXFLAGSVAL) return; // first check to see if it has already been defined if (Cvar_FindVar (name)) return; // check for overlap with a command if (Cmd_Exists (name)) { VM_Warning(prog, "VM_registercvar: %s is a command\n", name); return; } Cvar_Get(name, value, flags, NULL); PRVM_G_FLOAT(OFS_RETURN) = 1; // success } /* ================= VM_min returns the minimum of two supplied floats float min(float a, float b, ...[float]) ================= */ void VM_min(prvm_prog_t *prog) { VM_SAFEPARMCOUNTRANGE(2, 8, VM_min); // LordHavoc: 3+ argument enhancement suggested by FrikaC if (prog->argc >= 3) { int i; float f = PRVM_G_FLOAT(OFS_PARM0); for (i = 1;i < prog->argc;i++) if (f > PRVM_G_FLOAT((OFS_PARM0+i*3))) f = PRVM_G_FLOAT((OFS_PARM0+i*3)); PRVM_G_FLOAT(OFS_RETURN) = f; } else PRVM_G_FLOAT(OFS_RETURN) = min(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); } /* ================= VM_max returns the maximum of two supplied floats float max(float a, float b, ...[float]) ================= */ void VM_max(prvm_prog_t *prog) { VM_SAFEPARMCOUNTRANGE(2, 8, VM_max); // LordHavoc: 3+ argument enhancement suggested by FrikaC if (prog->argc >= 3) { int i; float f = PRVM_G_FLOAT(OFS_PARM0); for (i = 1;i < prog->argc;i++) if (f < PRVM_G_FLOAT((OFS_PARM0+i*3))) f = PRVM_G_FLOAT((OFS_PARM0+i*3)); PRVM_G_FLOAT(OFS_RETURN) = f; } else PRVM_G_FLOAT(OFS_RETURN) = max(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); } /* ================= VM_bound returns number bounded by supplied range float bound(float min, float value, float max) ================= */ void VM_bound(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(3,VM_bound); PRVM_G_FLOAT(OFS_RETURN) = bound(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1), PRVM_G_FLOAT(OFS_PARM2)); } /* ================= VM_pow returns a raised to power b float pow(float a, float b) ================= */ void VM_pow(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(2,VM_pow); PRVM_G_FLOAT(OFS_RETURN) = pow(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1)); } void VM_log(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_log); PRVM_G_FLOAT(OFS_RETURN) = log(PRVM_G_FLOAT(OFS_PARM0)); } void VM_Files_Init(prvm_prog_t *prog) { int i; for (i = 0;i < PRVM_MAX_OPENFILES;i++) prog->openfiles[i] = NULL; } void VM_Files_CloseAll(prvm_prog_t *prog) { int i; for (i = 0;i < PRVM_MAX_OPENFILES;i++) { if (prog->openfiles[i]) FS_Close(prog->openfiles[i]); prog->openfiles[i] = NULL; } } static qfile_t *VM_GetFileHandle(prvm_prog_t *prog, int index) { if (index < 0 || index >= PRVM_MAX_OPENFILES) { Con_Printf("VM_GetFileHandle: invalid file handle %i used in %s\n", index, prog->name); return NULL; } if (prog->openfiles[index] == NULL) { Con_Printf("VM_GetFileHandle: no such file handle %i (or file has been closed) in %s\n", index, prog->name); return NULL; } return prog->openfiles[index]; } /* ========= VM_fopen float fopen(string filename, float mode) ========= */ // float(string filename, float mode) fopen = #110; // opens a file inside quake/gamedir/data/ (mode is FILE_READ, FILE_APPEND, or FILE_WRITE), // returns fhandle >= 0 if successful, or fhandle < 0 if unable to open file for any reason void VM_fopen(prvm_prog_t *prog) { int filenum, mode; const char *modestring, *filename; char vabuf[1024]; VM_SAFEPARMCOUNT(2,VM_fopen); for (filenum = 0;filenum < PRVM_MAX_OPENFILES;filenum++) if (prog->openfiles[filenum] == NULL) break; if (filenum >= PRVM_MAX_OPENFILES) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_fopen: %s ran out of file handles (%i)\n", prog->name, PRVM_MAX_OPENFILES); return; } filename = PRVM_G_STRING(OFS_PARM0); mode = (int)PRVM_G_FLOAT(OFS_PARM1); switch(mode) { case 0: // FILE_READ modestring = "rb"; prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "data/%s", filename), false); if (prog->openfiles[filenum] == NULL) prog->openfiles[filenum] = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "%s", filename), false); break; case 1: // FILE_APPEND modestring = "a"; prog->openfiles[filenum] = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "data/%s", filename), modestring, false); break; case 2: // FILE_WRITE modestring = "w"; prog->openfiles[filenum] = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "data/%s", filename), modestring, false); break; default: PRVM_G_FLOAT(OFS_RETURN) = -3; VM_Warning(prog, "VM_fopen: %s: no such mode %i (valid: 0 = read, 1 = append, 2 = write)\n", prog->name, mode); return; } if (prog->openfiles[filenum] == NULL) { PRVM_G_FLOAT(OFS_RETURN) = -1; if (developer_extra.integer) VM_Warning(prog, "VM_fopen: %s: %s mode %s failed\n", prog->name, filename, modestring); } else { PRVM_G_FLOAT(OFS_RETURN) = filenum; if (developer_extra.integer) Con_DPrintf("VM_fopen: %s: %s mode %s opened as #%i\n", prog->name, filename, modestring, filenum); prog->openfiles_origin[filenum] = PRVM_AllocationOrigin(prog); } } /* ========= VM_fclose fclose(float fhandle) ========= */ //void(float fhandle) fclose = #111; // closes a file void VM_fclose(prvm_prog_t *prog) { int filenum; VM_SAFEPARMCOUNT(1,VM_fclose); filenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) { VM_Warning(prog, "VM_fclose: invalid file handle %i used in %s\n", filenum, prog->name); return; } if (prog->openfiles[filenum] == NULL) { VM_Warning(prog, "VM_fclose: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); return; } FS_Close(prog->openfiles[filenum]); prog->openfiles[filenum] = NULL; if(prog->openfiles_origin[filenum]) PRVM_Free((char *)prog->openfiles_origin[filenum]); if (developer_extra.integer) Con_DPrintf("VM_fclose: %s: #%i closed\n", prog->name, filenum); } /* ========= VM_fgets string fgets(float fhandle) ========= */ //string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring void VM_fgets(prvm_prog_t *prog) { int c, end; char string[VM_STRINGTEMP_LENGTH]; int filenum; VM_SAFEPARMCOUNT(1,VM_fgets); // set the return value regardless of any possible errors PRVM_G_INT(OFS_RETURN) = OFS_NULL; filenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) { VM_Warning(prog, "VM_fgets: invalid file handle %i used in %s\n", filenum, prog->name); return; } if (prog->openfiles[filenum] == NULL) { VM_Warning(prog, "VM_fgets: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); return; } end = 0; for (;;) { c = FS_Getc(prog->openfiles[filenum]); if (c == '\r' || c == '\n' || c < 0) break; if (end < VM_STRINGTEMP_LENGTH - 1) string[end++] = c; } string[end] = 0; // remove \n following \r if (c == '\r') { c = FS_Getc(prog->openfiles[filenum]); if (c != '\n') FS_UnGetc(prog->openfiles[filenum], (unsigned char)c); } if (developer_extra.integer) Con_DPrintf("fgets: %s: %s\n", prog->name, string); if (c >= 0 || end) PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } /* ========= VM_fputs fputs(float fhandle, string s) ========= */ //void(float fhandle, string s) fputs = #113; // writes a line of text to the end of the file void VM_fputs(prvm_prog_t *prog) { int stringlength; char string[VM_STRINGTEMP_LENGTH]; int filenum; VM_SAFEPARMCOUNT(2,VM_fputs); filenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) { VM_Warning(prog, "VM_fputs: invalid file handle %i used in %s\n", filenum, prog->name); return; } if (prog->openfiles[filenum] == NULL) { VM_Warning(prog, "VM_fputs: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); return; } VM_VarString(prog, 1, string, sizeof(string)); if ((stringlength = (int)strlen(string))) FS_Write(prog->openfiles[filenum], string, stringlength); if (developer_extra.integer) Con_DPrintf("fputs: %s: %s\n", prog->name, string); } /* ========= VM_writetofile writetofile(float fhandle, entity ent) ========= */ void VM_writetofile(prvm_prog_t *prog) { prvm_edict_t * ent; qfile_t *file; VM_SAFEPARMCOUNT(2, VM_writetofile); file = VM_GetFileHandle(prog, (int)PRVM_G_FLOAT(OFS_PARM0)); if( !file ) { VM_Warning(prog, "VM_writetofile: invalid or closed file handle\n"); return; } ent = PRVM_G_EDICT(OFS_PARM1); if(ent->priv.required->free) { VM_Warning(prog, "VM_writetofile: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); return; } PRVM_ED_Write (prog, file, ent); } // KrimZon - DP_QC_ENTITYDATA /* ========= VM_numentityfields float() numentityfields Return the number of entity fields - NOT offsets ========= */ void VM_numentityfields(prvm_prog_t *prog) { PRVM_G_FLOAT(OFS_RETURN) = prog->numfielddefs; } // KrimZon - DP_QC_ENTITYDATA /* ========= VM_entityfieldname string(float fieldnum) entityfieldname Return name of the specified field as a string, or empty if the field is invalid (warning) ========= */ void VM_entityfieldname(prvm_prog_t *prog) { ddef_t *d; int i = (int)PRVM_G_FLOAT(OFS_PARM0); if (i < 0 || i >= prog->numfielddefs) { VM_Warning(prog, "VM_entityfieldname: %s: field index out of bounds\n", prog->name); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); return; } d = &prog->fielddefs[i]; PRVM_G_INT(OFS_RETURN) = d->s_name; // presuming that s_name points to a string already } // KrimZon - DP_QC_ENTITYDATA /* ========= VM_entityfieldtype float(float fieldnum) entityfieldtype ========= */ void VM_entityfieldtype(prvm_prog_t *prog) { ddef_t *d; int i = (int)PRVM_G_FLOAT(OFS_PARM0); if (i < 0 || i >= prog->numfielddefs) { VM_Warning(prog, "VM_entityfieldtype: %s: field index out of bounds\n", prog->name); PRVM_G_FLOAT(OFS_RETURN) = -1.0; return; } d = &prog->fielddefs[i]; PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t)d->type; } // KrimZon - DP_QC_ENTITYDATA /* ========= VM_getentityfieldstring string(float fieldnum, entity ent) getentityfieldstring ========= */ void VM_getentityfieldstring(prvm_prog_t *prog) { // put the data into a string ddef_t *d; int type, j; prvm_eval_t *val; prvm_edict_t * ent; int i = (int)PRVM_G_FLOAT(OFS_PARM0); char valuebuf[MAX_INPUTLINE]; if (i < 0 || i >= prog->numfielddefs) { VM_Warning(prog, "VM_entityfielddata: %s: field index out of bounds\n", prog->name); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); return; } d = &prog->fielddefs[i]; // get the entity ent = PRVM_G_EDICT(OFS_PARM1); if(ent->priv.required->free) { PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); VM_Warning(prog, "VM_entityfielddata: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); return; } val = (prvm_eval_t *)(ent->fields.fp + d->ofs); // if it's 0 or blank, return an empty string type = d->type & ~DEF_SAVEGLOBAL; for (j=0 ; jivector[j]) break; if (j == prvm_type_size[type]) { PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); return; } PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, PRVM_UglyValueString(prog, (etype_t)d->type, val, valuebuf, sizeof(valuebuf))); } // KrimZon - DP_QC_ENTITYDATA /* ========= VM_putentityfieldstring float(float fieldnum, entity ent, string s) putentityfieldstring ========= */ void VM_putentityfieldstring(prvm_prog_t *prog) { ddef_t *d; prvm_edict_t * ent; int i = (int)PRVM_G_FLOAT(OFS_PARM0); if (i < 0 || i >= prog->numfielddefs) { VM_Warning(prog, "VM_entityfielddata: %s: field index out of bounds\n", prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0.0f; return; } d = &prog->fielddefs[i]; // get the entity ent = PRVM_G_EDICT(OFS_PARM1); if(ent->priv.required->free) { VM_Warning(prog, "VM_entityfielddata: %s: entity %i is free !\n", prog->name, PRVM_NUM_FOR_EDICT(ent)); PRVM_G_FLOAT(OFS_RETURN) = 0.0f; return; } // parse the string into the value PRVM_G_FLOAT(OFS_RETURN) = ( PRVM_ED_ParseEpair(prog, ent, d, PRVM_G_STRING(OFS_PARM2), false) ) ? 1.0f : 0.0f; } /* ========= VM_strlen float strlen(string s) ========= */ //float(string s) strlen = #114; // returns how many characters are in a string void VM_strlen(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_strlen); //PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0)); PRVM_G_FLOAT(OFS_RETURN) = u8_strlen(PRVM_G_STRING(OFS_PARM0)); } // DRESK - Decolorized String /* ========= VM_strdecolorize string strdecolorize(string s) ========= */ // string (string s) strdecolorize = #472; // returns the passed in string with color codes stripped void VM_strdecolorize(prvm_prog_t *prog) { char szNewString[VM_STRINGTEMP_LENGTH]; const char *szString; // Prepare Strings VM_SAFEPARMCOUNT(1,VM_strdecolorize); szString = PRVM_G_STRING(OFS_PARM0); COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); } // DRESK - String Length (not counting color codes) /* ========= VM_strlennocol float strlennocol(string s) ========= */ // float(string s) strlennocol = #471; // returns how many characters are in a string not including color codes // For example, ^2Dresk returns a length of 5 void VM_strlennocol(prvm_prog_t *prog) { const char *szString; int nCnt; VM_SAFEPARMCOUNT(1,VM_strlennocol); szString = PRVM_G_STRING(OFS_PARM0); //nCnt = (int)COM_StringLengthNoColors(szString, 0, NULL); nCnt = (int)u8_COM_StringLengthNoColors(szString, 0, NULL); PRVM_G_FLOAT(OFS_RETURN) = nCnt; } // DRESK - String to Uppercase and Lowercase /* ========= VM_strtolower string strtolower(string s) ========= */ // string (string s) strtolower = #480; // returns passed in string in lowercase form void VM_strtolower(prvm_prog_t *prog) { char szNewString[VM_STRINGTEMP_LENGTH]; const char *szString; // Prepare Strings VM_SAFEPARMCOUNT(1,VM_strtolower); szString = PRVM_G_STRING(OFS_PARM0); COM_ToLowerString(szString, szNewString, sizeof(szNewString) ); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); } /* ========= VM_strtoupper string strtoupper(string s) ========= */ // string (string s) strtoupper = #481; // returns passed in string in uppercase form void VM_strtoupper(prvm_prog_t *prog) { char szNewString[VM_STRINGTEMP_LENGTH]; const char *szString; // Prepare Strings VM_SAFEPARMCOUNT(1,VM_strtoupper); szString = PRVM_G_STRING(OFS_PARM0); COM_ToUpperString(szString, szNewString, sizeof(szNewString) ); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); } /* ========= VM_strcat string strcat(string,string,...[string]) ========= */ //string(string s1, string s2) strcat = #115; // concatenates two strings (for example "abc", "def" would return "abcdef") // and returns as a tempstring void VM_strcat(prvm_prog_t *prog) { char s[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(1, 8, VM_strcat); VM_VarString(prog, 0, s, sizeof(s)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, s); } /* ========= VM_substring string substring(string s, float start, float length) ========= */ // string(string s, float start, float length) substring = #116; // returns a section of a string as a tempstring void VM_substring(prvm_prog_t *prog) { int start, length; int u_slength = 0, u_start; size_t u_length; const char *s; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(3,VM_substring); /* s = PRVM_G_STRING(OFS_PARM0); start = (int)PRVM_G_FLOAT(OFS_PARM1); length = (int)PRVM_G_FLOAT(OFS_PARM2); slength = strlen(s); if (start < 0) // FTE_STRINGS feature start += slength; start = bound(0, start, slength); if (length < 0) // FTE_STRINGS feature length += slength - start + 1; maxlen = min((int)sizeof(string) - 1, slength - start); length = bound(0, length, maxlen); memcpy(string, s + start, length); string[length] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); */ s = PRVM_G_STRING(OFS_PARM0); start = (int)PRVM_G_FLOAT(OFS_PARM1); length = (int)PRVM_G_FLOAT(OFS_PARM2); if (start < 0) // FTE_STRINGS feature { u_slength = (int)u8_strlen(s); start += u_slength; start = bound(0, start, u_slength); } if (length < 0) // FTE_STRINGS feature { if (!u_slength) // it's not calculated when it's not needed above u_slength = (int)u8_strlen(s); length += u_slength - start + 1; } // positive start, positive length u_start = u8_byteofs(s, start, NULL); if (u_start < 0) { PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); return; } u_length = u8_bytelen(s + u_start, length); if (u_length >= sizeof(string)-1) u_length = sizeof(string)-1; memcpy(string, s + u_start, u_length); string[u_length] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } /* ========= VM_strreplace string(string search, string replace, string subject) strreplace = #484; ========= */ // replaces all occurrences of search with replace in the string subject, and returns the result void VM_strreplace(prvm_prog_t *prog) { int i, j, si; const char *search, *replace, *subject; char string[VM_STRINGTEMP_LENGTH]; int search_len, replace_len, subject_len; VM_SAFEPARMCOUNT(3,VM_strreplace); search = PRVM_G_STRING(OFS_PARM0); replace = PRVM_G_STRING(OFS_PARM1); subject = PRVM_G_STRING(OFS_PARM2); search_len = (int)strlen(search); replace_len = (int)strlen(replace); subject_len = (int)strlen(subject); si = 0; for (i = 0; i <= subject_len - search_len; i++) { for (j = 0; j < search_len; j++) // thus, i+j < subject_len if (subject[i+j] != search[j]) break; if (j == search_len) { // NOTE: if search_len == 0, we always hit THIS case, and never the other // found it at offset 'i' for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) string[si++] = replace[j]; if(search_len > 0) { i += search_len - 1; } else { // the above would subtract 1 from i... so we // don't do that, but instead output the next // char if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; } } else { // in THIS case, we know search_len > 0, thus i < subject_len // not found if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; } } // remaining chars (these cannot match) for (; i < subject_len; i++) if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; string[si] = '\0'; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } /* ========= VM_strireplace string(string search, string replace, string subject) strireplace = #485; ========= */ // case-insensitive version of strreplace void VM_strireplace(prvm_prog_t *prog) { int i, j, si; const char *search, *replace, *subject; char string[VM_STRINGTEMP_LENGTH]; int search_len, replace_len, subject_len; VM_SAFEPARMCOUNT(3,VM_strreplace); search = PRVM_G_STRING(OFS_PARM0); replace = PRVM_G_STRING(OFS_PARM1); subject = PRVM_G_STRING(OFS_PARM2); search_len = (int)strlen(search); replace_len = (int)strlen(replace); subject_len = (int)strlen(subject); si = 0; for (i = 0; i <= subject_len - search_len; i++) { for (j = 0; j < search_len; j++) // thus, i+j < subject_len if (tolower(subject[i+j]) != tolower(search[j])) break; if (j == search_len) { // NOTE: if search_len == 0, we always hit THIS case, and never the other // found it at offset 'i' for (j = 0; j < replace_len && si < (int)sizeof(string) - 1; j++) string[si++] = replace[j]; if(search_len > 0) { i += search_len - 1; } else { // the above would subtract 1 from i... so we // don't do that, but instead output the next // char if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; } } else { // in THIS case, we know search_len > 0, thus i < subject_len // not found if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; } } // remaining chars (these cannot match) for (; i < subject_len; i++) if (si < (int)sizeof(string) - 1) string[si++] = subject[i]; string[si] = '\0'; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, string); } /* ========= VM_stov vector stov(string s) ========= */ //vector(string s) stov = #117; // returns vector value from a string void VM_stov(prvm_prog_t *prog) { char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(1,VM_stov); VM_VarString(prog, 0, string, sizeof(string)); Math_atov(string, PRVM_G_VECTOR(OFS_RETURN)); } /* ========= VM_strzone string strzone(string s) ========= */ //string(string s, ...) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to keep around a tempstring for longer periods of time (tempstrings are replaced often) void VM_strzone(prvm_prog_t *prog) { char *out; char string[VM_STRINGTEMP_LENGTH]; size_t alloclen; VM_SAFEPARMCOUNT(1,VM_strzone); VM_VarString(prog, 0, string, sizeof(string)); alloclen = strlen(string) + 1; PRVM_G_INT(OFS_RETURN) = PRVM_AllocString(prog, alloclen, &out); memcpy(out, string, alloclen); } /* ========= VM_strunzone strunzone(string s) ========= */ //void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again or it may crash!!!) void VM_strunzone(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_strunzone); PRVM_FreeString(prog, PRVM_G_INT(OFS_PARM0)); } /* ========= VM_command (used by client and menu) clientcommand(float client, string s) (for client and menu) ========= */ //void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client //this function originally written by KrimZon, made shorter by LordHavoc void VM_clcommand (prvm_prog_t *prog) { client_t *temp_client; int i; VM_SAFEPARMCOUNT(2,VM_clcommand); i = (int)PRVM_G_FLOAT(OFS_PARM0); if (!sv.active || i < 0 || i >= svs.maxclients || !svs.clients[i].active) { VM_Warning(prog, "VM_clientcommand: %s: invalid client/server is not active !\n", prog->name); return; } temp_client = host_client; host_client = svs.clients + i; Cmd_ExecuteString (PRVM_G_STRING(OFS_PARM1), src_client, true); host_client = temp_client; } /* ========= VM_tokenize float tokenize(string s) ========= */ //float(string s) tokenize = #441; // takes apart a string into individal words (access them with argv), returns how many //this function originally written by KrimZon, made shorter by LordHavoc //20040203: rewritten by LordHavoc (no longer uses allocations) static int num_tokens = 0; static int tokens[VM_STRINGTEMP_LENGTH / 2]; static int tokens_startpos[VM_STRINGTEMP_LENGTH / 2]; static int tokens_endpos[VM_STRINGTEMP_LENGTH / 2]; static char tokenize_string[VM_STRINGTEMP_LENGTH]; void VM_tokenize (prvm_prog_t *prog) { const char *p; VM_SAFEPARMCOUNT(1,VM_tokenize); strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); p = tokenize_string; num_tokens = 0; for(;;) { if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) break; // skip whitespace here to find token start pos while(*p && ISWHITESPACE(*p)) ++p; tokens_startpos[num_tokens] = p - tokenize_string; if(!COM_ParseToken_VM_Tokenize(&p, false)) break; tokens_endpos[num_tokens] = p - tokenize_string; tokens[num_tokens] = PRVM_SetTempString(prog, com_token); ++num_tokens; } PRVM_G_FLOAT(OFS_RETURN) = num_tokens; } //float(string s) tokenize = #514; // takes apart a string into individal words (access them with argv), returns how many void VM_tokenize_console (prvm_prog_t *prog) { const char *p; VM_SAFEPARMCOUNT(1,VM_tokenize); strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); p = tokenize_string; num_tokens = 0; for(;;) { if (num_tokens >= (int)(sizeof(tokens)/sizeof(tokens[0]))) break; // skip whitespace here to find token start pos while(*p && ISWHITESPACE(*p)) ++p; tokens_startpos[num_tokens] = p - tokenize_string; if(!COM_ParseToken_Console(&p)) break; tokens_endpos[num_tokens] = p - tokenize_string; tokens[num_tokens] = PRVM_SetTempString(prog, com_token); ++num_tokens; } PRVM_G_FLOAT(OFS_RETURN) = num_tokens; } /* ========= VM_tokenizebyseparator float tokenizebyseparator(string s, string separator1, ...) ========= */ //float(string s, string separator1, ...) tokenizebyseparator = #479; // takes apart a string into individal words (access them with argv), returns how many //this function returns the token preceding each instance of a separator (of //which there can be multiple), and the text following the last separator //useful for parsing certain kinds of data like IP addresses //example: //numnumbers = tokenizebyseparator("10.1.2.3", "."); //returns 4 and the tokens "10" "1" "2" "3". void VM_tokenizebyseparator (prvm_prog_t *prog) { int j, k; int numseparators; int separatorlen[7]; const char *separators[7]; const char *p, *p0; const char *token; char tokentext[MAX_INPUTLINE]; VM_SAFEPARMCOUNTRANGE(2, 8,VM_tokenizebyseparator); strlcpy(tokenize_string, PRVM_G_STRING(OFS_PARM0), sizeof(tokenize_string)); p = tokenize_string; numseparators = 0; for (j = 1;j < prog->argc;j++) { // skip any blank separator strings const char *s = PRVM_G_STRING(OFS_PARM0+j*3); if (!s[0]) continue; separators[numseparators] = s; separatorlen[numseparators] = (int)strlen(s); numseparators++; } num_tokens = 0; j = 0; while (num_tokens < (int)(sizeof(tokens)/sizeof(tokens[0]))) { token = tokentext + j; tokens_startpos[num_tokens] = p - tokenize_string; p0 = p; while (*p) { for (k = 0;k < numseparators;k++) { if (!strncmp(p, separators[k], separatorlen[k])) { p += separatorlen[k]; break; } } if (k < numseparators) break; if (j < (int)sizeof(tokentext)-1) tokentext[j++] = *p; p++; p0 = p; } tokens_endpos[num_tokens] = p0 - tokenize_string; if (j >= (int)sizeof(tokentext)) break; tokentext[j++] = 0; tokens[num_tokens++] = PRVM_SetTempString(prog, token); if (!*p) break; } PRVM_G_FLOAT(OFS_RETURN) = num_tokens; } //string(float n) argv = #442; // returns a word from the tokenized string (returns nothing for an invalid index) //this function originally written by KrimZon, made shorter by LordHavoc void VM_argv (prvm_prog_t *prog) { int token_num; VM_SAFEPARMCOUNT(1,VM_argv); token_num = (int)PRVM_G_FLOAT(OFS_PARM0); if(token_num < 0) token_num += num_tokens; if (token_num >= 0 && token_num < num_tokens) PRVM_G_INT(OFS_RETURN) = tokens[token_num]; else PRVM_G_INT(OFS_RETURN) = OFS_NULL; } //float(float n) argv_start_index = #515; // returns the start index of a token void VM_argv_start_index (prvm_prog_t *prog) { int token_num; VM_SAFEPARMCOUNT(1,VM_argv); token_num = (int)PRVM_G_FLOAT(OFS_PARM0); if(token_num < 0) token_num += num_tokens; if (token_num >= 0 && token_num < num_tokens) PRVM_G_FLOAT(OFS_RETURN) = tokens_startpos[token_num]; else PRVM_G_FLOAT(OFS_RETURN) = -1; } //float(float n) argv_end_index = #516; // returns the end index of a token void VM_argv_end_index (prvm_prog_t *prog) { int token_num; VM_SAFEPARMCOUNT(1,VM_argv); token_num = (int)PRVM_G_FLOAT(OFS_PARM0); if(token_num < 0) token_num += num_tokens; if (token_num >= 0 && token_num < num_tokens) PRVM_G_FLOAT(OFS_RETURN) = tokens_endpos[token_num]; else PRVM_G_FLOAT(OFS_RETURN) = -1; } /* ========= VM_isserver float isserver() ========= */ void VM_isserver(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_serverstate); PRVM_G_FLOAT(OFS_RETURN) = sv.active; } /* ========= VM_clientcount float clientcount() ========= */ void VM_clientcount(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_clientcount); PRVM_G_FLOAT(OFS_RETURN) = svs.maxclients; } /* ========= VM_clientstate float clientstate() ========= */ void VM_clientstate(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_clientstate); switch( cls.state ) { case ca_uninitialized: case ca_dedicated: PRVM_G_FLOAT(OFS_RETURN) = 0; break; case ca_disconnected: PRVM_G_FLOAT(OFS_RETURN) = 1; break; case ca_connected: PRVM_G_FLOAT(OFS_RETURN) = 2; break; default: // should never be reached! break; } } /* ========= VM_getostype float getostype(prvm_prog_t *prog) ========= */ // not used at the moment -> not included in the common list void VM_getostype(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_getostype); /* OS_WINDOWS OS_LINUX OS_MAC - not supported */ #ifdef WIN32 PRVM_G_FLOAT(OFS_RETURN) = 0; #elif defined(MACOSX) PRVM_G_FLOAT(OFS_RETURN) = 2; #else PRVM_G_FLOAT(OFS_RETURN) = 1; #endif } /* ========= VM_gettime float gettime(prvm_prog_t *prog) ========= */ #ifdef CONFIG_CD float CDAudio_GetPosition(void); #endif void VM_gettime(prvm_prog_t *prog) { int timer_index; VM_SAFEPARMCOUNTRANGE(0,1,VM_gettime); if(prog->argc == 0) { PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t) realtime; } else { timer_index = (int) PRVM_G_FLOAT(OFS_PARM0); switch(timer_index) { case 0: // GETTIME_FRAMESTART PRVM_G_FLOAT(OFS_RETURN) = realtime; break; case 1: // GETTIME_REALTIME PRVM_G_FLOAT(OFS_RETURN) = Sys_DirtyTime(); break; case 2: // GETTIME_HIRES PRVM_G_FLOAT(OFS_RETURN) = (Sys_DirtyTime() - host_dirtytime); break; case 3: // GETTIME_UPTIME PRVM_G_FLOAT(OFS_RETURN) = realtime; break; #ifdef CONFIG_CD case 4: // GETTIME_CDTRACK PRVM_G_FLOAT(OFS_RETURN) = CDAudio_GetPosition(); break; #endif default: VM_Warning(prog, "VM_gettime: %s: unsupported timer specified, returning realtime\n", prog->name); PRVM_G_FLOAT(OFS_RETURN) = realtime; break; } } } /* ========= VM_getsoundtime float getsoundtime(prvm_prog_t *prog) ========= */ void VM_getsoundtime (prvm_prog_t *prog) { int entnum, entchannel; VM_SAFEPARMCOUNT(2,VM_getsoundtime); if (prog == SVVM_prog) entnum = PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)); else if (prog == CLVM_prog) entnum = MAX_EDICTS + PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0)); else { VM_Warning(prog, "VM_getsoundtime: %s: not supported on this progs\n", prog->name); PRVM_G_FLOAT(OFS_RETURN) = -1; return; } entchannel = (int)PRVM_G_FLOAT(OFS_PARM1); entchannel = CHAN_USER2ENGINE(entchannel); if (!IS_CHAN(entchannel)) VM_Warning(prog, "VM_getsoundtime: %s: bad channel %i\n", prog->name, entchannel); PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t)S_GetEntChannelPosition(entnum, entchannel); } /* ========= VM_GetSoundLen string soundlength (string sample) ========= */ void VM_soundlength (prvm_prog_t *prog) { const char *s; VM_SAFEPARMCOUNT(1, VM_soundlength); s = PRVM_G_STRING(OFS_PARM0); PRVM_G_FLOAT(OFS_RETURN) = S_SoundLength(s); } /* ========= VM_loadfromdata loadfromdata(string data) ========= */ void VM_loadfromdata(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_loadentsfromfile); PRVM_ED_LoadFromFile(prog, PRVM_G_STRING(OFS_PARM0)); } /* ======================== VM_parseentitydata parseentitydata(entity ent, string data) ======================== */ void VM_parseentitydata(prvm_prog_t *prog) { prvm_edict_t *ent; const char *data; VM_SAFEPARMCOUNT(2, VM_parseentitydata); // get edict and test it ent = PRVM_G_EDICT(OFS_PARM0); if (ent->priv.required->free) prog->error_cmd("VM_parseentitydata: %s: Can only set already spawned entities (entity %i is free)!", prog->name, PRVM_NUM_FOR_EDICT(ent)); data = PRVM_G_STRING(OFS_PARM1); // parse the opening brace if (!COM_ParseToken_Simple(&data, false, false, true) || com_token[0] != '{' ) prog->error_cmd("VM_parseentitydata: %s: Couldn't parse entity data:\n%s", prog->name, data ); PRVM_ED_ParseEdict (prog, data, ent); } /* ========= VM_loadfromfile loadfromfile(string file) ========= */ void VM_loadfromfile(prvm_prog_t *prog) { const char *filename; char *data; VM_SAFEPARMCOUNT(1,VM_loadfromfile); filename = PRVM_G_STRING(OFS_PARM0); if (FS_CheckNastyPath(filename, false)) { PRVM_G_FLOAT(OFS_RETURN) = -4; VM_Warning(prog, "VM_loadfromfile: %s dangerous or non-portable filename \"%s\" not allowed. (contains : or \\ or begins with .. or /)\n", prog->name, filename); return; } // not conform with VM_fopen data = (char *)FS_LoadFile(filename, tempmempool, false, NULL); if (data == NULL) PRVM_G_FLOAT(OFS_RETURN) = -1; PRVM_ED_LoadFromFile(prog, data); if(data) Mem_Free(data); } /* ========= VM_modulo float mod(float val, float m) ========= */ void VM_modulo(prvm_prog_t *prog) { prvm_int_t val, m; VM_SAFEPARMCOUNT(2,VM_module); val = (prvm_int_t) PRVM_G_FLOAT(OFS_PARM0); m = (prvm_int_t) PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = (prvm_vec_t) (val % m); } static void VM_Search_Init(prvm_prog_t *prog) { int i; for (i = 0;i < PRVM_MAX_OPENSEARCHES;i++) prog->opensearches[i] = NULL; } static void VM_Search_Reset(prvm_prog_t *prog) { int i; // reset the fssearch list for(i = 0; i < PRVM_MAX_OPENSEARCHES; i++) { if(prog->opensearches[i]) FS_FreeSearch(prog->opensearches[i]); prog->opensearches[i] = NULL; } } /* ========= VM_search_begin float search_begin(string pattern, float caseinsensitive, float quiet) ========= */ void VM_search_begin(prvm_prog_t *prog) { int handle; const char *pattern; int caseinsens, quiet; VM_SAFEPARMCOUNT(3, VM_search_begin); pattern = PRVM_G_STRING(OFS_PARM0); VM_CheckEmptyString(prog, pattern); caseinsens = (int)PRVM_G_FLOAT(OFS_PARM1); quiet = (int)PRVM_G_FLOAT(OFS_PARM2); for(handle = 0; handle < PRVM_MAX_OPENSEARCHES; handle++) if(!prog->opensearches[handle]) break; if(handle >= PRVM_MAX_OPENSEARCHES) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_search_begin: %s ran out of search handles (%i)\n", prog->name, PRVM_MAX_OPENSEARCHES); return; } if(!(prog->opensearches[handle] = FS_Search(pattern,caseinsens, quiet))) PRVM_G_FLOAT(OFS_RETURN) = -1; else { prog->opensearches_origin[handle] = PRVM_AllocationOrigin(prog); PRVM_G_FLOAT(OFS_RETURN) = handle; } } /* ========= VM_search_end void search_end(float handle) ========= */ void VM_search_end(prvm_prog_t *prog) { int handle; VM_SAFEPARMCOUNT(1, VM_search_end); handle = (int)PRVM_G_FLOAT(OFS_PARM0); if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) { VM_Warning(prog, "VM_search_end: invalid handle %i used in %s\n", handle, prog->name); return; } if(prog->opensearches[handle] == NULL) { VM_Warning(prog, "VM_search_end: no such handle %i in %s\n", handle, prog->name); return; } FS_FreeSearch(prog->opensearches[handle]); prog->opensearches[handle] = NULL; if(prog->opensearches_origin[handle]) PRVM_Free((char *)prog->opensearches_origin[handle]); } /* ========= VM_search_getsize float search_getsize(float handle) ========= */ void VM_search_getsize(prvm_prog_t *prog) { int handle; VM_SAFEPARMCOUNT(1, VM_M_search_getsize); handle = (int)PRVM_G_FLOAT(OFS_PARM0); if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) { VM_Warning(prog, "VM_search_getsize: invalid handle %i used in %s\n", handle, prog->name); return; } if(prog->opensearches[handle] == NULL) { VM_Warning(prog, "VM_search_getsize: no such handle %i in %s\n", handle, prog->name); return; } PRVM_G_FLOAT(OFS_RETURN) = prog->opensearches[handle]->numfilenames; } /* ========= VM_search_getfilename string search_getfilename(float handle, float num) ========= */ void VM_search_getfilename(prvm_prog_t *prog) { int handle, filenum; VM_SAFEPARMCOUNT(2, VM_search_getfilename); handle = (int)PRVM_G_FLOAT(OFS_PARM0); filenum = (int)PRVM_G_FLOAT(OFS_PARM1); if(handle < 0 || handle >= PRVM_MAX_OPENSEARCHES) { VM_Warning(prog, "VM_search_getfilename: invalid handle %i used in %s\n", handle, prog->name); return; } if(prog->opensearches[handle] == NULL) { VM_Warning(prog, "VM_search_getfilename: no such handle %i in %s\n", handle, prog->name); return; } if(filenum < 0 || filenum >= prog->opensearches[handle]->numfilenames) { VM_Warning(prog, "VM_search_getfilename: invalid filenum %i in %s\n", filenum, prog->name); return; } PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, prog->opensearches[handle]->filenames[filenum]); } /* ========= VM_chr string chr(float ascii) ========= */ void VM_chr(prvm_prog_t *prog) { /* char tmp[2]; VM_SAFEPARMCOUNT(1, VM_chr); tmp[0] = (unsigned char) PRVM_G_FLOAT(OFS_PARM0); tmp[1] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, tmp); */ char tmp[8]; int len; VM_SAFEPARMCOUNT(1, VM_chr); len = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0), tmp, sizeof(tmp)); tmp[len] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, tmp); } //============================================================================= // Draw builtins (client & menu) /* ========= VM_iscachedpic float iscachedpic(string pic) ========= */ void VM_iscachedpic(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_iscachedpic); // drawq hasnt such a function, thus always return true PRVM_G_FLOAT(OFS_RETURN) = false; } /* ========= VM_precache_pic string precache_pic(string pic) ========= */ #define PRECACHE_PIC_FROMWAD 1 /* FTEQW, not supported here */ #define PRECACHE_PIC_NOTPERSISTENT 2 //#define PRECACHE_PIC_NOCLAMP 4 #define PRECACHE_PIC_MIPMAP 8 void VM_precache_pic(prvm_prog_t *prog) { const char *s; int flags = 0; VM_SAFEPARMCOUNTRANGE(1, 2, VM_precache_pic); s = PRVM_G_STRING(OFS_PARM0); PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0); VM_CheckEmptyString(prog, s); if(prog->argc >= 2) { int f = PRVM_G_FLOAT(OFS_PARM1); if(f & PRECACHE_PIC_NOTPERSISTENT) flags |= CACHEPICFLAG_NOTPERSISTENT; //if(f & PRECACHE_PIC_NOCLAMP) // flags |= CACHEPICFLAG_NOCLAMP; if(f & PRECACHE_PIC_MIPMAP) flags |= CACHEPICFLAG_MIPMAP; } // AK Draw_CachePic is supposed to always return a valid pointer if( Draw_CachePic_Flags(s, flags)->tex == r_texture_notexture ) PRVM_G_INT(OFS_RETURN) = OFS_NULL; } /* ========= VM_freepic freepic(string s) ========= */ void VM_freepic(prvm_prog_t *prog) { const char *s; VM_SAFEPARMCOUNT(1,VM_freepic); s = PRVM_G_STRING(OFS_PARM0); VM_CheckEmptyString(prog, s); Draw_FreePic(s); } static void getdrawfontscale(prvm_prog_t *prog, float *sx, float *sy) { vec3_t v; *sx = *sy = 1; VectorCopy(PRVM_drawglobalvector(drawfontscale), v); if(VectorLength2(v) > 0) { *sx = v[0]; *sy = v[1]; } } static dp_font_t *getdrawfont(prvm_prog_t *prog) { int f = (int) PRVM_drawglobalfloat(drawfont); if(f < 0 || f >= dp_fonts.maxsize) return FONT_DEFAULT; return &dp_fonts.f[f]; } /* ========= VM_drawcharacter float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) ========= */ void VM_drawcharacter(prvm_prog_t *prog) { prvm_vec_t *pos,*scale,*rgb; char character; int flag; float sx, sy; VM_SAFEPARMCOUNT(6,VM_drawcharacter); character = (char) PRVM_G_FLOAT(OFS_PARM1); if(character == 0) { PRVM_G_FLOAT(OFS_RETURN) = -1; VM_Warning(prog, "VM_drawcharacter: %s passed null character !\n",prog->name); return; } pos = PRVM_G_VECTOR(OFS_PARM0); scale = PRVM_G_VECTOR(OFS_PARM2); rgb = PRVM_G_VECTOR(OFS_PARM3); flag = (int)PRVM_G_FLOAT(OFS_PARM5); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawcharacter: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(pos[2] || scale[2]) VM_Warning(prog, "VM_drawcharacter: z value%c from %s discarded\n",(pos[2] && scale[2]) ? 's' : 0,((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); if(!scale[0] || !scale[1]) { PRVM_G_FLOAT(OFS_RETURN) = -3; VM_Warning(prog, "VM_drawcharacter: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); return; } getdrawfontscale(prog, &sx, &sy); DrawQ_String_Scale(pos[0], pos[1], &character, 1, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont(prog)); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawstring float drawstring(vector position, string text, vector scale, vector rgb, float alpha[, float flag]) ========= */ void VM_drawstring(prvm_prog_t *prog) { prvm_vec_t *pos,*scale,*rgb; const char *string; int flag = 0; float sx, sy; VM_SAFEPARMCOUNTRANGE(5,6,VM_drawstring); string = PRVM_G_STRING(OFS_PARM1); pos = PRVM_G_VECTOR(OFS_PARM0); scale = PRVM_G_VECTOR(OFS_PARM2); rgb = PRVM_G_VECTOR(OFS_PARM3); if (prog->argc >= 6) flag = (int)PRVM_G_FLOAT(OFS_PARM5); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawstring: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(!scale[0] || !scale[1]) { PRVM_G_FLOAT(OFS_RETURN) = -3; VM_Warning(prog, "VM_drawstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); return; } if(pos[2] || scale[2]) VM_Warning(prog, "VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); getdrawfontscale(prog, &sx, &sy); DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont(prog)); //Font_DrawString(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawcolorcodedstring float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) / float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) ========= */ void VM_drawcolorcodedstring(prvm_prog_t *prog) { prvm_vec_t *pos, *scale; const char *string; int flag; vec3_t rgb; float sx, sy, alpha; VM_SAFEPARMCOUNTRANGE(5,6,VM_drawcolorcodedstring); if (prog->argc == 6) // full 6 parms, like normal drawstring { pos = PRVM_G_VECTOR(OFS_PARM0); string = PRVM_G_STRING(OFS_PARM1); scale = PRVM_G_VECTOR(OFS_PARM2); VectorCopy(PRVM_G_VECTOR(OFS_PARM3), rgb); alpha = PRVM_G_FLOAT(OFS_PARM4); flag = (int)PRVM_G_FLOAT(OFS_PARM5); } else { pos = PRVM_G_VECTOR(OFS_PARM0); string = PRVM_G_STRING(OFS_PARM1); scale = PRVM_G_VECTOR(OFS_PARM2); rgb[0] = 1.0; rgb[1] = 1.0; rgb[2] = 1.0; alpha = PRVM_G_FLOAT(OFS_PARM3); flag = (int)PRVM_G_FLOAT(OFS_PARM4); } if(flag < DRAWFLAG_NORMAL || flag >= DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawcolorcodedstring: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(!scale[0] || !scale[1]) { PRVM_G_FLOAT(OFS_RETURN) = -3; VM_Warning(prog, "VM_drawcolorcodedstring: scale %s is null !\n", (scale[0] == 0) ? ((scale[1] == 0) ? "x and y" : "x") : "y"); return; } if(pos[2] || scale[2]) VM_Warning(prog, "VM_drawcolorcodedstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); getdrawfontscale(prog, &sx, &sy); DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], alpha, flag, NULL, false, getdrawfont(prog)); if (prog->argc == 6) // also return vector of last color VectorCopy(DrawQ_Color, PRVM_G_VECTOR(OFS_RETURN)); else PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_stringwidth float stringwidth(string text, float allowColorCodes, float size) ========= */ void VM_stringwidth(prvm_prog_t *prog) { const char *string; vec2_t szv; float mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell int colors; float sx, sy; size_t maxlen = 0; VM_SAFEPARMCOUNTRANGE(2,3,VM_drawstring); getdrawfontscale(prog, &sx, &sy); if(prog->argc == 3) { Vector2Copy(PRVM_G_VECTOR(OFS_PARM2), szv); mult = 1; } else { // we want the width for 8x8 font size, divided by 8 Vector2Set(szv, 8, 8); mult = 0.125; // to make sure snapping is turned off, ALWAYS use a nontrivial scale in this case if(sx >= 0.9 && sx <= 1.1) { mult *= 2; sx /= 2; sy /= 2; } } string = PRVM_G_STRING(OFS_PARM0); colors = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_UntilWidth_TrackColors_Scale(string, &maxlen, szv[0], szv[1], sx, sy, NULL, !colors, getdrawfont(prog), 1000000000) * mult; /* if(prog->argc == 3) { mult = sz = PRVM_G_FLOAT(OFS_PARM2); } else { sz = 8; mult = 1; } string = PRVM_G_STRING(OFS_PARM0); colors = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw */ } /* ========= VM_findfont float findfont(string s) ========= */ static float getdrawfontnum(const char *fontname) { int i; for(i = 0; i < dp_fonts.maxsize; ++i) if(!strcmp(dp_fonts.f[i].title, fontname)) return i; return -1; } void VM_findfont(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1,VM_findfont); PRVM_G_FLOAT(OFS_RETURN) = getdrawfontnum(PRVM_G_STRING(OFS_PARM0)); } /* ========= VM_loadfont float loadfont(string fontname, string fontmaps, string sizes, float slot) ========= */ void VM_loadfont(prvm_prog_t *prog) { const char *fontname, *filelist, *sizes, *c, *cm; char mainfont[MAX_QPATH]; int i, numsizes; float sz, scale, voffset; dp_font_t *f; VM_SAFEPARMCOUNTRANGE(3,6,VM_loadfont); fontname = PRVM_G_STRING(OFS_PARM0); if (!fontname[0]) fontname = "default"; filelist = PRVM_G_STRING(OFS_PARM1); if (!filelist[0]) filelist = "gfx/conchars"; sizes = PRVM_G_STRING(OFS_PARM2); if (!sizes[0]) sizes = "10"; // find a font f = NULL; if (prog->argc >= 4) { i = PRVM_G_FLOAT(OFS_PARM3); if (i >= 0 && i < dp_fonts.maxsize) { f = &dp_fonts.f[i]; strlcpy(f->title, fontname, sizeof(f->title)); // replace name } } if (!f) f = FindFont(fontname, true); if (!f) { PRVM_G_FLOAT(OFS_RETURN) = -1; return; // something go wrong } memset(f->fallbacks, 0, sizeof(f->fallbacks)); memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); // first font is handled "normally" c = strchr(filelist, ':'); cm = strchr(filelist, ','); if(c && (!cm || c < cm)) f->req_face = atoi(c+1); else { f->req_face = 0; c = cm; } if(!c || (c - filelist) > MAX_QPATH) strlcpy(mainfont, filelist, sizeof(mainfont)); else { memcpy(mainfont, filelist, c - filelist); mainfont[c - filelist] = 0; } // handle fallbacks for(i = 0; i < MAX_FONT_FALLBACKS; ++i) { c = strchr(filelist, ','); if(!c) break; filelist = c + 1; if(!*filelist) break; c = strchr(filelist, ':'); cm = strchr(filelist, ','); if(c && (!cm || c < cm)) f->fallback_faces[i] = atoi(c+1); else { f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index c = cm; } if(!c || (c-filelist) > MAX_QPATH) { strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); } else { memcpy(f->fallbacks[i], filelist, c - filelist); f->fallbacks[i][c - filelist] = 0; } } // handle sizes for(i = 0; i < MAX_FONT_SIZES; ++i) f->req_sizes[i] = -1; for (numsizes = 0,c = sizes;;) { if (!COM_ParseToken_VM_Tokenize(&c, 0)) break; sz = atof(com_token); // detect crap size if (sz < 0.001f || sz > 1000.0f) { VM_Warning(prog, "VM_loadfont: crap size %s", com_token); continue; } // check overflow if (numsizes == MAX_FONT_SIZES) { VM_Warning(prog, "VM_loadfont: MAX_FONT_SIZES = %i exceeded", MAX_FONT_SIZES); break; } f->req_sizes[numsizes] = sz; numsizes++; } // additional scale/hoffset parms scale = 1; voffset = 0; if (prog->argc >= 5) { scale = PRVM_G_FLOAT(OFS_PARM4); if (scale <= 0) scale = 1; } if (prog->argc >= 6) voffset = PRVM_G_FLOAT(OFS_PARM5); // load LoadFont(true, mainfont, f, scale, voffset); // return index of loaded font PRVM_G_FLOAT(OFS_RETURN) = (f - dp_fonts.f); } /* ========= VM_drawpic float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) ========= */ void VM_drawpic(prvm_prog_t *prog) { const char *picname; prvm_vec_t *size, *pos, *rgb; int flag = 0; VM_SAFEPARMCOUNTRANGE(5,6,VM_drawpic); picname = PRVM_G_STRING(OFS_PARM1); VM_CheckEmptyString(prog, picname); // is pic cached ? no function yet for that if(!1) { PRVM_G_FLOAT(OFS_RETURN) = -4; VM_Warning(prog, "VM_drawpic: %s: %s not cached !\n", prog->name, picname); return; } pos = PRVM_G_VECTOR(OFS_PARM0); size = PRVM_G_VECTOR(OFS_PARM2); rgb = PRVM_G_VECTOR(OFS_PARM3); if (prog->argc >= 6) flag = (int) PRVM_G_FLOAT(OFS_PARM5); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(pos[2] || size[2]) VM_Warning(prog, "VM_drawpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); DrawQ_Pic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawrotpic float drawrotpic(vector position, string pic, vector size, vector org, float angle, vector rgb, float alpha, float flag) ========= */ void VM_drawrotpic(prvm_prog_t *prog) { const char *picname; prvm_vec_t *size, *pos, *org, *rgb; int flag; VM_SAFEPARMCOUNT(8,VM_drawrotpic); picname = PRVM_G_STRING(OFS_PARM1); VM_CheckEmptyString(prog, picname); // is pic cached ? no function yet for that if(!1) { PRVM_G_FLOAT(OFS_RETURN) = -4; VM_Warning(prog, "VM_drawrotpic: %s: %s not cached !\n", prog->name, picname); return; } pos = PRVM_G_VECTOR(OFS_PARM0); size = PRVM_G_VECTOR(OFS_PARM2); org = PRVM_G_VECTOR(OFS_PARM3); rgb = PRVM_G_VECTOR(OFS_PARM5); flag = (int) PRVM_G_FLOAT(OFS_PARM7); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawrotpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(pos[2] || size[2] || org[2]) VM_Warning(prog, "VM_drawrotpic: z value from pos/size/org discarded\n"); DrawQ_RotPic(pos[0], pos[1], Draw_CachePic_Flags(picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], org[0], org[1], PRVM_G_FLOAT(OFS_PARM4), rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM6), flag); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawsubpic float drawsubpic(vector position, vector size, string pic, vector srcPos, vector srcSize, vector rgb, float alpha, float flag) ========= */ void VM_drawsubpic(prvm_prog_t *prog) { const char *picname; prvm_vec_t *size, *pos, *rgb, *srcPos, *srcSize, alpha; int flag; VM_SAFEPARMCOUNT(8,VM_drawsubpic); picname = PRVM_G_STRING(OFS_PARM2); VM_CheckEmptyString(prog, picname); // is pic cached ? no function yet for that if(!1) { PRVM_G_FLOAT(OFS_RETURN) = -4; VM_Warning(prog, "VM_drawsubpic: %s: %s not cached !\n", prog->name, picname); return; } pos = PRVM_G_VECTOR(OFS_PARM0); size = PRVM_G_VECTOR(OFS_PARM1); srcPos = PRVM_G_VECTOR(OFS_PARM3); srcSize = PRVM_G_VECTOR(OFS_PARM4); rgb = PRVM_G_VECTOR(OFS_PARM5); alpha = PRVM_G_FLOAT(OFS_PARM6); flag = (int) PRVM_G_FLOAT(OFS_PARM7); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawsubpic: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(pos[2] || size[2]) VM_Warning(prog, "VM_drawsubpic: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); DrawQ_SuperPic(pos[0], pos[1], Draw_CachePic_Flags (picname, CACHEPICFLAG_NOTPERSISTENT), size[0], size[1], srcPos[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, srcPos[0] + srcSize[0], srcPos[1], rgb[0], rgb[1], rgb[2], alpha, srcPos[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, srcPos[0] + srcSize[0], srcPos[1] + srcSize[1], rgb[0], rgb[1], rgb[2], alpha, flag); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawfill float drawfill(vector position, vector size, vector rgb, float alpha, float flag) ========= */ void VM_drawfill(prvm_prog_t *prog) { prvm_vec_t *size, *pos, *rgb; int flag; VM_SAFEPARMCOUNT(5,VM_drawfill); pos = PRVM_G_VECTOR(OFS_PARM0); size = PRVM_G_VECTOR(OFS_PARM1); rgb = PRVM_G_VECTOR(OFS_PARM2); flag = (int) PRVM_G_FLOAT(OFS_PARM4); if(flag < DRAWFLAG_NORMAL || flag >=DRAWFLAG_NUMFLAGS) { PRVM_G_FLOAT(OFS_RETURN) = -2; VM_Warning(prog, "VM_drawfill: %s: wrong DRAWFLAG %i !\n",prog->name,flag); return; } if(pos[2] || size[2]) VM_Warning(prog, "VM_drawfill: z value%s from %s discarded\n",(pos[2] && size[2]) ? "s" : " ",((pos[2] && size[2]) ? "pos and size" : (pos[2] ? "pos" : "size"))); DrawQ_Fill(pos[0], pos[1], size[0], size[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM3), flag); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_drawsetcliparea drawsetcliparea(float x, float y, float width, float height) ========= */ void VM_drawsetcliparea(prvm_prog_t *prog) { float x,y,w,h; VM_SAFEPARMCOUNT(4,VM_drawsetcliparea); x = bound(0, PRVM_G_FLOAT(OFS_PARM0), vid_conwidth.integer); y = bound(0, PRVM_G_FLOAT(OFS_PARM1), vid_conheight.integer); w = bound(0, PRVM_G_FLOAT(OFS_PARM2) + PRVM_G_FLOAT(OFS_PARM0) - x, (vid_conwidth.integer - x)); h = bound(0, PRVM_G_FLOAT(OFS_PARM3) + PRVM_G_FLOAT(OFS_PARM1) - y, (vid_conheight.integer - y)); DrawQ_SetClipArea(x, y, w, h); } /* ========= VM_drawresetcliparea drawresetcliparea() ========= */ void VM_drawresetcliparea(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_drawresetcliparea); DrawQ_ResetClipArea(); } /* ========= VM_getimagesize vector getimagesize(string pic) ========= */ void VM_getimagesize(prvm_prog_t *prog) { const char *p; cachepic_t *pic; VM_SAFEPARMCOUNT(1,VM_getimagesize); p = PRVM_G_STRING(OFS_PARM0); VM_CheckEmptyString(prog, p); pic = Draw_CachePic_Flags (p, CACHEPICFLAG_NOTPERSISTENT); if( pic->tex == r_texture_notexture ) { PRVM_G_VECTOR(OFS_RETURN)[0] = 0; PRVM_G_VECTOR(OFS_RETURN)[1] = 0; } else { PRVM_G_VECTOR(OFS_RETURN)[0] = pic->width; PRVM_G_VECTOR(OFS_RETURN)[1] = pic->height; } PRVM_G_VECTOR(OFS_RETURN)[2] = 0; } /* ========= VM_keynumtostring string keynumtostring(float keynum) ========= */ void VM_keynumtostring (prvm_prog_t *prog) { char tinystr[2]; VM_SAFEPARMCOUNT(1, VM_keynumtostring); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Key_KeynumToString((int)PRVM_G_FLOAT(OFS_PARM0), tinystr, sizeof(tinystr))); } /* ========= VM_findkeysforcommand string findkeysforcommand(string command, float bindmap) the returned string is an altstring ========= */ #define FKFC_NUMKEYS 5 void M_FindKeysForCommand(const char *command, int *keys); void VM_findkeysforcommand(prvm_prog_t *prog) { const char *cmd; char ret[VM_STRINGTEMP_LENGTH]; int keys[FKFC_NUMKEYS]; int i; int bindmap; char vabuf[1024]; VM_SAFEPARMCOUNTRANGE(1, 2, VM_findkeysforcommand); cmd = PRVM_G_STRING(OFS_PARM0); if(prog->argc == 2) bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); else bindmap = 0; // consistent to "bind" VM_CheckEmptyString(prog, cmd); Key_FindKeysForCommand(cmd, keys, FKFC_NUMKEYS, bindmap); ret[0] = 0; for(i = 0; i < FKFC_NUMKEYS; i++) strlcat(ret, va(vabuf, sizeof(vabuf), " \'%i\'", keys[i]), sizeof(ret)); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ret); } /* ========= VM_stringtokeynum float stringtokeynum(string key) ========= */ void VM_stringtokeynum (prvm_prog_t *prog) { VM_SAFEPARMCOUNT( 1, VM_keynumtostring ); PRVM_G_FLOAT(OFS_RETURN) = Key_StringToKeynum(PRVM_G_STRING(OFS_PARM0)); } /* ========= VM_getkeybind string getkeybind(float key, float bindmap) ========= */ void VM_getkeybind (prvm_prog_t *prog) { int bindmap; VM_SAFEPARMCOUNTRANGE(1, 2, VM_CL_getkeybind); if(prog->argc == 2) bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM1), MAX_BINDMAPS-1); else bindmap = 0; // consistent to "bind" PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, Key_GetBind((int)PRVM_G_FLOAT(OFS_PARM0), bindmap)); } /* ========= VM_setkeybind float setkeybind(float key, string cmd, float bindmap) ========= */ void VM_setkeybind (prvm_prog_t *prog) { int bindmap; VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_setkeybind); if(prog->argc == 3) bindmap = bound(-1, PRVM_G_FLOAT(OFS_PARM2), MAX_BINDMAPS-1); else bindmap = 0; // consistent to "bind" PRVM_G_FLOAT(OFS_RETURN) = 0; if(Key_SetBinding((int)PRVM_G_FLOAT(OFS_PARM0), bindmap, PRVM_G_STRING(OFS_PARM1))) PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ========= VM_getbindmap vector getbindmaps() ========= */ void VM_getbindmaps (prvm_prog_t *prog) { int fg, bg; VM_SAFEPARMCOUNT(0, VM_CL_getbindmap); Key_GetBindMap(&fg, &bg); PRVM_G_VECTOR(OFS_RETURN)[0] = fg; PRVM_G_VECTOR(OFS_RETURN)[1] = bg; PRVM_G_VECTOR(OFS_RETURN)[2] = 0; } /* ========= VM_setbindmap float setbindmaps(vector bindmap) ========= */ void VM_setbindmaps (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_CL_setbindmap); PRVM_G_FLOAT(OFS_RETURN) = 0; if(PRVM_G_VECTOR(OFS_PARM0)[2] == 0) if(Key_SetBindMap((int)PRVM_G_VECTOR(OFS_PARM0)[0], (int)PRVM_G_VECTOR(OFS_PARM0)[1])) PRVM_G_FLOAT(OFS_RETURN) = 1; } // CL_Video interface functions /* ======================== VM_cin_open float cin_open(string file, string name) ======================== */ void VM_cin_open(prvm_prog_t *prog) { const char *file; const char *name; VM_SAFEPARMCOUNT( 2, VM_cin_open ); file = PRVM_G_STRING( OFS_PARM0 ); name = PRVM_G_STRING( OFS_PARM1 ); VM_CheckEmptyString(prog, file ); VM_CheckEmptyString(prog, name ); if( CL_OpenVideo( file, name, MENUOWNER, "" ) ) PRVM_G_FLOAT( OFS_RETURN ) = 1; else PRVM_G_FLOAT( OFS_RETURN ) = 0; } /* ======================== VM_cin_close void cin_close(string name) ======================== */ void VM_cin_close(prvm_prog_t *prog) { const char *name; VM_SAFEPARMCOUNT( 1, VM_cin_close ); name = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString(prog, name ); CL_CloseVideo( CL_GetVideoByName( name ) ); } /* ======================== VM_cin_setstate void cin_setstate(string name, float type) ======================== */ void VM_cin_setstate(prvm_prog_t *prog) { const char *name; clvideostate_t state; clvideo_t *video; VM_SAFEPARMCOUNT( 2, VM_cin_netstate ); name = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString(prog, name ); state = (clvideostate_t)((int)PRVM_G_FLOAT( OFS_PARM1 )); video = CL_GetVideoByName( name ); if( video && state > CLVIDEO_UNUSED && state < CLVIDEO_STATECOUNT ) CL_SetVideoState( video, state ); } /* ======================== VM_cin_getstate float cin_getstate(string name) ======================== */ void VM_cin_getstate(prvm_prog_t *prog) { const char *name; clvideo_t *video; VM_SAFEPARMCOUNT( 1, VM_cin_getstate ); name = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString(prog, name ); video = CL_GetVideoByName( name ); if( video ) PRVM_G_FLOAT( OFS_RETURN ) = (int)video->state; else PRVM_G_FLOAT( OFS_RETURN ) = 0; } /* ======================== VM_cin_restart void cin_restart(string name) ======================== */ void VM_cin_restart(prvm_prog_t *prog) { const char *name; clvideo_t *video; VM_SAFEPARMCOUNT( 1, VM_cin_restart ); name = PRVM_G_STRING( OFS_PARM0 ); VM_CheckEmptyString(prog, name ); video = CL_GetVideoByName( name ); if( video ) CL_RestartVideo( video ); } /* ======================== VM_gecko_create float[bool] gecko_create( string name ) ======================== */ void VM_gecko_create(prvm_prog_t *prog) { // REMOVED PRVM_G_FLOAT( OFS_RETURN ) = 0; } /* ======================== VM_gecko_destroy void gecko_destroy( string name ) ======================== */ void VM_gecko_destroy(prvm_prog_t *prog) { // REMOVED } /* ======================== VM_gecko_navigate void gecko_navigate( string name, string URI ) ======================== */ void VM_gecko_navigate(prvm_prog_t *prog) { // REMOVED } /* ======================== VM_gecko_keyevent float[bool] gecko_keyevent( string name, float key, float eventtype ) ======================== */ void VM_gecko_keyevent(prvm_prog_t *prog) { // REMOVED PRVM_G_FLOAT( OFS_RETURN ) = 0; } /* ======================== VM_gecko_movemouse void gecko_mousemove( string name, float x, float y ) ======================== */ void VM_gecko_movemouse(prvm_prog_t *prog) { // REMOVED } /* ======================== VM_gecko_resize void gecko_resize( string name, float w, float h ) ======================== */ void VM_gecko_resize(prvm_prog_t *prog) { // REMOVED } /* ======================== VM_gecko_get_texture_extent vector gecko_get_texture_extent( string name ) ======================== */ void VM_gecko_get_texture_extent(prvm_prog_t *prog) { // REMOVED PRVM_G_VECTOR(OFS_RETURN)[0] = 0; PRVM_G_VECTOR(OFS_RETURN)[1] = 0; } /* ============== VM_makevectors Writes new values for v_forward, v_up, and v_right based on angles void makevectors(vector angle) ============== */ void VM_makevectors (prvm_prog_t *prog) { vec3_t angles, forward, right, up; VM_SAFEPARMCOUNT(1, VM_makevectors); VectorCopy(PRVM_G_VECTOR(OFS_PARM0), angles); AngleVectors(angles, forward, right, up); VectorCopy(forward, PRVM_gameglobalvector(v_forward)); VectorCopy(right, PRVM_gameglobalvector(v_right)); VectorCopy(up, PRVM_gameglobalvector(v_up)); } /* ============== VM_vectorvectors Writes new values for v_forward, v_up, and v_right based on the given forward vector vectorvectors(vector) ============== */ void VM_vectorvectors (prvm_prog_t *prog) { vec3_t forward, right, up; VM_SAFEPARMCOUNT(1, VM_vectorvectors); VectorNormalize2(PRVM_G_VECTOR(OFS_PARM0), forward); VectorVectors(forward, right, up); VectorCopy(forward, PRVM_gameglobalvector(v_forward)); VectorCopy(right, PRVM_gameglobalvector(v_right)); VectorCopy(up, PRVM_gameglobalvector(v_up)); } /* ======================== VM_drawline void drawline(float width, vector pos1, vector pos2, vector rgb, float alpha, float flags) ======================== */ void VM_drawline (prvm_prog_t *prog) { prvm_vec_t *c1, *c2, *rgb; float alpha, width; unsigned char flags; VM_SAFEPARMCOUNT(6, VM_drawline); width = PRVM_G_FLOAT(OFS_PARM0); c1 = PRVM_G_VECTOR(OFS_PARM1); c2 = PRVM_G_VECTOR(OFS_PARM2); rgb = PRVM_G_VECTOR(OFS_PARM3); alpha = PRVM_G_FLOAT(OFS_PARM4); flags = (int)PRVM_G_FLOAT(OFS_PARM5); DrawQ_Line(width, c1[0], c1[1], c2[0], c2[1], rgb[0], rgb[1], rgb[2], alpha, flags); } // float(float number, float quantity) bitshift (EXT_BITSHIFT) void VM_bitshift (prvm_prog_t *prog) { prvm_int_t n1, n2; VM_SAFEPARMCOUNT(2, VM_bitshift); n1 = (prvm_int_t)fabs((prvm_vec_t)((prvm_int_t)PRVM_G_FLOAT(OFS_PARM0))); n2 = (prvm_int_t)PRVM_G_FLOAT(OFS_PARM1); if(!n1) PRVM_G_FLOAT(OFS_RETURN) = n1; else if(n2 < 0) PRVM_G_FLOAT(OFS_RETURN) = (n1 >> -n2); else PRVM_G_FLOAT(OFS_RETURN) = (n1 << n2); } //////////////////////////////////////// // AltString functions //////////////////////////////////////// /* ======================== VM_altstr_count float altstr_count(string) ======================== */ void VM_altstr_count(prvm_prog_t *prog) { const char *altstr, *pos; int count; VM_SAFEPARMCOUNT( 1, VM_altstr_count ); altstr = PRVM_G_STRING( OFS_PARM0 ); //VM_CheckEmptyString(prog, altstr ); for( count = 0, pos = altstr ; *pos ; pos++ ) { if( *pos == '\\' ) { if( !*++pos ) { break; } } else if( *pos == '\'' ) { count++; } } PRVM_G_FLOAT( OFS_RETURN ) = (prvm_vec_t) (count / 2); } /* ======================== VM_altstr_prepare string altstr_prepare(string) ======================== */ void VM_altstr_prepare(prvm_prog_t *prog) { const char *instr, *in; char outstr[VM_STRINGTEMP_LENGTH]; size_t outpos; VM_SAFEPARMCOUNT( 1, VM_altstr_prepare ); instr = PRVM_G_STRING( OFS_PARM0 ); for (in = instr, outpos = 0; *in && outpos < sizeof(outstr) - 1; ++in) { if (*in == '\'' && outpos < sizeof(outstr) - 2) { outstr[outpos++] = '\\'; outstr[outpos++] = '\''; } else outstr[outpos++] = *in; } outstr[outpos] = 0; PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); } /* ======================== VM_altstr_get string altstr_get(string, float) ======================== */ void VM_altstr_get(prvm_prog_t *prog) { const char *altstr, *pos; char *out; int count, size; char outstr[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT( 2, VM_altstr_get ); altstr = PRVM_G_STRING( OFS_PARM0 ); count = (int)PRVM_G_FLOAT( OFS_PARM1 ); count = count * 2 + 1; for( pos = altstr ; *pos && count ; pos++ ) if( *pos == '\\' ) { if( !*++pos ) break; } else if( *pos == '\'' ) count--; if( !*pos ) { PRVM_G_INT( OFS_RETURN ) = 0; return; } for( out = outstr, size = sizeof(outstr) - 1 ; size && *pos ; size--, pos++, out++ ) if( *pos == '\\' ) { if( !*++pos ) break; *out = *pos; size--; } else if( *pos == '\'' ) break; else *out = *pos; *out = 0; PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); } /* ======================== VM_altstr_set string altstr_set(string altstr, float num, string set) ======================== */ void VM_altstr_set(prvm_prog_t *prog) { int num; const char *altstr, *str; const char *in; char *out; char outstr[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT( 3, VM_altstr_set ); altstr = PRVM_G_STRING( OFS_PARM0 ); num = (int)PRVM_G_FLOAT( OFS_PARM1 ); str = PRVM_G_STRING( OFS_PARM2 ); out = outstr; for( num = num * 2 + 1, in = altstr; *in && num; *out++ = *in++ ) if( *in == '\\' ) { if( !*++in ) { break; } } else if( *in == '\'' ) { num--; } // copy set in for( ; *str; *out++ = *str++ ); // now jump over the old content for( ; *in ; in++ ) if( *in == '\'' || (*in == '\\' && !*++in) ) break; strlcpy(out, in, outstr + sizeof(outstr) - out); PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); } /* ======================== VM_altstr_ins insert after num string altstr_ins(string altstr, float num, string set) ======================== */ void VM_altstr_ins(prvm_prog_t *prog) { int num; const char *set; const char *in; char *out; char outstr[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(3, VM_altstr_ins); in = PRVM_G_STRING( OFS_PARM0 ); num = (int)PRVM_G_FLOAT( OFS_PARM1 ); set = PRVM_G_STRING( OFS_PARM2 ); out = outstr; for( num = num * 2 + 2 ; *in && num > 0 ; *out++ = *in++ ) if( *in == '\\' ) { if( !*++in ) { break; } } else if( *in == '\'' ) { num--; } *out++ = '\''; for( ; *set ; *out++ = *set++ ); *out++ = '\''; strlcpy(out, in, outstr + sizeof(outstr) - out); PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString(prog, outstr ); } //////////////////////////////////////// // BufString functions //////////////////////////////////////// //[515]: string buffers support static size_t stringbuffers_sortlength; static void BufStr_Expand(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex) { if (stringbuffer->max_strings <= strindex) { char **oldstrings = stringbuffer->strings; stringbuffer->max_strings = max(stringbuffer->max_strings * 2, 128); while (stringbuffer->max_strings <= strindex) stringbuffer->max_strings *= 2; stringbuffer->strings = (char **) Mem_Alloc(prog->progs_mempool, stringbuffer->max_strings * sizeof(stringbuffer->strings[0])); if (stringbuffer->num_strings > 0) memcpy(stringbuffer->strings, oldstrings, stringbuffer->num_strings * sizeof(stringbuffer->strings[0])); if (oldstrings) Mem_Free(oldstrings); } } static void BufStr_Shrink(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer) { // reduce num_strings if there are empty string slots at the end while (stringbuffer->num_strings > 0 && stringbuffer->strings[stringbuffer->num_strings - 1] == NULL) stringbuffer->num_strings--; // if empty, free the string pointer array if (stringbuffer->num_strings == 0) { stringbuffer->max_strings = 0; if (stringbuffer->strings) Mem_Free(stringbuffer->strings); stringbuffer->strings = NULL; } } static int BufStr_SortStringsUP (const void *in1, const void *in2) { const char *a, *b; a = *((const char **) in1); b = *((const char **) in2); if(!a || !a[0]) return 1; if(!b || !b[0]) return -1; return strncmp(a, b, stringbuffers_sortlength); } static int BufStr_SortStringsDOWN (const void *in1, const void *in2) { const char *a, *b; a = *((const char **) in1); b = *((const char **) in2); if(!a || !a[0]) return 1; if(!b || !b[0]) return -1; return strncmp(b, a, stringbuffers_sortlength); } prvm_stringbuffer_t *BufStr_FindCreateReplace (prvm_prog_t *prog, int bufindex, int flags, const char *format) { prvm_stringbuffer_t *stringbuffer; int i; if (bufindex < 0) return NULL; // find buffer with wanted index if (bufindex < (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray)) { if ( (stringbuffer = (prvm_stringbuffer_t*) Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, bufindex)) ) { if (stringbuffer->flags & STRINGBUFFER_TEMP) stringbuffer->flags = flags; // created but has not been used yet return stringbuffer; } return NULL; } // allocate new buffer with wanted index while(1) { stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray); stringbuffer->flags = STRINGBUFFER_TEMP; for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++); if (i == bufindex) { stringbuffer->flags = flags; // mark as used break; } } return stringbuffer; } void BufStr_Set(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer, int strindex, const char *str) { size_t alloclen; if (!stringbuffer || strindex < 0) return; BufStr_Expand(prog, stringbuffer, strindex); stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); if (stringbuffer->strings[strindex]) Mem_Free(stringbuffer->strings[strindex]); stringbuffer->strings[strindex] = NULL; if (str) { // not the NULL string! alloclen = strlen(str) + 1; stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); memcpy(stringbuffer->strings[strindex], str, alloclen); } BufStr_Shrink(prog, stringbuffer); } void BufStr_Del(prvm_prog_t *prog, prvm_stringbuffer_t *stringbuffer) { int i; if (!stringbuffer) return; for (i = 0;i < stringbuffer->num_strings;i++) if (stringbuffer->strings[i]) Mem_Free(stringbuffer->strings[i]); if (stringbuffer->strings) Mem_Free(stringbuffer->strings); if(stringbuffer->origin) PRVM_Free((char *)stringbuffer->origin); Mem_ExpandableArray_FreeRecord(&prog->stringbuffersarray, stringbuffer); } void BufStr_Flush(prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; int i, numbuffers; numbuffers = (int)Mem_ExpandableArray_IndexRange(&prog->stringbuffersarray); for (i = 0; i < numbuffers; i++) if ( (stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i)) ) BufStr_Del(prog, stringbuffer); Mem_ExpandableArray_NewArray(&prog->stringbuffersarray, prog->progs_mempool, sizeof(prvm_stringbuffer_t), 64); } /* ======================== VM_buf_create creates new buffer, and returns it's index, returns -1 if failed float buf_create(prvm_prog_t *prog) = #460; float newbuf(string format, float flags) = #460; ======================== */ void VM_buf_create (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; int i; VM_SAFEPARMCOUNTRANGE(0, 2, VM_buf_create); // VorteX: optional parm1 (buffer format) is unfinished, to keep intact with future databuffers extension must be set to "string" if(prog->argc >= 1 && strcmp(PRVM_G_STRING(OFS_PARM0), "string")) { PRVM_G_FLOAT(OFS_RETURN) = -1; return; } stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray); for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++); stringbuffer->origin = PRVM_AllocationOrigin(prog); // optional flags parm if (prog->argc >= 2) stringbuffer->flags = (int)PRVM_G_FLOAT(OFS_PARM1) & STRINGBUFFER_QCFLAGS; PRVM_G_FLOAT(OFS_RETURN) = i; } /* ======================== VM_buf_del deletes buffer and all strings in it void buf_del(float bufhandle) = #461; ======================== */ void VM_buf_del (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNT(1, VM_buf_del); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if (stringbuffer) BufStr_Del(prog, stringbuffer); else { VM_Warning(prog, "VM_buf_del: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } } /* ======================== VM_buf_getsize how many strings are stored in buffer float buf_getsize(float bufhandle) = #462; ======================== */ void VM_buf_getsize (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNT(1, VM_buf_getsize); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { PRVM_G_FLOAT(OFS_RETURN) = -1; VM_Warning(prog, "VM_buf_getsize: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } else PRVM_G_FLOAT(OFS_RETURN) = stringbuffer->num_strings; } /* ======================== VM_buf_copy copy all content from one buffer to another, make sure it exists void buf_copy(float bufhandle_from, float bufhandle_to) = #463; ======================== */ void VM_buf_copy (prvm_prog_t *prog) { prvm_stringbuffer_t *srcstringbuffer, *dststringbuffer; int i; VM_SAFEPARMCOUNT(2, VM_buf_copy); srcstringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!srcstringbuffer) { VM_Warning(prog, "VM_buf_copy: invalid source buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } i = (int)PRVM_G_FLOAT(OFS_PARM1); if(i == (int)PRVM_G_FLOAT(OFS_PARM0)) { VM_Warning(prog, "VM_buf_copy: source == destination (%i) in %s\n", i, prog->name); return; } dststringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!dststringbuffer) { VM_Warning(prog, "VM_buf_copy: invalid destination buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); return; } for (i = 0;i < dststringbuffer->num_strings;i++) if (dststringbuffer->strings[i]) Mem_Free(dststringbuffer->strings[i]); if (dststringbuffer->strings) Mem_Free(dststringbuffer->strings); *dststringbuffer = *srcstringbuffer; if (dststringbuffer->max_strings) dststringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(dststringbuffer->strings[0]) * dststringbuffer->max_strings); for (i = 0;i < dststringbuffer->num_strings;i++) { if (srcstringbuffer->strings[i]) { size_t stringlen; stringlen = strlen(srcstringbuffer->strings[i]) + 1; dststringbuffer->strings[i] = (char *)Mem_Alloc(prog->progs_mempool, stringlen); memcpy(dststringbuffer->strings[i], srcstringbuffer->strings[i], stringlen); } } } /* ======================== VM_buf_sort sort buffer by beginnings of strings (cmplength defaults it's length) "backward == TRUE" means that sorting goes upside-down void buf_sort(float bufhandle, float cmplength, float backward) = #464; ======================== */ void VM_buf_sort (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNT(3, VM_buf_sort); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_buf_sort: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } if(stringbuffer->num_strings <= 0) { VM_Warning(prog, "VM_buf_sort: tried to sort empty buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } stringbuffers_sortlength = (int)PRVM_G_FLOAT(OFS_PARM1); if(stringbuffers_sortlength <= 0) stringbuffers_sortlength = 0x7FFFFFFF; if(!PRVM_G_FLOAT(OFS_PARM2)) qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsUP); else qsort(stringbuffer->strings, stringbuffer->num_strings, sizeof(char*), BufStr_SortStringsDOWN); BufStr_Shrink(prog, stringbuffer); } /* ======================== VM_buf_implode concantenates all buffer string into one with "glue" separator and returns it as tempstring string buf_implode(float bufhandle, string glue) = #465; ======================== */ void VM_buf_implode (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; char k[VM_STRINGTEMP_LENGTH]; const char *sep; int i; size_t l; VM_SAFEPARMCOUNT(2, VM_buf_implode); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); PRVM_G_INT(OFS_RETURN) = OFS_NULL; if(!stringbuffer) { VM_Warning(prog, "VM_buf_implode: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } if(!stringbuffer->num_strings) return; sep = PRVM_G_STRING(OFS_PARM1); k[0] = 0; for(l = i = 0;i < stringbuffer->num_strings;i++) { if(stringbuffer->strings[i]) { l += (i > 0 ? strlen(sep) : 0) + strlen(stringbuffer->strings[i]); if (l >= sizeof(k) - 1) break; strlcat(k, sep, sizeof(k)); strlcat(k, stringbuffer->strings[i], sizeof(k)); } } PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, k); } /* ======================== VM_bufstr_get get a string from buffer, returns tempstring, dont str_unzone it! string bufstr_get(float bufhandle, float string_index) = #465; ======================== */ void VM_bufstr_get (prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; int strindex; VM_SAFEPARMCOUNT(2, VM_bufstr_get); PRVM_G_INT(OFS_RETURN) = OFS_NULL; stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } strindex = (int)PRVM_G_FLOAT(OFS_PARM1); if (strindex < 0) { // VM_Warning(prog, "VM_bufstr_get: invalid string index %i used in %s\n", strindex, prog->name); return; } if (strindex < stringbuffer->num_strings && stringbuffer->strings[strindex]) PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, stringbuffer->strings[strindex]); } /* ======================== VM_bufstr_set copies a string into selected slot of buffer void bufstr_set(float bufhandle, float string_index, string str) = #466; ======================== */ void VM_bufstr_set (prvm_prog_t *prog) { int strindex; prvm_stringbuffer_t *stringbuffer; const char *news; VM_SAFEPARMCOUNT(3, VM_bufstr_set); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_set: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } strindex = (int)PRVM_G_FLOAT(OFS_PARM1); if(strindex < 0 || strindex >= 1000000) // huge number of strings { VM_Warning(prog, "VM_bufstr_set: invalid string index %i used in %s\n", strindex, prog->name); return; } news = PRVM_G_STRING(OFS_PARM2); BufStr_Set(prog, stringbuffer, strindex, news); } /* ======================== VM_bufstr_add adds string to buffer in first free slot and returns its index "order == TRUE" means that string will be added after last "full" slot float bufstr_add(float bufhandle, string str, float order) = #467; ======================== */ void VM_bufstr_add (prvm_prog_t *prog) { int order, strindex; prvm_stringbuffer_t *stringbuffer; const char *string; size_t alloclen; VM_SAFEPARMCOUNT(3, VM_bufstr_add); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); PRVM_G_FLOAT(OFS_RETURN) = -1; if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_add: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } if(!PRVM_G_INT(OFS_PARM1)) // NULL string { VM_Warning(prog, "VM_bufstr_add: can not add an empty string to buffer %i in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } string = PRVM_G_STRING(OFS_PARM1); order = (int)PRVM_G_FLOAT(OFS_PARM2); if(order) strindex = stringbuffer->num_strings; else for (strindex = 0;strindex < stringbuffer->num_strings;strindex++) if (stringbuffer->strings[strindex] == NULL) break; BufStr_Expand(prog, stringbuffer, strindex); stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); alloclen = strlen(string) + 1; stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); memcpy(stringbuffer->strings[strindex], string, alloclen); PRVM_G_FLOAT(OFS_RETURN) = strindex; } /* ======================== VM_bufstr_free delete string from buffer void bufstr_free(float bufhandle, float string_index) = #468; ======================== */ void VM_bufstr_free (prvm_prog_t *prog) { int i; prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNT(2, VM_bufstr_free); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } i = (int)PRVM_G_FLOAT(OFS_PARM1); if(i < 0) { VM_Warning(prog, "VM_bufstr_free: invalid string index %i used in %s\n", i, prog->name); return; } if (i < stringbuffer->num_strings) { if(stringbuffer->strings[i]) Mem_Free(stringbuffer->strings[i]); stringbuffer->strings[i] = NULL; } BufStr_Shrink(prog, stringbuffer); } /* ======================== VM_buf_loadfile load a file into string buffer, return 0 or 1 float buf_loadfile(string filename, float bufhandle) = #535; ======================== */ void VM_buf_loadfile(prvm_prog_t *prog) { size_t alloclen; prvm_stringbuffer_t *stringbuffer; char string[VM_STRINGTEMP_LENGTH]; int strindex, c, end; const char *filename; char vabuf[1024]; qfile_t *file; VM_SAFEPARMCOUNT(2, VM_buf_loadfile); // get file filename = PRVM_G_STRING(OFS_PARM0); file = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "data/%s", filename), false); if (file == NULL) file = FS_OpenVirtualFile(va(vabuf, sizeof(vabuf), "%s", filename), false); if (file == NULL) { if (developer_extra.integer) VM_Warning(prog, "VM_buf_loadfile: failed to open file %s in %s\n", filename, prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // get string buffer stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1)); if(!stringbuffer) { VM_Warning(prog, "VM_buf_loadfile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // read file (append to the end of buffer) strindex = stringbuffer->num_strings; while(1) { // read line end = 0; for (;;) { c = FS_Getc(file); if (c == '\r' || c == '\n' || c < 0) break; if (end < VM_STRINGTEMP_LENGTH - 1) string[end++] = c; } string[end] = 0; // remove \n following \r if (c == '\r') { c = FS_Getc(file); if (c != '\n') FS_UnGetc(file, (unsigned char)c); } // add and continue if (c >= 0 || end) { BufStr_Expand(prog, stringbuffer, strindex); stringbuffer->num_strings = max(stringbuffer->num_strings, strindex + 1); alloclen = strlen(string) + 1; stringbuffer->strings[strindex] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); memcpy(stringbuffer->strings[strindex], string, alloclen); strindex = stringbuffer->num_strings; } else break; } // close file FS_Close(file); PRVM_G_FLOAT(OFS_RETURN) = 1; } /* ======================== VM_buf_writefile writes stringbuffer to a file, returns 0 or 1 float buf_writefile(float filehandle, float bufhandle, [, float startpos, float numstrings]) = #468; ======================== */ void VM_buf_writefile(prvm_prog_t *prog) { int filenum, strindex, strnum, strlength; prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNTRANGE(2, 4, VM_buf_writefile); // get file filenum = (int)PRVM_G_FLOAT(OFS_PARM0); if (filenum < 0 || filenum >= PRVM_MAX_OPENFILES) { VM_Warning(prog, "VM_buf_writefile: invalid file handle %i used in %s\n", filenum, prog->name); return; } if (prog->openfiles[filenum] == NULL) { VM_Warning(prog, "VM_buf_writefile: no such file handle %i (or file has been closed) in %s\n", filenum, prog->name); return; } // get string buffer stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM1)); if(!stringbuffer) { VM_Warning(prog, "VM_buf_writefile: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM1), prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // get start and end parms if (prog->argc > 3) { strindex = (int)PRVM_G_FLOAT(OFS_PARM2); strnum = (int)PRVM_G_FLOAT(OFS_PARM3); } else if (prog->argc > 2) { strindex = (int)PRVM_G_FLOAT(OFS_PARM2); strnum = stringbuffer->num_strings - strindex; } else { strindex = 0; strnum = stringbuffer->num_strings; } if (strindex < 0 || strindex >= stringbuffer->num_strings) { VM_Warning(prog, "VM_buf_writefile: wrong start string index %i used in %s\n", strindex, prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } if (strnum < 0) { VM_Warning(prog, "VM_buf_writefile: wrong strings count %i used in %s\n", strnum, prog->name); PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // write while(strindex < stringbuffer->num_strings && strnum) { if (stringbuffer->strings[strindex]) { if ((strlength = (int)strlen(stringbuffer->strings[strindex]))) FS_Write(prog->openfiles[filenum], stringbuffer->strings[strindex], strlength); FS_Write(prog->openfiles[filenum], "\n", 1); } strindex++; strnum--; } PRVM_G_FLOAT(OFS_RETURN) = 1; } #define MATCH_AUTO 0 #define MATCH_WHOLE 1 #define MATCH_LEFT 2 #define MATCH_RIGHT 3 #define MATCH_MIDDLE 4 #define MATCH_PATTERN 5 static const char *detect_match_rule(char *pattern, int *matchrule) { char *ppos, *qpos; int patternlength; patternlength = (int)strlen(pattern); ppos = strchr(pattern, '*'); qpos = strchr(pattern, '?'); // has ? - pattern if (qpos) { *matchrule = MATCH_PATTERN; return pattern; } // has * - left, mid, right or pattern if (ppos) { // starts with * - may be right/mid or pattern if ((ppos - pattern) == 0) { ppos = strchr(pattern+1, '*'); // *something if (!ppos) { *matchrule = MATCH_RIGHT; return pattern+1; } // *something* if ((ppos - pattern) == patternlength) { *matchrule = MATCH_MIDDLE; *ppos = 0; return pattern+1; } // *som*thing *matchrule = MATCH_PATTERN; return pattern; } // end with * - left if ((ppos - pattern) == patternlength) { *matchrule = MATCH_LEFT; *ppos = 0; return pattern; } // som*thing *matchrule = MATCH_PATTERN; return pattern; } // have no wildcards - whole string *matchrule = MATCH_WHOLE; return pattern; } // todo: support UTF8 static qboolean match_rule(const char *string, int max_string, const char *pattern, int patternlength, int rule) { const char *mid; if (rule == 1) return !strncmp(string, pattern, max_string) ? true : false; if (rule == 2) return !strncmp(string, pattern, patternlength) ? true : false; if (rule == 3) { mid = strstr(string, pattern); return mid && !*(mid+patternlength); } if (rule == 4) return strstr(string, pattern) ? true : false; // pattern return matchpattern_with_separator(string, pattern, false, "", false) ? true : false; } /* ======================== VM_bufstr_find find an index of bufstring matching rule float bufstr_find(float bufhandle, string match, float matchrule, float startpos, float step) = #468; ======================== */ void VM_bufstr_find(prvm_prog_t *prog) { prvm_stringbuffer_t *stringbuffer; char string[VM_STRINGTEMP_LENGTH]; int matchrule, matchlen, i, step; const char *match; VM_SAFEPARMCOUNTRANGE(3, 5, VM_bufstr_find); PRVM_G_FLOAT(OFS_RETURN) = -1; // get string buffer stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_find: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } // get pattern/rule matchrule = (int)PRVM_G_FLOAT(OFS_PARM2); if (matchrule < 0 && matchrule > 5) { VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name); return; } if (matchrule) match = PRVM_G_STRING(OFS_PARM1); else { strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string)); match = detect_match_rule(string, &matchrule); } matchlen = (int)strlen(match); // find i = (prog->argc > 3) ? (int)PRVM_G_FLOAT(OFS_PARM3) : 0; step = (prog->argc > 4) ? (int)PRVM_G_FLOAT(OFS_PARM4) : 1; while(i < stringbuffer->num_strings) { if (stringbuffer->strings[i] && match_rule(stringbuffer->strings[i], VM_STRINGTEMP_LENGTH, match, matchlen, matchrule)) { PRVM_G_FLOAT(OFS_RETURN) = i; break; } i += step; } } /* ======================== VM_matchpattern float matchpattern(string s, string pattern, float matchrule, float startpos) = #468; ======================== */ void VM_matchpattern(prvm_prog_t *prog) { const char *s, *match; char string[VM_STRINGTEMP_LENGTH]; int matchrule, l; VM_SAFEPARMCOUNTRANGE(2, 4, VM_matchpattern); s = PRVM_G_STRING(OFS_PARM0); // get pattern/rule matchrule = (int)PRVM_G_FLOAT(OFS_PARM2); if (matchrule < 0 && matchrule > 5) { VM_Warning(prog, "VM_bufstr_find: invalid match rule %i in %s\n", matchrule, prog->name); return; } if (matchrule) match = PRVM_G_STRING(OFS_PARM1); else { strlcpy(string, PRVM_G_STRING(OFS_PARM1), sizeof(string)); match = detect_match_rule(string, &matchrule); } // offset l = (int)strlen(match); if (prog->argc > 3) s += max(0, min((unsigned int)PRVM_G_FLOAT(OFS_PARM3), strlen(s)-1)); // match PRVM_G_FLOAT(OFS_RETURN) = match_rule(s, VM_STRINGTEMP_LENGTH, match, l, matchrule); } /* ======================== VM_buf_cvarlist ======================== */ void VM_buf_cvarlist(prvm_prog_t *prog) { cvar_t *cvar; const char *partial, *antipartial; size_t len, antilen; size_t alloclen; qboolean ispattern, antiispattern; int n; prvm_stringbuffer_t *stringbuffer; VM_SAFEPARMCOUNTRANGE(2, 3, VM_buf_cvarlist); stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, (int)PRVM_G_FLOAT(OFS_PARM0)); if(!stringbuffer) { VM_Warning(prog, "VM_bufstr_free: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } partial = PRVM_G_STRING(OFS_PARM1); if(!partial) len = 0; else len = strlen(partial); if(prog->argc == 3) antipartial = PRVM_G_STRING(OFS_PARM2); else antipartial = NULL; if(!antipartial) antilen = 0; else antilen = strlen(antipartial); for (n = 0;n < stringbuffer->num_strings;n++) if (stringbuffer->strings[n]) Mem_Free(stringbuffer->strings[n]); if (stringbuffer->strings) Mem_Free(stringbuffer->strings); stringbuffer->strings = NULL; ispattern = partial && (strchr(partial, '*') || strchr(partial, '?')); antiispattern = antipartial && (strchr(antipartial, '*') || strchr(antipartial, '?')); n = 0; for(cvar = cvar_vars; cvar; cvar = cvar->next) { if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) continue; if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) continue; ++n; } stringbuffer->max_strings = stringbuffer->num_strings = n; if (stringbuffer->max_strings) stringbuffer->strings = (char **)Mem_Alloc(prog->progs_mempool, sizeof(stringbuffer->strings[0]) * stringbuffer->max_strings); n = 0; for(cvar = cvar_vars; cvar; cvar = cvar->next) { if(len && (ispattern ? !matchpattern_with_separator(cvar->name, partial, false, "", false) : strncmp(partial, cvar->name, len))) continue; if(antilen && (antiispattern ? matchpattern_with_separator(cvar->name, antipartial, false, "", false) : !strncmp(antipartial, cvar->name, antilen))) continue; alloclen = strlen(cvar->name) + 1; stringbuffer->strings[n] = (char *)Mem_Alloc(prog->progs_mempool, alloclen); memcpy(stringbuffer->strings[n], cvar->name, alloclen); ++n; } } //============= /* ============== VM_changeyaw This was a major timewaster in progs, so it was converted to C ============== */ void VM_changeyaw (prvm_prog_t *prog) { prvm_edict_t *ent; float ideal, current, move, speed; // this is called (VERY HACKISHLY) by VM_SV_MoveToGoal, so it can not use any // parameters because they are the parameters to VM_SV_MoveToGoal, not this //VM_SAFEPARMCOUNT(0, VM_changeyaw); ent = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); if (ent == prog->edicts) { VM_Warning(prog, "changeyaw: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "changeyaw: can not modify free entity\n"); return; } current = PRVM_gameedictvector(ent, angles)[1]; current = ANGLEMOD(current); ideal = PRVM_gameedictfloat(ent, ideal_yaw); speed = PRVM_gameedictfloat(ent, yaw_speed); if (current == ideal) return; move = ideal - current; if (ideal > current) { if (move >= 180) move = move - 360; } else { if (move <= -180) move = move + 360; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } current += move; PRVM_gameedictvector(ent, angles)[1] = ANGLEMOD(current); } /* ============== VM_changepitch ============== */ void VM_changepitch (prvm_prog_t *prog) { prvm_edict_t *ent; float ideal, current, move, speed; VM_SAFEPARMCOUNT(1, VM_changepitch); ent = PRVM_G_EDICT(OFS_PARM0); if (ent == prog->edicts) { VM_Warning(prog, "changepitch: can not modify world entity\n"); return; } if (ent->priv.server->free) { VM_Warning(prog, "changepitch: can not modify free entity\n"); return; } current = PRVM_gameedictvector(ent, angles)[0]; current = ANGLEMOD(current); ideal = PRVM_gameedictfloat(ent, idealpitch); speed = PRVM_gameedictfloat(ent, pitch_speed); if (current == ideal) return; move = ideal - current; if (ideal > current) { if (move >= 180) move = move - 360; } else { if (move <= -180) move = move + 360; } if (move > 0) { if (move > speed) move = speed; } else { if (move < -speed) move = -speed; } current += move; PRVM_gameedictvector(ent, angles)[0] = ANGLEMOD(current); } void VM_uncolorstring (prvm_prog_t *prog) { char szNewString[VM_STRINGTEMP_LENGTH]; const char *szString; // Prepare Strings VM_SAFEPARMCOUNT(1, VM_uncolorstring); szString = PRVM_G_STRING(OFS_PARM0); COM_StringDecolorize(szString, 0, szNewString, sizeof(szNewString), TRUE); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, szNewString); } // #221 float(string str, string sub[, float startpos]) strstrofs (FTE_STRINGS) //strstr, without generating a new string. Use in conjunction with FRIK_FILE's substring for more similar strstr. void VM_strstrofs (prvm_prog_t *prog) { const char *instr, *match; int firstofs; VM_SAFEPARMCOUNTRANGE(2, 3, VM_strstrofs); instr = PRVM_G_STRING(OFS_PARM0); match = PRVM_G_STRING(OFS_PARM1); firstofs = (prog->argc > 2)?(int)PRVM_G_FLOAT(OFS_PARM2):0; firstofs = (int)u8_bytelen(instr, firstofs); if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) { PRVM_G_FLOAT(OFS_RETURN) = -1; return; } match = strstr(instr+firstofs, match); if (!match) PRVM_G_FLOAT(OFS_RETURN) = -1; else PRVM_G_FLOAT(OFS_RETURN) = u8_strnlen(instr, match-instr); } //#222 string(string s, float index) str2chr (FTE_STRINGS) void VM_str2chr (prvm_prog_t *prog) { const char *s; Uchar ch; int index; VM_SAFEPARMCOUNT(2, VM_str2chr); s = PRVM_G_STRING(OFS_PARM0); index = (int)u8_bytelen(s, (int)PRVM_G_FLOAT(OFS_PARM1)); if((unsigned)index < strlen(s)) { if (utf8_enable.integer) ch = u8_getchar_noendptr(s + index); else ch = (unsigned char)s[index]; PRVM_G_FLOAT(OFS_RETURN) = ch; } else PRVM_G_FLOAT(OFS_RETURN) = 0; } //#223 string(float c, ...) chr2str (FTE_STRINGS) void VM_chr2str (prvm_prog_t *prog) { /* char t[9]; int i; VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); for(i = 0;i < prog->argc && i < (int)sizeof(t) - 1;i++) t[i] = (unsigned char)PRVM_G_FLOAT(OFS_PARM0+i*3); t[i] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); */ char t[9 * 4 + 1]; int i; size_t len = 0; VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); for(i = 0; i < prog->argc && len < sizeof(t)-1; ++i) len += u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0+i*3), t + len, sizeof(t)-1); t[len] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, t); } static int chrconv_number(int i, int base, int conv) { i -= base; switch (conv) { default: case 5: case 6: case 0: break; case 1: base = '0'; break; case 2: base = '0'+128; break; case 3: base = '0'-30; break; case 4: base = '0'+128-30; break; } return i + base; } static int chrconv_punct(int i, int base, int conv) { i -= base; switch (conv) { default: case 0: break; case 1: base = 0; break; case 2: base = 128; break; } return i + base; } static int chrchar_alpha(int i, int basec, int baset, int convc, int convt, int charnum) { //convert case and colour seperatly... i -= baset + basec; switch (convt) { default: case 0: break; case 1: baset = 0; break; case 2: baset = 128; break; case 5: case 6: baset = 128*((charnum&1) == (convt-5)); break; } switch (convc) { default: case 0: break; case 1: basec = 'a'; break; case 2: basec = 'A'; break; } return i + basec + baset; } // #224 string(float ccase, float calpha, float cnum, string s, ...) strconv (FTE_STRINGS) //bulk convert a string. change case or colouring. void VM_strconv (prvm_prog_t *prog) { int ccase, redalpha, rednum, len, i; unsigned char resbuf[VM_STRINGTEMP_LENGTH]; unsigned char *result = resbuf; VM_SAFEPARMCOUNTRANGE(3, 8, VM_strconv); ccase = (int) PRVM_G_FLOAT(OFS_PARM0); //0 same, 1 lower, 2 upper redalpha = (int) PRVM_G_FLOAT(OFS_PARM1); //0 same, 1 white, 2 red, 5 alternate, 6 alternate-alternate rednum = (int) PRVM_G_FLOAT(OFS_PARM2); //0 same, 1 white, 2 red, 3 redspecial, 4 whitespecial, 5 alternate, 6 alternate-alternate VM_VarString(prog, 3, (char *) resbuf, sizeof(resbuf)); len = (int)strlen((char *) resbuf); for (i = 0; i < len; i++, result++) //should this be done backwards? { if (*result >= '0' && *result <= '9') //normal numbers... *result = chrconv_number(*result, '0', rednum); else if (*result >= '0'+128 && *result <= '9'+128) *result = chrconv_number(*result, '0'+128, rednum); else if (*result >= '0'+128-30 && *result <= '9'+128-30) *result = chrconv_number(*result, '0'+128-30, rednum); else if (*result >= '0'-30 && *result <= '9'-30) *result = chrconv_number(*result, '0'-30, rednum); else if (*result >= 'a' && *result <= 'z') //normal numbers... *result = chrchar_alpha(*result, 'a', 0, ccase, redalpha, i); else if (*result >= 'A' && *result <= 'Z') //normal numbers... *result = chrchar_alpha(*result, 'A', 0, ccase, redalpha, i); else if (*result >= 'a'+128 && *result <= 'z'+128) //normal numbers... *result = chrchar_alpha(*result, 'a', 128, ccase, redalpha, i); else if (*result >= 'A'+128 && *result <= 'Z'+128) //normal numbers... *result = chrchar_alpha(*result, 'A', 128, ccase, redalpha, i); else if ((*result & 127) < 16 || !redalpha) //special chars.. *result = *result; else if (*result < 128) *result = chrconv_punct(*result, 0, redalpha); else *result = chrconv_punct(*result, 128, redalpha); } *result = '\0'; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, (char *) resbuf); } // #225 string(float chars, string s, ...) strpad (FTE_STRINGS) void VM_strpad (prvm_prog_t *prog) { char src[VM_STRINGTEMP_LENGTH]; char destbuf[VM_STRINGTEMP_LENGTH]; int pad; VM_SAFEPARMCOUNTRANGE(1, 8, VM_strpad); pad = (int) PRVM_G_FLOAT(OFS_PARM0); VM_VarString(prog, 1, src, sizeof(src)); // note: < 0 = left padding, > 0 = right padding, // this is reverse logic of printf! dpsnprintf(destbuf, sizeof(destbuf), "%*s", -pad, src); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, destbuf); } // #226 string(string info, string key, string value, ...) infoadd (FTE_STRINGS) //uses qw style \key\value strings void VM_infoadd (prvm_prog_t *prog) { const char *info, *key; char value[VM_STRINGTEMP_LENGTH]; char temp[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_infoadd); info = PRVM_G_STRING(OFS_PARM0); key = PRVM_G_STRING(OFS_PARM1); VM_VarString(prog, 2, value, sizeof(value)); strlcpy(temp, info, VM_STRINGTEMP_LENGTH); InfoString_SetValue(temp, VM_STRINGTEMP_LENGTH, key, value); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, temp); } // #227 string(string info, string key) infoget (FTE_STRINGS) //uses qw style \key\value strings void VM_infoget (prvm_prog_t *prog) { const char *info; const char *key; char value[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(2, VM_infoget); info = PRVM_G_STRING(OFS_PARM0); key = PRVM_G_STRING(OFS_PARM1); InfoString_GetValue(info, key, value, VM_STRINGTEMP_LENGTH); PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, value); } //#228 float(string s1, string s2, float len) strncmp (FTE_STRINGS) // also float(string s1, string s2) strcmp (FRIK_FILE) void VM_strncmp (prvm_prog_t *prog) { const char *s1, *s2; VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncmp); s1 = PRVM_G_STRING(OFS_PARM0); s2 = PRVM_G_STRING(OFS_PARM1); if (prog->argc > 2) { PRVM_G_FLOAT(OFS_RETURN) = strncmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); } else { PRVM_G_FLOAT(OFS_RETURN) = strcmp(s1, s2); } } // #229 float(string s1, string s2) strcasecmp (FTE_STRINGS) // #230 float(string s1, string s2, float len) strncasecmp (FTE_STRINGS) void VM_strncasecmp (prvm_prog_t *prog) { const char *s1, *s2; VM_SAFEPARMCOUNTRANGE(2, 3, VM_strncasecmp); s1 = PRVM_G_STRING(OFS_PARM0); s2 = PRVM_G_STRING(OFS_PARM1); if (prog->argc > 2) { PRVM_G_FLOAT(OFS_RETURN) = strncasecmp(s1, s2, (size_t)PRVM_G_FLOAT(OFS_PARM2)); } else { PRVM_G_FLOAT(OFS_RETURN) = strcasecmp(s1, s2); } } // #494 float(float caseinsensitive, string s, ...) crc16 void VM_crc16(prvm_prog_t *prog) { float insensitive; char s[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNTRANGE(2, 8, VM_crc16); insensitive = PRVM_G_FLOAT(OFS_PARM0); VM_VarString(prog, 1, s, sizeof(s)); PRVM_G_FLOAT(OFS_RETURN) = (unsigned short) ((insensitive ? CRC_Block_CaseInsensitive : CRC_Block) ((unsigned char *) s, strlen(s))); } // #639 float(string digest, string data, ...) digest_hex void VM_digest_hex(prvm_prog_t *prog) { const char *digest; char out[32]; char outhex[65]; int outlen; char s[VM_STRINGTEMP_LENGTH]; int len; VM_SAFEPARMCOUNTRANGE(2, 8, VM_digest_hex); digest = PRVM_G_STRING(OFS_PARM0); if(!digest) digest = ""; VM_VarString(prog, 1, s, sizeof(s)); len = (int)strlen(s); outlen = 0; if(!strcmp(digest, "MD4")) { outlen = 16; mdfour((unsigned char *) out, (unsigned char *) s, len); } else if(!strcmp(digest, "SHA256") && Crypto_Available()) { outlen = 32; sha256((unsigned char *) out, (unsigned char *) s, len); } // no warning needed on mismatch - we return string_null to QC if(outlen) { int i; static const char *hexmap = "0123456789abcdef"; for(i = 0; i < outlen; ++i) { outhex[2*i] = hexmap[(out[i] >> 4) & 15]; outhex[2*i+1] = hexmap[(out[i] >> 0) & 15]; } outhex[2*i] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, outhex); } else PRVM_G_INT(OFS_RETURN) = 0; } void VM_wasfreed (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(1, VM_wasfreed); PRVM_G_FLOAT(OFS_RETURN) = PRVM_G_EDICT(OFS_PARM0)->priv.required->free; } void VM_SetTraceGlobals(prvm_prog_t *prog, const trace_t *trace) { PRVM_gameglobalfloat(trace_allsolid) = trace->allsolid; PRVM_gameglobalfloat(trace_startsolid) = trace->startsolid; PRVM_gameglobalfloat(trace_fraction) = trace->fraction; PRVM_gameglobalfloat(trace_inwater) = trace->inwater; PRVM_gameglobalfloat(trace_inopen) = trace->inopen; VectorCopy(trace->endpos, PRVM_gameglobalvector(trace_endpos)); VectorCopy(trace->plane.normal, PRVM_gameglobalvector(trace_plane_normal)); PRVM_gameglobalfloat(trace_plane_dist) = trace->plane.dist; PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(trace->ent ? trace->ent : prog->edicts); PRVM_gameglobalfloat(trace_dpstartcontents) = trace->startsupercontents; PRVM_gameglobalfloat(trace_dphitcontents) = trace->hitsupercontents; PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = trace->hitq3surfaceflags; PRVM_gameglobalstring(trace_dphittexturename) = trace->hittexture ? PRVM_SetTempString(prog, trace->hittexture->name) : 0; } void VM_ClearTraceGlobals(prvm_prog_t *prog) { // clean up all trace globals when leaving the VM (anti-triggerbot safeguard) PRVM_gameglobalfloat(trace_allsolid) = 0; PRVM_gameglobalfloat(trace_startsolid) = 0; PRVM_gameglobalfloat(trace_fraction) = 0; PRVM_gameglobalfloat(trace_inwater) = 0; PRVM_gameglobalfloat(trace_inopen) = 0; VectorClear(PRVM_gameglobalvector(trace_endpos)); VectorClear(PRVM_gameglobalvector(trace_plane_normal)); PRVM_gameglobalfloat(trace_plane_dist) = 0; PRVM_gameglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_gameglobalfloat(trace_dpstartcontents) = 0; PRVM_gameglobalfloat(trace_dphitcontents) = 0; PRVM_gameglobalfloat(trace_dphitq3surfaceflags) = 0; PRVM_gameglobalstring(trace_dphittexturename) = 0; } //============= void VM_Cmd_Init(prvm_prog_t *prog) { // only init the stuff for the current prog VM_Files_Init(prog); VM_Search_Init(prog); } static void animatemodel_reset(prvm_prog_t *prog); void VM_Cmd_Reset(prvm_prog_t *prog) { CL_PurgeOwner( MENUOWNER ); VM_Search_Reset(prog); VM_Files_CloseAll(prog); animatemodel_reset(prog); } // #510 string(string input, ...) uri_escape (DP_QC_URI_ESCAPE) // does URI escaping on a string (replace evil stuff by %AB escapes) void VM_uri_escape (prvm_prog_t *prog) { char src[VM_STRINGTEMP_LENGTH]; char dest[VM_STRINGTEMP_LENGTH]; char *p, *q; static const char *hex = "0123456789ABCDEF"; VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_escape); VM_VarString(prog, 0, src, sizeof(src)); for(p = src, q = dest; *p && q < dest + sizeof(dest) - 3; ++p) { if((*p >= 'A' && *p <= 'Z') || (*p >= 'a' && *p <= 'z') || (*p >= '0' && *p <= '9') || (*p == '-') || (*p == '_') || (*p == '.') || (*p == '!') || (*p == '~') || (*p == '\'') || (*p == '(') || (*p == ')')) *q++ = *p; else { *q++ = '%'; *q++ = hex[(*(unsigned char *)p >> 4) & 0xF]; *q++ = hex[ *(unsigned char *)p & 0xF]; } } *q++ = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest); } // #510 string(string input, ...) uri_unescape (DP_QC_URI_ESCAPE) // does URI unescaping on a string (get back the evil stuff) void VM_uri_unescape (prvm_prog_t *prog) { char src[VM_STRINGTEMP_LENGTH]; char dest[VM_STRINGTEMP_LENGTH]; char *p, *q; int hi, lo; VM_SAFEPARMCOUNTRANGE(1, 8, VM_uri_unescape); VM_VarString(prog, 0, src, sizeof(src)); for(p = src, q = dest; *p; ) // no need to check size, because unescape can't expand { if(*p == '%') { if(p[1] >= '0' && p[1] <= '9') hi = p[1] - '0'; else if(p[1] >= 'a' && p[1] <= 'f') hi = p[1] - 'a' + 10; else if(p[1] >= 'A' && p[1] <= 'F') hi = p[1] - 'A' + 10; else goto nohex; if(p[2] >= '0' && p[2] <= '9') lo = p[2] - '0'; else if(p[2] >= 'a' && p[2] <= 'f') lo = p[2] - 'a' + 10; else if(p[2] >= 'A' && p[2] <= 'F') lo = p[2] - 'A' + 10; else goto nohex; if(hi != 0 || lo != 0) // don't unescape NUL bytes *q++ = (char) (hi * 0x10 + lo); p += 3; continue; } nohex: // otherwise: *q++ = *p++; } *q++ = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, dest); } // #502 string(string filename) whichpack (DP_QC_WHICHPACK) // returns the name of the pack containing a file, or "" if it is not in any pack (but local or non-existant) void VM_whichpack (prvm_prog_t *prog) { const char *fn, *pack; VM_SAFEPARMCOUNT(1, VM_whichpack); fn = PRVM_G_STRING(OFS_PARM0); pack = FS_WhichPack(fn); PRVM_G_INT(OFS_RETURN) = pack ? PRVM_SetTempString(prog, pack) : 0; } typedef struct { prvm_prog_t *prog; double starttime; float id; char buffer[MAX_INPUTLINE]; char posttype[128]; unsigned char *postdata; // free when uri_to_prog_t is freed size_t postlen; char *sigdata; // free when uri_to_prog_t is freed size_t siglen; } uri_to_prog_t; static void uri_to_string_callback(int status, size_t length_received, unsigned char *buffer, void *cbdata) { prvm_prog_t *prog; uri_to_prog_t *handle = (uri_to_prog_t *) cbdata; prog = handle->prog; if(!prog->loaded) { // curl reply came too late... so just drop it if(handle->postdata) Z_Free(handle->postdata); if(handle->sigdata) Z_Free(handle->sigdata); Z_Free(handle); return; } if((prog->starttime == handle->starttime) && (PRVM_allfunction(URI_Get_Callback))) { if(length_received >= sizeof(handle->buffer)) length_received = sizeof(handle->buffer) - 1; handle->buffer[length_received] = 0; PRVM_G_FLOAT(OFS_PARM0) = handle->id; PRVM_G_FLOAT(OFS_PARM1) = status; PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(prog, handle->buffer); prog->ExecuteProgram(prog, PRVM_allfunction(URI_Get_Callback), "QC function URI_Get_Callback is missing"); } if(handle->postdata) Z_Free(handle->postdata); if(handle->sigdata) Z_Free(handle->sigdata); Z_Free(handle); } // uri_get() gets content from an URL and calls a callback "uri_get_callback" with it set as string; an unique ID of the transfer is returned // returns 1 on success, and then calls the callback with the ID, 0 or the HTTP status code, and the received data in a string void VM_uri_get (prvm_prog_t *prog) { const char *url; float id; qboolean ret; uri_to_prog_t *handle; const char *posttype = NULL; const char *postseparator = NULL; int poststringbuffer = -1; int postkeyid = -1; const char *query_string = NULL; size_t lq; if(!PRVM_allfunction(URI_Get_Callback)) prog->error_cmd("uri_get called by %s without URI_Get_Callback defined", prog->name); VM_SAFEPARMCOUNTRANGE(2, 6, VM_uri_get); url = PRVM_G_STRING(OFS_PARM0); id = PRVM_G_FLOAT(OFS_PARM1); if(prog->argc >= 3) posttype = PRVM_G_STRING(OFS_PARM2); if(prog->argc >= 4) postseparator = PRVM_G_STRING(OFS_PARM3); if(prog->argc >= 5) poststringbuffer = PRVM_G_FLOAT(OFS_PARM4); if(prog->argc >= 6) postkeyid = PRVM_G_FLOAT(OFS_PARM5); handle = (uri_to_prog_t *) Z_Malloc(sizeof(*handle)); // this can't be the prog's mem pool, as curl may call the callback later! query_string = strchr(url, '?'); if(query_string) ++query_string; lq = query_string ? strlen(query_string) : 0; handle->prog = prog; handle->starttime = prog->starttime; handle->id = id; if(postseparator && posttype && *posttype) { size_t l = strlen(postseparator); if(poststringbuffer >= 0) { size_t ltotal; int i; // "implode" prvm_stringbuffer_t *stringbuffer; stringbuffer = (prvm_stringbuffer_t *)Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, poststringbuffer); if(!stringbuffer) { VM_Warning(prog, "uri_get: invalid buffer %i used in %s\n", (int)PRVM_G_FLOAT(OFS_PARM0), prog->name); return; } ltotal = 0; for(i = 0;i < stringbuffer->num_strings;i++) { if(i > 0) ltotal += l; if(stringbuffer->strings[i]) ltotal += strlen(stringbuffer->strings[i]); } handle->postdata = (unsigned char *)Z_Malloc(ltotal + 1 + lq); handle->postlen = ltotal; ltotal = 0; for(i = 0;i < stringbuffer->num_strings;i++) { if(i > 0) { memcpy(handle->postdata + ltotal, postseparator, l); ltotal += l; } if(stringbuffer->strings[i]) { memcpy(handle->postdata + ltotal, stringbuffer->strings[i], strlen(stringbuffer->strings[i])); ltotal += strlen(stringbuffer->strings[i]); } } if(ltotal != handle->postlen) prog->error_cmd("%s: string buffer content size mismatch, possible overrun", prog->name); } else { handle->postdata = (unsigned char *)Z_Malloc(l + 1 + lq); handle->postlen = l; memcpy(handle->postdata, postseparator, l); } handle->postdata[handle->postlen] = 0; if(query_string) memcpy(handle->postdata + handle->postlen + 1, query_string, lq); if(postkeyid >= 0) { // POST: we sign postdata \0 query string size_t ll; handle->sigdata = (char *)Z_Malloc(8192); strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); l = strlen(handle->sigdata); handle->siglen = Crypto_SignDataDetached(handle->postdata, handle->postlen + 1 + lq, postkeyid, handle->sigdata + l, 8192 - l); if(!handle->siglen) { Z_Free(handle->sigdata); handle->sigdata = NULL; goto out1; } ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); if(!ll) { Z_Free(handle->sigdata); handle->sigdata = NULL; goto out1; } handle->siglen = l + ll; handle->sigdata[handle->siglen] = 0; } out1: strlcpy(handle->posttype, posttype, sizeof(handle->posttype)); ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, handle->posttype, handle->postdata, handle->postlen, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); } else { if(postkeyid >= 0 && query_string) { // GET: we sign JUST the query string size_t l, ll; handle->sigdata = (char *)Z_Malloc(8192); strlcpy(handle->sigdata, "X-D0-Blind-ID-Detached-Signature: ", 8192); l = strlen(handle->sigdata); handle->siglen = Crypto_SignDataDetached(query_string, lq, postkeyid, handle->sigdata + l, 8192 - l); if(!handle->siglen) { Z_Free(handle->sigdata); handle->sigdata = NULL; goto out2; } ll = base64_encode((unsigned char *) (handle->sigdata + l), handle->siglen, 8192 - l - 1); if(!ll) { Z_Free(handle->sigdata); handle->sigdata = NULL; goto out2; } handle->siglen = l + ll; handle->sigdata[handle->siglen] = 0; } out2: handle->postdata = NULL; handle->postlen = 0; ret = Curl_Begin_ToMemory_POST(url, handle->sigdata, 0, NULL, NULL, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle); } if(ret) { PRVM_G_INT(OFS_RETURN) = 1; } else { if(handle->postdata) Z_Free(handle->postdata); if(handle->sigdata) Z_Free(handle->sigdata); Z_Free(handle); PRVM_G_INT(OFS_RETURN) = 0; } } void VM_netaddress_resolve (prvm_prog_t *prog) { const char *ip; char normalized[128]; int port; lhnetaddress_t addr; VM_SAFEPARMCOUNTRANGE(1, 2, VM_netaddress_resolve); ip = PRVM_G_STRING(OFS_PARM0); port = 0; if(prog->argc > 1) port = (int) PRVM_G_FLOAT(OFS_PARM1); if(LHNETADDRESS_FromString(&addr, ip, port) && LHNETADDRESS_ToString(&addr, normalized, sizeof(normalized), prog->argc > 1)) PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, normalized); else PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, ""); } //string(prvm_prog_t *prog) getextresponse = #624; // returns the next extResponse packet that was sent to this client void VM_CL_getextresponse (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_argv); if (cl_net_extresponse_count <= 0) PRVM_G_INT(OFS_RETURN) = OFS_NULL; else { int first; --cl_net_extresponse_count; first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, cl_net_extresponse[first]); } } void VM_SV_getextresponse (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0,VM_argv); if (sv_net_extresponse_count <= 0) PRVM_G_INT(OFS_RETURN) = OFS_NULL; else { int first; --sv_net_extresponse_count; first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, sv_net_extresponse[first]); } } /* ========= Common functions between menu.dat and clsprogs ========= */ //#349 float() isdemo void VM_CL_isdemo (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_isdemo); PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback; } //#355 float() videoplaying void VM_CL_videoplaying (prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_CL_videoplaying); PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying; } /* ========= VM_M_callfunction callfunction(...,string function_name) Extension: pass ========= */ void VM_callfunction(prvm_prog_t *prog) { mfunction_t *func; const char *s; VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction); s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3); VM_CheckEmptyString(prog, s); func = PRVM_ED_FindFunction(prog, s); if(!func) prog->error_cmd("VM_callfunciton: function %s not found !", s); else if (func->first_statement < 0) { // negative statements are built in functions int builtinnumber = -func->first_statement; prog->xfunction->builtinsprofile++; if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) prog->builtins[builtinnumber](prog); else prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name); } else if(func - prog->functions > 0) { prog->argc--; prog->ExecuteProgram(prog, func - prog->functions,""); prog->argc++; } } /* ========= VM_isfunction float isfunction(string function_name) ========= */ void VM_isfunction(prvm_prog_t *prog) { mfunction_t *func; const char *s; VM_SAFEPARMCOUNT(1, VM_isfunction); s = PRVM_G_STRING(OFS_PARM0); VM_CheckEmptyString(prog, s); func = PRVM_ED_FindFunction(prog, s); if(!func) PRVM_G_FLOAT(OFS_RETURN) = false; else PRVM_G_FLOAT(OFS_RETURN) = true; } /* ========= VM_sprintf string sprintf(string format, ...) ========= */ void VM_sprintf(prvm_prog_t *prog) { const char *s, *s0; char outbuf[MAX_INPUTLINE]; char *o = outbuf, *end = outbuf + sizeof(outbuf), *err; const char *p; int argpos = 1; int width, precision, thisarg, flags; char formatbuf[16]; char *f; int isfloat; static prvm_int_t dummyivec[3] = {0, 0, 0}; static prvm_vec_t dummyvec[3] = {0, 0, 0}; char vabuf[1024]; #define PRINTF_ALTERNATE 1 #define PRINTF_ZEROPAD 2 #define PRINTF_LEFT 4 #define PRINTF_SPACEPOSITIVE 8 #define PRINTF_SIGNPOSITIVE 16 formatbuf[0] = '%'; s = PRVM_G_STRING(OFS_PARM0); #define GETARG_FLOAT(a) (((a)>=1 && (a)argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0) #define GETARG_VECTOR(a) (((a)>=1 && (a)argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec) #define GETARG_INT(a) (((a)>=1 && (a)argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0) #define GETARG_INTVECTOR(a) (((a)>=1 && (a)argc) ? ((prvm_int_t*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec) #define GETARG_STRING(a) (((a)>=1 && (a)argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "") for(;;) { s0 = s; switch(*s) { case 0: goto finished; case '%': ++s; if(*s == '%') goto verbatim; // complete directive format: // %3$*1$.*2$ld width = -1; precision = -1; thisarg = -1; flags = 0; isfloat = -1; // is number following? if(*s >= '0' && *s <= '9') { width = strtol(s, &err, 10); if(!err) { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } if(*err == '$') { thisarg = width; width = -1; s = err + 1; } else { if(*s == '0') { flags |= PRINTF_ZEROPAD; if(width == 0) width = -1; // it was just a flag } s = err; } } if(width < 0) { for(;;) { switch(*s) { case '#': flags |= PRINTF_ALTERNATE; break; case '0': flags |= PRINTF_ZEROPAD; break; case '-': flags |= PRINTF_LEFT; break; case ' ': flags |= PRINTF_SPACEPOSITIVE; break; case '+': flags |= PRINTF_SIGNPOSITIVE; break; default: goto noflags; } ++s; } noflags: if(*s == '*') { ++s; if(*s >= '0' && *s <= '9') { width = strtol(s, &err, 10); if(!err || *err != '$') { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } s = err + 1; } else width = argpos++; width = GETARG_FLOAT(width); if(width < 0) { flags |= PRINTF_LEFT; width = -width; } } else if(*s >= '0' && *s <= '9') { width = strtol(s, &err, 10); if(!err) { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } s = err; if(width < 0) { flags |= PRINTF_LEFT; width = -width; } } // otherwise width stays -1 } if(*s == '.') { ++s; if(*s == '*') { ++s; if(*s >= '0' && *s <= '9') { precision = strtol(s, &err, 10); if(!err || *err != '$') { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } s = err + 1; } else precision = argpos++; precision = GETARG_FLOAT(precision); } else if(*s >= '0' && *s <= '9') { precision = strtol(s, &err, 10); if(!err) { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } s = err; } else { VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } } for(;;) { switch(*s) { case 'h': isfloat = 1; break; case 'l': isfloat = 0; break; case 'L': isfloat = 0; break; case 'j': break; case 'z': break; case 't': break; default: goto nolength; } ++s; } nolength: // now s points to the final directive char and is no longer changed if(isfloat < 0) { if(*s == 'i') isfloat = 0; else isfloat = 1; } if(thisarg < 0) thisarg = argpos++; if(o < end - 1) { f = &formatbuf[1]; if(*s != 's' && *s != 'c') if(flags & PRINTF_ALTERNATE) *f++ = '#'; if(flags & PRINTF_ZEROPAD) *f++ = '0'; if(flags & PRINTF_LEFT) *f++ = '-'; if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' '; if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+'; *f++ = '*'; if(precision >= 0) { *f++ = '.'; *f++ = '*'; } if(*s == 'd' || *s == 'i' || *s == 'o' || *s == 'u' || *s == 'x' || *s == 'X') { // make it use a good integer type for(p = INT_LOSSLESS_FORMAT_SIZE; *p; ) *f++ = *p++; } *f++ = *s; *f++ = 0; if(width < 0) // not set width = 0; switch(*s) { case 'd': case 'i': if(precision < 0) // not set o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_INT(thisarg)))); else o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_S(GETARG_INT(thisarg)))); break; case 'o': case 'u': case 'x': case 'X': if(precision < 0) // not set o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_INT(thisarg)))); else o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_FLOAT(thisarg)) : INT_LOSSLESS_FORMAT_CONVERT_U(GETARG_INT(thisarg)))); break; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': if(precision < 0) // not set o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); else o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg))); break; case 'v': case 'V': f[-2] += 'g' - 'v'; if(precision < 0) // not set o += dpsnprintf(o, end - o, va(vabuf, sizeof(vabuf), "%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), width, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) ); else o += dpsnprintf(o, end - o, va(vabuf, sizeof(vabuf), "%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]), width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2]) ); break; case 'c': if(flags & PRINTF_ALTERNATE) { if(precision < 0) // not set o += dpsnprintf(o, end - o, formatbuf, width, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); else o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg))); } else { unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)); char charbuf16[16]; const char *buf = u8_encodech(c, NULL, charbuf16); if(!buf) buf = ""; if(precision < 0) // not set precision = end - o - 1; o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision); } break; case 's': if(flags & PRINTF_ALTERNATE) { if(precision < 0) // not set o += dpsnprintf(o, end - o, formatbuf, width, GETARG_STRING(thisarg)); else o += dpsnprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg)); } else { if(precision < 0) // not set precision = end - o - 1; if(flags & PRINTF_SIGNPOSITIVE) o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); else o += u8_strpad_colorcodes(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision); } break; default: VM_Warning(prog, "VM_sprintf: invalid directive in %s: %s\n", prog->name, s0); goto finished; } } ++s; break; default: verbatim: if(o < end - 1) *o++ = *s; ++s; break; } } finished: *o = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, outbuf); } // surface querying static dp_model_t *getmodel(prvm_prog_t *prog, prvm_edict_t *ed) { if (prog == SVVM_prog) return SV_GetModelFromEdict(ed); else if (prog == CLVM_prog) return CL_GetModelFromEdict(ed); else return NULL; } struct animatemodel_cache { dp_model_t *model; frameblend_t frameblend[MAX_FRAMEBLENDS]; skeleton_t *skeleton_p; skeleton_t skeleton; float *data_vertex3f; float *data_svector3f; float *data_tvector3f; float *data_normal3f; int max_vertices; float *buf_vertex3f; float *buf_svector3f; float *buf_tvector3f; float *buf_normal3f; }; static void animatemodel_reset(prvm_prog_t *prog) { if (!prog->animatemodel_cache) return; if(prog->animatemodel_cache->buf_vertex3f) Mem_Free(prog->animatemodel_cache->buf_vertex3f); if(prog->animatemodel_cache->buf_svector3f) Mem_Free(prog->animatemodel_cache->buf_svector3f); if(prog->animatemodel_cache->buf_tvector3f) Mem_Free(prog->animatemodel_cache->buf_tvector3f); if(prog->animatemodel_cache->buf_normal3f) Mem_Free(prog->animatemodel_cache->buf_normal3f); Mem_Free(prog->animatemodel_cache); } static void animatemodel(prvm_prog_t *prog, dp_model_t *model, prvm_edict_t *ed) { skeleton_t *skeleton; int skeletonindex = -1; qboolean need = false; struct animatemodel_cache *animatemodel_cache; if (!prog->animatemodel_cache) { prog->animatemodel_cache = (struct animatemodel_cache *)Mem_Alloc(prog->progs_mempool, sizeof(struct animatemodel_cache)); memset(prog->animatemodel_cache, 0, sizeof(struct animatemodel_cache)); } animatemodel_cache = prog->animatemodel_cache; if(!(model->surfmesh.isanimated && model->AnimateVertices)) { animatemodel_cache->data_vertex3f = model->surfmesh.data_vertex3f; animatemodel_cache->data_svector3f = model->surfmesh.data_svector3f; animatemodel_cache->data_tvector3f = model->surfmesh.data_tvector3f; animatemodel_cache->data_normal3f = model->surfmesh.data_normal3f; return; } need |= (animatemodel_cache->model != model); VM_GenerateFrameGroupBlend(prog, ed->priv.server->framegroupblend, ed); VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model, PRVM_serverglobalfloat(time)); need |= (memcmp(&animatemodel_cache->frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend))) != 0; skeletonindex = (int)PRVM_gameedictfloat(ed, skeletonindex) - 1; if (!(skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones)) skeleton = NULL; need |= (animatemodel_cache->skeleton_p != skeleton); if(skeleton) need |= (memcmp(&animatemodel_cache->skeleton, skeleton, sizeof(ed->priv.server->skeleton))) != 0; if(!need) return; if(model->surfmesh.num_vertices > animatemodel_cache->max_vertices) { animatemodel_cache->max_vertices = model->surfmesh.num_vertices * 2; if(animatemodel_cache->buf_vertex3f) Mem_Free(animatemodel_cache->buf_vertex3f); if(animatemodel_cache->buf_svector3f) Mem_Free(animatemodel_cache->buf_svector3f); if(animatemodel_cache->buf_tvector3f) Mem_Free(animatemodel_cache->buf_tvector3f); if(animatemodel_cache->buf_normal3f) Mem_Free(animatemodel_cache->buf_normal3f); animatemodel_cache->buf_vertex3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache->max_vertices); animatemodel_cache->buf_svector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache->max_vertices); animatemodel_cache->buf_tvector3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache->max_vertices); animatemodel_cache->buf_normal3f = (float *)Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache->max_vertices); } animatemodel_cache->data_vertex3f = animatemodel_cache->buf_vertex3f; animatemodel_cache->data_svector3f = animatemodel_cache->buf_svector3f; animatemodel_cache->data_tvector3f = animatemodel_cache->buf_tvector3f; animatemodel_cache->data_normal3f = animatemodel_cache->buf_normal3f; VM_UpdateEdictSkeleton(prog, ed, model, ed->priv.server->frameblend); model->AnimateVertices(model, ed->priv.server->frameblend, &ed->priv.server->skeleton, animatemodel_cache->data_vertex3f, animatemodel_cache->data_normal3f, animatemodel_cache->data_svector3f, animatemodel_cache->data_tvector3f); animatemodel_cache->model = model; memcpy(&animatemodel_cache->frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)); animatemodel_cache->skeleton_p = skeleton; if(skeleton) memcpy(&animatemodel_cache->skeleton, skeleton, sizeof(ed->priv.server->skeleton)); } static void getmatrix(prvm_prog_t *prog, prvm_edict_t *ed, matrix4x4_t *out) { if (prog == SVVM_prog) SV_GetEntityMatrix(prog, ed, out, false); else if (prog == CLVM_prog) CL_GetEntityMatrix(prog, ed, out, false); else *out = identitymatrix; } static void applytransform_forward(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) { matrix4x4_t m; getmatrix(prog, ed, &m); Matrix4x4_Transform(&m, in, out); } static void applytransform_forward_direction(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) { matrix4x4_t m; getmatrix(prog, ed, &m); Matrix4x4_Transform3x3(&m, in, out); } static void applytransform_inverted(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) { matrix4x4_t m, n; getmatrix(prog, ed, &m); Matrix4x4_Invert_Full(&n, &m); Matrix4x4_Transform3x3(&n, in, out); } static void applytransform_forward_normal(prvm_prog_t *prog, const vec3_t in, prvm_edict_t *ed, vec3_t out) { matrix4x4_t m; float p[4]; getmatrix(prog, ed, &m); Matrix4x4_TransformPositivePlane(&m, in[0], in[1], in[2], 0, p); VectorCopy(p, out); } static void clippointtosurface(prvm_prog_t *prog, prvm_edict_t *ed, dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out) { int i, j, k; float *v[3], facenormal[3], edgenormal[3], sidenormal[3], temp[3], offsetdist, dist, bestdist; const int *e; animatemodel(prog, model, ed); bestdist = 1000000000; VectorCopy(p, out); for (i = 0, e = (model->surfmesh.data_element3i + 3 * surface->num_firsttriangle);i < surface->num_triangles;i++, e += 3) { // clip original point to each triangle of the surface and find the // triangle that is closest v[0] = prog->animatemodel_cache->data_vertex3f + e[0] * 3; v[1] = prog->animatemodel_cache->data_vertex3f + e[1] * 3; v[2] = prog->animatemodel_cache->data_vertex3f + e[2] * 3; TriangleNormal(v[0], v[1], v[2], facenormal); VectorNormalize(facenormal); offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal); VectorMA(p, offsetdist, facenormal, temp); for (j = 0, k = 2;j < 3;k = j, j++) { VectorSubtract(v[k], v[j], edgenormal); CrossProduct(edgenormal, facenormal, sidenormal); VectorNormalize(sidenormal); offsetdist = DotProduct(v[k], sidenormal) - DotProduct(temp, sidenormal); if (offsetdist < 0) VectorMA(temp, offsetdist, sidenormal, temp); } dist = VectorDistance2(temp, p); if (bestdist > dist) { bestdist = dist; VectorCopy(temp, out); } } } static msurface_t *getsurface(dp_model_t *model, int surfacenum) { if (surfacenum < 0 || surfacenum >= model->nummodelsurfaces) return NULL; return model->data_surfaces + surfacenum + model->firstmodelsurface; } //PF_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints = #434; void VM_getsurfacenumpoints(prvm_prog_t *prog) { dp_model_t *model; msurface_t *surface; VM_SAFEPARMCOUNT(2, VM_getsurfacenumpoints); // return 0 if no such surface if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } // note: this (incorrectly) assumes it is a simple polygon PRVM_G_FLOAT(OFS_RETURN) = surface->num_vertices; } //PF_getsurfacepoint, // #435 vector(entity e, float s, float n) getsurfacepoint = #435; void VM_getsurfacepoint(prvm_prog_t *prog) { prvm_edict_t *ed; dp_model_t *model; msurface_t *surface; int pointnum; vec3_t result; VM_SAFEPARMCOUNT(3, VM_getsurfacepoint); VectorClear(PRVM_G_VECTOR(OFS_RETURN)); ed = PRVM_G_EDICT(OFS_PARM0); if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; // note: this (incorrectly) assumes it is a simple polygon pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); if (pointnum < 0 || pointnum >= surface->num_vertices) return; animatemodel(prog, model, ed); applytransform_forward(prog, &(prog->animatemodel_cache->data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); } //PF_getsurfacepointattribute, // #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; // float SPA_POSITION = 0; // float SPA_S_AXIS = 1; // float SPA_T_AXIS = 2; // float SPA_R_AXIS = 3; // same as SPA_NORMAL // float SPA_TEXCOORDS0 = 4; // float SPA_LIGHTMAP0_TEXCOORDS = 5; // float SPA_LIGHTMAP0_COLOR = 6; void VM_getsurfacepointattribute(prvm_prog_t *prog) { prvm_edict_t *ed; dp_model_t *model; msurface_t *surface; int pointnum; int attributetype; vec3_t result; VM_SAFEPARMCOUNT(4, VM_getsurfacepoint); VectorClear(PRVM_G_VECTOR(OFS_RETURN)); ed = PRVM_G_EDICT(OFS_PARM0); if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; pointnum = (int)PRVM_G_FLOAT(OFS_PARM2); if (pointnum < 0 || pointnum >= surface->num_vertices) return; attributetype = (int) PRVM_G_FLOAT(OFS_PARM3); animatemodel(prog, model, ed); switch( attributetype ) { // float SPA_POSITION = 0; case 0: applytransform_forward(prog, &(prog->animatemodel_cache->data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; // float SPA_S_AXIS = 1; case 1: applytransform_forward_direction(prog, &(prog->animatemodel_cache->data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; // float SPA_T_AXIS = 2; case 2: applytransform_forward_direction(prog, &(prog->animatemodel_cache->data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; // float SPA_R_AXIS = 3; // same as SPA_NORMAL case 3: applytransform_forward_direction(prog, &(prog->animatemodel_cache->data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; // float SPA_TEXCOORDS0 = 4; case 4: { float *texcoord = &(model->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[pointnum * 2]; result[0] = texcoord[0]; result[1] = texcoord[1]; result[2] = 0.0f; VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; } // float SPA_LIGHTMAP0_TEXCOORDS = 5; case 5: { float *texcoord = &(model->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[pointnum * 2]; result[0] = texcoord[0]; result[1] = texcoord[1]; result[2] = 0.0f; VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); break; } // float SPA_LIGHTMAP0_COLOR = 6; case 6: // ignore alpha for now.. VectorCopy( &(model->surfmesh.data_lightmapcolor4f + 4 * surface->num_firstvertex)[pointnum * 4], PRVM_G_VECTOR(OFS_RETURN)); break; default: VectorSet( PRVM_G_VECTOR(OFS_RETURN), 0.0f, 0.0f, 0.0f ); break; } } //PF_getsurfacenormal, // #436 vector(entity e, float s) getsurfacenormal = #436; void VM_getsurfacenormal(prvm_prog_t *prog) { dp_model_t *model; msurface_t *surface; vec3_t normal; vec3_t result; VM_SAFEPARMCOUNT(2, VM_getsurfacenormal); VectorClear(PRVM_G_VECTOR(OFS_RETURN)); if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; // note: this only returns the first triangle, so it doesn't work very // well for curved surfaces or arbitrary meshes animatemodel(prog, model, PRVM_G_EDICT(OFS_PARM0)); TriangleNormal((prog->animatemodel_cache->data_vertex3f + 3 * surface->num_firstvertex), (prog->animatemodel_cache->data_vertex3f + 3 * surface->num_firstvertex) + 3, (prog->animatemodel_cache->data_vertex3f + 3 * surface->num_firstvertex) + 6, normal); applytransform_forward_normal(prog, normal, PRVM_G_EDICT(OFS_PARM0), result); VectorNormalize(result); VectorCopy(result, PRVM_G_VECTOR(OFS_RETURN)); } //PF_getsurfacetexture, // #437 string(entity e, float s) getsurfacetexture = #437; void VM_getsurfacetexture(prvm_prog_t *prog) { dp_model_t *model; msurface_t *surface; VM_SAFEPARMCOUNT(2, VM_getsurfacetexture); PRVM_G_INT(OFS_RETURN) = OFS_NULL; if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(prog, surface->texture->name); } //PF_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint = #438; void VM_getsurfacenearpoint(prvm_prog_t *prog) { int surfacenum, best; vec3_t clipped, p; vec_t dist, bestdist; prvm_edict_t *ed; dp_model_t *model; msurface_t *surface; vec3_t point; VM_SAFEPARMCOUNT(2, VM_getsurfacenearpoint); PRVM_G_FLOAT(OFS_RETURN) = -1; ed = PRVM_G_EDICT(OFS_PARM0); VectorCopy(PRVM_G_VECTOR(OFS_PARM1), point); if (!ed || ed->priv.server->free) return; model = getmodel(prog, ed); if (!model || !model->num_surfaces) return; animatemodel(prog, model, ed); applytransform_inverted(prog, point, ed, p); best = -1; bestdist = 1000000000; for (surfacenum = 0;surfacenum < model->nummodelsurfaces;surfacenum++) { surface = model->data_surfaces + surfacenum + model->firstmodelsurface; // first see if the nearest point on the surface's box is closer than the previous match clipped[0] = bound(surface->mins[0], p[0], surface->maxs[0]) - p[0]; clipped[1] = bound(surface->mins[1], p[1], surface->maxs[1]) - p[1]; clipped[2] = bound(surface->mins[2], p[2], surface->maxs[2]) - p[2]; dist = VectorLength2(clipped); if (dist < bestdist) { // it is, check the nearest point on the actual geometry clippointtosurface(prog, ed, model, surface, p, clipped); VectorSubtract(clipped, p, clipped); dist += VectorLength2(clipped); if (dist < bestdist) { // that's closer too, store it as the best match best = surfacenum; bestdist = dist; } } } PRVM_G_FLOAT(OFS_RETURN) = best; } //PF_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; void VM_getsurfaceclippedpoint(prvm_prog_t *prog) { prvm_edict_t *ed; dp_model_t *model; msurface_t *surface; vec3_t p, out, inp; VM_SAFEPARMCOUNT(3, VM_te_getsurfaceclippedpoint); VectorClear(PRVM_G_VECTOR(OFS_RETURN)); ed = PRVM_G_EDICT(OFS_PARM0); if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; animatemodel(prog, model, ed); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), inp); applytransform_inverted(prog, inp, ed, p); clippointtosurface(prog, ed, model, surface, p, out); VectorAdd(out, PRVM_serveredictvector(ed, origin), PRVM_G_VECTOR(OFS_RETURN)); } //PF_getsurfacenumtriangles, // #??? float(entity e, float s) getsurfacenumtriangles = #???; void VM_getsurfacenumtriangles(prvm_prog_t *prog) { dp_model_t *model; msurface_t *surface; VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumtriangles); // return 0 if no such surface if (!(model = getmodel(prog, PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) { PRVM_G_FLOAT(OFS_RETURN) = 0; return; } PRVM_G_FLOAT(OFS_RETURN) = surface->num_triangles; } //PF_getsurfacetriangle, // #??? vector(entity e, float s, float n) getsurfacetriangle = #???; void VM_getsurfacetriangle(prvm_prog_t *prog) { const vec3_t d = {-1, -1, -1}; prvm_edict_t *ed; dp_model_t *model; msurface_t *surface; int trinum; VM_SAFEPARMCOUNT(3, VM_SV_getsurfacetriangle); VectorClear(PRVM_G_VECTOR(OFS_RETURN)); ed = PRVM_G_EDICT(OFS_PARM0); if (!(model = getmodel(prog, ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1)))) return; trinum = (int)PRVM_G_FLOAT(OFS_PARM2); if (trinum < 0 || trinum >= surface->num_triangles) return; // FIXME: implement rotation/scaling VectorMA(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], surface->num_firstvertex, d, PRVM_G_VECTOR(OFS_RETURN)); } // // physics builtins // #define VM_physics_ApplyCmd(ed,f) if (!ed->priv.server->ode_body) VM_physics_newstackfunction(prog, ed, f); else World_Physics_ApplyCmd(ed, f) static edict_odefunc_t *VM_physics_newstackfunction(prvm_prog_t *prog, prvm_edict_t *ed, edict_odefunc_t *f) { edict_odefunc_t *newfunc, *func; newfunc = (edict_odefunc_t *)Mem_Alloc(prog->progs_mempool, sizeof(edict_odefunc_t)); memcpy(newfunc, f, sizeof(edict_odefunc_t)); newfunc->next = NULL; if (!ed->priv.server->ode_func) ed->priv.server->ode_func = newfunc; else { for (func = ed->priv.server->ode_func; func->next; func = func->next); func->next = newfunc; } return newfunc; } // void(entity e, float physics_enabled) physics_enable = #; void VM_physics_enable(prvm_prog_t *prog) { prvm_edict_t *ed; edict_odefunc_t f; VM_SAFEPARMCOUNT(2, VM_physics_enable); ed = PRVM_G_EDICT(OFS_PARM0); if (!ed) { if (developer.integer > 0) VM_Warning(prog, "VM_physics_enable: null entity!\n"); return; } // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) { VM_Warning(prog, "VM_physics_enable: entity is not MOVETYPE_PHYSICS!\n"); return; } f.type = PRVM_G_FLOAT(OFS_PARM1) == 0 ? ODEFUNC_DISABLE : ODEFUNC_ENABLE; VM_physics_ApplyCmd(ed, &f); } // void(entity e, vector force, vector relative_ofs) physics_addforce = #; void VM_physics_addforce(prvm_prog_t *prog) { prvm_edict_t *ed; edict_odefunc_t f; VM_SAFEPARMCOUNT(3, VM_physics_addforce); ed = PRVM_G_EDICT(OFS_PARM0); if (!ed) { if (developer.integer > 0) VM_Warning(prog, "VM_physics_addforce: null entity!\n"); return; } // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) { VM_Warning(prog, "VM_physics_addforce: entity is not MOVETYPE_PHYSICS!\n"); return; } f.type = ODEFUNC_FORCE; VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); VectorCopy(PRVM_G_VECTOR(OFS_PARM2), f.v2); VM_physics_ApplyCmd(ed, &f); } // void(entity e, vector torque) physics_addtorque = #; void VM_physics_addtorque(prvm_prog_t *prog) { prvm_edict_t *ed; edict_odefunc_t f; VM_SAFEPARMCOUNT(2, VM_physics_addtorque); ed = PRVM_G_EDICT(OFS_PARM0); if (!ed) { if (developer.integer > 0) VM_Warning(prog, "VM_physics_addtorque: null entity!\n"); return; } // entity should have MOVETYPE_PHYSICS already set, this can damage memory (making leaked allocation) so warn about this even if non-developer if (PRVM_serveredictfloat(ed, movetype) != MOVETYPE_PHYSICS) { VM_Warning(prog, "VM_physics_addtorque: entity is not MOVETYPE_PHYSICS!\n"); return; } f.type = ODEFUNC_TORQUE; VectorCopy(PRVM_G_VECTOR(OFS_PARM1), f.v1); VM_physics_ApplyCmd(ed, &f); } extern cvar_t prvm_coverage; void VM_coverage(prvm_prog_t *prog) { VM_SAFEPARMCOUNT(0, VM_coverage); if (prog->explicit_profile[prog->xstatement]++ == 0 && (prvm_coverage.integer & 2)) PRVM_ExplicitCoverageEvent(prog, prog->xfunction, prog->xstatement); } darkplaces/meshqueue.h0000664000175000017500000000103213067716220014331 0ustar kalevkalev #ifndef MESHQUEUE_H #define MESHQUEUE_H // VorteX: seems this value is hardcoded in other several defines as it's changing makes mess #define MESHQUEUE_TRANSPARENT_BATCHSIZE 256 void R_MeshQueue_BeginScene(void); void R_MeshQueue_AddTransparent(dptransparentsortcategory_t category, const vec3_t center, void (*callback)(const entity_render_t *ent, const rtlight_t *rtlight, int numsurfaces, int *surfacelist), const entity_render_t *ent, int surfacenumber, const rtlight_t *rtlight); void R_MeshQueue_RenderTransparent(void); #endif darkplaces/sbar.h0000664000175000017500000000204213067716222013263 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef SBAR_H #define SBAR_H #define SBAR_HEIGHT 24 extern int sb_lines; ///< scan lines to draw extern cvar_t sbar_alpha_bg; extern cvar_t sbar_alpha_fg; void Sbar_Init (void); /// called every frame by screen void Sbar_Draw (void); int Sbar_GetSortedPlayerIndex (int index); void Sbar_SortFrags (void); #endif darkplaces/pr_comp.h0000664000175000017500000001050113067716222013772 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // this file is shared by quake and qcc #ifndef PR_COMP_H #define PR_COMP_H typedef unsigned int func_t; typedef int string_t; typedef enum etype_e {ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_field, ev_function, ev_pointer} etype_t; #define OFS_NULL 0 #define OFS_RETURN 1 #define OFS_PARM0 4 // leave 3 ofs for each parm to hold vectors #define OFS_PARM1 7 #define OFS_PARM2 10 #define OFS_PARM3 13 #define OFS_PARM4 16 #define OFS_PARM5 19 #define OFS_PARM6 22 #define OFS_PARM7 25 #define RESERVED_OFS 28 typedef enum opcode_e { OP_DONE, OP_MUL_F, OP_MUL_V, OP_MUL_FV, OP_MUL_VF, OP_DIV_F, OP_ADD_F, OP_ADD_V, OP_SUB_F, OP_SUB_V, OP_EQ_F, OP_EQ_V, OP_EQ_S, OP_EQ_E, OP_EQ_FNC, OP_NE_F, OP_NE_V, OP_NE_S, OP_NE_E, OP_NE_FNC, OP_LE, OP_GE, OP_LT, OP_GT, OP_LOAD_F, OP_LOAD_V, OP_LOAD_S, OP_LOAD_ENT, OP_LOAD_FLD, OP_LOAD_FNC, OP_ADDRESS, OP_STORE_F, OP_STORE_V, OP_STORE_S, OP_STORE_ENT, OP_STORE_FLD, OP_STORE_FNC, OP_STOREP_F, OP_STOREP_V, OP_STOREP_S, OP_STOREP_ENT, OP_STOREP_FLD, OP_STOREP_FNC, OP_RETURN, OP_NOT_F, OP_NOT_V, OP_NOT_S, OP_NOT_ENT, OP_NOT_FNC, OP_IF, OP_IFNOT, OP_CALL0, OP_CALL1, OP_CALL2, OP_CALL3, OP_CALL4, OP_CALL5, OP_CALL6, OP_CALL7, OP_CALL8, OP_STATE, OP_GOTO, OP_AND, OP_OR, OP_BITAND, OP_BITOR } opcode_t; typedef struct statement_s { unsigned short op; signed short a,b,c; } dstatement_t; typedef struct ddef_s { unsigned short type; // if DEF_SAVEGLOBGAL bit is set // the variable needs to be saved in savegames unsigned short ofs; int s_name; } ddef_t; #define DEF_SAVEGLOBAL (1<<15) #define MAX_PARMS 8 typedef struct dfunction_s { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals int profile; // runtime int s_name; int s_file; // source file defined in int numparms; unsigned char parm_size[MAX_PARMS]; } dfunction_t; typedef struct mfunction_s { int first_statement; // negative numbers are builtins int parm_start; int locals; // total ints of parms + locals // these are doubles so that they can count up to 54bits or so rather than 32bit double tprofile; // realtime in this function double tbprofile; // realtime in builtins called by this function (NOTE: builtins also have a tprofile!) double profile; // runtime double builtinsprofile; // cost of builtin functions called by this function double callcount; // times the functions has been called since the last profile call double totaltime; // total execution time of this function DIRECTLY FROM THE ENGINE double tprofile_total; // runtime (NOTE: tbprofile_total makes no real sense, so not accumulating that) double profile_total; // runtime double builtinsprofile_total; // cost of builtin functions called by this function int recursion; int s_name; int s_file; // source file defined in int numparms; unsigned char parm_size[MAX_PARMS]; } mfunction_t; typedef struct mstatement_s { opcode_t op; int operand[3]; // always a global or -1 for unused int jumpabsolute; // only used by IF, IFNOT, GOTO } mstatement_t; #define PROG_VERSION 6 typedef struct dprograms_s { int version; int crc; // check of header file int ofs_statements; int numstatements; // statement 0 is an error int ofs_globaldefs; int numglobaldefs; int ofs_fielddefs; int numfielddefs; int ofs_functions; int numfunctions; // function 0 is an empty int ofs_strings; int numstrings; // first string is a null string int ofs_globals; int numglobals; int entityfields; } dprograms_t; #endif darkplaces/bih.h0000664000175000017500000000471713067716216013114 0ustar kalevkalev // This code written in 2010 by Forest Hale (darkplacesengine gmail com), and placed into public domain. // Based on information in http://zach.in.tu-clausthal.de/papers/vrst02.html (in particular vrst02_boxtree.pdf) #ifndef BIH_H #define BIH_H #define BIH_MAXUNORDEREDCHILDREN 8 typedef enum biherror_e { BIHERROR_OK, // no error, be happy BIHERROR_OUT_OF_NODES // could not produce complete hierarchy, maxnodes too low (should be roughly half of numleafs) } biherror_t; typedef enum bih_nodetype_e { BIH_SPLITX = 0, BIH_SPLITY = 1, BIH_SPLITZ = 2, BIH_UNORDERED = 3, } bih_nodetype_t; typedef enum bih_leaftype_e { BIH_BRUSH = 4, BIH_COLLISIONTRIANGLE = 5, BIH_RENDERTRIANGLE = 6 } bih_leaftype_t; typedef struct bih_node_s { bih_nodetype_t type; // = BIH_SPLITX and similar values // TODO: store just one float for distance, and have BIH_SPLITMINX and BIH_SPLITMAXX distinctions, to reduce memory footprint and traversal time, as described in the paper (vrst02_boxtree.pdf) // TODO: move bounds data to parent node and remove it from leafs? float mins[3]; float maxs[3]; // node indexes of children (always > this node's index) int front; int back; // interval of children float frontmin; // children[0] float backmax; // children[1] // BIH_UNORDERED uses this for a list of leafindex (all >= 0), -1 = end of list int children[BIH_MAXUNORDEREDCHILDREN]; } bih_node_t; typedef struct bih_leaf_s { bih_leaftype_t type; // = BIH_BRUSH And similar values float mins[3]; float maxs[3]; // data past this point is generic and entirely up to the caller... int textureindex; int surfaceindex; int itemindex; // triangle or brush index } bih_leaf_t; typedef struct bih_s { // permanent fields // leafs are constructed by caller before calling BIH_Build int numleafs; bih_leaf_t *leafs; // nodes are constructed by BIH_Build int numnodes; bih_node_t *nodes; int rootnode; // 0 if numnodes > 0, -1 otherwise // bounds calculated by BIH_Build float mins[3]; float maxs[3]; // fields used only during BIH_Build: int maxnodes; int error; // set to a value if an error occurs in building (such as numnodes == maxnodes) int *leafsort; int *leafsortscratch; } bih_t; int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch); int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs); #endif darkplaces/sys_sdl.c0000664000175000017500000001130113067716222014005 0ustar kalevkalev #ifdef WIN32 #include #include "conio.h" #else #include #include #include #endif #ifdef __ANDROID__ #include #ifndef FNDELAY #define FNDELAY O_NDELAY #endif #endif #include #include #ifdef WIN32 #ifdef _MSC_VER #if SDL_MAJOR_VERSION == 1 #pragma comment(lib, "sdl.lib") #pragma comment(lib, "sdlmain.lib") #else #pragma comment(lib, "sdl2.lib") #pragma comment(lib, "sdl2main.lib") #endif #endif #endif #include "quakedef.h" // ======================================================================= // General routines // ======================================================================= void Sys_Shutdown (void) { #ifdef __ANDROID__ Sys_AllowProfiling(false); #endif #ifndef WIN32 fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); #endif fflush(stdout); SDL_Quit(); } void Sys_Error (const char *error, ...) { va_list argptr; char string[MAX_INPUTLINE]; // change stdin to non blocking #ifndef WIN32 fcntl (0, F_SETFL, fcntl (0, F_GETFL, 0) & ~FNDELAY); #endif va_start (argptr,error); dpvsnprintf (string, sizeof (string), error, argptr); va_end (argptr); Con_Printf ("Quake Error: %s\n", string); #ifdef WIN32 MessageBox(NULL, string, "Quake Error", MB_OK | MB_SETFOREGROUND | MB_ICONSTOP); #endif Host_Shutdown (); exit (1); } static int outfd = 1; void Sys_PrintToTerminal(const char *text) { #ifdef __ANDROID__ if (developer.integer > 0) { __android_log_write(ANDROID_LOG_DEBUG, com_argv[0], text); } #else if(outfd < 0) return; #ifdef FNDELAY // BUG: for some reason, NDELAY also affects stdout (1) when used on stdin (0). // this is because both go to /dev/tty by default! { int origflags = fcntl (outfd, F_GETFL, 0); fcntl (outfd, F_SETFL, origflags & ~FNDELAY); #endif #ifdef WIN32 #define write _write #endif while(*text) { fs_offset_t written = (fs_offset_t)write(outfd, text, (int)strlen(text)); if(written <= 0) break; // sorry, I cannot do anything about this error - without an output text += written; } #ifdef FNDELAY fcntl (outfd, F_SETFL, origflags); } #endif //fprintf(stdout, "%s", text); #endif } char *Sys_ConsoleInput(void) { // if (cls.state == ca_dedicated) { static char text[MAX_INPUTLINE]; int len = 0; #ifdef WIN32 int c; // read a line out while (_kbhit ()) { c = _getch (); _putch (c); if (c == '\r') { text[len] = 0; _putch ('\n'); len = 0; return text; } if (c == 8) { if (len) { _putch (' '); _putch (c); len--; text[len] = 0; } continue; } text[len] = c; len++; text[len] = 0; if (len == sizeof (text)) len = 0; } #else fd_set fdset; struct timeval timeout; FD_ZERO(&fdset); FD_SET(0, &fdset); // stdin timeout.tv_sec = 0; timeout.tv_usec = 0; if (select (1, &fdset, NULL, NULL, &timeout) != -1 && FD_ISSET(0, &fdset)) { len = read (0, text, sizeof(text)); if (len >= 1) { // rip off the \n and terminate text[len-1] = 0; return text; } } #endif } return NULL; } char *Sys_GetClipboardData (void) { #if SDL_MAJOR_VERSION != 1 char *data = NULL; char *cliptext; cliptext = SDL_GetClipboardText(); if (cliptext != NULL) { size_t allocsize; allocsize = strlen(cliptext) + 1; data = (char *)Z_Malloc (allocsize); strlcpy (data, cliptext, allocsize); SDL_free(cliptext); } return data; #elif defined(WIN32) char *data = NULL; char *cliptext; if (OpenClipboard (NULL) != 0) { HANDLE hClipboardData; if ((hClipboardData = GetClipboardData (CF_TEXT)) != 0) { if ((cliptext = (char *)GlobalLock (hClipboardData)) != 0) { size_t allocsize; allocsize = GlobalSize (hClipboardData) + 1; data = (char *)Z_Malloc (allocsize); strlcpy (data, cliptext, allocsize); GlobalUnlock (hClipboardData); } } CloseClipboard (); } return data; #else return NULL; #endif } void Sys_InitConsole (void) { } int main (int argc, char *argv[]) { signal(SIGFPE, SIG_IGN); #ifdef __ANDROID__ Sys_AllowProfiling(true); #endif com_argc = argc; com_argv = (const char **)argv; Sys_ProvideSelfFD(); // COMMANDLINEOPTION: sdl: -noterminal disables console output on stdout if(COM_CheckParm("-noterminal")) outfd = -1; // COMMANDLINEOPTION: sdl: -stderr moves console output to stderr else if(COM_CheckParm("-stderr")) outfd = 2; else outfd = 1; #ifndef WIN32 fcntl(0, F_SETFL, fcntl (0, F_GETFL, 0) | FNDELAY); #endif // we don't know which systems we'll want to init, yet... SDL_Init(0); Host_Main(); return 0; } qboolean sys_supportsdlgetticks = true; unsigned int Sys_SDL_GetTicks (void) { return SDL_GetTicks(); } void Sys_SDL_Delay (unsigned int milliseconds) { SDL_Delay(milliseconds); } darkplaces/protocol.h0000664000175000017500000012112713067716222014203 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // protocol.h -- communications protocols #ifndef PROTOCOL_H #define PROTOCOL_H // protocolversion_t is defined in common.h protocolversion_t Protocol_EnumForName(const char *s); const char *Protocol_NameForEnum(protocolversion_t p); protocolversion_t Protocol_EnumForNumber(int n); int Protocol_NumberForEnum(protocolversion_t p); void Protocol_Names(char *buffer, size_t buffersize); // model effects #define MF_ROCKET 1 // leave a trail #define MF_GRENADE 2 // leave a trail #define MF_GIB 4 // leave a trail #define MF_ROTATE 8 // rotate (bonus items) #define MF_TRACER 16 // green split trail #define MF_ZOMGIB 32 // small blood trail #define MF_TRACER2 64 // orange split trail + rotate #define MF_TRACER3 128 // purple trail // entity effects #define EF_BRIGHTFIELD 1 #define EF_MUZZLEFLASH 2 #define EF_BRIGHTLIGHT 4 #define EF_DIMLIGHT 8 #define EF_NODRAW 16 #define EF_ADDITIVE 32 #define EF_BLUE 64 #define EF_RED 128 #define EF_NOGUNBOB 256 // LordHavoc: when used with .viewmodelforclient this makes the entity attach to the view without gun bobbing and such effects, it also works on the player entity to disable gun bobbing of the engine-managed .viewmodel (without affecting any .viewmodelforclient entities attached to the player) #define EF_FULLBRIGHT 512 // LordHavoc: fullbright #define EF_FLAME 1024 // LordHavoc: on fire #define EF_STARDUST 2048 // LordHavoc: showering sparks #define EF_NOSHADOW 4096 // LordHavoc: does not cast a shadow #define EF_NODEPTHTEST 8192 // LordHavoc: shows through walls #define EF_SELECTABLE 16384 // LordHavoc: highlights when PRYDON_CLIENTCURSOR mouse is over it #define EF_DOUBLESIDED 32768 //[515]: disable cull face for this entity #define EF_NOSELFSHADOW 65536 // LordHavoc: does not cast a shadow on itself (or any other EF_NOSELFSHADOW entities) #define EF_DYNAMICMODELLIGHT 131072 #define EF_UNUSED18 262144 #define EF_UNUSED19 524288 #define EF_RESTARTANIM_BIT 1048576 // div0: restart animation bit (like teleport bit, but lerps between end and start of the anim, and doesn't stop player lerping) #define EF_TELEPORT_BIT 2097152 // div0: teleport bit (toggled when teleporting, prevents lerping when the bit has changed) #define EF_LOWPRECISION 4194304 // LordHavoc: entity is low precision (integer coordinates) to save network bandwidth (serverside only) #define EF_NOMODELFLAGS 8388608 // indicates the model's .effects should be ignored (allows overriding them) #define EF_ROCKET 16777216 // leave a trail #define EF_GRENADE 33554432 // leave a trail #define EF_GIB 67108864 // leave a trail #define EF_ROTATE 134217728 // rotate (bonus items) #define EF_TRACER 268435456 // green split trail #define EF_ZOMGIB 536870912 // small blood trail #define EF_TRACER2 1073741824 // orange split trail + rotate #define EF_TRACER3 0x80000000 // purple trail // internaleffects bits (no overlap with EF_ bits): #define INTEF_FLAG1QW 1 #define INTEF_FLAG2QW 2 // flags for the pflags field of entities #define PFLAGS_NOSHADOW 1 #define PFLAGS_CORONA 2 #define PFLAGS_FULLDYNAMIC 128 // must be set or the light fields are ignored // if the high bit of the servercmd is set, the low bits are fast update flags: #define U_MOREBITS (1<<0) #define U_ORIGIN1 (1<<1) #define U_ORIGIN2 (1<<2) #define U_ORIGIN3 (1<<3) #define U_ANGLE2 (1<<4) // LordHavoc: U_NOLERP was only ever used for monsters, so I renamed it U_STEP #define U_STEP (1<<5) #define U_FRAME (1<<6) // just differentiates from other updates #define U_SIGNAL (1<<7) #define U_ANGLE1 (1<<8) #define U_ANGLE3 (1<<9) #define U_MODEL (1<<10) #define U_COLORMAP (1<<11) #define U_SKIN (1<<12) #define U_EFFECTS (1<<13) #define U_LONGENTITY (1<<14) // LordHavoc: protocol extension #define U_EXTEND1 (1<<15) // LordHavoc: first extend byte #define U_DELTA (1<<16) // no data, while this is set the entity is delta compressed (uses previous frame as a baseline, meaning only things that have changed from the previous frame are sent, except for the forced full update every half second) #define U_ALPHA (1<<17) // 1 byte, 0.0-1.0 maps to 0-255, not sent if exactly 1, and the entity is not sent if <=0 unless it has effects (model effects are checked as well) #define U_SCALE (1<<18) // 1 byte, scale / 16 positive, not sent if 1.0 #define U_EFFECTS2 (1<<19) // 1 byte, this is .effects & 0xFF00 (second byte) #define U_GLOWSIZE (1<<20) // 1 byte, encoding is float/4.0, unsigned, not sent if 0 #define U_GLOWCOLOR (1<<21) // 1 byte, palette index, default is 254 (white), this IS used for darklight (allowing colored darklight), however the particles from a darklight are always black, not sent if default value (even if glowsize or glowtrail is set) #define U_COLORMOD (1<<22) // 1 byte, 3 bit red, 3 bit green, 2 bit blue, this lets you tint an object artifically, so you could make a red rocket, or a blue fiend... #define U_EXTEND2 (1<<23) // another byte to follow // LordHavoc: second extend byte #define U_GLOWTRAIL (1<<24) // leaves a trail of particles (of color .glowcolor, or black if it is a negative glowsize) #define U_VIEWMODEL (1<<25) // attachs the model to the view (origin and angles become relative to it), only shown to owner, a more powerful alternative to .weaponmodel and such #define U_FRAME2 (1<<26) // 1 byte, this is .frame & 0xFF00 (second byte) #define U_MODEL2 (1<<27) // 1 byte, this is .modelindex & 0xFF00 (second byte) #define U_EXTERIORMODEL (1<<28) // causes this model to not be drawn when using a first person view (third person will draw it, first person will not) #define U_UNUSED29 (1<<29) // future expansion #define U_UNUSED30 (1<<30) // future expansion #define U_EXTEND3 (1<<31) // another byte to follow, future expansion #define SU_VIEWHEIGHT (1<<0) #define SU_IDEALPITCH (1<<1) #define SU_PUNCH1 (1<<2) #define SU_PUNCH2 (1<<3) #define SU_PUNCH3 (1<<4) #define SU_VELOCITY1 (1<<5) #define SU_VELOCITY2 (1<<6) #define SU_VELOCITY3 (1<<7) //define SU_AIMENT (1<<8) AVAILABLE BIT #define SU_ITEMS (1<<9) #define SU_ONGROUND (1<<10) // no data follows, the bit is it #define SU_INWATER (1<<11) // no data follows, the bit is it #define SU_WEAPONFRAME (1<<12) #define SU_ARMOR (1<<13) #define SU_WEAPON (1<<14) #define SU_EXTEND1 (1<<15) // first extend byte #define SU_PUNCHVEC1 (1<<16) #define SU_PUNCHVEC2 (1<<17) #define SU_PUNCHVEC3 (1<<18) #define SU_VIEWZOOM (1<<19) // byte factor (0 = 0.0 (not valid), 255 = 1.0) #define SU_UNUSED20 (1<<20) #define SU_UNUSED21 (1<<21) #define SU_UNUSED22 (1<<22) #define SU_EXTEND2 (1<<23) // another byte to follow, future expansion // second extend byte #define SU_UNUSED24 (1<<24) #define SU_UNUSED25 (1<<25) #define SU_UNUSED26 (1<<26) #define SU_UNUSED27 (1<<27) #define SU_UNUSED28 (1<<28) #define SU_UNUSED29 (1<<29) #define SU_UNUSED30 (1<<30) #define SU_EXTEND3 (1<<31) // another byte to follow, future expansion // a sound with no channel is a local only sound #define SND_VOLUME (1<<0) // a byte #define SND_ATTENUATION (1<<1) // a byte #define SND_LOOPING (1<<2) // a long #define SND_LARGEENTITY (1<<3) // a short and a byte (instead of a short) #define SND_LARGESOUND (1<<4) // a short (instead of a byte) #define SND_SPEEDUSHORT4000 (1<<5) // ushort speed*4000 (speed is usually 1.0, a value of 0.0 is the same as 1.0) // defaults for clientinfo messages #define DEFAULT_VIEWHEIGHT 22 // game types sent by serverinfo // these determine which intermission screen plays #define GAME_COOP 0 #define GAME_DEATHMATCH 1 //================== // note that there are some defs.qc that mirror to these numbers // also related to svc_strings[] in cl_parse //================== // // server to client // #define svc_bad 0 #define svc_nop 1 #define svc_disconnect 2 #define svc_updatestat 3 // [byte] [long] #define svc_version 4 // [long] server version #define svc_setview 5 // [short] entity number #define svc_sound 6 // #define svc_time 7 // [float] server time #define svc_print 8 // [string] null terminated string #define svc_stufftext 9 // [string] stuffed into client's console buffer // the string should be \n terminated #define svc_setangle 10 // [angle3] set the view angle to this absolute value #define svc_serverinfo 11 // [long] version // [string] signon string // [string]..[0]model cache // [string]...[0]sounds cache #define svc_lightstyle 12 // [byte] [string] #define svc_updatename 13 // [byte] [string] #define svc_updatefrags 14 // [byte] [short] #define svc_clientdata 15 // #define svc_stopsound 16 // #define svc_updatecolors 17 // [byte] [byte] #define svc_particle 18 // [vec3] #define svc_damage 19 #define svc_spawnstatic 20 // svc_spawnbinary 21 #define svc_spawnbaseline 22 #define svc_temp_entity 23 #define svc_setpause 24 // [byte] on / off #define svc_signonnum 25 // [byte] used for the signon sequence #define svc_centerprint 26 // [string] to put in center of the screen #define svc_killedmonster 27 #define svc_foundsecret 28 #define svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten #define svc_intermission 30 // [string] music #define svc_finale 31 // [string] music [string] text #define svc_cdtrack 32 // [byte] track [byte] looptrack #define svc_sellscreen 33 #define svc_cutscene 34 #define svc_showlmp 35 // [string] slotname [string] lmpfilename [short] x [short] y #define svc_hidelmp 36 // [string] slotname #define svc_skybox 37 // [string] skyname // LordHavoc: my svc_ range, 50-69 #define svc_downloaddata 50 // [int] start [short] size #define svc_updatestatubyte 51 // [byte] stat [byte] value #define svc_effect 52 // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate #define svc_effect2 53 // [vector] org [short] modelindex [short] startframe [byte] framecount [byte] framerate #define svc_sound2 54 // (obsolete in DP6 and later) short soundindex instead of byte #define svc_precache 54 // [short] precacheindex [string] filename, precacheindex is + 0 for modelindex and +32768 for soundindex #define svc_spawnbaseline2 55 // short modelindex instead of byte #define svc_spawnstatic2 56 // short modelindex instead of byte #define svc_entities 57 // [int] deltaframe [int] thisframe [float vector] eye [variable length] entitydata #define svc_csqcentities 58 // [short] entnum [variable length] entitydata ... [short] 0x0000 #define svc_spawnstaticsound2 59 // [coord3] [short] samp [byte] vol [byte] aten #define svc_trailparticles 60 // [short] entnum [short] effectnum [vector] start [vector] end #define svc_pointparticles 61 // [short] effectnum [vector] start [vector] velocity [short] count #define svc_pointparticles1 62 // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 // // client to server // #define clc_bad 0 #define clc_nop 1 #define clc_disconnect 2 #define clc_move 3 // [usercmd_t] #define clc_stringcmd 4 // [string] message // LordHavoc: my clc_ range, 50-59 #define clc_ackframe 50 // [int] framenumber #define clc_ackdownloaddata 51 // [int] start [short] size (note: exact echo of latest values received in svc_downloaddata, packet-loss handling is in the server) #define clc_unusedlh2 52 #define clc_unusedlh3 53 #define clc_unusedlh4 54 #define clc_unusedlh5 55 #define clc_unusedlh6 56 #define clc_unusedlh7 57 #define clc_unusedlh8 58 #define clc_unusedlh9 59 // // temp entity events // #define TE_SPIKE 0 // [vector] origin #define TE_SUPERSPIKE 1 // [vector] origin #define TE_GUNSHOT 2 // [vector] origin #define TE_EXPLOSION 3 // [vector] origin #define TE_TAREXPLOSION 4 // [vector] origin #define TE_LIGHTNING1 5 // [entity] entity [vector] start [vector] end #define TE_LIGHTNING2 6 // [entity] entity [vector] start [vector] end #define TE_WIZSPIKE 7 // [vector] origin #define TE_KNIGHTSPIKE 8 // [vector] origin #define TE_LIGHTNING3 9 // [entity] entity [vector] start [vector] end #define TE_LAVASPLASH 10 // [vector] origin #define TE_TELEPORT 11 // [vector] origin #define TE_EXPLOSION2 12 // [vector] origin [byte] startcolor [byte] colorcount // PGM 01/21/97 #define TE_BEAM 13 // [entity] entity [vector] start [vector] end // PGM 01/21/97 // Nehahra effects used in the movie (TE_EXPLOSION3 also got written up in a QSG tutorial, hence it's not marked NEH) #define TE_EXPLOSION3 16 // [vector] origin [coord] red [coord] green [coord] blue #define TE_LIGHTNING4NEH 17 // [string] model [entity] entity [vector] start [vector] end // LordHavoc: added some TE_ codes (block1 - 50-60) #define TE_BLOOD 50 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count #define TE_SPARK 51 // [vector] origin [byte] xvel [byte] yvel [byte] zvel [byte] count #define TE_BLOODSHOWER 52 // [vector] min [vector] max [coord] explosionspeed [short] count #define TE_EXPLOSIONRGB 53 // [vector] origin [byte] red [byte] green [byte] blue #define TE_PARTICLECUBE 54 // [vector] min [vector] max [vector] dir [short] count [byte] color [byte] gravity [coord] randomvel #define TE_PARTICLERAIN 55 // [vector] min [vector] max [vector] dir [short] count [byte] color #define TE_PARTICLESNOW 56 // [vector] min [vector] max [vector] dir [short] count [byte] color #define TE_GUNSHOTQUAD 57 // [vector] origin #define TE_SPIKEQUAD 58 // [vector] origin #define TE_SUPERSPIKEQUAD 59 // [vector] origin // LordHavoc: block2 - 70-80 #define TE_EXPLOSIONQUAD 70 // [vector] origin #define TE_UNUSED1 71 // unused #define TE_SMALLFLASH 72 // [vector] origin #define TE_CUSTOMFLASH 73 // [vector] origin [byte] radius / 8 - 1 [byte] lifetime / 256 - 1 [byte] red [byte] green [byte] blue #define TE_FLAMEJET 74 // [vector] origin [vector] velocity [byte] count #define TE_PLASMABURN 75 // [vector] origin // LordHavoc: Tei grabbed these codes #define TE_TEI_G3 76 // [vector] start [vector] end [vector] angles #define TE_TEI_SMOKE 77 // [vector] origin [vector] dir [byte] count #define TE_TEI_BIGEXPLOSION 78 // [vector] origin #define TE_TEI_PLASMAHIT 79 // [vector} origin [vector] dir [byte] count // these are bits for the 'flags' field of the entity_state_t #define RENDER_STEP 1 #define RENDER_GLOWTRAIL 2 #define RENDER_VIEWMODEL 4 #define RENDER_EXTERIORMODEL 8 #define RENDER_LOWPRECISION 16 // send as low precision coordinates to save bandwidth #define RENDER_COLORMAPPED 32 #define RENDER_WORLDOBJECT 64 // do not cull this entity with r_cullentities #define RENDER_COMPLEXANIMATION 128 #define RENDER_SHADOW 65536 // cast shadow #define RENDER_LIGHT 131072 // receive light #define RENDER_NOSELFSHADOW 262144 // render lighting on this entity before its own shadow is added to the scene // (note: all RENDER_NOSELFSHADOW entities are grouped together and rendered in a batch before their shadows are rendered, so they can not shadow eachother either) #define RENDER_EQUALIZE 524288 // (subflag of RENDER_LIGHT) equalize the light from the light grid hitting this ent (less invasive EF_FULLBRIGHT implementation) #define RENDER_NODEPTHTEST 1048576 #define RENDER_ADDITIVE 2097152 #define RENDER_DOUBLESIDED 4194304 #define RENDER_CUSTOMIZEDMODELLIGHT 4096 #define RENDER_DYNAMICMODELLIGHT 8388608 // origin dependent model light #define MAX_FRAMEGROUPBLENDS 4 typedef struct framegroupblend_s { // animation number and blend factor // (for most models this is the frame number) int frame; float lerp; // time frame began playing (for framegroup animations) double start; } framegroupblend_t; struct matrix4x4_s; struct model_s; typedef struct skeleton_s { const struct model_s *model; struct matrix4x4_s *relativetransforms; } skeleton_t; typedef enum entity_state_active_e { ACTIVE_NOT = 0, ACTIVE_NETWORK = 1, ACTIVE_SHARED = 2 } entity_state_active_t; // this was 96 bytes, now 168 bytes (32bit) or 176 bytes (64bit) typedef struct entity_state_s { // ! means this is not sent to client double time; // ! time this state was built (used on client for interpolation) float netcenter[3]; // ! for network prioritization, this is the center of the bounding box (which may differ from the origin) float origin[3]; float angles[3]; int effects; unsigned int customizeentityforclient; // ! unsigned short number; // entity number this state is for unsigned short modelindex; unsigned short frame; unsigned short tagentity; unsigned short specialvisibilityradius; // ! larger if it has effects/light unsigned short viewmodelforclient; // ! unsigned short exteriormodelforclient; // ! not shown if first person viewing from this entity, shown in all other cases unsigned short nodrawtoclient; // ! unsigned short drawonlytoclient; // ! unsigned short traileffectnum; unsigned short light[4]; // color*256 (0.00 to 255.996), and radius*1 unsigned char active; // true if a valid state unsigned char lightstyle; unsigned char lightpflags; unsigned char colormap; unsigned char skin; // also chooses cubemap for rtlights if lightpflags & LIGHTPFLAGS_FULLDYNAMIC unsigned char alpha; unsigned char scale; unsigned char glowsize; unsigned char glowcolor; unsigned char flags; unsigned char internaleffects; // INTEF_FLAG1QW and so on unsigned char tagindex; unsigned char colormod[3]; unsigned char glowmod[3]; // LordHavoc: very big data here :( framegroupblend_t framegroupblend[4]; skeleton_t skeletonobject; } entity_state_t; // baseline state values extern entity_state_t defaultstate; // reads a quake entity from the network stream void EntityFrameQuake_ReadEntity(int bits); // checks for stats changes and sets corresponding host_client->statsdeltabits // (also updates host_client->stats array) void Protocol_UpdateClientStats(const int *stats); // writes reliable messages updating stats (not used by DP6 and later // protocols which send updates in their WriteFrame function using a different // method of reliable messaging) void Protocol_WriteStatsReliable(void); // writes a list of quake entities to the network stream // (or as many will fit) qboolean EntityFrameQuake_WriteFrame(sizebuf_t *msg, int maxsize, int numstates, const entity_state_t **states); // cleans up dead entities each frame after ReadEntity (which doesn't clear unused entities) void EntityFrameQuake_ISeeDeadEntities(void); /* PROTOCOL_DARKPLACES3 server updates entities according to some (unmentioned) scheme. a frame consists of all visible entities, some of which are up to date, often some are not up to date. these entities are stored in a range (firstentity/endentity) of structs in the entitydata[] buffer. to make a commit the server performs these steps: 1. duplicate oldest frame in database (this is the baseline) as new frame, and write frame numbers (oldest frame's number, new frame's number) and eye location to network packet (eye location is obsolete and will be removed in future revisions) 2. write an entity change to packet and modify new frame accordingly (this repeats until packet is sufficiently full or new frame is complete) 3. write terminator (0xFFFF) to network packet (FIXME: this terminator value conflicts with MAX_EDICTS 32768...) to read a commit the client performs these steps: 1. reads frame numbers from packet and duplicates baseline frame as new frame, also reads eye location but does nothing with it (obsolete). 2. delete frames older than the baseline which was used 3. read entity changes from packet until terminator (0xFFFF) is encountered, each change is applied to entity frame. 4. sends ack framenumber to server as part of input packet if server receives ack message in put packet it performs these steps: 1. remove all older frames from database. */ /* PROTOCOL_DARKPLACES4 a frame consists of some visible entities in a range (this is stored as start and end, note that end may be less than start if it wrapped). these entities are stored in a range (firstentity/endentity) of structs in the entitydata[] buffer. to make a commit the server performs these steps: 1. build an entity_frame_t using appropriate functions, containing (some of) the visible entities, this is passed to the Write function to send it. This documention is unfinished! the Write function performs these steps: 1. check if entity frame is larger than MAX_ENTITYFRAME or is larger than available space in database, if so the baseline is defaults, otherwise it is the current baseline of the database. 2. write differences of an entity compared to selected baseline. 3. add entity to entity update in database. 4. if there are more entities to write and packet is not full, go back to step 2. 5. write terminator (0xFFFF) as entity number. 6. return. server updates entities in looping ranges, a frame consists of a range of visible entities (not always all visible entities), */ #define MAX_ENTITY_HISTORY 64 #define MAX_ENTITY_DATABASE (MAX_EDICTS * 2) // build entity data in this, to pass to entity read/write functions typedef struct entity_frame_s { double time; int framenum; int numentities; int firstentitynum; int lastentitynum; vec3_t eye; entity_state_t entitydata[MAX_ENTITY_DATABASE]; } entity_frame_t; typedef struct entity_frameinfo_s { double time; int framenum; int firstentity; // index into entitydata, modulo MAX_ENTITY_DATABASE int endentity; // index into entitydata, firstentity + numentities } entity_frameinfo_t; typedef struct entityframe_database_s { // note: these can be far out of range, modulo with MAX_ENTITY_DATABASE to get a valid range (which may wrap) // start and end of used area, when adding a new update to database, store at endpos, and increment endpos // when removing updates from database, nudge down frames array to only contain useful frames // this logic should explain better: // if (numframes >= MAX_ENTITY_HISTORY || (frames[numframes - 1].endentity - frames[0].firstentity) + entitiestoadd > MAX_ENTITY_DATABASE) // flushdatabase(); // note: if numframes == 0, insert at start (0 in entitydata) // the only reason this system is used is to avoid copying memory when frames are removed int numframes; // server only: last sent frame int latestframenum; // server only: last acknowledged frame int ackframenum; // the current state in the database vec3_t eye; // table of entities in the entityhistorydata entity_frameinfo_t frames[MAX_ENTITY_HISTORY]; // entities entity_state_t entitydata[MAX_ENTITY_DATABASE]; // structs for building new frames and reading them entity_frame_t deltaframe; entity_frame_t framedata; } entityframe_database_t; // LordHavoc: these are in approximately sorted order, according to cost and // likelyhood of being used for numerous objects in a frame // note that the bytes are not written/read in this order, this is only the // order of the bits to minimize overhead from extend bytes // enough to describe a nail, gib, shell casing, bullet hole, or rocket #define E_ORIGIN1 (1<<0) #define E_ORIGIN2 (1<<1) #define E_ORIGIN3 (1<<2) #define E_ANGLE1 (1<<3) #define E_ANGLE2 (1<<4) #define E_ANGLE3 (1<<5) #define E_MODEL1 (1<<6) #define E_EXTEND1 (1<<7) // enough to describe almost anything #define E_FRAME1 (1<<8) #define E_EFFECTS1 (1<<9) #define E_ALPHA (1<<10) #define E_SCALE (1<<11) #define E_COLORMAP (1<<12) #define E_SKIN (1<<13) #define E_FLAGS (1<<14) #define E_EXTEND2 (1<<15) // players, custom color glows, high model numbers #define E_FRAME2 (1<<16) #define E_MODEL2 (1<<17) #define E_EFFECTS2 (1<<18) #define E_GLOWSIZE (1<<19) #define E_GLOWCOLOR (1<<20) #define E_LIGHT (1<<21) #define E_LIGHTPFLAGS (1<<22) #define E_EXTEND3 (1<<23) #define E_SOUND1 (1<<24) #define E_SOUNDVOL (1<<25) #define E_SOUNDATTEN (1<<26) #define E_TAGATTACHMENT (1<<27) #define E_LIGHTSTYLE (1<<28) #define E_UNUSED6 (1<<29) #define E_UNUSED7 (1<<30) #define E_EXTEND4 (1<<31) // returns difference between two states as E_ flags int EntityState_DeltaBits(const entity_state_t *o, const entity_state_t *n); // write E_ flags to a msg void EntityState_WriteExtendBits(sizebuf_t *msg, unsigned int bits); // write values for the E_ flagged fields to a msg void EntityState_WriteFields(const entity_state_t *ent, sizebuf_t *msg, unsigned int bits); // write entity number and E_ flags and their values, or a remove number, describing the change from delta to ent void EntityState_WriteUpdate(const entity_state_t *ent, sizebuf_t *msg, const entity_state_t *delta); // read E_ flags int EntityState_ReadExtendBits(void); // read values for E_ flagged fields and apply them to a state void EntityState_ReadFields(entity_state_t *e, unsigned int bits); // (client and server) allocates a new empty database entityframe_database_t *EntityFrame_AllocDatabase(mempool_t *mempool); // (client and server) frees the database void EntityFrame_FreeDatabase(entityframe_database_t *d); // (server) clears the database to contain no frames (thus delta compression // compresses against nothing) void EntityFrame_ClearDatabase(entityframe_database_t *d); // (server and client) removes frames older than 'frame' from database void EntityFrame_AckFrame(entityframe_database_t *d, int frame); // (server) clears frame, to prepare for adding entities void EntityFrame_Clear(entity_frame_t *f, vec3_t eye, int framenum); // (server and client) reads a frame from the database void EntityFrame_FetchFrame(entityframe_database_t *d, int framenum, entity_frame_t *f); // (client) adds a entity_frame to the database, for future reference void EntityFrame_AddFrame_Client(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t *entitydata); // (server) adds a entity_frame to the database, for future reference void EntityFrame_AddFrame_Server(entityframe_database_t *d, vec3_t eye, int framenum, int numentities, const entity_state_t **entitydata); // (server) writes a frame to network stream qboolean EntityFrame_WriteFrame(sizebuf_t *msg, int maxsize, entityframe_database_t *d, int numstates, const entity_state_t **states, int viewentnum); // (client) reads a frame from network stream void EntityFrame_CL_ReadFrame(void); // (client) returns the frame number of the most recent frame recieved int EntityFrame_MostRecentlyRecievedFrameNum(entityframe_database_t *d); typedef struct entity_database4_commit_s { // frame number this commit represents int framenum; // number of entities in entity[] array int numentities; // maximum number of entities in entity[] array (dynamic resizing) int maxentities; entity_state_t *entity; } entity_database4_commit_t; typedef struct entity_database4_s { // what mempool to use for allocations mempool_t *mempool; // reference frame int referenceframenum; // reference entities array is resized according to demand int maxreferenceentities; // array of states for entities, these are indexable by their entity number (yes there are gaps) entity_state_t *referenceentity; // commits waiting to be applied to the reference database when confirmed // (commit[i]->numentities == 0 means it is empty) entity_database4_commit_t commit[MAX_ENTITY_HISTORY]; // (server only) the current commit being worked on entity_database4_commit_t *currentcommit; // (server only) if a commit won't fit entirely, continue where it left // off next frame int currententitynumber; // (server only) int latestframenumber; } entityframe4_database_t; // should-be-private functions that aren't entity_state_t *EntityFrame4_GetReferenceEntity(entityframe4_database_t *d, int number); void EntityFrame4_AddCommitEntity(entityframe4_database_t *d, const entity_state_t *s); // allocate a database entityframe4_database_t *EntityFrame4_AllocDatabase(mempool_t *pool); // free a database void EntityFrame4_FreeDatabase(entityframe4_database_t *d); // reset a database (resets compression but does not reallocate anything) void EntityFrame4_ResetDatabase(entityframe4_database_t *d); // updates database to account for a frame-received acknowledgment int EntityFrame4_AckFrame(entityframe4_database_t *d, int framenum, int servermode); // writes a frame to the network stream qboolean EntityFrame4_WriteFrame(sizebuf_t *msg, int maxsize, entityframe4_database_t *d, int numstates, const entity_state_t **states); // reads a frame from the network stream void EntityFrame4_CL_ReadFrame(void); // reset all entity fields (typically used if status changed) #define E5_FULLUPDATE (1<<0) // E5_ORIGIN32=0: short[3] = s->origin[0] * 8, s->origin[1] * 8, s->origin[2] * 8 // E5_ORIGIN32=1: float[3] = s->origin[0], s->origin[1], s->origin[2] #define E5_ORIGIN (1<<1) // E5_ANGLES16=0: byte[3] = s->angle[0] * 256 / 360, s->angle[1] * 256 / 360, s->angle[2] * 256 / 360 // E5_ANGLES16=1: short[3] = s->angle[0] * 65536 / 360, s->angle[1] * 65536 / 360, s->angle[2] * 65536 / 360 #define E5_ANGLES (1<<2) // E5_MODEL16=0: byte = s->modelindex // E5_MODEL16=1: short = s->modelindex #define E5_MODEL (1<<3) // E5_FRAME16=0: byte = s->frame // E5_FRAME16=1: short = s->frame #define E5_FRAME (1<<4) // byte = s->skin #define E5_SKIN (1<<5) // E5_EFFECTS16=0 && E5_EFFECTS32=0: byte = s->effects // E5_EFFECTS16=1 && E5_EFFECTS32=0: short = s->effects // E5_EFFECTS16=0 && E5_EFFECTS32=1: int = s->effects // E5_EFFECTS16=1 && E5_EFFECTS32=1: int = s->effects #define E5_EFFECTS (1<<6) // bits >= (1<<8) #define E5_EXTEND1 (1<<7) // byte = s->renderflags #define E5_FLAGS (1<<8) // byte = bound(0, s->alpha * 255, 255) #define E5_ALPHA (1<<9) // byte = bound(0, s->scale * 16, 255) #define E5_SCALE (1<<10) // flag #define E5_ORIGIN32 (1<<11) // flag #define E5_ANGLES16 (1<<12) // flag #define E5_MODEL16 (1<<13) // byte = s->colormap #define E5_COLORMAP (1<<14) // bits >= (1<<16) #define E5_EXTEND2 (1<<15) // short = s->tagentity // byte = s->tagindex #define E5_ATTACHMENT (1<<16) // short[4] = s->light[0], s->light[1], s->light[2], s->light[3] // byte = s->lightstyle // byte = s->lightpflags #define E5_LIGHT (1<<17) // byte = s->glowsize // byte = s->glowcolor #define E5_GLOW (1<<18) // short = s->effects #define E5_EFFECTS16 (1<<19) // int = s->effects #define E5_EFFECTS32 (1<<20) // flag #define E5_FRAME16 (1<<21) // byte[3] = s->colormod[0], s->colormod[1], s->colormod[2] #define E5_COLORMOD (1<<22) // bits >= (1<<24) #define E5_EXTEND3 (1<<23) // byte[3] = s->glowmod[0], s->glowmod[1], s->glowmod[2] #define E5_GLOWMOD (1<<24) // byte type=0 short frames[1] short times[1] // byte type=1 short frames[2] short times[2] byte lerps[2] // byte type=2 short frames[3] short times[3] byte lerps[3] // byte type=3 short frames[4] short times[4] byte lerps[4] // byte type=4 short modelindex byte numbones {short pose7s[7]} // see also RENDER_COMPLEXANIMATION #define E5_COMPLEXANIMATION (1<<25) // ushort traileffectnum #define E5_TRAILEFFECTNUM (1<<26) // unused #define E5_UNUSED27 (1<<27) // unused #define E5_UNUSED28 (1<<28) // unused #define E5_UNUSED29 (1<<29) // unused #define E5_UNUSED30 (1<<30) // bits2 > 0 #define E5_EXTEND4 (1<<31) #define ENTITYFRAME5_MAXPACKETLOGS 64 #define ENTITYFRAME5_MAXSTATES 1024 #define ENTITYFRAME5_PRIORITYLEVELS 32 typedef struct entityframe5_changestate_s { unsigned int number; unsigned int bits; } entityframe5_changestate_t; typedef struct entityframe5_packetlog_s { int packetnumber; int numstates; entityframe5_changestate_t states[ENTITYFRAME5_MAXSTATES]; unsigned char statsdeltabits[(MAX_CL_STATS+7)/8]; } entityframe5_packetlog_t; typedef struct entityframe5_database_s { // number of the latest message sent to client int latestframenum; // updated by WriteFrame for internal use int viewentnum; // logs of all recently sent messages (between acked and latest) entityframe5_packetlog_t packetlog[ENTITYFRAME5_MAXPACKETLOGS]; // this goes up as needed and causes all the arrays to be reallocated int maxedicts; // which properties of each entity have changed since last send int *deltabits; // [maxedicts] // priorities of entities (updated whenever deltabits change) // (derived from deltabits) unsigned char *priorities; // [maxedicts] // last frame this entity was sent on, for prioritzation int *updateframenum; // [maxedicts] // database of current status of all entities entity_state_t *states; // [maxedicts] // which entities are currently active // (duplicate of the active bit of every state in states[]) // (derived from states) unsigned char *visiblebits; // [(maxedicts+7)/8] // old notes // this is used to decide which changestates to set each frame //int numvisiblestates; //entity_state_t visiblestates[MAX_EDICTS]; // sorted changing states that need to be sent to the client // kept sorted in lowest to highest priority order, because this allows // the numchangestates to simply be decremented whenever an state is sent, // rather than a memmove to remove them from the start. //int numchangestates; //entityframe5_changestate_t changestates[MAX_EDICTS]; // buffers for building priority info int prioritychaincounts[ENTITYFRAME5_PRIORITYLEVELS]; unsigned short prioritychains[ENTITYFRAME5_PRIORITYLEVELS][ENTITYFRAME5_MAXSTATES]; } entityframe5_database_t; entityframe5_database_t *EntityFrame5_AllocDatabase(mempool_t *pool); void EntityFrame5_FreeDatabase(entityframe5_database_t *d); void EntityState5_WriteUpdate(int number, const entity_state_t *s, int changedbits, sizebuf_t *msg); int EntityState5_DeltaBitsForState(entity_state_t *o, entity_state_t *n); void EntityFrame5_CL_ReadFrame(void); void EntityFrame5_LostFrame(entityframe5_database_t *d, int framenum); void EntityFrame5_AckFrame(entityframe5_database_t *d, int framenum); qboolean EntityFrame5_WriteFrame(sizebuf_t *msg, int maxsize, entityframe5_database_t *d, int numstates, const entity_state_t **states, int viewentnum, unsigned int movesequence, qboolean need_empty); extern cvar_t developer_networkentities; // QUAKEWORLD // server to client #define qw_svc_bad 0 #define qw_svc_nop 1 #define qw_svc_disconnect 2 #define qw_svc_updatestat 3 // [byte] [byte] #define qw_svc_setview 5 // [short] entity number #define qw_svc_sound 6 // #define qw_svc_print 8 // [byte] id [string] null terminated string #define qw_svc_stufftext 9 // [string] stuffed into client's console buffer #define qw_svc_setangle 10 // [angle3] set the view angle to this absolute value #define qw_svc_serverdata 11 // [long] protocol ... #define qw_svc_lightstyle 12 // [byte] [string] #define qw_svc_updatefrags 14 // [byte] [short] #define qw_svc_stopsound 16 // #define qw_svc_damage 19 #define qw_svc_spawnstatic 20 #define qw_svc_spawnbaseline 22 #define qw_svc_temp_entity 23 // variable #define qw_svc_setpause 24 // [byte] on / off #define qw_svc_centerprint 26 // [string] to put in center of the screen #define qw_svc_killedmonster 27 #define qw_svc_foundsecret 28 #define qw_svc_spawnstaticsound 29 // [coord3] [byte] samp [byte] vol [byte] aten #define qw_svc_intermission 30 // [vec3_t] origin [vec3_t] angle #define qw_svc_finale 31 // [string] text #define qw_svc_cdtrack 32 // [byte] track #define qw_svc_sellscreen 33 #define qw_svc_smallkick 34 // set client punchangle to 2 #define qw_svc_bigkick 35 // set client punchangle to 4 #define qw_svc_updateping 36 // [byte] [short] #define qw_svc_updateentertime 37 // [byte] [float] #define qw_svc_updatestatlong 38 // [byte] [long] #define qw_svc_muzzleflash 39 // [short] entity #define qw_svc_updateuserinfo 40 // [byte] slot [long] uid #define qw_svc_download 41 // [short] size [size bytes] #define qw_svc_playerinfo 42 // variable #define qw_svc_nails 43 // [byte] num [48 bits] xyzpy 12 12 12 4 8 #define qw_svc_chokecount 44 // [byte] packets choked #define qw_svc_modellist 45 // [strings] #define qw_svc_soundlist 46 // [strings] #define qw_svc_packetentities 47 // [...] #define qw_svc_deltapacketentities 48 // [...] #define qw_svc_maxspeed 49 // maxspeed change, for prediction #define qw_svc_entgravity 50 // gravity change, for prediction #define qw_svc_setinfo 51 // setinfo on a client #define qw_svc_serverinfo 52 // serverinfo #define qw_svc_updatepl 53 // [byte] [byte] // QUAKEWORLD // client to server #define qw_clc_bad 0 #define qw_clc_nop 1 #define qw_clc_move 3 // [[usercmd_t] #define qw_clc_stringcmd 4 // [string] message #define qw_clc_delta 5 // [byte] sequence number, requests delta compression of message #define qw_clc_tmove 6 // teleport request, spectator only #define qw_clc_upload 7 // teleport request, spectator only // QUAKEWORLD // playerinfo flags from server // playerinfo always sends: playernum, flags, origin[] and framenumber #define QW_PF_MSEC (1<<0) #define QW_PF_COMMAND (1<<1) #define QW_PF_VELOCITY1 (1<<2) #define QW_PF_VELOCITY2 (1<<3) #define QW_PF_VELOCITY3 (1<<4) #define QW_PF_MODEL (1<<5) #define QW_PF_SKINNUM (1<<6) #define QW_PF_EFFECTS (1<<7) #define QW_PF_WEAPONFRAME (1<<8) // only sent for view player #define QW_PF_DEAD (1<<9) // don't block movement any more #define QW_PF_GIB (1<<10) // offset the view height differently #define QW_PF_NOGRAV (1<<11) // don't apply gravity for prediction // QUAKEWORLD // if the high bit of the client to server byte is set, the low bits are // client move cmd bits // ms and angle2 are allways sent, the others are optional #define QW_CM_ANGLE1 (1<<0) #define QW_CM_ANGLE3 (1<<1) #define QW_CM_FORWARD (1<<2) #define QW_CM_SIDE (1<<3) #define QW_CM_UP (1<<4) #define QW_CM_BUTTONS (1<<5) #define QW_CM_IMPULSE (1<<6) #define QW_CM_ANGLE2 (1<<7) // QUAKEWORLD // the first 16 bits of a packetentities update holds 9 bits // of entity number and 7 bits of flags #define QW_U_ORIGIN1 (1<<9) #define QW_U_ORIGIN2 (1<<10) #define QW_U_ORIGIN3 (1<<11) #define QW_U_ANGLE2 (1<<12) #define QW_U_FRAME (1<<13) #define QW_U_REMOVE (1<<14) // REMOVE this entity, don't add it #define QW_U_MOREBITS (1<<15) // if MOREBITS is set, these additional flags are read in next #define QW_U_ANGLE1 (1<<0) #define QW_U_ANGLE3 (1<<1) #define QW_U_MODEL (1<<2) #define QW_U_COLORMAP (1<<3) #define QW_U_SKIN (1<<4) #define QW_U_EFFECTS (1<<5) #define QW_U_SOLID (1<<6) // the entity should be solid for prediction // QUAKEWORLD // temp entity events #define QW_TE_SPIKE 0 #define QW_TE_SUPERSPIKE 1 #define QW_TE_GUNSHOT 2 #define QW_TE_EXPLOSION 3 #define QW_TE_TAREXPLOSION 4 #define QW_TE_LIGHTNING1 5 #define QW_TE_LIGHTNING2 6 #define QW_TE_WIZSPIKE 7 #define QW_TE_KNIGHTSPIKE 8 #define QW_TE_LIGHTNING3 9 #define QW_TE_LAVASPLASH 10 #define QW_TE_TELEPORT 11 #define QW_TE_BLOOD 12 #define QW_TE_LIGHTNINGBLOOD 13 // QUAKEWORLD // effect flags #define QW_EF_BRIGHTFIELD 1 #define QW_EF_MUZZLEFLASH 2 #define QW_EF_BRIGHTLIGHT 4 #define QW_EF_DIMLIGHT 8 #define QW_EF_FLAG1 16 #define QW_EF_FLAG2 32 #define QW_EF_BLUE 64 #define QW_EF_RED 128 #define QW_UPDATE_BACKUP 64 #define QW_UPDATE_MASK (QW_UPDATE_BACKUP - 1) #define QW_MAX_PACKET_ENTITIES 64 // note: QW stats are directly compatible with NQ // (but FRAGS, WEAPONFRAME, and VIEWHEIGHT are unused) // so these defines are not actually used by darkplaces, but kept for reference #define QW_STAT_HEALTH 0 //#define QW_STAT_FRAGS 1 #define QW_STAT_WEAPON 2 #define QW_STAT_AMMO 3 #define QW_STAT_ARMOR 4 //#define QW_STAT_WEAPONFRAME 5 #define QW_STAT_SHELLS 6 #define QW_STAT_NAILS 7 #define QW_STAT_ROCKETS 8 #define QW_STAT_CELLS 9 #define QW_STAT_ACTIVEWEAPON 10 #define QW_STAT_TOTALSECRETS 11 #define QW_STAT_TOTALMONSTERS 12 #define QW_STAT_SECRETS 13 // bumped on client side by svc_foundsecret #define QW_STAT_MONSTERS 14 // bumped by svc_killedmonster #define QW_STAT_ITEMS 15 //#define QW_STAT_VIEWHEIGHT 16 // build entity data in this, to pass to entity read/write functions typedef struct entityframeqw_snapshot_s { double time; qboolean invalid; int num_entities; entity_state_t entities[QW_MAX_PACKET_ENTITIES]; } entityframeqw_snapshot_t; typedef struct entityframeqw_database_s { entityframeqw_snapshot_t snapshot[QW_UPDATE_BACKUP]; } entityframeqw_database_t; entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool); void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d); void EntityStateQW_ReadPlayerUpdate(void); void EntityFrameQW_CL_ReadFrame(qboolean delta); struct client_s; void EntityFrameCSQC_LostFrame(struct client_s *client, int framenum); qboolean EntityFrameCSQC_WriteFrame (sizebuf_t *msg, int maxsize, int numnumbers, const unsigned short *numbers, int framenum); #endif darkplaces/sv_phys.c0000664000175000017500000034322513067716222014035 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_phys.c #include "quakedef.h" #include "prvm_cmds.h" /* pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS corpses are SOLID_NOT and MOVETYPE_TOSS crates are SOLID_BBOX and MOVETYPE_TOSS walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY solid_edge items only clip against bsp models. */ #define MOVE_EPSILON 0.01 void SV_Physics_Toss (prvm_edict_t *ent); int SV_GetPitchSign(prvm_prog_t *prog, prvm_edict_t *ent) { dp_model_t *model; if ( (model = SV_GetModelFromEdict(ent)) ? model->type == mod_alias : ( (((unsigned char)PRVM_serveredictfloat(ent, pflags)) & PFLAGS_FULLDYNAMIC) || ((gamemode == GAME_TENEBRAE) && ((unsigned int)PRVM_serveredictfloat(ent, effects) & (16 | 32))) ) ) return -1; return 1; } /* =============================================================================== LINE TESTING IN HULLS =============================================================================== */ int SV_GenericHitSuperContentsMask(const prvm_edict_t *passedict) { prvm_prog_t *prog = SVVM_prog; if (passedict) { int dphitcontentsmask = (int)PRVM_serveredictfloat(passedict, dphitcontentsmask); if (dphitcontentsmask) return dphitcontentsmask; else if (PRVM_serveredictfloat(passedict, solid) == SOLID_SLIDEBOX) { if ((int)PRVM_serveredictfloat(passedict, flags) & FL_MONSTER) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP; else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP; } else if (PRVM_serveredictfloat(passedict, solid) == SOLID_CORPSE) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; else if (PRVM_serveredictfloat(passedict, solid) == SOLID_TRIGGER) return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY; else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; } else return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE; } /* ================== SV_TracePoint ================== */ trace_t SV_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask) { prvm_prog_t *prog = SVVM_prog; int i, bodysupercontents; int passedictprog; float pitchsign = 1; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may differ from vec_t vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask, skipsupercontentsmask); VectorCopy(start, clipstart); VectorClear(clipmins2); VectorClear(clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f)", clipstart[0], clipstart[1], clipstart[2]); #endif // clip to world Collision_ClipPointToWorld(&cliptrace, sv.worldmodel, clipstart, hitsupercontentsmask, skipsupercontentsmask); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog->edicts; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) if (passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = PRVM_EDICT_TO_PROG(passedict); // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_serveredictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) { model = SV_GetModelFromEdict(touch); pitchsign = SV_GetPitchSign(prog, touch); } if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipstart, hitsupercontentsmask, skipsupercontentsmask, 0.0f); else Collision_ClipPointToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, hitsupercontentsmask, skipsupercontentsmask); Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } /* ================== SV_TraceLine ================== */ trace_t SV_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend) { prvm_prog_t *prog = SVVM_prog; int i, bodysupercontents; int passedictprog; float pitchsign = 1; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may differ from vec_t vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (VectorCompare(start, end)) return SV_TracePoint(start, type, passedict, hitsupercontentsmask, skipsupercontentsmask); //return SV_TraceBox(start, vec3_origin, vec3_origin, end, type, passedict, hitsupercontentsmask); VectorCopy(start, clipstart); VectorCopy(end, clipend); VectorClear(clipmins2); VectorClear(clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_ClipLineToWorld(&cliptrace, sv.worldmodel, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask, extend, false); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog->edicts; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) if (passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = PRVM_EDICT_TO_PROG(passedict); // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_serveredictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) { model = SV_GetModelFromEdict(touch); pitchsign = SV_GetPitchSign(prog, touch); } if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); else Collision_ClipLineToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, skipsupercontentsmask, extend, false); Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } /* ================== SV_Move ================== */ #if COLLISIONPARANOID >= 1 trace_t SV_TraceBox_(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend) #else trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask, float extend) #endif { prvm_prog_t *prog = SVVM_prog; vec3_t hullmins, hullmaxs; int i, bodysupercontents; int passedictprog; float pitchsign = 1; qboolean pointtrace; prvm_edict_t *traceowner, *touch; trace_t trace; // temporary storage because prvm_vec_t may differ from vec_t vec3_t touchmins, touchmaxs; // bounding box of entire move area vec3_t clipboxmins, clipboxmaxs; // size of the moving object vec3_t clipmins, clipmaxs; // size when clipping against monsters vec3_t clipmins2, clipmaxs2; // start and end origin of move vec3_t clipstart, clipend; // trace results trace_t cliptrace; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (VectorCompare(mins, maxs)) { vec3_t shiftstart, shiftend; VectorAdd(start, mins, shiftstart); VectorAdd(end, mins, shiftend); if (VectorCompare(start, end)) trace = SV_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, skipsupercontentsmask); else trace = SV_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, skipsupercontentsmask, extend); VectorSubtract(trace.endpos, mins, trace.endpos); return trace; } VectorCopy(start, clipstart); VectorCopy(end, clipend); VectorCopy(mins, clipmins); VectorCopy(maxs, clipmaxs); VectorCopy(mins, clipmins2); VectorCopy(maxs, clipmaxs2); #if COLLISIONPARANOID >= 3 Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]); #endif // clip to world Collision_ClipToWorld(&cliptrace, sv.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid; if (cliptrace.startsolid || cliptrace.fraction < 1) cliptrace.ent = prog->edicts; if (type == MOVE_WORLDONLY) goto finished; if (type == MOVE_MISSILE) { // LordHavoc: modified this, was = -15, now -= 15 for (i = 0;i < 3;i++) { clipmins2[i] -= 15; clipmaxs2[i] += 15; } } // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp if (sv.worldmodel && sv.worldmodel->brush.RoundUpToHullSize) sv.worldmodel->brush.RoundUpToHullSize(sv.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs); else { VectorCopy(clipmins, hullmins); VectorCopy(clipmaxs, hullmaxs); } // create the bounding box of the entire move for (i = 0;i < 3;i++) { clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1; clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1; } // debug override to test against everything if (sv_debugmove.integer) { clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999; clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999; } // if the passedict is world, make it NULL (to avoid two checks each time) if (passedict == prog->edicts) passedict = NULL; // precalculate prog value for passedict for comparisons passedictprog = PRVM_EDICT_TO_PROG(passedict); // figure out whether this is a point trace for comparisons pointtrace = VectorCompare(clipmins, clipmaxs); // precalculate passedict's owner edict pointer for comparisons traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_serveredictedict(passedict, owner)) : 0; // clip to entities // because this uses World_EntitiestoBox, we know all entity boxes overlap // the clip region, so we can skip culling checks in the loop below numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (PRVM_serveredictfloat(touch, solid) < SOLID_BBOX) continue; if (type == MOVE_NOMONSTERS && PRVM_serveredictfloat(touch, solid) != SOLID_BSP) continue; if (passedict) { // don't clip against self if (passedict == touch) continue; // don't clip owned entities against owner if (traceowner == touch) continue; // don't clip owner against owned entities if (passedictprog == PRVM_serveredictedict(touch, owner)) continue; // don't clip points against points (they can't collide) if (pointtrace && VectorCompare(PRVM_serveredictvector(touch, mins), PRVM_serveredictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER))) continue; } bodysupercontents = PRVM_serveredictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY; // might interact, so do an exact clip model = NULL; if ((int) PRVM_serveredictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL) { model = SV_GetModelFromEdict(touch); pitchsign = SV_GetPitchSign(prog, touch); } if (model) Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); else Matrix4x4_CreateTranslate(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2]); Matrix4x4_Invert_Simple(&imatrix, &matrix); VM_GenerateFrameGroupBlend(prog, touch->priv.server->framegroupblend, touch); VM_FrameBlendFromFrameGroupBlend(touch->priv.server->frameblend, touch->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, touch, model, touch->priv.server->frameblend); VectorCopy(PRVM_serveredictvector(touch, mins), touchmins); VectorCopy(PRVM_serveredictvector(touch, maxs), touchmaxs); if (type == MOVE_MISSILE && (int)PRVM_serveredictfloat(touch, flags) & FL_MONSTER) Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); else Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask, skipsupercontentsmask, extend); Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_serveredictfloat(touch, solid) == SOLID_BSP); } finished: return cliptrace; } #if COLLISIONPARANOID >= 1 trace_t SV_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, int skipsupercontentsmask) { prvm_prog_t *prog = SVVM_prog; int endstuck; trace_t trace; vec3_t temp; trace = SV_TraceBox_(start, mins, maxs, end, type, passedict, hitsupercontentsmask, skipsupercontentsmask); if (passedict) { VectorCopy(trace.endpos, temp); endstuck = SV_TraceBox_(temp, mins, maxs, temp, type, passedict, hitsupercontentsmask, skipsupercontentsmask).startsolid; #if COLLISIONPARANOID < 3 if (trace.startsolid || endstuck) #endif Con_Printf("%s{e%i:%f %f %f:%f %f %f:%f:%f %f %f%s%s}\n", (trace.startsolid || endstuck) ? "^3" : "", passedict ? (int)(passedict - prog->edicts) : -1, PRVM_serveredictvector(passedict, origin)[0], PRVM_serveredictvector(passedict, origin)[1], PRVM_serveredictvector(passedict, origin)[2], end[0] - PRVM_serveredictvector(passedict, origin)[0], end[1] - PRVM_serveredictvector(passedict, origin)[1], end[2] - PRVM_serveredictvector(passedict, origin)[2], trace.fraction, trace.endpos[0] - PRVM_serveredictvector(passedict, origin)[0], trace.endpos[1] - PRVM_serveredictvector(passedict, origin)[1], trace.endpos[2] - PRVM_serveredictvector(passedict, origin)[2], trace.startsolid ? " startstuck" : "", endstuck ? " endstuck" : ""); } return trace; } #endif int SV_PointSuperContents(const vec3_t point) { prvm_prog_t *prog = SVVM_prog; int supercontents = 0; int i; prvm_edict_t *touch; vec3_t transformed; // matrices to transform into/out of other entity's space matrix4x4_t matrix, imatrix; // model of other entity dp_model_t *model; int frame; // list of entities to test for collisions int numtouchedicts; static prvm_edict_t *touchedicts[MAX_EDICTS]; // get world supercontents at this point if (sv.worldmodel && sv.worldmodel->PointSuperContents) supercontents = sv.worldmodel->PointSuperContents(sv.worldmodel, 0, point); // if sv_gameplayfix_swiminbmodels is off we're done if (!sv_gameplayfix_swiminbmodels.integer) return supercontents; // get list of entities at this point numtouchedicts = SV_EntitiesInBox(point, point, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; // we only care about SOLID_BSP for pointcontents if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) continue; // might interact, so do an exact clip model = SV_GetModelFromEdict(touch); if (!model || !model->PointSuperContents) continue; Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); Matrix4x4_Invert_Simple(&imatrix, &matrix); Matrix4x4_Transform(&imatrix, point, transformed); frame = (int)PRVM_serveredictfloat(touch, frame); supercontents |= model->PointSuperContents(model, bound(0, frame, (model->numframes - 1)), transformed); } return supercontents; } /* =============================================================================== Linking entities into the world culling system =============================================================================== */ int SV_EntitiesInBox(const vec3_t mins, const vec3_t maxs, int maxedicts, prvm_edict_t **resultedicts) { prvm_prog_t *prog = SVVM_prog; vec3_t paddedmins, paddedmaxs; if (maxedicts < 1 || resultedicts == NULL) return 0; // LordHavoc: discovered this actually causes its own bugs (dm6 teleporters being too close to info_teleport_destination) //VectorSet(paddedmins, mins[0] - 10, mins[1] - 10, mins[2] - 1); //VectorSet(paddedmaxs, maxs[0] + 10, maxs[1] + 10, maxs[2] + 1); VectorCopy(mins, paddedmins); VectorCopy(maxs, paddedmaxs); if (sv_areadebug.integer) { int numresultedicts = 0; int edictindex; prvm_edict_t *ed; for (edictindex = 1;edictindex < prog->num_edicts;edictindex++) { ed = PRVM_EDICT_NUM(edictindex); if (!ed->priv.required->free && BoxesOverlap(PRVM_serveredictvector(ed, absmin), PRVM_serveredictvector(ed, absmax), paddedmins, paddedmaxs)) { resultedicts[numresultedicts++] = ed; if (numresultedicts == maxedicts) break; } } return numresultedicts; } else return World_EntitiesInBox(&sv.world, paddedmins, paddedmaxs, maxedicts, resultedicts); } void SV_LinkEdict_TouchAreaGrid_Call(prvm_edict_t *touch, prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(touch); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(ent); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobalfloat(trace_allsolid) = false; PRVM_serverglobalfloat(trace_startsolid) = false; PRVM_serverglobalfloat(trace_fraction) = 1; PRVM_serverglobalfloat(trace_inwater) = false; PRVM_serverglobalfloat(trace_inopen) = true; VectorCopy (PRVM_serveredictvector(touch, origin), PRVM_serverglobalvector(trace_endpos)); VectorSet (PRVM_serverglobalvector(trace_plane_normal), 0, 0, 1); PRVM_serverglobalfloat(trace_plane_dist) = 0; PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(ent); PRVM_serverglobalfloat(trace_dpstartcontents) = 0; PRVM_serverglobalfloat(trace_dphitcontents) = 0; PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; PRVM_serverglobalstring(trace_dphittexturename) = 0; prog->ExecuteProgram(prog, PRVM_serveredictfunction(touch, touch), "QC function self.touch is missing"); } void SV_LinkEdict_TouchAreaGrid(prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int i, numtouchedicts, old_self, old_other; prvm_edict_t *touch; static prvm_edict_t *touchedicts[MAX_EDICTS]; if (ent == prog->edicts) return; // don't add the world if (ent->priv.server->free) return; if (PRVM_serveredictfloat(ent, solid) == SOLID_NOT) return; // build a list of edicts to touch, because the link loop can be corrupted // by IncreaseEdicts called during touch functions numtouchedicts = SV_EntitiesInBox(ent->priv.server->areamins, ent->priv.server->areamaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } old_self = PRVM_serverglobaledict(self); old_other = PRVM_serverglobaledict(other); for (i = 0;i < numtouchedicts;i++) { touch = touchedicts[i]; if (touch != ent && (int)PRVM_serveredictfloat(touch, solid) == SOLID_TRIGGER && PRVM_serveredictfunction(touch, touch)) { SV_LinkEdict_TouchAreaGrid_Call(touch, ent); } } PRVM_serverglobaledict(self) = old_self; PRVM_serverglobaledict(other) = old_other; } static void RotateBBox(const vec3_t mins, const vec3_t maxs, const vec3_t angles, vec3_t rotatedmins, vec3_t rotatedmaxs) { vec3_t v, u; matrix4x4_t m; Matrix4x4_CreateFromQuakeEntity(&m, 0, 0, 0, angles[PITCH], angles[YAW], angles[ROLL], 1.0); v[0] = mins[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); VectorCopy(u, rotatedmins); VectorCopy(u, rotatedmaxs); v[0] = maxs[0]; v[1] = mins[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = mins[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = maxs[0]; v[1] = maxs[1]; v[2] = mins[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = mins[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = maxs[0]; v[1] = mins[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = mins[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; v[0] = maxs[0]; v[1] = maxs[1]; v[2] = maxs[2]; Matrix4x4_Transform(&m, v, u); if(rotatedmins[0] > u[0]) rotatedmins[0] = u[0]; if(rotatedmins[1] > u[1]) rotatedmins[1] = u[1]; if(rotatedmins[2] > u[2]) rotatedmins[2] = u[2]; if(rotatedmaxs[0] < u[0]) rotatedmaxs[0] = u[0]; if(rotatedmaxs[1] < u[1]) rotatedmaxs[1] = u[1]; if(rotatedmaxs[2] < u[2]) rotatedmaxs[2] = u[2]; } /* =============== SV_LinkEdict =============== */ void SV_LinkEdict (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; dp_model_t *model; vec3_t mins, maxs, entmins, entmaxs, entangles; int modelindex; if (ent == prog->edicts) return; // don't add the world if (ent->priv.server->free) return; modelindex = (int)PRVM_serveredictfloat(ent, modelindex); if (modelindex < 0 || modelindex >= MAX_MODELS) { Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent)); modelindex = 0; } model = SV_GetModelByIndex(modelindex); VM_GenerateFrameGroupBlend(prog, ent->priv.server->framegroupblend, ent); VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model, sv.time); VM_UpdateEdictSkeleton(prog, ent, model, ent->priv.server->frameblend); // set the abs box if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_PHYSICS) { // TODO maybe should do this for rotating SOLID_BSP too? Would behave better with rotating doors // TODO special handling for spheres? VectorCopy(PRVM_serveredictvector(ent, mins), entmins); VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); VectorCopy(PRVM_serveredictvector(ent, angles), entangles); RotateBBox(entmins, entmaxs, entangles, mins, maxs); VectorAdd(PRVM_serveredictvector(ent, origin), mins, mins); VectorAdd(PRVM_serveredictvector(ent, origin), maxs, maxs); } else if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) { if (model != NULL) { if (!model->TraceBox) Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent)); if (PRVM_serveredictvector(ent, angles)[0] || PRVM_serveredictvector(ent, angles)[2] || PRVM_serveredictvector(ent, avelocity)[0] || PRVM_serveredictvector(ent, avelocity)[2]) { VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmins, mins); VectorAdd(PRVM_serveredictvector(ent, origin), model->rotatedmaxs, maxs); } else if (PRVM_serveredictvector(ent, angles)[1] || PRVM_serveredictvector(ent, avelocity)[1]) { VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmins, mins); VectorAdd(PRVM_serveredictvector(ent, origin), model->yawmaxs, maxs); } else { VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmins, mins); VectorAdd(PRVM_serveredictvector(ent, origin), model->normalmaxs, maxs); } } else { // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); } } else { VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), mins); VectorAdd(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, maxs), maxs); } // // to make items easier to pick up and allow them to be grabbed off // of shelves, the abs sizes are expanded // if ((int)PRVM_serveredictfloat(ent, flags) & FL_ITEM) { mins[0] -= 15; mins[1] -= 15; mins[2] -= 1; maxs[0] += 15; maxs[1] += 15; maxs[2] += 1; } else { // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch mins[0] -= 1; mins[1] -= 1; mins[2] -= 1; maxs[0] += 1; maxs[1] += 1; maxs[2] += 1; } VectorCopy(mins, PRVM_serveredictvector(ent, absmin)); VectorCopy(maxs, PRVM_serveredictvector(ent, absmax)); World_LinkEdict(&sv.world, ent, mins, maxs); } /* =============================================================================== Utility functions =============================================================================== */ /* ============ SV_TestEntityPosition returns true if the entity is in solid currently ============ */ static int SV_TestEntityPosition (prvm_edict_t *ent, vec3_t offset) { prvm_prog_t *prog = SVVM_prog; int contents; vec3_t org, entorigin, entmins, entmaxs; trace_t trace; contents = SV_GenericHitSuperContentsMask(ent); VectorAdd(PRVM_serveredictvector(ent, origin), offset, org); VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); VectorCopy(PRVM_serveredictvector(ent, mins), entmins); VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); trace = SV_TraceBox(org, entmins, entmaxs, entorigin, ((PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) ? MOVE_WORLDONLY : MOVE_NOMONSTERS), ent, contents, 0, collision_extendmovelength.value); if (trace.startsupercontents & contents) return true; else { if (sv.worldmodel->brushq1.numclipnodes && !VectorCompare(PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs))) { // q1bsp/hlbsp use hulls and if the entity does not exactly match // a hull size it is incorrectly tested, so this code tries to // 'fix' it slightly... // FIXME: this breaks entities larger than the hull size int i; vec3_t v, m1, m2, s; VectorAdd(org, entmins, m1); VectorAdd(org, entmaxs, m2); VectorSubtract(m2, m1, s); #define EPSILON (1.0f / 32.0f) if (s[0] >= EPSILON*2) {m1[0] += EPSILON;m2[0] -= EPSILON;} if (s[1] >= EPSILON*2) {m1[1] += EPSILON;m2[1] -= EPSILON;} if (s[2] >= EPSILON*2) {m1[2] += EPSILON;m2[2] -= EPSILON;} for (i = 0;i < 8;i++) { v[0] = (i & 1) ? m2[0] : m1[0]; v[1] = (i & 2) ? m2[1] : m1[1]; v[2] = (i & 4) ? m2[2] : m1[2]; if (SV_PointSuperContents(v) & contents) return true; } } } // if the trace found a better position for the entity, move it there if (VectorDistance2(trace.endpos, PRVM_serveredictvector(ent, origin)) >= 0.0001) { #if 0 // please switch back to this code when trace.endpos sometimes being in solid bug is fixed VectorCopy(trace.endpos, PRVM_serveredictvector(ent, origin)); #else // verify if the endpos is REALLY outside solid VectorCopy(trace.endpos, org); trace = SV_TraceBox(org, entmins, entmaxs, org, MOVE_NOMONSTERS, ent, contents, 0, collision_extendmovelength.value); if(trace.startsolid) Con_Printf("SV_TestEntityPosition: trace.endpos detected to be in solid. NOT using it.\n"); else VectorCopy(org, PRVM_serveredictvector(ent, origin)); #endif } return false; } // DRESK - Support for Entity Contents Transition Event /* ================ SV_CheckContentsTransition returns true if entity had a valid contentstransition function call ================ */ static int SV_CheckContentsTransition(prvm_edict_t *ent, const int nContents) { prvm_prog_t *prog = SVVM_prog; int bValidFunctionCall; // Default Valid Function Call to False bValidFunctionCall = false; if(PRVM_serveredictfloat(ent, watertype) != nContents) { // Changed Contents // Acquire Contents Transition Function from QC if(PRVM_serveredictfunction(ent, contentstransition)) { // Valid Function; Execute // Assign Valid Function bValidFunctionCall = true; // Prepare Parameters (Original Contents, New Contents) // Original Contents PRVM_G_FLOAT(OFS_PARM0) = PRVM_serveredictfloat(ent, watertype); // New Contents PRVM_G_FLOAT(OFS_PARM1) = nContents; // Assign Self PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); // Set Time PRVM_serverglobalfloat(time) = sv.time; // Execute VM Function prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, contentstransition), "contentstransition: NULL function"); } } // Return if Function Call was Valid return bValidFunctionCall; } /* ================ SV_CheckVelocity ================ */ void SV_CheckVelocity (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int i; float wishspeed; // // bound velocity // for (i=0 ; i<3 ; i++) { if (PRVM_IS_NAN(PRVM_serveredictvector(ent, velocity)[i])) { Con_Printf("Got a NaN velocity on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); PRVM_serveredictvector(ent, velocity)[i] = 0; } if (PRVM_IS_NAN(PRVM_serveredictvector(ent, origin)[i])) { Con_Printf("Got a NaN origin on entity #%i (%s)\n", PRVM_NUM_FOR_EDICT(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); PRVM_serveredictvector(ent, origin)[i] = 0; } } // LordHavoc: a hack to ensure that the (rather silly) id1 quakec // player_run/player_stand1 does not horribly malfunction if the // velocity becomes a denormalized float if (VectorLength2(PRVM_serveredictvector(ent, velocity)) < 0.0001) VectorClear(PRVM_serveredictvector(ent, velocity)); // LordHavoc: max velocity fix, inspired by Maddes's source fixes, but this is faster wishspeed = DotProduct(PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, velocity)); if (wishspeed > sv_maxvelocity.value * sv_maxvelocity.value) { wishspeed = sv_maxvelocity.value / sqrt(wishspeed); PRVM_serveredictvector(ent, velocity)[0] *= wishspeed; PRVM_serveredictvector(ent, velocity)[1] *= wishspeed; PRVM_serveredictvector(ent, velocity)[2] *= wishspeed; } } /* ============= SV_RunThink Runs thinking code if time. There is some play in the exact time the think function will be called, because it is called before any movement is done in a frame. Not used for pushmove objects, because they must be exact. Returns false if the entity removed itself. ============= */ static qboolean SV_RunThink (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int iterations; // don't let things stay in the past. // it is possible to start that way by a trigger with a local time. if (PRVM_serveredictfloat(ent, nextthink) <= 0 || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime) return true; for (iterations = 0;iterations < 128 && !ent->priv.server->free;iterations++) { PRVM_serverglobalfloat(time) = max(sv.time, PRVM_serveredictfloat(ent, nextthink)); PRVM_serveredictfloat(ent, nextthink) = 0; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); // mods often set nextthink to time to cause a think every frame, // we don't want to loop in that case, so exit if the new nextthink is // <= the time the qc was told, also exit if it is past the end of the // frame if (PRVM_serveredictfloat(ent, nextthink) <= PRVM_serverglobalfloat(time) || PRVM_serveredictfloat(ent, nextthink) > sv.time + sv.frametime || !sv_gameplayfix_multiplethinksperframe.integer) break; } return !ent->priv.server->free; } /* ================== SV_Impact Two entities have touched, so run their touch functions ================== */ static void SV_Impact (prvm_edict_t *e1, trace_t *trace) { prvm_prog_t *prog = SVVM_prog; int restorevm_tempstringsbuf_cursize; int old_self, old_other; prvm_edict_t *e2 = (prvm_edict_t *)trace->ent; old_self = PRVM_serverglobaledict(self); old_other = PRVM_serverglobaledict(other); restorevm_tempstringsbuf_cursize = prog->tempstringsbuf.cursize; VM_SetTraceGlobals(prog, trace); if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e1, touch) && PRVM_serveredictfloat(e1, solid) != SOLID_NOT) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e1); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e2); prog->ExecuteProgram(prog, PRVM_serveredictfunction(e1, touch), "QC function self.touch is missing"); } if (!e1->priv.server->free && !e2->priv.server->free && PRVM_serveredictfunction(e2, touch) && PRVM_serveredictfloat(e2, solid) != SOLID_NOT) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(e2); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(e1); VectorCopy(PRVM_serveredictvector(e2, origin), PRVM_serverglobalvector(trace_endpos)); VectorNegate(trace->plane.normal, PRVM_serverglobalvector(trace_plane_normal)); PRVM_serverglobalfloat(trace_plane_dist) = -trace->plane.dist; PRVM_serverglobaledict(trace_ent) = PRVM_EDICT_TO_PROG(e1); PRVM_serverglobalfloat(trace_dpstartcontents) = 0; PRVM_serverglobalfloat(trace_dphitcontents) = 0; PRVM_serverglobalfloat(trace_dphitq3surfaceflags) = 0; PRVM_serverglobalstring(trace_dphittexturename) = 0; prog->ExecuteProgram(prog, PRVM_serveredictfunction(e2, touch), "QC function self.touch is missing"); } PRVM_serverglobaledict(self) = old_self; PRVM_serverglobaledict(other) = old_other; prog->tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize; } /* ================== ClipVelocity Slide off of the impacting object returns the blocked flags (1 = floor, 2 = step / wall) ================== */ #define STOP_EPSILON 0.1 static void ClipVelocity (prvm_vec3_t in, vec3_t normal, prvm_vec3_t out, prvm_vec_t overbounce) { int i; float backoff; backoff = -DotProduct (in, normal) * overbounce; VectorMA(in, backoff, normal, out); for (i = 0;i < 3;i++) if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) out[i] = 0; } /* ============ SV_FlyMove The basic solid body movement clip that slides along multiple planes Returns the clipflags if the velocity was modified (hit something solid) 1 = floor 2 = wall / step 4 = dead stop 8 = teleported by touch method If stepnormal is not NULL, the plane normal of any vertical wall hit will be stored ============ */ static float SV_Gravity (prvm_edict_t *ent); static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean dolink); #define MAX_CLIP_PLANES 5 static int SV_FlyMove (prvm_edict_t *ent, float time, qboolean applygravity, float *stepnormal, int hitsupercontentsmask, int skipsupercontentsmask, float stepheight) { prvm_prog_t *prog = SVVM_prog; int blocked, bumpcount; int i, j, numplanes; float d, time_left, gravity; vec3_t dir, push, planes[MAX_CLIP_PLANES]; prvm_vec3_t primal_velocity, original_velocity, new_velocity, restore_velocity; #if 0 vec3_t end; #endif trace_t trace; if (time <= 0) return 0; gravity = 0; VectorCopy(PRVM_serveredictvector(ent, velocity), restore_velocity); if(applygravity) { gravity = SV_Gravity(ent); if(!sv_gameplayfix_nogravityonground.integer || !((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) { if (sv_gameplayfix_gravityunaffectedbyticrate.integer) PRVM_serveredictvector(ent, velocity)[2] -= gravity * 0.5f; else PRVM_serveredictvector(ent, velocity)[2] -= gravity; } } blocked = 0; VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); VectorCopy(PRVM_serveredictvector(ent, velocity), primal_velocity); numplanes = 0; time_left = time; for (bumpcount = 0;bumpcount < MAX_CLIP_PLANES;bumpcount++) { if (!PRVM_serveredictvector(ent, velocity)[0] && !PRVM_serveredictvector(ent, velocity)[1] && !PRVM_serveredictvector(ent, velocity)[2]) break; VectorScale(PRVM_serveredictvector(ent, velocity), time_left, push); if(!SV_PushEntity(&trace, ent, push, false)) { // we got teleported by a touch function // let's abort the move blocked |= 8; break; } // this code is used by MOVETYPE_WALK and MOVETYPE_STEP and SV_UnstickEntity // abort move if we're stuck in the world (and didn't make it out) if (trace.worldstartsolid && trace.allsolid) { VectorCopy(restore_velocity, PRVM_serveredictvector(ent, velocity)); return 3; } if (trace.fraction == 1) break; if (trace.plane.normal[2]) { if (trace.plane.normal[2] > 0.7) { // floor blocked |= 1; if (!trace.ent) { Con_Printf ("SV_FlyMove: !trace.ent"); trace.ent = prog->edicts; } PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); } } else if (stepheight) { // step - handle it immediately vec3_t org; vec3_t steppush; trace_t steptrace; trace_t steptrace2; trace_t steptrace3; //Con_Printf("step %f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); VectorSet(steppush, 0, 0, stepheight); VectorCopy(PRVM_serveredictvector(ent, origin), org); if(!SV_PushEntity(&steptrace, ent, steppush, false)) { blocked |= 8; break; } //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); if(!SV_PushEntity(&steptrace2, ent, push, false)) { blocked |= 8; break; } //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); VectorSet(steppush, 0, 0, org[2] - PRVM_serveredictvector(ent, origin)[2]); if(!SV_PushEntity(&steptrace3, ent, steppush, false)) { blocked |= 8; break; } //Con_Printf("%f %f %f : ", PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2]); // accept the new position if it made some progress... if (fabs(PRVM_serveredictvector(ent, origin)[0] - org[0]) >= 0.03125 || fabs(PRVM_serveredictvector(ent, origin)[1] - org[1]) >= 0.03125) { //Con_Printf("accepted (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); trace = steptrace2; VectorCopy(PRVM_serveredictvector(ent, origin), trace.endpos); time_left *= 1 - trace.fraction; numplanes = 0; continue; } else { //Con_Printf("REJECTED (delta %f %f %f)\n", PRVM_serveredictvector(ent, origin)[0] - org[0], PRVM_serveredictvector(ent, origin)[1] - org[1], PRVM_serveredictvector(ent, origin)[2] - org[2]); VectorCopy(org, PRVM_serveredictvector(ent, origin)); } } else { // step - return it to caller blocked |= 2; // save the trace for player extrafriction if (stepnormal) VectorCopy(trace.plane.normal, stepnormal); } if (trace.fraction >= 0.001) { // actually covered some distance VectorCopy(PRVM_serveredictvector(ent, velocity), original_velocity); numplanes = 0; } time_left *= 1 - trace.fraction; // clipped to another plane if (numplanes >= MAX_CLIP_PLANES) { // this shouldn't really happen VectorClear(PRVM_serveredictvector(ent, velocity)); blocked = 3; break; } /* for (i = 0;i < numplanes;i++) if (DotProduct(trace.plane.normal, planes[i]) > 0.99) break; if (i < numplanes) { VectorAdd(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity)); continue; } */ VectorCopy(trace.plane.normal, planes[numplanes]); numplanes++; // modify original_velocity so it parallels all of the clip planes for (i = 0;i < numplanes;i++) { ClipVelocity(original_velocity, planes[i], new_velocity, 1); for (j = 0;j < numplanes;j++) { if (j != i) { // not ok if (DotProduct(new_velocity, planes[j]) < 0) break; } } if (j == numplanes) break; } if (i != numplanes) { // go along this plane VectorCopy(new_velocity, PRVM_serveredictvector(ent, velocity)); } else { // go along the crease if (numplanes != 2) { VectorClear(PRVM_serveredictvector(ent, velocity)); blocked = 7; break; } CrossProduct(planes[0], planes[1], dir); // LordHavoc: thanks to taniwha of QuakeForge for pointing out this fix for slowed falling in corners VectorNormalize(dir); d = DotProduct(dir, PRVM_serveredictvector(ent, velocity)); VectorScale(dir, d, PRVM_serveredictvector(ent, velocity)); } // if current velocity is against the original velocity, // stop dead to avoid tiny occilations in sloping corners if (DotProduct(PRVM_serveredictvector(ent, velocity), primal_velocity) <= 0) { VectorClear(PRVM_serveredictvector(ent, velocity)); break; } } //Con_Printf("entity %i final: blocked %i velocity %f %f %f\n", ent - prog->edicts, blocked, PRVM_serveredictvector(ent, velocity)[0], PRVM_serveredictvector(ent, velocity)[1], PRVM_serveredictvector(ent, velocity)[2]); /* if ((blocked & 1) == 0 && bumpcount > 1) { // LordHavoc: fix the 'fall to your death in a wedge corner' glitch // flag ONGROUND if there's ground under it trace = SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), end, MOVE_NORMAL, ent, hitsupercontentsmask, skipsupercontentsmask); } */ // LordHavoc: this came from QW and allows you to get out of water more easily if (sv_gameplayfix_easierwaterjump.integer && ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) && !(blocked & 8)) VectorCopy(primal_velocity, PRVM_serveredictvector(ent, velocity)); if(applygravity) { if(!sv_gameplayfix_nogravityonground.integer || !((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) { if (sv_gameplayfix_gravityunaffectedbyticrate.integer) PRVM_serveredictvector(ent, velocity)[2] -= gravity * 0.5f; } } return blocked; } /* ============ SV_Gravity ============ */ static float SV_Gravity (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; float ent_gravity; ent_gravity = PRVM_serveredictfloat(ent, gravity); if (!ent_gravity) ent_gravity = 1.0f; return ent_gravity * sv_gravity.value * sv.frametime; } /* =============================================================================== PUSHMOVE =============================================================================== */ static qboolean SV_NudgeOutOfSolid_PivotIsKnownGood(prvm_edict_t *ent, vec3_t pivot) { prvm_prog_t *prog = SVVM_prog; int bump; trace_t stucktrace; vec3_t stuckorigin; vec3_t stuckmins, stuckmaxs; vec3_t goodmins, goodmaxs; vec3_t testorigin; vec_t nudge; vec3_t move; VectorCopy(PRVM_serveredictvector(ent, origin), stuckorigin); VectorCopy(PRVM_serveredictvector(ent, mins), stuckmins); VectorCopy(PRVM_serveredictvector(ent, maxs), stuckmaxs); VectorCopy(pivot, goodmins); VectorCopy(pivot, goodmaxs); for (bump = 0;bump < 6;bump++) { int coord = 2-(bump >> 1); //int coord = (bump >> 1); int dir = (bump & 1); int subbump; for(subbump = 0; ; ++subbump) { VectorCopy(stuckorigin, testorigin); if(dir) { // pushing maxs testorigin[coord] += stuckmaxs[coord] - goodmaxs[coord]; } else { // pushing mins testorigin[coord] += stuckmins[coord] - goodmins[coord]; } stucktrace = SV_TraceBox(stuckorigin, goodmins, goodmaxs, testorigin, MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (stucktrace.bmodelstartsolid) { // BAD BAD, can't fix that return false; } if (stucktrace.fraction >= 1) break; // it WORKS! if(subbump >= 10) { // BAD BAD, can't fix that return false; } // we hit something... let's move out of it VectorSubtract(stucktrace.endpos, testorigin, move); nudge = DotProduct(stucktrace.plane.normal, move) + 0.03125f; // FIXME cvar this constant VectorMA(stuckorigin, nudge, stucktrace.plane.normal, stuckorigin); } /* if(subbump > 0) Con_Printf("subbump: %d\n", subbump); */ if(dir) { // pushing maxs goodmaxs[coord] = stuckmaxs[coord]; } else { // pushing mins goodmins[coord] = stuckmins[coord]; } } // WE WIN VectorCopy(stuckorigin, PRVM_serveredictvector(ent, origin)); return true; } qboolean SV_NudgeOutOfSolid(prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int bump, pass; trace_t stucktrace; vec3_t stuckorigin; vec3_t stuckmins, stuckmaxs; vec_t nudge; vec_t separation = sv_gameplayfix_nudgeoutofsolid_separation.value; if (sv.worldmodel && sv.worldmodel->brushq1.numclipnodes) separation = 0.0f; // when using hulls, it can not be enlarged VectorCopy(PRVM_serveredictvector(ent, mins), stuckmins); VectorCopy(PRVM_serveredictvector(ent, maxs), stuckmaxs); stuckmins[0] -= separation; stuckmins[1] -= separation; stuckmins[2] -= separation; stuckmaxs[0] += separation; stuckmaxs[1] += separation; stuckmaxs[2] += separation; // first pass we try to get it out of brush entities // second pass we try to get it out of world only (can't win them all) for (pass = 0;pass < 2;pass++) { VectorCopy(PRVM_serveredictvector(ent, origin), stuckorigin); for (bump = 0;bump < 10;bump++) { stucktrace = SV_TraceBox(stuckorigin, stuckmins, stuckmaxs, stuckorigin, pass ? MOVE_WORLDONLY : MOVE_NOMONSTERS, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); if (!stucktrace.bmodelstartsolid || stucktrace.startdepth >= 0) { // found a good location, use it VectorCopy(stuckorigin, PRVM_serveredictvector(ent, origin)); return true; } nudge = -stucktrace.startdepth; VectorMA(stuckorigin, nudge, stucktrace.startdepthnormal, stuckorigin); } } return false; } /* ============ SV_PushEntity Does not change the entities velocity at all The trace struct is filled with the trace that has been done. Returns true if the push did not result in the entity being teleported by QC code. ============ */ static qboolean SV_PushEntity (trace_t *trace, prvm_edict_t *ent, vec3_t push, qboolean dolink) { prvm_prog_t *prog = SVVM_prog; int solid; int movetype; int type; vec3_t mins, maxs; vec3_t start; vec3_t end; solid = (int)PRVM_serveredictfloat(ent, solid); movetype = (int)PRVM_serveredictfloat(ent, movetype); VectorCopy(PRVM_serveredictvector(ent, mins), mins); VectorCopy(PRVM_serveredictvector(ent, maxs), maxs); // move start position out of solids if (sv_gameplayfix_nudgeoutofsolid.integer && sv_gameplayfix_nudgeoutofsolid_separation.value >= 0) { SV_NudgeOutOfSolid(ent); } VectorCopy(PRVM_serveredictvector(ent, origin), start); VectorAdd(start, push, end); if (movetype == MOVETYPE_FLYMISSILE) type = MOVE_MISSILE; else if (movetype == MOVETYPE_FLY_WORLDONLY) type = MOVE_WORLDONLY; else if (solid == SOLID_TRIGGER || solid == SOLID_NOT) type = MOVE_NOMONSTERS; // only clip against bmodels else type = MOVE_NORMAL; *trace = SV_TraceBox(start, mins, maxs, end, type, ent, SV_GenericHitSuperContentsMask(ent), 0, collision_extendmovelength.value); // fail the move if stuck in world if (trace->worldstartsolid) return true; VectorCopy(trace->endpos, PRVM_serveredictvector(ent, origin)); ent->priv.required->mark = PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN; // -2: setorigin running SV_LinkEdict(ent); #if 0 if(!trace->startsolid) if(SV_TraceBox(PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, mins), PRVM_serveredictvector(ent, maxs), PRVM_serveredictvector(ent, origin), type, ent, SV_GenericHitSuperContentsMask(ent), 0).startsolid) { Con_Printf("something eeeeevil happened\n"); } #endif if (dolink) SV_LinkEdict_TouchAreaGrid(ent); if((PRVM_serveredictfloat(ent, solid) >= SOLID_TRIGGER && trace->ent && (!((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) || PRVM_serveredictedict(ent, groundentity) != PRVM_EDICT_TO_PROG(trace->ent)))) SV_Impact (ent, trace); if(ent->priv.required->mark == PRVM_EDICT_MARK_SETORIGIN_CAUGHT) { ent->priv.required->mark = 0; return false; } else if(ent->priv.required->mark == PRVM_EDICT_MARK_WAIT_FOR_SETORIGIN) { ent->priv.required->mark = 0; return true; } else { Con_Printf("The edict mark had been overwritten! Please debug this.\n"); return true; } } /* ============ SV_PushMove ============ */ static void SV_PushMove (prvm_edict_t *pusher, float movetime) { prvm_prog_t *prog = SVVM_prog; int i, e, index; int pusherowner, pusherprog; int checkcontents; qboolean rotated; float savesolid, movetime2, pushltime; vec3_t mins, maxs, move, move1, moveangle, pushorig, pushang, a, forward, left, up, org, pushermins, pushermaxs, checkorigin, checkmins, checkmaxs; int num_moved; int numcheckentities; static prvm_edict_t *checkentities[MAX_EDICTS]; dp_model_t *pushermodel; trace_t trace, trace2; matrix4x4_t pusherfinalmatrix, pusherfinalimatrix; static unsigned short moved_edicts[MAX_EDICTS]; vec3_t pivot; if (!PRVM_serveredictvector(pusher, velocity)[0] && !PRVM_serveredictvector(pusher, velocity)[1] && !PRVM_serveredictvector(pusher, velocity)[2] && !PRVM_serveredictvector(pusher, avelocity)[0] && !PRVM_serveredictvector(pusher, avelocity)[1] && !PRVM_serveredictvector(pusher, avelocity)[2]) { PRVM_serveredictfloat(pusher, ltime) += movetime; return; } switch ((int) PRVM_serveredictfloat(pusher, solid)) { // LordHavoc: valid pusher types case SOLID_BSP: case SOLID_BBOX: case SOLID_SLIDEBOX: case SOLID_CORPSE: // LordHavoc: this would be weird... break; // LordHavoc: no collisions case SOLID_NOT: case SOLID_TRIGGER: VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); PRVM_serveredictfloat(pusher, ltime) += movetime; SV_LinkEdict(pusher); return; default: Con_Printf("SV_PushMove: entity #%i, unrecognized solid type %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, solid)); return; } index = (int) PRVM_serveredictfloat(pusher, modelindex); if (index < 1 || index >= MAX_MODELS) { Con_Printf("SV_PushMove: entity #%i has an invalid modelindex %f\n", PRVM_NUM_FOR_EDICT(pusher), PRVM_serveredictfloat(pusher, modelindex)); return; } pushermodel = SV_GetModelByIndex(index); pusherowner = PRVM_serveredictedict(pusher, owner); pusherprog = PRVM_EDICT_TO_PROG(pusher); rotated = VectorLength2(PRVM_serveredictvector(pusher, angles)) + VectorLength2(PRVM_serveredictvector(pusher, avelocity)) > 0; movetime2 = movetime; VectorScale(PRVM_serveredictvector(pusher, velocity), movetime2, move1); VectorScale(PRVM_serveredictvector(pusher, avelocity), movetime2, moveangle); if (moveangle[0] || moveangle[2]) { for (i = 0;i < 3;i++) { if (move1[i] > 0) { mins[i] = pushermodel->rotatedmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->rotatedmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } else { mins[i] = pushermodel->rotatedmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->rotatedmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } } } else if (moveangle[1]) { for (i = 0;i < 3;i++) { if (move1[i] > 0) { mins[i] = pushermodel->yawmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->yawmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } else { mins[i] = pushermodel->yawmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->yawmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } } } else { for (i = 0;i < 3;i++) { if (move1[i] > 0) { mins[i] = pushermodel->normalmins[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->normalmaxs[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } else { mins[i] = pushermodel->normalmins[i] + move1[i] + PRVM_serveredictvector(pusher, origin)[i] - 1; maxs[i] = pushermodel->normalmaxs[i] + PRVM_serveredictvector(pusher, origin)[i] + 1; } } } VectorNegate (moveangle, a); AngleVectorsFLU (a, forward, left, up); VectorCopy (PRVM_serveredictvector(pusher, origin), pushorig); VectorCopy (PRVM_serveredictvector(pusher, angles), pushang); pushltime = PRVM_serveredictfloat(pusher, ltime); // move the pusher to its final position VectorMA (PRVM_serveredictvector(pusher, origin), movetime, PRVM_serveredictvector(pusher, velocity), PRVM_serveredictvector(pusher, origin)); VectorMA (PRVM_serveredictvector(pusher, angles), movetime, PRVM_serveredictvector(pusher, avelocity), PRVM_serveredictvector(pusher, angles)); PRVM_serveredictfloat(pusher, ltime) += movetime; SV_LinkEdict(pusher); pushermodel = SV_GetModelFromEdict(pusher); Matrix4x4_CreateFromQuakeEntity(&pusherfinalmatrix, PRVM_serveredictvector(pusher, origin)[0], PRVM_serveredictvector(pusher, origin)[1], PRVM_serveredictvector(pusher, origin)[2], PRVM_serveredictvector(pusher, angles)[0], PRVM_serveredictvector(pusher, angles)[1], PRVM_serveredictvector(pusher, angles)[2], 1); Matrix4x4_Invert_Simple(&pusherfinalimatrix, &pusherfinalmatrix); savesolid = PRVM_serveredictfloat(pusher, solid); // see if any solid entities are inside the final position num_moved = 0; if (PRVM_serveredictfloat(pusher, movetype) == MOVETYPE_FAKEPUSH) // Tenebrae's MOVETYPE_PUSH variant that doesn't push... numcheckentities = 0; else // MOVETYPE_PUSH numcheckentities = SV_EntitiesInBox(mins, maxs, MAX_EDICTS, checkentities); for (e = 0;e < numcheckentities;e++) { prvm_edict_t *check = checkentities[e]; int movetype = (int)PRVM_serveredictfloat(check, movetype); switch(movetype) { case MOVETYPE_NONE: case MOVETYPE_PUSH: case MOVETYPE_FOLLOW: case MOVETYPE_NOCLIP: case MOVETYPE_FLY_WORLDONLY: continue; default: break; } if (PRVM_serveredictedict(check, owner) == pusherprog) continue; if (pusherowner == PRVM_EDICT_TO_PROG(check)) continue; //Con_Printf("%i %s ", PRVM_NUM_FOR_EDICT(check), PRVM_GetString(PRVM_serveredictstring(check, classname))); // tell any MOVETYPE_STEP entity that it may need to check for water transitions check->priv.server->waterposition_forceupdate = true; checkcontents = SV_GenericHitSuperContentsMask(check); // if the entity is standing on the pusher, it will definitely be moved // if the entity is not standing on the pusher, but is in the pusher's // final position, move it if (!((int)PRVM_serveredictfloat(check, flags) & FL_ONGROUND) || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher) { VectorCopy(PRVM_serveredictvector(pusher, mins), pushermins); VectorCopy(PRVM_serveredictvector(pusher, maxs), pushermaxs); VectorCopy(PRVM_serveredictvector(check, origin), checkorigin); VectorCopy(PRVM_serveredictvector(check, mins), checkmins); VectorCopy(PRVM_serveredictvector(check, maxs), checkmaxs); Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, pushermins, pushermaxs, SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, checkorigin, checkmins, checkmaxs, checkorigin, checkcontents, 0, collision_extendmovelength.value); //trace = SV_TraceBox(PRVM_serveredictvector(check, origin), PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs), PRVM_serveredictvector(check, origin), MOVE_NOMONSTERS, check, checkcontents); if (!trace.startsolid) { //Con_Printf("- not in solid\n"); continue; } } VectorLerp(PRVM_serveredictvector(check, mins), 0.5f, PRVM_serveredictvector(check, maxs), pivot); //VectorClear(pivot); if (rotated) { vec3_t org2; VectorSubtract (PRVM_serveredictvector(check, origin), PRVM_serveredictvector(pusher, origin), org); VectorAdd (org, pivot, org); org2[0] = DotProduct (org, forward); org2[1] = DotProduct (org, left); org2[2] = DotProduct (org, up); VectorSubtract (org2, org, move); VectorAdd (move, move1, move); } else VectorCopy (move1, move); //Con_Printf("- pushing %f %f %f\n", move[0], move[1], move[2]); VectorCopy (PRVM_serveredictvector(check, origin), check->priv.server->moved_from); VectorCopy (PRVM_serveredictvector(check, angles), check->priv.server->moved_fromangles); moved_edicts[num_moved++] = PRVM_NUM_FOR_EDICT(check); // physics objects need better collisions than this code can do if (movetype == MOVETYPE_PHYSICS) { VectorAdd(PRVM_serveredictvector(check, origin), move, PRVM_serveredictvector(check, origin)); SV_LinkEdict(check); SV_LinkEdict_TouchAreaGrid(check); continue; } // try moving the contacted entity PRVM_serveredictfloat(pusher, solid) = SOLID_NOT; if(!SV_PushEntity (&trace, check, move, true)) { // entity "check" got teleported PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP continue; // pushed enough } // FIXME: turn players specially PRVM_serveredictvector(check, angles)[1] += trace.fraction * moveangle[1]; PRVM_serveredictfloat(pusher, solid) = savesolid; // was SOLID_BSP //Con_Printf("%s:%d frac %f startsolid %d bmodelstartsolid %d allsolid %d\n", __FILE__, __LINE__, trace.fraction, trace.startsolid, trace.bmodelstartsolid, trace.allsolid); // this trace.fraction < 1 check causes items to fall off of pushers // if they pass under or through a wall // the groundentity check causes items to fall off of ledges if (PRVM_serveredictfloat(check, movetype) != MOVETYPE_WALK && (trace.fraction < 1 || PRVM_PROG_TO_EDICT(PRVM_serveredictedict(check, groundentity)) != pusher)) PRVM_serveredictfloat(check, flags) = (int)PRVM_serveredictfloat(check, flags) & ~FL_ONGROUND; // if it is still inside the pusher, block VectorCopy(PRVM_serveredictvector(pusher, mins), pushermins); VectorCopy(PRVM_serveredictvector(pusher, maxs), pushermaxs); VectorCopy(PRVM_serveredictvector(check, origin), checkorigin); VectorCopy(PRVM_serveredictvector(check, mins), checkmins); VectorCopy(PRVM_serveredictvector(check, maxs), checkmaxs); Collision_ClipToGenericEntity(&trace, pushermodel, pusher->priv.server->frameblend, &pusher->priv.server->skeleton, pushermins, pushermaxs, SUPERCONTENTS_BODY, &pusherfinalmatrix, &pusherfinalimatrix, checkorigin, checkmins, checkmaxs, checkorigin, checkcontents, 0, collision_extendmovelength.value); if (trace.startsolid) { vec3_t move2; if(SV_NudgeOutOfSolid_PivotIsKnownGood(check, pivot)) { // hack to invoke all necessary movement triggers VectorClear(move2); if(!SV_PushEntity(&trace2, check, move2, true)) { // entity "check" got teleported continue; } // we could fix it continue; } // still inside pusher, so it's really blocked // fail the move if (PRVM_serveredictvector(check, mins)[0] == PRVM_serveredictvector(check, maxs)[0]) continue; if (PRVM_serveredictfloat(check, solid) == SOLID_NOT || PRVM_serveredictfloat(check, solid) == SOLID_TRIGGER) { // corpse PRVM_serveredictvector(check, mins)[0] = PRVM_serveredictvector(check, mins)[1] = 0; VectorCopy (PRVM_serveredictvector(check, mins), PRVM_serveredictvector(check, maxs)); continue; } VectorCopy (pushorig, PRVM_serveredictvector(pusher, origin)); VectorCopy (pushang, PRVM_serveredictvector(pusher, angles)); PRVM_serveredictfloat(pusher, ltime) = pushltime; SV_LinkEdict(pusher); // move back any entities we already moved for (i = 0;i < num_moved;i++) { prvm_edict_t *ed = PRVM_EDICT_NUM(moved_edicts[i]); VectorCopy (ed->priv.server->moved_from, PRVM_serveredictvector(ed, origin)); VectorCopy (ed->priv.server->moved_fromangles, PRVM_serveredictvector(ed, angles)); SV_LinkEdict(ed); } // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone if (PRVM_serveredictfunction(pusher, blocked)) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(pusher); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(check); prog->ExecuteProgram(prog, PRVM_serveredictfunction(pusher, blocked), "QC function self.blocked is missing"); } break; } } PRVM_serveredictvector(pusher, angles)[0] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[0] * (1.0 / 360.0)); PRVM_serveredictvector(pusher, angles)[1] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[1] * (1.0 / 360.0)); PRVM_serveredictvector(pusher, angles)[2] -= 360.0 * floor(PRVM_serveredictvector(pusher, angles)[2] * (1.0 / 360.0)); } /* ================ SV_Physics_Pusher ================ */ static void SV_Physics_Pusher (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; float thinktime, oldltime, movetime; oldltime = PRVM_serveredictfloat(ent, ltime); thinktime = PRVM_serveredictfloat(ent, nextthink); if (thinktime < PRVM_serveredictfloat(ent, ltime) + sv.frametime) { movetime = thinktime - PRVM_serveredictfloat(ent, ltime); if (movetime < 0) movetime = 0; } else movetime = sv.frametime; if (movetime) // advances PRVM_serveredictfloat(ent, ltime) if not blocked SV_PushMove (ent, movetime); if (thinktime > oldltime && thinktime <= PRVM_serveredictfloat(ent, ltime)) { PRVM_serveredictfloat(ent, nextthink) = 0; PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, think), "QC function self.think is missing"); } } /* =============================================================================== CLIENT MOVEMENT =============================================================================== */ static float unstickoffsets[] = { // poutting -/+z changes first as they are least weird 0, 0, -1, 0, 0, 1, // x or y changes -1, 0, 0, 1, 0, 0, 0, -1, 0, 0, 1, 0, // x and y changes -1, -1, 0, 1, -1, 0, -1, 1, 0, 1, 1, 0, }; typedef enum unstickresult_e { UNSTICK_STUCK = 0, UNSTICK_GOOD = 1, UNSTICK_UNSTUCK = 2 } unstickresult_t; static unstickresult_t SV_UnstickEntityReturnOffset (prvm_edict_t *ent, vec3_t offset) { prvm_prog_t *prog = SVVM_prog; int i, maxunstick; // if not stuck in a bmodel, just return if (!SV_TestEntityPosition(ent, vec3_origin)) return UNSTICK_GOOD; for (i = 0;i < (int)(sizeof(unstickoffsets) / sizeof(unstickoffsets[0]));i += 3) { if (!SV_TestEntityPosition(ent, unstickoffsets + i)) { VectorCopy(unstickoffsets + i, offset); SV_LinkEdict(ent); //SV_LinkEdict_TouchAreaGrid(ent); return UNSTICK_UNSTUCK; } } maxunstick = (int) ((PRVM_serveredictvector(ent, maxs)[2] - PRVM_serveredictvector(ent, mins)[2]) * 0.36); // magic number 0.36 allows unsticking by up to 17 units with the largest supported bbox for(i = 2; i <= maxunstick; ++i) { VectorClear(offset); offset[2] = -i; if (!SV_TestEntityPosition(ent, offset)) { SV_LinkEdict(ent); //SV_LinkEdict_TouchAreaGrid(ent); return UNSTICK_UNSTUCK; } offset[2] = i; if (!SV_TestEntityPosition(ent, offset)) { SV_LinkEdict(ent); //SV_LinkEdict_TouchAreaGrid(ent); return UNSTICK_UNSTUCK; } } return UNSTICK_STUCK; } qboolean SV_UnstickEntity (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; vec3_t offset; switch(SV_UnstickEntityReturnOffset(ent, offset)) { case UNSTICK_GOOD: return true; case UNSTICK_UNSTUCK: Con_DPrintf("Unstuck entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); return true; case UNSTICK_STUCK: if (developer_extra.integer) Con_DPrintf("Stuck entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); return false; default: Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); return false; } } /* ============= SV_CheckStuck This is a big hack to try and fix the rare case of getting stuck in the world clipping hull. ============= */ static void SV_CheckStuck (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; vec3_t offset; switch(SV_UnstickEntityReturnOffset(ent, offset)) { case UNSTICK_GOOD: VectorCopy (PRVM_serveredictvector(ent, origin), PRVM_serveredictvector(ent, oldorigin)); break; case UNSTICK_UNSTUCK: Con_DPrintf("Unstuck player entity %i (classname \"%s\") with offset %f %f %f.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname)), offset[0], offset[1], offset[2]); break; case UNSTICK_STUCK: VectorSubtract(PRVM_serveredictvector(ent, oldorigin), PRVM_serveredictvector(ent, origin), offset); if (!SV_TestEntityPosition(ent, offset)) { Con_DPrintf("Unstuck player entity %i (classname \"%s\") by restoring oldorigin.\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); SV_LinkEdict(ent); //SV_LinkEdict_TouchAreaGrid(ent); } else Con_DPrintf("Stuck player entity %i (classname \"%s\").\n", (int)PRVM_EDICT_TO_PROG(ent), PRVM_GetString(prog, PRVM_serveredictstring(ent, classname))); break; default: Con_Printf("SV_UnstickEntityReturnOffset returned a value outside its enum.\n"); } } /* ============= SV_CheckWater ============= */ static qboolean SV_CheckWater (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int cont; int nNativeContents; vec3_t point; point[0] = PRVM_serveredictvector(ent, origin)[0]; point[1] = PRVM_serveredictvector(ent, origin)[1]; point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, mins)[2] + 1; // DRESK - Support for Entity Contents Transition Event // NOTE: Some logic needed to be slightly re-ordered // to not affect performance and allow for the feature. // Acquire Super Contents Prior to Resets cont = SV_PointSuperContents(point); // Acquire Native Contents Here nNativeContents = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, cont); // DRESK - Support for Entity Contents Transition Event if(PRVM_serveredictfloat(ent, watertype)) // Entity did NOT Spawn; Check SV_CheckContentsTransition(ent, nNativeContents); PRVM_serveredictfloat(ent, waterlevel) = 0; PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; cont = SV_PointSuperContents(point); if (cont & (SUPERCONTENTS_LIQUIDSMASK)) { PRVM_serveredictfloat(ent, watertype) = nNativeContents; PRVM_serveredictfloat(ent, waterlevel) = 1; point[2] = PRVM_serveredictvector(ent, origin)[2] + (PRVM_serveredictvector(ent, mins)[2] + PRVM_serveredictvector(ent, maxs)[2])*0.5; if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) { PRVM_serveredictfloat(ent, waterlevel) = 2; point[2] = PRVM_serveredictvector(ent, origin)[2] + PRVM_serveredictvector(ent, view_ofs)[2]; if (SV_PointSuperContents(point) & (SUPERCONTENTS_LIQUIDSMASK)) PRVM_serveredictfloat(ent, waterlevel) = 3; } } return PRVM_serveredictfloat(ent, waterlevel) > 1; } /* ============ SV_WallFriction ============ */ static void SV_WallFriction (prvm_edict_t *ent, float *stepnormal) { prvm_prog_t *prog = SVVM_prog; float d, i; vec3_t forward, into, side, v_angle; VectorCopy(PRVM_serveredictvector(ent, v_angle), v_angle); AngleVectors (v_angle, forward, NULL, NULL); if ((d = DotProduct (stepnormal, forward) + 0.5) < 0) { // cut the tangential velocity i = DotProduct (stepnormal, PRVM_serveredictvector(ent, velocity)); VectorScale (stepnormal, i, into); VectorSubtract (PRVM_serveredictvector(ent, velocity), into, side); PRVM_serveredictvector(ent, velocity)[0] = side[0] * (1 + d); PRVM_serveredictvector(ent, velocity)[1] = side[1] * (1 + d); } } #if 0 /* ===================== SV_TryUnstick Player has come to a dead stop, possibly due to the problem with limited float precision at some angle joins in the BSP hull. Try fixing by pushing one pixel in each direction. This is a hack, but in the interest of good gameplay... ====================== */ int SV_TryUnstick (prvm_edict_t *ent, vec3_t oldvel) { int i, clip; vec3_t oldorg, dir; VectorCopy (PRVM_serveredictvector(ent, origin), oldorg); VectorClear (dir); for (i=0 ; i<8 ; i++) { // try pushing a little in an axial direction switch (i) { case 0: dir[0] = 2; dir[1] = 0; break; case 1: dir[0] = 0; dir[1] = 2; break; case 2: dir[0] = -2; dir[1] = 0; break; case 3: dir[0] = 0; dir[1] = -2; break; case 4: dir[0] = 2; dir[1] = 2; break; case 5: dir[0] = -2; dir[1] = 2; break; case 6: dir[0] = 2; dir[1] = -2; break; case 7: dir[0] = -2; dir[1] = -2; break; } SV_PushEntity (&trace, ent, dir, false, true); // retry the original move PRVM_serveredictvector(ent, velocity)[0] = oldvel[0]; PRVM_serveredictvector(ent, velocity)[1] = oldvel[1]; PRVM_serveredictvector(ent, velocity)[2] = 0; clip = SV_FlyMove (ent, 0.1, NULL, SV_GenericHitSuperContentsMask(ent), 0); if (fabs(oldorg[1] - PRVM_serveredictvector(ent, origin)[1]) > 4 || fabs(oldorg[0] - PRVM_serveredictvector(ent, origin)[0]) > 4) { Con_DPrint("TryUnstick - success.\n"); return clip; } // go back to the original pos and try again VectorCopy (oldorg, PRVM_serveredictvector(ent, origin)); } // still not moving VectorClear (PRVM_serveredictvector(ent, velocity)); Con_DPrint("TryUnstick - failure.\n"); return 7; } #endif /* ===================== SV_WalkMove Only used by players ====================== */ static void SV_WalkMove (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int clip; int oldonground; //int originalmove_clip; int originalmove_flags; int originalmove_groundentity; int hitsupercontentsmask; int skipsupercontentsmask; int type; vec3_t upmove, downmove, start_origin, start_velocity, stepnormal, originalmove_origin, originalmove_velocity, entmins, entmaxs; trace_t downtrace, trace; qboolean applygravity; // if frametime is 0 (due to client sending the same timestamp twice), // don't move if (sv.frametime <= 0) return; if (sv_gameplayfix_unstickplayers.integer) SV_CheckStuck (ent); applygravity = !SV_CheckWater (ent) && PRVM_serveredictfloat(ent, movetype) == MOVETYPE_WALK && ! ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP); hitsupercontentsmask = SV_GenericHitSuperContentsMask(ent); skipsupercontentsmask = 0; SV_CheckVelocity(ent); // do a regular slide move unless it looks like you ran into a step oldonground = (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND; VectorCopy (PRVM_serveredictvector(ent, origin), start_origin); VectorCopy (PRVM_serveredictvector(ent, velocity), start_velocity); clip = SV_FlyMove (ent, sv.frametime, applygravity, NULL, hitsupercontentsmask, skipsupercontentsmask, sv_gameplayfix_stepmultipletimes.integer ? sv_stepheight.value : 0); if(sv_gameplayfix_downtracesupportsongroundflag.integer) if(!(clip & 1)) { // only try this if there was no floor in the way in the trace (no, // this check seems to be not REALLY necessary, because if clip & 1, // our trace will hit that thing too) VectorSet(upmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] + 1); VectorSet(downmove, PRVM_serveredictvector(ent, origin)[0], PRVM_serveredictvector(ent, origin)[1], PRVM_serveredictvector(ent, origin)[2] - 1); if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLYMISSILE) type = MOVE_MISSILE; else if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_FLY_WORLDONLY) type = MOVE_WORLDONLY; else if (PRVM_serveredictfloat(ent, solid) == SOLID_TRIGGER || PRVM_serveredictfloat(ent, solid) == SOLID_NOT) type = MOVE_NOMONSTERS; // only clip against bmodels else type = MOVE_NORMAL; VectorCopy(PRVM_serveredictvector(ent, mins), entmins); VectorCopy(PRVM_serveredictvector(ent, maxs), entmaxs); trace = SV_TraceBox(upmove, entmins, entmaxs, downmove, type, ent, SV_GenericHitSuperContentsMask(ent), skipsupercontentsmask, collision_extendmovelength.value); if(trace.fraction < 1 && trace.plane.normal[2] > 0.7) clip |= 1; // but we HAVE found a floor } // if the move did not hit the ground at any point, we're not on ground if(!(clip & 1)) PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; SV_CheckVelocity(ent); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); if(clip & 8) // teleport return; if ((int)PRVM_serveredictfloat(ent, flags) & FL_WATERJUMP) return; if (sv_nostep.integer) return; VectorCopy(PRVM_serveredictvector(ent, origin), originalmove_origin); VectorCopy(PRVM_serveredictvector(ent, velocity), originalmove_velocity); //originalmove_clip = clip; originalmove_flags = (int)PRVM_serveredictfloat(ent, flags); originalmove_groundentity = PRVM_serveredictedict(ent, groundentity); // if move didn't block on a step, return if (clip & 2) { // if move was not trying to move into the step, return if (fabs(start_velocity[0]) < 0.03125 && fabs(start_velocity[1]) < 0.03125) return; if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_FLY) { // return if gibbed by a trigger if (PRVM_serveredictfloat(ent, movetype) != MOVETYPE_WALK) return; // return if attempting to jump while airborn (unless sv_jumpstep) if (!sv_jumpstep.integer) if (!oldonground && PRVM_serveredictfloat(ent, waterlevel) == 0) return; } // try moving up and forward to go up a step // back to start pos VectorCopy (start_origin, PRVM_serveredictvector(ent, origin)); VectorCopy (start_velocity, PRVM_serveredictvector(ent, velocity)); // move up VectorClear (upmove); upmove[2] = sv_stepheight.value; if(!SV_PushEntity(&trace, ent, upmove, true)) { // we got teleported when upstepping... must abort the move return; } // move forward PRVM_serveredictvector(ent, velocity)[2] = 0; clip = SV_FlyMove (ent, sv.frametime, applygravity, stepnormal, hitsupercontentsmask, skipsupercontentsmask, 0); PRVM_serveredictvector(ent, velocity)[2] += start_velocity[2]; if(clip & 8) { // we got teleported when upstepping... must abort the move // note that z velocity handling may not be what QC expects here, but we cannot help it return; } SV_CheckVelocity(ent); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); // check for stuckness, possibly due to the limited precision of floats // in the clipping hulls if (clip && fabs(originalmove_origin[1] - PRVM_serveredictvector(ent, origin)[1]) < 0.03125 && fabs(originalmove_origin[0] - PRVM_serveredictvector(ent, origin)[0]) < 0.03125) { //Con_Printf("wall\n"); // stepping up didn't make any progress, revert to original move VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); //clip = originalmove_clip; PRVM_serveredictfloat(ent, flags) = originalmove_flags; PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; // now try to unstick if needed //clip = SV_TryUnstick (ent, oldvel); return; } //Con_Printf("step - "); // extra friction based on view angle if (clip & 2 && sv_wallfriction.integer) SV_WallFriction (ent, stepnormal); } // don't do the down move if stepdown is disabled, moving upward, not in water, or the move started offground or ended onground else if (!sv_gameplayfix_stepdown.integer || PRVM_serveredictfloat(ent, waterlevel) >= 3 || start_velocity[2] >= (1.0 / 32.0) || !oldonground || ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND)) return; // move down VectorClear (downmove); downmove[2] = -sv_stepheight.value + start_velocity[2]*sv.frametime; if(!SV_PushEntity (&downtrace, ent, downmove, true)) { // we got teleported when downstepping... must abort the move return; } if (downtrace.fraction < 1 && downtrace.plane.normal[2] > 0.7) { // this has been disabled so that you can't jump when you are stepping // up while already jumping (also known as the Quake2 double jump bug) #if 0 // LordHavoc: disabled this check so you can walk on monsters/players //if (PRVM_serveredictfloat(ent, solid) == SOLID_BSP) { //Con_Printf("onground\n"); PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(downtrace.ent); } #endif } else { //Con_Printf("slope\n"); // if the push down didn't end up on good ground, use the move without // the step up. This happens near wall / slope combinations, and can // cause the player to hop up higher on a slope too steep to climb VectorCopy(originalmove_origin, PRVM_serveredictvector(ent, origin)); VectorCopy(originalmove_velocity, PRVM_serveredictvector(ent, velocity)); //clip = originalmove_clip; PRVM_serveredictfloat(ent, flags) = originalmove_flags; PRVM_serveredictedict(ent, groundentity) = originalmove_groundentity; } SV_CheckVelocity(ent); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } //============================================================================ /* ============= SV_Physics_Follow Entities that are "stuck" to another entity ============= */ static void SV_Physics_Follow (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; vec3_t vf, vr, vu, angles, v; prvm_edict_t *e; // LordHavoc: implemented rotation on MOVETYPE_FOLLOW objects e = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, aiment)); if (PRVM_serveredictvector(e, angles)[0] == PRVM_serveredictvector(ent, punchangle)[0] && PRVM_serveredictvector(e, angles)[1] == PRVM_serveredictvector(ent, punchangle)[1] && PRVM_serveredictvector(e, angles)[2] == PRVM_serveredictvector(ent, punchangle)[2]) { // quick case for no rotation VectorAdd(PRVM_serveredictvector(e, origin), PRVM_serveredictvector(ent, view_ofs), PRVM_serveredictvector(ent, origin)); } else { angles[0] = -PRVM_serveredictvector(ent, punchangle)[0]; angles[1] = PRVM_serveredictvector(ent, punchangle)[1]; angles[2] = PRVM_serveredictvector(ent, punchangle)[2]; AngleVectors (angles, vf, vr, vu); v[0] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[0] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[0] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[0]; v[1] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[1] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[1] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[1]; v[2] = PRVM_serveredictvector(ent, view_ofs)[0] * vf[2] + PRVM_serveredictvector(ent, view_ofs)[1] * vr[2] + PRVM_serveredictvector(ent, view_ofs)[2] * vu[2]; angles[0] = -PRVM_serveredictvector(e, angles)[0]; angles[1] = PRVM_serveredictvector(e, angles)[1]; angles[2] = PRVM_serveredictvector(e, angles)[2]; AngleVectors (angles, vf, vr, vu); PRVM_serveredictvector(ent, origin)[0] = v[0] * vf[0] + v[1] * vf[1] + v[2] * vf[2] + PRVM_serveredictvector(e, origin)[0]; PRVM_serveredictvector(ent, origin)[1] = v[0] * vr[0] + v[1] * vr[1] + v[2] * vr[2] + PRVM_serveredictvector(e, origin)[1]; PRVM_serveredictvector(ent, origin)[2] = v[0] * vu[0] + v[1] * vu[1] + v[2] * vu[2] + PRVM_serveredictvector(e, origin)[2]; } VectorAdd (PRVM_serveredictvector(e, angles), PRVM_serveredictvector(ent, v_angle), PRVM_serveredictvector(ent, angles)); SV_LinkEdict(ent); //SV_LinkEdict_TouchAreaGrid(ent); } /* ============================================================================== TOSS / BOUNCE ============================================================================== */ /* ============= SV_CheckWaterTransition ============= */ static void SV_CheckWaterTransition (prvm_edict_t *ent) { vec3_t entorigin; prvm_prog_t *prog = SVVM_prog; // LordHavoc: bugfixes in this function are keyed to the sv_gameplayfix_bugfixedcheckwatertransition cvar - if this cvar is 0 then all the original bugs should be reenabled for compatibility int cont; VectorCopy(PRVM_serveredictvector(ent, origin), entorigin); cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(entorigin)); if (!PRVM_serveredictfloat(ent, watertype)) { // just spawned here if (!sv_gameplayfix_fixedcheckwatertransition.integer) { PRVM_serveredictfloat(ent, watertype) = cont; PRVM_serveredictfloat(ent, waterlevel) = 1; return; } } // DRESK - Support for Entity Contents Transition Event // NOTE: Call here BEFORE updating the watertype below, // and suppress watersplash sound if a valid function // call was made to allow for custom "splash" sounds. else if( !SV_CheckContentsTransition(ent, cont) ) { // Contents Transition Function Invalid; Potentially Play Water Sound // check if the entity crossed into or out of water if (sv_sound_watersplash.string && ((PRVM_serveredictfloat(ent, watertype) == CONTENTS_WATER || PRVM_serveredictfloat(ent, watertype) == CONTENTS_SLIME) != (cont == CONTENTS_WATER || cont == CONTENTS_SLIME))) SV_StartSound (ent, 0, sv_sound_watersplash.string, 255, 1, false, 1.0f); } if (cont <= CONTENTS_WATER) { PRVM_serveredictfloat(ent, watertype) = cont; PRVM_serveredictfloat(ent, waterlevel) = 1; } else { PRVM_serveredictfloat(ent, watertype) = CONTENTS_EMPTY; PRVM_serveredictfloat(ent, waterlevel) = sv_gameplayfix_fixedcheckwatertransition.integer ? 0 : cont; } } /* ============= SV_Physics_Toss Toss, bounce, and fly movement. When onground, do nothing. ============= */ void SV_Physics_Toss (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; trace_t trace; vec3_t move; vec_t movetime; int bump; prvm_edict_t *groundentity; float d, ent_gravity; float bouncefactor; float bouncestop; // if onground, return without moving if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) { groundentity = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, groundentity)); if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) { // don't stick to ground if onground and moving upward PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; } else if (!PRVM_serveredictedict(ent, groundentity) || !sv_gameplayfix_noairborncorpse.integer) { // we can trust FL_ONGROUND if groundentity is world because it never moves return; } else if (ent->priv.server->suspendedinairflag && groundentity->priv.server->free) { // if ent was supported by a brush model on previous frame, // and groundentity is now freed, set groundentity to 0 (world) // which leaves it suspended in the air PRVM_serveredictedict(ent, groundentity) = 0; if (sv_gameplayfix_noairborncorpse_allowsuspendeditems.integer) return; } else if (BoxesOverlap(ent->priv.server->cullmins, ent->priv.server->cullmaxs, groundentity->priv.server->cullmins, groundentity->priv.server->cullmaxs)) { // don't slide if still touching the groundentity return; } } ent->priv.server->suspendedinairflag = false; SV_CheckVelocity (ent); // add gravity if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_TOSS || PRVM_serveredictfloat(ent, movetype) == MOVETYPE_BOUNCE) PRVM_serveredictvector(ent, velocity)[2] -= SV_Gravity(ent); // move angles VectorMA (PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); movetime = sv.frametime; for (bump = 0;bump < MAX_CLIP_PLANES && movetime > 0;bump++) { // move origin VectorScale(PRVM_serveredictvector(ent, velocity), movetime, move); if(!SV_PushEntity(&trace, ent, move, true)) return; // teleported if (ent->priv.server->free) return; if (trace.bmodelstartsolid && sv_gameplayfix_unstickentities.integer) { // try to unstick the entity SV_UnstickEntity(ent); if(!SV_PushEntity(&trace, ent, move, true)) return; // teleported if (ent->priv.server->free) return; } if (trace.fraction == 1) break; movetime *= 1 - min(1, trace.fraction); switch((int)PRVM_serveredictfloat(ent, movetype)) { case MOVETYPE_BOUNCEMISSILE: bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); if (!bouncefactor) bouncefactor = 1.0f; ClipVelocity(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; if (!sv_gameplayfix_slidemoveprojectiles.integer) movetime = 0; break; case MOVETYPE_BOUNCE: bouncefactor = PRVM_serveredictfloat(ent, bouncefactor); if (!bouncefactor) bouncefactor = 0.5f; bouncestop = PRVM_serveredictfloat(ent, bouncestop); if (!bouncestop) bouncestop = 60.0f / 800.0f; ClipVelocity(PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1 + bouncefactor); ent_gravity = PRVM_serveredictfloat(ent, gravity); if (!ent_gravity) ent_gravity = 1.0f; // LordHavoc: fixed grenades not bouncing when fired down a slope if (sv_gameplayfix_grenadebouncedownslopes.integer) d = fabs(DotProduct(trace.plane.normal, PRVM_serveredictvector(ent, velocity))); else d = PRVM_serveredictvector(ent, velocity)[2]; if (trace.plane.normal[2] > 0.7 && d < sv_gravity.value * bouncestop * ent_gravity) { PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); VectorClear(PRVM_serveredictvector(ent, velocity)); VectorClear(PRVM_serveredictvector(ent, avelocity)); movetime = 0; } else { PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; if (!sv_gameplayfix_slidemoveprojectiles.integer) movetime = 0; } break; default: ClipVelocity (PRVM_serveredictvector(ent, velocity), trace.plane.normal, PRVM_serveredictvector(ent, velocity), 1.0); if (trace.plane.normal[2] > 0.7) { PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) | FL_ONGROUND; PRVM_serveredictedict(ent, groundentity) = PRVM_EDICT_TO_PROG(trace.ent); if (PRVM_serveredictfloat(((prvm_edict_t *)trace.ent), solid) == SOLID_BSP) ent->priv.server->suspendedinairflag = true; VectorClear (PRVM_serveredictvector(ent, velocity)); VectorClear (PRVM_serveredictvector(ent, avelocity)); movetime = 0; } else { PRVM_serveredictfloat(ent, flags) = (int)PRVM_serveredictfloat(ent, flags) & ~FL_ONGROUND; if (!sv_gameplayfix_slidemoveprojectiles.integer) movetime = 0; } break; } } // check for in water SV_CheckWaterTransition (ent); } /* =============================================================================== STEPPING MOVEMENT =============================================================================== */ /* ============= SV_Physics_Step Monsters freefall when they don't have a ground entity, otherwise all movement is done with discrete steps. This is also used for objects that have become still on the ground, but will fall if the floor is pulled out from under them. ============= */ static void SV_Physics_Step (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; int flags = (int)PRVM_serveredictfloat(ent, flags); // DRESK // Backup Velocity in the event that movetypesteplandevent is called, // to provide a parameter with the entity's velocity at impact. vec3_t backupVelocity; VectorCopy(PRVM_serveredictvector(ent, velocity), backupVelocity); // don't fall at all if fly/swim if (!(flags & (FL_FLY | FL_SWIM))) { if (flags & FL_ONGROUND) { // freefall if onground and moving upward // freefall if not standing on a world surface (it may be a lift or trap door) if (PRVM_serveredictvector(ent, velocity)[2] >= (1.0 / 32.0) && sv_gameplayfix_upwardvelocityclearsongroundflag.integer) { PRVM_serveredictfloat(ent, flags) -= FL_ONGROUND; SV_CheckVelocity(ent); SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0, 0); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); ent->priv.server->waterposition_forceupdate = true; } } else { // freefall if not onground int hitsound = PRVM_serveredictvector(ent, velocity)[2] < sv_gravity.value * -0.1; SV_CheckVelocity(ent); SV_FlyMove(ent, sv.frametime, true, NULL, SV_GenericHitSuperContentsMask(ent), 0, 0); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); // just hit ground if (hitsound && (int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) { // DRESK - Check for Entity Land Event Function if(PRVM_serveredictfunction(ent, movetypesteplandevent)) { // Valid Function; Execute // Prepare Parameters // Assign Velocity at Impact PRVM_G_VECTOR(OFS_PARM0)[0] = backupVelocity[0]; PRVM_G_VECTOR(OFS_PARM0)[1] = backupVelocity[1]; PRVM_G_VECTOR(OFS_PARM0)[2] = backupVelocity[2]; // Assign Self PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); // Set Time PRVM_serverglobalfloat(time) = sv.time; // Execute VM Function prog->ExecuteProgram(prog, PRVM_serveredictfunction(ent, movetypesteplandevent), "movetypesteplandevent: NULL function"); } else // Check for Engine Landing Sound if(sv_sound_land.string) SV_StartSound(ent, 0, sv_sound_land.string, 255, 1, false, 1.0f); } ent->priv.server->waterposition_forceupdate = true; } } } //============================================================================ static void SV_Physics_Entity (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; // don't run think/move on newly spawned projectiles as it messes up // movement interpolation and rocket trails, and is inconsistent with // respect to entities spawned in the same frame // (if an ent spawns a higher numbered ent, it moves in the same frame, // but if it spawns a lower numbered ent, it doesn't - this never moves // ents in the first frame regardless) qboolean runmove = ent->priv.server->move; ent->priv.server->move = true; if (!runmove && sv_gameplayfix_delayprojectiles.integer > 0) return; switch ((int) PRVM_serveredictfloat(ent, movetype)) { case MOVETYPE_PUSH: case MOVETYPE_FAKEPUSH: SV_Physics_Pusher (ent); break; case MOVETYPE_NONE: // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) SV_RunThink (ent); break; case MOVETYPE_FOLLOW: if(SV_RunThink(ent)) SV_Physics_Follow (ent); break; case MOVETYPE_NOCLIP: if (SV_RunThink(ent)) { SV_CheckWater(ent); VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); } SV_LinkEdict(ent); break; case MOVETYPE_STEP: SV_Physics_Step (ent); // regular thinking if (SV_RunThink(ent)) if (ent->priv.server->waterposition_forceupdate || !VectorCompare(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin)) { ent->priv.server->waterposition_forceupdate = false; VectorCopy(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin); SV_CheckWaterTransition(ent); } break; case MOVETYPE_WALK: if (SV_RunThink (ent)) SV_WalkMove (ent); break; case MOVETYPE_TOSS: case MOVETYPE_BOUNCE: case MOVETYPE_BOUNCEMISSILE: case MOVETYPE_FLYMISSILE: case MOVETYPE_FLY: case MOVETYPE_FLY_WORLDONLY: // regular thinking if (SV_RunThink (ent)) SV_Physics_Toss (ent); break; case MOVETYPE_PHYSICS: if (SV_RunThink(ent)) { SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); } break; default: if((int) PRVM_serveredictfloat(ent, movetype) >= MOVETYPE_USER_FIRST && (int) PRVM_serveredictfloat(ent, movetype) <= MOVETYPE_USER_LAST) break; Con_Printf ("SV_Physics: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); break; } } static void SV_Physics_ClientEntity_NoThink (prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; // don't run think at all, that is done during server frames // instead, call the movetypes directly so they match client input // This probably only makes sense for CSQC-networked (SendEntity field set) player entities switch ((int) PRVM_serveredictfloat(ent, movetype)) { case MOVETYPE_PUSH: case MOVETYPE_FAKEPUSH: // push physics relies heavily on think times and calls, and so cannot be predicted currently Con_Printf ("SV_Physics_ClientEntity_NoThink: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); break; case MOVETYPE_NONE: break; case MOVETYPE_FOLLOW: SV_Physics_Follow (ent); break; case MOVETYPE_NOCLIP: VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); break; case MOVETYPE_STEP: SV_Physics_Step (ent); break; case MOVETYPE_WALK: SV_WalkMove (ent); break; case MOVETYPE_TOSS: case MOVETYPE_BOUNCE: case MOVETYPE_BOUNCEMISSILE: case MOVETYPE_FLYMISSILE: SV_Physics_Toss (ent); break; case MOVETYPE_FLY: case MOVETYPE_FLY_WORLDONLY: SV_WalkMove (ent); break; case MOVETYPE_PHYSICS: break; default: if((int) PRVM_serveredictfloat(ent, movetype) >= MOVETYPE_USER_FIRST && (int) PRVM_serveredictfloat(ent, movetype) <= MOVETYPE_USER_LAST) break; Con_Printf ("SV_Physics_ClientEntity_NoThink: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); break; } } void SV_Physics_ClientMove(void) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *ent; ent = host_client->edict; // call player physics, this needs the proper frametime PRVM_serverglobalfloat(frametime) = sv.frametime; SV_ClientThink(); // call standard client pre-think, with frametime = 0 PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobalfloat(frametime) = 0; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); PRVM_serverglobalfloat(frametime) = sv.frametime; // make sure the velocity is sane (not a NaN) SV_CheckVelocity(ent); // perform movetype behaviour // note: will always be MOVETYPE_WALK if disableclientprediction = 0 SV_Physics_ClientEntity_NoThink (ent); // call standard player post-think, with frametime = 0 PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobalfloat(frametime) = 0; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); PRVM_serverglobalfloat(frametime) = sv.frametime; if(PRVM_serveredictfloat(ent, fixangle)) { // angle fixing was requested by physics code... // so store the current angles for later use VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); host_client->fixangle_angles_set = TRUE; // and clear fixangle for the next frame PRVM_serveredictfloat(ent, fixangle) = 0; } } static void SV_Physics_ClientEntity_PreThink(prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; // don't do physics on disconnected clients, FrikBot relies on this if (!host_client->begun) return; // make sure the velocity is sane (not a NaN) SV_CheckVelocity(ent); // don't run physics here if running asynchronously if (host_client->clmovement_inputtimeout <= 0) { SV_ClientThink(); //host_client->cmd.time = max(host_client->cmd.time, sv.time); } // make sure the velocity is still sane (not a NaN) SV_CheckVelocity(ent); // call standard client pre-think PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPreThink), "QC function PlayerPreThink is missing"); // make sure the velocity is still sane (not a NaN) SV_CheckVelocity(ent); } static void SV_Physics_ClientEntity_PostThink(prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; // don't do physics on disconnected clients, FrikBot relies on this if (!host_client->begun) return; // make sure the velocity is sane (not a NaN) SV_CheckVelocity(ent); // call standard player post-think PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(ent); prog->ExecuteProgram(prog, PRVM_serverfunction(PlayerPostThink), "QC function PlayerPostThink is missing"); // make sure the velocity is still sane (not a NaN) SV_CheckVelocity(ent); if(PRVM_serveredictfloat(ent, fixangle)) { // angle fixing was requested by physics code... // so store the current angles for later use VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); host_client->fixangle_angles_set = TRUE; // and clear fixangle for the next frame PRVM_serveredictfloat(ent, fixangle) = 0; } // decrement the countdown variable used to decide when to go back to // synchronous physics if (host_client->clmovement_inputtimeout > sv.frametime) host_client->clmovement_inputtimeout -= sv.frametime; else host_client->clmovement_inputtimeout = 0; } static void SV_Physics_ClientEntity(prvm_edict_t *ent) { prvm_prog_t *prog = SVVM_prog; // don't do physics on disconnected clients, FrikBot relies on this if (!host_client->begun) { memset(&host_client->cmd, 0, sizeof(host_client->cmd)); return; } // make sure the velocity is sane (not a NaN) SV_CheckVelocity(ent); switch ((int) PRVM_serveredictfloat(ent, movetype)) { case MOVETYPE_PUSH: case MOVETYPE_FAKEPUSH: SV_Physics_Pusher (ent); break; case MOVETYPE_NONE: // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects if (PRVM_serveredictfloat(ent, nextthink) > 0 && PRVM_serveredictfloat(ent, nextthink) <= sv.time + sv.frametime) SV_RunThink (ent); break; case MOVETYPE_FOLLOW: SV_RunThink (ent); if (host_client->clmovement_inputtimeout <= 0) // don't run physics here if running asynchronously SV_Physics_Follow (ent); break; case MOVETYPE_NOCLIP: SV_RunThink(ent); if (host_client->clmovement_inputtimeout <= 0) // don't run physics here if running asynchronously { SV_CheckWater(ent); VectorMA(PRVM_serveredictvector(ent, origin), sv.frametime, PRVM_serveredictvector(ent, velocity), PRVM_serveredictvector(ent, origin)); VectorMA(PRVM_serveredictvector(ent, angles), sv.frametime, PRVM_serveredictvector(ent, avelocity), PRVM_serveredictvector(ent, angles)); } break; case MOVETYPE_STEP: if (host_client->clmovement_inputtimeout <= 0) // don't run physics here if running asynchronously SV_Physics_Step (ent); if (SV_RunThink(ent)) if (ent->priv.server->waterposition_forceupdate || !VectorCompare(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin)) { ent->priv.server->waterposition_forceupdate = false; VectorCopy(PRVM_serveredictvector(ent, origin), ent->priv.server->waterposition_origin); SV_CheckWaterTransition(ent); } break; case MOVETYPE_WALK: SV_RunThink (ent); // don't run physics here if running asynchronously if (host_client->clmovement_inputtimeout <= 0) SV_WalkMove (ent); break; case MOVETYPE_TOSS: case MOVETYPE_BOUNCE: case MOVETYPE_BOUNCEMISSILE: case MOVETYPE_FLYMISSILE: // regular thinking SV_RunThink (ent); if (host_client->clmovement_inputtimeout <= 0) // don't run physics here if running asynchronously SV_Physics_Toss (ent); break; case MOVETYPE_FLY: case MOVETYPE_FLY_WORLDONLY: SV_RunThink (ent); if (host_client->clmovement_inputtimeout <= 0) // don't run physics here if running asynchronously SV_WalkMove (ent); break; case MOVETYPE_PHYSICS: SV_RunThink (ent); break; default: if((int) PRVM_serveredictfloat(ent, movetype) >= MOVETYPE_USER_FIRST && (int) PRVM_serveredictfloat(ent, movetype) <= MOVETYPE_USER_LAST) break; Con_Printf ("SV_Physics_ClientEntity: bad movetype %i\n", (int)PRVM_serveredictfloat(ent, movetype)); break; } SV_CheckVelocity (ent); SV_LinkEdict(ent); SV_LinkEdict_TouchAreaGrid(ent); SV_CheckVelocity (ent); } /* ================ SV_Physics ================ */ void SV_Physics (void) { prvm_prog_t *prog = SVVM_prog; int i; prvm_edict_t *ent; // let the progs know that a new frame has started PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobalfloat(frametime) = sv.frametime; prog->ExecuteProgram(prog, PRVM_serverfunction(StartFrame), "QC function StartFrame is missing"); // run physics engine World_Physics_Frame(&sv.world, sv.frametime, sv_gravity.value); // // treat each object in turn // // if force_retouch, relink all the entities if (PRVM_serverglobalfloat(force_retouch) > 0) for (i = 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) if (!ent->priv.server->free) SV_LinkEdict_TouchAreaGrid(ent); // force retouch even for stationary if (sv_gameplayfix_consistentplayerprethink.integer) { // run physics on the client entities in 3 stages for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) if (!ent->priv.server->free) SV_Physics_ClientEntity_PreThink(ent); for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) if (!ent->priv.server->free) SV_Physics_ClientEntity(ent); for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) if (!ent->priv.server->free) SV_Physics_ClientEntity_PostThink(ent); } else { // run physics on the client entities for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) { if (!ent->priv.server->free) { SV_Physics_ClientEntity_PreThink(ent); SV_Physics_ClientEntity(ent); SV_Physics_ClientEntity_PostThink(ent); } } } // run physics on all the non-client entities if (!sv_freezenonclients.integer) { for (;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) if (!ent->priv.server->free) SV_Physics_Entity(ent); // make a second pass to see if any ents spawned this frame and make // sure they run their move/think if (sv_gameplayfix_delayprojectiles.integer < 0) for (i = svs.maxclients + 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) if (!ent->priv.server->move && !ent->priv.server->free) SV_Physics_Entity(ent); } if (PRVM_serverglobalfloat(force_retouch) > 0) PRVM_serverglobalfloat(force_retouch) = max(0, PRVM_serverglobalfloat(force_retouch) - 1); // LordHavoc: endframe support if (PRVM_serverfunction(EndFrame)) { PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_serverglobaledict(other) = PRVM_EDICT_TO_PROG(prog->edicts); PRVM_serverglobalfloat(time) = sv.time; prog->ExecuteProgram(prog, PRVM_serverfunction(EndFrame), "QC function EndFrame is missing"); } // decrement prog->num_edicts if the highest number entities died for (;PRVM_ED_CanAlloc(prog, PRVM_EDICT_NUM(prog->num_edicts - 1));prog->num_edicts--); if (!sv_freezenonclients.integer) sv.time += sv.frametime; } darkplaces/snd_coreaudio.c0000664000175000017500000002675613067716222015167 0ustar kalevkalev/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "quakedef.h" #include #include #include #include "snd_main.h" #define CHUNK_SIZE 1024 static unsigned int submissionChunk = 0; // in sample frames static unsigned int coreaudiotime = 0; // based on the number of chunks submitted so far static qboolean s_isRunning = false; static pthread_mutex_t coreaudio_mutex; static AudioDeviceID outputDeviceID = kAudioDeviceUnknown; static short *mixbuffer = NULL; /* ==================== audioDeviceIOProc ==================== */ static OSStatus audioDeviceIOProc(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const AudioBufferList *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inClientData) { float *outBuffer; unsigned int frameCount, factor, sampleIndex; float scale = 1.0f / SHRT_MAX; outBuffer = (float*)outOutputData->mBuffers[0].mData; factor = snd_renderbuffer->format.channels * snd_renderbuffer->format.width; frameCount = 0; if (snd_blocked) scale = 0; // Lock the snd_renderbuffer if (SndSys_LockRenderBuffer()) { unsigned int maxFrames, sampleCount; unsigned int startOffset, endOffset; const short *samples; if (snd_usethreadedmixing) { S_MixToBuffer(mixbuffer, submissionChunk); sampleCount = submissionChunk * snd_renderbuffer->format.channels; for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) outBuffer[sampleIndex] = mixbuffer[sampleIndex] * scale; // unlock the mutex now SndSys_UnlockRenderBuffer(); return 0; } // Transfert up to a chunk of sample frames from snd_renderbuffer to outBuffer maxFrames = snd_renderbuffer->endframe - snd_renderbuffer->startframe; if (maxFrames >= submissionChunk) frameCount = submissionChunk; else frameCount = maxFrames; // Convert the samples from shorts to floats. Scale the floats to be [-1..1]. startOffset = snd_renderbuffer->startframe % snd_renderbuffer->maxframes; endOffset = (snd_renderbuffer->startframe + frameCount) % snd_renderbuffer->maxframes; if (startOffset > endOffset) // if the buffer wraps { sampleCount = (snd_renderbuffer->maxframes - startOffset) * snd_renderbuffer->format.channels; samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]); for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) outBuffer[sampleIndex] = samples[sampleIndex] * scale; outBuffer = &outBuffer[sampleCount]; sampleCount = frameCount * snd_renderbuffer->format.channels - sampleCount; samples = (const short*)(&snd_renderbuffer->ring[0]); for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) outBuffer[sampleIndex] = samples[sampleIndex] * scale; } else { sampleCount = frameCount * snd_renderbuffer->format.channels; samples = (const short*)(&snd_renderbuffer->ring[startOffset * factor]); for (sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) outBuffer[sampleIndex] = samples[sampleIndex] * scale; } snd_renderbuffer->startframe += frameCount; // unlock the mutex now SndSys_UnlockRenderBuffer(); } // If there was not enough samples, complete with silence samples if (frameCount < submissionChunk) { unsigned int missingFrames; missingFrames = submissionChunk - frameCount; if (developer_insane.integer && vid_activewindow) Con_DPrintf("audioDeviceIOProc: %u sample frames missing\n", missingFrames); memset(&outBuffer[frameCount * snd_renderbuffer->format.channels], 0, missingFrames * sizeof(outBuffer[0])); } coreaudiotime += submissionChunk; return 0; } /* ==================== SndSys_Init Create "snd_renderbuffer" with the proper sound format if the call is successful May return a suggested format if the requested format isn't available ==================== */ qboolean SndSys_Init (const snd_format_t* requested, snd_format_t* suggested) { OSStatus status; UInt32 propertySize, bufferByteCount; AudioStreamBasicDescription streamDesc; if (s_isRunning) return true; Con_Printf("Initializing CoreAudio...\n"); snd_threaded = false; if(requested->width != 2) { // we can only do 16bit per sample for now if(suggested != NULL) { memcpy (suggested, requested, sizeof (*suggested)); suggested->width = 2; } return false; } // Get the output device propertySize = sizeof(outputDeviceID); status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &outputDeviceID); if (status) { Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioHardwarePropertyDefaultOutputDevice\n", (int)status); return false; } if (outputDeviceID == kAudioDeviceUnknown) { Con_Printf("CoreAudio: outputDeviceID is kAudioDeviceUnknown\n"); return false; } // Configure the output device propertySize = sizeof(bufferByteCount); bufferByteCount = CHUNK_SIZE * sizeof(float) * requested->channels; status = AudioDeviceSetProperty(outputDeviceID, NULL, 0, false, kAudioDevicePropertyBufferSize, propertySize, &bufferByteCount); if (status) { Con_Printf("CoreAudio: AudioDeviceSetProperty() returned %d when setting kAudioDevicePropertyBufferSize to %d\n", (int)status, CHUNK_SIZE); return false; } propertySize = sizeof(bufferByteCount); status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &bufferByteCount); if (status) { Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when setting kAudioDevicePropertyBufferSize\n", (int)status); return false; } submissionChunk = bufferByteCount / sizeof(float); if (submissionChunk % requested->channels != 0) { Con_Print("CoreAudio: chunk size is NOT a multiple of the number of channels\n"); return false; } submissionChunk /= requested->channels; Con_Printf(" Chunk size = %d sample frames\n", submissionChunk); // Print out the device status propertySize = sizeof(streamDesc); status = AudioDeviceGetProperty(outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &streamDesc); if (status) { Con_Printf("CoreAudio: AudioDeviceGetProperty() returned %d when getting kAudioDevicePropertyStreamFormat\n", (int)status); return false; } Con_Print (" Hardware format:\n"); Con_Printf(" %5d mSampleRate\n", (unsigned int)streamDesc.mSampleRate); Con_Printf(" %c%c%c%c mFormatID\n", (char)(streamDesc.mFormatID >> 24), (char)(streamDesc.mFormatID >> 16), (char)(streamDesc.mFormatID >> 8), (char)(streamDesc.mFormatID >> 0)); Con_Printf(" %5u mBytesPerPacket\n", (unsigned int)streamDesc.mBytesPerPacket); Con_Printf(" %5u mFramesPerPacket\n", (unsigned int)streamDesc.mFramesPerPacket); Con_Printf(" %5u mBytesPerFrame\n", (unsigned int)streamDesc.mBytesPerFrame); Con_Printf(" %5u mChannelsPerFrame\n", (unsigned int)streamDesc.mChannelsPerFrame); Con_Printf(" %5u mBitsPerChannel\n", (unsigned int)streamDesc.mBitsPerChannel); // Suggest proper settings if they differ if (requested->channels != streamDesc.mChannelsPerFrame || requested->speed != streamDesc.mSampleRate) { if (suggested != NULL) { memcpy (suggested, requested, sizeof (*suggested)); suggested->channels = streamDesc.mChannelsPerFrame; suggested->speed = streamDesc.mSampleRate; } return false; } if(streamDesc.mFormatID == kAudioFormatLinearPCM) { // Add the callback function status = AudioDeviceAddIOProc(outputDeviceID, audioDeviceIOProc, NULL); if (!status) { // We haven't sent any sample frames yet coreaudiotime = 0; if (pthread_mutex_init(&coreaudio_mutex, NULL) == 0) { if ((snd_renderbuffer = Snd_CreateRingBuffer(requested, 0, NULL))) { if ((mixbuffer = Mem_Alloc(snd_mempool, CHUNK_SIZE * sizeof(*mixbuffer) * requested->channels))) { // Start sound running status = AudioDeviceStart(outputDeviceID, audioDeviceIOProc); if (!status) { s_isRunning = true; snd_threaded = true; Con_Print(" Initialization successful\n"); return true; } else Con_Printf("CoreAudio: AudioDeviceStart() returned %d\n", (int)status); Mem_Free(mixbuffer); mixbuffer = NULL; } else Con_Print("CoreAudio: can't allocate memory for mixbuffer\n"); Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } else Con_Print("CoreAudio: can't allocate memory for ringbuffer\n"); pthread_mutex_destroy(&coreaudio_mutex); } else Con_Print("CoreAudio: can't create pthread mutex\n"); AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc); } else Con_Printf("CoreAudio: AudioDeviceAddIOProc() returned %d\n", (int)status); } else Con_Print("CoreAudio: Default audio device doesn't support linear PCM!\n"); return false; } /* ==================== SndSys_Shutdown Stop the sound card, delete "snd_renderbuffer" and free its other resources ==================== */ void SndSys_Shutdown(void) { OSStatus status; if (!s_isRunning) return; status = AudioDeviceStop(outputDeviceID, audioDeviceIOProc); if (status) { Con_Printf("AudioDeviceStop: returned %d\n", (int)status); return; } s_isRunning = false; pthread_mutex_destroy(&coreaudio_mutex); status = AudioDeviceRemoveIOProc(outputDeviceID, audioDeviceIOProc); if (status) { Con_Printf("AudioDeviceRemoveIOProc: returned %d\n", (int)status); return; } if (snd_renderbuffer != NULL) { Mem_Free(snd_renderbuffer->ring); Mem_Free(snd_renderbuffer); snd_renderbuffer = NULL; } if (mixbuffer != NULL) Mem_Free(mixbuffer); mixbuffer = NULL; } /* ==================== SndSys_Submit Submit the contents of "snd_renderbuffer" to the sound card ==================== */ void SndSys_Submit (void) { // Nothing to do here (this sound module is callback-based) } /* ==================== SndSys_GetSoundTime Returns the number of sample frames consumed since the sound started ==================== */ unsigned int SndSys_GetSoundTime (void) { return coreaudiotime; } /* ==================== SndSys_LockRenderBuffer Get the exclusive lock on "snd_renderbuffer" ==================== */ qboolean SndSys_LockRenderBuffer (void) { return (pthread_mutex_lock(&coreaudio_mutex) == 0); } /* ==================== SndSys_UnlockRenderBuffer Release the exclusive lock on "snd_renderbuffer" ==================== */ void SndSys_UnlockRenderBuffer (void) { pthread_mutex_unlock(&coreaudio_mutex); } /* ==================== SndSys_SendKeyEvents Send keyboard events originating from the sound system (e.g. MIDI) ==================== */ void SndSys_SendKeyEvents(void) { // not supported } darkplaces/prvm_execprogram.h0000664000175000017500000007004213067716222015721 0ustar kalevkalev// NEED to reset startst after calling this! startst may or may not be clobbered! #define ADVANCE_PROFILE_BEFORE_JUMP() \ prog->xfunction->profile += (st - startst); \ if (prvm_statementprofiling.integer || (prvm_coverage.integer & 4)) { \ /* All statements from startst+1 to st have been hit. */ \ while (++startst <= st) { \ if (prog->statement_profile[startst - cached_statements]++ == 0 && (prvm_coverage.integer & 4)) \ PRVM_StatementCoverageEvent(prog, prog->xfunction, startst - cached_statements); \ } \ /* Observe: startst now is clobbered (now at st+1)! */ \ } #ifdef PRVMTIMEPROFILING #define PRE_ERROR() \ ADVANCE_PROFILE_BEFORE_JUMP(); \ prog->xstatement = st - cached_statements; \ tm = Sys_DirtyTime(); \ prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; \ startst = st; \ starttm = tm #else #define PRE_ERROR() \ ADVANCE_PROFILE_BEFORE_JUMP(); \ prog->xstatement = st - cached_statements; \ startst = st #endif // This code isn't #ifdef/#define protectable, don't try. #if HAVE_COMPUTED_GOTOS && !(PRVMSLOWINTERPRETER || PRVMTIMEPROFILING) // NOTE: Due to otherwise duplicate labels, only ONE interpreter path may // ever hit this! # define USE_COMPUTED_GOTOS 1 #endif #if USE_COMPUTED_GOTOS // Must exactly match opcode_e enum in pr_comp.h const static void *dispatchtable[] = { &&handle_OP_DONE, &&handle_OP_MUL_F, &&handle_OP_MUL_V, &&handle_OP_MUL_FV, &&handle_OP_MUL_VF, &&handle_OP_DIV_F, &&handle_OP_ADD_F, &&handle_OP_ADD_V, &&handle_OP_SUB_F, &&handle_OP_SUB_V, &&handle_OP_EQ_F, &&handle_OP_EQ_V, &&handle_OP_EQ_S, &&handle_OP_EQ_E, &&handle_OP_EQ_FNC, &&handle_OP_NE_F, &&handle_OP_NE_V, &&handle_OP_NE_S, &&handle_OP_NE_E, &&handle_OP_NE_FNC, &&handle_OP_LE, &&handle_OP_GE, &&handle_OP_LT, &&handle_OP_GT, &&handle_OP_LOAD_F, &&handle_OP_LOAD_V, &&handle_OP_LOAD_S, &&handle_OP_LOAD_ENT, &&handle_OP_LOAD_FLD, &&handle_OP_LOAD_FNC, &&handle_OP_ADDRESS, &&handle_OP_STORE_F, &&handle_OP_STORE_V, &&handle_OP_STORE_S, &&handle_OP_STORE_ENT, &&handle_OP_STORE_FLD, &&handle_OP_STORE_FNC, &&handle_OP_STOREP_F, &&handle_OP_STOREP_V, &&handle_OP_STOREP_S, &&handle_OP_STOREP_ENT, &&handle_OP_STOREP_FLD, &&handle_OP_STOREP_FNC, &&handle_OP_RETURN, &&handle_OP_NOT_F, &&handle_OP_NOT_V, &&handle_OP_NOT_S, &&handle_OP_NOT_ENT, &&handle_OP_NOT_FNC, &&handle_OP_IF, &&handle_OP_IFNOT, &&handle_OP_CALL0, &&handle_OP_CALL1, &&handle_OP_CALL2, &&handle_OP_CALL3, &&handle_OP_CALL4, &&handle_OP_CALL5, &&handle_OP_CALL6, &&handle_OP_CALL7, &&handle_OP_CALL8, &&handle_OP_STATE, &&handle_OP_GOTO, &&handle_OP_AND, &&handle_OP_OR, &&handle_OP_BITAND, &&handle_OP_BITOR }; #define DISPATCH_OPCODE() \ goto *dispatchtable[(++st)->op] #define HANDLE_OPCODE(opcode) handle_##opcode DISPATCH_OPCODE(); // jump to first opcode #else // USE_COMPUTED_GOTOS #define DISPATCH_OPCODE() break #define HANDLE_OPCODE(opcode) case opcode #if PRVMSLOWINTERPRETER { if (prog->watch_global_type != ev_void) { prvm_eval_t *g = PRVM_GLOBALFIELDVALUE(prog->watch_global); prog->xstatement = st + 1 - cached_statements; PRVM_Watchpoint(prog, 1, "Global watchpoint hit by engine", prog->watch_global_type, &prog->watch_global_value, g); } if (prog->watch_field_type != ev_void && prog->watch_edict < prog->max_edicts) { prvm_eval_t *g = PRVM_EDICTFIELDVALUE(prog->edicts + prog->watch_edict, prog->watch_field); prog->xstatement = st + 1 - cached_statements; PRVM_Watchpoint(prog, 1, "Entityfield watchpoint hit by engine", prog->watch_field_type, &prog->watch_edictfield_value, g); } } #endif while (1) { st++; #endif // USE_COMPUTED_GOTOS #if !USE_COMPUTED_GOTOS #if PRVMSLOWINTERPRETER if (prog->trace) PRVM_PrintStatement(prog, st); if (prog->break_statement >= 0) if ((st - cached_statements) == prog->break_statement) { prog->xstatement = st - cached_statements; PRVM_Breakpoint(prog, prog->break_stack_index, "Breakpoint hit"); } #endif switch (st->op) { #endif HANDLE_OPCODE(OP_ADD_F): OPC->_float = OPA->_float + OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_ADD_V): OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_SUB_F): OPC->_float = OPA->_float - OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_SUB_V): OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_F): OPC->_float = OPA->_float * OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_V): OPC->_float = OPA->vector[0]*OPB->vector[0] + OPA->vector[1]*OPB->vector[1] + OPA->vector[2]*OPB->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_FV): tempfloat = OPA->_float; OPC->vector[0] = tempfloat * OPB->vector[0]; OPC->vector[1] = tempfloat * OPB->vector[1]; OPC->vector[2] = tempfloat * OPB->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_VF): tempfloat = OPB->_float; OPC->vector[0] = tempfloat * OPA->vector[0]; OPC->vector[1] = tempfloat * OPA->vector[1]; OPC->vector[2] = tempfloat * OPA->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DIV_F): if( OPB->_float != 0.0f ) { OPC->_float = OPA->_float / OPB->_float; } else { if (developer.integer) { PRE_ERROR(); VM_Warning(prog, "Attempted division by zero in %s\n", prog->name ); } OPC->_float = 0.0f; } DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITAND): OPC->_float = (prvm_int_t)OPA->_float & (prvm_int_t)OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITOR): OPC->_float = (prvm_int_t)OPA->_float | (prvm_int_t)OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GE): OPC->_float = OPA->_float >= OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LE): OPC->_float = OPA->_float <= OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GT): OPC->_float = OPA->_float > OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LT): OPC->_float = OPA->_float < OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_AND): OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) && FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add AND_I to be used by fteqcc for anything not a float DISPATCH_OPCODE(); HANDLE_OPCODE(OP_OR): OPC->_float = FLOAT_IS_TRUE_FOR_INT(OPA->_int) || FLOAT_IS_TRUE_FOR_INT(OPB->_int); // TODO change this back to float, and add OR_I to be used by fteqcc for anything not a float DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_F): OPC->_float = !FLOAT_IS_TRUE_FOR_INT(OPA->_int); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_V): OPC->_float = !OPA->vector[0] && !OPA->vector[1] && !OPA->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_S): OPC->_float = !OPA->string || !*PRVM_GetString(prog, OPA->string); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_FNC): OPC->_float = !OPA->function; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_ENT): OPC->_float = (OPA->edict == 0); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_F): OPC->_float = OPA->_float == OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_V): OPC->_float = (OPA->vector[0] == OPB->vector[0]) && (OPA->vector[1] == OPB->vector[1]) && (OPA->vector[2] == OPB->vector[2]); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_S): OPC->_float = !strcmp(PRVM_GetString(prog, OPA->string),PRVM_GetString(prog, OPB->string)); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_E): OPC->_float = OPA->_int == OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_FNC): OPC->_float = OPA->function == OPB->function; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_F): OPC->_float = OPA->_float != OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_V): OPC->_float = (OPA->vector[0] != OPB->vector[0]) || (OPA->vector[1] != OPB->vector[1]) || (OPA->vector[2] != OPB->vector[2]); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_S): OPC->_float = strcmp(PRVM_GetString(prog, OPA->string),PRVM_GetString(prog, OPB->string)); DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_E): OPC->_float = OPA->_int != OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_FNC): OPC->_float = OPA->function != OPB->function; DISPATCH_OPCODE(); //================== HANDLE_OPCODE(OP_STORE_F): HANDLE_OPCODE(OP_STORE_ENT): HANDLE_OPCODE(OP_STORE_FLD): // integers HANDLE_OPCODE(OP_STORE_S): HANDLE_OPCODE(OP_STORE_FNC): // pointers OPB->_int = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STORE_V): OPB->ivector[0] = OPA->ivector[0]; OPB->ivector[1] = OPA->ivector[1]; OPB->ivector[2] = OPA->ivector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STOREP_F): HANDLE_OPCODE(OP_STOREP_ENT): HANDLE_OPCODE(OP_STOREP_FLD): // integers HANDLE_OPCODE(OP_STOREP_S): HANDLE_OPCODE(OP_STOREP_FNC): // pointers if ((prvm_uint_t)OPB->_int - cached_entityfields >= cached_entityfieldsarea_entityfields) { if ((prvm_uint_t)OPB->_int >= cached_entityfieldsarea) { PRE_ERROR(); prog->error_cmd("%s attempted to write to an out of bounds edict (%i)", prog->name, (int)OPB->_int); goto cleanup; } if ((prvm_uint_t)OPB->_int < cached_entityfields && !cached_allowworldwrites) { PRE_ERROR(); VM_Warning(prog, "assignment to world.%s (field %i) in %s\n", PRVM_GetString(prog, PRVM_ED_FieldAtOfs(prog, OPB->_int)->s_name), (int)OPB->_int, prog->name); } } ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); ptr->_int = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STOREP_V): if ((prvm_uint_t)OPB->_int - cached_entityfields > (prvm_uint_t)cached_entityfieldsarea_entityfields_3) { if ((prvm_uint_t)OPB->_int > cached_entityfieldsarea_3) { PRE_ERROR(); prog->error_cmd("%s attempted to write to an out of bounds edict (%i)", prog->name, (int)OPB->_int); goto cleanup; } if ((prvm_uint_t)OPB->_int < cached_entityfields && !cached_allowworldwrites) { PRE_ERROR(); VM_Warning(prog, "assignment to world.%s (field %i) in %s\n", PRVM_GetString(prog, PRVM_ED_FieldAtOfs(prog, OPB->_int)->s_name), (int)OPB->_int, prog->name); } } ptr = (prvm_eval_t *)(cached_edictsfields + OPB->_int); ptr->ivector[0] = OPA->ivector[0]; ptr->ivector[1] = OPA->ivector[1]; ptr->ivector[2] = OPA->ivector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_ADDRESS): if ((prvm_uint_t)OPA->edict >= cached_max_edicts) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to address an out of bounds edict number", prog->name); goto cleanup; } if ((prvm_uint_t)OPB->_int >= cached_entityfields) { PRE_ERROR(); prog->error_cmd("%s attempted to address an invalid field (%i) in an edict", prog->name, (int)OPB->_int); goto cleanup; } #if 0 if (OPA->edict == 0 && !cached_allowworldwrites) { PRE_ERROR(); prog->error_cmd("forbidden assignment to null/world entity in %s", prog->name); goto cleanup; } #endif OPC->_int = OPA->edict * cached_entityfields + OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LOAD_F): HANDLE_OPCODE(OP_LOAD_FLD): HANDLE_OPCODE(OP_LOAD_ENT): HANDLE_OPCODE(OP_LOAD_S): HANDLE_OPCODE(OP_LOAD_FNC): if ((prvm_uint_t)OPA->edict >= cached_max_edicts) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); goto cleanup; } if ((prvm_uint_t)OPB->_int >= cached_entityfields) { PRE_ERROR(); prog->error_cmd("%s attempted to read an invalid field in an edict (%i)", prog->name, (int)OPB->_int); goto cleanup; } ed = PRVM_PROG_TO_EDICT(OPA->edict); OPC->_int = ((prvm_eval_t *)(ed->fields.ip + OPB->_int))->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LOAD_V): if ((prvm_uint_t)OPA->edict >= cached_max_edicts) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); goto cleanup; } if ((prvm_uint_t)OPB->_int > cached_entityfields_3) { PRE_ERROR(); prog->error_cmd("%s attempted to read an invalid field in an edict (%i)", prog->name, (int)OPB->_int); goto cleanup; } ed = PRVM_PROG_TO_EDICT(OPA->edict); ptr = (prvm_eval_t *)(ed->fields.ip + OPB->_int); OPC->ivector[0] = ptr->ivector[0]; OPC->ivector[1] = ptr->ivector[1]; OPC->ivector[2] = ptr->ivector[2]; DISPATCH_OPCODE(); //================== HANDLE_OPCODE(OP_IFNOT): if(!FLOAT_IS_TRUE_FOR_INT(OPA->_int)) // TODO add an "int-if", and change this one to OPA->_float // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) // and entity, string, field values can never have that value { ADVANCE_PROFILE_BEFORE_JUMP(); st = cached_statements + st->jumpabsolute - 1; // offset the st++ startst = st; // no bounds check needed, it is done when loading progs if (++jumpcount == 10000000 && prvm_runawaycheck) { prog->xstatement = st - cached_statements; PRVM_Profile(prog, 1<<30, 1000000, 0); prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); } } DISPATCH_OPCODE(); HANDLE_OPCODE(OP_IF): if(FLOAT_IS_TRUE_FOR_INT(OPA->_int)) // TODO add an "int-if", and change this one, as well as the FLOAT_IS_TRUE_FOR_INT usages, to OPA->_float // although mostly unneeded, thanks to the only float being false being 0x0 and 0x80000000 (negative zero) // and entity, string, field values can never have that value { ADVANCE_PROFILE_BEFORE_JUMP(); st = cached_statements + st->jumpabsolute - 1; // offset the st++ startst = st; // no bounds check needed, it is done when loading progs if (++jumpcount == 10000000 && prvm_runawaycheck) { prog->xstatement = st - cached_statements; PRVM_Profile(prog, 1<<30, 0.01, 0); prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); } } DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GOTO): ADVANCE_PROFILE_BEFORE_JUMP(); st = cached_statements + st->jumpabsolute - 1; // offset the st++ startst = st; // no bounds check needed, it is done when loading progs if (++jumpcount == 10000000 && prvm_runawaycheck) { prog->xstatement = st - cached_statements; PRVM_Profile(prog, 1<<30, 0.01, 0); prog->error_cmd("%s runaway loop counter hit limit of %d jumps\ntip: read above for list of most-executed functions", prog->name, jumpcount); } DISPATCH_OPCODE(); HANDLE_OPCODE(OP_CALL0): HANDLE_OPCODE(OP_CALL1): HANDLE_OPCODE(OP_CALL2): HANDLE_OPCODE(OP_CALL3): HANDLE_OPCODE(OP_CALL4): HANDLE_OPCODE(OP_CALL5): HANDLE_OPCODE(OP_CALL6): HANDLE_OPCODE(OP_CALL7): HANDLE_OPCODE(OP_CALL8): #ifdef PRVMTIMEPROFILING tm = Sys_DirtyTime(); prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; starttm = tm; #endif ADVANCE_PROFILE_BEFORE_JUMP(); startst = st; prog->xstatement = st - cached_statements; prog->argc = st->op - OP_CALL0; if (!OPA->function) { prog->error_cmd("NULL function in %s", prog->name); } if(!OPA->function || OPA->function < 0 || OPA->function >= prog->numfunctions) { PRE_ERROR(); prog->error_cmd("%s CALL outside the program", prog->name); goto cleanup; } enterfunc = &prog->functions[OPA->function]; if (enterfunc->callcount++ == 0 && (prvm_coverage.integer & 1)) PRVM_FunctionCoverageEvent(prog, enterfunc); if (enterfunc->first_statement < 0) { // negative first_statement values are built in functions int builtinnumber = -enterfunc->first_statement; prog->xfunction->builtinsprofile++; if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber]) { prog->builtins[builtinnumber](prog); #ifdef PRVMTIMEPROFILING tm = Sys_DirtyTime(); enterfunc->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; prog->xfunction->tbprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; starttm = tm; #endif // builtins may cause ED_Alloc() to be called, update cached variables cached_edictsfields = prog->edictsfields; cached_entityfields = prog->entityfields; cached_entityfields_3 = prog->entityfields - 3; cached_entityfieldsarea = prog->entityfieldsarea; cached_entityfieldsarea_entityfields = prog->entityfieldsarea - prog->entityfields; cached_entityfieldsarea_3 = prog->entityfieldsarea - 3; cached_entityfieldsarea_entityfields_3 = prog->entityfieldsarea - prog->entityfields - 3; cached_max_edicts = prog->max_edicts; // these do not change //cached_statements = prog->statements; //cached_allowworldwrites = prog->allowworldwrites; //cached_flag = prog->flag; // if prog->trace changed we need to change interpreter path if (prog->trace != cachedpr_trace) goto chooseexecprogram; } else prog->error_cmd("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, prog->name); } else st = cached_statements + PRVM_EnterFunction(prog, enterfunc); startst = st; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DONE): HANDLE_OPCODE(OP_RETURN): #ifdef PRVMTIMEPROFILING tm = Sys_DirtyTime(); prog->xfunction->tprofile += (tm - starttm >= 0 && tm - starttm < 1800) ? (tm - starttm) : 0; starttm = tm; #endif ADVANCE_PROFILE_BEFORE_JUMP(); prog->xstatement = st - cached_statements; prog->globals.ip[OFS_RETURN ] = prog->globals.ip[st->operand[0] ]; prog->globals.ip[OFS_RETURN+1] = prog->globals.ip[st->operand[0]+1]; prog->globals.ip[OFS_RETURN+2] = prog->globals.ip[st->operand[0]+2]; st = cached_statements + PRVM_LeaveFunction(prog); startst = st; if (prog->depth <= exitdepth) goto cleanup; // all done DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STATE): if(cached_flag & PRVM_OP_STATE) { ed = PRVM_PROG_TO_EDICT(PRVM_gameglobaledict(self)); PRVM_gameedictfloat(ed,nextthink) = PRVM_gameglobalfloat(time) + 0.1; PRVM_gameedictfloat(ed,frame) = OPA->_float; PRVM_gameedictfunction(ed,think) = OPB->function; } else { PRE_ERROR(); prog->xstatement = st - cached_statements; prog->error_cmd("OP_STATE not supported by %s", prog->name); } DISPATCH_OPCODE(); // LordHavoc: to be enabled when Progs version 7 (or whatever it will be numbered) is finalized /* HANDLE_OPCODE(OP_ADD_I): OPC->_int = OPA->_int + OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_ADD_IF): OPC->_int = OPA->_int + (prvm_int_t) OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_ADD_FI): OPC->_float = OPA->_float + (prvm_vec_t) OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_SUB_I): OPC->_int = OPA->_int - OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_SUB_IF): OPC->_int = OPA->_int - (prvm_int_t) OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_SUB_FI): OPC->_float = OPA->_float - (prvm_vec_t) OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_I): OPC->_int = OPA->_int * OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_IF): OPC->_int = OPA->_int * (prvm_int_t) OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_FI): OPC->_float = OPA->_float * (prvm_vec_t) OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_MUL_VI): OPC->vector[0] = (prvm_vec_t) OPB->_int * OPA->vector[0]; OPC->vector[1] = (prvm_vec_t) OPB->_int * OPA->vector[1]; OPC->vector[2] = (prvm_vec_t) OPB->_int * OPA->vector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DIV_VF): { float temp = 1.0f / OPB->_float; OPC->vector[0] = temp * OPA->vector[0]; OPC->vector[1] = temp * OPA->vector[1]; OPC->vector[2] = temp * OPA->vector[2]; } DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DIV_I): OPC->_int = OPA->_int / OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DIV_IF): OPC->_int = OPA->_int / (prvm_int_t) OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_DIV_FI): OPC->_float = OPA->_float / (prvm_vec_t) OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_CONV_IF): OPC->_float = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_CONV_FI): OPC->_int = OPA->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITAND_I): OPC->_int = OPA->_int & OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITOR_I): OPC->_int = OPA->_int | OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITAND_IF): OPC->_int = OPA->_int & (prvm_int_t)OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITOR_IF): OPC->_int = OPA->_int | (prvm_int_t)OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITAND_FI): OPC->_float = (prvm_int_t)OPA->_float & OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BITOR_FI): OPC->_float = (prvm_int_t)OPA->_float | OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GE_I): OPC->_float = OPA->_int >= OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LE_I): OPC->_float = OPA->_int <= OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GT_I): OPC->_float = OPA->_int > OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LT_I): OPC->_float = OPA->_int < OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_AND_I): OPC->_float = OPA->_int && OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_OR_I): OPC->_float = OPA->_int || OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GE_IF): OPC->_float = (prvm_vec_t)OPA->_int >= OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LE_IF): OPC->_float = (prvm_vec_t)OPA->_int <= OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GT_IF): OPC->_float = (prvm_vec_t)OPA->_int > OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LT_IF): OPC->_float = (prvm_vec_t)OPA->_int < OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_AND_IF): OPC->_float = (prvm_vec_t)OPA->_int && OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_OR_IF): OPC->_float = (prvm_vec_t)OPA->_int || OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GE_FI): OPC->_float = OPA->_float >= (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LE_FI): OPC->_float = OPA->_float <= (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GT_FI): OPC->_float = OPA->_float > (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LT_FI): OPC->_float = OPA->_float < (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_AND_FI): OPC->_float = OPA->_float && (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_OR_FI): OPC->_float = OPA->_float || (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NOT_I): OPC->_float = !OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_I): OPC->_float = OPA->_int == OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_IF): OPC->_float = (prvm_vec_t)OPA->_int == OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_EQ_FI): OPC->_float = OPA->_float == (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_I): OPC->_float = OPA->_int != OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_IF): OPC->_float = (prvm_vec_t)OPA->_int != OPB->_float; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_NE_FI): OPC->_float = OPA->_float != (prvm_vec_t)OPB->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STORE_I): OPB->_int = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_STOREP_I): #if PRBOUNDSCHECK if (OPB->_int < 0 || OPB->_int + 4 > pr_edictareasize) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to write to an out of bounds edict", prog->name); goto cleanup; } #endif ptr = (prvm_eval_t *)(prog->edictsfields + OPB->_int); ptr->_int = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_LOAD_I): #if PRBOUNDSCHECK if (OPA->edict < 0 || OPA->edict >= prog->max_edicts) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an out of bounds edict number", prog->name); goto cleanup; } if (OPB->_int < 0 || OPB->_int >= progs->entityfields) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an invalid field in an edict", prog->name); goto cleanup; } #endif ed = PRVM_PROG_TO_EDICT(OPA->edict); OPC->_int = ((prvm_eval_t *)((int *)ed->v + OPB->_int))->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GSTOREP_I): HANDLE_OPCODE(OP_GSTOREP_F): HANDLE_OPCODE(OP_GSTOREP_ENT): HANDLE_OPCODE(OP_GSTOREP_FLD): // integers HANDLE_OPCODE(OP_GSTOREP_S): HANDLE_OPCODE(OP_GSTOREP_FNC): // pointers #if PRBOUNDSCHECK if (OPB->_int < 0 || OPB->_int >= pr_globaldefs) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to write to an invalid indexed global", prog->name); goto cleanup; } #endif pr_iglobals[OPB->_int] = OPA->_int; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GSTOREP_V): #if PRBOUNDSCHECK if (OPB->_int < 0 || OPB->_int + 2 >= pr_globaldefs) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to write to an invalid indexed global", prog->name); goto cleanup; } #endif pr_iglobals[OPB->_int ] = OPA->ivector[0]; pr_iglobals[OPB->_int+1] = OPA->ivector[1]; pr_iglobals[OPB->_int+2] = OPA->ivector[2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GADDRESS): i = OPA->_int + (prvm_int_t) OPB->_float; #if PRBOUNDSCHECK if (i < 0 || i >= pr_globaldefs) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to address an out of bounds global", prog->name); goto cleanup; } #endif OPC->_int = pr_iglobals[i]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GLOAD_I): HANDLE_OPCODE(OP_GLOAD_F): HANDLE_OPCODE(OP_GLOAD_FLD): HANDLE_OPCODE(OP_GLOAD_ENT): HANDLE_OPCODE(OP_GLOAD_S): HANDLE_OPCODE(OP_GLOAD_FNC): #if PRBOUNDSCHECK if (OPA->_int < 0 || OPA->_int >= pr_globaldefs) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an invalid indexed global", prog->name); goto cleanup; } #endif OPC->_int = pr_iglobals[OPA->_int]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_GLOAD_V): #if PRBOUNDSCHECK if (OPA->_int < 0 || OPA->_int + 2 >= pr_globaldefs) { PRE_ERROR(); prog->error_cmd("%s Progs attempted to read an invalid indexed global", prog->name); goto cleanup; } #endif OPC->ivector[0] = pr_iglobals[OPA->_int ]; OPC->ivector[1] = pr_iglobals[OPA->_int+1]; OPC->ivector[2] = pr_iglobals[OPA->_int+2]; DISPATCH_OPCODE(); HANDLE_OPCODE(OP_BOUNDCHECK): if (OPA->_int < 0 || OPA->_int >= st->b) { PRE_ERROR(); prog->error_cmd("%s Progs boundcheck failed at line number %d, value is < 0 or >= %d", prog->name, st->b, st->c); goto cleanup; } DISPATCH_OPCODE(); */ #if !USE_COMPUTED_GOTOS default: PRE_ERROR(); prog->error_cmd("Bad opcode %i in %s", st->op, prog->name); goto cleanup; } #if PRVMSLOWINTERPRETER { if (prog->watch_global_type != ev_void) { prvm_eval_t *g = PRVM_GLOBALFIELDVALUE(prog->watch_global); prog->xstatement = st - cached_statements; PRVM_Watchpoint(prog, 0, "Global watchpoint hit", prog->watch_global_type, &prog->watch_global_value, g); } if (prog->watch_field_type != ev_void && prog->watch_edict < prog->max_edicts) { prvm_eval_t *g = PRVM_EDICTFIELDVALUE(prog->edicts + prog->watch_edict, prog->watch_field); prog->xstatement = st - cached_statements; PRVM_Watchpoint(prog, 0, "Entityfield watchpoint hit", prog->watch_field_type, &prog->watch_edictfield_value, g); } } #endif } #endif // !USE_COMPUTED_GOTOS #undef DISPATCH_OPCODE #undef HANDLE_OPCODE #undef USE_COMPUTED_GOTOS #undef PRE_ERROR #undef ADVANCE_PROFILE_BEFORE_JUMP darkplaces/filematch.c0000664000175000017500000001266513067716220014275 0ustar kalevkalev #ifdef WIN32 #include #else #include #endif #include "quakedef.h" // LordHavoc: some portable directory listing code I wrote for lmp2pcx, now used in darkplaces to load id1/*.pak and such... int matchpattern(const char *in, const char *pattern, int caseinsensitive) { return matchpattern_with_separator(in, pattern, caseinsensitive, "/\\:", false); } // wildcard_least_one: if true * matches 1 or more characters // if false * matches 0 or more characters int matchpattern_with_separator(const char *in, const char *pattern, int caseinsensitive, const char *separators, qboolean wildcard_least_one) { int c1, c2; while (*pattern) { switch (*pattern) { case 0: return 1; // end of pattern case '?': // match any single character if (*in == 0 || strchr(separators, *in)) return 0; // no match in++; pattern++; break; case '*': // match anything until following string if(wildcard_least_one) { if (*in == 0 || strchr(separators, *in)) return 0; // no match in++; } pattern++; while (*in) { if (strchr(separators, *in)) break; // see if pattern matches at this offset if (matchpattern_with_separator(in, pattern, caseinsensitive, separators, wildcard_least_one)) return 1; // nope, advance to next offset in++; } break; default: if (*in != *pattern) { if (!caseinsensitive) return 0; // no match c1 = *in; if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; c2 = *pattern; if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; if (c1 != c2) return 0; // no match } in++; pattern++; break; } } if (*in) return 0; // reached end of pattern but not end of input return 1; // success } // a little strings system void stringlistinit(stringlist_t *list) { memset(list, 0, sizeof(*list)); } void stringlistfreecontents(stringlist_t *list) { int i; for (i = 0;i < list->numstrings;i++) { if (list->strings[i]) Z_Free(list->strings[i]); list->strings[i] = NULL; } list->numstrings = 0; list->maxstrings = 0; if (list->strings) Z_Free(list->strings); list->strings = NULL; } void stringlistappend(stringlist_t *list, const char *text) { size_t textlen; char **oldstrings; if (list->numstrings >= list->maxstrings) { oldstrings = list->strings; list->maxstrings += 4096; list->strings = (char **) Z_Malloc(list->maxstrings * sizeof(*list->strings)); if (list->numstrings) memcpy(list->strings, oldstrings, list->numstrings * sizeof(*list->strings)); if (oldstrings) Z_Free(oldstrings); } textlen = strlen(text) + 1; list->strings[list->numstrings] = (char *) Z_Malloc(textlen); memcpy(list->strings[list->numstrings], text, textlen); list->numstrings++; } static int stringlistsort_cmp(const void *a, const void *b) { return strcasecmp(*(const char **)a, *(const char **)b); } void stringlistsort(stringlist_t *list, qboolean uniq) { int i, j; if(list->numstrings < 1) return; qsort(&list->strings[0], list->numstrings, sizeof(list->strings[0]), stringlistsort_cmp); if(uniq) { // i: the item to read // j: the item last written for (i = 1, j = 0; i < list->numstrings; ++i) { char *save; if(!strcasecmp(list->strings[i], list->strings[j])) continue; ++j; save = list->strings[j]; list->strings[j] = list->strings[i]; list->strings[i] = save; } for(i = j+1; i < list->numstrings; ++i) { if (list->strings[i]) Z_Free(list->strings[i]); } list->numstrings = j+1; } } // operating system specific code static void adddirentry(stringlist_t *list, const char *path, const char *name) { if (strcmp(name, ".") && strcmp(name, "..")) { char temp[MAX_OSPATH]; dpsnprintf( temp, sizeof( temp ), "%s%s", path, name ); stringlistappend(list, temp); } } #ifdef WIN32 void listdirectory(stringlist_t *list, const char *basepath, const char *path) { int i; char pattern[4096], *c; WIN32_FIND_DATA n_file; HANDLE hFile; strlcpy (pattern, basepath, sizeof(pattern)); strlcat (pattern, path, sizeof (pattern)); strlcat (pattern, "*", sizeof (pattern)); // ask for the directory listing handle hFile = FindFirstFile(pattern, &n_file); if(hFile == INVALID_HANDLE_VALUE) return; do { adddirentry(list, path, n_file.cFileName); } while (FindNextFile(hFile, &n_file) != 0); FindClose(hFile); // convert names to lowercase because windows does not care, but pattern matching code often does for (i = 0;i < list->numstrings;i++) for (c = list->strings[i];*c;c++) if (*c >= 'A' && *c <= 'Z') *c += 'a' - 'A'; } #else void listdirectory(stringlist_t *list, const char *basepath, const char *path) { char fullpath[MAX_OSPATH]; DIR *dir; struct dirent *ent; dpsnprintf(fullpath, sizeof(fullpath), "%s%s", basepath, path); #ifdef __ANDROID__ // SDL currently does not support listing assets, so we have to emulate // it. We're using relative paths for assets, so that will do. if (basepath[0] != '/') { char listpath[MAX_OSPATH]; qfile_t *listfile; dpsnprintf(listpath, sizeof(listpath), "%sls.txt", fullpath); char *buf = (char *) FS_SysLoadFile(listpath, tempmempool, true, NULL); if (!buf) return; char *p = buf; for (;;) { char *q = strchr(p, '\n'); if (q == NULL) break; *q = 0; adddirentry(list, path, p); p = q + 1; } Mem_Free(buf); return; } #endif dir = opendir(fullpath); if (!dir) return; while ((ent = readdir(dir))) adddirentry(list, path, ent->d_name); closedir(dir); } #endif darkplaces/darkplaces.ico0000664000175000017500000017742613067716220015011 0ustar kalevkalev 00¨¦ ¨NÈöh¾HH ˆT&$@@ (B®x00 ¨%Öº  ¨~à ˆ &ñ h®ú(0`  ! %- *+"-+1 8;2 $#6""")*-+,3$$8&*;//8/1;23534466655;68;::::;<=== BO KP P X U'X 'G *F(*E#)L+2I23B7;@=>B59K;=I!,T 3S.6Q+1V*4T'7[03V78Q56[8?_a j!` "e#o%h*i5f1o4|0|(.f$5`.„=ˆ5š9’Kž&G/B.K…(NŽ3X­P`‡Cd™[n•Wpœ`j€nuƒquƒry†z}‚ry‰y~‰es“mx‘nw˜gzœl{žSp¢Xu§dv¯b|· YÁƒ‡w€’{‚“t„§[‚Ñe‹Ø|šÐ‚‚‚‚†ƒ…‡………ƒ„‹†‰ˆˆˆˆˆŽŽŽ…“‹Œ’Š‹š‘—Œ‘š‘‘‘”““•••”•™˜˜™™šœœœ™«”• “š¦šœ¡•›©€°…‘µ†¿‰š¹–¤¾¢¢¢¥¥­ªªª§©°¬¬°©°¾³³³³µºœ¬Ä„¤Ø¤¯Ì¶¾Î²¿Ô¾ÂžÀÉ«ÁݽÊèÂÂÂÃÃÊÎÎÎÎÎÑÉÒØÒÙÞÙÙÜÐÛåÓÞîÚÝêÃÕñÞãòÑáûããäåçìçéëéêìëíöòóôôùþüýþÁͧj…fajËWaa»;k]´T…$‚ÞÞÞ„q½o‘ojaah‚‘f2¥aa»oaa632d‘ËOìÞa_‚RO1XµÁ̵íía]Éo™¸y´ÍyÁÿa$‘jI›©‘ÁÍ=Þÿa"m»ÁŠP0Ùw‘Í‹Ûÿh$]ajo²¹–v…Óþìo$$$a…ú>O¡ÿüð>ÿüðÿüðÿüøÿüüÿüüÿüþÿøÿÿøÿÀÿð?ÿøÿàÿÿÿ€ÿÿÿþÿÿÿðÿÿÿàÿÿÿƒàÿÿÿƒÿÿÿÿÿÃÿÿÿÿÿÃÿÿÿÿÿÃÿÿÿÿÿÇÿÿÿÿÿÏÿÿÿÿÿÿÿÿÿ( @2> !(&&2'(6"'?+/9*,?/2;/1>22334<56?78999:88>===$D (^(R$)@#,G89A??@9:D<=E8:K??N"*P&1R+3R.5U$1^59Q0:W'l/j*s7r.=c(z?@B9@N=AM5AW8BY?E^=Gd=Ia9Hi=P{cMYF”cJJk” b>žHABb ¦”8r.uZ”š°n!xr”™¶FcŽˆsbœr®¨8 8c@yW]r::>ŽƒHŸž"¬8SXh88Žb1\>¢¶›¸~8>5±¢F”°8n.2c¥¢FkŽc,>'‰ž>¢ $>JwvO8ž2²fžF888Jž–5³rŠz¬cr¢k#>*»8ŽQ‹¬F~ž>%º8Ÿ¤J]b¢>„‡´>3-8c:˜ž8 Œ”k…&Q”8n·]}H:|ž8”™ž”f©8f 1ª—nc”>c>†§•h”F”H()Z§{”]UFŽ0>iÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿüÿø?ÿðÿðÿáÿáÿ ÃÿÃÿÃÿ‡ÃþƒÁàƒÁÀÿÃàáÿÃàáÿÃðaÿ‡øÿ‡üÿÿþÿáüÿáð?ÿáÀÿÿñÿÿÿñÿÿÿóÿÿÿóÿÿÿÿÿÿ(0@''."&7,,5(-9..999:;<>??? I&E,3I45@;;E78K0}.=f1?o9@O:C^3Bf7Fe:Gk4DxAABEEEBDJJJJLLLAFVJJQAJ]QQQUUUWXXXXXXZ^]]^GOmJSjMSkVWaSVeTY`W\e]]gQUhQXlDPuEWvGXwAPIUyQ\w_af[`l_es\`wbbbccefffgimjjjhin`huoquwwwqr{pr|yyzyz|}}}9RˆXk™gsŠvw†yzŽvzTm¢Wt©dv¤j}£…’‰›€€€………€‚Š†‡Ž‰‰‰ŠŠ…ˆŒ‘“““”””’’˜’•ž–˜›››œ‡Œ©žž§£¤«ªªªªª±¹¹¹¬·Èµ¸Ã¹¿Ä¼ÆÓÃÈÏÉÉËÁÊÛÓÓ×ÅÒç$T$7]"T[W;H/aWn83ADp>ZZN;dZS$AKM,U@TTdC=`PUU;`]C >RITT;1O6$$$l2h>4"]5qCi$#-!&o1.!TY!n6' d!]Z'Y*M!YjTYb$]U JZZB Qc8!*Tfÿÿÿÿùÿÿùÿÿùÿø1ÿðñÿãð?ÇðÇñÇñLJðÇðãÇãÇãßãáãðÇüÇÿÿœ?ÿŸÿÿŸÿÿŸÿÿ¿ÿ( 44;;@L<@T;= )! 44$90(;@- ;ÿÿÿ¿û?ã?ÏŸŸ3Ÿ˜ùŒùÀùàóüÇüŸýÿýÿ(H `T444444444-444@444F4446444444 444444 444+444T444v444o444P444,444444444444444555P111œ444¯444ž444q444>444444444444444!HOW’GHJÎ444×444Å444444Q444#444 444444444407CÀEEHé444ï444Û444§444c444+444444444444E*2<æYYYø444÷444ê444»444r4444444444444444RRRjCOgðQRRû444û444ò444É444€444>444444444444444444444444444444444444444444444MNP‰ \úRVaö<<<ý444ö444Ô444444H444444444444444444444444444 444 444 444 444 444444444444444444444"JLO’"ù}„“ôEEEò444ù444ß444ž444T444!444 444444444444444444 444444444 444&444*444*444&444 444444444 44444444444444421:BËü|€‹öCCC÷444ü444ç444¬444_444'444 444444444444444 444444444*4449444H444V444`444d444b444Y444K444<444.444!444444 444444444444B.6EátÿôüÿñOOOü444ÿ444í444¹444k444.444444444444444 444444&444:444Q444j444ƒ444–444¦444°444±444ª444š444„444l444T444=444%444444444444QQQjïÿ¸»Æþeeeÿ444ÿ444ò444Ã444w4447444444444444444444444%444?444`444ƒ444¤444¾444Ð444Ý444å444è444å444Ú444È444®444”444y444S444.444444444444OOP} ñÿptÿQT[ÿBBBÿ444ö444Î444†444?444444444444444444 4444445444[444ˆ444²444Ó444ç444ñ>>>÷===ù>>>ù???÷???óAAAè444Ï444º444¦444€444U444*444444444444 IKO¢-móÿÿÿÿþuzøBBBô444ø444Ø444’444H444444444444444444444"444F444v444«444Õ444î>>>ù>>>ü9<@ó47Dö.6Kø*/C÷:ô%(7ô8;MÛcccÆhhh·UUUNNNj444;444444 444444444-@GNÇ Eÿøùÿÿÿÿÿÿ‹‹‹ù@@@÷444û444ß444œ444O444444 444444444444444)444S444‹444Ã444ë>?Bú5;L÷&&,ü2ù_ô}‹¨ÿyƒ”òlllæQSWÄJJP¬QQQvPPP^RRRPTTTGOOO.444444 44444444444469=BÎbr“þÿÿÿÿÿÿÿþ’’’üIIIû444ü444ã444¡444U444$444444444444444444444444444-444]444›BBBÔ?AE÷0?]ø%Yø8´ý Gÿ¦´ÖùŒŒŒñ\\\ÚMMMžJJJf444A444)444444444 444444444444444QQQM )ïðóûÿ(*/ÿÿÿÿÿÿÿÿÿMMMü444ü444ç444«444d4445444444444444 444 444444444444444444 444*444a444¥CCCá./4ü÷D¶ÿEpÐÿ¡ÆÿùX^føPSWèIII§444h444;444444 444444444444QQQR446ó$'Jÿ`_lÿÿÿÿÿÿÿÿÿLLLü444ÿ444ì444¼444ƒ444[444E444:4441444(444444444444 444444444444444444"444Y444£BDFè.;Yö'˜ñ$ÿ:BWþöÿÿûSTVûJJJÓFFF‹444N444&444444444444QQQR!(ò Tþ‹‰“ÿÿÿÿþÿÿÿÿJJJü444ÿ444ó444×444´444˜444ˆ444|444o444_444M444:444)444444444 444444444444444H444•=CKé##0û=ýJ¼ÿ¾ÔÿÿˆŠøNNPúFFFÊ444ƒ444D444444 444444444QQQR&4óorŽÿppzÿÿÿÿÿÿÿÿÿ___ÿ444ÿ444ú444ð444á444Ö444Î444Å444º444©444’444x444\444B444,444444444444444444444 4441444}PPPÝk}¤ø$þÿŽž¶þ¤¤¤úJJJûBBBÐ444‰444D444444 444444QQQPò–”¤þ|}‹ÿÿÿÿþÿÿÿÿZZZÿ444õ444ÿ444ú444ø444ö444ô444ò444í444ã444Ô444¾444¡444444]444?444&444444 444444444444444ZFHOÁh}´ñ€›Ùÿ UÈÿ*ÿ:T‹ú\]^ôAAAâ444444Q444!444 444444PPPJ-ó_\`ÿÿÿÿÿÿÿÿÿ¨¨°ÿ;;Cÿ<>?ü==>ü<<<ý;;;ý444ý444ü444ú444ö444í444Ü444Â444Ÿ444x444S4443444444 444444444444 4447JJJ‘%%4ö,M‰ý]ÐÿØìÿþL”ÿ/;OûAAAò444¹444j444-444444444444PPPGó þÛàöÿÿÿÿþ´´Àÿüû˜– òò÷ ú%*6ù/6<ù@@Aø@@@õ444ÿ444ÿ444ø444í444Ù444¸444Ž444b444;444444444444444444444];>JÖ2òÿ}†›ÿ Pÿ$Mù<<>û444Ø444Ž444C444444444444QQQAóÿMOXÿüFFFÿKJKô>>>ïñ%ÿÿÿ?yû*1<ý?ADû@@@÷444ÿ444ÿ444ö444è444É444œ444k444@444444 444444444444/IJM˜!ñ<þ5”ÿûÿÿÿöÿÿþY[]ù444ð444¸444f444*444 444444QQQ<ëþÿDEXþÿ?AGü444ü444çAAA°EFH45A®ð‹þpÿ$þ'ûKNTýCCCü444ö444ÿ444ú444í444Ñ444£444k444<444444 444444444444K15@Õ0ÿÿ*DÿÈÎàÿinzù@@@ü444ß444—444I444444444444LLL&004ÊÿÿÿÿÿÿááñÿKKKü444ü444å444¥444[III0NNQO'HÄ=ÿÿ'Lÿ‘£Ôú19LüRTYÿ???ú444ÿ444û444ð444Ò444ž444b4443444444444444444OOOy!$<óÿ¡¬Éÿÿÿÿÿÿ=>Aû>>>ö444Ê444{4445444444444444"IKNÀ LÿWÿÿÿÿÿÿÿÿÿMMMü444ü444å444£444T444!444LLLLPXs#4äDZ€þ„“©ÿwzÿ¸Éôú`jrò???û444ñ444ü444ï444É444444R444'444444444444444(FGL¤õÿ”¡¾þ«Áèÿ*FýMMMñ444ï444·444e444(444 444444444"norÄ/þ<ÿÿÿÿþÿÿÿÿNNNü444ü444ä444¡444Q444444444444 OQTW*6RÙ´»Îÿãøÿþûÿÿÿ‰–´ö+1=øLLLÿ444ÿ444ú444è444º444z444A444444 4444444446(,5Õý<ÿmzŒÿERñ<<<ý444ç444¨444X444!444 444444444"DEEÈÿÿÿÿÿÿÿÿÿÿMMMü444ü444â444444O444444444444444 QSTU'0<èÿÿÿÿÿÿÿÿhjmÿÆÆËÿopuö===û444ÿ444÷444Û444£444b4440444444444444??BP!2Wìþ =˜ÿùÿÿþñóôÿ\\\ý444ü444â444444O444444444444444$866ÊþÿÿÿÿþÿÿÿÿNNNû444û444á444›444Q444"444 444444444LLLlllz¨¨¨ì£¤¦ÿhozþ}€ˆÿ»¼ÀúIKMÿ@@@÷444ÿ444ï444Ç444…444G444444 444444:Ú "&é'((çpppäãâCCDâLKKâãä‘‘‘䆆†Ö444k444F444444 444444LLL,0@Ã,þTÉÿ¾Þÿþ‰¨Ûÿ~…ú==>ö444ú444Ý444š444P444444444444<<=Còÿ‹›¼ÿ¾Ñôÿ27Dþ>??ÿ444ÿ444÷444Õ444’444N444 444 444444444<444’444Ë444å444ñ444÷444ú444û444û444ú444÷444ï444Ö444š444]444<4441444,444,444,444,444,444+444$444444444 4444444449󛞴üüLPgü-Dxüü>>ô444É444}444;444444444444 OOPf ó(hÿLp¨þÿÿÿÿéùÿþGGJý444ü444æ444£444S444444444444NNN#0Úÿ%<ÿÿÿÿÿÿÿÿÿvvvô@@@ù444ÿ444ñ444Ë444Ž444S444(444444444444444444NOQtðÿ\rœÿ„œÿfgjó;;;ý444ñ444¾444n444.444 444444 QQQb/GrñuÿDzÓÿÿÿÿÿÿÿÿÿ=>Aü444û444ß444™444J444444444444 GLTxHüþÌÐÛÿÿÿÿÿáâçÿUUUÿ444ÿ444ú444ç444º444}444H444#444444444444444 444Nîþ ÿ  ¡þ„„„ñ444ÿ444ï444»444k444,444 444444444RSTr õ ?ÿXˆÌþÿÿÿÿÿÿÿþFGGû444ù444×444Œ444?444444444RRR0,Öÿ @‰ÿÿÿÿÿÿÿÿÿsssô???ú444ÿ444÷444Þ444®444t444C444"444444444444SSSs2ô ÿ]_|ÿØØáÿnqsÿ444ý444ï444»444k444,444 444444444OPS 5ô Pÿ/5EÿÿÿÿÿÿÿÿóHHH÷444ö444Ì444{4444444444LLL CGMd!òÿž¶ÿÞæþÿÔÞþòZZZÿ444ö444ÿ444ô444×444§444r444F444(444444 444MMO‡Hüÿ]]uÿÿÿÿÿ„„„ñ444ý444ï444»444k444,444 444444444$RRR‘0eõ(ŽÿJh¬ÿÿÿÿÿÿÿÿñFFFô444ò444¼444f444*444 444444PPP$ %¸ù?ÿØãùþÿÿÿÿsstùJJJÿ444ô444ü444ñ444Ö444ª444z444R4443444444#LMNŽ ü þ––¡ÿÿÿÿþ„„„ñ444ý444ð444»444k444-444444444444 4448X[bË(H‘ýjÿ¦ÃøþÿÿÿÿŸŸŸü===ý444è444¥444R444444444444RRR2%)AÙ @ÿ/Hxÿ¶ÀÓÿÿMMMù@@@ü444ÿ444û444ò444Û444¸444444f444G444?IJJšüÿƒƒ†ÿÿÿÿÿ†††ñ444ÿ444ð444¾444p4440444444444444444YORWà`t–ÿ(|ÿäôÿÿÿÿÿÿaaaý<<<ú444×444ˆ444=444444444 Y[`\/Eò „þ_ÿ<þ*ó&+2ñ@@@û444ÿ444ÿ444ö444æ444Ê444¦444ƒ444pGGG¯üþëëñÿøùÿþgikÿ444ÿ444ò444Ã444w4445444444444444 444.KKK‹#,?÷ þ ?ÿÿÿÿþÿÿÿÿXXXù444ò444»444g444)444 444444444 JNXa>ÙÿdÿHTbÿ†™¼øACKý@@@û444õ444ÿ444ù444ð444Ü444Ä444¯FEEÌý%ÿÿÿÿÿÿÿÿÿzzzÿ444ÿ444ô444Ë4444449444444444444444444SACHÈú=ÿb‹áÿîùÿÿŸŸŸûBBBü444Þ444•444G444444444444444 HLOAÒþ=\›ÿo€¦þPþ,5P÷=?Cü@@@÷444ÿ444ü444÷444í444áAABç-ü%þùûÿÿÿÿÿþ}}}ó444ò444÷444Ñ444…444>4444444444444444449HHHŠ$$.ôÿ,þÿÿÿÿÞîÿþfhhü>>>ó444¼444k444-444444444OQQ-LS`•/fã"ùˆÿEÿ\ñ'(.ü@@BøAAAò444ý444ú444÷?@D÷üÿ™™ ÿÿÿÿÿ‰‰‰øDDDö444ø444Ô444‰444@444444444444444444-444l@BEÎøÿ]gwÿÿÿÿÿÿÿÿöEEEü444Ù444444C444444444444LLLSVWD7Rw«+ròE¨ÿþýö$&0øAACñ???û444ú=>AüüþXZuÿhgwþxwzýGGG÷444ù444Ö444‹444B444444444444444444444+444^GGG®'*6øzþ:ÿðöÿþÿÿÿÿdddùAAAì444ª444\444%444 444444LLLPUVC=Jc›4à2óú24Dÿ úô%%%ù%$,ñý"ÿ¼ÁÜÿáäìÿ]`iýEEE÷444ù444×444Œ444B444444444444444444 4444440444_DDD¢46:ï÷ÿ”»ÿÿÿÿÿ¥¥¥ùDDDö444¿444s4444444444444444444 PQQ/MPRT:;@‹QU^ºihmÉA@CÒNNPéRQTõ þÿæèûÿÿÿÿÿ———ýEEEø444ù444×444Œ444B444444444444444444444444 444444"444?444m444¨=>@ç úÿ ÿÿÿÿÿÿÿÿþNNNúCCCË444ƒ444@444444444444444444 444KKKLLL#OOOBKMQ“ûþÞÜáÿÿÿÿþ˜˜˜ýEEEø444ù444Ö444‹444@444444444444444444444444444 444444444$4446444W444„444¸CDDê")=øþÿ£¾üþÿÿÿÿvvvòEEEÍ444ˆ444G444444 444444444MNQüÿääèÿÿÿÿÿšššýGGGø444ø444Ô444†444<444444444444444444 444444444"444/444@444Z444|444¦444Ï@BCó%%&øû%ÿuz„ÿîþÿû```ùGGGÅ444€444D444444 444444 MOQ}üþ»¹¾ÿÿÿÿþŠ‹ŒýGGG÷444÷444Î444}4446444444444444 444444$4442444@444R444k444‡444¨444É@@@æ<>Aù/lñjÿ,ÿ˜¤¼þž¤³öXXXîJJJ²444o444;444444444444 MMOw%üÿ®¬°ÿÄÄÌÿ444ýGGGõ444ô444Ä444q444/444444444444#444:444R444h444€444›444·444Ð444å?@@õ./4òô'ŒÿTxÄÿ“¹üÿ„††õQQQØIII•444X444.444444444444MMMzüþÛÛÛÿÿÿÿþ……†üIIIÿ444ï444·444c444(444 444 444 444E444i444ƒ444ž444¹444Õ444ç@@@ó9>Fû"%:úøGÿOl°þ‹¹ÿødhkëQQQ°HHHn444@444444 444444444OOQb Dóÿœœ¤ÿÿÿÿÿ[[[þ===ý444è444¨444V444 444 444444-444b444ŠGGGHHHµFFFÛ?AFíBFPö-Xø@Èÿ"œÿJô8è?@ZÿEEEü444ò444É444z4446444444444OOO,ÛEJZÿFFF÷444è444¯444`444'444 444LLL:?F¨¾¿ÆñNNNî444Ê444‰444A444444444444 CGLŸŽŽŽëFFFÉ444™444X444'444444444[[[p‘‘‘ÊJJJ‚444W444.444444444444VVV/hhhdLLL$444444444444ÿÿÿÿüÿÿÿÿÿÿÿüÿÿÿÿÿÿÿøÿÿÿÿÿÿÿøÿÿÿÿÿÿÿøÿÿÿÿÿÿÿøÿÿÿÿÿÿÿðÿÿÿÿüðÿÿÿÿà0ÿÿÿÿÿÿÿüÿÿÿøÿÿÿà?ÿÿÿÀ?ÿÿÿ€?ÿÿÿÿÿÿ`ÿþàÿüàÿüÿàÿøÿàÿøÿàÿðÿàðÿà?ðÿà?àÿàà?ÿàà?ÿàà?ÿàà?ÿàà?ÿÀà?àà> à< à8à8àààÿðÿÿðÿÿðÿÿøÿþøÿþüÿþüÿüþÿüÿÿøÿÿðÿ€ÿàÿàÿÀÿðÿÿüüÿÿÀ?ÿÿÀÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?ÿÿÿÿÿÿÿÿÿ€ÿÿÿÿÿ€ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÀÿÿÿÿÿÿÿÀ?ÿÿÿÿ(@€ B444444 444)444E444W444K4441444444444444444444J444€444…444o444E444!444 444444444.579“444¶444¸444—444_444.444444444 LLLH7APÅ444Þ444Û444·444w444<444444444444444MOOw049ìCCCò444ì444Í444444J444444444444444444444444444OQTŽ…žõAAAø444õ444Ý444Ÿ444W444#444 444444444444444444444444444444444444444444444)@FMÃ:KwòAAAü444ú444ç444°444d444*444 444444444444444444 444444444444444444444444 4444444444444444446=DKÊ(ZþVVVö444ü444ï444½444r4442444444444444444444 444444444)4444444=444C444D444?4446444*444444444 444444444 MMMP.ë MþSSSú444ý444ô444É444~444:444444444444444444444444444*444=444P444f444y444…444‹444ˆ444z444g444O4449444%444444444444LLLd4÷DX‰þòBBBñ444÷444Ô444Œ444D444444444444444444 444444-444I444h444‡444£444¸444È444Ð444Ï444Æ444²444—444w444Y4449444444 444444HHI› õ Dþ‘‘‘÷@@@ö444ú444Ü444™444O444444444444444444444$444B444l444–444¼444Õ444å444î444ò444ñ444î444á444Ë444®444’444i444F444444 444444'JMQ§"þ07WþX]küDDDú444û444å444¦444Y444#444 444444444444444/444Y444Œ444¼444ß444ñ>>>ø>>?û=>Aü;=Aû79@ø>@FïCDIßOOOÁNNN§444†444k4446444444444444419EØ ?ÿñóøþÿÿÿÿDDDû444ÿ444ë444±444c444)444 4444444444444444444447444i444£444Ö@@@ó=>Bü25<ö&->ú"÷Wj˜õp}”öJNWê:Gö#5[ü Xÿ'/=ÿØãûýTUUúIIIÑ444‰444L444$444444444444 TTSh48Jñ"ÿÿÿÿþÿÿÿÿyyyÿ444ÿ444ù444ç444Í444¶444¥444š444‰444w444a444J4443444"444444 444444444444444WFFF­4>O÷úlþ™¸øÿ™ž§ùKLLúEEEÌ444„444D444444 444444TTTh0=]ò/0Gÿÿÿÿþÿÿÿÿ}}}ó444õ444ÿ444÷444î444ç444á444Ù444Ï444¿444¨444Š444k444M4442444444444444444444444444JKL¥ö 4ÿ\o–ÿRe”ÿ%Fô@AA÷444È444y4446444444444444SSSW òÿ±´Æþ™›¤ÿ÷545ùIIIý "ñÿûõ%*:ú>>óFFFÔ46>Ë$ëþ?»ÿ:û,óEGLû@@@ø444ÿ444ÿ444÷444å444Á444444W444-444444444444444-HINŸ&õþ%?eÿÖÞîÿ`adý>>>ø444Ð4444449444444444NNN7"âÿrpuþØØäÿ`cqÿ444ÿ444ð444¾444vLLLV;>N£Dô:ÿ,ÿ¦¶ãõBJ\õDDEü@@@÷444ÿ444ù444è444¿444…444M444%444444444444 444E19MØþu€œþøùûþþGGJõ444ð444¹444f444)444 444444444*=?EÚ2þ“˜»þÿÿÿþ}}}ÿ444ý444ï444¹444i444.KKKOQWT,2BÔ0Rû„”±þpu|þt~”üVZ[ÿ???ù444ÿ444ù444ã444´444t444?444444 444444444NNNj(ôÿzˆ£þŒ¤Ðÿ6AWù<<<ü444ç444¥444T444444444444444*fhpÝÿŽ“±þÿÿÿÿ„„„ñ444ÿ444î444·444f444*444 444 SSS54;O¼›£¶ýéüÿÿøþÿÿ:BUýKNRÿ>>>ù444ÿ444ö444Ø444Ÿ444^444-444444444444444IKO“úLÿm™þEX€ÿIKMü444û444Þ444—444J444444444444444+%%%áÿ‰ˆþÿÿÿÿÿ444ÿ444î444´444e444,444444444QQQ08?FÒÿÿÿÿøùüÿpptÿóöÿüRSTÿ444ö444ÿ444î444Ä444„444G444444 444444444%?I_Á †þ7˜ÿÿÿÿþÿÿÿþUUUû444ù444Ù444444D4444444444444444440#!#âÿ“‘œþÿÿÿÿ€€€ÿ444ÿ444í444¶444n4446444444 444444 YYYIƒƒƒÝ_brÿLZwÿPU_þ78<ûPPRÿ444ÿ444ù444à444¨444c444.444444444444,,,-6ATÓ$lþE°þÿÿÿþ™™™ýDDDø444ù444Ö444‹444B444444444444444444444444444444444444444 444>äþŽŽ–þÿÿÿþ€€€ÿ444ÿ444ï444Ä444…444N444(444444444444 IKSq(ô=þGþPžþGSfó???ù444ÿ444ñ444Æ444‚444B444444444444,,,7+4BÞ(rÿ%bÿÿÿÿþšššýEEE÷444ù444Ö444Œ444D444444444444444444444444444 444 444 444 444 444444$444^&+0éÿŽŽžþÿÿÿÿcccÿ444ÿ444õ444Ù444§444m444>444444444444MMM#&7·ÿ7zþ:ÿ%„óJMWÿ444ÿ444ù444Ý444Ÿ444W444$444 444444,,,=-ðÿ2|ÿÿÿÿþ™™™ýEEE÷444ù444Ú444’444I444444444444444444 444444444444$444'444(444'444'4441444O444$%*ôÿgj|þëëðÿDDDý444ÿ444ù444è444Á444Œ444N444444 444444MMS`ô@þ<ÿ 'bÿ6Fc÷@@@ù444ü444ë444·444k444/444444444+++@8óÿÿ–ž°þ———ýCCCø444û444ß444444R444 444 444444444 4444441444D444R444\444`444b444a444^444e444…JJJÈó@ÿTr¾þMR|ÿ+-2ûGGGñ444ù444ê444Ì444444G444444444444LLL)4TÉwþ"X¹ÿ=pÈÿ?ÿJKLÿ444ÿ444ó444È444~444:444444444444+++20aì O¼þ*`Ëþÿÿÿþ™™™ýDDDú444ü444ç444«444a444*444444444444 444 444E444l444Œ444ž444ª444°444²444°444¨CCC¥EEGÄ$*1ð úJþ=¦þLþ"/öcfhôHHIìKKKá444º444j4443444444444444 JOU‚dþE¹þg˜äþbo‰þxxxñ444ó444÷444Õ444Œ444F444444444,,,,CÐ`ÿ@|ÎÿÿÿÿþòòòÿCCCû444ÿ444î444¾444u4447444444444444444444G444444¶444Ð444à444è444ë444ì444é444áBCDÝ004å:;;ßfffÙ$$$Ø:9:×UTUØddd؇‡‡Ùˆˆˆ×wwwÁ444`444<444444444444RRRF!ñxÿ¦Æûÿ°áÿ|€…û;;;÷444ú444Ý444›444O4444444444445>J·-ÿWm˜ÿŽ£ËþüFGHÿ444ÿ444õ444Ñ444444K444444 444444444444a444»444à444ò444ù444ü444ý444ý444û444ö444ã444¦444d444<444.444)444(444(444(444'444444444444444444NNN.éÿ8Gdÿþÿÿÿ™™™þ===ù444û444ä444¢444T444444444444 NR[M±þ-hÆÿjz–þäëÿÿlllÿ444ô444ú444ã444«444f4440444444444VUU:89=·889ÖSSRï:::ñGFFùFFFü444ý99:ü:;=ø>>?úBBBÔ444‡444?444444444444444444444444444444444":?NÇJ´ÿ€¦æÿÿÿÿÿÿÿÿõ@@@û444ü444æ444¤444T444444444444RRS¸‚ˆžóþDMuþ:”þZgˆþˆ‘£ûWY]ñFFFð444µ444i444,444 444444444!68?Å%þ<þÿÿÿþÿÿÿþFFGü444ü444ä444Ÿ444Q444444444444)6NÆdÿ`|¤þÿÿÿÿÿÿÿóRRRÿ444ÿ444ù444ã444°444o444:444444 444444 QQQ/ILPõ RÿˆœÖÿ ¨ÃþPPQü===ü444ã444¢444T444 444 444444444"EHM¼ *eÿ8™ÿÿÿÿÿëñÿþ:;<ü444û444ß444—444I444444444444JLQd$óþûüÿÿÿÿÿÿzzzô???ù444ÿ444ô444Ó444›444a4444444444 444444 444D "äÿGHLÿÃÃÄþJJJû444û444à444›444O444444444444444'EGKÅ_ÿL±ÿÿÿÿÿÿÿÿþ?@Aû444ù444×444‰444>444444444NNN! .GÊþHj£ÿÿÿÿÿ–––ýQQQÿ444ó444û444í444È444444Y4443444444 444OOOV 2ëÿ€€“ÿ¬®¶ýIIIú444û444à444›444M44444444444444416:EÎ/ÿÿÿÿÿÿÿÿÿöCCC÷444ö444Ë444x4443444444444ILPO$íþ¸ÔîþÎÖóþvy÷???û444ÿ444ú444è444Â444Ž444_4449444!444QRRn?ñþ˜˜«þÿÿÿýIIIú444û444à444›444N444444444444444 444BLUaÖ"ƒþuþÿÿÿþÿÿÿòBBBô444ñ444¸444d444'444 444444MMM-/4¤ù MÿþÿÿÿôöþöNNOÿ???ù444ÿ444ø444ç444Å444˜444m444J444:PPP~òÿËËÑÿÿÿÿþJJJû444û444à444œ444P444444444444444LLLfNUgôtÿOu¾ÿÿÿÿÿžžžû:::ý444å444¡444N444444444444OOO%7=VÎZÿPjœÿ2ü??AøAAAò444ü444ù444ôABBô )õÿÿÿÿÿÿÿÿý]]]ÿ444ÿ444í444µ444e444(444 444444444444444*444hCDGËóþæðÿÿÿÿÿÿbffú@@@î444¯444_444&444 444444444SW]T.Hqº5°ô'€þrÿö*-8ö;;=û>>>ú444ù@AAù)÷ ÿll€ÿ¦¤®þ{{{ÿ444ÿ444ï444¸444h444*444 444444444444444444*444ZFFF©'-<ø \ÿ"8þÿÿÿÿ¡¡¡ùDDDù444É444|4449444444444444444MMMPV_^0Hl»6ðÿÿ4öò//0ö558÷ó ÿËÌàÿž¡°ÿ]]aÿ444ÿ444ï444¹444h444*444 444444444444444444 4444442444^444 79=ìóUÿáîÿþÿÿÿÿdddó444Û444”444K444444444444444LLLOPR>GHLl8;B«cenÇ]]_Õ(',æaadóüþÿÿÿþÿÿÿþzzzÿ444ÿ444ï444¹444g444*444 444444444444444444444444444444'444C444p444¨@@Bä ú þMPZþÿÿÿþrrrúEEEå444¡444Y444'444 444444444444444KKK NNN@ù#/Mû'}ÿ*J†þÄËÛ÷bbbøLLLÆGGG444I444#444 444444444RRR\ ôþööøþ¾¾ÄþBBBý444ü444à444˜444J444444444#444B444_444z444–444¶444Ñ444å@AAõ(,2ò÷Oþh™ðþŽ¡¿óbbbèIII 444f4446444444444444STTRôÿóóöÿÿÿÿþWWWû444ù444Ö444ˆ444>4444444445444b444|444–444¾DDDÜEFGí4:Hù>—ñHý ÿZe÷QYmèTTT²HHHo444D444#444444444444RRR9#-JÞGÿ¹ÁàÿÿÿÿýGGG÷444ö444É444x4443444QQW-aacYPQ_qOSYˆELc¹FKWÛ!%4í8N|ö$|ü5cö!.Qî@CIÂNOR“KKK`444;444#44444444444444406OÌ2ÿðùÿÿ¡¡¡üLLLó444ð444º444g444)444 444RRR^^^J\\\Xkkk„®_ae·DGQªNQYPPPm444J4446444"444444 4444444447>M´þ»Áàþ–––ô<<<ý444è444©444W444 444444444444444444 444444444444444444 444444444444 NPU5ÿèèîÿjjjü444ú444Þ444–444I444444444444NOQoø»ÉìÿYYYô444ö444Ï444ƒ444;444444444RRRC#F딜¶õJJJû444í444»444l444.444 444444NNN-'éWYd÷AAAô444Þ444 444T444!444 444MMM  %Ñ‘“õFFFæ444Â444{444:444444444444@GSª|||áFFFÃ444Ž444O444"444 444444 ]]_ˆ‚‚‚½444u444N444'444444444444ZZZ:```R444!444444 444444ÿÿÿÿ€ÿÿÿÿÿÿ€ÿÿÿÿÿÿ€ÿÿÿÿÿÿ€ÿÿÿÿÿÿÿÿÿÿü?ÿÿÿÿÀÿÿÿþÿÿÿøÿÿÿàÿÿÿÀÿÿÿ€ÿÿÿÿÿþ?ÿüÿü>ÿøþÿøþðþ?ðþàþà?þàþàþÀþÀþÀüÀ|ÀpÀ`À`À@€à€à€à?€àÿ€ðÿ€ðÿ€ðÿ€øÿøÿüþþüÿøÿ€ðÿÀÀÿàÿøÿÿÿÿð?ÿÿøÿÿøÿÿÿøÿÿÿøÿÿÿø?ÿÿÿüÿÿÿÿü?ÿÿÿÿÿüÿÿÿÿÿüÿÿÿÿÿüÿÿÿÿÿþÿÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿÿÿþÿÿÿÿ(0` €%444444444+444-444444444444444(''<444\444Q4442444444444444444MVf‚444•444}444I444444 444444444'DWÕLO_§sss„ooo]ccc/444444444gggD!FóÞáéÿ˜˜˜ù@@@÷444Ì444v444/444444444444444444444.444]444šKNQÜ6=SöñUm¨ô…ï¶klojjjRggg3jjj+fff444444444 hikVN[sòîðóþŸŸŸýFFFú444Ô444ƒ444<444444444 444444444444444444444.444eUUUµ%)4õB¸û]‰ÞüY`l÷lll²]]]_4442444444444444444444444 aaajl}þ––›þÿÿÿÿHHHû444Û444—444[444;444,444"444444444 444444444444444 444%444_NQYÄ%5Zû0pÿˆŽžþpppéZZZ444F444 444 444444444 ```>>ù444æ444±444i4441444444444ENe±TþŽ©Ùþ¼ÆÙ÷FFFô444¸444`444 444444444iii@ò`_hþÿÿÿÿIIIù444Ï444z4443444444444 zz|tàâãò†‰þ¹»ÃþFGHý???÷444×444–444M444444 4444444444EfÐ(ˆþþÿÿþ”””ùCCCñ444²444Z444444444444444444444444444444 gggHñ]]hþÿÿÿÿJJJù444Î444ƒ444A444444 444444 CGWœ*Xþ'þ#6VüBCDü444ð444»444o4440444444444&&&!+Hß8ÿéñÿÿ‘‘‘ø444ð444²444[444444444444444444444 444 444 444 444444ccc] õ]`|ÿ›››ûCCCø444Ó444—444U444(444 444444iii" å(]ÿ/ÿ6Fjõ???ø444Ø444‘444D444444444%%%(ð(þ®¼Öþ÷444ò444¸444b444#444 444444444444444444"444(444+444)444*4449aaa ÷5HtþkmyøHHHð444Ù444¢444f444/444 444444444RSd‚Bþ=ˆÿ M–ö?@Cü444ë444¬444X444444444%%%"8{î=¨þ¾Éãþø444ô444Á444n444,444 444444444444444.444I444^444k444p444k444^__bt(.7Õü=£þ=ÿ>?FñabcÅ[[[444^444$444 444444lll3WóXÄÿRožþmnró444ô444¾444i444(444 444'''"=Ï%lÿÿÿÿÿ†ˆ‹ú???ø444Ñ444ƒ444<444444444444 444*444c444444®444¿444Ã444ºSSS¤JLNµffg«___§UTU¥nnn§¨‘‘‘¦{{{x444,444444444ccc2Ïlÿ¨Ààÿ‡Žü888÷444Ë444y444/444 444444 FWs²5‘þUm˜þRXjöBBBû444â444 444R444 444 444hhhjjjVaaa“VVVËOOOçIIIóGGFôKKKêRRRÃ444p4442444444444444444444 444 444444444GKXª\ÿÓáûþÿÿÿõ788ù444Ô444€4443444 444444eipo/Gmþœ®ÃþÿÿÿüKKKý444ð444¾444s4447444444nnn!JMUˆgkqã"üHWƒý%Mýƒ“°óNQWõWWW²444W444!444444444SW`–Gÿ¦³Ìþÿÿÿþ;;;û444Õ4444440444 444444iii! 9ê,Goÿÿÿÿÿƒƒƒò???ø444Ü444ž444X444'444444444 mmmC ØLÿ™£¾ÿSTVýFFFã444™444G444444444adi(uÿ”¹ôÿÿÿÿÿ445ú444Î444v444+444 444444 PYmþÃËàÿÿÿÿôDDDü444ñ444Ç444†444I444"444444444;>G·þ¡£¨þRRRü444à444“444A444444444444TUYœ ÿœ´Øþÿÿÿû999÷444Á444g444#444444444lll(,Ù*Tÿöÿÿþ~ˆñ???ù444æ444¸444|444G444%444444$;â þÿÿÿþRRRü444à444“444A444444444444aiv¤'†ÿ‘¡Ãþÿÿÿö:::ò444¯444U444444444WY]j÷o¡ÿæéñúCCCû???ö444ß444³444444P44434448!é ÿÿÿÿÿRRRü444á444•444C444444444444 4441gm|Ö%|ÿèùÿÿŸŸŸú:::æ444”444?444444444444 ]gƒŸ,ƒþZþ*5Eò?ABø444ó444Þ444¹444‘444f444^íþÿÿÿþQQQý444â444š444G444444444444^^^U4>Rï7ÿÿÿÿþcccü444Í444r444*444 444444ggg8E^ù:Rzÿhu‹÷HIOóBBBï444ã444Å444¤444”(ñ4:lþÿÿÿþOOOý444æ444¢444L4444444444444440SU[ŸüRwÀÿóþÿþWWWò444¢444L444444444444eee VYbn#9oÜ%üRÿ(-AôGHJæ444Ý444Í444À ;ô$ÿÿÿÿÿrrrÿ444ë444¦444Q444444444444444 444$ZZZ^%4èÿÿÿÿÿ¤¤¤÷NNNÌ444o444+444 444444444jjj#ShŒ… 9…ëPþò #.çKKLÒPPPÉ4ø :þŒŒ›þompó444ì444ª444R444444444444444444444$444RJKP¸BùWl”þÿÿÿû[[[ê444‰444A444444444444444fffX_he8;DŸhkyÊB@D×FEHìù"Oþÿÿÿþˆˆˆó444í444ª444R444444444444444444444444 444444/444ZVWX¡ùþüÿÿþttt÷SSSœ444Q444444 444444444444 444cccccc82äÿÿÿÿÿ‹‹‹ô444ì444¨444N444444444444444444444444+444G444oRRRª(0>ôÿƒ™Ëÿ………ù\\\¤444U444$444 444444444)â%þÿÿÿþ‰‰‰ó444é444Ÿ444H4444444444444444441444H444f444ŒRSSÄ$0K÷þ|Œ¸ú„„„î```’444K444!444 444444444â þäæëþSRSñ444â444“444>444444 444444*444E444`444ƒRRR©@BHÛø@¡þ¨Æÿñ~~~Ï]]]j4448444444 444444%Ü ÿÿÿÿÿ[[[û444Ö4444443444444 444&444F^^^a[\]–V\mÅ!444 444 444444‚‚‚Nnnn,444444 444444ÿÿÿ€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿüÿÿàÿÿ€ÿþ?ÿü?ÿøÿøÿðà>?àþÀþÀþÀþÀþÀþÀþ€€€€ÀÀÀ8ÀøÀ?øà?ðà?ðð?àð?àø?Àü?þ<ÿ€ÿàÿþÿÿ?ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ€ÿÿÿÿ€ÿÿÿÿÿ€ÿÿÿ( @ €444444444=444c444_4448444444444444444444444444444444444444Zam{444ž444{444G444444 444444444444444 444444444444444 444444_jã444»444™444]444'444 444444444444 444444'4444444<444;444.444444444*ÿ444É444¶444z4448444444444444 444444,444F444e444~444‡444u444R444.4444444ÿQQQÝ444Ñ444–444I444444444444444 444444D444m444–444¯444Ã444º444”444d4442444444Cÿ^^^ð444ä444®444Z444$444 444444444444444444 444 444O444„444ÈDEKâ'Kñ:}ÿ)1XЖiiiM444444W\do¡²Ôÿjjjú444ð444¾444q444:444!444444444 444444444444444M444‹%/Eò£ÿ=Tÿooo¯444X4442444444444 444PQS°rnvÿÿÿÿÿ444ô444Ð444•444i444R444B444/444444444444444444444;444€(ÿ5Iwÿqqqî444„444G444444 444444444444LOX½]]ÿÿÿÿ÷444ö444ß444À444ª444š444‡444h444G444+444444 444444444444cŠ¹ÿÿWÿ```ó444›444Q444444 444444.4D±–––ÿÝÜãÿFEG÷MMOä444Ö444×444Ù444Ã444ª444Š444U444/444444444444 4447+Ü#Brÿ0Jõ444Æ444w444.444 444444 ! %²ÿÿJIHõÿÿPÿLNQß444ç444ä444¾444–444T444(444444444444Xÿ€†‡ÿEEEð444²444[444 444444444444 cdgmÿÿÿÿú444ñ444À444t,÷@ÿh|¦ò444î444í444¿444ˆ444E444444 444RVb»ÿ=Xÿ444é444§444P444444444444444444444444sssoÿÿÿÿÿ444î444³444e4444OWf¿±¿×ÿem}ÿ444ñ444ç444±444g444.444444OÍöÁèÿÿdddü444æ444¡444N444444444444 444444444444'YXV…ÿÿÿÿÿ444æ444¯444`444+444*4Nñÿ:FVú444ó444Î444444E444444ÿ§ÄèÿZZZû444ç444¦444T444444 444444$444<444O444Q444VZ[Zÿqqqó444Û444¤444[444&444444ÿ±¿×ÿBBBô444é444©444V444 444pÿ[w¬ÿYYYû444ë444´444^444$444444-444[444‹444¡444¢444’ðSÿÿmmm¶444‡444M444444444 Uídúÿ}€ˆó444ð444¸444_444%444Hÿ­ÔÿÿMLLú444ò444Â444r44464444449444¢444Ñ444ã444áUUVÓwwv«db\|{{{x•pppW444$444444444 IGHˆ$ÿÿÿÿÿ444ð444»444d444&444 ‰´Â;>B ÿnnnü444é444«444W444444444444444 cccH5ÿÿÿÿÿ...ð444¶444Z444444 Ý^†¯ÿ}}}ø444î444×444¨444w444[GK]¶ÿiiiú444è444©444U444444444444___cÿÿÿÿÿ444ë444¦444I444444444ÿ±¿×ÿRQOé444æ444Ï444ª444”)+3Øÿ±¿×ÿ444ê444°444Y444444444444444444 444*yyy¯Qÿÿÿÿÿ444Ü444„4445444444nrr:ÿÿY[_Ù444Û444Ö444·-.4çx|¨ÿ±¿×ÿ444ð444·444^444 444444444444444 444!444Y ôDqÈÿnnnú444¯444[444 444 444444 4‹è5ÿÿQQQ¾444Í6%RRRE\dwƲ‹K (HfxtV/!UX\vW]mçΤ`* -[Ž555¹BEL¾MOZ“WWWWOOO# GP_¡hlx÷$$$Þ¼{H- %Z@BE´2`ð5@Wëlll¡UUUVTTT+RRRWZd¿ÿÿÿû(((é΢x\C/ FGPd¿ -o÷Z]`æ<<<‡D EGXÊÿÿÿÿ(((éÚì’€_;!LO]7eÄû%/Eò­_' (ÆÁÁÓÿ%ó ì(4DÍ:::¶Ê¶–j:$%'4èdp‹ø,,,Ú–J02?·rt–þ,,,ã444Â%/OÈBóW\gÐ'''ÙÇ™`3  e TýHMWóÐB (EEM´ÿÿÿÿ///ß®igjpŒøÿÿúRRXã࿆L ŠjŽÛþJJJîÐG" 0?P005¼ðóÿ÷222ΘR,"(Bµ*ú258æÛ¢a˜:g¸þHHHìÕœT1Gmˆ:::‘ Ù *€øXZ\¥EEEn8```D|ú/:Ròå³oo?tÌþMMPëÜ­nLLLRMMMs<<<¸000Õ<<<ÇTTU d`dyrrrgZZZ5 !/âÿÿÿõæ¼q- 4r÷‘‘‘ìàÁŽOOOz'-=Ñ/rþEGMñ///Ãy6#*LÓÿÿÿþè³e ,H¨ÎæÿüBBBÕÕ§ˆ@DL±:zù]h{óEEEVTXzöˆˆ‰ÿ778í###ј@  '2RÒ+Czÿ;;;˜'  ,òOSuÿ '>Þ'NúVWYÿÂC 1cú_aeøo#((@îÿ{abcp8PˆÿIKNÿœ+ aòbbböu)3`DDDs0ùim}ÿCCC  1ZÈ 1qÿÔ D &Fyúttuÿ£EEECDHUØOWoÿAABåTTT‚^^^iNNN(IR]sf‹¨ú$$$æP'7Z¤¥¨ÿ%%%ßo>B^ 0=uÿÇ 7 QRTjn•¨ý""#ß =IIJ+XôHTlÿ'''Ù/4LÔ~™ÿÌ 9ENaµƒ—¨ÿ¨!GHH5Kz¿7ð!CÿUYŒÿ Ò 8 JJJR !8ú„„…ðAELZ“smŒÿ"""Ç9KFFF›"3\ö|‚ˆíLLLMQU`i›Ÿ¡ÿ  TTVKdeh¥9JuÉ`bf†LLL#nt…ÿdmno±À€€Àðøø?darkplaces/vs2010_win64.props0000664000175000017500000000130213067716222015230 0ustar kalevkalev C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Include;C:\dev\SDL-1.2\include;$(IncludePath) C:\Program Files %28x86%29\Microsoft DirectX SDK %28June 2010%29\Lib\x64;C:\dev\SDL-1.2\lib\x64;$(LibraryPath) <_PropertySheetDisplayName>vs2010_win64 darkplaces/sv_main.c0000664000175000017500000051543413067716222014001 0ustar kalevkalev/* Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // sv_main.c -- server main program #include "quakedef.h" #include "sv_demo.h" #include "libcurl.h" #include "csprogs.h" #include "thread.h" static void SV_SaveEntFile_f(void); static void SV_StartDownload_f(void); static void SV_Download_f(void); static void SV_VM_Setup(void); extern cvar_t net_connecttimeout; cvar_t sv_worldmessage = {CVAR_READONLY, "sv_worldmessage", "", "title of current level"}; cvar_t sv_worldname = {CVAR_READONLY, "sv_worldname", "", "name of current worldmodel"}; cvar_t sv_worldnamenoextension = {CVAR_READONLY, "sv_worldnamenoextension", "", "name of current worldmodel without extension"}; cvar_t sv_worldbasename = {CVAR_READONLY, "sv_worldbasename", "", "name of current worldmodel without maps/ prefix or extension"}; cvar_t sv_disablenotify = {0, "sv_disablenotify", "1", "suppress broadcast prints when certain cvars are changed (CVAR_NOTIFY flag in engine code)"}; cvar_t coop = {0, "coop","0", "coop mode, 0 = no coop, 1 = coop mode, multiple players playing through the singleplayer game (coop mode also shuts off deathmatch)"}; cvar_t deathmatch = {0, "deathmatch","0", "deathmatch mode, values depend on mod but typically 0 = no deathmatch, 1 = normal deathmatch with respawning weapons, 2 = weapons stay (players can only pick up new weapons)"}; cvar_t fraglimit = {CVAR_NOTIFY, "fraglimit","0", "ends level if this many frags is reached by any player"}; cvar_t gamecfg = {0, "gamecfg", "0", "unused cvar in quake, can be used by mods"}; cvar_t noexit = {CVAR_NOTIFY, "noexit","0", "kills anyone attempting to use an exit"}; cvar_t nomonsters = {0, "nomonsters", "0", "unused cvar in quake, can be used by mods"}; cvar_t pausable = {0, "pausable","1", "allow players to pause or not (otherwise, only the server admin can)"}; cvar_t pr_checkextension = {CVAR_READONLY, "pr_checkextension", "1", "indicates to QuakeC that the standard quakec extensions system is available (if 0, quakec should not attempt to use extensions)"}; cvar_t samelevel = {CVAR_NOTIFY, "samelevel","0", "repeats same level if level ends (due to timelimit or someone hitting an exit)"}; cvar_t skill = {0, "skill","1", "difficulty level of game, affects monster layouts in levels, 0 = easy, 1 = normal, 2 = hard, 3 = nightmare (same layout as hard but monsters fire twice)"}; cvar_t slowmo = {0, "slowmo", "1.0", "controls game speed, 0.5 is half speed, 2 is double speed"}; cvar_t sv_accelerate = {0, "sv_accelerate", "10", "rate at which a player accelerates to sv_maxspeed"}; cvar_t sv_aim = {CVAR_SAVE, "sv_aim", "2", "maximum cosine angle for quake's vertical autoaim, a value above 1 completely disables the autoaim, quake used 0.93"}; cvar_t sv_airaccel_qw = {0, "sv_airaccel_qw", "1", "ratio of QW-style air control as opposed to simple acceleration; when < 0, the speed is clamped against the maximum allowed forward speed after the move"}; cvar_t sv_airaccel_qw_stretchfactor = {0, "sv_airaccel_qw_stretchfactor", "0", "when set, the maximum acceleration increase the player may get compared to forward-acceleration when strafejumping"}; cvar_t sv_airaccel_sideways_friction = {0, "sv_airaccel_sideways_friction", "", "anti-sideways movement stabilization (reduces speed gain when zigzagging); when < 0, only so much friction is applied that braking (by accelerating backwards) cannot be stronger"}; cvar_t sv_airaccelerate = {0, "sv_airaccelerate", "-1", "rate at which a player accelerates to sv_maxairspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; cvar_t sv_airstopaccelerate = {0, "sv_airstopaccelerate", "0", "when set, replacement for sv_airaccelerate when moving backwards"}; cvar_t sv_airspeedlimit_nonqw = {0, "sv_airspeedlimit_nonqw", "0", "when set, this is a soft speed limit while in air when using airaccel_qw not equal to 1"}; cvar_t sv_airstrafeaccelerate = {0, "sv_airstrafeaccelerate", "0", "when set, replacement for sv_airaccelerate when just strafing"}; cvar_t sv_maxairstrafespeed = {0, "sv_maxairstrafespeed", "0", "when set, replacement for sv_maxairspeed when just strafing"}; cvar_t sv_airstrafeaccel_qw = {0, "sv_airstrafeaccel_qw", "0", "when set, replacement for sv_airaccel_qw when just strafing"}; cvar_t sv_aircontrol = {0, "sv_aircontrol", "0", "CPMA-style air control"}; cvar_t sv_aircontrol_power = {0, "sv_aircontrol_power", "2", "CPMA-style air control exponent"}; cvar_t sv_aircontrol_penalty = {0, "sv_aircontrol_penalty", "0", "deceleration while using CPMA-style air control"}; cvar_t sv_allowdownloads = {0, "sv_allowdownloads", "1", "whether to allow clients to download files from the server (does not affect http downloads)"}; cvar_t sv_allowdownloads_archive = {0, "sv_allowdownloads_archive", "0", "whether to allow downloads of archives (pak/pk3)"}; cvar_t sv_allowdownloads_config = {0, "sv_allowdownloads_config", "0", "whether to allow downloads of config files (cfg)"}; cvar_t sv_allowdownloads_dlcache = {0, "sv_allowdownloads_dlcache", "0", "whether to allow downloads of dlcache files (dlcache/)"}; cvar_t sv_allowdownloads_inarchive = {0, "sv_allowdownloads_inarchive", "0", "whether to allow downloads from archives (pak/pk3)"}; cvar_t sv_areagrid_mingridsize = {CVAR_NOTIFY, "sv_areagrid_mingridsize", "128", "minimum areagrid cell size, smaller values work better for lots of small objects, higher values for large objects"}; cvar_t sv_checkforpacketsduringsleep = {0, "sv_checkforpacketsduringsleep", "0", "uses select() function to wait between frames which can be interrupted by packets being received, instead of Sleep()/usleep()/SDL_Sleep() functions which do not check for packets"}; cvar_t sv_clmovement_enable = {0, "sv_clmovement_enable", "1", "whether to allow clients to use cl_movement prediction, which can cause choppy movement on the server which may annoy other players"}; cvar_t sv_clmovement_minping = {0, "sv_clmovement_minping", "0", "if client ping is below this time in milliseconds, then their ability to use cl_movement prediction is disabled for a while (as they don't need it)"}; cvar_t sv_clmovement_minping_disabletime = {0, "sv_clmovement_minping_disabletime", "1000", "when client falls below minping, disable their prediction for this many milliseconds (should be at least 1000 or else their prediction may turn on/off frequently)"}; cvar_t sv_clmovement_inputtimeout = {0, "sv_clmovement_inputtimeout", "0.2", "when a client does not send input for this many seconds, force them to move anyway (unlike QuakeWorld)"}; cvar_t sv_cullentities_nevercullbmodels = {0, "sv_cullentities_nevercullbmodels", "0", "if enabled the clients are always notified of moving doors and lifts and other submodels of world (warning: eats a lot of network bandwidth on some levels!)"}; cvar_t sv_cullentities_pvs = {0, "sv_cullentities_pvs", "1", "fast but loose culling of hidden entities"}; cvar_t sv_cullentities_stats = {0, "sv_cullentities_stats", "0", "displays stats on network entities culled by various methods for each client"}; cvar_t sv_cullentities_trace = {0, "sv_cullentities_trace", "0", "somewhat slow but very tight culling of hidden entities, minimizes network traffic and makes wallhack cheats useless"}; cvar_t sv_cullentities_trace_delay = {0, "sv_cullentities_trace_delay", "1", "number of seconds until the entity gets actually culled"}; cvar_t sv_cullentities_trace_delay_players = {0, "sv_cullentities_trace_delay_players", "0.2", "number of seconds until the entity gets actually culled if it is a player entity"}; cvar_t sv_cullentities_trace_enlarge = {0, "sv_cullentities_trace_enlarge", "0", "box enlargement for entity culling"}; cvar_t sv_cullentities_trace_prediction = {0, "sv_cullentities_trace_prediction", "1", "also trace from the predicted player position"}; cvar_t sv_cullentities_trace_prediction_time = {0, "sv_cullentities_trace_prediction_time", "0.2", "how many seconds of prediction to use"}; cvar_t sv_cullentities_trace_entityocclusion = {0, "sv_cullentities_trace_entityocclusion", "0", "also check if doors and other bsp models are in the way"}; cvar_t sv_cullentities_trace_samples = {0, "sv_cullentities_trace_samples", "2", "number of samples to test for entity culling"}; cvar_t sv_cullentities_trace_samples_extra = {0, "sv_cullentities_trace_samples_extra", "2", "number of samples to test for entity culling when the entity affects its surroundings by e.g. dlight"}; cvar_t sv_cullentities_trace_samples_players = {0, "sv_cullentities_trace_samples_players", "8", "number of samples to test for entity culling when the entity is a player entity"}; cvar_t sv_debugmove = {CVAR_NOTIFY, "sv_debugmove", "0", "disables collision detection optimizations for debugging purposes"}; cvar_t sv_echobprint = {CVAR_SAVE, "sv_echobprint", "1", "prints gamecode bprint() calls to server console"}; cvar_t sv_edgefriction = {0, "edgefriction", "1", "how much you slow down when nearing a ledge you might fall off, multiplier of sv_friction (Quake used 2, QuakeWorld used 1 due to a bug in physics code)"}; cvar_t sv_entpatch = {0, "sv_entpatch", "1", "enables loading of .ent files to override entities in the bsp (for example Threewave CTF server pack contains .ent patch files enabling play of CTF on id1 maps)"}; cvar_t sv_fixedframeratesingleplayer = {0, "sv_fixedframeratesingleplayer", "1", "allows you to use server-style timing system in singleplayer (don't run faster than sys_ticrate)"}; cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"}; cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4", "how fast you slow down"}; cvar_t sv_gameplayfix_blowupfallenzombies = {0, "sv_gameplayfix_blowupfallenzombies", "1", "causes findradius to detect SOLID_NOT entities such as zombies and corpses on the floor, allowing splash damage to apply to them"}; cvar_t sv_gameplayfix_consistentplayerprethink = {0, "sv_gameplayfix_consistentplayerprethink", "0", "improves fairness in multiplayer by running all PlayerPreThink functions (which fire weapons) before performing physics, then running all PlayerPostThink functions"}; cvar_t sv_gameplayfix_delayprojectiles = {0, "sv_gameplayfix_delayprojectiles", "1", "causes entities to not move on the same frame they are spawned, meaning that projectiles wait until the next frame to perform their first move, giving proper interpolation and rocket trails, but making weapons harder to use at low framerates"}; cvar_t sv_gameplayfix_droptofloorstartsolid = {0, "sv_gameplayfix_droptofloorstartsolid", "1", "prevents items and monsters that start in a solid area from falling out of the level (makes droptofloor treat trace_startsolid as an acceptable outcome)"}; cvar_t sv_gameplayfix_droptofloorstartsolid_nudgetocorrect = {0, "sv_gameplayfix_droptofloorstartsolid_nudgetocorrect", "1", "tries to nudge stuck items and monsters out of walls before droptofloor is performed"}; cvar_t sv_gameplayfix_easierwaterjump = {0, "sv_gameplayfix_easierwaterjump", "1", "changes water jumping to make it easier to get out of water (exactly like in QuakeWorld)"}; cvar_t sv_gameplayfix_findradiusdistancetobox = {0, "sv_gameplayfix_findradiusdistancetobox", "1", "causes findradius to check the distance to the corner of a box rather than the center of the box, makes findradius detect bmodels such as very large doors that would otherwise be unaffected by splash damage"}; cvar_t sv_gameplayfix_gravityunaffectedbyticrate = {0, "sv_gameplayfix_gravityunaffectedbyticrate", "0", "fix some ticrate issues in physics."}; cvar_t sv_gameplayfix_grenadebouncedownslopes = {0, "sv_gameplayfix_grenadebouncedownslopes", "1", "prevents MOVETYPE_BOUNCE (grenades) from getting stuck when fired down a downward sloping surface"}; cvar_t sv_gameplayfix_multiplethinksperframe = {0, "sv_gameplayfix_multiplethinksperframe", "1", "allows entities to think more often than the server framerate, primarily useful for very high fire rate weapons"}; cvar_t sv_gameplayfix_noairborncorpse = {0, "sv_gameplayfix_noairborncorpse", "1", "causes entities (corpses, items, etc) sitting ontop of moving entities (players) to fall when the moving entity (player) is no longer supporting them"}; cvar_t sv_gameplayfix_noairborncorpse_allowsuspendeditems = {0, "sv_gameplayfix_noairborncorpse_allowsuspendeditems", "1", "causes entities sitting ontop of objects that are instantaneously remove to float in midair (special hack to allow a common level design trick for floating items)"}; cvar_t sv_gameplayfix_nudgeoutofsolid = {0, "sv_gameplayfix_nudgeoutofsolid", "0", "attempts to fix physics errors (where an object ended up in solid for some reason)"}; cvar_t sv_gameplayfix_nudgeoutofsolid_separation = {0, "sv_gameplayfix_nudgeoutofsolid_separation", "0.03125", "keep objects this distance apart to prevent collision issues on seams"}; cvar_t sv_gameplayfix_q2airaccelerate = {0, "sv_gameplayfix_q2airaccelerate", "0", "Quake2-style air acceleration"}; cvar_t sv_gameplayfix_nogravityonground = {0, "sv_gameplayfix_nogravityonground", "0", "turn off gravity when on ground (to get rid of sliding)"}; cvar_t sv_gameplayfix_setmodelrealbox = {0, "sv_gameplayfix_setmodelrealbox", "1", "fixes a bug in Quake that made setmodel always set the entity box to ('-16 -16 -16', '16 16 16') rather than properly checking the model box, breaks some poorly coded mods"}; cvar_t sv_gameplayfix_slidemoveprojectiles = {0, "sv_gameplayfix_slidemoveprojectiles", "1", "allows MOVETYPE_FLY/FLYMISSILE/TOSS/BOUNCE/BOUNCEMISSILE entities to finish their move in a frame even if they hit something, fixes 'gravity accumulation' bug for grenades on steep slopes"}; cvar_t sv_gameplayfix_stepdown = {0, "sv_gameplayfix_stepdown", "0", "attempts to step down stairs, not just up them (prevents the familiar thud..thud..thud.. when running down stairs and slopes)"}; cvar_t sv_gameplayfix_stepmultipletimes = {0, "sv_gameplayfix_stepmultipletimes", "0", "applies step-up onto a ledge more than once in a single frame, when running quickly up stairs"}; cvar_t sv_gameplayfix_nostepmoveonsteepslopes = {0, "sv_gameplayfix_nostepmoveonsteepslopes", "0", "crude fix which prevents MOVETYPE_STEP (not swimming or flying) to move on slopes whose angle is bigger than 45 degree"}; cvar_t sv_gameplayfix_swiminbmodels = {0, "sv_gameplayfix_swiminbmodels", "1", "causes pointcontents (used to determine if you are in a liquid) to check bmodel entities as well as the world model, so you can swim around in (possibly moving) water bmodel entities"}; cvar_t sv_gameplayfix_upwardvelocityclearsongroundflag = {0, "sv_gameplayfix_upwardvelocityclearsongroundflag", "1", "prevents monsters, items, and most other objects from being stuck to the floor when pushed around by damage, and other situations in mods"}; cvar_t sv_gameplayfix_downtracesupportsongroundflag = {0, "sv_gameplayfix_downtracesupportsongroundflag", "1", "prevents very short moves from clearing onground (which may make the player stick to the floor at high netfps)"}; cvar_t sv_gameplayfix_q1bsptracelinereportstexture = {0, "sv_gameplayfix_q1bsptracelinereportstexture", "1", "enables mods to get accurate trace_texture results on q1bsp by using a surface-hitting traceline implementation rather than the standard solidbsp method, q3bsp always reports texture accurately"}; cvar_t sv_gameplayfix_unstickplayers = {0, "sv_gameplayfix_unstickplayers", "1", "big hack to try and fix the rare case of MOVETYPE_WALK entities getting stuck in the world clipping hull."}; cvar_t sv_gameplayfix_unstickentities = {0, "sv_gameplayfix_unstickentities", "1", "hack to check if entities are crossing world collision hull and try to move them to the right position"}; cvar_t sv_gameplayfix_fixedcheckwatertransition = {0, "sv_gameplayfix_fixedcheckwatertransition", "1", "fix two very stupid bugs in SV_CheckWaterTransition when watertype is CONTENTS_EMPTY (the bugs causes waterlevel to be 1 on first frame, -1 on second frame - the fix makes it 0 on both frames)"}; cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"}; cvar_t sv_init_frame_count = {0, "sv_init_frame_count", "2", "number of frames to run to allow everything to settle before letting clients connect"}; cvar_t sv_idealpitchscale = {0, "sv_idealpitchscale","0.8", "how much to look up/down slopes and stairs when not using freelook"}; cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "0", "whether you can step up while jumping"}; cvar_t sv_jumpvelocity = {0, "sv_jumpvelocity", "270", "cvar that can be used by QuakeC code for jump velocity"}; cvar_t sv_maxairspeed = {0, "sv_maxairspeed", "30", "maximum speed a player can accelerate to when airborn (note that it is possible to completely stop by moving the opposite direction)"}; cvar_t sv_maxrate = {CVAR_SAVE | CVAR_NOTIFY, "sv_maxrate", "1000000", "upper limit on client rate cvar, should reflect your network connection quality"}; cvar_t sv_maxspeed = {CVAR_NOTIFY, "sv_maxspeed", "320", "maximum speed a player can accelerate to when on ground (can be exceeded by tricks)"}; cvar_t sv_maxvelocity = {CVAR_NOTIFY, "sv_maxvelocity","2000", "universal speed limit on all entities"}; cvar_t sv_nostep = {CVAR_NOTIFY, "sv_nostep","0", "prevents MOVETYPE_STEP entities (monsters) from moving"}; cvar_t sv_playerphysicsqc = {CVAR_NOTIFY, "sv_playerphysicsqc", "1", "enables QuakeC function to override player physics"}; cvar_t sv_progs = {0, "sv_progs", "progs.dat", "selects which quakec progs.dat file to run" }; cvar_t sv_protocolname = {0, "sv_protocolname", "DP7", "selects network protocol to host for (values include QUAKE, QUAKEDP, NEHAHRAMOVIE, DP1 and up)"}; cvar_t sv_random_seed = {0, "sv_random_seed", "", "random seed; when set, on every map start this random seed is used to initialize the random number generator. Don't touch it unless for benchmarking or debugging"}; cvar_t sv_ratelimitlocalplayer = {0, "sv_ratelimitlocalplayer", "0", "whether to apply rate limiting to the local player in a listen server (only useful for testing)"}; cvar_t sv_sound_land = {0, "sv_sound_land", "demon/dland2.wav", "sound to play when MOVETYPE_STEP entity hits the ground at high speed (empty cvar disables the sound)"}; cvar_t sv_sound_watersplash = {0, "sv_sound_watersplash", "misc/h2ohit1.wav", "sound to play when MOVETYPE_FLY/TOSS/BOUNCE/STEP entity enters or leaves water (empty cvar disables the sound)"}; cvar_t sv_stepheight = {CVAR_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"}; cvar_t sv_stopspeed = {CVAR_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"}; cvar_t sv_wallfriction = {CVAR_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"}; cvar_t sv_wateraccelerate = {0, "sv_wateraccelerate", "-1", "rate at which a player accelerates to sv_maxspeed while in the air, if less than 0 the sv_accelerate variable is used instead"}; cvar_t sv_waterfriction = {CVAR_NOTIFY, "sv_waterfriction","-1", "how fast you slow down, if less than 0 the sv_friction variable is used instead"}; cvar_t sv_warsowbunny_airforwardaccel = {0, "sv_warsowbunny_airforwardaccel", "1.00001", "how fast you accelerate until you reach sv_maxspeed"}; cvar_t sv_warsowbunny_accel = {0, "sv_warsowbunny_accel", "0.1585", "how fast you accelerate until after reaching sv_maxspeed (it gets harder as you near sv_warsowbunny_topspeed)"}; cvar_t sv_warsowbunny_topspeed = {0, "sv_warsowbunny_topspeed", "925", "soft speed limit (can get faster with rjs and on ramps)"}; cvar_t sv_warsowbunny_turnaccel = {0, "sv_warsowbunny_turnaccel", "0", "max sharpness of turns (also master switch for the sv_warsowbunny_* mode; set this to 9 to enable)"}; cvar_t sv_warsowbunny_backtosideratio = {0, "sv_warsowbunny_backtosideratio", "0.8", "lower values make it easier to change direction without losing speed; the drawback is \"understeering\" in sharp turns"}; cvar_t sv_onlycsqcnetworking = {0, "sv_onlycsqcnetworking", "0", "disables legacy entity networking code for higher performance (except on clients, which can still be legacy)"}; cvar_t sv_areadebug = {0, "sv_areadebug", "0", "disables physics culling for debugging purposes (only for development)"}; cvar_t sys_ticrate = {CVAR_SAVE, "sys_ticrate","0.0138889", "how long a server frame is in seconds, 0.05 is 20fps server rate, 0.1 is 10fps (can not be set higher than 0.1), 0 runs as many server frames as possible (makes games against bots a little smoother, overwhelms network players), 0.0138889 matches QuakeWorld physics"}; cvar_t teamplay = {CVAR_NOTIFY, "teamplay","0", "teamplay mode, values depend on mod but typically 0 = no teams, 1 = no team damage no self damage, 2 = team damage and self damage, some mods support 3 = no team damage but can damage self"}; cvar_t timelimit = {CVAR_NOTIFY, "timelimit","0", "ends level at this time (in minutes)"}; cvar_t sv_threaded = {0, "sv_threaded", "0", "enables a separate thread for server code, improving performance, especially when hosting a game while playing, EXPERIMENTAL, may be crashy"}; cvar_t saved1 = {CVAR_SAVE, "saved1", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t saved2 = {CVAR_SAVE, "saved2", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t saved3 = {CVAR_SAVE, "saved3", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t saved4 = {CVAR_SAVE, "saved4", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t savedgamecfg = {CVAR_SAVE, "savedgamecfg", "0", "unused cvar in quake that is saved to config.cfg on exit, can be used by mods"}; cvar_t scratch1 = {0, "scratch1", "0", "unused cvar in quake, can be used by mods"}; cvar_t scratch2 = {0,"scratch2", "0", "unused cvar in quake, can be used by mods"}; cvar_t scratch3 = {0, "scratch3", "0", "unused cvar in quake, can be used by mods"}; cvar_t scratch4 = {0, "scratch4", "0", "unused cvar in quake, can be used by mods"}; cvar_t temp1 = {0, "temp1","0", "general cvar for mods to use, in stock id1 this selects which death animation to use on players (0 = random death, other values select specific death scenes)"}; cvar_t nehx00 = {0, "nehx00", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx01 = {0, "nehx01", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx02 = {0, "nehx02", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx03 = {0, "nehx03", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx04 = {0, "nehx04", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx05 = {0, "nehx05", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx06 = {0, "nehx06", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx07 = {0, "nehx07", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx08 = {0, "nehx08", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx09 = {0, "nehx09", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx10 = {0, "nehx10", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx11 = {0, "nehx11", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx12 = {0, "nehx12", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx13 = {0, "nehx13", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx14 = {0, "nehx14", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx15 = {0, "nehx15", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx16 = {0, "nehx16", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx17 = {0, "nehx17", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx18 = {0, "nehx18", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t nehx19 = {0, "nehx19", "0", "nehahra data storage cvar (used in singleplayer)"}; cvar_t cutscene = {0, "cutscene", "1", "enables cutscenes in nehahra, can be used by other mods"}; cvar_t sv_autodemo_perclient = {CVAR_SAVE, "sv_autodemo_perclient", "0", "set to 1 to enable autorecorded per-client demos (they'll start to record at the beginning of a match); set it to 2 to also record client->server packets (for debugging)"}; cvar_t sv_autodemo_perclient_nameformat = {CVAR_SAVE, "sv_autodemo_perclient_nameformat", "sv_autodemos/%Y-%m-%d_%H-%M", "The format of the sv_autodemo_perclient filename, followed by the map name, the client number and the IP address + port number, separated by underscores (the date is encoded using strftime escapes)" }; cvar_t sv_autodemo_perclient_discardable = {CVAR_SAVE, "sv_autodemo_perclient_discardable", "0", "Allow game code to decide whether a demo should be kept or discarded."}; cvar_t halflifebsp = {0, "halflifebsp", "0", "indicates the current map is hlbsp format (useful to know because of different bounding box sizes)"}; cvar_t sv_mapformat_is_quake2 = {0, "sv_mapformat_is_quake2", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors, .frame on submodels and other things)"}; cvar_t sv_mapformat_is_quake3 = {0, "sv_mapformat_is_quake3", "0", "indicates the current map is q2bsp format (useful to know because of different entity behaviors)"}; server_t sv; server_static_t svs; mempool_t *sv_mempool = NULL; extern cvar_t slowmo; extern float scr_centertime_off; // MUST match effectnameindex_t in client.h static const char *standardeffectnames[EFFECT_TOTAL] = { "", "TE_GUNSHOT", "TE_GUNSHOTQUAD", "TE_SPIKE", "TE_SPIKEQUAD", "TE_SUPERSPIKE", "TE_SUPERSPIKEQUAD", "TE_WIZSPIKE", "TE_KNIGHTSPIKE", "TE_EXPLOSION", "TE_EXPLOSIONQUAD", "TE_TAREXPLOSION", "TE_TELEPORT", "TE_LAVASPLASH", "TE_SMALLFLASH", "TE_FLAMEJET", "EF_FLAME", "TE_BLOOD", "TE_SPARK", "TE_PLASMABURN", "TE_TEI_G3", "TE_TEI_SMOKE", "TE_TEI_BIGEXPLOSION", "TE_TEI_PLASMAHIT", "EF_STARDUST", "TR_ROCKET", "TR_GRENADE", "TR_BLOOD", "TR_WIZSPIKE", "TR_SLIGHTBLOOD", "TR_KNIGHTSPIKE", "TR_VORESPIKE", "TR_NEHAHRASMOKE", "TR_NEXUIZPLASMA", "TR_GLOWTRAIL", "SVC_PARTICLE" }; #define SV_REQFUNCS 0 #define sv_reqfuncs NULL //#define SV_REQFUNCS (sizeof(sv_reqfuncs) / sizeof(const char *)) //static const char *sv_reqfuncs[] = { //}; #define SV_REQFIELDS (sizeof(sv_reqfields) / sizeof(prvm_required_field_t)) prvm_required_field_t sv_reqfields[] = { #define PRVM_DECLARE_serverglobalfloat(x) #define PRVM_DECLARE_serverglobalvector(x) #define PRVM_DECLARE_serverglobalstring(x) #define PRVM_DECLARE_serverglobaledict(x) #define PRVM_DECLARE_serverglobalfunction(x) #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) {ev_float, #x}, #define PRVM_DECLARE_serverfieldvector(x) {ev_vector, #x}, #define PRVM_DECLARE_serverfieldstring(x) {ev_string, #x}, #define PRVM_DECLARE_serverfieldedict(x) {ev_entity, #x}, #define PRVM_DECLARE_serverfieldfunction(x) {ev_function, #x}, #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; #define SV_REQGLOBALS (sizeof(sv_reqglobals) / sizeof(prvm_required_field_t)) prvm_required_field_t sv_reqglobals[] = { #define PRVM_DECLARE_serverglobalfloat(x) {ev_float, #x}, #define PRVM_DECLARE_serverglobalvector(x) {ev_vector, #x}, #define PRVM_DECLARE_serverglobalstring(x) {ev_string, #x}, #define PRVM_DECLARE_serverglobaledict(x) {ev_entity, #x}, #define PRVM_DECLARE_serverglobalfunction(x) {ev_function, #x}, #define PRVM_DECLARE_clientglobalfloat(x) #define PRVM_DECLARE_clientglobalvector(x) #define PRVM_DECLARE_clientglobalstring(x) #define PRVM_DECLARE_clientglobaledict(x) #define PRVM_DECLARE_clientglobalfunction(x) #define PRVM_DECLARE_menuglobalfloat(x) #define PRVM_DECLARE_menuglobalvector(x) #define PRVM_DECLARE_menuglobalstring(x) #define PRVM_DECLARE_menuglobaledict(x) #define PRVM_DECLARE_menuglobalfunction(x) #define PRVM_DECLARE_serverfieldfloat(x) #define PRVM_DECLARE_serverfieldvector(x) #define PRVM_DECLARE_serverfieldstring(x) #define PRVM_DECLARE_serverfieldedict(x) #define PRVM_DECLARE_serverfieldfunction(x) #define PRVM_DECLARE_clientfieldfloat(x) #define PRVM_DECLARE_clientfieldvector(x) #define PRVM_DECLARE_clientfieldstring(x) #define PRVM_DECLARE_clientfieldedict(x) #define PRVM_DECLARE_clientfieldfunction(x) #define PRVM_DECLARE_menufieldfloat(x) #define PRVM_DECLARE_menufieldvector(x) #define PRVM_DECLARE_menufieldstring(x) #define PRVM_DECLARE_menufieldedict(x) #define PRVM_DECLARE_menufieldfunction(x) #define PRVM_DECLARE_serverfunction(x) #define PRVM_DECLARE_clientfunction(x) #define PRVM_DECLARE_menufunction(x) #define PRVM_DECLARE_field(x) #define PRVM_DECLARE_global(x) #define PRVM_DECLARE_function(x) #include "prvm_offsets.h" #undef PRVM_DECLARE_serverglobalfloat #undef PRVM_DECLARE_serverglobalvector #undef PRVM_DECLARE_serverglobalstring #undef PRVM_DECLARE_serverglobaledict #undef PRVM_DECLARE_serverglobalfunction #undef PRVM_DECLARE_clientglobalfloat #undef PRVM_DECLARE_clientglobalvector #undef PRVM_DECLARE_clientglobalstring #undef PRVM_DECLARE_clientglobaledict #undef PRVM_DECLARE_clientglobalfunction #undef PRVM_DECLARE_menuglobalfloat #undef PRVM_DECLARE_menuglobalvector #undef PRVM_DECLARE_menuglobalstring #undef PRVM_DECLARE_menuglobaledict #undef PRVM_DECLARE_menuglobalfunction #undef PRVM_DECLARE_serverfieldfloat #undef PRVM_DECLARE_serverfieldvector #undef PRVM_DECLARE_serverfieldstring #undef PRVM_DECLARE_serverfieldedict #undef PRVM_DECLARE_serverfieldfunction #undef PRVM_DECLARE_clientfieldfloat #undef PRVM_DECLARE_clientfieldvector #undef PRVM_DECLARE_clientfieldstring #undef PRVM_DECLARE_clientfieldedict #undef PRVM_DECLARE_clientfieldfunction #undef PRVM_DECLARE_menufieldfloat #undef PRVM_DECLARE_menufieldvector #undef PRVM_DECLARE_menufieldstring #undef PRVM_DECLARE_menufieldedict #undef PRVM_DECLARE_menufieldfunction #undef PRVM_DECLARE_serverfunction #undef PRVM_DECLARE_clientfunction #undef PRVM_DECLARE_menufunction #undef PRVM_DECLARE_field #undef PRVM_DECLARE_global #undef PRVM_DECLARE_function }; //============================================================================ static void SV_AreaStats_f(void) { World_PrintAreaStats(&sv.world, "server"); } /* =============== SV_Init =============== */ void SV_Init (void) { // init the csqc progs cvars, since they are updated/used by the server code // TODO: fix this since this is a quick hack to make some of [515]'s broken code run ;) [9/13/2006 Black] extern cvar_t csqc_progname; //[515]: csqc crc check and right csprogs name according to progs.dat extern cvar_t csqc_progcrc; extern cvar_t csqc_progsize; extern cvar_t csqc_usedemoprogs; Cvar_RegisterVariable(&sv_worldmessage); Cvar_RegisterVariable(&sv_worldname); Cvar_RegisterVariable(&sv_worldnamenoextension); Cvar_RegisterVariable(&sv_worldbasename); Cvar_RegisterVariable (&csqc_progname); Cvar_RegisterVariable (&csqc_progcrc); Cvar_RegisterVariable (&csqc_progsize); Cvar_RegisterVariable (&csqc_usedemoprogs); Cmd_AddCommand("sv_saveentfile", SV_SaveEntFile_f, "save map entities to .ent file (to allow external editing)"); Cmd_AddCommand("sv_areastats", SV_AreaStats_f, "prints statistics on entity culling during collision traces"); Cmd_AddCommand_WithClientCommand("sv_startdownload", NULL, SV_StartDownload_f, "begins sending a file to the client (network protocol use only)"); Cmd_AddCommand_WithClientCommand("download", NULL, SV_Download_f, "downloads a specified file from the server"); Cvar_RegisterVariable (&sv_disablenotify); Cvar_RegisterVariable (&coop); Cvar_RegisterVariable (&deathmatch); Cvar_RegisterVariable (&fraglimit); Cvar_RegisterVariable (&gamecfg); Cvar_RegisterVariable (&noexit); Cvar_RegisterVariable (&nomonsters); Cvar_RegisterVariable (&pausable); Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&samelevel); Cvar_RegisterVariable (&skill); Cvar_RegisterVariable (&slowmo); Cvar_RegisterVariable (&sv_accelerate); Cvar_RegisterVariable (&sv_aim); Cvar_RegisterVariable (&sv_airaccel_qw); Cvar_RegisterVariable (&sv_airaccel_qw_stretchfactor); Cvar_RegisterVariable (&sv_airaccel_sideways_friction); Cvar_RegisterVariable (&sv_airaccelerate); Cvar_RegisterVariable (&sv_airstopaccelerate); Cvar_RegisterVariable (&sv_airstrafeaccelerate); Cvar_RegisterVariable (&sv_maxairstrafespeed); Cvar_RegisterVariable (&sv_airstrafeaccel_qw); Cvar_RegisterVariable (&sv_airspeedlimit_nonqw); Cvar_RegisterVariable (&sv_aircontrol); Cvar_RegisterVariable (&sv_aircontrol_power); Cvar_RegisterVariable (&sv_aircontrol_penalty); Cvar_RegisterVariable (&sv_allowdownloads); Cvar_RegisterVariable (&sv_allowdownloads_archive); Cvar_RegisterVariable (&sv_allowdownloads_config); Cvar_RegisterVariable (&sv_allowdownloads_dlcache); Cvar_RegisterVariable (&sv_allowdownloads_inarchive); Cvar_RegisterVariable (&sv_areagrid_mingridsize); Cvar_RegisterVariable (&sv_checkforpacketsduringsleep); Cvar_RegisterVariable (&sv_clmovement_enable); Cvar_RegisterVariable (&sv_clmovement_minping); Cvar_RegisterVariable (&sv_clmovement_minping_disabletime); Cvar_RegisterVariable (&sv_clmovement_inputtimeout); Cvar_RegisterVariable (&sv_cullentities_nevercullbmodels); Cvar_RegisterVariable (&sv_cullentities_pvs); Cvar_RegisterVariable (&sv_cullentities_stats); Cvar_RegisterVariable (&sv_cullentities_trace); Cvar_RegisterVariable (&sv_cullentities_trace_delay); Cvar_RegisterVariable (&sv_cullentities_trace_delay_players); Cvar_RegisterVariable (&sv_cullentities_trace_enlarge); Cvar_RegisterVariable (&sv_cullentities_trace_entityocclusion); Cvar_RegisterVariable (&sv_cullentities_trace_prediction); Cvar_RegisterVariable (&sv_cullentities_trace_prediction_time); Cvar_RegisterVariable (&sv_cullentities_trace_samples); Cvar_RegisterVariable (&sv_cullentities_trace_samples_extra); Cvar_RegisterVariable (&sv_cullentities_trace_samples_players); Cvar_RegisterVariable (&sv_debugmove); Cvar_RegisterVariable (&sv_echobprint); Cvar_RegisterVariable (&sv_edgefriction); Cvar_RegisterVariable (&sv_entpatch); Cvar_RegisterVariable (&sv_fixedframeratesingleplayer); Cvar_RegisterVariable (&sv_freezenonclients); Cvar_RegisterVariable (&sv_friction); Cvar_RegisterVariable (&sv_gameplayfix_blowupfallenzombies); Cvar_RegisterVariable (&sv_gameplayfix_consistentplayerprethink); Cvar_RegisterVariable (&sv_gameplayfix_delayprojectiles); Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid); Cvar_RegisterVariable (&sv_gameplayfix_droptofloorstartsolid_nudgetocorrect); Cvar_RegisterVariable (&sv_gameplayfix_easierwaterjump); Cvar_RegisterVariable (&sv_gameplayfix_findradiusdistancetobox); Cvar_RegisterVariable (&sv_gameplayfix_gravityunaffectedbyticrate); Cvar_RegisterVariable (&sv_gameplayfix_grenadebouncedownslopes); Cvar_RegisterVariable (&sv_gameplayfix_multiplethinksperframe); Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse); Cvar_RegisterVariable (&sv_gameplayfix_noairborncorpse_allowsuspendeditems); Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid); Cvar_RegisterVariable (&sv_gameplayfix_nudgeoutofsolid_separation); Cvar_RegisterVariable (&sv_gameplayfix_q2airaccelerate); Cvar_RegisterVariable (&sv_gameplayfix_nogravityonground); Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); Cvar_RegisterVariable (&sv_gameplayfix_slidemoveprojectiles); Cvar_RegisterVariable (&sv_gameplayfix_stepdown); Cvar_RegisterVariable (&sv_gameplayfix_stepmultipletimes); Cvar_RegisterVariable (&sv_gameplayfix_nostepmoveonsteepslopes); Cvar_RegisterVariable (&sv_gameplayfix_swiminbmodels); Cvar_RegisterVariable (&sv_gameplayfix_upwardvelocityclearsongroundflag); Cvar_RegisterVariable (&sv_gameplayfix_downtracesupportsongroundflag); Cvar_RegisterVariable (&sv_gameplayfix_q1bsptracelinereportstexture); Cvar_RegisterVariable (&sv_gameplayfix_unstickplayers); Cvar_RegisterVariable (&sv_gameplayfix_unstickentities); Cvar_RegisterVariable (&sv_gameplayfix_fixedcheckwatertransition); Cvar_RegisterVariable (&sv_gravity); Cvar_RegisterVariable (&sv_init_frame_count); Cvar_RegisterVariable (&sv_idealpitchscale); Cvar_RegisterVariable (&sv_jumpstep); Cvar_RegisterVariable (&sv_jumpvelocity); Cvar_RegisterVariable (&sv_maxairspeed); Cvar_RegisterVariable (&sv_maxrate); Cvar_RegisterVariable (&sv_maxspeed); Cvar_RegisterVariable (&sv_maxvelocity); Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_playerphysicsqc); Cvar_RegisterVariable (&sv_progs); Cvar_RegisterVariable (&sv_protocolname); Cvar_RegisterVariable (&sv_random_seed); Cvar_RegisterVariable (&sv_ratelimitlocalplayer); Cvar_RegisterVariable (&sv_sound_land); Cvar_RegisterVariable (&sv_sound_watersplash); Cvar_RegisterVariable (&sv_stepheight); Cvar_RegisterVariable (&sv_stopspeed); Cvar_RegisterVariable (&sv_wallfriction); Cvar_RegisterVariable (&sv_wateraccelerate); Cvar_RegisterVariable (&sv_waterfriction); Cvar_RegisterVariable (&sv_warsowbunny_airforwardaccel); Cvar_RegisterVariable (&sv_warsowbunny_accel); Cvar_RegisterVariable (&sv_warsowbunny_topspeed); Cvar_RegisterVariable (&sv_warsowbunny_turnaccel); Cvar_RegisterVariable (&sv_warsowbunny_backtosideratio); Cvar_RegisterVariable (&sv_onlycsqcnetworking); Cvar_RegisterVariable (&sv_areadebug); Cvar_RegisterVariable (&sys_ticrate); Cvar_RegisterVariable (&teamplay); Cvar_RegisterVariable (&timelimit); Cvar_RegisterVariable (&sv_threaded); Cvar_RegisterVariable (&saved1); Cvar_RegisterVariable (&saved2); Cvar_RegisterVariable (&saved3); Cvar_RegisterVariable (&saved4); Cvar_RegisterVariable (&savedgamecfg); Cvar_RegisterVariable (&scratch1); Cvar_RegisterVariable (&scratch2); Cvar_RegisterVariable (&scratch3); Cvar_RegisterVariable (&scratch4); Cvar_RegisterVariable (&temp1); // LordHavoc: Nehahra uses these to pass data around cutscene demos Cvar_RegisterVariable (&nehx00); Cvar_RegisterVariable (&nehx01); Cvar_RegisterVariable (&nehx02); Cvar_RegisterVariable (&nehx03); Cvar_RegisterVariable (&nehx04); Cvar_RegisterVariable (&nehx05); Cvar_RegisterVariable (&nehx06); Cvar_RegisterVariable (&nehx07); Cvar_RegisterVariable (&nehx08); Cvar_RegisterVariable (&nehx09); Cvar_RegisterVariable (&nehx10); Cvar_RegisterVariable (&nehx11); Cvar_RegisterVariable (&nehx12); Cvar_RegisterVariable (&nehx13); Cvar_RegisterVariable (&nehx14); Cvar_RegisterVariable (&nehx15); Cvar_RegisterVariable (&nehx16); Cvar_RegisterVariable (&nehx17); Cvar_RegisterVariable (&nehx18); Cvar_RegisterVariable (&nehx19); Cvar_RegisterVariable (&cutscene); // for Nehahra but useful to other mods as well Cvar_RegisterVariable (&sv_autodemo_perclient); Cvar_RegisterVariable (&sv_autodemo_perclient_nameformat); Cvar_RegisterVariable (&sv_autodemo_perclient_discardable); Cvar_RegisterVariable (&halflifebsp); Cvar_RegisterVariable (&sv_mapformat_is_quake2); Cvar_RegisterVariable (&sv_mapformat_is_quake3); sv_mempool = Mem_AllocPool("server", 0, NULL); } static void SV_SaveEntFile_f(void) { char vabuf[1024]; if (!sv.active || !sv.worldmodel) { Con_Print("Not running a server\n"); return; } FS_WriteFile(va(vabuf, sizeof(vabuf), "%s.ent", sv.worldnamenoextension), sv.worldmodel->brush.entities, (fs_offset_t)strlen(sv.worldmodel->brush.entities)); } /* ============================================================================= EVENT MESSAGES ============================================================================= */ /* ================== SV_StartParticle Make sure the event gets sent to all clients ================== */ void SV_StartParticle (vec3_t org, vec3_t dir, int color, int count) { int i; if (sv.datagram.cursize > MAX_PACKETFRAGMENT-18) return; MSG_WriteByte (&sv.datagram, svc_particle); MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); for (i=0 ; i<3 ; i++) MSG_WriteChar (&sv.datagram, (int)bound(-128, dir[i]*16, 127)); MSG_WriteByte (&sv.datagram, count); MSG_WriteByte (&sv.datagram, color); SV_FlushBroadcastMessages(); } /* ================== SV_StartEffect Make sure the event gets sent to all clients ================== */ void SV_StartEffect (vec3_t org, int modelindex, int startframe, int framecount, int framerate) { if (modelindex >= 256 || startframe >= 256) { if (sv.datagram.cursize > MAX_PACKETFRAGMENT-19) return; MSG_WriteByte (&sv.datagram, svc_effect2); MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); MSG_WriteShort (&sv.datagram, modelindex); MSG_WriteShort (&sv.datagram, startframe); MSG_WriteByte (&sv.datagram, framecount); MSG_WriteByte (&sv.datagram, framerate); } else { if (sv.datagram.cursize > MAX_PACKETFRAGMENT-17) return; MSG_WriteByte (&sv.datagram, svc_effect); MSG_WriteCoord (&sv.datagram, org[0], sv.protocol); MSG_WriteCoord (&sv.datagram, org[1], sv.protocol); MSG_WriteCoord (&sv.datagram, org[2], sv.protocol); MSG_WriteByte (&sv.datagram, modelindex); MSG_WriteByte (&sv.datagram, startframe); MSG_WriteByte (&sv.datagram, framecount); MSG_WriteByte (&sv.datagram, framerate); } SV_FlushBroadcastMessages(); } /* ================== SV_StartSound Each entity can have eight independant sound sources, like voice, weapon, feet, etc. Channel 0 is an auto-allocate channel, the others override anything already running on that entity/channel pair. An attenuation of 0 will play full volume everywhere in the level. Larger attenuations will drop off. (max 4 attenuation) ================== */ void SV_StartSound (prvm_edict_t *entity, int channel, const char *sample, int nvolume, float attenuation, qboolean reliable, float speed) { prvm_prog_t *prog = SVVM_prog; sizebuf_t *dest; int sound_num, field_mask, i, ent, speed4000; dest = (reliable ? &sv.reliable_datagram : &sv.datagram); if (nvolume < 0 || nvolume > 255) { Con_Printf ("SV_StartSound: volume = %i\n", nvolume); return; } if (attenuation < 0 || attenuation > 4) { Con_Printf ("SV_StartSound: attenuation = %f\n", attenuation); return; } if (!IS_CHAN(channel)) { Con_Printf ("SV_StartSound: channel = %i\n", channel); return; } channel = CHAN_ENGINE2NET(channel); if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) return; // find precache number for sound sound_num = SV_SoundIndex(sample, 1); if (!sound_num) return; ent = PRVM_NUM_FOR_EDICT(entity); speed4000 = (int)floor(speed * 4000.0f + 0.5f); field_mask = 0; if (nvolume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= SND_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) field_mask |= SND_ATTENUATION; if (speed4000 && speed4000 != 4000) field_mask |= SND_SPEEDUSHORT4000; if (ent >= 8192 || channel < 0 || channel > 7) field_mask |= SND_LARGEENTITY; if (sound_num >= 256) field_mask |= SND_LARGESOUND; // directed messages go only to the entity they are targeted on MSG_WriteByte (dest, svc_sound); MSG_WriteByte (dest, field_mask); if (field_mask & SND_VOLUME) MSG_WriteByte (dest, nvolume); if (field_mask & SND_ATTENUATION) MSG_WriteByte (dest, (int)(attenuation*64)); if (field_mask & SND_SPEEDUSHORT4000) MSG_WriteShort (dest, speed4000); if (field_mask & SND_LARGEENTITY) { MSG_WriteShort (dest, ent); MSG_WriteChar (dest, channel); } else MSG_WriteShort (dest, (ent<<3) | channel); if ((field_mask & SND_LARGESOUND) || sv.protocol == PROTOCOL_NEHAHRABJP2) MSG_WriteShort (dest, sound_num); else MSG_WriteByte (dest, sound_num); for (i = 0;i < 3;i++) MSG_WriteCoord (dest, PRVM_serveredictvector(entity, origin)[i]+0.5*(PRVM_serveredictvector(entity, mins)[i]+PRVM_serveredictvector(entity, maxs)[i]), sv.protocol); // TODO do we have to do anything here when dest is &sv.reliable_datagram? if(!reliable) SV_FlushBroadcastMessages(); } /* ================== SV_StartPointSound Nearly the same logic as SV_StartSound, except an origin instead of an entity is provided and channel is omitted. The entity sent to the client is 0 (world) and the channel is 0 (CHAN_AUTO). SND_LARGEENTITY will never occur in this function, therefore the check for it is omitted. ================== */ void SV_StartPointSound (vec3_t origin, const char *sample, int nvolume, float attenuation, float speed) { int sound_num, field_mask, i, speed4000; if (nvolume < 0 || nvolume > 255) { Con_Printf ("SV_StartPointSound: volume = %i\n", nvolume); return; } if (attenuation < 0 || attenuation > 4) { Con_Printf ("SV_StartPointSound: attenuation = %f\n", attenuation); return; } if (sv.datagram.cursize > MAX_PACKETFRAGMENT-21) return; // find precache number for sound sound_num = SV_SoundIndex(sample, 1); if (!sound_num) return; speed4000 = (int)(speed * 40.0f); field_mask = 0; if (nvolume != DEFAULT_SOUND_PACKET_VOLUME) field_mask |= SND_VOLUME; if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION) field_mask |= SND_ATTENUATION; if (sound_num >= 256) field_mask |= SND_LARGESOUND; if (speed4000 && speed4000 != 4000) field_mask |= SND_SPEEDUSHORT4000; // directed messages go only to the entity they are targeted on MSG_WriteByte (&sv.datagram, svc_sound); MSG_WriteByte (&sv.datagram, field_mask); if (field_mask & SND_VOLUME) MSG_WriteByte (&sv.datagram, nvolume); if (field_mask & SND_ATTENUATION) MSG_WriteByte (&sv.datagram, (int)(attenuation*64)); if (field_mask & SND_SPEEDUSHORT4000) MSG_WriteShort (&sv.datagram, speed4000); // Always write entnum 0 for the world entity MSG_WriteShort (&sv.datagram, (0<<3) | 0); if (field_mask & SND_LARGESOUND) MSG_WriteShort (&sv.datagram, sound_num); else MSG_WriteByte (&sv.datagram, sound_num); for (i = 0;i < 3;i++) MSG_WriteCoord (&sv.datagram, origin[i], sv.protocol); SV_FlushBroadcastMessages(); } /* ============================================================================== CLIENT SPAWNING ============================================================================== */ /* ================ SV_SendServerinfo Sends the first message from the server to a connected client. This will be sent on the initial connection and upon each server load. ================ */ void SV_SendServerinfo (client_t *client) { prvm_prog_t *prog = SVVM_prog; int i; char message[128]; char vabuf[1024]; // we know that this client has a netconnection and thus is not a bot // edicts get reallocated on level changes, so we need to update it here client->edict = PRVM_EDICT_NUM((client - svs.clients) + 1); // clear cached stuff that depends on the level client->weaponmodel[0] = 0; client->weaponmodelindex = 0; // LordHavoc: clear entityframe tracking client->latestframenum = 0; // initialize the movetime, so a speedhack can't make use of the time before this client joined client->cmd.time = sv.time; if (client->entitydatabase) EntityFrame_FreeDatabase(client->entitydatabase); if (client->entitydatabase4) EntityFrame4_FreeDatabase(client->entitydatabase4); if (client->entitydatabase5) EntityFrame5_FreeDatabase(client->entitydatabase5); memset(client->stats, 0, sizeof(client->stats)); memset(client->statsdeltabits, 0, sizeof(client->statsdeltabits)); if (sv.protocol != PROTOCOL_QUAKE && sv.protocol != PROTOCOL_QUAKEDP && sv.protocol != PROTOCOL_NEHAHRAMOVIE && sv.protocol != PROTOCOL_NEHAHRABJP && sv.protocol != PROTOCOL_NEHAHRABJP2 && sv.protocol != PROTOCOL_NEHAHRABJP3) { if (sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3) client->entitydatabase = EntityFrame_AllocDatabase(sv_mempool); else if (sv.protocol == PROTOCOL_DARKPLACES4) client->entitydatabase4 = EntityFrame4_AllocDatabase(sv_mempool); else client->entitydatabase5 = EntityFrame5_AllocDatabase(sv_mempool); } // reset csqc entity versions for (i = 0;i < prog->max_edicts;i++) { client->csqcentityscope[i] = 0; client->csqcentitysendflags[i] = 0xFFFFFF; } for (i = 0;i < NUM_CSQCENTITYDB_FRAMES;i++) { client->csqcentityframehistory[i].num = 0; client->csqcentityframehistory[i].framenum = -1; } client->csqcnumedicts = 0; client->csqcentityframehistory_next = 0; SZ_Clear (&client->netconnection->message); MSG_WriteByte (&client->netconnection->message, svc_print); dpsnprintf (message, sizeof (message), "\nServer: %s build %s (progs %i crc)\n", gamename, buildstring, prog->filecrc); MSG_WriteString (&client->netconnection->message,message); SV_StopDemoRecording(client); // to split up demos into different files if(sv_autodemo_perclient.integer) { char demofile[MAX_OSPATH]; char ipaddress[MAX_QPATH]; size_t j; // start a new demo file LHNETADDRESS_ToString(&(client->netconnection->peeraddress), ipaddress, sizeof(ipaddress), true); for(j = 0; ipaddress[j]; ++j) if(!isalnum(ipaddress[j])) ipaddress[j] = '-'; dpsnprintf (demofile, sizeof(demofile), "%s_%s_%d_%s.dem", Sys_TimeString (sv_autodemo_perclient_nameformat.string), sv.worldbasename, PRVM_NUM_FOR_EDICT(client->edict), ipaddress); SV_StartDemoRecording(client, demofile, -1); } //[515]: init csprogs according to version of svprogs, check the crc, etc. if (sv.csqc_progname[0]) { Con_DPrintf("sending csqc info to client (\"%s\" with size %i and crc %i)\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progname %s\n", sv.csqc_progname)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progsize %i\n", sv.csqc_progsize)); MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "csqc_progcrc %i\n", sv.csqc_progcrc)); if(client->sv_demo_file != NULL) { int k; static char buf[NET_MAXMESSAGE]; sizebuf_t sb; sb.data = (unsigned char *) buf; sb.maxsize = sizeof(buf); k = 0; while(MakeDownloadPacket(sv.csqc_progname, svs.csqc_progdata, sv.csqc_progsize, sv.csqc_progcrc, k++, &sb, sv.protocol)) SV_WriteDemoMessage(client, &sb, false); } //[515]: init stufftext string (it is sent before svc_serverinfo) if (PRVM_GetString(prog, PRVM_serverglobalstring(SV_InitCmd))) { MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, va(vabuf, sizeof(vabuf), "%s\n", PRVM_GetString(prog, PRVM_serverglobalstring(SV_InitCmd)))); } } //if (sv_allowdownloads.integer) // always send the info that the server supports the protocol, even if downloads are forbidden // only because of that, the CSQC exception can work { MSG_WriteByte (&client->netconnection->message, svc_stufftext); MSG_WriteString (&client->netconnection->message, "cl_serverextension_download 2\n"); } // send at this time so it's guaranteed to get executed at the right time { client_t *save; save = host_client; host_client = client; Curl_SendRequirements(); host_client = save; } MSG_WriteByte (&client->netconnection->message, svc_serverinfo); MSG_WriteLong (&client->netconnection->message, Protocol_NumberForEnum(sv.protocol)); MSG_WriteByte (&client->netconnection->message, svs.maxclients); if (!coop.integer && deathmatch.integer) MSG_WriteByte (&client->netconnection->message, GAME_DEATHMATCH); else MSG_WriteByte (&client->netconnection->message, GAME_COOP); MSG_WriteString (&client->netconnection->message,PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message))); for (i = 1;i < MAX_MODELS && sv.model_precache[i][0];i++) MSG_WriteString (&client->netconnection->message, sv.model_precache[i]); MSG_WriteByte (&client->netconnection->message, 0); for (i = 1;i < MAX_SOUNDS && sv.sound_precache[i][0];i++) MSG_WriteString (&client->netconnection->message, sv.sound_precache[i]); MSG_WriteByte (&client->netconnection->message, 0); // send music MSG_WriteByte (&client->netconnection->message, svc_cdtrack); MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); MSG_WriteByte (&client->netconnection->message, (int)PRVM_serveredictfloat(prog->edicts, sounds)); // set view // store this in clientcamera, too client->clientcamera = PRVM_NUM_FOR_EDICT(client->edict); MSG_WriteByte (&client->netconnection->message, svc_setview); MSG_WriteShort (&client->netconnection->message, client->clientcamera); MSG_WriteByte (&client->netconnection->message, svc_signonnum); MSG_WriteByte (&client->netconnection->message, 1); client->prespawned = false; // need prespawn, spawn, etc client->spawned = false; // need prespawn, spawn, etc client->begun = false; // need prespawn, spawn, etc client->sendsignon = 1; // send this message, and increment to 2, 2 will be set to 0 by the prespawn command // clear movement info until client enters the new level properly memset(&client->cmd, 0, sizeof(client->cmd)); client->movesequence = 0; client->movement_highestsequence_seen = 0; memset(&client->movement_count, 0, sizeof(client->movement_count)); #ifdef NUM_PING_TIMES for (i = 0;i < NUM_PING_TIMES;i++) client->ping_times[i] = 0; client->num_pings = 0; #endif client->ping = 0; // allow the client some time to send his keepalives, even if map loading took ages client->netconnection->timeout = realtime + net_connecttimeout.value; } /* ================ SV_ConnectClient Initializes a client_t for a new net connection. This will only be called once for a player each game, not once for each level change. ================ */ void SV_ConnectClient (int clientnum, netconn_t *netconnection) { prvm_prog_t *prog = SVVM_prog; client_t *client; int i; client = svs.clients + clientnum; // set up the client_t if (sv.loadgame) { float backupparms[NUM_SPAWN_PARMS]; memcpy(backupparms, client->spawn_parms, sizeof(backupparms)); memset(client, 0, sizeof(*client)); memcpy(client->spawn_parms, backupparms, sizeof(backupparms)); } else memset(client, 0, sizeof(*client)); client->active = true; client->netconnection = netconnection; Con_DPrintf("Client %s connected\n", client->netconnection ? client->netconnection->address : "botclient"); if(client->netconnection && client->netconnection->crypto.authenticated) { Con_Printf("%s connection to %s has been established: client is %s@%s%.*s, I am %.*s@%s%.*s\n", client->netconnection->crypto.use_aes ? "Encrypted" : "Authenticated", client->netconnection->address, client->netconnection->crypto.client_idfp[0] ? client->netconnection->crypto.client_idfp : "-", (client->netconnection->crypto.client_issigned || !client->netconnection->crypto.client_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, client->netconnection->crypto.client_keyfp[0] ? client->netconnection->crypto.client_keyfp : "-", crypto_keyfp_recommended_length, client->netconnection->crypto.server_idfp[0] ? client->netconnection->crypto.server_idfp : "-", (client->netconnection->crypto.server_issigned || !client->netconnection->crypto.server_keyfp[0]) ? "" : "~", crypto_keyfp_recommended_length, client->netconnection->crypto.server_keyfp[0] ? client->netconnection->crypto.server_keyfp : "-" ); } strlcpy(client->name, "unconnected", sizeof(client->name)); strlcpy(client->old_name, "unconnected", sizeof(client->old_name)); client->prespawned = false; client->spawned = false; client->begun = false; client->edict = PRVM_EDICT_NUM(clientnum+1); if (client->netconnection) client->netconnection->message.allowoverflow = true; // we can catch it // prepare the unreliable message buffer client->unreliablemsg.data = client->unreliablemsg_data; client->unreliablemsg.maxsize = sizeof(client->unreliablemsg_data); // updated by receiving "rate" command from client, this is also the default if not using a DP client client->rate = 1000000000; client->connecttime = realtime; if (!sv.loadgame) { // call the progs to get default spawn parms for the new client // set self to world to intentionally cause errors with broken SetNewParms code in some mods PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = 0; prog->ExecuteProgram(prog, PRVM_serverfunction(SetNewParms), "QC function SetNewParms is missing"); for (i=0 ; ispawn_parms[i] = (&PRVM_serverglobalfloat(parm1))[i]; // set up the entity for this client (including .colormap, .team, etc) PRVM_ED_ClearEdict(prog, client->edict); } // don't call SendServerinfo for a fresh botclient because its fields have // not been set up by the qc yet if (client->netconnection) SV_SendServerinfo (client); else client->prespawned = client->spawned = client->begun = true; } /* =============================================================================== FRAME UPDATES =============================================================================== */ /* ============================================================================= The PVS must include a small area around the client to allow head bobbing or other small motion on the client side. Otherwise, a bob might cause an entity that should be visible to not show up, especially when the bob crosses a waterline. ============================================================================= */ static qboolean SV_PrepareEntityForSending (prvm_edict_t *ent, entity_state_t *cs, int enumber) { prvm_prog_t *prog = SVVM_prog; int i; unsigned int sendflags; unsigned int version; unsigned int modelindex, effects, flags, glowsize, lightstyle, lightpflags, light[4], specialvisibilityradius; unsigned int customizeentityforclient; unsigned int sendentity; float f; prvm_vec_t *v; vec3_t cullmins, cullmaxs; dp_model_t *model; // fast path for games that do not use legacy entity networking // note: still networks clients even if they are legacy sendentity = PRVM_serveredictfunction(ent, SendEntity); if (sv_onlycsqcnetworking.integer && !sendentity && enumber > svs.maxclients) return false; // this 2 billion unit check is actually to detect NAN origins // (we really don't want to send those) if (!(VectorLength2(PRVM_serveredictvector(ent, origin)) < 2000000000.0*2000000000.0)) return false; // EF_NODRAW prevents sending for any reason except for your own // client, so we must keep all clients in this superset effects = (unsigned)PRVM_serveredictfloat(ent, effects); // we can omit invisible entities with no effects that are not clients // LordHavoc: this could kill tags attached to an invisible entity, I // just hope we never have to support that case i = (int)PRVM_serveredictfloat(ent, modelindex); modelindex = (i >= 1 && i < MAX_MODELS && PRVM_serveredictstring(ent, model) && *PRVM_GetString(prog, PRVM_serveredictstring(ent, model)) && sv.models[i]) ? i : 0; flags = 0; i = (int)(PRVM_serveredictfloat(ent, glow_size) * 0.25f); glowsize = (unsigned char)bound(0, i, 255); if (PRVM_serveredictfloat(ent, glow_trail)) flags |= RENDER_GLOWTRAIL; if (PRVM_serveredictedict(ent, viewmodelforclient)) flags |= RENDER_VIEWMODEL; v = PRVM_serveredictvector(ent, color); f = v[0]*256; light[0] = (unsigned short)bound(0, f, 65535); f = v[1]*256; light[1] = (unsigned short)bound(0, f, 65535); f = v[2]*256; light[2] = (unsigned short)bound(0, f, 65535); f = PRVM_serveredictfloat(ent, light_lev); light[3] = (unsigned short)bound(0, f, 65535); lightstyle = (unsigned char)PRVM_serveredictfloat(ent, style); lightpflags = (unsigned char)PRVM_serveredictfloat(ent, pflags); if (gamemode == GAME_TENEBRAE) { // tenebrae's EF_FULLDYNAMIC conflicts with Q2's EF_NODRAW if (effects & 16) { effects &= ~16; lightpflags |= PFLAGS_FULLDYNAMIC; } // tenebrae's EF_GREEN conflicts with DP's EF_ADDITIVE if (effects & 32) { effects &= ~32; light[0] = (int)(0.2*256); light[1] = (int)(1.0*256); light[2] = (int)(0.2*256); light[3] = 200; lightpflags |= PFLAGS_FULLDYNAMIC; } } specialvisibilityradius = 0; if (lightpflags & PFLAGS_FULLDYNAMIC) specialvisibilityradius = max(specialvisibilityradius, light[3]); if (glowsize) specialvisibilityradius = max(specialvisibilityradius, glowsize * 4); if (flags & RENDER_GLOWTRAIL) specialvisibilityradius = max(specialvisibilityradius, 100); if (effects & (EF_BRIGHTFIELD | EF_MUZZLEFLASH | EF_BRIGHTLIGHT | EF_DIMLIGHT | EF_RED | EF_BLUE | EF_FLAME | EF_STARDUST)) { if (effects & EF_BRIGHTFIELD) specialvisibilityradius = max(specialvisibilityradius, 80); if (effects & EF_MUZZLEFLASH) specialvisibilityradius = max(specialvisibilityradius, 100); if (effects & EF_BRIGHTLIGHT) specialvisibilityradius = max(specialvisibilityradius, 400); if (effects & EF_DIMLIGHT) specialvisibilityradius = max(specialvisibilityradius, 200); if (effects & EF_RED) specialvisibilityradius = max(specialvisibilityradius, 200); if (effects & EF_BLUE) specialvisibilityradius = max(specialvisibilityradius, 200); if (effects & EF_FLAME) specialvisibilityradius = max(specialvisibilityradius, 250); if (effects & EF_STARDUST) specialvisibilityradius = max(specialvisibilityradius, 100); } // early culling checks // (final culling is done by SV_MarkWriteEntityStateToClient) customizeentityforclient = PRVM_serveredictfunction(ent, customizeentityforclient); if (!customizeentityforclient && enumber > svs.maxclients && (!modelindex && !specialvisibilityradius)) return false; *cs = defaultstate; cs->active = ACTIVE_NETWORK; cs->number = enumber; VectorCopy(PRVM_serveredictvector(ent, origin), cs->origin); VectorCopy(PRVM_serveredictvector(ent, angles), cs->angles); cs->flags = flags; cs->effects = effects; cs->colormap = (unsigned)PRVM_serveredictfloat(ent, colormap); cs->modelindex = modelindex; cs->skin = (unsigned)PRVM_serveredictfloat(ent, skin); cs->frame = (unsigned)PRVM_serveredictfloat(ent, frame); cs->viewmodelforclient = PRVM_serveredictedict(ent, viewmodelforclient); cs->exteriormodelforclient = PRVM_serveredictedict(ent, exteriormodeltoclient); cs->nodrawtoclient = PRVM_serveredictedict(ent, nodrawtoclient); cs->drawonlytoclient = PRVM_serveredictedict(ent, drawonlytoclient); cs->customizeentityforclient = customizeentityforclient; cs->tagentity = PRVM_serveredictedict(ent, tag_entity); cs->tagindex = (unsigned char)PRVM_serveredictfloat(ent, tag_index); cs->glowsize = glowsize; cs->traileffectnum = PRVM_serveredictfloat(ent, traileffectnum); // don't need to init cs->colormod because the defaultstate did that for us //cs->colormod[0] = cs->colormod[1] = cs->colormod[2] = 32; v = PRVM_serveredictvector(ent, colormod); if (VectorLength2(v)) { i = (int)(v[0] * 32.0f);cs->colormod[0] = bound(0, i, 255); i = (int)(v[1] * 32.0f);cs->colormod[1] = bound(0, i, 255); i = (int)(v[2] * 32.0f);cs->colormod[2] = bound(0, i, 255); } // don't need to init cs->glowmod because the defaultstate did that for us //cs->glowmod[0] = cs->glowmod[1] = cs->glowmod[2] = 32; v = PRVM_serveredictvector(ent, glowmod); if (VectorLength2(v)) { i = (int)(v[0] * 32.0f);cs->glowmod[0] = bound(0, i, 255); i = (int)(v[1] * 32.0f);cs->glowmod[1] = bound(0, i, 255); i = (int)(v[2] * 32.0f);cs->glowmod[2] = bound(0, i, 255); } cs->modelindex = modelindex; cs->alpha = 255; f = (PRVM_serveredictfloat(ent, alpha) * 255.0f); if (f) { i = (int)f; cs->alpha = (unsigned char)bound(0, i, 255); } // halflife f = (PRVM_serveredictfloat(ent, renderamt)); if (f) { i = (int)f; cs->alpha = (unsigned char)bound(0, i, 255); } cs->scale = 16; f = (PRVM_serveredictfloat(ent, scale) * 16.0f); if (f) { i = (int)f; cs->scale = (unsigned char)bound(0, i, 255); } cs->glowcolor = 254; f = PRVM_serveredictfloat(ent, glow_color); if (f) cs->glowcolor = (int)f; if (PRVM_serveredictfloat(ent, fullbright)) cs->effects |= EF_FULLBRIGHT; f = PRVM_serveredictfloat(ent, modelflags); if (f) cs->effects |= ((unsigned int)f & 0xff) << 24; if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) cs->flags |= RENDER_STEP; if (cs->number != sv.writeentitiestoclient_cliententitynumber && (cs->effects & EF_LOWPRECISION) && cs->origin[0] >= -32768 && cs->origin[1] >= -32768 && cs->origin[2] >= -32768 && cs->origin[0] <= 32767 && cs->origin[1] <= 32767 && cs->origin[2] <= 32767) cs->flags |= RENDER_LOWPRECISION; if (PRVM_serveredictfloat(ent, colormap) >= 1024) cs->flags |= RENDER_COLORMAPPED; if (cs->viewmodelforclient) cs->flags |= RENDER_VIEWMODEL; // show relative to the view if (PRVM_serveredictfloat(ent, sendcomplexanimation)) { cs->flags |= RENDER_COMPLEXANIMATION; if (PRVM_serveredictfloat(ent, skeletonindex) >= 1) cs->skeletonobject = ent->priv.server->skeleton; cs->framegroupblend[0].frame = PRVM_serveredictfloat(ent, frame); cs->framegroupblend[1].frame = PRVM_serveredictfloat(ent, frame2); cs->framegroupblend[2].frame = PRVM_serveredictfloat(ent, frame3); cs->framegroupblend[3].frame = PRVM_serveredictfloat(ent, frame4); cs->framegroupblend[0].start = PRVM_serveredictfloat(ent, frame1time); cs->framegroupblend[1].start = PRVM_serveredictfloat(ent, frame2time); cs->framegroupblend[2].start = PRVM_serveredictfloat(ent, frame3time); cs->framegroupblend[3].start = PRVM_serveredictfloat(ent, frame4time); cs->framegroupblend[1].lerp = PRVM_serveredictfloat(ent, lerpfrac); cs->framegroupblend[2].lerp = PRVM_serveredictfloat(ent, lerpfrac3); cs->framegroupblend[3].lerp = PRVM_serveredictfloat(ent, lerpfrac4); cs->framegroupblend[0].lerp = 1.0f - cs->framegroupblend[1].lerp - cs->framegroupblend[2].lerp - cs->framegroupblend[3].lerp; cs->frame = 0; // don't need the legacy frame } cs->light[0] = light[0]; cs->light[1] = light[1]; cs->light[2] = light[2]; cs->light[3] = light[3]; cs->lightstyle = lightstyle; cs->lightpflags = lightpflags; cs->specialvisibilityradius = specialvisibilityradius; // calculate the visible box of this entity (don't use the physics box // as that is often smaller than a model, and would not count // specialvisibilityradius) if ((model = SV_GetModelByIndex(modelindex)) && (model->type != mod_null)) { float scale = cs->scale * (1.0f / 16.0f); if (cs->angles[0] || cs->angles[2]) // pitch and roll { VectorMA(cs->origin, scale, model->rotatedmins, cullmins); VectorMA(cs->origin, scale, model->rotatedmaxs, cullmaxs); } else if (cs->angles[1] || ((effects | model->effects) & EF_ROTATE)) { VectorMA(cs->origin, scale, model->yawmins, cullmins); VectorMA(cs->origin, scale, model->yawmaxs, cullmaxs); } else { VectorMA(cs->origin, scale, model->normalmins, cullmins); VectorMA(cs->origin, scale, model->normalmaxs, cullmaxs); } } else { // if there is no model (or it could not be loaded), use the physics box VectorAdd(cs->origin, PRVM_serveredictvector(ent, mins), cullmins); VectorAdd(cs->origin, PRVM_serveredictvector(ent, maxs), cullmaxs); } if (specialvisibilityradius) { cullmins[0] = min(cullmins[0], cs->origin[0] - specialvisibilityradius); cullmins[1] = min(cullmins[1], cs->origin[1] - specialvisibilityradius); cullmins[2] = min(cullmins[2], cs->origin[2] - specialvisibilityradius); cullmaxs[0] = max(cullmaxs[0], cs->origin[0] + specialvisibilityradius); cullmaxs[1] = max(cullmaxs[1], cs->origin[1] + specialvisibilityradius); cullmaxs[2] = max(cullmaxs[2], cs->origin[2] + specialvisibilityradius); } // calculate center of bbox for network prioritization purposes VectorMAM(0.5f, cullmins, 0.5f, cullmaxs, cs->netcenter); // if culling box has moved, update pvs cluster links if (!VectorCompare(cullmins, ent->priv.server->cullmins) || !VectorCompare(cullmaxs, ent->priv.server->cullmaxs)) { VectorCopy(cullmins, ent->priv.server->cullmins); VectorCopy(cullmaxs, ent->priv.server->cullmaxs); // a value of -1 for pvs_numclusters indicates that the links are not // cached, and should be re-tested each time, this is the case if the // culling box touches too many pvs clusters to store, or if the world // model does not support FindBoxClusters ent->priv.server->pvs_numclusters = -1; if (sv.worldmodel && sv.worldmodel->brush.FindBoxClusters) { i = sv.worldmodel->brush.FindBoxClusters(sv.worldmodel, cullmins, cullmaxs, MAX_ENTITYCLUSTERS, ent->priv.server->pvs_clusterlist); if (i <= MAX_ENTITYCLUSTERS) ent->priv.server->pvs_numclusters = i; } } // we need to do some csqc entity upkeep here // get self.SendFlags and clear them // (to let the QC know that they've been read) if (sendentity) { sendflags = (unsigned int)PRVM_serveredictfloat(ent, SendFlags); PRVM_serveredictfloat(ent, SendFlags) = 0; // legacy self.Version system if ((version = (unsigned int)PRVM_serveredictfloat(ent, Version))) { if (sv.csqcentityversion[enumber] != version) sendflags = 0xFFFFFF; sv.csqcentityversion[enumber] = version; } // move sendflags into the per-client sendflags if (sendflags) for (i = 0;i < svs.maxclients;i++) svs.clients[i].csqcentitysendflags[enumber] |= sendflags; // mark it as inactive for non-csqc networking cs->active = ACTIVE_SHARED; } return true; } static void SV_PrepareEntitiesForSending(void) { prvm_prog_t *prog = SVVM_prog; int e; prvm_edict_t *ent; // send all entities that touch the pvs sv.numsendentities = 0; sv.sendentitiesindex[0] = NULL; memset(sv.sendentitiesindex, 0, prog->num_edicts * sizeof(*sv.sendentitiesindex)); for (e = 1, ent = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ent = PRVM_NEXT_EDICT(ent)) { if (!ent->priv.server->free && SV_PrepareEntityForSending(ent, sv.sendentities + sv.numsendentities, e)) { sv.sendentitiesindex[e] = sv.sendentities + sv.numsendentities; sv.numsendentities++; } } } #define MAX_LINEOFSIGHTTRACES 64 qboolean SV_CanSeeBox(int numtraces, vec_t enlarge, vec3_t eye, vec3_t entboxmins, vec3_t entboxmaxs) { prvm_prog_t *prog = SVVM_prog; float pitchsign; float alpha; float starttransformed[3], endtransformed[3]; int blocked = 0; int traceindex; int originalnumtouchedicts; int numtouchedicts = 0; int touchindex; matrix4x4_t matrix, imatrix; dp_model_t *model; prvm_edict_t *touch; static prvm_edict_t *touchedicts[MAX_EDICTS]; vec3_t boxmins, boxmaxs; vec3_t clipboxmins, clipboxmaxs; vec3_t endpoints[MAX_LINEOFSIGHTTRACES]; numtraces = min(numtraces, MAX_LINEOFSIGHTTRACES); // expand the box a little boxmins[0] = (enlarge+1) * entboxmins[0] - enlarge * entboxmaxs[0]; boxmaxs[0] = (enlarge+1) * entboxmaxs[0] - enlarge * entboxmins[0]; boxmins[1] = (enlarge+1) * entboxmins[1] - enlarge * entboxmaxs[1]; boxmaxs[1] = (enlarge+1) * entboxmaxs[1] - enlarge * entboxmins[1]; boxmins[2] = (enlarge+1) * entboxmins[2] - enlarge * entboxmaxs[2]; boxmaxs[2] = (enlarge+1) * entboxmaxs[2] - enlarge * entboxmins[2]; VectorMAM(0.5f, boxmins, 0.5f, boxmaxs, endpoints[0]); for (traceindex = 1;traceindex < numtraces;traceindex++) VectorSet(endpoints[traceindex], lhrandom(boxmins[0], boxmaxs[0]), lhrandom(boxmins[1], boxmaxs[1]), lhrandom(boxmins[2], boxmaxs[2])); // calculate sweep box for the entire swarm of traces VectorCopy(eye, clipboxmins); VectorCopy(eye, clipboxmaxs); for (traceindex = 0;traceindex < numtraces;traceindex++) { clipboxmins[0] = min(clipboxmins[0], endpoints[traceindex][0]); clipboxmins[1] = min(clipboxmins[1], endpoints[traceindex][1]); clipboxmins[2] = min(clipboxmins[2], endpoints[traceindex][2]); clipboxmaxs[0] = max(clipboxmaxs[0], endpoints[traceindex][0]); clipboxmaxs[1] = max(clipboxmaxs[1], endpoints[traceindex][1]); clipboxmaxs[2] = max(clipboxmaxs[2], endpoints[traceindex][2]); } // get the list of entities in the sweep box if (sv_cullentities_trace_entityocclusion.integer) numtouchedicts = SV_EntitiesInBox(clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts); if (numtouchedicts > MAX_EDICTS) { // this never happens Con_Printf("SV_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS); numtouchedicts = MAX_EDICTS; } // iterate the entities found in the sweep box and filter them originalnumtouchedicts = numtouchedicts; numtouchedicts = 0; for (touchindex = 0;touchindex < originalnumtouchedicts;touchindex++) { touch = touchedicts[touchindex]; if (PRVM_serveredictfloat(touch, solid) != SOLID_BSP) continue; model = SV_GetModelFromEdict(touch); if (!model || !model->brush.TraceLineOfSight) continue; // skip obviously transparent entities alpha = PRVM_serveredictfloat(touch, alpha); if (alpha && alpha < 1) continue; if ((int)PRVM_serveredictfloat(touch, effects) & EF_ADDITIVE) continue; touchedicts[numtouchedicts++] = touch; } // now that we have a filtered list of "interesting" entities, fire each // ray against all of them, this gives us an early-out case when something // is visible (which it often is) for (traceindex = 0;traceindex < numtraces;traceindex++) { // check world occlusion if (sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight) if (!sv.worldmodel->brush.TraceLineOfSight(sv.worldmodel, eye, endpoints[traceindex])) continue; for (touchindex = 0;touchindex < numtouchedicts;touchindex++) { touch = touchedicts[touchindex]; model = SV_GetModelFromEdict(touch); if(model && model->brush.TraceLineOfSight) { // get the entity matrix pitchsign = SV_GetPitchSign(prog, touch); Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_serveredictvector(touch, origin)[0], PRVM_serveredictvector(touch, origin)[1], PRVM_serveredictvector(touch, origin)[2], pitchsign * PRVM_serveredictvector(touch, angles)[0], PRVM_serveredictvector(touch, angles)[1], PRVM_serveredictvector(touch, angles)[2], 1); Matrix4x4_Invert_Simple(&imatrix, &matrix); // see if the ray hits this entity Matrix4x4_Transform(&imatrix, eye, starttransformed); Matrix4x4_Transform(&imatrix, endpoints[traceindex], endtransformed); if (!model->brush.TraceLineOfSight(model, starttransformed, endtransformed)) { blocked++; break; } } } // check if the ray was blocked if (touchindex < numtouchedicts) continue; // return if the ray was not blocked return true; } // no rays survived return false; } static void SV_MarkWriteEntityStateToClient(entity_state_t *s) { prvm_prog_t *prog = SVVM_prog; int isbmodel; dp_model_t *model; prvm_edict_t *ed; if (sv.sententitiesconsideration[s->number] == sv.sententitiesmark) return; sv.sententitiesconsideration[s->number] = sv.sententitiesmark; sv.writeentitiestoclient_stats_totalentities++; if (s->customizeentityforclient) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = s->number; PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; prog->ExecuteProgram(prog, s->customizeentityforclient, "customizeentityforclient: NULL function"); if(!PRVM_G_FLOAT(OFS_RETURN) || !SV_PrepareEntityForSending(PRVM_EDICT_NUM(s->number), s, s->number)) return; } // never reject player if (s->number != sv.writeentitiestoclient_cliententitynumber) { // check various rejection conditions if (s->nodrawtoclient == sv.writeentitiestoclient_cliententitynumber) return; if (s->drawonlytoclient && s->drawonlytoclient != sv.writeentitiestoclient_cliententitynumber) return; if (s->effects & EF_NODRAW) return; // LordHavoc: only send entities with a model or important effects if (!s->modelindex && s->specialvisibilityradius == 0) return; isbmodel = (model = SV_GetModelByIndex(s->modelindex)) != NULL && model->name[0] == '*'; // viewmodels don't have visibility checking if (s->viewmodelforclient) { if (s->viewmodelforclient != sv.writeentitiestoclient_cliententitynumber) return; } else if (s->tagentity) { // tag attached entities simply check their parent if (!sv.sendentitiesindex[s->tagentity]) return; SV_MarkWriteEntityStateToClient(sv.sendentitiesindex[s->tagentity]); if (sv.sententities[s->tagentity] != sv.sententitiesmark) return; } // always send world submodels in newer protocols because they don't // generate much traffic (in old protocols they hog bandwidth) // but only if sv_cullentities_nevercullbmodels is off else if (!(s->effects & EF_NODEPTHTEST) && (!isbmodel || !sv_cullentities_nevercullbmodels.integer || sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE)) { // entity has survived every check so far, check if visible ed = PRVM_EDICT_NUM(s->number); // if not touching a visible leaf if (sv_cullentities_pvs.integer && !r_novis.integer && !r_trippy.integer && sv.writeentitiestoclient_pvsbytes) { if (ed->priv.server->pvs_numclusters < 0) { // entity too big for clusters list if (sv.worldmodel && sv.worldmodel->brush.BoxTouchingPVS && !sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, sv.writeentitiestoclient_pvs, ed->priv.server->cullmins, ed->priv.server->cullmaxs)) { sv.writeentitiestoclient_stats_culled_pvs++; return; } } else { int i; // check cached clusters list for (i = 0;i < ed->priv.server->pvs_numclusters;i++) if (CHECKPVSBIT(sv.writeentitiestoclient_pvs, ed->priv.server->pvs_clusterlist[i])) break; if (i == ed->priv.server->pvs_numclusters) { sv.writeentitiestoclient_stats_culled_pvs++; return; } } } // or not seen by random tracelines if (sv_cullentities_trace.integer && !isbmodel && sv.worldmodel && sv.worldmodel->brush.TraceLineOfSight && !r_trippy.integer) { int samples = s->number <= svs.maxclients ? sv_cullentities_trace_samples_players.integer : s->specialvisibilityradius ? sv_cullentities_trace_samples_extra.integer : sv_cullentities_trace_samples.integer; float enlarge = sv_cullentities_trace_enlarge.value; if(samples > 0) { int eyeindex; for (eyeindex = 0;eyeindex < sv.writeentitiestoclient_numeyes;eyeindex++) if(SV_CanSeeBox(samples, enlarge, sv.writeentitiestoclient_eyes[eyeindex], ed->priv.server->cullmins, ed->priv.server->cullmaxs)) break; if(eyeindex < sv.writeentitiestoclient_numeyes) svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number] = realtime + ( s->number <= svs.maxclients ? sv_cullentities_trace_delay_players.value : sv_cullentities_trace_delay.value ); else if (realtime > svs.clients[sv.writeentitiestoclient_clientnumber].visibletime[s->number]) { sv.writeentitiestoclient_stats_culled_trace++; return; } } } } } // this just marks it for sending // FIXME: it would be more efficient to send here, but the entity // compressor isn't that flexible sv.writeentitiestoclient_stats_visibleentities++; sv.sententities[s->number] = sv.sententitiesmark; } #if MAX_LEVELNETWORKEYES > 0 #define MAX_EYE_RECURSION 1 // increase if recursion gets supported by portals static void SV_AddCameraEyes(void) { prvm_prog_t *prog = SVVM_prog; int e, i, j, k; prvm_edict_t *ed; static int cameras[MAX_LEVELNETWORKEYES]; static vec3_t camera_origins[MAX_LEVELNETWORKEYES]; static int eye_levels[MAX_CLIENTNETWORKEYES]; int n_cameras = 0; vec3_t mi, ma; for(i = 0; i < sv.writeentitiestoclient_numeyes; ++i) eye_levels[i] = 0; // check line of sight to portal entities and add them to PVS for (e = 1, ed = PRVM_NEXT_EDICT(prog->edicts);e < prog->num_edicts;e++, ed = PRVM_NEXT_EDICT(ed)) { if (!ed->priv.server->free) { if(PRVM_serveredictfunction(ed, camera_transform)) { PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = e; PRVM_serverglobaledict(other) = sv.writeentitiestoclient_cliententitynumber; VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_serverglobalvector(trace_endpos)); VectorCopy(sv.writeentitiestoclient_eyes[0], PRVM_G_VECTOR(OFS_PARM0)); VectorClear(PRVM_G_VECTOR(OFS_PARM1)); prog->ExecuteProgram(prog, PRVM_serveredictfunction(ed, camera_transform), "QC function e.camera_transform is missing"); if(!VectorCompare(PRVM_serverglobalvector(trace_endpos), sv.writeentitiestoclient_eyes[0])) { VectorCopy(PRVM_serverglobalvector(trace_endpos), camera_origins[n_cameras]); cameras[n_cameras] = e; ++n_cameras; if(n_cameras >= MAX_LEVELNETWORKEYES) break; } } } } if(!n_cameras) return; // i is loop counter, is reset to 0 when an eye got added // j is camera index to check for(i = 0, j = 0; sv.writeentitiestoclient_numeyes < MAX_CLIENTNETWORKEYES && i < n_cameras; ++i, ++j, j %= n_cameras) { if(!cameras[j]) continue; ed = PRVM_EDICT_NUM(cameras[j]); VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, mins), mi); VectorAdd(PRVM_serveredictvector(ed, origin), PRVM_serveredictvector(ed, maxs), ma); for(k = 0; k < sv.writeentitiestoclient_numeyes; ++k) if(eye_levels[k] <= MAX_EYE_RECURSION) { if(SV_CanSeeBox(sv_cullentities_trace_samples.integer, sv_cullentities_trace_enlarge.value, sv.writeentitiestoclient_eyes[k], mi, ma)) { eye_levels[sv.writeentitiestoclient_numeyes] = eye_levels[k] + 1; VectorCopy(camera_origins[j], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); // Con_Printf("added eye %d: %f %f %f because we can see %f %f %f .. %f %f %f from eye %d\n", j, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][0], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][1], sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes][2], mi[0], mi[1], mi[2], ma[0], ma[1], ma[2], k); sv.writeentitiestoclient_numeyes++; cameras[j] = 0; i = 0; break; } } } } #else void SV_AddCameraEyes(void) { } #endif static void SV_WriteEntitiesToClient(client_t *client, prvm_edict_t *clent, sizebuf_t *msg, int maxsize) { prvm_prog_t *prog = SVVM_prog; qboolean need_empty = false; int i, numsendstates, numcsqcsendstates; entity_state_t *s; prvm_edict_t *camera; qboolean success; vec3_t eye; // if there isn't enough space to accomplish anything, skip it if (msg->cursize + 25 > maxsize) return; sv.writeentitiestoclient_msg = msg; sv.writeentitiestoclient_clientnumber = client - svs.clients; sv.writeentitiestoclient_stats_culled_pvs = 0; sv.writeentitiestoclient_stats_culled_trace = 0; sv.writeentitiestoclient_stats_visibleentities = 0; sv.writeentitiestoclient_stats_totalentities = 0; sv.writeentitiestoclient_numeyes = 0; // get eye location sv.writeentitiestoclient_cliententitynumber = PRVM_EDICT_TO_PROG(clent); // LordHavoc: for comparison purposes camera = PRVM_EDICT_NUM( client->clientcamera ); VectorAdd(PRVM_serveredictvector(camera, origin), PRVM_serveredictvector(clent, view_ofs), eye); sv.writeentitiestoclient_pvsbytes = 0; // get the PVS values for the eye location, later FatPVS calls will merge if (sv.worldmodel && sv.worldmodel->brush.FatPVS) sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, eye, 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); // add the eye to a list for SV_CanSeeBox tests VectorCopy(eye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); sv.writeentitiestoclient_numeyes++; // calculate predicted eye origin for SV_CanSeeBox tests if (sv_cullentities_trace_prediction.integer) { vec_t predtime = bound(0, host_client->ping, sv_cullentities_trace_prediction_time.value); vec3_t predeye; VectorMA(eye, predtime, PRVM_serveredictvector(camera, velocity), predeye); if (SV_CanSeeBox(1, 0, eye, predeye, predeye)) { VectorCopy(predeye, sv.writeentitiestoclient_eyes[sv.writeentitiestoclient_numeyes]); sv.writeentitiestoclient_numeyes++; } //if (!sv.writeentitiestoclient_useprediction) // Con_DPrintf("Trying to walk into solid in a pingtime... not predicting for culling\n"); } SV_AddCameraEyes(); // build PVS from the new eyes if (sv.worldmodel && sv.worldmodel->brush.FatPVS) for(i = 1; i < sv.writeentitiestoclient_numeyes; ++i) sv.writeentitiestoclient_pvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, sv.writeentitiestoclient_eyes[i], 8, sv.writeentitiestoclient_pvs, sizeof(sv.writeentitiestoclient_pvs), sv.writeentitiestoclient_pvsbytes != 0); sv.sententitiesmark++; for (i = 0;i < sv.numsendentities;i++) SV_MarkWriteEntityStateToClient(sv.sendentities + i); numsendstates = 0; numcsqcsendstates = 0; for (i = 0;i < sv.numsendentities;i++) { s = &sv.sendentities[i]; if (sv.sententities[s->number] == sv.sententitiesmark) { if(s->active == ACTIVE_NETWORK) { if (s->exteriormodelforclient) { if (s->exteriormodelforclient == sv.writeentitiestoclient_cliententitynumber) s->flags |= RENDER_EXTERIORMODEL; else s->flags &= ~RENDER_EXTERIORMODEL; } sv.writeentitiestoclient_sendstates[numsendstates++] = s; } else if(sv.sendentities[i].active == ACTIVE_SHARED) sv.writeentitiestoclient_csqcsendstates[numcsqcsendstates++] = s->number; else Con_Printf("entity %d is in sv.sendentities and marked, but not active, please breakpoint me\n", s->number); } } if (sv_cullentities_stats.integer) Con_Printf("client \"%s\" entities: %d total, %d visible, %d culled by: %d pvs %d trace\n", client->name, sv.writeentitiestoclient_stats_totalentities, sv.writeentitiestoclient_stats_visibleentities, sv.writeentitiestoclient_stats_culled_pvs + sv.writeentitiestoclient_stats_culled_trace, sv.writeentitiestoclient_stats_culled_pvs, sv.writeentitiestoclient_stats_culled_trace); if(client->entitydatabase5) need_empty = EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, client->entitydatabase5->latestframenum + 1); else EntityFrameCSQC_WriteFrame(msg, maxsize, numcsqcsendstates, sv.writeentitiestoclient_csqcsendstates, 0); // force every 16th frame to be not empty (or cl_movement replay takes // too long) // BTW, this should normally not kick in any more due to the check // below, except if the client stopped sending movement frames if(client->num_skippedentityframes >= 16) need_empty = true; // help cl_movement a bit more if(client->movesequence != client->lastmovesequence) need_empty = true; client->lastmovesequence = client->movesequence; if (client->entitydatabase5) success = EntityFrame5_WriteFrame(msg, maxsize, client->entitydatabase5, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1, client->movesequence, need_empty); else if (client->entitydatabase4) { success = EntityFrame4_WriteFrame(msg, maxsize, client->entitydatabase4, numsendstates, sv.writeentitiestoclient_sendstates); Protocol_WriteStatsReliable(); } else if (client->entitydatabase) { success = EntityFrame_WriteFrame(msg, maxsize, client->entitydatabase, numsendstates, sv.writeentitiestoclient_sendstates, client - svs.clients + 1); Protocol_WriteStatsReliable(); } else { success = EntityFrameQuake_WriteFrame(msg, maxsize, numsendstates, sv.writeentitiestoclient_sendstates); Protocol_WriteStatsReliable(); } if(success) client->num_skippedentityframes = 0; else ++client->num_skippedentityframes; } /* ============= SV_CleanupEnts ============= */ static void SV_CleanupEnts (void) { prvm_prog_t *prog = SVVM_prog; int e; prvm_edict_t *ent; ent = PRVM_NEXT_EDICT(prog->edicts); for (e=1 ; enum_edicts ; e++, ent = PRVM_NEXT_EDICT(ent)) PRVM_serveredictfloat(ent, effects) = (int)PRVM_serveredictfloat(ent, effects) & ~EF_MUZZLEFLASH; } /* ================== SV_WriteClientdataToMessage ================== */ void SV_WriteClientdataToMessage (client_t *client, prvm_edict_t *ent, sizebuf_t *msg, int *stats) { prvm_prog_t *prog = SVVM_prog; int bits; int i; prvm_edict_t *other; int items; vec3_t punchvector; int viewzoom; const char *s; float *statsf = (float *)stats; float gravity; // // send a damage message // if (PRVM_serveredictfloat(ent, dmg_take) || PRVM_serveredictfloat(ent, dmg_save)) { other = PRVM_PROG_TO_EDICT(PRVM_serveredictedict(ent, dmg_inflictor)); MSG_WriteByte (msg, svc_damage); MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_save)); MSG_WriteByte (msg, (int)PRVM_serveredictfloat(ent, dmg_take)); for (i=0 ; i<3 ; i++) MSG_WriteCoord (msg, PRVM_serveredictvector(other, origin)[i] + 0.5*(PRVM_serveredictvector(other, mins)[i] + PRVM_serveredictvector(other, maxs)[i]), sv.protocol); PRVM_serveredictfloat(ent, dmg_take) = 0; PRVM_serveredictfloat(ent, dmg_save) = 0; } // // send the current viewpos offset from the view entity // SV_SetIdealPitch (); // how much to look up / down ideally // a fixangle might get lost in a dropped packet. Oh well. if(PRVM_serveredictfloat(ent, fixangle)) { // angle fixing was requested by global thinking code... // so store the current angles for later use VectorCopy(PRVM_serveredictvector(ent, angles), host_client->fixangle_angles); host_client->fixangle_angles_set = TRUE; // and clear fixangle for the next frame PRVM_serveredictfloat(ent, fixangle) = 0; } if (host_client->fixangle_angles_set) { MSG_WriteByte (msg, svc_setangle); for (i=0 ; i < 3 ; i++) MSG_WriteAngle (msg, host_client->fixangle_angles[i], sv.protocol); host_client->fixangle_angles_set = FALSE; } // the runes are in serverflags, pack them into the items value, also pack // in the items2 value for mission pack huds // (used only in the mission packs, which do not use serverflags) items = (int)PRVM_serveredictfloat(ent, items) | ((int)PRVM_serveredictfloat(ent, items2) << 23) | ((int)PRVM_serverglobalfloat(serverflags) << 28); VectorCopy(PRVM_serveredictvector(ent, punchvector), punchvector); // cache weapon model name and index in client struct to save time // (this search can be almost 1% of cpu time!) s = PRVM_GetString(prog, PRVM_serveredictstring(ent, weaponmodel)); if (strcmp(s, client->weaponmodel)) { strlcpy(client->weaponmodel, s, sizeof(client->weaponmodel)); client->weaponmodelindex = SV_ModelIndex(s, 1); } viewzoom = (int)(PRVM_serveredictfloat(ent, viewzoom) * 255.0f); if (viewzoom == 0) viewzoom = 255; bits = 0; if ((int)PRVM_serveredictfloat(ent, flags) & FL_ONGROUND) bits |= SU_ONGROUND; if (PRVM_serveredictfloat(ent, waterlevel) >= 2) bits |= SU_INWATER; if (PRVM_serveredictfloat(ent, idealpitch)) bits |= SU_IDEALPITCH; for (i=0 ; i<3 ; i++) { if (PRVM_serveredictvector(ent, punchangle)[i]) bits |= (SU_PUNCH1<weaponmodelindex; stats[STAT_HEALTH] = (int)PRVM_serveredictfloat(ent, health); stats[STAT_AMMO] = (int)PRVM_serveredictfloat(ent, currentammo); stats[STAT_SHELLS] = (int)PRVM_serveredictfloat(ent, ammo_shells); stats[STAT_NAILS] = (int)PRVM_serveredictfloat(ent, ammo_nails); stats[STAT_ROCKETS] = (int)PRVM_serveredictfloat(ent, ammo_rockets); stats[STAT_CELLS] = (int)PRVM_serveredictfloat(ent, ammo_cells); stats[STAT_ACTIVEWEAPON] = (int)PRVM_serveredictfloat(ent, weapon); stats[STAT_VIEWZOOM] = viewzoom; stats[STAT_TOTALSECRETS] = (int)PRVM_serverglobalfloat(total_secrets); stats[STAT_TOTALMONSTERS] = (int)PRVM_serverglobalfloat(total_monsters); // the QC bumps these itself by sending svc_'s, so we have to keep them // zero or they'll be corrected by the engine //stats[STAT_SECRETS] = PRVM_serverglobalfloat(found_secrets); //stats[STAT_MONSTERS] = PRVM_serverglobalfloat(killed_monsters); // movement settings for prediction // note: these are not sent in protocols with lower MAX_CL_STATS limits stats[STAT_MOVEFLAGS] = MOVEFLAG_VALID | (sv_gameplayfix_q2airaccelerate.integer ? MOVEFLAG_Q2AIRACCELERATE : 0) | (sv_gameplayfix_nogravityonground.integer ? MOVEFLAG_NOGRAVITYONGROUND : 0) | (sv_gameplayfix_gravityunaffectedbyticrate.integer ? MOVEFLAG_GRAVITYUNAFFECTEDBYTICRATE : 0) ; statsf[STAT_MOVEVARS_TICRATE] = sys_ticrate.value; statsf[STAT_MOVEVARS_TIMESCALE] = slowmo.value; statsf[STAT_MOVEVARS_GRAVITY] = sv_gravity.value; statsf[STAT_MOVEVARS_STOPSPEED] = sv_stopspeed.value; statsf[STAT_MOVEVARS_MAXSPEED] = sv_maxspeed.value; statsf[STAT_MOVEVARS_SPECTATORMAXSPEED] = sv_maxspeed.value; // FIXME: QW has a separate cvar for this statsf[STAT_MOVEVARS_ACCELERATE] = sv_accelerate.value; statsf[STAT_MOVEVARS_AIRACCELERATE] = sv_airaccelerate.value >= 0 ? sv_airaccelerate.value : sv_accelerate.value; statsf[STAT_MOVEVARS_WATERACCELERATE] = sv_wateraccelerate.value >= 0 ? sv_wateraccelerate.value : sv_accelerate.value; statsf[STAT_MOVEVARS_ENTGRAVITY] = gravity; statsf[STAT_MOVEVARS_JUMPVELOCITY] = sv_jumpvelocity.value; statsf[STAT_MOVEVARS_EDGEFRICTION] = sv_edgefriction.value; statsf[STAT_MOVEVARS_MAXAIRSPEED] = sv_maxairspeed.value; statsf[STAT_MOVEVARS_STEPHEIGHT] = sv_stepheight.value; statsf[STAT_MOVEVARS_AIRACCEL_QW] = sv_airaccel_qw.value; statsf[STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR] = sv_airaccel_qw_stretchfactor.value; statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_airaccel_sideways_friction.value; statsf[STAT_MOVEVARS_FRICTION] = sv_friction.value; statsf[STAT_MOVEVARS_WATERFRICTION] = sv_waterfriction.value >= 0 ? sv_waterfriction.value : sv_friction.value; statsf[STAT_MOVEVARS_AIRSTOPACCELERATE] = sv_airstopaccelerate.value; statsf[STAT_MOVEVARS_AIRSTRAFEACCELERATE] = sv_airstrafeaccelerate.value; statsf[STAT_MOVEVARS_MAXAIRSTRAFESPEED] = sv_maxairstrafespeed.value; statsf[STAT_MOVEVARS_AIRSTRAFEACCEL_QW] = sv_airstrafeaccel_qw.value; statsf[STAT_MOVEVARS_AIRCONTROL] = sv_aircontrol.value; statsf[STAT_MOVEVARS_AIRCONTROL_POWER] = sv_aircontrol_power.value; statsf[STAT_MOVEVARS_AIRCONTROL_PENALTY] = sv_aircontrol_penalty.value; statsf[STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL] = sv_warsowbunny_airforwardaccel.value; statsf[STAT_MOVEVARS_WARSOWBUNNY_ACCEL] = sv_warsowbunny_accel.value; statsf[STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED] = sv_warsowbunny_topspeed.value; statsf[STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL] = sv_warsowbunny_turnaccel.value; statsf[STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO] = sv_warsowbunny_backtosideratio.value; statsf[STAT_MOVEVARS_AIRSPEEDLIMIT_NONQW] = sv_airspeedlimit_nonqw.value; statsf[STAT_FRAGLIMIT] = fraglimit.value; statsf[STAT_TIMELIMIT] = timelimit.value; if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) { if (stats[STAT_VIEWHEIGHT] != DEFAULT_VIEWHEIGHT) bits |= SU_VIEWHEIGHT; bits |= SU_ITEMS; if (stats[STAT_WEAPONFRAME]) bits |= SU_WEAPONFRAME; if (stats[STAT_ARMOR]) bits |= SU_ARMOR; bits |= SU_WEAPON; // FIXME: which protocols support this? does PROTOCOL_DARKPLACES3 support viewzoom? if (sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5) if (viewzoom != 255) bits |= SU_VIEWZOOM; } if (bits >= 65536) bits |= SU_EXTEND1; if (bits >= 16777216) bits |= SU_EXTEND2; // send the data MSG_WriteByte (msg, svc_clientdata); MSG_WriteShort (msg, bits); if (bits & SU_EXTEND1) MSG_WriteByte(msg, bits >> 16); if (bits & SU_EXTEND2) MSG_WriteByte(msg, bits >> 24); if (bits & SU_VIEWHEIGHT) MSG_WriteChar (msg, stats[STAT_VIEWHEIGHT]); if (bits & SU_IDEALPITCH) MSG_WriteChar (msg, (int)PRVM_serveredictfloat(ent, idealpitch)); for (i=0 ; i<3 ; i++) { if (bits & (SU_PUNCH1<begun || !client->netconnection || client->unreliablemsg.cursize + sv.datagram.cursize > client->unreliablemsg.maxsize || client->unreliablemsg_splitpoints >= (int)(sizeof(client->unreliablemsg_splitpoint)/sizeof(client->unreliablemsg_splitpoint[0]))) continue; SZ_Write(&client->unreliablemsg, sv.datagram.data, sv.datagram.cursize); client->unreliablemsg_splitpoint[client->unreliablemsg_splitpoints++] = client->unreliablemsg.cursize; } SZ_Clear(&sv.datagram); } static void SV_WriteUnreliableMessages(client_t *client, sizebuf_t *msg, int maxsize, int maxsize2) { // scan the splitpoints to find out how many we can fit in int numsegments, j, split; if (!client->unreliablemsg_splitpoints) return; // always accept the first one if it's within 1024 bytes, this ensures // that very big datagrams which are over the rate limit still get // through, just to keep it working for (numsegments = 1;numsegments < client->unreliablemsg_splitpoints;numsegments++) if (msg->cursize + client->unreliablemsg_splitpoint[numsegments] > maxsize) break; // the first segment gets an exemption from the rate limiting, otherwise // it could get dropped consistently due to a low rate limit if (numsegments == 1) maxsize = maxsize2; // some will fit, so add the ones that will fit split = client->unreliablemsg_splitpoint[numsegments-1]; // note this discards ones that were accepted by the segments scan but // can not fit, such as a really huge first one that will never ever // fit in a packet... if (msg->cursize + split <= maxsize) SZ_Write(msg, client->unreliablemsg.data, split); // remove the part we sent, keeping any remaining data client->unreliablemsg.cursize -= split; if (client->unreliablemsg.cursize > 0) memmove(client->unreliablemsg.data, client->unreliablemsg.data + split, client->unreliablemsg.cursize); // adjust remaining splitpoints client->unreliablemsg_splitpoints -= numsegments; for (j = 0;j < client->unreliablemsg_splitpoints;j++) client->unreliablemsg_splitpoint[j] = client->unreliablemsg_splitpoint[numsegments + j] - split; } /* ======================= SV_SendClientDatagram ======================= */ static void SV_SendClientDatagram (client_t *client) { int clientrate, maxrate, maxsize, maxsize2, downloadsize; sizebuf_t msg; int stats[MAX_CL_STATS]; static unsigned char sv_sendclientdatagram_buf[NET_MAXMESSAGE]; double timedelta; // obey rate limit by limiting packet frequency if the packet size // limiting fails // (usually this is caused by reliable messages) if (!NetConn_CanSend(client->netconnection)) return; // PROTOCOL_DARKPLACES5 and later support packet size limiting of updates maxrate = max(NET_MINRATE, sv_maxrate.integer); if (sv_maxrate.integer != maxrate) Cvar_SetValueQuick(&sv_maxrate, maxrate); // clientrate determines the 'cleartime' of a packet // (how long to wait before sending another, based on this packet's size) clientrate = bound(NET_MINRATE, client->rate, maxrate); switch (sv.protocol) { case PROTOCOL_QUAKE: case PROTOCOL_QUAKEDP: case PROTOCOL_NEHAHRAMOVIE: case PROTOCOL_NEHAHRABJP: case PROTOCOL_NEHAHRABJP2: case PROTOCOL_NEHAHRABJP3: case PROTOCOL_QUAKEWORLD: // no packet size limit support on Quake protocols because it just // causes missing entities/effects // packets are simply sent less often to obey the rate limit maxsize = 1024; maxsize2 = 1024; break; case PROTOCOL_DARKPLACES1: case PROTOCOL_DARKPLACES2: case PROTOCOL_DARKPLACES3: case PROTOCOL_DARKPLACES4: // no packet size limit support on DP1-4 protocols because they kick // the client off if they overflow, and miss effects // packets are simply sent less often to obey the rate limit maxsize = sizeof(sv_sendclientdatagram_buf); maxsize2 = sizeof(sv_sendclientdatagram_buf); break; default: // DP5 and later protocols support packet size limiting which is a // better method than limiting packet frequency as QW does // // at very low rates (or very small sys_ticrate) the packet size is // not reduced below 128, but packets may be sent less often // how long are bursts? timedelta = host_client->rate_burstsize / (double)client->rate; // how much of the burst do we keep reserved? timedelta *= 1 - net_burstreserve.value; // only try to use excess time timedelta = bound(0, realtime - host_client->netconnection->cleartime, timedelta); // but we know next packet will be in sys_ticrate, so we can use up THAT bandwidth timedelta += sys_ticrate.value; // note: packet overhead (not counted in maxsize) is 28 bytes maxsize = (int)(clientrate * timedelta) - 28; // put it in sound bounds maxsize = bound(128, maxsize, 1400); maxsize2 = 1400; // csqc entities can easily exceed 128 bytes, so disable throttling in // mods that use csqc (they are likely to use less bandwidth anyway) if((net_usesizelimit.integer == 1) ? (sv.csqc_progsize > 0) : (net_usesizelimit.integer < 1)) maxsize = maxsize2; break; } if (LHNETADDRESS_GetAddressType(&host_client->netconnection->peeraddress) == LHNETADDRESSTYPE_LOOP && !sv_ratelimitlocalplayer.integer) { // for good singleplayer, send huge packets maxsize = sizeof(sv_sendclientdatagram_buf); maxsize2 = sizeof(sv_sendclientdatagram_buf); // never limit frequency in singleplayer clientrate = 1000000000; } // while downloading, limit entity updates to half the packet // (any leftover space will be used for downloading) if (host_client->download_file) maxsize /= 2; msg.data = sv_sendclientdatagram_buf; msg.maxsize = sizeof(sv_sendclientdatagram_buf); msg.cursize = 0; msg.allowoverflow = false; if (host_client->begun) { // the player is in the game MSG_WriteByte (&msg, svc_time); MSG_WriteFloat (&msg, sv.time); // add the client specific data to the datagram SV_WriteClientdataToMessage (client, client->edict, &msg, stats); // now update the stats[] array using any registered custom fields VM_SV_UpdateCustomStats(client, client->edict, &msg, stats); // set host_client->statsdeltabits Protocol_UpdateClientStats (stats); // add as many queued unreliable messages (effects) as we can fit // limit effects to half of the remaining space if (client->unreliablemsg.cursize) SV_WriteUnreliableMessages (client, &msg, maxsize/2, maxsize2); // now write as many entities as we can fit, and also sends stats SV_WriteEntitiesToClient (client, client->edict, &msg, maxsize); } else if (realtime > client->keepalivetime) { // the player isn't totally in the game yet // send small keepalive messages if too much time has passed // (may also be sending downloads) client->keepalivetime = realtime + 5; MSG_WriteChar (&msg, svc_nop); } // if a download is active, see if there is room to fit some download data // in this packet downloadsize = min(maxsize*2,maxsize2) - msg.cursize - 7; if (host_client->download_file && host_client->download_started && downloadsize > 0) { fs_offset_t downloadstart; unsigned char data[1400]; downloadstart = FS_Tell(host_client->download_file); downloadsize = min(downloadsize, (int)sizeof(data)); downloadsize = FS_Read(host_client->download_file, data, downloadsize); // note this sends empty messages if at the end of the file, which is // necessary to keep the packet loss logic working // (the last blocks may be lost and need to be re-sent, and that will // only occur if the client acks the empty end messages, revealing // a gap in the download progress, causing the last blocks to be // sent again) MSG_WriteChar (&msg, svc_downloaddata); MSG_WriteLong (&msg, downloadstart); MSG_WriteShort (&msg, downloadsize); if (downloadsize > 0) SZ_Write (&msg, data, downloadsize); } // reliable only if none is in progress if(client->sendsignon != 2 && !client->netconnection->sendMessageLength) SV_WriteDemoMessage(client, &(client->netconnection->message), false); // unreliable SV_WriteDemoMessage(client, &msg, false); // send the datagram NetConn_SendUnreliableMessage (client->netconnection, &msg, sv.protocol, clientrate, client->rate_burstsize, client->sendsignon == 2); if (client->sendsignon == 1 && !client->netconnection->message.cursize) client->sendsignon = 2; // prevent reliable until client sends prespawn (this is the keepalive phase) } /* ======================= SV_UpdateToReliableMessages ======================= */ static void SV_UpdateToReliableMessages (void) { prvm_prog_t *prog = SVVM_prog; int i, j; client_t *client; const char *name; const char *model; const char *skin; int clientcamera; // check for changes to be sent over the reliable streams for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { // update the host_client fields we care about according to the entity fields host_client->edict = PRVM_EDICT_NUM(i+1); // DP_SV_CLIENTNAME name = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, netname)); if (name == NULL) name = ""; // always point the string back at host_client->name to keep it safe //strlcpy (host_client->name, name, sizeof (host_client->name)); if (name != host_client->name) // prevent buffer overlap SIGABRT on Mac OSX strlcpy (host_client->name, name, sizeof (host_client->name)); PRVM_serveredictstring(host_client->edict, netname) = PRVM_SetEngineString(prog, host_client->name); if (strcmp(host_client->old_name, host_client->name)) { if (host_client->begun) SV_BroadcastPrintf("%s ^7changed name to %s\n", host_client->old_name, host_client->name); strlcpy(host_client->old_name, host_client->name, sizeof(host_client->old_name)); // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatename); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteString (&sv.reliable_datagram, host_client->name); SV_WriteNetnameIntoDemo(host_client); } // DP_SV_CLIENTCOLORS host_client->colors = (int)PRVM_serveredictfloat(host_client->edict, clientcolors); if (host_client->old_colors != host_client->colors) { host_client->old_colors = host_client->colors; // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatecolors); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteByte (&sv.reliable_datagram, host_client->colors); } // NEXUIZ_PLAYERMODEL model = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, playermodel)); if (model == NULL) model = ""; // always point the string back at host_client->name to keep it safe //strlcpy (host_client->playermodel, model, sizeof (host_client->playermodel)); if (model != host_client->playermodel) // prevent buffer overlap SIGABRT on Mac OSX strlcpy (host_client->playermodel, model, sizeof (host_client->playermodel)); PRVM_serveredictstring(host_client->edict, playermodel) = PRVM_SetEngineString(prog, host_client->playermodel); // NEXUIZ_PLAYERSKIN skin = PRVM_GetString(prog, PRVM_serveredictstring(host_client->edict, playerskin)); if (skin == NULL) skin = ""; // always point the string back at host_client->name to keep it safe //strlcpy (host_client->playerskin, skin, sizeof (host_client->playerskin)); if (skin != host_client->playerskin) // prevent buffer overlap SIGABRT on Mac OSX strlcpy (host_client->playerskin, skin, sizeof (host_client->playerskin)); PRVM_serveredictstring(host_client->edict, playerskin) = PRVM_SetEngineString(prog, host_client->playerskin); // TODO: add an extension name for this [1/17/2008 Black] clientcamera = PRVM_serveredictedict(host_client->edict, clientcamera); if (clientcamera > 0) { int oldclientcamera = host_client->clientcamera; if (clientcamera >= prog->max_edicts || PRVM_EDICT_NUM(clientcamera)->priv.required->free) clientcamera = PRVM_NUM_FOR_EDICT(host_client->edict); host_client->clientcamera = clientcamera; if (oldclientcamera != host_client->clientcamera && host_client->netconnection) { MSG_WriteByte(&host_client->netconnection->message, svc_setview); MSG_WriteShort(&host_client->netconnection->message, host_client->clientcamera); } } // frags host_client->frags = (int)PRVM_serveredictfloat(host_client->edict, frags); if(IS_OLDNEXUIZ_DERIVED(gamemode)) if(!host_client->begun && host_client->netconnection) host_client->frags = -666; if (host_client->old_frags != host_client->frags) { host_client->old_frags = host_client->frags; // send notification to all clients MSG_WriteByte (&sv.reliable_datagram, svc_updatefrags); MSG_WriteByte (&sv.reliable_datagram, i); MSG_WriteShort (&sv.reliable_datagram, host_client->frags); } } for (j = 0, client = svs.clients;j < svs.maxclients;j++, client++) if (client->netconnection && (client->begun || client->clientconnectcalled)) // also send MSG_ALL to people who are past ClientConnect, but not spawned yet SZ_Write (&client->netconnection->message, sv.reliable_datagram.data, sv.reliable_datagram.cursize); SZ_Clear (&sv.reliable_datagram); } /* ======================= SV_SendClientMessages ======================= */ void SV_SendClientMessages(void) { int i, prepared = false; if (sv.protocol == PROTOCOL_QUAKEWORLD) Sys_Error("SV_SendClientMessages: no quakeworld support\n"); SV_FlushBroadcastMessages(); // update frags, names, etc SV_UpdateToReliableMessages(); // build individual updates for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { if (!host_client->active) continue; if (!host_client->netconnection) continue; if (host_client->netconnection->message.overflowed) { SV_DropClient (true); // if the message couldn't send, kick off continue; } if (!prepared) { prepared = true; // only prepare entities once per frame SV_PrepareEntitiesForSending(); } SV_SendClientDatagram(host_client); } // clear muzzle flashes SV_CleanupEnts(); } static void SV_StartDownload_f(void) { if (host_client->download_file) host_client->download_started = true; } /* * Compression extension negotiation: * * Server to client: * cl_serverextension_download 2 * * Client to server: * download * e.g. * download maps/map1.bsp lzo deflate huffman * * Server to client: * cl_downloadbegin * e.g. * cl_downloadbegin 123456 maps/map1.bsp deflate * * The server may choose not to compress the file by sending no compression name, like: * cl_downloadbegin 345678 maps/map1.bsp * * NOTE: the "download" command may only specify compression algorithms if * cl_serverextension_download is 2! * If cl_serverextension_download has a different value, the client must * assume this extension is not supported! */ static void Download_CheckExtensions(void) { int i; int argc = Cmd_Argc(); // first reset them all host_client->download_deflate = false; for(i = 2; i < argc; ++i) { if(!strcmp(Cmd_Argv(i), "deflate")) { host_client->download_deflate = true; break; } } } static void SV_Download_f(void) { const char *whichpack, *whichpack2, *extension; qboolean is_csqc; // so we need to check only once if (Cmd_Argc() < 2) { SV_ClientPrintf("usage: download {}*\n"); SV_ClientPrintf(" supported extensions: deflate\n"); return; } if (FS_CheckNastyPath(Cmd_Argv(1), false)) { SV_ClientPrintf("Download rejected: nasty filename \"%s\"\n", Cmd_Argv(1)); return; } if (host_client->download_file) { // at this point we'll assume the previous download should be aborted Con_DPrintf("Download of %s aborted by %s starting a new download\n", host_client->download_name, host_client->name); Host_ClientCommands("\nstopdownload\n"); // close the file and reset variables FS_Close(host_client->download_file); host_client->download_file = NULL; host_client->download_name[0] = 0; host_client->download_expectedposition = 0; host_client->download_started = false; } is_csqc = (sv.csqc_progname[0] && strcmp(Cmd_Argv(1), sv.csqc_progname) == 0); if (!sv_allowdownloads.integer && !is_csqc) { SV_ClientPrintf("Downloads are disabled on this server\n"); Host_ClientCommands("\nstopdownload\n"); return; } Download_CheckExtensions(); strlcpy(host_client->download_name, Cmd_Argv(1), sizeof(host_client->download_name)); extension = FS_FileExtension(host_client->download_name); // host_client is asking to download a specified file if (developer_extra.integer) Con_DPrintf("Download request for %s by %s\n", host_client->download_name, host_client->name); if(is_csqc) { char extensions[MAX_QPATH]; // make sure this can hold all extensions extensions[0] = '\0'; if(host_client->download_deflate) strlcat(extensions, " deflate", sizeof(extensions)); Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); if(host_client->download_deflate && svs.csqc_progdata_deflated) host_client->download_file = FS_FileFromData(svs.csqc_progdata_deflated, svs.csqc_progsize_deflated, true); else host_client->download_file = FS_FileFromData(svs.csqc_progdata, sv.csqc_progsize, true); // no, no space is needed between %s and %s :P Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); host_client->download_expectedposition = 0; host_client->download_started = false; host_client->sendsignon = true; // make sure this message is sent return; } if (!FS_FileExists(host_client->download_name)) { SV_ClientPrintf("Download rejected: server does not have the file \"%s\"\nYou may need to separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } // check if the user is trying to download part of registered Quake(r) whichpack = FS_WhichPack(host_client->download_name); whichpack2 = FS_WhichPack("gfx/pop.lmp"); if ((whichpack && whichpack2 && !strcasecmp(whichpack, whichpack2)) || FS_IsRegisteredQuakePack(host_client->download_name)) { SV_ClientPrintf("Download rejected: file \"%s\" is part of registered Quake(r)\nYou must purchase Quake(r) from id Software or a retailer to get this file\nPlease go to http://www.idsoftware.com/games/quake/quake/index.php?game_section=buy\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } // check if the server has forbidden archive downloads entirely if (!sv_allowdownloads_inarchive.integer) { whichpack = FS_WhichPack(host_client->download_name); if (whichpack) { SV_ClientPrintf("Download rejected: file \"%s\" is in an archive (\"%s\")\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name, whichpack); Host_ClientCommands("\nstopdownload\n"); return; } } if (!sv_allowdownloads_config.integer) { if (!strcasecmp(extension, "cfg")) { SV_ClientPrintf("Download rejected: file \"%s\" is a .cfg file which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } } if (!sv_allowdownloads_dlcache.integer) { if (!strncasecmp(host_client->download_name, "dlcache/", 8)) { SV_ClientPrintf("Download rejected: file \"%s\" is in the dlcache/ directory which is forbidden for security reasons\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } } if (!sv_allowdownloads_archive.integer) { if (!strcasecmp(extension, "pak") || !strcasecmp(extension, "pk3")) { SV_ClientPrintf("Download rejected: file \"%s\" is an archive\nYou must separately download or purchase the data archives for this game/mod to get this file\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } } host_client->download_file = FS_OpenVirtualFile(host_client->download_name, true); if (!host_client->download_file) { SV_ClientPrintf("Download rejected: server could not open the file \"%s\"\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); return; } if (FS_FileSize(host_client->download_file) > 1<<30) { SV_ClientPrintf("Download rejected: file \"%s\" is very large\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); FS_Close(host_client->download_file); host_client->download_file = NULL; return; } if (FS_FileSize(host_client->download_file) < 0) { SV_ClientPrintf("Download rejected: file \"%s\" is not a regular file\n", host_client->download_name); Host_ClientCommands("\nstopdownload\n"); FS_Close(host_client->download_file); host_client->download_file = NULL; return; } Con_DPrintf("Downloading %s to %s\n", host_client->download_name, host_client->name); /* * we can only do this if we would actually deflate on the fly * which we do not (yet)! { char extensions[MAX_QPATH]; // make sure this can hold all extensions extensions[0] = '\0'; if(host_client->download_deflate) strlcat(extensions, " deflate", sizeof(extensions)); // no, no space is needed between %s and %s :P Host_ClientCommands("\ncl_downloadbegin %i %s%s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name, extensions); } */ Host_ClientCommands("\ncl_downloadbegin %i %s\n", (int)FS_FileSize(host_client->download_file), host_client->download_name); host_client->download_expectedposition = 0; host_client->download_started = false; host_client->sendsignon = true; // make sure this message is sent // the rest of the download process is handled in SV_SendClientDatagram // and other code dealing with svc_downloaddata and clc_ackdownloaddata // // no svc_downloaddata messages will be sent until sv_startdownload is // sent by the client } /* ============================================================================== SERVER SPAWNING ============================================================================== */ /* ================ SV_ModelIndex ================ */ int SV_ModelIndex(const char *s, int precachemode) { int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_MODELS); char filename[MAX_QPATH]; if (!s || !*s) return 0; // testing //if (precachemode == 2) // return 0; strlcpy(filename, s, sizeof(filename)); for (i = 2;i < limit;i++) { if (!sv.model_precache[i][0]) { if (precachemode) { if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) { Con_Printf("SV_ModelIndex(\"%s\"): precache_model can only be done in spawn functions\n", filename); return 0; } if (precachemode == 1) Con_Printf("SV_ModelIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); strlcpy(sv.model_precache[i], filename, sizeof(sv.model_precache[i])); if (sv.state == ss_loading) { // running from SV_SpawnServer which is launched from the client console command interpreter sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); } else { if (svs.threaded) { // this is running on the server thread, we can't load a model here (it would crash on renderer calls), so only look it up, the svc_precache will cause it to be loaded when it reaches the client sv.models[i] = Mod_FindName (sv.model_precache[i], s[0] == '*' ? sv.worldname : NULL); } else { // running single threaded, so we can load the model here sv.models[i] = Mod_ForName (sv.model_precache[i], true, false, s[0] == '*' ? sv.worldname : NULL); } MSG_WriteByte(&sv.reliable_datagram, svc_precache); MSG_WriteShort(&sv.reliable_datagram, i); MSG_WriteString(&sv.reliable_datagram, filename); } return i; } Con_Printf("SV_ModelIndex(\"%s\"): not precached\n", filename); return 0; } if (!strcmp(sv.model_precache[i], filename)) return i; } Con_Printf("SV_ModelIndex(\"%s\"): i (%i) == MAX_MODELS (%i)\n", filename, i, MAX_MODELS); return 0; } /* ================ SV_SoundIndex ================ */ int SV_SoundIndex(const char *s, int precachemode) { int i, limit = ((sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) ? 256 : MAX_SOUNDS); char filename[MAX_QPATH]; if (!s || !*s) return 0; // testing //if (precachemode == 2) // return 0; strlcpy(filename, s, sizeof(filename)); for (i = 1;i < limit;i++) { if (!sv.sound_precache[i][0]) { if (precachemode) { if (sv.state != ss_loading && (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3 || sv.protocol == PROTOCOL_DARKPLACES1 || sv.protocol == PROTOCOL_DARKPLACES2 || sv.protocol == PROTOCOL_DARKPLACES3 || sv.protocol == PROTOCOL_DARKPLACES4 || sv.protocol == PROTOCOL_DARKPLACES5)) { Con_Printf("SV_SoundIndex(\"%s\"): precache_sound can only be done in spawn functions\n", filename); return 0; } if (precachemode == 1) Con_Printf("SV_SoundIndex(\"%s\"): not precached (fix your code), precaching anyway\n", filename); strlcpy(sv.sound_precache[i], filename, sizeof(sv.sound_precache[i])); if (sv.state != ss_loading) { MSG_WriteByte(&sv.reliable_datagram, svc_precache); MSG_WriteShort(&sv.reliable_datagram, i + 32768); MSG_WriteString(&sv.reliable_datagram, filename); } return i; } Con_Printf("SV_SoundIndex(\"%s\"): not precached\n", filename); return 0; } if (!strcmp(sv.sound_precache[i], filename)) return i; } Con_Printf("SV_SoundIndex(\"%s\"): i (%i) == MAX_SOUNDS (%i)\n", filename, i, MAX_SOUNDS); return 0; } /* ================ SV_ParticleEffectIndex ================ */ int SV_ParticleEffectIndex(const char *name) { int i, argc, linenumber, effectnameindex; int filepass; fs_offset_t filesize; unsigned char *filedata; const char *text; const char *textstart; //const char *textend; char argv[16][1024]; char filename[MAX_QPATH]; if (!sv.particleeffectnamesloaded) { sv.particleeffectnamesloaded = true; memset(sv.particleeffectname, 0, sizeof(sv.particleeffectname)); for (i = 0;i < EFFECT_TOTAL;i++) strlcpy(sv.particleeffectname[i], standardeffectnames[i], sizeof(sv.particleeffectname[i])); for (filepass = 0;;filepass++) { if (filepass == 0) dpsnprintf(filename, sizeof(filename), "effectinfo.txt"); else if (filepass == 1) dpsnprintf(filename, sizeof(filename), "%s_effectinfo.txt", sv.worldnamenoextension); else break; filedata = FS_LoadFile(filename, tempmempool, true, &filesize); if (!filedata) continue; textstart = (const char *)filedata; //textend = (const char *)filedata + filesize; text = textstart; for (linenumber = 1;;linenumber++) { argc = 0; for (;;) { if (!COM_ParseToken_Simple(&text, true, false, true) || !strcmp(com_token, "\n")) break; if (argc < 16) { strlcpy(argv[argc], com_token, sizeof(argv[argc])); argc++; } } if (com_token[0] == 0) break; // if the loop exited and it's not a \n, it's EOF if (argc < 1) continue; if (!strcmp(argv[0], "effect")) { if (argc == 2) { for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME;effectnameindex++) { if (sv.particleeffectname[effectnameindex][0]) { if (!strcmp(sv.particleeffectname[effectnameindex], argv[1])) break; } else { strlcpy(sv.particleeffectname[effectnameindex], argv[1], sizeof(sv.particleeffectname[effectnameindex])); break; } } // if we run out of names, abort if (effectnameindex == MAX_PARTICLEEFFECTNAME) { Con_Printf("%s:%i: too many effects!\n", filename, linenumber); break; } } } } Mem_Free(filedata); } } // search for the name for (effectnameindex = 1;effectnameindex < MAX_PARTICLEEFFECTNAME && sv.particleeffectname[effectnameindex][0];effectnameindex++) if (!strcmp(sv.particleeffectname[effectnameindex], name)) return effectnameindex; // return 0 if we couldn't find it return 0; } dp_model_t *SV_GetModelByIndex(int modelindex) { return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; } dp_model_t *SV_GetModelFromEdict(prvm_edict_t *ed) { prvm_prog_t *prog = SVVM_prog; int modelindex; if (!ed || ed->priv.server->free) return NULL; modelindex = (int)PRVM_serveredictfloat(ed, modelindex); return (modelindex > 0 && modelindex < MAX_MODELS) ? sv.models[modelindex] : NULL; } /* ================ SV_CreateBaseline ================ */ static void SV_CreateBaseline (void) { prvm_prog_t *prog = SVVM_prog; int i, entnum, large; prvm_edict_t *svent; // LordHavoc: clear *all* baselines (not just active ones) for (entnum = 0;entnum < prog->max_edicts;entnum++) { // get the current server version svent = PRVM_EDICT_NUM(entnum); // LordHavoc: always clear state values, whether the entity is in use or not svent->priv.server->baseline = defaultstate; if (svent->priv.server->free) continue; if (entnum > svs.maxclients && !PRVM_serveredictfloat(svent, modelindex)) continue; // create entity baseline VectorCopy (PRVM_serveredictvector(svent, origin), svent->priv.server->baseline.origin); VectorCopy (PRVM_serveredictvector(svent, angles), svent->priv.server->baseline.angles); svent->priv.server->baseline.frame = (int)PRVM_serveredictfloat(svent, frame); svent->priv.server->baseline.skin = (int)PRVM_serveredictfloat(svent, skin); if (entnum > 0 && entnum <= svs.maxclients) { svent->priv.server->baseline.colormap = entnum; svent->priv.server->baseline.modelindex = SV_ModelIndex("progs/player.mdl", 1); } else { svent->priv.server->baseline.colormap = 0; svent->priv.server->baseline.modelindex = (int)PRVM_serveredictfloat(svent, modelindex); } large = false; if (svent->priv.server->baseline.modelindex & 0xFF00 || svent->priv.server->baseline.frame & 0xFF00) { large = true; if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) large = false; } // add to the message if (large) MSG_WriteByte (&sv.signon, svc_spawnbaseline2); else MSG_WriteByte (&sv.signon, svc_spawnbaseline); MSG_WriteShort (&sv.signon, entnum); if (large) { MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); MSG_WriteShort (&sv.signon, svent->priv.server->baseline.frame); } else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) { MSG_WriteShort (&sv.signon, svent->priv.server->baseline.modelindex); MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); } else { MSG_WriteByte (&sv.signon, svent->priv.server->baseline.modelindex); MSG_WriteByte (&sv.signon, svent->priv.server->baseline.frame); } MSG_WriteByte (&sv.signon, svent->priv.server->baseline.colormap); MSG_WriteByte (&sv.signon, svent->priv.server->baseline.skin); for (i=0 ; i<3 ; i++) { MSG_WriteCoord(&sv.signon, svent->priv.server->baseline.origin[i], sv.protocol); MSG_WriteAngle(&sv.signon, svent->priv.server->baseline.angles[i], sv.protocol); } } } /* ================ SV_Prepare_CSQC Load csprogs.dat and comperss it so it doesn't need to be reloaded on request. ================ */ static void SV_Prepare_CSQC(void) { fs_offset_t progsize; if(svs.csqc_progdata) { Con_DPrintf("Unloading old CSQC data.\n"); Mem_Free(svs.csqc_progdata); if(svs.csqc_progdata_deflated) Mem_Free(svs.csqc_progdata_deflated); } svs.csqc_progdata = NULL; svs.csqc_progdata_deflated = NULL; sv.csqc_progname[0] = 0; svs.csqc_progdata = FS_LoadFile(csqc_progname.string, sv_mempool, false, &progsize); if(progsize > 0) { size_t deflated_size; sv.csqc_progsize = (int)progsize; sv.csqc_progcrc = CRC_Block(svs.csqc_progdata, progsize); strlcpy(sv.csqc_progname, csqc_progname.string, sizeof(sv.csqc_progname)); Con_DPrintf("server detected csqc progs file \"%s\" with size %i and crc %i\n", sv.csqc_progname, sv.csqc_progsize, sv.csqc_progcrc); Con_DPrint("Compressing csprogs.dat\n"); //unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool); svs.csqc_progdata_deflated = FS_Deflate(svs.csqc_progdata, progsize, &deflated_size, -1, sv_mempool); svs.csqc_progsize_deflated = (int)deflated_size; if(svs.csqc_progdata_deflated) { Con_DPrintf("Deflated: %g%%\n", 100.0 - 100.0 * (deflated_size / (float)progsize)); Con_DPrintf("Uncompressed: %u\nCompressed: %u\n", (unsigned)sv.csqc_progsize, (unsigned)svs.csqc_progsize_deflated); } else Con_DPrintf("Cannot compress - need zlib for this. Using uncompressed progs only.\n"); } } /* ================ SV_SaveSpawnparms Grabs the current state of each client for saving across the transition to another level ================ */ void SV_SaveSpawnparms (void) { prvm_prog_t *prog = SVVM_prog; int i, j; svs.serverflags = (int)PRVM_serverglobalfloat(serverflags); for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { if (!host_client->active) continue; // call the progs to get default spawn parms for the new client PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(SetChangeParms), "QC function SetChangeParms is missing"); for (j=0 ; jspawn_parms[j] = (&PRVM_serverglobalfloat(parm1))[j]; } } /* ================ SV_SpawnServer This is called at the start of each level ================ */ void SV_SpawnServer (const char *server) { prvm_prog_t *prog = SVVM_prog; prvm_edict_t *ent; int i; char *entities; dp_model_t *worldmodel; char modelname[sizeof(sv.worldname)]; char vabuf[1024]; Con_DPrintf("SpawnServer: %s\n", server); dpsnprintf (modelname, sizeof(modelname), "maps/%s.bsp", server); if (!FS_FileExists(modelname)) { dpsnprintf (modelname, sizeof(modelname), "maps/%s", server); if (!FS_FileExists(modelname)) { Con_Printf("SpawnServer: no map file named maps/%s.bsp\n", server); return; } } // SV_LockThreadMutex(); if(cls.state == ca_dedicated) Sys_MakeProcessNice(); if (cls.state != ca_dedicated) { SCR_BeginLoadingPlaque(false); S_StopAllSounds(); } if(sv.active) { World_End(&sv.world); if(PRVM_serverfunction(SV_Shutdown)) { func_t s = PRVM_serverfunction(SV_Shutdown); PRVM_serverglobalfloat(time) = sv.time; PRVM_serverfunction(SV_Shutdown) = 0; // prevent it from getting called again prog->ExecuteProgram(prog, s,"SV_Shutdown() required"); } } // free q3 shaders so that any newly downloaded shaders will be active Mod_FreeQ3Shaders(); worldmodel = Mod_ForName(modelname, false, developer.integer > 0, NULL); if (!worldmodel || !worldmodel->TraceBox) { Con_Printf("Couldn't load map %s\n", modelname); if(cls.state == ca_dedicated) Sys_MakeProcessMean(); // SV_UnlockThreadMutex(); return; } Collision_Cache_Reset(true); // let's not have any servers with no name if (hostname.string[0] == 0) Cvar_Set ("hostname", "UNNAMED"); scr_centertime_off = 0; svs.changelevel_issued = false; // now safe to issue another // make the map a required file for clients Curl_ClearRequirements(); Curl_RequireFile(modelname); // // tell all connected clients that we are going to a new level // if (sv.active) { client_t *client; for (i = 0, client = svs.clients;i < svs.maxclients;i++, client++) { if (client->netconnection) { MSG_WriteByte(&client->netconnection->message, svc_stufftext); MSG_WriteString(&client->netconnection->message, "reconnect\n"); } } } else { // open server port NetConn_OpenServerPorts(true); } // // make cvars consistant // if (coop.integer) Cvar_SetValue ("deathmatch", 0); // LordHavoc: it can be useful to have skills outside the range 0-3... //current_skill = bound(0, (int)(skill.value + 0.5), 3); //Cvar_SetValue ("skill", (float)current_skill); current_skill = (int)(skill.value + 0.5); // // set up the new server // memset (&sv, 0, sizeof(sv)); // if running a local client, make sure it doesn't try to access the last // level's data which is no longer valiud cls.signon = 0; Cvar_SetValue("halflifebsp", worldmodel->brush.ishlbsp); Cvar_SetValue("sv_mapformat_is_quake2", worldmodel->brush.isq2bsp); Cvar_SetValue("sv_mapformat_is_quake3", worldmodel->brush.isq3bsp); if(*sv_random_seed.string) { srand(sv_random_seed.integer); Con_Printf("NOTE: random seed is %d; use for debugging/benchmarking only!\nUnset sv_random_seed to get real random numbers again.\n", sv_random_seed.integer); } SV_VM_Setup(); sv.active = true; // set level base name variables for later use strlcpy (sv.name, server, sizeof (sv.name)); strlcpy(sv.worldname, modelname, sizeof(sv.worldname)); FS_StripExtension(sv.worldname, sv.worldnamenoextension, sizeof(sv.worldnamenoextension)); strlcpy(sv.worldbasename, !strncmp(sv.worldnamenoextension, "maps/", 5) ? sv.worldnamenoextension + 5 : sv.worldnamenoextension, sizeof(sv.worldbasename)); //Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); // set later after QC is spawned Cvar_SetQuick(&sv_worldname, sv.worldname); Cvar_SetQuick(&sv_worldnamenoextension, sv.worldnamenoextension); Cvar_SetQuick(&sv_worldbasename, sv.worldbasename); sv.protocol = Protocol_EnumForName(sv_protocolname.string); if (sv.protocol == PROTOCOL_UNKNOWN) { char buffer[1024]; Protocol_Names(buffer, sizeof(buffer)); Con_Printf("Unknown sv_protocolname \"%s\", valid values are:\n%s\n", sv_protocolname.string, buffer); sv.protocol = PROTOCOL_QUAKE; } // load progs to get entity field count //PR_LoadProgs ( sv_progs.string ); sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.cursize = 0; sv.datagram.data = sv.datagram_buf; sv.reliable_datagram.maxsize = sizeof(sv.reliable_datagram_buf); sv.reliable_datagram.cursize = 0; sv.reliable_datagram.data = sv.reliable_datagram_buf; sv.signon.maxsize = sizeof(sv.signon_buf); sv.signon.cursize = 0; sv.signon.data = sv.signon_buf; // leave slots at start for clients only //prog->num_edicts = svs.maxclients+1; sv.state = ss_loading; prog->allowworldwrites = true; sv.paused = false; sv.time = 1.0; Mod_ClearUsed(); worldmodel->used = true; sv.worldmodel = worldmodel; sv.models[1] = sv.worldmodel; // // clear world interaction links // World_SetSize(&sv.world, sv.worldname, sv.worldmodel->normalmins, sv.worldmodel->normalmaxs, prog); World_Start(&sv.world); strlcpy(sv.sound_precache[0], "", sizeof(sv.sound_precache[0])); strlcpy(sv.model_precache[0], "", sizeof(sv.model_precache[0])); strlcpy(sv.model_precache[1], sv.worldname, sizeof(sv.model_precache[1])); for (i = 1;i < sv.worldmodel->brush.numsubmodels && i+1 < MAX_MODELS;i++) { dpsnprintf(sv.model_precache[i+1], sizeof(sv.model_precache[i+1]), "*%i", i); sv.models[i+1] = Mod_ForName (sv.model_precache[i+1], false, false, sv.worldname); } if(i < sv.worldmodel->brush.numsubmodels) Con_Printf("Too many submodels (MAX_MODELS is %i)\n", MAX_MODELS); // // load the rest of the entities // // AK possible hack since num_edicts is still 0 ent = PRVM_EDICT_NUM(0); memset (ent->fields.fp, 0, prog->entityfields * sizeof(prvm_vec_t)); ent->priv.server->free = false; PRVM_serveredictstring(ent, model) = PRVM_SetEngineString(prog, sv.worldname); PRVM_serveredictfloat(ent, modelindex) = 1; // world model PRVM_serveredictfloat(ent, solid) = SOLID_BSP; PRVM_serveredictfloat(ent, movetype) = MOVETYPE_PUSH; VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, mins)); VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, maxs)); VectorCopy(sv.world.mins, PRVM_serveredictvector(ent, absmin)); VectorCopy(sv.world.maxs, PRVM_serveredictvector(ent, absmax)); if (coop.value) PRVM_serverglobalfloat(coop) = coop.integer; else PRVM_serverglobalfloat(deathmatch) = deathmatch.integer; PRVM_serverglobalstring(mapname) = PRVM_SetEngineString(prog, sv.name); // serverflags are for cross level information (sigils) PRVM_serverglobalfloat(serverflags) = svs.serverflags; // we need to reset the spawned flag on all connected clients here so that // their thinks don't run during startup (before PutClientInServer) // we also need to set up the client entities now // and we need to set the ->edict pointers to point into the progs edicts for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { host_client->begun = false; host_client->edict = PRVM_EDICT_NUM(i + 1); PRVM_ED_ClearEdict(prog, host_client->edict); } // load replacement entity file if found if (sv_entpatch.integer && (entities = (char *)FS_LoadFile(va(vabuf, sizeof(vabuf), "%s.ent", sv.worldnamenoextension), tempmempool, true, NULL))) { Con_Printf("Loaded %s.ent\n", sv.worldnamenoextension); PRVM_ED_LoadFromFile(prog, entities); Mem_Free(entities); } else PRVM_ED_LoadFromFile(prog, sv.worldmodel->brush.entities); // LordHavoc: clear world angles (to fix e3m3.bsp) VectorClear(PRVM_serveredictvector(prog->edicts, angles)); // all setup is completed, any further precache statements are errors // sv.state = ss_active; // LordHavoc: workaround for svc_precache bug prog->allowworldwrites = false; // run two frames to allow everything to settle sv.time = 1.0001; for (i = 0;i < sv_init_frame_count.integer;i++) { sv.frametime = 0.1; SV_Physics (); } // Once all init frames have been run, we consider svqc code fully initialized. prog->inittime = realtime; if (cls.state == ca_dedicated) Mod_PurgeUnused(); // create a baseline for more efficient communications if (sv.protocol == PROTOCOL_QUAKE || sv.protocol == PROTOCOL_QUAKEDP || sv.protocol == PROTOCOL_NEHAHRAMOVIE || sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) SV_CreateBaseline (); sv.state = ss_active; // LordHavoc: workaround for svc_precache bug // send serverinfo to all connected clients, and set up botclients coming back from a level change for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) { host_client->clientconnectcalled = false; // do NOT call ClientDisconnect if he drops before ClientConnect! if (!host_client->active) continue; if (host_client->netconnection) SV_SendServerinfo(host_client); else { int j; // if client is a botclient coming from a level change, we need to // set up client info that normally requires networking // copy spawn parms out of the client_t for (j=0 ; j< NUM_SPAWN_PARMS ; j++) (&PRVM_serverglobalfloat(parm1))[j] = host_client->spawn_parms[j]; // call the spawn function host_client->clientconnectcalled = true; PRVM_serverglobalfloat(time) = sv.time; PRVM_serverglobaledict(self) = PRVM_EDICT_TO_PROG(host_client->edict); prog->ExecuteProgram(prog, PRVM_serverfunction(ClientConnect), "QC function ClientConnect is missing"); prog->ExecuteProgram(prog, PRVM_serverfunction(PutClientInServer), "QC function PutClientInServer is missing"); host_client->begun = true; } } // update the map title cvar strlcpy(sv.worldmessage, PRVM_GetString(prog, PRVM_serveredictstring(prog->edicts, message)), sizeof(sv.worldmessage)); // map title (not related to filename) Cvar_SetQuick(&sv_worldmessage, sv.worldmessage); Con_DPrint("Server spawned.\n"); NetConn_Heartbeat (2); if(cls.state == ca_dedicated) Sys_MakeProcessMean(); // SV_UnlockThreadMutex(); } ///////////////////////////////////////////////////// // SV VM stuff static void SVVM_begin_increase_edicts(prvm_prog_t *prog) { // links don't survive the transition, so unlink everything World_UnlinkAll(&sv.world); } static void SVVM_end_increase_edicts(prvm_prog_t *prog) { int i; prvm_edict_t *ent; // link every entity except world for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++) if (!ent->priv.server->free) SV_LinkEdict(ent); } static void SVVM_init_edict(prvm_prog_t *prog, prvm_edict_t *e) { // LordHavoc: for consistency set these here int num = PRVM_NUM_FOR_EDICT(e) - 1; e->priv.server->move = false; // don't move on first frame if (num >= 0 && num < svs.maxclients) { // set colormap and team on newly created player entity PRVM_serveredictfloat(e, colormap) = num + 1; PRVM_serveredictfloat(e, team) = (svs.clients[num].colors & 15) + 1; // set netname/clientcolors back to client values so that // DP_SV_CLIENTNAME and DP_SV_CLIENTCOLORS will not immediately // reset them PRVM_serveredictstring(e, netname) = PRVM_SetEngineString(prog, svs.clients[num].name); PRVM_serveredictfloat(e, clientcolors) = svs.clients[num].colors; // NEXUIZ_PLAYERMODEL and NEXUIZ_PLAYERSKIN PRVM_serveredictstring(e, playermodel) = PRVM_SetEngineString(prog, svs.clients[num].playermodel); PRVM_serveredictstring(e, playerskin) = PRVM_SetEngineString(prog, svs.clients[num].playerskin); // Assign netaddress (IP Address, etc) if(svs.clients[num].netconnection != NULL) { // Acquire Readable Address LHNETADDRESS_ToString(&svs.clients[num].netconnection->peeraddress, svs.clients[num].netaddress, sizeof(svs.clients[num].netaddress), false); PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString(prog, svs.clients[num].netaddress); } else PRVM_serveredictstring(e, netaddress) = PRVM_SetEngineString(prog, "null/botclient"); if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_idfp[0]) PRVM_serveredictstring(e, crypto_idfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.client_idfp); else PRVM_serveredictstring(e, crypto_idfp) = 0; PRVM_serveredictfloat(e, crypto_idfp_signed) = (svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_issigned); if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.client_keyfp[0]) PRVM_serveredictstring(e, crypto_keyfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.client_keyfp); else PRVM_serveredictstring(e, crypto_keyfp) = 0; if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.server_keyfp[0]) PRVM_serveredictstring(e, crypto_mykeyfp) = PRVM_SetEngineString(prog, svs.clients[num].netconnection->crypto.server_keyfp); else PRVM_serveredictstring(e, crypto_mykeyfp) = 0; if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated && svs.clients[num].netconnection->crypto.use_aes) PRVM_serveredictstring(e, crypto_encryptmethod) = PRVM_SetEngineString(prog, "AES128"); else PRVM_serveredictstring(e, crypto_encryptmethod) = 0; if(svs.clients[num].netconnection != NULL && svs.clients[num].netconnection->crypto.authenticated) PRVM_serveredictstring(e, crypto_signmethod) = PRVM_SetEngineString(prog, "HMAC-SHA256"); else PRVM_serveredictstring(e, crypto_signmethod) = 0; } } static void SVVM_free_edict(prvm_prog_t *prog, prvm_edict_t *ed) { int i; int e; World_UnlinkEdict(ed); // unlink from world bsp PRVM_serveredictstring(ed, model) = 0; PRVM_serveredictfloat(ed, takedamage) = 0; PRVM_serveredictfloat(ed, modelindex) = 0; PRVM_serveredictfloat(ed, colormap) = 0; PRVM_serveredictfloat(ed, skin) = 0; PRVM_serveredictfloat(ed, frame) = 0; VectorClear(PRVM_serveredictvector(ed, origin)); VectorClear(PRVM_serveredictvector(ed, angles)); PRVM_serveredictfloat(ed, nextthink) = -1; PRVM_serveredictfloat(ed, solid) = 0; VM_RemoveEdictSkeleton(prog, ed); World_Physics_RemoveFromEntity(&sv.world, ed); World_Physics_RemoveJointFromEntity(&sv.world, ed); // make sure csqc networking is aware of the removed entity e = PRVM_NUM_FOR_EDICT(ed); sv.csqcentityversion[e] = 0; for (i = 0;i < svs.maxclients;i++) svs.clients[i].csqcentitysendflags[e] = 0xFFFFFF; } static void SVVM_count_edicts(prvm_prog_t *prog) { int i; prvm_edict_t *ent; int active, models, solid, step; active = models = solid = step = 0; for (i=0 ; inum_edicts ; i++) { ent = PRVM_EDICT_NUM(i); if (ent->priv.server->free) continue; active++; if (PRVM_serveredictfloat(ent, solid)) solid++; if (PRVM_serveredictstring(ent, model)) models++; if (PRVM_serveredictfloat(ent, movetype) == MOVETYPE_STEP) step++; } Con_Printf("num_edicts:%3i\n", prog->num_edicts); Con_Printf("active :%3i\n", active); Con_Printf("view :%3i\n", models); Con_Printf("touch :%3i\n", solid); Con_Printf("step :%3i\n", step); } static qboolean SVVM_load_edict(prvm_prog_t *prog, prvm_edict_t *ent) { // remove things from different skill levels or deathmatch if (gamemode != GAME_TRANSFUSION) //Transfusion does this in QC { if (deathmatch.integer) { if (((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_DEATHMATCH)) { return false; } } else if ((current_skill <= 0 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_EASY )) || (current_skill == 1 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_MEDIUM)) || (current_skill >= 2 && ((int)PRVM_serveredictfloat(ent, spawnflags) & SPAWNFLAG_NOT_HARD ))) { return false; } } return true; } static void SV_VM_Setup(void) { prvm_prog_t *prog = SVVM_prog; PRVM_Prog_Init(prog); // allocate the mempools // TODO: move the magic numbers/constants into #defines [9/13/2006 Black] prog->progs_mempool = Mem_AllocPool("Server Progs", 0, NULL); prog->builtins = vm_sv_builtins; prog->numbuiltins = vm_sv_numbuiltins; prog->max_edicts = 512; if (sv.protocol == PROTOCOL_QUAKE) prog->limit_edicts = 640; // before quake mission pack 1 this was 512 else if (sv.protocol == PROTOCOL_QUAKEDP) prog->limit_edicts = 2048; // guessing else if (sv.protocol == PROTOCOL_NEHAHRAMOVIE) prog->limit_edicts = 2048; // guessing! else if (sv.protocol == PROTOCOL_NEHAHRABJP || sv.protocol == PROTOCOL_NEHAHRABJP2 || sv.protocol == PROTOCOL_NEHAHRABJP3) prog->limit_edicts = 4096; // guessing! else prog->limit_edicts = MAX_EDICTS; prog->reserved_edicts = svs.maxclients; prog->edictprivate_size = sizeof(edict_engineprivate_t); prog->name = "server"; prog->extensionstring = vm_sv_extensions; prog->loadintoworld = true; // all callbacks must be defined (pointers are not checked before calling) prog->begin_increase_edicts = SVVM_begin_increase_edicts; prog->end_increase_edicts = SVVM_end_increase_edicts; prog->init_edict = SVVM_init_edict; prog->free_edict = SVVM_free_edict; prog->count_edicts = SVVM_count_edicts; prog->load_edict = SVVM_load_edict; prog->init_cmd = SVVM_init_cmd; prog->reset_cmd = SVVM_reset_cmd; prog->error_cmd = Host_Error; prog->ExecuteProgram = SVVM_ExecuteProgram; PRVM_Prog_Load(prog, sv_progs.string, NULL, 0, SV_REQFUNCS, sv_reqfuncs, SV_REQFIELDS, sv_reqfields, SV_REQGLOBALS, sv_reqglobals); // some mods compiled with scrambling compilers lack certain critical // global names and field names such as "self" and "time" and "nextthink" // so we have to set these offsets manually, matching the entvars_t // but we only do this if the prog header crc matches, otherwise it's totally freeform if (prog->progs_crc == PROGHEADER_CRC || prog->progs_crc == PROGHEADER_CRC_TENEBRAE) { PRVM_ED_FindFieldOffset_FromStruct(entvars_t, modelindex); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmin); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, absmax); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ltime); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movetype); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, solid); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, origin); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, oldorigin); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, velocity); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, angles); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, avelocity); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, punchangle); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, classname); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, model); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frame); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, skin); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, effects); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, mins); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, maxs); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, size); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, touch); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, use); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, think); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, blocked); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, nextthink); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, groundentity); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, health); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, frags); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weapon); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponmodel); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, weaponframe); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, currentammo); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_shells); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_nails); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_rockets); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ammo_cells); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, items); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, takedamage); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, chain); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, deadflag); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, view_ofs); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button0); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button1); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, button2); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, impulse); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, fixangle); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, v_angle); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, idealpitch); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, netname); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, enemy); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, flags); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, colormap); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, team); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, max_health); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, teleport_time); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armortype); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, armorvalue); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, waterlevel); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, watertype); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, ideal_yaw); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, yaw_speed); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, aiment); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, goalentity); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, spawnflags); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, target); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, targetname); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_take); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_save); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, dmg_inflictor); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, owner); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, movedir); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, message); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, sounds); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise1); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise2); PRVM_ED_FindFieldOffset_FromStruct(entvars_t, noise3); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, self); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, other); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, world); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, time); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, frametime); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, force_retouch); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, mapname); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, deathmatch); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, coop); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, teamplay); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, serverflags); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_secrets); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, total_monsters); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, found_secrets); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, killed_monsters); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm1); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm2); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm3); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm4); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm5); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm6); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm7); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm8); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm9); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm10); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm11); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm12); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm13); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm14); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm15); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, parm16); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_forward); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_up); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, v_right); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_allsolid); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_startsolid); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_fraction); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_endpos); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_normal); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_plane_dist); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_ent); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inopen); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, trace_inwater); PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, msg_entity); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, main); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, StartFrame); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPreThink); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PlayerPostThink); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientKill); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientConnect); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, PutClientInServer); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, ClientDisconnect); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetNewParms); // PRVM_ED_FindGlobalOffset_FromStruct(globalvars_t, SetChangeParms); } else Con_DPrintf("%s: %s system vars have been modified (CRC %i != engine %i), will not load in other engines", prog->name, sv_progs.string, prog->progs_crc, PROGHEADER_CRC); // OP_STATE is always supported on server because we add fields/globals for it prog->flag |= PRVM_OP_STATE; VM_CustomStats_Clear();//[515]: csqc SV_Prepare_CSQC(); } extern cvar_t host_maxwait; extern cvar_t host_framerate; static int SV_ThreadFunc(void *voiddata) { prvm_prog_t *prog = SVVM_prog; qboolean playing = false; double sv_timer = 0; double sv_deltarealtime, sv_oldrealtime, sv_realtime; double wait; int i; char vabuf[1024]; sv_realtime = Sys_DirtyTime(); while (!svs.threadstop) { // FIXME: we need to handle Host_Error in the server thread somehow // if (setjmp(sv_abortframe)) // continue; // something bad happened in the server game sv_oldrealtime = sv_realtime; sv_realtime = Sys_DirtyTime(); sv_deltarealtime = sv_realtime - sv_oldrealtime; if (sv_deltarealtime < 0 || sv_deltarealtime >= 1800) sv_deltarealtime = 0; sv_timer += sv_deltarealtime; svs.perf_acc_realtime += sv_deltarealtime; // at this point we start doing real server work, and must block on any client activity pertaining to the server (such as executing SV_SpawnServer) SV_LockThreadMutex(); // Look for clients who have spawned playing = false; if (sv.active) for (i = 0, host_client = svs.clients;i < svs.maxclients;i++, host_client++) if(host_client->begun) if(host_client->netconnection) playing = true; if(sv.time < 10) { // don't accumulate time for the first 10 seconds of a match // so things can settle svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; } else if(svs.perf_acc_realtime > 5) { svs.perf_cpuload = 1 - svs.perf_acc_sleeptime / svs.perf_acc_realtime; svs.perf_lost = svs.perf_acc_lost / svs.perf_acc_realtime; if(svs.perf_acc_offset_samples > 0) { svs.perf_offset_max = svs.perf_acc_offset_max; svs.perf_offset_avg = svs.perf_acc_offset / svs.perf_acc_offset_samples; svs.perf_offset_sdev = sqrt(svs.perf_acc_offset_squared / svs.perf_acc_offset_samples - svs.perf_offset_avg * svs.perf_offset_avg); } if(svs.perf_lost > 0 && developer_extra.integer) if(playing) Con_DPrintf("Server can't keep up: %s\n", Host_TimingReport(vabuf, sizeof(vabuf))); svs.perf_acc_realtime = svs.perf_acc_sleeptime = svs.perf_acc_lost = svs.perf_acc_offset = svs.perf_acc_offset_squared = svs.perf_acc_offset_max = svs.perf_acc_offset_samples = 0; } // get new packets if (sv.active) NetConn_ServerFrame(); // if the accumulators haven't become positive yet, wait a while wait = sv_timer * -1000000.0; if (wait >= 1) { double time0, delta; SV_UnlockThreadMutex(); // don't keep mutex locked while sleeping if (host_maxwait.value <= 0) wait = min(wait, 1000000.0); else wait = min(wait, host_maxwait.value * 1000.0); if(wait < 1) wait = 1; // because we cast to int time0 = Sys_DirtyTime(); Sys_Sleep((int)wait); delta = Sys_DirtyTime() - time0;if (delta < 0 || delta >= 1800) delta = 0; svs.perf_acc_sleeptime += delta; continue; } if (sv.active && sv_timer > 0) { // execute one server frame double advancetime; float offset; if (sys_ticrate.value <= 0) advancetime = min(sv_timer, 0.1); // don't step more than 100ms else advancetime = sys_ticrate.value; if(advancetime > 0) { offset = sv_timer + (Sys_DirtyTime() - sv_realtime); // LordHavoc: FIXME: I don't understand this line ++svs.perf_acc_offset_samples; svs.perf_acc_offset += offset; svs.perf_acc_offset_squared += offset * offset; if(svs.perf_acc_offset_max < offset) svs.perf_acc_offset_max = offset; } // only advance time if not paused // the game also pauses in singleplayer when menu or console is used sv.frametime = advancetime * slowmo.value; if (host_framerate.value) sv.frametime = host_framerate.value; if (sv.paused || (cl.islocalgame && (key_dest != key_game || key_consoleactive || cl.csqc_paused))) sv.frametime = 0; sv_timer -= advancetime; // move things around and think unless paused if (sv.frametime) SV_Physics(); // send all messages to the clients SV_SendClientMessages(); if (sv.paused == 1 && sv_realtime > sv.pausedstart && sv.pausedstart > 0) { PRVM_serverglobalfloat(time) = sv.time; prog->globals.fp[OFS_PARM0] = sv_realtime - sv.pausedstart; prog->ExecuteProgram(prog, PRVM_serverfunction(SV_PausedTic), "QC function SV_PausedTic is missing"); } // send an heartbeat if enough time has passed since the last one NetConn_Heartbeat(0); } // we're back to safe code now SV_UnlockThreadMutex(); // if there is some time remaining from this frame, reset the timers if (sv_timer >= 0) { svs.perf_acc_lost += sv_timer; sv_timer = 0; } } return 0; } void SV_StartThread(void) { if (!sv_threaded.integer || !Thread_HasThreads()) return; svs.threaded = true; svs.threadstop = false; svs.threadmutex = Thread_CreateMutex(); svs.thread = Thread_CreateThread(SV_ThreadFunc, NULL); } void SV_StopThread(void) { if (!svs.threaded) return; svs.threadstop = true; Thread_WaitThread(svs.thread, 0); Thread_DestroyMutex(svs.threadmutex); svs.threaded = false; } darkplaces/darkplaces-wgl-vs2010.vcxproj0000664000175000017500000003227713067716220017444 0ustar kalevkalev Debug Win32 Release Win32 {6E3D4311-BB84-4EB7-8C6C-10AA3D249C28} darkplaceswgl Win32Proj Application MultiByte true Application MultiByte <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ true $(SolutionDir)\ $(Configuration)-$(ProjectName)-$(Platform)\ false $(ProjectName) $(ProjectName) Disabled CONFIG_MENU;CONFIG_CD;WIN32;_DEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL Level4 EditAndContinue CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) true Windows MachineX86 MaxSpeed true CONFIG_MENU;CONFIG_CD;WIN32;NDEBUG;_WINDOWS;WIN32_LEAN_AND_MEAN;SUPPORTDIRECTX;SUPPORTD3D;_FILE_OFFSET_BITS=64;__KERNEL_STRICT_NAMES;%(PreprocessorDefinitions) MultiThreadedDLL true Level4 ProgramDatabase CompileAsCpp 4706;4127;4100;4055;4054;4244;4305;4702;4201;4611;%(DisableSpecificWarnings) $(OutDir)$(TargetName)$(TargetExt) true Windows true true MachineX86 darkplaces/darkplaces16x16.png0000664000175000017500000000140213067716220015505 0ustar kalevkalev‰PNG  IHDRóÿaÉIDATxœu“OH“qÇ?Û;]«Íw*+×H35ÉI)êP‘)‚¤Å¬?t Šôt‰7;d„ì &»$ƒaÚÉCÅ:ˆa-1ÍõG˵5ç¦Î?óÝ:ôìïåy~ßçÏïy¾°½©Ýnw©@ýŸwÛ$I2¤Cß3 2Az+O³ _•Ÿ¥=¬Ú½÷8°¿µµ5Z^^nʪŒ©oiiÖ€ µ5JÉ,Dç¾ì‹Âû€|‡Ã¼mmm_–»ï>r»ŸÉ##îtuuÅÄ¿Q$I]7ÎÝ«Òãl°˜Ÿv^ºnG\@lnn.,€‰ÞÞÞ§@ `T*I’ Ÿº¥×A¨_b°¨ÄÎ*;œP:R2èéé‰ßºy¥OL‡Îe€QªÑ6k—{Íf³M1g÷¬ÿå@H)‰ù¬ü9lÊ@X˜›ºì÷û‡]v»Ý¯Õa1ìɼh9”]\\\`2™´±Xl²½½]6ïXÓ’X••ÑE ±à“Mó>ŸD\.Wò¬V«ÞÕÑÑyÿáƒwSÁ`ˆÍ¼zQ;½ðmX’j€„ž ÑhÌQZ^V€éßA³9L 9N§³H’¤#9ÙÙ×B¶çÀMMEaž>ä?»ã`•'„•`H­,-]¯uiýÆbÉwÏp½®ÚÖít:×/ðS ÈrU}¯ny.ráXéU»Ýn´Êï«škOïOÕEÞ—ó! $5›;îëë ½ð6­øÕ¾Ïc“‰¢“³@P©úK9åR+{ÞâÀo` ‹@²²Ö:žL&½À¬RõŸ6﫶þª0ÐK@XÞ$üؾOôj-IEND®B`‚darkplaces/dpdefs/0000775000175000017500000000000013067716406013436 5ustar kalevkalevdarkplaces/dpdefs/README.txt0000664000175000017500000000032113067716220015122 0ustar kalevkalevYet incomplete collected definition files for the DP extensions etc. TODO: - make sure only fields/globals that DP actually uses are in the defs files - make sure all builtins of DP are mentioned (by script) darkplaces/dpdefs/menudefs.qc0000664000175000017500000005111013067716220015561 0ustar kalevkalev////////////////////////////////////////////////////////// // sys globals entity self; ///////////////////////////////////////////////////////// void end_sys_globals; ///////////////////////////////////////////////////////// // sys fields ///////////////////////////////////////////////////////// void end_sys_fields; ///////////////////////////////////////////////////////// // sys functions void() m_init; void(float keynr, float ascii) m_keydown; void(float width, float height) m_draw; void(float mode) m_toggle; void() m_shutdown; // optional: float(float) m_gethostcachecategory; ///////////////////////////////////////////////////////// // sys constants /////////////////////////// // key dest constants float KEY_UNKNOWN = -1; float KEY_GAME = 0; float KEY_MENU = 2; float KEY_MENU_GRABBED = 3; /////////////////////////// // file constants float FILE_READ = 0; float FILE_APPEND = 1; float FILE_WRITE = 2; /////////////////////////// // logical constants (just for completeness) float TRUE = 1; float FALSE = 0; /////////////////////////// // boolean constants float true = 1; float false = 0; /////////////////////////// // msg constants float MSG_BROADCAST = 0; // unreliable to all float MSG_ONE = 1; // reliable to one (msg_entity) float MSG_ALL = 2; // reliable to all float MSG_INIT = 3; // write to the init string ///////////////////////////// // mouse target constants float MT_MENU = 1; float MT_CLIENT = 2; ///////////////////////// // client state constants float CS_DEDICATED = 0; float CS_DISCONNECTED = 1; float CS_CONNECTED = 2; /////////////////////////// // blend flags float DRAWFLAG_NORMAL = 0; float DRAWFLAG_ADDITIVE = 1; float DRAWFLAG_MODULATE = 2; float DRAWFLAG_2XMODULATE = 3; /////////////////////////// // null entity (actually it is the same like the world entity) entity null_entity; /////////////////////////// // error constants // file handling float ERR_CANNOTOPEN = -1; // fopen float ERR_NOTENOUGHFILEHANDLES = -2; // fopen float ERR_INVALIDMODE = -3; // fopen float ERR_BADFILENAME = -4; // fopen // drawing functions float ERR_NULLSTRING = -1; float ERR_BADDRAWFLAG = -2; float ERR_BADSCALE = -3; float ERR_BADSIZE = -3; // same as ERR_BADSCALE float ERR_NOTCACHED = -4; // server list stuff float SLIST_HOSTCACHEVIEWCOUNT = 0; float SLIST_HOSTCACHETOTALCOUNT = 1; float SLIST_MASTERQUERYCOUNT = 2; float SLIST_MASTERREPLYCOUNT = 3; float SLIST_SERVERQUERYCOUNT = 4; float SLIST_SERVERREPLYCOUNT = 5; float SLIST_SORTFIELD = 6; float SLIST_SORTDESCENDING = 7; float SLIST_LEGACY_LINE1 = 1024; float SLIST_LEGACY_LINE2 = 1025; float SLIST_TEST_CONTAINS = 0; float SLIST_TEST_NOTCONTAIN = 1; float SLIST_TEST_LESSEQUAL = 2; float SLIST_TEST_LESS = 3; float SLIST_TEST_EQUAL = 4; float SLIST_TEST_GREATER = 5; float SLIST_TEST_GREATEREQUAL = 6; float SLIST_TEST_NOTEQUAL = 7; float SLIST_TEST_STARTSWITH = 8; float SLIST_TEST_NOTSTARTSWITH = 9; float SLIST_MASK_AND = 0; float SLIST_MASK_OR = 512; // font stuff float FONT_DEFAULT = 0; float FONT_CONSOLE = 1; float FONT_SBAR = 2; float FONT_NOTIFY = 3; float FONT_CHAT = 4; float FONT_CENTERPRINT = 5; float FONT_INFOBAR = 6; float FONT_MENU = 7; float FONT_USER = 8; // add to this the index, like FONT_USER+3 = user3. At least 8 of them are supported. float drawfont; /* not supported at the moment /////////////////////////// // os constants float OS_WINDOWS = 0; float OS_LINUX = 1; float OS_MAC = 2; */ ////////////////////////////////////////////////// // common cmd ////////////////////////////////////////////////// // AK FIXME: Create perhaps a special builtin file for the common cmds void checkextension(string ext) = #1; // error cmds void error(string err,...) = #2; void objerror(string err,...) = #3; // print void print(string text,...) = #4; void bprint(string text,...) = #5; void sprint(float clientnum, string text,...) = #6; void centerprint(string text,...) = #7; // vector stuff vector normalize(vector v) = #8; float vlen(vector v) = #9; float vectoyaw(vector v) = #10; vector vectoangles(vector v) = #11; float random(void) = #12; void cmd(string command, ...) = #13; // cvar cmds float cvar(string name) = #14; const string str_cvar(string name) = #71; void cvar_set(string name, string value) = #15; void dprint(string text,...) = #16; // conversion functions string ftos(float f) = #17; float fabs(float f) = #18; string vtos(vector v) = #19; string etos(entity e) = #20; float stof(string val,...) = #21; entity spawn(void) = #22; void remove(entity e) = #23; entity find(entity start, .string field, string match) = #24; entity findfloat(entity start, .float field, float match) = #25; entity findentity(entity start, .entity field, entity match) = #25; entity findchainstring(.string field, string match) = #26; entity findchainfloat(.float field, float match) = #27; entity findchainentity(.entity field, entity match) = #27; string precache_file(string file) = #28; string precache_sound(string sample) = #29; void crash(void) = #72; void coredump(void) = #30; void stackdump(void) = #73; void traceon(void) = #31; void traceoff(void) = #32; void eprint(entity e) = #33; float rint(float f) = #34; float floor(float f) = #35; float ceil(float f) = #36; entity nextent(entity e) = #37; float sin(float f) = #38; float cos(float f) = #39; float sqrt(float f) = #40; vector randomvec(void) = #41; float registercvar(string name, string value, float flags) = #42; // returns 1 if success float min(float f,...) = #43; float max(float f,...) = #44; float bound(float min,float value, float max) = #45; float pow(float a, float b) = #46; void copyentity(entity src, entity dst) = #47; float fopen(string filename, float mode) = #48; void fclose(float fhandle) = #49; string fgets(float fhandle) = #50; void fputs(float fhandle, string s) = #51; float strlen(string s) = #52; string strcat(string s1,string s2,...) = #53; string substring(string s, float start, float length) = #54; vector stov(string s) = #55; string strzone(string s) = #56; void strunzone(string s) = #57; float tokenize(string s) = #58; string argv(float n) = #59; float isserver(void) = #60; float clientcount(void) = #61; float clientstate(void) = #62; void clientcommand(float client, string s) = #63; void changelevel(string map) = #64; void localsound(string sample) = #65; vector getmousepos(void) = #66; float gettime(void) = #67; void loadfromdata(string data) = #68; void loadfromfile(string file) = #69; float mod(float val, float m) = #70; float search_begin(string pattern, float caseinsensitive, float quiet) = #74; void search_end(float handle) = #75; float search_getsize(float handle) = #76; string search_getfilename(float handle, float num) = #77; string chr(float ascii) = #78; ///////////////////////////////////////////////// // Write* Functions ///////////////////////////////////////////////// void WriteByte(float data, float dest, float desto) = #401; void WriteChar(float data, float dest, float desto) = #402; void WriteShort(float data, float dest, float desto) = #403; void WriteLong(float data, float dest, float desto) = #404; void WriteAngle(float data, float dest, float desto) = #405; void WriteCoord(float data, float dest, float desto) = #406; void WriteString(string data, float dest, float desto)= #407; void WriteEntity(entity data, float dest, float desto) = #408; ////////////////////////////////////////////////// // Draw funtions ////////////////////////////////////////////////// float iscachedpic(string name) = #451; string precache_pic(string name, ...) = #452; void freepic(string name) = #453; float drawcharacter(vector position, float character, vector scale, vector rgb, float alpha, float flag) = #454; float drawstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #455; float drawcolorcodedstring(vector position, string text, vector scale, float alpha, float flag) = #467; vector drawcolorcodedstring2(vector position, string text, vector scale, vector rgb, float alpha, float flag) = #467; float drawpic(vector position, string pic, vector size, vector rgb, float alpha, float flag) = #456; float drawfill(vector position, vector size, vector rgb, float alpha, float flag) = #457; void drawsetcliparea(float x, float y, float width, float height) = #458; void drawresetcliparea(void) = #459; vector drawgetimagesize(string pic) = #460; //////////////////////////////////////////////// // Menu functions //////////////////////////////////////////////// void setkeydest(float dest) = #601; float getkeydest(void) = #602; void setmousetarget(float trg) = #603; float getmousetarget(void) = #604; float isfunction(string function_name) = #607; void callfunction(...) = #605; void writetofile(float fhandle, entity ent) = #606; vector getresolution(float number) = #608; string keynumtostring(float keynum) = #609; float gethostcachevalue(float type) = #611; string gethostcachestring(float type, float hostnr) = #612; //DP_CSQC_BINDMAPS //idea: daemon, motorsep //darkplaces implementation: divVerent //builtin definitions: string(float key, float bindmap) getkeybind_bindmap = #342; float(float key, string bind, float bindmap) setkeybind_bindmap = #630; vector(void) getbindmaps = #631; float(vector bm) setbindmaps = #632; string(string command, float bindmap) findkeysforcommand = #610; float(string key) stringtokeynum = #341; // string(float keynum) keynumtostring = #340; //description: key bind setting/getting including support for switchable //bindmaps. //DP_CRYPTO //idea: divVerent //darkplaces implementation: divVerent //field definitions: (MENUQC) string(string serveraddress) crypto_getkeyfp = #633; // retrieves the cached host key's CA fingerprint of a server given by IP address string(string serveraddress) crypto_getidfp = #634; // retrieves the cached host key fingerprint of a server given by IP address float(string serveraddress) crypto_getidstatus = #643; // retrieves the cached host key's key status. See below for CRYPTO_IDSTATUS_ defines. string(string serveraddress) crypto_getencryptlevel = #635; // 0 if never encrypting, 1 supported, 2 requested, 3 required, appended by list of allowed methods in order of preference ("AES128"), preceded by a space each string(float i) crypto_getmykeyfp = #636; // retrieves the CA key fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list string(float i) crypto_getmyidfp = #637; // retrieves the ID fingerprint of a given CA slot, or "" if slot is unused but more to come, or string_null if end of list float CRYPTO_IDSTATUS_OUTOFRANGE = -1; float CRYPTO_IDSTATUS_EMPTY = 0; float CRYPTO_IDSTATUS_UNSIGNED = 1; float CRYPTO_IDSTATUS_SIGNED = 2; float(float i) crypto_getmyidstatus = #641; // retrieves the ID's status of a given CA slot, or 0 if slot is unused but more to come, or -1 if end of list float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; //description: //use -1 as buffer handle to justs end delim as postdata //DP_GECKO_SUPPORT //idea: Res2k, BlackHC //darkplaces implementation: Res2k, BlackHC //constant definitions: float GECKO_BUTTON_DOWN = 0; float GECKO_BUTTON_UP = 1; // either use down and up or just press but not all of them! float GECKO_BUTTON_PRESS = 2; // use this for mouse events if needed? float GECKO_BUTTON_DOUBLECLICK = 3; //builtin definitions: float gecko_create( string name ) = #487; void gecko_destroy( string name ) = #488; void gecko_navigate( string name, string URI ) = #489; float gecko_keyevent( string name, float key, float eventtype ) = #490; void gecko_mousemove( string name, float x, float y ) = #491; void gecko_resize( string name, float w, float h ) = #492; vector gecko_get_texture_extent( string name ) = #493; //engine-called QC prototypes: //string(string name, string query) Qecko_Query; //description: //provides an interface to the offscreengecko library and allows for internet browsing in games //FTE_STRINGS //idea: many //darkplaces implementation: KrimZon //description: //various string manipulation functions float(string str, string sub, float startpos) strstrofs = #221; float(string str, float ofs) str2chr = #222; string(float c, ...) chr2str = #223; string(float ccase, float calpha, float cnum, string s, ...) strconv = #224; string(float chars, string s, ...) strpad = #225; string(string info, string key, string value, ...) infoadd = #226; string(string info, string key) infoget = #227; float(string s1, string s2) strcmp = #228; float(string s1, string s2, float len) strncmp = #228; float(string s1, string s2) strcasecmp = #229; float(string s1, string s2, float len) strncasecmp = #230; //DP_PRECACHE_PIC_FLAGS //idea: divVerent //darkplaces implementation: divVerent //constant definitions: float PRECACHE_PIC_FROMWAD = 1; // this one actually is part of EXT_CSQC float PRECACHE_PIC_NOTPERSISTENT = 2; // picture may get deallocated when unused float PRECACHE_PIC_MIPMAP = 8; // mipmap the texture for possibly better downscaling at memory expense //notes: these constants are given as optional second argument to precache_pic() //DP_QC_CRC16 //idea: div0 //darkplaces implementation: div0 //Some hash function to build hash tables with. This has to be be the CRC-16-CCITT that is also required for the QuakeWorld download protocol. //When caseinsensitive is set, the CRC is calculated of the lower cased string. float(float caseinsensitive, string s, ...) crc16 = #494; //DP_QC_CVAR_TYPE float(string name) cvar_type = #495; float CVAR_TYPEFLAG_EXISTS = 1; float CVAR_TYPEFLAG_SAVED = 2; float CVAR_TYPEFLAG_PRIVATE = 4; float CVAR_TYPEFLAG_ENGINE = 8; float CVAR_TYPEFLAG_HASDESCRIPTION = 16; float CVAR_TYPEFLAG_READONLY = 32; //DP_QC_STRINGBUFFERS //idea: ?? //darkplaces implementation: LordHavoc //functions to manage string buffer objects - that is, arbitrary length string arrays that are handled by the engine float() buf_create = #440; void(float bufhandle) buf_del = #441; float(float bufhandle) buf_getsize = #442; void(float bufhandle_from, float bufhandle_to) buf_copy = #443; void(float bufhandle, float sortpower, float backward) buf_sort = #444; string(float bufhandle, string glue) buf_implode = #445; string(float bufhandle, float string_index) bufstr_get = #446; void(float bufhandle, float string_index, string str) bufstr_set = #447; float(float bufhandle, string str, float order) bufstr_add = #448; void(float bufhandle, float string_index) bufstr_free = #449; void(float bufhandle, string pattern, string antipattern) buf_cvarlist = #517; //DP_QC_STRING_CASE_FUNCTIONS //idea: Dresk //darkplaces implementation: LordHavoc / Dresk //builtin definitions: string(string s) strtolower = #480; // returns the passed in string in pure lowercase form string(string s) strtoupper = #481; // returns the passed in string in pure uppercase form //description: //provides simple string uppercase and lowercase functions //DP_QC_CVAR_DESCRIPTION //idea: divVerent //DarkPlaces implementation: divVerent //builtin definitions: string(string name) cvar_description = #518; //description: //returns the description of a cvar //DP_QC_DIGEST //idea: motorsep, Spike //DarkPlaces implementation: divVerent //builtin definitions: string(string digest, string data, ...) digest_hex = #639; //description: //returns a given hex digest of given data //the returned digest is always encoded in hexadecimal //only the "MD4" digest is always supported! //if the given digest is not supported, string_null is returned //the digest string is matched case sensitively, use "MD4", not "md4"! //DP_QC_URI_ESCAPE //idea: div0 //darkplaces implementation: div0 //URI::Escape's functionality string(string in) uri_escape = #510; string(string in) uri_unescape = #511; //DP_QC_URI_GET //idea: divVerent //darkplaces implementation: divVerent //loads text from an URL into a string //returns 1 on success of initiation, 0 if there are too many concurrent //connections already or if the URL is invalid //the following callback will receive the data and MUST exist! // void(float id, float status, string data) URI_Get_Callback; //status is either // negative for an internal error, // 0 for success, or // the HTTP response code on server error (e.g. 404) //if 1 is returned by uri_get, the callback will be called in the future float(string url, float id) uri_get = #513; //DP_QC_URI_POST //idea: divVerent //darkplaces implementation: divVerent //loads text from an URL into a string after POSTing via HTTP //works like uri_get, but uri_post sends data with Content-Type: content_type to the server //and uri_post sends the string buffer buf, joined using the delimiter delim float(string url, float id, string content_type, string data) uri_post = #513; float(string url, float id, string content_type, string delim, float buf) uri_postbuf = #513; //DP_QC_ENTITYDATA //idea: KrimZon //darkplaces implementation: KrimZon //builtin definitions: float() numentityfields = #496; string(float fieldnum) entityfieldname = #497; float(float fieldnum) entityfieldtype = #498; string(float fieldnum, entity ent) getentityfieldstring = #499; float(float fieldnum, entity ent, string s) putentityfieldstring = #500; //constants: //Returned by entityfieldtype float FIELD_STRING = 1; float FIELD_FLOAT = 2; float FIELD_VECTOR = 3; float FIELD_ENTITY = 4; float FIELD_FUNCTION = 6; //description: //Versatile functions intended for storing data from specific entities between level changes, but can be customized for some kind of partial savegame. //WARNING: .entity fields cannot be saved and restored between map loads as they will leave dangling pointers. //numentityfields returns the number of entity fields. NOT offsets. Vectors comprise 4 fields: v, v_x, v_y and v_z. //entityfieldname returns the name as a string, eg. "origin" or "classname" or whatever. //entityfieldtype returns a value that the constants represent, but the field may be of another type in more exotic progs.dat formats or compilers. //getentityfieldstring returns data as would be written to a savegame, eg... "0.05" (float), "0 0 1" (vector), or "Hello World!" (string). Function names can also be returned. //putentityfieldstring puts the data returned by getentityfieldstring back into the entity. //DP_COVERAGE //idea: divVerent //darkplaces implementation: divVerent //function definitions: void coverage() = #642; // Reports a coverage event. The engine counts for each of the calls to this builtin whether it has been called. // assorted undocumented extensions string(string, float) netaddress_resolve = #625; string(string search, string replace, string subject) strreplace = #484; string(float uselocaltime, string format, ...) strftime = #478; float(string s) tokenize_console = #514; float(float i) argv_start_index = #515; float(float i) argv_end_index = #516; string(float, float) getgamedirinfo = #626; #define GETGAMEDIRINFO_NAME 0 #define GETGAMEDIRINFO_DESCRIPTION 1 float log(float f) = #532; string(string format, ...) sprintf = #627; string(string s) strdecolorize = #477; entity findflags(entity start, .float field, float match) = #87; entity findchainflags(.float field, float match) = #88; float(string s, string separator1, ...) tokenizebyseparator = #479; float etof(entity ent) = #79; entity ftoe(float num) = #80; float validstring(string str) = #81; float altstr_count(string str) = #82; string altstr_prepare(string str) = #83; string altstr_get(string str, float num) = #84; string altstr_set(string str, float num, string set) = #85; string altstr_ins(string str, float num, string set) = #86; float isdemo() = #349; float drawsubpic(vector position, vector size, string pic, vector srcPosition, vector srcSize, vector rgb, float alpha, float flag) = #469; //vector getresolution(float number, ...) = #608; // optional argument "isfullscreen" void parseentitydata(entity ent, string data) = #613; void resethostcachemasks(void) = #615; void sethostcachemaskstring(float mask, float fld, string str, float op) = #616; void sethostcachemasknumber(float mask, float fld, float num, float op) = #617; void resorthostcache(void) = #618; float SLSF_DESCENDING = 1; float SLSF_FAVORITES = 2; float SLSF_CATEGORIES = 4; void sethostcachesort(float fld, float slsf) = #619; void refreshhostcache(...) = #620; // optional boolean argument "clear_list" float gethostcachenumber(float fld, float hostnr) = #621; float gethostcacheindexforkey(string key) = #622; void addwantedhostcachekey(string key) = #623; string getextresponse(void) = #624; const string cvar_string(string name) = #71; const string cvar_defstring(string name) = #89; float stringwidth(string text, float handleColors, vector size) = #468; darkplaces/dpdefs/source_compare.pl0000775000175000017500000001247313067716220017005 0ustar kalevkalevuse strict; use warnings; my %vm = ( menu => {}, csprogs => {}, progs => {} ); my $skip = 0; my $parsing_builtins = undef; my $parsing_builtin = 0; my $parsing_fields = undef; my $parsing_globals = undef; my $parsing_vm = undef; for(<../*.h>, <../*.c>) { open my $fh, "<", $_ or die "<$_: $!"; while(<$fh>) { chomp; if(/^#if 0$/) { $skip = 1; } elsif(/^#else$/) { $skip = 0; } elsif(/^#endif$/) { $skip = 0; } elsif($skip) { } elsif(/^prvm_builtin_t vm_m_/) { $parsing_builtins = "menu"; $parsing_builtin = 0; } elsif(/^prvm_builtin_t vm_cl_/) { $parsing_builtins = "csprogs"; $parsing_builtin = 0; } elsif(/^prvm_builtin_t vm_sv_/) { $parsing_builtins = "progs"; $parsing_builtin = 0; } elsif(/^\}/) { $parsing_builtins = undef; $parsing_globals = undef; $parsing_fields = undef; $parsing_vm = undef; } elsif(/^typedef struct entvars_s$/) { $parsing_fields = "fields"; $parsing_vm = "progs"; } elsif(/^typedef struct cl_entvars_s$/) { $parsing_fields = "fields"; $parsing_vm = "csprogs"; } elsif(/^typedef struct prvm_prog_fieldoffsets_s$/) { $parsing_fields = "fields"; } elsif(/^typedef struct globalvars_s$/) { $parsing_globals = "globals"; $parsing_vm = "progs"; } elsif(/^typedef struct cl_globalvars_s$/) { $parsing_globals = "globals"; $parsing_vm = "csprogs"; } elsif(/^typedef struct m_globalvars_s$/) { $parsing_globals = "globals"; $parsing_vm = "menu"; } elsif(/^typedef struct prvm_prog_globaloffsets_s$/) { $parsing_globals = "globals"; } elsif($parsing_builtins) { s/\/\*.*?\*\// /g; if(/^\s*\/\//) { } elsif(/^NULL\b/) { $parsing_builtin += 1; } elsif(/^(\w+)\s*,?\s*\/\/\s+#(\d+)\s*(.*)/) { my $func = $1; my $builtin = int $2; my $descr = $3; my $extension = "DP_UNKNOWN"; if($descr =~ s/\s+\(([0-9A-Z_]*)\)//) { $extension = $1; } # 'void(vector ang) makevectors' if($descr eq "") { } elsif($descr eq "draw functions...") { } elsif($descr =~ /^\/\//) { } elsif($descr =~ /\) (\w+)/) { $func = $1; } elsif($descr =~ /(\w+)\s*\(/) { $func = $1; } elsif($descr =~ /^\w+$/) { $func = $descr; } else { warn "No function name found in $descr"; } warn "builtin sequence error: #$builtin (expected: $parsing_builtin)" if $builtin != $parsing_builtin; $parsing_builtin = $builtin + 1; $vm{$parsing_builtins}{builtins}[$builtin] = [0, $func, $extension]; } else { warn "Fails to parse: $_"; } } elsif($parsing_fields || $parsing_globals) { my $f = $parsing_fields || $parsing_globals; if(/^\s*\/\//) { } elsif(/^\s+(?:int|float|string_t|vec3_t|func_t)\s+(\w+);\s*(?:\/\/(.*))?/) { my $name = $1; my $descr = $2 || ""; my $extension = "DP_UNKNOWN"; $extension = $1 if $descr =~ /\b([0-9A-Z_]+)\b/; my $found = undef; $vm{menu}{$f}{$name} = ($found = [0, $extension]) if $descr =~ /common|menu/; $vm{progs}{$f}{$name} = ($found = [0, $extension]) if $descr =~ /common|ssqc/; $vm{csprogs}{$f}{$name} = ($found = [0, $extension]) if $descr =~ /common|csqc/; $vm{$parsing_vm}{$f}{$name} = ($found = [0, $extension]) if not defined $found and defined $parsing_vm; warn "$descr does not yield info about target VM" if not defined $found; } } elsif(/getglobal\w*\(\w+, "(\w+)"\)/) { # hack for weird DP source $vm{csprogs}{globals}{$1} = [0, "DP_CSQC_SPAWNPARTICLE"]; } } close $fh; } # now read in dpdefs for(( ["csprogsdefs.qc", "csprogs"], ["dpextensions.qc", "progs"], ["menudefs.qc", "menu"], ["progsdefs.qc", "progs"] )) { my ($file, $v) = @$_; open my $fh, "<", "$file" or die "<$file: $!"; while(<$fh>) { s/\/\/.*//; if(/^(?:float|entity|string|vector)\s+((?:\w+\s*,\s*)*\w+)\s*;/) { for(split /\s*,\s*/, $1) { print "// $v: Global $_ declared but not defined\n" if not $vm{$v}{globals}{$_}; $vm{$v}{globals}{$_}[0] = 1; # documented! } } elsif(/^\.(?:float|entity|string|vector|void)(?:.*\))?\s+((?:\w+\s*,\s*)*\w+)\s*;/) { for(split /\s*,\s*/, $1) { print "// $v: Field $_ declared but not defined\n" if not $vm{$v}{fields}{$_}; $vm{$v}{fields}{$_}[0] = 1; # documented! } } elsif(/#(\d+)/) { print "// $v: Builtin #$1 declared but not defined\n" if not $vm{$v}{builtins}[$1]; $vm{$v}{builtins}[$1][0] = 1; # documented! } else { } } close $fh; } # some dumb output for my $v(sort keys %vm) { print "/******************************************\n"; print " * $v\n"; print " ******************************************/\n"; my $b = $vm{$v}{builtins}; for(0..@$b) { next if not defined $b->[$_]; my ($documented, $func, $extension) = @{$b->[$_]}; print "float $func(...) = #$_; // $extension\n" unless $documented; } my $g = $vm{$v}{globals}; for(sort keys %$g) { my ($documented, $extension) = @{$g->{$_}}; print "float $_; // $extension\n" unless $documented; } my $f = $vm{$v}{fields}; for(sort keys %$f) { my ($documented, $extension) = @{$f->{$_}}; print ".float $_; // $extension\n" unless $documented; } } __END__ use Data::Dumper; $Data::Dumper::Sortkeys = 1; print Dumper \%vm; darkplaces/dpdefs/keycodes.qc0000664000175000017500000000610013067716220015560 0ustar kalevkalev/////////////////////////// // key constants // // these are the key numbers that should be passed to Key_Event // float K_TAB = 9; float K_ENTER = 13; float K_ESCAPE = 27; float K_SPACE = 32; // normal keys should be passed as lowercased ascii float K_BACKSPACE = 127; float K_UPARROW = 128; float K_DOWNARROW = 129; float K_LEFTARROW = 130; float K_RIGHTARROW = 131; float K_ALT = 132; float K_CTRL = 133; float K_SHIFT = 134; float K_F1 = 135; float K_F2 = 136; float K_F3 = 137; float K_F4 = 138; float K_F5 = 139; float K_F6 = 140; float K_F7 = 141; float K_F8 = 142; float K_F9 = 143; float K_F10 = 144; float K_F11 = 145; float K_F12 = 146; float K_INS = 147; float K_DEL = 148; float K_PGDN = 149; float K_PGUP = 150; float K_HOME = 151; float K_END = 152; float K_NUMLOCK = 154; float K_CAPSLOCK = 155; float K_SCROLLOCK = 156; float K_KP_0 = 157; float K_KP_INS = 157; // same as K_KP_0 float K_KP_1 = 158; float K_KP_END = 158; // same as K_KP_1 float K_KP_2 = 159; float K_KP_DOWNARROW = 159; // same as K_KP_2 float K_KP_3 = 160; float K_KP_PGDN = 160; // same as K_KP_3 float K_KP_4 = 161; float K_KP_LEFTARROW = 161; // same as K_KP_4 float K_KP_5 = 162; float K_KP_6 = 163; float K_KP_RIGHTARROW = 163; // same as K_KP_6 float K_KP_7 = 164; float K_KP_HOME = 164; // same as K_KP_7 float K_KP_8 = 165; float K_KP_UPARROW = 165; // same as K_KP_8 float K_KP_9 = 166; float K_KP_PGUP = 166; // same as K_KP_9 float K_KP_PERIOD = 167; float K_KP_DEL = 167; // same as K_KP_PERIOD float K_KP_DIVIDE = 168; float K_KP_SLASH = 168; // same as K_KP_DIVIDE float K_KP_MULTIPLY = 169; float K_KP_MINUS = 170; float K_KP_PLUS = 171; float K_KP_ENTER = 172; float K_KP_EQUALS = 173; // mouse buttons generate virtual keys float K_PAUSE = 153; // // joystick buttons // float K_JOY1 = 768; float K_JOY2 = 769; float K_JOY3 = 770; float K_JOY4 = 771; // // // aux keys are for multi-buttoned joysticks to generate so they can use // the normal binding process // float K_AUX1 = 772; float K_AUX2 = 773; float K_AUX3 = 774; float K_AUX4 = 775; float K_AUX5 = 776; float K_AUX6 = 777; float K_AUX7 = 778; float K_AUX8 = 779; float K_AUX9 = 780; float K_AUX10 = 781; float K_AUX11 = 782; float K_AUX12 = 783; float K_AUX13 = 784; float K_AUX14 = 785; float K_AUX15 = 786; float K_AUX16 = 787; float K_AUX17 = 788; float K_AUX18 = 789; float K_AUX19 = 790; float K_AUX20 = 791; float K_AUX21 = 792; float K_AUX22 = 793; float K_AUX23 = 794; float K_AUX24 = 795; float K_AUX25 = 796; float K_AUX26 = 797; float K_AUX27 = 798; float K_AUX28 = 799; float K_AUX29 = 800; float K_AUX30 = 801; float K_AUX31 = 802; float K_AUX32 = 803; // // mouse buttons generate virtual keys // float K_MOUSE1 = 512; float K_MOUSE2 = 513; float K_MOUSE3 = 514; float K_MWHEELUP = 515; float K_MWHEELDOWN = 516; float K_MOUSE4 = 517; float K_MOUSE5 = 518; float K_MOUSE6 = 519; float K_MOUSE7 = 520; float K_MOUSE8 = 521; float K_MOUSE9 = 522; float K_MOUSE10 = 523; float K_MOUSE11 = 524; float K_MOUSE12 = 525; float K_MOUSE13 = 526; float K_MOUSE14 = 527; float K_MOUSE15 = 528; float K_MOUSE16 = 529; darkplaces/dpdefs/progsdefs.qc0000664000175000017500000003350413067716220015756 0ustar kalevkalev/* ============================================================================== SOURCE FOR GLOBALVARS_T C STRUCTURE MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR ============================================================================== */ // // system globals // entity self; entity other; entity world; float time; float frametime; float force_retouch; // force all entities to touch triggers // next frame. this is needed because // non-moving things don't normally scan // for triggers, and when a trigger is // created (like a teleport trigger), it // needs to catch everything. // decremented each frame, so set to 2 // to guarantee everything is touched string mapname; float deathmatch; float coop; float teamplay; float serverflags; // propagated from level to level, used to // keep track of completed episodes float total_secrets; float total_monsters; float found_secrets; // number of secrets found float killed_monsters; // number of monsters killed // spawnparms are used to encode information about clients across server // level changes float parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16; // // global variables set by built in functions // vector v_forward, v_up, v_right; // set by makevectors() // set by traceline / tracebox float trace_allsolid; float trace_startsolid; float trace_fraction; vector trace_endpos; vector trace_plane_normal; float trace_plane_dist; entity trace_ent; float trace_inopen; float trace_inwater; entity msg_entity; // destination of single entity writes // // required prog functions // void() main; // only for testing void() StartFrame; void() PlayerPreThink; void() PlayerPostThink; void() ClientKill; void() ClientConnect; void() PutClientInServer; // call after setting the parm1... parms void() ClientDisconnect; void() SetNewParms; // called when a client first connects to // a server. sets parms so they can be // saved off for restarts void() SetChangeParms; // call to set parms for self so they can // be saved for a level transition //================================================ void end_sys_globals; // flag for structure dumping //================================================ /* ============================================================================== SOURCE FOR ENTVARS_T C STRUCTURE MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR ============================================================================== */ // // system fields (*** = do not set in prog code, maintained by C code) // .float modelindex; // *** model index in the precached list .vector absmin, absmax; // *** origin + mins / maxs .float ltime; // local time for entity .float movetype; .float solid; .vector origin; // *** .vector oldorigin; // *** .vector velocity; .vector angles; .vector avelocity; .vector punchangle; // temp angle adjust from damage or recoil .string classname; // spawn function .string model; .float frame; .float skin; .float effects; .vector mins, maxs; // bounding box extents reletive to origin .vector size; // maxs - mins .void() touch; .void() use; .void() think; .void() blocked; // for doors or plats, called when can't push other .float nextthink; .entity groundentity; // stats .float health; .float frags; .float weapon; // one of the IT_SHOTGUN, etc flags .string weaponmodel; .float weaponframe; .float currentammo; .float ammo_shells, ammo_nails, ammo_rockets, ammo_cells; .float items; // bit flags .float takedamage; .entity chain; .float deadflag; .vector view_ofs; // add to origin to get eye point .float button0; // fire .float button1; // use .float button2; // jump .float impulse; // weapon changes .float fixangle; .vector v_angle; // view / targeting angle for players .float idealpitch; // calculated pitch angle for lookup up slopes .string netname; .entity enemy; .float flags; .float colormap; .float team; .float max_health; // players maximum health is stored here .float teleport_time; // don't back up .float armortype; // save this fraction of incoming damage .float armorvalue; .float waterlevel; // 0 = not in, 1 = feet, 2 = wast, 3 = eyes .float watertype; // a contents value .float ideal_yaw; .float yaw_speed; .entity aiment; .entity goalentity; // a movetarget or an enemy .float spawnflags; .string target; .string targetname; // damage is accumulated through a frame. and sent as one single // message, so the super shotgun doesn't generate huge messages .float dmg_take; .float dmg_save; .entity dmg_inflictor; .entity owner; // who launched a missile .vector movedir; // mostly for doors, but also used for waterjump .string message; // trigger messages .float sounds; // either a cd track number or sound number .string noise, noise1, noise2, noise3; // contains names of wavs to play //================================================ void end_sys_fields; // flag for structure dumping //================================================ /* ============================================================================== CONSTANT DEFINITIONS ============================================================================== */ // // constants // float FALSE = 0; float TRUE = 1; // edict.flags float FL_FLY = 1; float FL_SWIM = 2; float FL_CLIENT = 8; // set for all client edicts float FL_INWATER = 16; // for enter / leave water splash float FL_MONSTER = 32; float FL_GODMODE = 64; // player cheat float FL_NOTARGET = 128; // player cheat float FL_ITEM = 256; // extra wide size for bonus items float FL_ONGROUND = 512; // standing on something float FL_PARTIALGROUND = 1024; // not all corners are valid float FL_WATERJUMP = 2048; // player jumping out of water float FL_JUMPRELEASED = 4096; // for jump debouncing // edict.movetype values float MOVETYPE_NONE = 0; // never moves //float MOVETYPE_ANGLENOCLIP = 1; //float MOVETYPE_ANGLECLIP = 2; float MOVETYPE_WALK = 3; // players only float MOVETYPE_STEP = 4; // discrete, not real time unless fall float MOVETYPE_FLY = 5; float MOVETYPE_TOSS = 6; // gravity float MOVETYPE_PUSH = 7; // no clip to world, push and crush float MOVETYPE_NOCLIP = 8; float MOVETYPE_FLYMISSILE = 9; // fly with extra size against monsters float MOVETYPE_BOUNCE = 10; float MOVETYPE_BOUNCEMISSILE = 11; // bounce with extra size // edict.solid values float SOLID_NOT = 0; // no interaction with other objects float SOLID_TRIGGER = 1; // touch on edge, but not blocking float SOLID_BBOX = 2; // touch on edge, block float SOLID_SLIDEBOX = 3; // touch on edge, but not an onground float SOLID_BSP = 4; // bsp clip, touch on edge, block // range values float RANGE_MELEE = 0; float RANGE_NEAR = 1; float RANGE_MID = 2; float RANGE_FAR = 3; // deadflag values float DEAD_NO = 0; float DEAD_DYING = 1; float DEAD_DEAD = 2; float DEAD_RESPAWNABLE = 3; float DEAD_RESPAWNING = 4; // dead, waiting for buttons to be released // takedamage values float DAMAGE_NO = 0; float DAMAGE_YES = 1; float DAMAGE_AIM = 2; // items float IT_AXE = 4096; float IT_SHOTGUN = 1; float IT_SUPER_SHOTGUN = 2; float IT_NAILGUN = 4; float IT_SUPER_NAILGUN = 8; float IT_GRENADE_LAUNCHER = 16; float IT_ROCKET_LAUNCHER = 32; float IT_LIGHTNING = 64; float IT_EXTRA_WEAPON = 128; float IT_SHELLS = 256; float IT_NAILS = 512; float IT_ROCKETS = 1024; float IT_CELLS = 2048; float IT_ARMOR1 = 8192; float IT_ARMOR2 = 16384; float IT_ARMOR3 = 32768; float IT_SUPERHEALTH = 65536; float IT_KEY1 = 131072; float IT_KEY2 = 262144; float IT_INVISIBILITY = 524288; float IT_INVULNERABILITY = 1048576; float IT_SUIT = 2097152; float IT_QUAD = 4194304; // point content values float CONTENT_EMPTY = -1; float CONTENT_SOLID = -2; float CONTENT_WATER = -3; float CONTENT_SLIME = -4; float CONTENT_LAVA = -5; float CONTENT_SKY = -6; float STATE_TOP = 0; float STATE_BOTTOM = 1; float STATE_UP = 2; float STATE_DOWN = 3; vector VEC_ORIGIN = '0 0 0'; vector VEC_HULL_MIN = '-16 -16 -24'; vector VEC_HULL_MAX = '16 16 32'; vector VEC_HULL2_MIN = '-32 -32 -24'; vector VEC_HULL2_MAX = '32 32 64'; // protocol bytes float SVC_TEMPENTITY = 23; float SVC_KILLEDMONSTER = 27; float SVC_FOUNDSECRET = 28; float SVC_INTERMISSION = 30; float SVC_FINALE = 31; float SVC_CDTRACK = 32; float SVC_SELLSCREEN = 33; float TE_SPIKE = 0; float TE_SUPERSPIKE = 1; float TE_GUNSHOT = 2; float TE_EXPLOSION = 3; float TE_TAREXPLOSION = 4; float TE_LIGHTNING1 = 5; float TE_LIGHTNING2 = 6; float TE_WIZSPIKE = 7; float TE_KNIGHTSPIKE = 8; float TE_LIGHTNING3 = 9; float TE_LAVASPLASH = 10; float TE_TELEPORT = 11; // sound channels // channel 0 never willingly overrides // other channels (1-7) allways override a playing sound on that channel float CHAN_AUTO = 0; float CHAN_WEAPON = 1; float CHAN_VOICE = 2; float CHAN_ITEM = 3; float CHAN_BODY = 4; float ATTN_NONE = 0; float ATTN_NORM = 1; float ATTN_IDLE = 2; float ATTN_STATIC = 3; // update types float UPDATE_GENERAL = 0; float UPDATE_STATIC = 1; float UPDATE_BINARY = 2; float UPDATE_TEMP = 3; // entity effects float EF_BRIGHTFIELD = 1; float EF_MUZZLEFLASH = 2; float EF_BRIGHTLIGHT = 4; float EF_DIMLIGHT = 8; // messages float MSG_BROADCAST = 0; // unreliable to all float MSG_ONE = 1; // reliable to one (msg_entity) float MSG_ALL = 2; // reliable to all float MSG_INIT = 3; // write to the init string //=========================================================================== // // builtin functions // void(vector ang) makevectors = #1; // sets v_forward, etc globals void(entity e, vector o) setorigin = #2; void(entity e, string m) setmodel = #3; // set movetype and solid first void(entity e, vector min, vector max) setsize = #4; // #5 was removed void() break_to_debugger = #6; float() random = #7; // returns 0 - 1 void(entity e, float chan, string samp, float vol, float atten) sound = #8; vector(vector v) normalize = #9; void(string e, ...) error = #10; void(string e, ...) objerror = #11; float(vector v) vlen = #12; float(vector v) vectoyaw = #13; entity() spawn = #14; void(entity e) remove = #15; // sets trace_* globals // nomonsters can be: // An entity will also be ignored for testing if forent == test, // forent->owner == test, or test->owner == forent // a forent of world is ignored void(vector v1, vector v2, float nomonsters, entity forent) traceline = #16; entity() checkclient = #17; // returns a client to look for entity(entity start, .string fld, string match) find = #18; string(string s) precache_sound = #19; string(string s) precache_model = #20; void(entity client, string s, ...)stuffcmd = #21; entity(vector org, float rad) findradius = #22; void(string s, ...) bprint = #23; void(entity client, string s, ...) sprint = #24; void(string s, ...) dprint = #25; string(float f) ftos = #26; string(vector v) vtos = #27; void() coredump = #28; // prints all edicts void() traceon = #29; // turns statment trace on void() traceoff = #30; void(entity e) eprint = #31; // prints an entire edict float(float yaw, float dist) walkmove = #32; // returns TRUE or FALSE // #33 was removed float() droptofloor= #34; // TRUE if landed on floor void(float style, string value) lightstyle = #35; float(float v) rint = #36; // round to nearest int float(float v) floor = #37; // largest integer <= v float(float v) ceil = #38; // smallest integer >= v // #39 was removed float(entity e) checkbottom = #40; // true if self is on ground float(vector v) pointcontents = #41; // returns a CONTENT_* // #42 was removed float(float f) fabs = #43; vector(entity e, float speed) aim = #44; // returns the shooting vector float(string s) cvar = #45; // return cvar.value void(string s, ...) localcmd = #46; // put string into local que entity(entity e) nextent = #47; // for looping through all ents void(vector o, vector d, float color, float count) particle = #48;// start a particle effect void() ChangeYaw = #49; // turn towards self.ideal_yaw // at self.yaw_speed // #50 was removed vector(vector v) vectoangles = #51; // // direct client message generation // void(float to, float f) WriteByte = #52; void(float to, float f) WriteChar = #53; void(float to, float f) WriteShort = #54; void(float to, float f) WriteLong = #55; void(float to, float f) WriteCoord = #56; void(float to, float f) WriteAngle = #57; void(float to, string s, ...) WriteString = #58; void(float to, entity s) WriteEntity = #59; // // broadcast client message generation // // void(float f) bWriteByte = #59; // void(float f) bWriteChar = #60; // void(float f) bWriteShort = #61; // void(float f) bWriteLong = #62; // void(float f) bWriteCoord = #63; // void(float f) bWriteAngle = #64; // void(string s) bWriteString = #65; // void(entity e) bWriteEntity = #66; void(float step) movetogoal = #67; string(string s) precache_file = #68; // no effect except for -copy void(entity e) makestatic = #69; void(string s) changelevel = #70; //#71 was removed void(string var, string val) cvar_set = #72; // sets cvar.value void(entity client, string s, ...) centerprint = #73; // sprint, but in middle void(vector pos, string samp, float vol, float atten) ambientsound = #74; string(string s) precache_model2 = #75; // registered version only string(string s) precache_sound2 = #76; // registered version only string(string s) precache_file2 = #77; // registered version only void(entity e) setspawnparms = #78; // set parm1... to the // values at level start // for coop respawn //============================================================================ darkplaces/dpdefs/source_compare.txt0000664000175000017500000004741513067716220017212 0ustar kalevkalev// progs: Builtin #487 declared but not defined // progs: Builtin #488 declared but not defined // progs: Builtin #489 declared but not defined // progs: Builtin #490 declared but not defined // progs: Builtin #491 declared but not defined // progs: Builtin #492 declared but not defined // progs: Builtin #493 declared but not defined // progs: Builtin #608 declared but not defined // progs: Field frame2 declared but not defined // progs: Field frame3 declared but not defined // progs: Field frame4 declared but not defined // progs: Field lerpfrac declared but not defined // progs: Field lerpfrac3 declared but not defined // progs: Field lerpfrac4 declared but not defined // progs: Field frame1time declared but not defined // progs: Field frame2time declared but not defined // progs: Field frame3time declared but not defined // progs: Field frame4time declared but not defined // menu: Global null_entity declared but not defined // progs: Global movedist declared but not defined // progs: Global gameover declared but not defined // progs: Global string_null declared but not defined // progs: Global newmis declared but not defined // progs: Global activator declared but not defined // progs: Global damage_attacker declared but not defined // progs: Global framecount declared but not defined // progs: Global skill declared but not defined // progs: Field map declared but not defined // progs: Field worldtype declared but not defined // progs: Field killtarget declared but not defined // progs: Field th_stand declared but not defined // progs: Field th_walk declared but not defined // progs: Field th_run declared but not defined // progs: Field th_missile declared but not defined // progs: Field th_melee declared but not defined // progs: Field th_pain declared but not defined // progs: Field th_die declared but not defined // progs: Field oldenemy declared but not defined // progs: Field speed declared but not defined // progs: Field lefty declared but not defined // progs: Field search_time declared but not defined // progs: Field attack_state declared but not defined // progs: Field attack_finished declared but not defined // progs: Field pain_finished declared but not defined // progs: Field invincible_finished declared but not defined // progs: Field invisible_finished declared but not defined // progs: Field super_damage_finished declared but not defined // progs: Field radsuit_finished declared but not defined // progs: Field spawnshieldtime declared but not defined // progs: Field invincible_time declared but not defined // progs: Field invincible_sound declared but not defined // progs: Field invisible_time declared but not defined // progs: Field invisible_sound declared but not defined // progs: Field super_time declared but not defined // progs: Field super_sound declared but not defined // progs: Field rad_time declared but not defined // progs: Field fly_sound declared but not defined // progs: Field show_hostile declared but not defined // progs: Field jump_flag declared but not defined // progs: Field swim_flag declared but not defined // progs: Field air_finished declared but not defined // progs: Field bubble_count declared but not defined // progs: Field deathtype declared but not defined // progs: Field mdl declared but not defined // progs: Field mangle declared but not defined // progs: Field t_length declared but not defined // progs: Field t_width declared but not defined // progs: Field dest declared but not defined // progs: Field dest1 declared but not defined // progs: Field dest2 declared but not defined // progs: Field wait declared but not defined // progs: Field delay declared but not defined // progs: Field trigger_field declared but not defined // progs: Field noise4 declared but not defined // progs: Field pausetime declared but not defined // progs: Field movetarget declared but not defined // progs: Field aflag declared but not defined // progs: Field dmg declared but not defined // progs: Field cnt declared but not defined // progs: Field think1 declared but not defined // progs: Field count declared but not defined // progs: Field lip declared but not defined // progs: Field state declared but not defined // progs: Field pos1 declared but not defined // progs: Field pos2 declared but not defined // progs: Field height declared but not defined // progs: Field waitmin declared but not defined // progs: Field waitmax declared but not defined // progs: Field cantrigger declared but not defined // progs: Global deathstring1 declared but not defined // progs: Global deathstring2 declared but not defined // progs: Global deathstring3 declared but not defined // progs: Global deathstring4 declared but not defined // progs: Global game declared but not defined // progs: Global darkmode declared but not defined // progs: Field bodyhealth declared but not defined // progs: Field iscorpse declared but not defined // progs: Field dest3 declared but not defined // progs: Field dest4 declared but not defined // progs: Field flame declared but not defined // progs: Field doobits declared but not defined // progs: Field rotate declared but not defined // progs: Field group declared but not defined // progs: Field keys_silver declared but not defined // progs: Field keys_gold declared but not defined // progs: Field havocattack declared but not defined // progs: Field havocpickup declared but not defined // progs: Field pickupevalfunc declared but not defined // progs: Field shoulddodge declared but not defined // progs: Field dangerrating declared but not defined // progs: Field bleedfunc declared but not defined // progs: Field count1 declared but not defined // progs: Field count2 declared but not defined // progs: Field count3 declared but not defined // progs: Field count4 declared but not defined // progs: Field count5 declared but not defined // progs: Field count6 declared but not defined // progs: Field cnt1 declared but not defined // progs: Field cnt2 declared but not defined // progs: Field obitfunc1 declared but not defined // progs: Field realowner declared but not defined // progs: Global maxclients declared but not defined // progs: Global numdecors declared but not defined // progs: Global maxdecors declared but not defined // progs: Field createdtime declared but not defined // progs: Field th_gib declared but not defined // progs: Field resist_bullet declared but not defined // progs: Field resist_explosive declared but not defined // progs: Field resist_energy declared but not defined // progs: Field resist_fire declared but not defined // progs: Field resist_ice declared but not defined // progs: Field resist_axe declared but not defined // progs: Field deathmsg declared but not defined // progs: Field regenthink declared but not defined // progs: Field isdecor declared but not defined // progs: Field radiusdamage_amount declared but not defined // progs: Field radiusdamage_force declared but not defined // progs: Field radiusdamage_hit declared but not defined // progs: Field radiusdamage_lasthit declared but not defined // progs: Field radiusdamage_ownerdamagescale declared but not defined // progs: Global raddamage_lasthit declared but not defined // progs: Field frozen declared but not defined // progs: Field thawtime declared but not defined // progs: Field thawedeffects declared but not defined // progs: Field thawedtouch declared but not defined // progs: Field thawedmovetype declared but not defined // progs: Field thawedthink declared but not defined // progs: Field thawedthinkdelay declared but not defined // progs: Field knockedloosefunc declared but not defined // progs: Field forcescale declared but not defined // progs: Field bleedratio declared but not defined // progs: Field damagemodifier declared but not defined // progs: Field healthregen declared but not defined // progs: Field healthlostthisframe declared but not defined // progs: Global monsterdamagescale declared but not defined // progs: Global monsterresistancescale declared but not defined // progs: Global playerdamagescale declared but not defined /****************************************** * csprogs ******************************************/ float VM_CL_checkpvs(...) = #240; // DP_UNKNOWN float skel_create(...) = #263; // FTE_CSQC_SKELETONOBJECTS float skel_build(...) = #264; // FTE_CSQC_SKELETONOBJECTS float skel_get_numbones(...) = #265; // FTE_CSQC_SKELETONOBJECTS float skel_get_bonename(...) = #266; // FTE_CSQC_SKELETONOBJECTS float skel_get_boneparent(...) = #267; // FTE_CSQC_SKELETONOBJECTS float skel_find_bone(...) = #268; // FTE_CSQC_SKELETONOBJECTS float skel_get_bonerel(...) = #269; // FTE_CSQC_SKELETONOBJECTS float skel_get_boneabs(...) = #270; // FTE_CSQC_SKELETONOBJECTS float skel_set_bone(...) = #271; // FTE_CSQC_SKELETONOBJECTS float skel_mul_bone(...) = #272; // FTE_CSQC_SKELETONOBJECTS float skel_mul_bones(...) = #273; // FTE_CSQC_SKELETONOBJECTS float skel_copybones(...) = #274; // FTE_CSQC_SKELETONOBJECTS float skel_delete(...) = #275; // FTE_CSQC_SKELETONOBJECTS float frameforname(...) = #276; // FTE_CSQC_SKELETONOBJECTS float frameduration(...) = #277; // FTE_CSQC_SKELETONOBJECTS float VM_drawsubpic(...) = #328; // DP_UNKNOWN float VM_drawrotpic(...) = #329; // DP_UNKNOWN float VM_CL_videoplaying(...) = #355; // DP_UNKNOWN float crc16(...) = #494; // DP_QC_CRC16 float cvar_type(...) = #495; // DP_QC_CVAR_TYPE float numentityfields(...) = #496; // QP_QC_ENTITYDATA float entityfieldname(...) = #497; // DP_QC_ENTITYDATA float entityfieldtype(...) = #498; // DP_QC_ENTITYDATA float getentityfieldstring(...) = #499; // DP_QC_ENTITYDATA float putentityfieldstring(...) = #500; // DP_QC_ENTITYDATA float ReadPicture(...) = #501; // DP_UNKNOWN float boxparticles(...) = #502; // DP_CSQC_BOXPARTICLES float whichpack(...) = #503; // DP_UNKNOWN float uri_escape(...) = #510; // DP_UNKNOWN float uri_unescape(...) = #511; // DP_UNKNOWN float num_for_edict(...) = #512; // DP_QC_NUM_FOR_EDICT float tokenize_console(...) = #514; // DP_QC_TOKENIZE_CONSOLE float argv_start_index(...) = #515; // DP_QC_TOKENIZE_CONSOLE float argv_end_index(...) = #516; // DP_QC_TOKENIZE_CONSOLE float buf_cvarlist(...) = #517; // DP_QC_STRINGBUFFERS_CVARLIST float cvar_description(...) = #518; // DP_QC_CVAR_DESCRIPTION float gettime(...) = #519; // DP_QC_GETTIME float keynumtostring(...) = #520; // DP_UNKNOWN float findkeysforcommand(...) = #521; // DP_UNKNOWN float VM_loadfromdata(...) = #529; // DP_UNKNOWN float VM_loadfromfile(...) = #530; // DP_UNKNOWN float VM_log(...) = #532; // DP_UNKNOWN float getsoundtime(...) = #533; // DP_SND_GETSOUNDTIME float soundlength(...) = #534; // DP_SND_GETSOUNDTIME float physics_enable(...) = #540; // DP_PHYSICS_ODE float physics_addforce(...) = #541; // DP_PHYSICS_ODE float physics_addtorque(...) = #542; // DP_PHYSICS_ODE float VM_callfunction(...) = #605; // DP_UNKNOWN float VM_writetofile(...) = #606; // DP_UNKNOWN float VM_isfunction(...) = #607; // DP_UNKNOWN float VM_parseentitydata(...) = #613; // DP_UNKNOWN float getextresponse(...) = #624; // DP_UNKNOWN float sprintf(...) = #627; // DP_UNKNOWN float getsurfacenumpoints(...) = #628; // DP_QC_GETSURFACETRIANGLE float getsurfacepoint(...) = #629; // DP_QC_GETSURFACETRIANGLE float VM_CL_RotateMoves(...) = #638; // DP_UNKNOWN float CSQC_ConsoleCommand; // DP_UNKNOWN float CSQC_Init; // DP_UNKNOWN float CSQC_InputEvent; // DP_UNKNOWN float CSQC_Shutdown; // DP_UNKNOWN float CSQC_UpdateView; // DP_UNKNOWN float coop; // DP_UNKNOWN float deathmatch; // DP_UNKNOWN float dmg_origin; // DP_UNKNOWN float dmg_save; // DP_UNKNOWN float dmg_take; // DP_UNKNOWN float drawfontscale; // DP_UNKNOWN float gettaginfo_forward; // DP_UNKNOWN float gettaginfo_name; // DP_UNKNOWN float gettaginfo_offset; // DP_UNKNOWN float gettaginfo_parent; // DP_UNKNOWN float gettaginfo_right; // DP_UNKNOWN float gettaginfo_up; // DP_UNKNOWN float particles_alphamax; // DP_UNKNOWN float particles_alphamin; // DP_UNKNOWN float particles_colormax; // DP_UNKNOWN float particles_colormin; // DP_UNKNOWN float sb_showscores; // DP_UNKNOWN float serverdeltatime; // DP_UNKNOWN float serverprevtime; // DP_UNKNOWN float servertime; // DP_UNKNOWN float trace_dphitcontents; // DP_UNKNOWN float trace_dphitq3surfaceflags; // DP_UNKNOWN float trace_dphittexturename; // DP_UNKNOWN float trace_dpstartcontents; // DP_UNKNOWN float trace_networkentity; // DP_UNKNOWN .float aiment; // DP_UNKNOWN .float alpha; // DP_UNKNOWN .float camera_transform; // DP_UNKNOWN .float colormod; // DP_UNKNOWN .float dimension_hit; // DP_UNKNOWN .float dimension_solid; // DP_UNKNOWN .float dphitcontentsmask; // DP_UNKNOWN .float fatness; // DP_UNKNOWN .float forceshader; // DP_UNKNOWN .float frame1time; // DP_UNKNOWN .float frame2; // DP_UNKNOWN .float frame2time; // DP_UNKNOWN .float frame3; // DP_UNKNOWN .float frame3time; // DP_UNKNOWN .float frame4; // DP_UNKNOWN .float frame4time; // DP_UNKNOWN .float glowmod; // DP_UNKNOWN .float groundentity; // DP_UNKNOWN .float hull; // DP_UNKNOWN .float ideal_yaw; // DP_UNKNOWN .float idealpitch; // DP_UNKNOWN .float jointtype; // DP_UNKNOWN .float lerpfrac; // DP_UNKNOWN .float lerpfrac3; // DP_UNKNOWN .float lerpfrac4; // DP_UNKNOWN .float mass; // DP_UNKNOWN .float message; // DP_UNKNOWN .float movedir; // DP_UNKNOWN .float pitch_speed; // DP_UNKNOWN .float renderflags; // DP_UNKNOWN .float scale; // DP_UNKNOWN .float shadertime; // DP_UNKNOWN .float skeletonindex; // FTE_CSQC_SKELETONOBJECTS .float tag_entity; // DP_UNKNOWN .float tag_index; // DP_UNKNOWN .float userwavefunc_param0; // DP_UNKNOWN .float userwavefunc_param1; // DP_UNKNOWN .float userwavefunc_param2; // DP_UNKNOWN .float userwavefunc_param3; // DP_UNKNOWN .float yaw_speed; // DP_UNKNOWN /****************************************** * menu ******************************************/ float VM_itof(...) = #79; // DP_UNKNOWN float VM_ftoe(...) = #80; // DP_UNKNOWN float isString(...) = #81; // DP_UNKNOWN float VM_altstr_count(...) = #82; // DP_UNKNOWN float VM_altstr_prepare(...) = #83; // DP_UNKNOWN float VM_altstr_get(...) = #84; // DP_UNKNOWN float VM_altstr_set(...) = #85; // DP_UNKNOWN float VM_altstr_ins(...) = #86; // DP_UNKNOWN float VM_findflags(...) = #87; // DP_UNKNOWN float VM_findchainflags(...) = #88; // DP_UNKNOWN float VM_cvar_defstring(...) = #89; // DP_UNKNOWN float strstrofs(...) = #221; // FTE_STRINGS float str2chr(...) = #222; // FTE_STRINGS float chr2str(...) = #223; // FTE_STRINGS float strconv(...) = #224; // FTE_STRINGS float strpad(...) = #225; // FTE_STRINGS float infoadd(...) = #226; // FTE_STRINGS float infoget(...) = #227; // FTE_STRINGS float strncmp(...) = #228; // FTE_STRINGS float strcasecmp(...) = #229; // FTE_STRINGS float strncasecmp(...) = #230; // FTE_STRINGS float keynumtostring(...) = #340; // DP_UNKNOWN float VM_CL_isdemo(...) = #349; // DP_UNKNOWN float wasfreed(...) = #353; // DP_UNKNOWN float VM_CL_videoplaying(...) = #355; // DP_UNKNOWN float loadfont(...) = #356; // DP_GFX_FONTS float loadfont(...) = #357; // DP_GFX_FONTS float buf_create(...) = #440; // DP_QC_STRINGBUFFERS float buf_del(...) = #441; // DP_QC_STRINGBUFFERS float buf_getsize(...) = #442; // DP_QC_STRINGBUFFERS float buf_copy(...) = #443; // DP_QC_STRINGBUFFERS float buf_sort(...) = #444; // DP_QC_STRINGBUFFERS float buf_implode(...) = #445; // DP_QC_STRINGBUFFERS float bufstr_get(...) = #446; // DP_QC_STRINGBUFFERS float bufstr_set(...) = #447; // DP_QC_STRINGBUFFERS float bufstr_add(...) = #448; // DP_QC_STRINGBUFFERS float bufstr_free(...) = #449; // DP_QC_STRINGBUFFERS float VM_cin_open(...) = #461; // DP_UNKNOWN float VM_cin_close(...) = #462; // DP_UNKNOWN float VM_cin_setstate(...) = #463; // DP_UNKNOWN float VM_cin_getstate(...) = #464; // DP_UNKNOWN float VM_cin_restart(...) = #465; // DP_UNKNOWN float VM_drawline(...) = #466; // DP_UNKNOWN float VM_stringwidth(...) = #468; // DP_UNKNOWN float VM_drawsubpic(...) = #469; // DP_UNKNOWN float VM_drawrotpic(...) = #470; // DP_UNKNOWN float VM_asin(...) = #471; // DP_QC_ASINACOSATANATAN2TAN float VM_acos(...) = #472; // DP_QC_ASINACOSATANATAN2TAN float VM_atan(...) = #473; // DP_QC_ASINACOSATANATAN2TAN float VM_atan2(...) = #474; // DP_QC_ASINACOSATANATAN2TAN float VM_tan(...) = #475; // DP_QC_ASINACOSATANATAN2TAN float float(...) = #476; // DP_QC_STRINGCOLORFUNCTIONS float string(...) = #477; // DP_QC_STRINGCOLORFUNCTIONS float string(...) = #478; // DP_QC_STRFTIME float tokenizebyseparator(...) = #479; // DP_QC_TOKENIZEBYSEPARATOR float VM_strtolower(...) = #480; // DP_UNKNOWN float VM_strtoupper(...) = #481; // DP_UNKNOWN float strreplace(...) = #484; // DP_QC_STRREPLACE float strireplace(...) = #485; // DP_QC_STRREPLACE float gecko_create(...) = #487; // DP_UNKNOWN float gecko_destroy(...) = #488; // DP_UNKNOWN float gecko_navigate(...) = #489; // DP_UNKNOWN float gecko_keyevent(...) = #490; // DP_UNKNOWN float gecko_mousemove(...) = #491; // DP_UNKNOWN float gecko_resize(...) = #492; // DP_UNKNOWN float gecko_get_texture_extent(...) = #493; // DP_UNKNOWN float crc16(...) = #494; // DP_QC_CRC16 float cvar_type(...) = #495; // DP_QC_CVAR_TYPE float whichpack(...) = #503; // DP_UNKNOWN float uri_escape(...) = #510; // DP_UNKNOWN float uri_unescape(...) = #511; // DP_UNKNOWN float num_for_edict(...) = #512; // DP_QC_NUM_FOR_EDICT float tokenize_console(...) = #514; // DP_QC_TOKENIZE_CONSOLE float argv_start_index(...) = #515; // DP_QC_TOKENIZE_CONSOLE float argv_end_index(...) = #516; // DP_QC_TOKENIZE_CONSOLE float buf_cvarlist(...) = #517; // DP_QC_STRINGBUFFERS_CVARLIST float cvar_description(...) = #518; // DP_QC_CVAR_DESCRIPTION float VM_log(...) = #532; // DP_UNKNOWN float getsoundtime(...) = #533; // DP_SND_GETSOUNDTIME float soundlength(...) = #534; // DP_SND_GETSOUNDTIME float parseentitydata(...) = #613; // DP_UNKNOWN float stringtokeynum(...) = #614; // DP_UNKNOWN float resethostcachemasks(...) = #615; // DP_UNKNOWN float sethostcachemaskstring(...) = #616; // DP_UNKNOWN float sethostcachemasknumber(...) = #617; // DP_UNKNOWN float resorthostcache(...) = #618; // DP_UNKNOWN float sethostcachesort(...) = #619; // DP_UNKNOWN float refreshhostcache(...) = #620; // DP_UNKNOWN float gethostcachenumber(...) = #621; // DP_UNKNOWN float gethostcacheindexforkey(...) = #622; // DP_UNKNOWN float addwantedhostcachekey(...) = #623; // DP_UNKNOWN float getextresponse(...) = #624; // DP_UNKNOWN float netaddress_resolve(...) = #625; // DP_UNKNOWN float getgamedirinfo(...) = #626; // DP_UNKNOWN float sprintf(...) = #627; // DP_UNKNOWN float drawfont; // DP_UNKNOWN float drawfontscale; // DP_UNKNOWN .float angles; // DP_UNKNOWN .float chain; // DP_UNKNOWN .float classname; // DP_UNKNOWN .float frame; // OP_STATE .float nextthink; // OP_STATE .float think; // OP_STATE /****************************************** * progs ******************************************/ float setmodelindex(...) = #333; // EXT_CSQC float modelnameforindex(...) = #334; // EXT_CSQC float isserver(...) = #350; // EXT_CSQC float serverkey(...) = #354; // EXT_CSQC float VM_parseentitydata(...) = #613; // DP_UNKNOWN float ClientConnect; // DP_UNKNOWN float ClientDisconnect; // DP_UNKNOWN float ClientKill; // DP_UNKNOWN float PlayerPostThink; // DP_UNKNOWN float PlayerPreThink; // DP_UNKNOWN float PutClientInServer; // DP_UNKNOWN float SV_InitCmd; // DP_UNKNOWN float SetChangeParms; // DP_UNKNOWN float SetNewParms; // DP_UNKNOWN float StartFrame; // DP_UNKNOWN float main; // DP_UNKNOWN float require_spawnfunc_prefix; // DP_UNKNOWN .float SendEntity; // DP_UNKNOWN .float SendFlags; // DP_UNKNOWN .float Version; // DP_UNKNOWN .float ammo_cells1; // DP_UNKNOWN .float ammo_lava_nails; // DP_UNKNOWN .float ammo_multi_rockets; // DP_UNKNOWN .float ammo_nails1; // DP_UNKNOWN .float ammo_plasma; // DP_UNKNOWN .float ammo_rockets1; // DP_UNKNOWN .float ammo_shells1; // DP_UNKNOWN .float dimension_hit; // DP_UNKNOWN .float dimension_solid; // DP_UNKNOWN .float fatness; // DP_UNKNOWN .float fullbright; // DP_UNKNOWN .float hull; // DP_UNKNOWN .float items2; // DP_UNKNOWN .float pmodel; // DP_UNKNOWN .float renderamt; // DP_UNKNOWN .float rendermode; // DP_UNKNOWN .float sendcomplexanimation; // DP_UNKNOWN darkplaces/dpdefs/csprogsdefs.qc0000664000175000017500000017570513067716220016316 0ustar kalevkalev/* ============================================================================== SOURCE FOR GLOBALVARS_T C STRUCTURE MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR ============================================================================== */ // // system globals // entity self; entity other; entity world; float time; float frametime; float player_localentnum; //the entnum float player_localnum; //the playernum float maxclients; //a constant filled in by the engine. gah, portability eh? float clientcommandframe; //player movement float servercommandframe; //clientframe echoed off the server string mapname; // // global variables set by built in functions // vector v_forward, v_up, v_right; // set by makevectors() // set by traceline / tracebox float trace_allsolid; float trace_startsolid; float trace_fraction; vector trace_endpos; vector trace_plane_normal; float trace_plane_dist; entity trace_ent; float trace_inopen; float trace_inwater; // // required prog functions // void() CSQC_Init; void() CSQC_Shutdown; float(float f, float t, float n) CSQC_InputEvent; void(float w, float h) CSQC_UpdateView; float(string s) CSQC_ConsoleCommand; //these fields are read and set by the default player physics vector pmove_org; vector pmove_vel; vector pmove_mins; vector pmove_maxs; //retrieved from the current movement commands (read by player physics) float input_timelength; vector input_angles; vector input_movevalues; //forwards, right, up. float input_buttons; //attack, use, jump (default physics only uses jump) float movevar_gravity; float movevar_stopspeed; float movevar_maxspeed; float movevar_spectatormaxspeed; //used by NOCLIP movetypes. float movevar_accelerate; float movevar_airaccelerate; float movevar_wateraccelerate; float movevar_friction; float movevar_waterfriction; float movevar_entgravity; //the local player's gravity field. Is a multiple (1 is the normal value) //================================================ void end_sys_globals; // flag for structure dumping //================================================ /* ============================================================================== SOURCE FOR ENTVARS_T C STRUCTURE MUST NOT BE MODIFIED, OR CRC ERRORS WILL APPEAR ============================================================================== */ // // system fields (*** = do not set in prog code, maintained by C code) // .float modelindex; // *** model index in the precached list .vector absmin, absmax; // *** origin + mins / maxs .float entnum; // *** the ent number as on the server .float drawmask; .void() predraw; .float movetype; .float solid; .vector origin; // *** .vector oldorigin; // *** .vector velocity; .vector angles; .vector avelocity; .string classname; // spawn function .string model; .float frame; .float skin; .float effects; .vector mins, maxs; // bounding box extents reletive to origin .vector size; // maxs - mins .void() touch; .void() use; .void() think; .void() blocked; // for doors or plats, called when can't push other .float nextthink; .entity chain; .string netname; .entity enemy; .float flags; .float colormap; .entity owner; // who launched a missile //================================================ void end_sys_fields; // flag for structure dumping //================================================ /* ============================================================================== OPTIONAL FIELDS AND GLOBALS ============================================================================== */ // Additional OPTIONAL Fields and Globals float intermission; // indicates intermission state (0 = normal, 1 = scores, 2 = finale text) vector view_angles; // same as input_angles vector view_punchangle; // from server vector view_punchvector; // from server /* ============================================================================== CONSTANT DEFINITIONS ============================================================================== */ const float MASK_ENGINE = 1; const float MASK_ENGINEVIEWMODELS = 2; const float MASK_NORMAL = 4; const float RF_VIEWMODEL = 1; const float RF_EXTERNALMODEL = 2; const float RF_DEPTHHACK = 4; const float RF_ADDITIVE = 8; const float RF_USEAXIS = 16; const float VF_MIN = 1; //(vector) const float VF_MIN_X = 2; //(float) const float VF_MIN_Y = 3; //(float) const float VF_SIZE = 4; //(vector) (viewport size) const float VF_SIZE_Y = 5; //(float) const float VF_SIZE_X = 6; //(float) const float VF_VIEWPORT = 7; //(vector, vector) const float VF_FOV = 8; //(vector) const float VF_FOVX = 9; //(float) const float VF_FOVY = 10; //(float) const float VF_ORIGIN = 11; //(vector) const float VF_ORIGIN_X = 12; //(float) const float VF_ORIGIN_Y = 13; //(float) const float VF_ORIGIN_Z = 14; //(float) const float VF_ANGLES = 15; //(vector) const float VF_ANGLES_X = 16; //(float) const float VF_ANGLES_Y = 17; //(float) const float VF_ANGLES_Z = 18; //(float) const float VF_DRAWWORLD = 19; //(float) const float VF_DRAWENGINESBAR = 20; //(float) const float VF_DRAWCROSSHAIR = 21; //(float) const float VF_CL_VIEWANGLES = 33; //(vector) const float VF_CL_VIEWANGLES_X = 34; //(float) const float VF_CL_VIEWANGLES_Y = 35; //(float) const float VF_CL_VIEWANGLES_Z = 36; //(float) const float VF_PERSPECTIVE = 200; const float STAT_HEALTH = 0; const float STAT_WEAPONMODEL = 2; const float STAT_AMMO = 3; const float STAT_ARMOR = 4; const float STAT_WEAPONFRAME = 5; const float STAT_SHELLS = 6; const float STAT_NAILS = 7; const float STAT_ROCKETS = 8; const float STAT_CELLS = 9; const float STAT_ACTIVEWEAPON = 10; const float STAT_TOTALSECRETS = 11; const float STAT_TOTALMONSTERS = 12; const float STAT_SECRETS = 13; const float STAT_MONSTERS = 14; const float STAT_ITEMS = 15; const float STAT_VIEWHEIGHT = 16; // Quake Sound Constants const float CHAN_AUTO = 0; const float CHAN_WEAPON = 1; const float CHAN_VOICE = 2; const float CHAN_ITEM = 3; const float CHAN_BODY = 4; const float ATTN_NONE = 0; const float ATTN_NORM = 1; const float ATTN_IDLE = 2; const float ATTN_STATIC = 3; // Frik File Constants const float FILE_READ = 0; const float FILE_APPEND = 1; const float FILE_WRITE = 2; // Quake Point Contents const float CONTENT_EMPTY = -1; const float CONTENT_SOLID = -2; const float CONTENT_WATER = -3; const float CONTENT_SLIME = -4; const float CONTENT_LAVA = -5; const float CONTENT_SKY = -6; // Quake Solid Constants const float SOLID_NOT = 0; const float SOLID_TRIGGER = 1; const float SOLID_BBOX = 2; const float SOLID_SLIDEBOX = 3; const float SOLID_BSP = 4; const float SOLID_CORPSE = 5; // Quake Move Constants const float MOVE_NORMAL = 0; const float MOVE_NOMONSTERS = 1; const float MOVE_MISSILE = 2; // Boolean Constants const float true = 1; const float false = 0; const float TRUE = 1; const float FALSE = 0; const float EXTRA_LOW = -99999999; const float EXTRA_HIGH = 99999999; const vector VEC_1 = '1 1 1'; const vector VEC_0 = '0 0 0'; const vector VEC_M1 = '-1 -1 -1'; const float M_PI = 3.14159265358979323846; vector VEC_HULL_MIN = '-16 -16 -24'; vector VEC_HULL_MAX = '16 16 32'; // Quake Temporary Entity Constants const float TE_SPIKE = 0; const float TE_SUPERSPIKE = 1; const float TE_GUNSHOT = 2; const float TE_EXPLOSION = 3; const float TE_TAREXPLOSION = 4; const float TE_LIGHTNING1 = 5; const float TE_LIGHTNING2 = 6; const float TE_WIZSPIKE = 7; const float TE_KNIGHTSPIKE = 8; const float TE_LIGHTNING3 = 9; const float TE_LAVASPLASH = 10; const float TE_TELEPORT = 11; const float TE_EXPLOSION2 = 12; // Darkplaces Additions const float TE_EXPLOSIONRGB = 53; const float TE_GUNSHOTQUAD = 57; const float TE_EXPLOSIONQUAD = 70; const float TE_SPIKEQUAD = 58; const float TE_SUPERSPIKEQUAD = 59; // PFlags for Dynamic Lights const float PFLAGS_NOSHADOW = 1; const float PFLAGS_CORONA = 2; const float PFLAGS_FULLDYNAMIC = 128; const float EF_ADDITIVE = 32; const float EF_BLUE = 64; const float EF_FLAME = 1024; const float EF_FULLBRIGHT = 512; const float EF_NODEPTHTEST = 8192; const float EF_NODRAW = 16; const float EF_NOSHADOW = 4096; const float EF_RED = 128; const float EF_STARDUST = 2048; const float EF_SELECTABLE = 16384; const float PFL_ONGROUND = 1; const float PFL_CROUCH = 2; const float PFL_DEAD = 4; const float PFL_GIBBED = 8; // draw flags const float DRAWFLAG_NORMAL = 0; const float DRAWFLAG_ADDITIVE = 1; const float DRAWFLAG_MODULATE = 2; const float DRAWFLAG_2XMODULATE = 3; const float DRAWFLAG_SCREEN = 4; const float DRAWFLAG_MIPMAP = 0x100; // only for R_BeginPolygon /* ============================================================================== BUILTIN DEFINITIONS EXTENSIONS ARE NOT ADDED HERE, BUT BELOW! ============================================================================== */ void(vector ang) makevectors = #1; void(entity e, vector o) setorigin = #2; void(entity e, string m) setmodel = #3; void(entity e, vector min, vector max) setsize = #4; void() break_to_debugger = #6; float() random = #7; void(entity e, float chan, string samp) sound = #8; vector(vector v) normalize = #9; void(string e) error = #10; void(string e) objerror = #11; float(vector v) vlen = #12; float(vector v) vectoyaw = #13; entity() spawn = #14; void(entity e) remove = #15; float(vector v1, vector v2, float tryents, entity ignoreentity) traceline = #16; entity(entity start, .string fld, string match) find = #18; void(string s) precache_sound = #19; void(string s) precache_model = #20; entity(vector org, float rad) findradius = #22; void(string s, ...) dprint = #25; string(float f) ftos = #26; string(vector v) vtos = #27; void() coredump = #28; void() traceon = #29; void() traceoff = #30; void(entity e) eprint = #31; // settrace optional float(float yaw, float dist, float settrace) walkmove = #32; float() droptofloor = #34; void(float style, string value) lightstyle = #35; float(float v) rint = #36; float(float v) floor = #37; float(float v) ceil = #38; float(entity e) checkbottom = #40; float(vector v) pointcontents = #41; float(float f) fabs = #43; float(string s) cvar = #45; void(string s, ...) localcmd = #46; entity(entity e) nextent = #47; void(vector o, vector d, float color, float count) particle = #48; void() ChangeYaw = #49; vector(vector v) vectoangles = #51; vector(vector v, vector w) vectoangles2 = #51; float(float f) sin = #60; float(float f) cos = #61; float(float f) sqrt = #62; void(entity ent) changepitch = #63; void(entity e, entity ignore) tracetoss = #64; string(entity ent) etos = #65; string(string s) precache_file = #68; void(entity e) makestatic = #69; void(string var, string val) cvar_set = #72; void(vector pos, string samp, float vol, float atten) ambientsound = #74; string(string s) precache_model2 = #75; string(string s) precache_sound2 = #76; string(string s) precache_file2 = #77; float(string s) stof = #81; void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox = #90; vector() randomvec = #91; vector(vector org) getlight = #92; vector(vector org, float lpflags) getlight2 = #92; vector getlight_dir; vector getlight_ambient; vector getlight_diffuse; const float LP_LIGHTMAP = 1; const float LP_RTWORLD = 2; const float LP_DYNLIGHT = 4; const float LP_COMPLETE = 7; float(string name, string value) registercvar = #93; float( float a, ... ) min = #94; float( float b, ... ) max = #95; float(float minimum, float val, float maximum) bound = #96; float(float f, float f) pow = #97; entity(entity start, .float fld, float match) findfloat = #98; float(string s) checkextension = #99; // FrikaC and Telejano range #100-#199 float(string filename, float mode) fopen = #110; void(float fhandle) fclose = #111; string(float fhandle) fgets = #112; void(float fhandle, string s) fputs = #113; float(string s) strlen = #114; string(...) strcat = #115; string(string s, float start, float length) substring = #116; vector(string) stov = #117; string(string s) strzone = #118; void(string s) strunzone = #119; // FTEQW range #200-#299 float(float number, float quantity) bitshift = #218; //float(string str, string sub[, float startpos]) strstrofs = #221; float(string str, string sub, float startpos) strstrofs = #221; float(string str, float ofs) str2chr = #222; string(float c, ...) chr2str = #223; string(float ccase, float calpha, float cnum, string s, ...) strconv = #224; string(float chars, string s, ...) strpad = #225; string(string info, string key, string value, ...) infoadd = #226; string(string info, string key) infoget = #227; float(string s1, string s2) strcmp = #228; float(string s1, string s2, float len) strncmp = #228; float(string s1, string s2) strcasecmp = #229; float(string s1, string s2, float len) strncasecmp = #230; // CSQC range #300-#399 void() clearscene = #300; void(float mask) addentities = #301; void(entity ent) addentity = #302; float(float property, ...) setproperty = #303; float(float property) getproperty = #309; vector(float property) getpropertyvec = #309; void() renderscene = #304; void(vector org, float radius, vector lightcolours) adddynamiclight = #305; void(vector org, float radius, vector lightcolours, float style, string cubemapname, float pflags) adddynamiclight2 = #305; //void(string texturename, float flag[, float is2d, float lines]) R_BeginPolygon = #306; void(string texturename, float flag, ...) R_BeginPolygon = #306; void(vector org, vector texcoords, vector rgb, float alpha) R_PolygonVertex = #307; void() R_EndPolygon = #308; vector (vector v) cs_unproject = #310; vector (vector v) cs_project = #311; void(float width, vector pos1, vector pos2, float flag) drawline = #315; float(string name) iscachedpic = #316; string(string name, ...) precache_pic = #317; string(string name) precache_cubemap = #317; vector(string picname) draw_getimagesize = #318; void(string name) freepic = #319; float(vector position, float character, vector scale, vector rgb, float alpha, float flag) drawcharacter = #320; float(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawstring = #321; float(vector position, string pic, vector size, vector rgb, float alpha, float flag) drawpic = #322; float(vector position, vector size, vector rgb, float alpha, float flag) drawfill = #323; void(float x, float y, float width, float height) drawsetcliparea = #324; void(void) drawresetcliparea = #325; float(vector position, string text, vector scale, float alpha, float flag) drawcolorcodedstring = #326; vector(vector position, string text, vector scale, vector rgb, float alpha, float flag) drawcolorcodedstring2 = #326; float(float stnum) getstatf = #330; float(float stnum, ...) getstati = #331; // can optionally take first bit and count string(float firststnum) getstats = #332; void(entity e, float mdlindex) setmodelindex = #333; string(float mdlindex) modelnameforindex = #334; float(string effectname) particleeffectnum = #335; void(entity ent, float effectnum, vector start, vector end) trailparticles = #336; //void(float effectnum, vector origin [, vector dir, float count]) pointparticles = #337; void(float effectnum, vector origin , vector dir, float count) pointparticles = #337; void(string s, ...) centerprint = #338; void(string s, ...) print = #339; string(float keynum) keynumtostring = #340; float(string keyname) stringtokeynum = #341; string(float keynum) getkeybind = #342; void(float usecursor) setcursormode = #343; vector() getmousepos = #344; float(float framenum) getinputstate = #345; void(float sens) setsensitivityscale = #346; void(...) runstandardplayerphysics = #347; // this may or may not take a player ent string(float playernum, string keyname) getplayerkeyvalue = #348; float() isdemo = #349; float() isserver = #350; void(vector origin, vector forward, vector right, vector up) SetListener = #351; void(string cmdname) registercommand = #352; float(entity ent) wasfreed = #353; string(string key) serverkey = #354; // Use proper case; refer to the id1 Write* functions! float() ReadByte = #360; float() ReadChar = #361; float() ReadShort = #362; float() ReadLong = #363; float() ReadCoord = #364; float() ReadAngle = #365; string() ReadString = #366; float() ReadFloat = #367; // LordHavoc's range #400-#499 void(entity from, entity to) copyentity = #400; entity(.string fld, string match) findchain = #402; entity(.float fld, float match) findchainfloat = #403; void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; void(vector org, vector velocity, float howmany) te_blood = #405; void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; void(vector org, vector color) te_explosionrgb = #407; void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; void(vector org, vector vel, float howmany) te_spark = #411; void(vector org) te_gunshotquad = #412; void(vector org) te_spikequad = #413; void(vector org) te_superspikequad = #414; void(vector org) te_explosionquad = #415; void(vector org) te_smallflash = #416; void(vector org, float radius, float lifetime, vector color) te_customflash = #417; void(vector org) te_gunshot = #418; void(vector org) te_spike = #419; void(vector org) te_superspike = #420; void(vector org) te_explosion = #421; void(vector org) te_tarexplosion = #422; void(vector org) te_wizspike = #423; void(vector org) te_knightspike = #424; void(vector org) te_lavasplash = #425; void(vector org) te_teleport = #426; void(vector org, float colorstart, float colorlength) te_explosion2 = #427; void(entity own, vector start, vector end) te_lightning1 = #428; void(entity own, vector start, vector end) te_lightning2 = #429; void(entity own, vector start, vector end) te_lightning3 = #430; void(entity own, vector start, vector end) te_beam = #431; void(vector dir) vectorvectors = #432; void(vector org) te_plasmaburn = #433; float(entity e, float s) getsurfacenumpoints = #434; vector(entity e, float s, float n) getsurfacepoint = #435; vector(entity e, float s) getsurfacenormal = #436; string(entity e, float s) getsurfacetexture = #437; float(entity e, vector p) getsurfacenearpoint = #438; vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; float(string s) tokenize = #441; string(float n) argv = #442; void(entity e, entity tagentity, string tagname) setattachment = #443; float(string pattern, float caseinsensitive, float quiet) search_begin = #444; void(float handle) search_end = #445; float(float handle) search_getsize = #446; string(float handle, float num) search_getfilename = #447; string(string s) cvar_string = #448; entity(entity start, .float fld, float match) findflags = #449; entity(.float fld, float match) findchainflags = #450; float(entity ent, string tagname) gettagindex = #451; vector(entity ent, float tagindex) gettaginfo = #452; void(vector org, vector vel, float howmany) te_flamejet = #457; entity(float num) entitybyindex = #459; float() buf_create = #460; void(float bufhandle) buf_del = #461; float(float bufhandle) buf_getsize = #462; void(float bufhandle_from, float bufhandle_to) buf_copy = #463; void(float bufhandle, float sortpower, float backward) buf_sort = #464; string(float bufhandle, string glue) buf_implode = #465; string(float bufhandle, float string_index) bufstr_get = #466; void(float bufhandle, float string_index, string str) bufstr_set = #467; float(float bufhandle, string str, float order) bufstr_add = #468; void(float bufhandle, float string_index) bufstr_free = #469; float(float s) asin = #471; float(float c) acos = #472; float(float t) atan = #473; float(float c, float s) atan2 = #474; float(float a) tan = #475; float(string s) strippedstringlen = #476; float(string s) strlennocol = #476; // This is the correct name for the function, but not removing the decolorizedstring mapping. string(string s) decolorizedstring = #477; string(string s) strdecolorize = #477; // This is the correct name for the function, but not removing the decolorizedstring mapping. string(float uselocaltime, string format, ...) strftime = #478; string(string s) strtolower = #480; string(string s) strtoupper = #481; string(string s) cvar_defstring = #482; void(vector origin, string sample, float volume, float attenuation) pointsound = #483; string(string search, string replace, string subject) strreplace = #484; string(string search, string replace, string subject) strireplace = #485; vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; #ifdef SUPPORT_GECKO float gecko_create( string name ) = #487; void gecko_destroy( string name ) = #488; void gecko_navigate( string name, string URI ) = #489; float gecko_keyevent( string name, float key, float eventtype ) = #490; void gecko_mousemove( string name, float x, float y ) = #491; void gecko_resize( string name, float w, float h ) = #492; vector gecko_get_texture_extent( string name ) = #493; #else #endif /* ============================================================================== EXTENSION DEFINITIONS ============================================================================== */ // DP_CSQC_SPAWNPARTICLE // idea: VorteX // darkplaces implementation: VorteX // constant definitions: // particle base behavior: float PT_ALPHASTATIC = 1; float PT_STATIC = 2; float PT_SPARK = 3; float PT_BEAM = 4; float PT_RAIN = 5; float PT_RAINDECAL = 6; float PT_SNOW = 7; float PT_BUBBLE = 8; float PT_BLOOD = 9; float PT_SMOKE = 10; float PT_DECAL = 11; float PT_ENTITYPARTICLE = 12; // particle blendtypes: float PBLEND_ALPHA = 0; float PBLEND_ADD = 1; float PBLEND_INVMOD = 2; // particle orientation: float PARTICLE_BILLBOARD = 0; float PARTICLE_SPARK = 1; float PARTICLE_ORIENTED_DOUBLESIDED = 2; float PARTICLE_BEAM = 3; // global definitions: float particle_type; // one of PT_ float particle_blendmode; // one of PBLEND_ values float particle_orientation; // one of PARTICLE_ values vector particle_color1; vector particle_color2; float particle_tex; // number of chunk in particlefont float particle_size; float particle_sizeincrease; float particle_alpha; float particle_alphafade; float particle_time; float particle_gravity; float particle_bounce; float particle_airfriction; float particle_liquidfriction; float particle_originjitter; float particle_velocityjitter; float particle_qualityreduction; // enable culling of this particle when FPS is low float particle_stretch; vector particle_staincolor1; vector particle_staincolor2; float particle_staintex; float particle_stainalpha; float particle_stainsize; float particle_delayspawn; float particle_delaycollision; float particle_angle; float particle_spin; // builtin definitions: float(float max_themes) initparticlespawner = #522; // check fields/globals for integration and enable particle spawner, return 1 is succeded, otherwise returns 0 void() resetparticle = #523; // reset p_ globals to default theme #0 void(float theme) particletheme = #524; // restore p_ globals from saved theme float() particlethemesave = #525; // save p_ globals to new particletheme and return it's index void(float theme) particlethemeupdate = #525; // save p_ globals to new particletheme and return it's index void(float theme) particlethemefree = #526; // delete a particle theme float(vector org, vector vel) spawnparticle = #527; // returns 0 when failed, 1 when spawned float(vector org, vector vel, float theme) quickparticle = #527; // not reading globals, just theme, returns 0 when failed, 1 when spawned float(vector org, vector vel, float delay, float collisiondelay) delayedparticle = #528; float(vector org, vector vel, float delay, float collisiondelay, float theme) quickdelayedparticle = #528; // description: this builtin provides an easy and flexible way to spawn particles, // it is not created as replace for DP_SV_POINTPARTICLES but as an addition to it. // With this extension you can create a specific particles like rain particles, or entity particles // notes: // 1) 0 is default particle template, it could be changed // 2) color vectors could have value 0-255 of each component // restrictions: max themes could be between 4 and 2048 // warning: you should call initparticlespawner() at very beginning BEFORE all other particle spawner functions // function to query particle info // don't remove this function as it protects all particle_ globals from FTEQCC/FRIKQCC non-referenced removal optimisation void() printparticle = { // vortex: this also protects from 'non-referenced' optimisation on some compilers print("PARTICLE:\n"); print(strcat(" type: ", ftos(particle_type), "\n")); print(strcat(" blendmode: ", ftos(particle_blendmode), "\n")); print(strcat(" orientation: ", ftos(particle_orientation), "\n")); print(strcat(" color1: ", vtos(particle_color1), "\n")); print(strcat(" color2: ", vtos(particle_color2), "\n")); print(strcat(" tex: ", ftos(particle_tex), "\n")); print(strcat(" size: ", ftos(particle_size), "\n")); print(strcat(" sizeincrease: ", ftos(particle_sizeincrease), "\n")); print(strcat(" alpha: ", ftos(particle_alpha), "\n")); print(strcat(" alphafade: ", ftos(particle_alphafade), "\n")); print(strcat(" time: ", ftos(particle_time), "\n")); print(strcat(" gravity: ", ftos(particle_gravity), "\n")); print(strcat(" bounce: ", ftos(particle_bounce), "\n")); print(strcat(" airfriction: ", ftos(particle_airfriction), "\n")); print(strcat(" liquidfriction: ", ftos(particle_liquidfriction), "\n")); print(strcat(" originjitter: ", ftos(particle_originjitter), "\n")); print(strcat(" velocityjitter: ", ftos(particle_velocityjitter), "\n")); print(strcat(" qualityreduction: ", ftos(particle_qualityreduction), "\n")); print(strcat(" stretch: ", ftos(particle_stretch), "\n")); print(strcat(" staincolor1: ", vtos(particle_staincolor1), "\n")); print(strcat(" staincolor2: ", vtos(particle_staincolor2), "\n")); print(strcat(" staintex: ", ftos(particle_staintex), "\n")); print(strcat(" stainalpha: ", ftos(particle_stainalpha), "\n")); print(strcat(" stainsize: ", ftos(particle_stainsize), "\n")); print(strcat(" delayspawn: ", ftos(particle_delayspawn), "\n")); print(strcat(" delaycollision: ", ftos(particle_delaycollision), "\n")); print(strcat(" angle: ", ftos(particle_angle), "\n")); print(strcat(" spin: ", ftos(particle_spin), "\n")); } // DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET // idea: VorteX // darkplaces implementation: VorteX float RF_USETRANSPARENTOFFSET = 64; // enables transparent origin offsetting // global definitions float transparent_offset; // should be set before entity is added // description: offset a model's meshes origin used for transparent sorting. Could be used to tweak sorting bugs on very large transparent entities or hacking transparent sorting order for certain objects // example: transparent_offset = 1000000; // entity always appear on background of other transparents // note: offset is done in view forward axis // DP_CSQC_ENTITYWORLDOBJECT // idea: VorteX // darkplaces implementation: VorteX const float RF_WORLDOBJECT = 128; // description: when renderflag is set, engine will not use culling methods for this entity, e.g. it will always be drawn // useful for large outdoor objects (like asteroids on sky horizon or sky models) // DP_CSQC_ENTITYMODELLIGHT // idea: VorteX // darkplaces implementation: VorteX const float RF_MODELLIGHT = 4096; .vector modellight_ambient; .vector modellight_diffuse; .vector modellight_dir; // description: allows CSQC to override directional model lightning on entity // DP_CSQC_SETPAUSE // idea: VorteX // darkplaces implementation: VorteX // builtin definitions: void(float ispaused) setpause = #531; // description: provides ability to set pause in local games (similar to one set once console is activated) // not stopping sound/cd track, useful for inventory screens, ingame menus with input etc. // DP_CSQC_QUERYRENDERENTITY // idea: VorteX // darkplaces implementation: VorteX // constant definitions: // render entity fields: float E_ACTIVE = 0; // float 0/1 float E_ORIGIN = 1; // vector float E_FORWARD = 2; // vector float E_RIGHT = 3; // vector float E_UP = 4; // vector float E_SCALE = 5; // float float E_ORIGINANDVECTORS = 6; // returns origin, + sets v_* vectors to orientation float E_ALPHA = 7; // float float E_COLORMOD = 8; // vector float E_PANTSCOLOR = 9; // vector float E_SHIRTCOLOR = 10; // vector float E_SKIN = 11; // float float E_MINS = 12; // vector float E_MAXS = 13; // vector float E_ABSMIN = 14; // vector float E_ABSMAX = 15; // vector float E_LIGHT = 16; // vector - modellight // builtin definitions: float(float entitynum, float fldnum) getentity = #504; vector(float entitynum, float fldnum) getentityvec = #504; // description: allows to query parms from render entities, especially useful with attaching CSQC ents to // server entities networked and interpolated by engine (monsters, players), number of entity is it's SVQC number // you can send it via tempentity/CSQC entity message. Note that this builtin doesnt know about entity removing/reallocating // so it's meaning to work for short period of time, dont use it on missiles/grenades whatever will be removed next five seconds //DP_GFX_FONTS //idea: Blub\0, divVerent //darkplaces implementation: Blub\0 //console commands: // loadfont fontname fontmaps size1 size2 ... // A font can simply be gfx/tgafile (freetype fonts doent need extension), // or alternatively you can specify multiple fonts and faces // Like this: gfx/vera-sans:2,gfx/fallback:1 // to load face 2 of the font gfx/vera-sans and use face 1 // of gfx/fallback as fallback font // You can also specify a list of font sizes to load, like this: // loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32 // In many cases, 8 12 16 24 32 should be a good choice. // for slots see: //constant definitions: float drawfont; // set it before drawstring()/drawchar() calls float FONT_DEFAULT = 0; // 'default' float FONT_CONSOLE = 1; // 'console', REALLY should be fixed width (ls!) float FONT_SBAR = 2; // 'sbar', used on hud, must be fixed width float FONT_NOTIFY = 3; // 'notify', used on sprint/bprint float FONT_CHAT = 4; // 'chat' float FONT_CENTERPRINT = 5;// 'centerprint' float FONT_INFOBAR = 6; // 'infobar' float FONT_MENU = 7; // 'menu', should be fixed width float FONT_USER0 = 8; // 'user0', userdefined fonts float FONT_USER1 = 9; // 'user1', userdefined fonts float FONT_USER2 = 10; // 'user2', userdefined fonts float FONT_USER3 = 11; // 'user3', userdefined fonts float FONT_USER4 = 12; // 'user4', userdefined fonts float FONT_USER5 = 13; // 'user5', userdefined fonts float FONT_USER6 = 14; // 'user6', userdefined fonts float FONT_USER7 = 15; // 'user7' slot, userdefined fonts //builtin definitions: float findfont(string s) = #356; // find font by fontname and return it's index float loadfont(string fontname, string fontmaps, string sizes, float slot, float fix_scale, float fix_voffset) = #357; // loads font immediately so stringwidth() function can be used just after builtin call // returns a font slotnum (which is used to set drawfont to) // first 3 parms are identical to "loadfont" console command ones // slot could be one of FONT_ constants or result of findfont() or -1 to not use it // if slot is given, font will be loaded to this slotnum and fontname become new title for it // this way you can rename user* fonts to something more usable // fix_* parms let you fix badly made fonts by applying some transformations to them // fix_scale : per-character center-oriented scale (doesn't change line height at all) // fix_voffset : vertical offset for each character, it's a multiplier to character height float stringwidth(string text, float allowColorCodes, vector size) = #327; // get a width of string with given font and char size float stringwidth_menu(string text, float allowColorCodes, vector size) = #468; // in menu.dat it has different builtin # //description: engine support for custom fonts in console, hud, qc etc. // limits: // max 128 chars for font name // max 3 font fallbacks // max 8 sizes per font //DP_GFX_FONTS_FREETYPE //idea: Blub\0, divVerent //darkplaces implementation: Blub\0 //cvar definitions: // r_font_disable_freetype 0/1 : disable freetype fonts loading (uttetly disables freetype library initialization) // r_font_antialias 0/1 : antialiasing when loading font // r_font_hint 0/1/2/3 : hinting when loading font, 0 is no hinting, 1 light autohinting , 2 full autohinting, 3 full hinting // r_font_postprocess_blur X : font outline blur amount // r_font_postprocess_outline X : font outline width // r_font_postprocess_shadow_x X : font outline shadow x shift amount, applied during outlining // r_font_postprocess_shadow_y X : font outline shadow y shift amount, applied during outlining // r_font_postprocess_shadow_z X : font outline shadow z shift amount, applied during blurring //description: engine support for truetype/freetype fonts //so .AFM+.PFB/.OTF/.TTF files could be stuffed as fontmaps in loadfont() //(console command version will support them as well) //DP_CSQC_BINDMAPS //idea: daemon, motorsep //darkplaces implementation: divVerent //builtin definitions: string(float key, float bindmap) getkeybind_bindmap = #342; float(float key, string bind, float bindmap) setkeybind_bindmap = #630; vector(void) getbindmaps = #631; float(vector bm) setbindmaps = #632; string(string command, float bindmap) findkeysforcommand = #610; // float(string key) stringtokeynum = #341; // string(float keynum) keynumtostring = #340; //description: key bind setting/getting including support for switchable //bindmaps. //DP_CRYPTO //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: (CSQC) float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; //description: //use -1 as buffer handle to justs end delim as postdata //DP_CSQC_MAINVIEW //idea: divVerent //darkplaces implementation: divVerent //constant definitions: const float VF_MAINVIEW = 400; //use setproperty(VF_MAINVIEW, 1); before calling R_RenderView for the render //that shall become the "main" view, which is e.g. used by PRYDON_CLIENTCURSOR //this flag is set for the first scene, and not cleared by R_ClearScene //this flag is automatically cleared by R_RenderView //so when not using this extension, the first view rendered is the main view //DP_CSQC_MINFPS_QUALITY //idea: divVerent //darkplaces implementation: divVerent //constant definitions: const float VF_MINFPS_QUALITY = 401; //use getproperty(VF_MINFPS_QUALITY); to do CSQC based LOD based on cl_minfps //1 should lead to an unmodified view //DP_CSQC_V_CALCREFDEF_WIP1 //DP_CSQC_V_CALCREFDEF_WIP2 //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: void(entity e, float refdefflags) V_CalcRefdef = #640; //constant definitions: float PMF_DUCKED = 4; float PMF_ONGROUND = 8; float REFDEFFLAG_TELEPORTED = 1; float REFDEFFLAG_JUMPING = 2; float REFDEFFLAG_DEAD = 4; float REFDEFFLAG_INTERMISSION = 8; //- use this on the player entity after performing prediction //- pass REFDEFFLAG_TELEPORTED if the player teleported since last frame //- pass REFDEFFLAG_JUMPING if jump button is pressed //- pass REFDEFFLAG_DEAD if dead (DP_CSQC_V_CALCREFDEF_WIP2) //- pass REFDEFFLAG_INTERMISSION if in intermission (DP_CSQC_V_CALCREFDEF_WIP2) //- the player entity needs to have origin, velocity, pmove_flags set according // to prediction (the above two PMF_ flags are used in the player's pmove_flags) //- NOTE: to check for this, ALSO OR a check with DP_CSQC_V_CALCREFDEF to also support // the finished extension once done // assorted builtins float drawsubpic(vector position, vector size, string pic, vector srcPosition, vector srcSize, vector rgb, float alpha, float flag) = #328; vector drawgetimagesize(string pic) = #318; #define SPA_POSITION 0 #define SPA_S_AXIS 1 #define SPA_T_AXIS 2 #define SPA_R_AXIS 3 #define SPA_TEXCOORDS0 4 #define SPA_LIGHTMAP0_TEXCOORDS 5 #define SPA_LIGHTMAP_COLOR 6 float (entity e, float s) getsurfacenumpoints = #434; vector (entity e, float s, float n) getsurfacepoint = #435; vector (entity e, float s) getsurfacenormal = #436; string (entity e, float s) getsurfacetexture = #437; float (entity e, vector p) getsurfacenearpoint = #438; vector (entity e, float s, vector p) getsurfaceclippedpoint = #439; vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; float(entity e, float s) getsurfacenumtriangles = #628; vector(entity e, float s, float n) getsurfacetriangle = #629; //DP_QC_ASINACOSATANATAN2TAN //idea: Urre //darkplaces implementation: LordHavoc //constant definitions: float DEG2RAD = 0.0174532925199432957692369076848861271344287188854172545609719144; float RAD2DEG = 57.2957795130823208767981548141051703324054724665643215491602438612; float PI = 3.1415926535897932384626433832795028841971693993751058209749445923; //builtin definitions: float(float s) asin = #471; // returns angle in radians for a given sin() value, the result is in the range -PI*0.5 to PI*0.5 float(float c) acos = #472; // returns angle in radians for a given cos() value, the result is in the range 0 to PI float(float t) atan = #473; // returns angle in radians for a given tan() value, the result is in the range -PI*0.5 to PI*0.5 float(float c, float s) atan2 = #474; // returns angle in radians for a given cos() and sin() value pair, the result is in the range -PI to PI (this is identical to vectoyaw except it returns radians rather than degrees) float(float a) tan = #475; // returns tangent value (which is simply sin(a)/cos(a)) for the given angle in radians, the result is in the range -infinity to +infinity //description: //useful math functions for analyzing vectors, note that these all use angles in radians (just like the cos/sin functions) not degrees unlike makevectors/vectoyaw/vectoangles, so be sure to do the appropriate conversions (multiply by DEG2RAD or RAD2DEG as needed). //note: atan2 can take unnormalized vectors (just like vectoyaw), and the function was included only for completeness (more often you want vectoyaw or vectoangles), atan2(v_x,v_y) * RAD2DEG gives the same result as vectoyaw(v) //DP_QC_SPRINTF //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: string(string format, ...) sprintf = #627; //description: //you know sprintf :P //supported stuff: // % // optional: $ for the argument to format (the arg counter then is not increased) // flags: #0- + // optional: , *, or *$ for the field width (width is read before value and precision) // optional: ., .*, or .*$ for the precision (precision is read before value) // length modifiers: h for forcing a float, l for forcing an int/entity (by default, %d etc. cast a float to int) // conversions: // d takes a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to an int // i takes an int/entity if no length is specified or i is, and a float if h is specified as length, and cast it to an int // ouxXc take a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to an unsigned int // eEfFgG take a float if no length is specified or h is, and an int/entity if l is specified as length, and cast it to a double // s takes a string // vV takes a vector, and processes the three components as if it were a gG for all three components, separated by space // For conversions s and c, the flag # makes precision and width interpreted // as byte count, by default it is interpreted as character count in UTF-8 // enabled engines. No other conversions can create wide characters, and # // has another meaning in these. //DP_QC_GETTIME //idea: tZork //darkplaces implementation: tZork, divVerent //constant definitions: float GETTIME_FRAMESTART = 0; // time of start of frame float GETTIME_REALTIME = 1; // current time (may be OS specific) float GETTIME_HIRES = 2; // like REALTIME, but may reset between QC invocations and thus can be higher precision float GETTIME_UPTIME = 3; // time since start of the engine //builtin definitions: float(float tmr) gettime = #519; //description: //some timers to query... //DP_QC_GETTIME_CDTRACK //idea: divVerent //darkplaces implementation: divVerent //constant definitions: float GETTIME_CDTRACK = 4; //description: //returns the playing time of the current cdtrack when passed to gettime() //see DP_END_GETSOUNDTIME for similar functionality but for entity sound channels //DP_QC_TOKENIZEBYSEPARATOR //idea: Electro, SavageX, LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: float(string s, string separator1, ...) tokenizebyseparator = #479; //description: //this function returns tokens separated by any of the supplied separator strings, example: //numnumbers = tokenizebyseparator("10.2.3.4", "."); //returns 4 and the tokens are "10" "2" "3" "4" //possibly useful for parsing IPv4 addresses (such as "1.2.3.4") and IPv6 addresses (such as "[1234:5678:9abc:def0:1234:5678:9abc:def0]:26000") //DP_QC_TOKENIZE_CONSOLE //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: float(string s) tokenize_console = #514; float(float i) argv_start_index = #515; float(float i) argv_end_index = #516; //description: //this function returns tokens separated just like the console does //also, functions are provided to get the index of the first and last character of each token in the original string //Passing negative values to them, or to argv, will be treated as indexes from the LAST token (like lists work in Perl). So argv(-1) will return the LAST token. //DP_SND_SOUND7_WIP1 //DP_SND_SOUND7_WIP2 //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: void(entity e, float chan, string samp, float vol, float atten, float speed, float flags) sound7 = #8; float SOUNDFLAG_RELIABLE = 1; //description: //plays a sound, with some more flags //extensions to sound(): //- channel may be in the range from -128 to 127; channels -128 to 0 are "auto", // i.e. support multiple sounds at once, but cannot be stopped/restarted //- a value 0 in the speed parameter means no change; otherwise, it is a // percentage of playback speed ("pitch shifting"). 100 is normal pitch, 50 is // half speed, 200 is double speed, etc. (DP_SND_SOUND7_WIP2) //- the flag SOUNDFLAG_RELIABLE can be specified, which makes the sound send // to MSG_ALL (reliable) instead of MSG_BROADCAST (unreliable, default); // similarily, SOUNDFLAG_RELIABLE_TO_ONE sends to MSG_ONE //- channel 0 is controlled by snd_channel0volume; channel 1 and -1 by // snd_channel1volume, etc. (so, a channel shares the cvar with its respective // auto-channel); however, the mod MUST define snd_channel8volume and upwards // in default.cfg if they are to be used, as the engine does not create them // to not litter the cvar list //- this extension applies to CSQC as well; CSQC_Event_Sound will get speed and // flags as extra 7th and 8th argument //- WIP2 ideas: SOUNDFLAG_RELIABLE_TO_ONE, SOUNDFLAG_NOPHS, SOUNDFLAG_FORCELOOP //- NOTE: to check for this, ALSO OR a check with DP_SND_SOUND7 to also support // the finished extension once done //DP_PRECACHE_PIC_FLAGS //idea: divVerent //darkplaces implementation: divVerent //constant definitions: float PRECACHE_PIC_FROMWAD = 1; // this one actually is part of EXT_CSQC float PRECACHE_PIC_NOTPERSISTENT = 2; // picture may get deallocated when unused float PRECACHE_PIC_MIPMAP = 8; // mipmap the texture for possibly better downscaling at memory expense //notes: these constants are given as optional second argument to precache_pic() //DP_QC_TRACE_MOVETYPE_WORLDONLY //idea: LordHavoc //darkplaces implementation: LordHavoc //constant definitions: float MOVE_WORLDONLY = 3; //description: //allows traces to hit only world (ignoring all entities, unlike MOVE_NOMONSTERS which hits all bmodels), use as the nomonsters parameter to trace functions //DP_SND_GETSOUNDTIME //idea: VorteX //darkplaces implementation: VorteX //constant definitions: float(entity e, float channel) getsoundtime = #533; // get currently sound playing position on entity channel, -1 if not playing or error float(string sample) soundlength = #534; // returns length of sound sample in seconds, -1 on error (sound not precached, sound system not initialized etc.) //description: provides opportunity to query length of sound samples and realtime tracking of sound playing on entities (similar to DP_GETTIME_CDTRACK) //note: beware dedicated server not running sound engine at all, so in dedicated mode this builtins will not work in server progs //note also: menu progs not supporting getsoundtime() (will give a warning) since it has no sound playing on entities //examples of use: // - QC-driven looped sounds // - QC events when sound playing is finished // - toggleable ambientsounds // - subtitles //DP_QC_NUM_FOR_EDICT //idea: Blub\0 //darkplaces implementation: Blub\0 //Function to get the number of an entity - a clean way. float(entity num) num_for_edict = #512; //DP_TRACE_HITCONTENTSMASK_SURFACEINFO //idea: LordHavoc //darkplaces implementation: LordHavoc //globals: .float dphitcontentsmask; // if non-zero on the entity passed to traceline/tracebox/tracetoss this will override the normal collidable contents rules and instead hit these contents values (for example AI can use tracelines that hit DONOTENTER if it wants to, by simply changing this field on the entity passed to traceline), this affects normal movement as well as trace calls float trace_dpstartcontents; // DPCONTENTS_ value at start position of trace float trace_dphitcontents; // DPCONTENTS_ value of impacted surface (not contents at impact point, just contents of the surface that was hit) float trace_dphitq3surfaceflags; // Q3SURFACEFLAG_ value of impacted surface string trace_dphittexturename; // texture name of impacted surface //constants: float DPCONTENTS_SOLID = 1; // hit a bmodel, not a bounding box float DPCONTENTS_WATER = 2; float DPCONTENTS_SLIME = 4; float DPCONTENTS_LAVA = 8; float DPCONTENTS_SKY = 16; float DPCONTENTS_BODY = 32; // hit a bounding box, not a bmodel float DPCONTENTS_CORPSE = 64; // hit a SOLID_CORPSE entity float DPCONTENTS_NODROP = 128; // an area where backpacks should not spawn float DPCONTENTS_PLAYERCLIP = 256; // blocks player movement float DPCONTENTS_MONSTERCLIP = 512; // blocks monster movement float DPCONTENTS_DONOTENTER = 1024; // AI hint brush float DPCONTENTS_LIQUIDSMASK = 14; // WATER | SLIME | LAVA float DPCONTENTS_BOTCLIP = 2048; // AI hint brush float DPCONTENTS_OPAQUE = 4096; // only fully opaque brushes get this (may be useful for line of sight checks) float Q3SURFACEFLAG_NODAMAGE = 1; float Q3SURFACEFLAG_SLICK = 2; // low friction surface float Q3SURFACEFLAG_SKY = 4; // sky surface (also has NOIMPACT and NOMARKS set) float Q3SURFACEFLAG_LADDER = 8; // climbable surface float Q3SURFACEFLAG_NOIMPACT = 16; // projectiles should remove themselves on impact (this is set on sky) float Q3SURFACEFLAG_NOMARKS = 32; // projectiles should not leave marks, such as decals (this is set on sky) float Q3SURFACEFLAG_FLESH = 64; // projectiles should do a fleshy effect (blood?) on impact float Q3SURFACEFLAG_NODRAW = 128; // compiler hint (not important to qc) //float Q3SURFACEFLAG_HINT = 256; // compiler hint (not important to qc) //float Q3SURFACEFLAG_SKIP = 512; // compiler hint (not important to qc) //float Q3SURFACEFLAG_NOLIGHTMAP = 1024; // compiler hint (not important to qc) //float Q3SURFACEFLAG_POINTLIGHT = 2048; // compiler hint (not important to qc) float Q3SURFACEFLAG_METALSTEPS = 4096; // walking on this surface should make metal step sounds float Q3SURFACEFLAG_NOSTEPS = 8192; // walking on this surface should not make footstep sounds float Q3SURFACEFLAG_NONSOLID = 16384; // compiler hint (not important to qc) //float Q3SURFACEFLAG_LIGHTFILTER = 32768; // compiler hint (not important to qc) //float Q3SURFACEFLAG_ALPHASHADOW = 65536; // compiler hint (not important to qc) //float Q3SURFACEFLAG_NODLIGHT = 131072; // compiler hint (not important to qc) //float Q3SURFACEFLAG_DUST = 262144; // translucent 'light beam' effect (not important to qc) //description: //adds additional information after a traceline/tracebox/tracetoss call. //also (very important) sets trace_* globals before calling .touch functions, //this allows them to inspect the nature of the collision (for example //determining if a projectile hit sky), clears trace_* variables for the other //object in a touch event (that is to say, a projectile moving will see the //trace results in its .touch function, but the player it hit will see very //little information in the trace_ variables as it was not moving at the time) //DP_QC_CVAR_TYPE //idea: divVerent //DarkPlaces implementation: divVerent //builtin definitions: float(string name) cvar_type = #495; float CVAR_TYPEFLAG_EXISTS = 1; float CVAR_TYPEFLAG_SAVED = 2; float CVAR_TYPEFLAG_PRIVATE = 4; float CVAR_TYPEFLAG_ENGINE = 8; float CVAR_TYPEFLAG_HASDESCRIPTION = 16; float CVAR_TYPEFLAG_READONLY = 32; //DP_QC_CRC16 //idea: divVerent //darkplaces implementation: divVerent //Some hash function to build hash tables with. This has to be be the CRC-16-CCITT that is also required for the QuakeWorld download protocol. //When caseinsensitive is set, the CRC is calculated of the lower cased string. float(float caseinsensitive, string s, ...) crc16 = #494; //DP_QC_URI_ESCAPE //idea: divVerent //darkplaces implementation: divVerent //URI::Escape's functionality string(string in) uri_escape = #510; string(string in) uri_unescape = #511; //DP_QC_DIGEST //idea: motorsep, Spike //DarkPlaces implementation: divVerent //builtin definitions: string(string digest, string data, ...) digest_hex = #639; //description: //returns a given hex digest of given data //the returned digest is always encoded in hexadecimal //only the "MD4" digest is always supported! //if the given digest is not supported, string_null is returned //the digest string is matched case sensitively, use "MD4", not "md4"! //DP_QC_DIGEST_SHA256 //idea: motorsep, Spike //DarkPlaces implementation: divVerent //description: //"SHA256" is also an allowed digest type //DP_QC_LOG //darkplaces implementation: divVerent //builtin definitions: float log(float f) = #532; //description: //logarithm //FTE_CSQC_SKELETONOBJECTS //idea: Spike, LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: // all skeleton numbers are 1-based (0 being no skeleton) // all bone numbers are 1-based (0 being invalid) float(float modlindex) skel_create = #263; // create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex, as the skeleton uses the hierarchy from the model. float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure float(float skel) skel_get_numbones = #265; // returns how many bones exist in the created skeleton, 0 if skeleton does not exist string(float skel, float bonenum) skel_get_bonename = #266; // returns name of bone (as a tempstring), "" if invalid bonenum (< 1 for example) or skeleton does not exist float(float skel, float bonenum) skel_get_boneparent = #267; // returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) float(float skel, string tagname) skel_find_bone = #268; // get number of bone with specified name, 0 on failure, bonenum (1-based) on success, same as using gettagindex but takes modelindex instead of entity vector(float skel, float bonenum) skel_get_bonerel = #269; // get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) vector(float skel, float bonenum) skel_get_boneabs = #270; // get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) void(float skel, float bonenum, vector org) skel_set_bone = #271; // set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) void(float skel, float bonenum, vector org) skel_mul_bone = #272; // transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse void(float skel) skel_delete = #275; // deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) float(float modlindex, string framename) frameforname = #276; // finds number of a specified frame in the animation, returns -1 if no match found float(float modlindex, float framenum) frameduration = #277; // returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. //fields: .float skeletonindex; // active skeleton overriding standard animation on model .float frame; // primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4) .float frame2; // secondary framegroup animation (strength = lerpfrac) .float frame3; // tertiary framegroup animation (strength = lerpfrac3) .float frame4; // quaternary framegroup animation (strength = lerpfrac4) .float lerpfrac; // strength of framegroup blend .float lerpfrac3; // strength of framegroup blend .float lerpfrac4; // strength of framegroup blend .float frame1time; // start time of framegroup animation .float frame2time; // start time of framegroup animation .float frame3time; // start time of framegroup animation .float frame4time; // start time of framegroup animation //description: //this extension provides a way to do complex skeletal animation on an entity. // //see also DP_SKELETONOBJECTS (this extension implemented on server as well as client) // //notes: //each model contains its own skeleton, reusing a skeleton with incompatible models will yield garbage (or not render). //each model contains its own animation data, you can use animations from other model files (for example saving out all character animations as separate model files). //if an engine supports loading an animation-only file format such as .md5anim in FTEQW, it can be used to animate any model with a compatible skeleton. //proper use of this extension may require understanding matrix transforms (v_forward, v_right, v_up, origin), and you must keep in mind that v_right is negative for this purpose. // //features include: //multiple animations blended together. //animating a model with animations from another model with a compatible skeleton. //restricting animation blends to certain bones of a model - for example independent animation of legs, torso, head. //custom bone controllers - for example making eyes track a target location. // // // //example code follows... // //this helper function lets you identify (by parentage) what group a bone //belongs to - for example "torso", "leftarm", would return 1 ("torso") for //all children of the bone named "torso", unless they are children of //"leftarm" (which is a child of "torso") which would return 2 instead... float(float skel, float bonenum, string g1, string g2, string g3, string g4, string g5, string g6) example_skel_findbonegroup = { local string bonename; while (bonenum >= 0) { bonename = skel_get_bonename(skel, bonenum); if (bonename == g1) return 1; if (bonename == g2) return 2; if (bonename == g3) return 3; if (bonename == g4) return 4; if (bonename == g5) return 5; if (bonename == g6) return 6; bonenum = skel_get_boneparent(skel, bonenum); } return 0; }; // create a skeletonindex for our player using current modelindex void() example_skel_player_setup = { self.skeletonindex = skel_create(self.modelindex); }; // setup bones of skeleton based on an animation // note: animmodelindex can be a different model than self.modelindex void(float animmodelindex, float framegroup, float framegroupstarttime) example_skel_player_update_begin = { // start with our standard animation self.frame = framegroup; self.frame2 = 0; self.frame3 = 0; self.frame4 = 0; self.frame1time = framegroupstarttime; self.frame2time = 0; self.frame3time = 0; self.frame4time = 0; self.lerpfrac = 0; self.lerpfrac3 = 0; self.lerpfrac4 = 0; skel_build(self.skeletonindex, self, animmodelindex, 0, 0, 100000); }; // apply a different framegroup animation to bones with a specified parent void(float animmodelindex, float framegroup, float framegroupstarttime, float blendalpha, string groupbonename, string excludegroupname1, string excludegroupname2) example_skel_player_update_applyoverride = { local float bonenum; local float numbones; self.frame = framegroup; self.frame2 = 0; self.frame3 = 0; self.frame4 = 0; self.frame1time = framegroupstarttime; self.frame2time = 0; self.frame3time = 0; self.frame4time = 0; self.lerpfrac = 0; self.lerpfrac3 = 0; self.lerpfrac4 = 0; bonenum = 0; numbones = skel_get_numbones(self.skeletonindex); while (bonenum < numbones) { if (example_skel_findbonegroup(self.skeletonindex, bonenum, groupbonename, excludegroupname1, excludegroupname2, "", "", "") == 1) skel_build(self.skeletonindex, self, animmodelindex, 1 - blendalpha, bonenum, bonenum + 1); bonenum = bonenum + 1; } }; // make eyes point at a target location, be sure v_forward, v_right, v_up are set correctly before calling void(vector eyetarget, string bonename) example_skel_player_update_eyetarget = { local float bonenum; local vector ang; local vector oldforward, oldright, oldup; local vector relforward, relright, relup, relorg; local vector boneforward, boneright, boneup, boneorg; local vector parentforward, parentright, parentup, parentorg; local vector u, v; local vector modeleyetarget; bonenum = skel_find_bone(self.skeletonindex, bonename) - 1; if (bonenum < 0) return; oldforward = v_forward; oldright = v_right; oldup = v_up; v = eyetarget - self.origin; modeleyetarget_x = v * v_forward; modeleyetarget_y = 0-v * v_right; modeleyetarget_z = v * v_up; // this is an eyeball, make it point at the target location // first get all the data we can... relorg = skel_get_bonerel(self.skeletonindex, bonenum); relforward = v_forward; relright = v_right; relup = v_up; boneorg = skel_get_boneabs(self.skeletonindex, bonenum); boneforward = v_forward; boneright = v_right; boneup = v_up; parentorg = skel_get_boneabs(self.skeletonindex, skel_get_boneparent(self.skeletonindex, bonenum)); parentforward = v_forward; parentright = v_right; parentup = v_up; // get the vector from the eyeball to the target u = modeleyetarget - boneorg; // now transform it inversely by the parent matrix to produce new rel vectors v_x = u * parentforward; v_y = u * parentright; v_z = u * parentup; ang = vectoangles2(v, relup); ang_x = 0 - ang_x; makevectors(ang); // set the relative bone matrix skel_set_bone(self.skeletonindex, bonenum, relorg); // restore caller's v_ vectors v_forward = oldforward; v_right = oldright; v_up = oldup; }; // delete skeleton when we're done with it // note: skeleton remains valid until next frame when it is really deleted void() example_skel_player_delete = { skel_delete(self.skeletonindex); self.skeletonindex = 0; }; // // END OF EXAMPLES FOR FTE_CSQC_SKELETONOBJECTS // //DP_QC_ENTITYDATA //idea: KrimZon //darkplaces implementation: KrimZon //builtin definitions: float() numentityfields = #496; string(float fieldnum) entityfieldname = #497; float(float fieldnum) entityfieldtype = #498; string(float fieldnum, entity ent) getentityfieldstring = #499; float(float fieldnum, entity ent, string s) putentityfieldstring = #500; //constants: //Returned by entityfieldtype float FIELD_STRING = 1; float FIELD_FLOAT = 2; float FIELD_VECTOR = 3; float FIELD_ENTITY = 4; float FIELD_FUNCTION = 6; //description: //Versatile functions intended for storing data from specific entities between level changes, but can be customized for some kind of partial savegame. //WARNING: .entity fields cannot be saved and restored between map loads as they will leave dangling pointers. //numentityfields returns the number of entity fields. NOT offsets. Vectors comprise 4 fields: v, v_x, v_y and v_z. //entityfieldname returns the name as a string, eg. "origin" or "classname" or whatever. //entityfieldtype returns a value that the constants represent, but the field may be of another type in more exotic progs.dat formats or compilers. //getentityfieldstring returns data as would be written to a savegame, eg... "0.05" (float), "0 0 1" (vector), or "Hello World!" (string). Function names can also be returned. //putentityfieldstring puts the data returned by getentityfieldstring back into the entity. //DP_QC_ENTITYSTRING void(string s) loadfromdata = #529; void(string s) loadfromfile = #530; void(string s) callfunction = #605; void(float fh, entity e) writetofile = #606; float(string s) isfunction = #607; void(entity e, string s) parseentitydata = #608; //DP_COVERAGE //idea: divVerent //darkplaces implementation: divVerent //function definitions: void coverage() = #642; // Reports a coverage event. The engine counts for each of the calls to this builtin whether it has been called. // assorted builtins const float STAT_MOVEVARS_TICRATE = 240; const float STAT_MOVEVARS_TIMESCALE = 241; const float STAT_FRAGLIMIT = 235; const float STAT_TIMELIMIT = 236; const float STAT_MOVEVARS_GRAVITY = 242; string(void) ReadPicture = #501; float PARTICLES_USEALPHA = 1; float particles_alphamin, particles_alphamax; float PARTICLES_USECOLOR = 2; vector particles_colormin, particles_colormax; float PARTICLES_USEFADE = 4; // fades the COUNT (fade alpha using alphamin/alphamax) float particles_fade; float PARTICLES_DRAWASTRAIL = 128; void(float effectindex, entity own, vector org_from, vector org_to, vector dir_from, vector dir_to, float countmultiplier, float flags) boxparticles = #502; float trace_networkentity; const float RF_FULLBRIGHT = 256; const float RF_NOSHADOW = 512; float RF_DYNAMICMODELLIGHT = 8192; float gettaginfo_parent; string gettaginfo_name; vector gettaginfo_offset; vector gettaginfo_forward; vector gettaginfo_right; vector gettaginfo_up; float checkpvs(vector viewpos, entity viewee) = #240; darkplaces/dpdefs/dpextensions.qc0000664000175000017500000037143113067716220016511 0ustar kalevkalev //DarkPlaces supported extension list, draft version 1.04 //definitions that id Software left out: //these are passed as the 'nomonsters' parameter to traceline/tracebox (yes really this was supported in all quake engines, nomonsters is misnamed) float MOVE_NORMAL = 0; // same as FALSE float MOVE_NOMONSTERS = 1; // same as TRUE float MOVE_MISSILE = 2; // save as movement with .movetype == MOVETYPE_FLYMISSILE //checkextension function //idea: expected by almost everyone //darkplaces implementation: LordHavoc float(string s) checkextension = #99; //description: //check if (cvar("pr_checkextension")) before calling this, this is the only //guaranteed extension to be present in the extension system, it allows you //to check if an extension is available, by name, to check for an extension //use code like this: //// (it is recommended this code be placed in worldspawn or a worldspawn called function somewhere) //if (cvar("pr_checkextension")) //if (checkextension("DP_SV_SETCOLOR")) // ext_setcolor = TRUE; //from then on you can check ext_setcolor to know if that extension is available //BX_WAL_SUPPORT //idea: id Software //darkplaces implementation: LordHavoc //description: //indicates the engine supports .wal textures for filenames in the textures/ directory //(note: DarkPlaces has supported this since 2001 or 2002, but did not advertise it as an extension, then I noticed Betwix was advertising it and added the extension accordingly) //DP_BUTTONCHAT //idea: Vermeulen //darkplaces implementation: LordHavoc //field definitions: .float buttonchat; //description: //true if the player is currently chatting (in messagemode, menus or console) //DP_BUTTONUSE //idea: id Software //darkplaces implementation: LordHavoc //field definitions: .float buttonuse; //client console commands: //+use //-use //description: //made +use and -use commands work, they now control the .buttonuse field (.button1 was used by many mods for other purposes). //DP_CL_LOADSKY //idea: Nehahra, LordHavoc //darkplaces implementation: LordHavoc //client console commands: //"loadsky" (parameters: "basename", example: "mtnsun_" would load "mtnsun_up.tga" and "mtnsun_rt.tga" and similar names, use "" to revert to quake sky, note: this is the same as Quake2 skybox naming) //description: //sets global skybox for the map for this client (can be stuffed to a client by QC), does not hurt much to repeatedly execute this command, please don't use this in mods if it can be avoided (only if changing skybox is REALLY needed, otherwise please use DP_GFX_SKYBOX). //DP_CON_SET //idea: id Software //darkplaces implementation: LordHavoc //description: //indicates this engine supports the "set" console command which creates or sets a non-archived cvar (not saved to config.cfg on exit), it is recommended that set and seta commands be placed in default.cfg for mod-specific cvars. //DP_CON_SETA //idea: id Software //darkplaces implementation: LordHavoc //description: //indicates this engine supports the "seta" console command which creates or sets an archived cvar (saved to config.cfg on exit), it is recommended that set and seta commands be placed in default.cfg for mod-specific cvars. //DP_CON_ALIASPARAMETERS //idea: many //darkplaces implementation: Black //description: //indicates this engine supports aliases containing $1 through $9 parameter macros (which when called will expand to the parameters passed to the alias, for example alias test "say $2 $1", then you can type test hi there and it will execute say there hi), as well as $0 (name of the alias) and $* (all parameters $1 onward). //DP_CON_EXPANDCVAR //idea: many, PHP //darkplaces implementation: Black //description: //indicates this engine supports console commandlines containing $cvarname which will expand to the contents of that cvar as a parameter, for instance say my fov is $fov, will say "my fov is 90", or similar. //DP_CON_STARTMAP //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //adds two engine-called aliases named startmap_sp and startmap_dm which are called when the engine tries to start a singleplayer game from the menu (startmap_sp) or the -listen or -dedicated options are used or the engine is a dedicated server (uses startmap_dm), these allow a mod or game to specify their own map instead of start, and also distinguish between singleplayer and -listen/-dedicated, also these need not be a simple "map start" command, they can do other things if desired, startmap_sp and startmap_dm both default to "map start". //DP_EF_ADDITIVE //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_ADDITIVE = 32; //description: //additive blending when this object is rendered //DP_EF_BLUE //idea: id Software //darkplaces implementation: LordHavoc //effects bit: float EF_BLUE = 64; //description: //entity emits blue light (used for quad) //DP_EF_DOUBLESIDED //idea: LordHavoc //darkplaces implementation: [515] and LordHavoc //effects bit: float EF_DOUBLESIDED = 32768; //description: //render entity as double sided (backfaces are visible, I.E. you see the 'interior' of the model, rather than just the front), can be occasionally useful on transparent stuff. //DP_EF_DYNAMICMODELLIGHT //idea: C.Brutail, divVerent, maikmerten //darkplaces implementation: divVerent //effects bit: float EF_DYNAMICMODELLIGHT = 131072; //description: //force dynamic model light on the entity, even if it's a BSP model (or anything else with lightmaps or light colors) //DP_EF_FLAME //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_FLAME = 1024; //description: //entity is on fire //DP_EF_FULLBRIGHT //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_FULLBRIGHT = 512; //description: //entity is always brightly lit //DP_EF_NODEPTHTEST //idea: Supa //darkplaces implementation: LordHavoc //effects bit: float EF_NODEPTHTEST = 8192; //description: //makes entity show up to client even through walls, useful with EF_ADDITIVE for special indicators like where team bases are in a map, so that people don't get lost //DP_EF_NODRAW //idea: id Software //darkplaces implementation: LordHavoc //effects bit: float EF_NODRAW = 16; //description: //prevents server from sending entity to client (forced invisible, even if it would have been a light source or other such things) //DP_EF_NOGUNBOB //idea: Chris Page, Dresk //darkplaces implementation: LordHAvoc //effects bit: float EF_NOGUNBOB = 256; //description: //this has different meanings depending on the entity it is used on: //player entity - prevents gun bobbing on player.viewmodel //viewmodelforclient entity - prevents gun bobbing on an entity attached to the player's view //other entities - no effect //uses: //disabling gun bobbing on a diving mask or other model used as a .viewmodel. //disabling gun bobbing on view-relative models meant to be part of the heads up display. (note: if fov is changed these entities may be off-screen, or too near the center of the screen, so use fov 90 in this case) //DP_EF_NOSHADOW //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_NOSHADOW = 4096; //description: //realtime lights will not cast shadows from this entity (but can still illuminate it) //DP_EF_RED //idea: id Software //darkplaces implementation: LordHavoc //effects bit: float EF_RED = 128; //description: //entity emits red light (used for invulnerability) //DP_EF_RESTARTANIM_BIT //idea: id software //darkplaces implementation: divVerent //effects bit: float EF_RESTARTANIM_BIT = 1048576; //description: //when toggled, the current animation is restarted. Useful for weapon animation. //to toggle this bit in QC, you can do: // self.effects += (EF_RESTARTANIM_BIT - 2 * (self.effects & EF_RESTARTANIM_BIT)); //DP_EF_STARDUST //idea: MythWorks Inc //darkplaces implementation: LordHavoc //effects bit: float EF_STARDUST = 2048; //description: //entity emits bouncing sparkles in every direction //DP_EF_TELEPORT_BIT //idea: id software //darkplaces implementation: divVerent //effects bit: float EF_TELEPORT_BIT = 2097152; //description: //when toggled, interpolation of the entity is skipped for one frame. Useful for teleporting. //to toggle this bit in QC, you can do: // self.effects += (EF_TELEPORT_BIT - 2 * (self.effects & EF_TELEPORT_BIT)); //DP_ENT_ALPHA //idea: Nehahra //darkplaces implementation: LordHavoc //fields: .float alpha; //description: //controls opacity of the entity, 0.0 is forced to be 1.0 (otherwise everything would be invisible), use -1 if you want to make something invisible, 1.0 is solid (like normal). //DP_ENT_COLORMOD //idea: LordHavoc //darkplaces implementation: LordHavoc //field definition: .vector colormod; //description: //controls color of the entity, '0 0 0', is forced to be '1 1 1' (otherwise everything would be black), used for tinting objects, for instance using '1 0.6 0.4' on an ogre would give you an orange ogre (order is red green blue), note the colors can go up to '8 8 8' (8x as bright as normal). //DP_ENT_CUSTOMCOLORMAP //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //if .colormap is set to 1024 + pants + shirt * 16, those colors will be used for colormapping the entity, rather than looking up a colormap by player number. /* //NOTE: no longer supported by darkplaces because all entities are delta compressed now //DP_ENT_DELTACOMPRESS // no longer supported //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_DELTA = 8388608; //description: //(obsolete) applies delta compression to the network updates of the entity, making updates smaller, this might cause some unreliable behavior in packet loss situations, so it should only be used on numerous (nails/plasma shots/etc) or unimportant objects (gibs/shell casings/bullet holes/etc). */ //DP_ENT_EXTERIORMODELTOCLIENT //idea: LordHavoc //darkplaces implementation: LordHavoc //fields: .entity exteriormodeltoclient; //description: //the entity is visible to all clients with one exception: if the specified client is using first person view (not using chase_active) the entity will not be shown. Also if tag attachments are supported any entities attached to the player entity will not be drawn in first person. //DP_ENT_GLOW //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float glow_color; .float glow_size; .float glow_trail; //description: //customizable glowing light effect on the entity, glow_color is a paletted (8bit) color in the range 0-255 (note: 0 and 254 are white), glow_size is 0 or higher (up to the engine what limit to cap it to, darkplaces imposes a 1020 limit), if glow_trail is true it will leave a trail of particles of the same color as the light. //DP_ENT_GLOWMOD //idea: LordHavoc //darkplaces implementation: LordHavoc //field definition: .vector glowmod; //description: //controls color of the entity's glow texture (fullbrights), '0 0 0', is forced to be '1 1 1' (otherwise everything would be black), used for tinting objects, see colormod (same color restrictions apply). //DP_ENT_LOWPRECISION //idea: LordHavoc //darkplaces implementation: LordHavoc //effects bit: float EF_LOWPRECISION = 4194304; //description: //uses low quality origin coordinates, reducing network traffic compared to the default high precision, intended for numerous objects (projectiles/gibs/bullet holes/etc). //DP_ENT_SCALE //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float scale; //description: //controls rendering scale of the object, 0 is forced to be 1, darkplaces uses 1/16th accuracy and a limit of 15.9375, can be used to make an object larger or smaller. //DP_ENT_TRAILEFFECTNUM //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float traileffectnum; //description: //use a custom effectinfo.txt effect on this entity, assign it like this: //self.traileffectnum = particleeffectnum("mycustomeffect"); //this will do both the dlight and particle trail as described in the effect, basically equivalent to trailparticles() in CSQC but performed on a server entity. //DP_ENT_VIEWMODEL //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .entity viewmodelforclient; //description: //this is a very special capability, attachs the entity to the view of the client specified, origin and angles become relative to the view of that client, all effects can be used (multiple skins on a weapon model etc)... the entity is not visible to any other client. //DP_GFX_EXTERNALTEXTURES //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //loads external textures found in various directories (tenebrae compatible)... /* in all examples .tga is merely the base texture, it can be any of these: .tga (base texture) _glow.tga (fullbrights or other glowing overlay stuff, NOTE: this is done using additive blend, not alpha) _pants.tga (pants overlay for colormapping on models, this should be shades of grey (it is tinted by pants color) and black wherever the base texture is not black, as this is an additive blend) _shirt.tga (same idea as pants, but for shirt color) _diffuse.tga (this may be used instead of base texture for per pixel lighting) _gloss.tga (specular texture for per pixel lighting, note this can be in color (tenebrae only supports greyscale)) _norm.tga (normalmap texture for per pixel lighting) _bump.tga (bumpmap, converted to normalmap at load time, supported only for reasons of tenebrae compatibility) _luma.tga (same as _glow but supported only for reasons of tenebrae compatibility) due to glquake's incomplete Targa(r) loader, this section describes required Targa(r) features support: types: type 1 (uncompressed 8bit paletted with 24bit/32bit palette) type 2 (uncompressed 24bit/32bit true color, glquake supported this) type 3 (uncompressed 8bit greyscale) type 9 (RLE compressed 8bit paletted with 24bit/32bit palette) type 10 (RLE compressed 24bit/32bit true color, glquake supported this) type 11 (RLE compressed 8bit greyscale) attribute bit 0x20 (Origin At Top Left, top to bottom, left to right) image formats guaranteed to be supported: tga, pcx, lmp image formats that are optional: png, jpg mdl/spr/spr32 examples: skins are named _A (A being a number) and skingroups are named like _A_B these act as suffixes on the model name... example names for skin _2_1 of model "progs/armor.mdl": game/override/progs/armor.mdl_2_1.tga game/textures/progs/armor.mdl_2_1.tga game/progs/armor.mdl_2_1.tga example names for skin _0 of the model "progs/armor.mdl": game/override/progs/armor.mdl_0.tga game/textures/progs/armor.mdl_0.tga game/progs/armor.mdl_0.tga note that there can be more skins files (of the _0 naming) than the mdl contains, this is only useful to save space in the .mdl file if classic quake compatibility is not a concern. bsp/md2/md3 examples: example names for the texture "quake" of model "maps/start.bsp": game/override/quake.tga game/textures/quake.tga game/quake.tga sbar/menu/console textures: for example the texture "conchars" (console font) in gfx.wad game/override/gfx/conchars.tga game/textures/gfx/conchars.tga game/gfx/conchars.tga */ //DP_GFX_EXTERNALTEXTURES_PERMAPTEXTURES //idea: Fuh? //darkplaces implementation: LordHavoc //description: //Q1BSP and HLBSP map loading loads external textures found in textures// as well as textures/. //Where mapname is the bsp filename minus the extension (typically .bsp) and minus maps/ if it is in maps/ (any other path is not removed) //example: //maps/e1m1.bsp uses textures in the directory textures/e1m1/ and falls back to textures/ //maps/b_batt0.bsp uses textures in the directory textures/b_batt0.bsp and falls back to textures/ //as a more extreme example: //progs/something/blah.bsp uses textures in the directory textures/progs/something/blah/ and falls back to textures/ //DP_GFX_FOG //idea: LordHavoc //darkplaces implementation: LordHavoc //worldspawn fields: //"fog" (parameters: "density red green blue", example: "0.1 0.3 0.3 0.3") //description: //global fog for the map, can not be changed by QC //DP_GFX_QUAKE3MODELTAGS //idea: id Software //darkplaces implementation: LordHavoc //field definitions: .entity tag_entity; // entity this is attached to (call setattachment to set this) .float tag_index; // which tag on that entity (0 is relative to the entity, > 0 is an index into the tags on the model if it has any) (call setattachment to set this) //builtin definitions: void(entity e, entity tagentity, string tagname) setattachment = #443; // attachs e to a tag on tagentity (note: use "" to attach to entity origin/angles instead of a tag) //description: //allows entities to be visually attached to model tags (which follow animations perfectly) on other entities, for example attaching a weapon to a player's hand, or upper body attached to lower body, allowing it to change angles and frame separately (note: origin and angles are relative to the tag, use '0 0 0' for both if you want it to follow exactly, this is similar to viewmodelforclient's behavior). //note 2: if the tag is not found, it defaults to "" (attach to origin/angles of entity) //note 3: attaching to world turns off attachment //note 4: the entity that this is attached to must be visible for this to work //note 5: if an entity is attached to the player entity it will not be drawn in first person. //DP_GFX_SKINFILES //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //alias models (mdl, md2, md3) can have .skin files to replace conventional texture naming, these have a naming format such as: //progs/test.md3_0.skin //progs/test.md3_1.skin //... // //these files contain replace commands (replace meshname shadername), example: //replace "helmet" "progs/test/helmet1.tga" // this is a mesh shader replacement //replace "teamstripes" "progs/test/redstripes.tga" //replace "visor" "common/nodraw" // this makes the visor mesh invisible ////it is not possible to rename tags using this format // //Or the Quake3 syntax (100% compatible with Quake3's .skin files): //helmet,progs/test/helmet1.tga // this is a mesh shader replacement //teamstripes,progs/test/redstripes.tga //visor,common/nodraw // this makes the visor mesh invisible //tag_camera, // this defines that the first tag in the model is called tag_camera //tag_test, // this defines that the second tag in the model is called tag_test // //any names that are not replaced are automatically show up as a grey checkerboard to indicate the error status, and "common/nodraw" is a special case that is invisible. //this feature is intended to allow multiple skin sets on md3 models (which otherwise only have one skin set). //other commands might be added someday but it is not expected. //DP_GFX_SKYBOX //idea: LordHavoc //darkplaces implementation: LordHavoc //worldspawn fields: //"sky" (parameters: "basename", example: "mtnsun_" would load "mtnsun_up.tga" and "mtnsun_rt.tga" and similar names, note: "sky" is also used the same way by Quake2) //description: //global skybox for the map, can not be changed by QC //DP_UTF8 //idea: Blub\0, divVerent //darkplaces implementation: Blub\0 //cvar definitions: // utf8_enable: enable utf8 encoding //description: utf8 characters are allowed inside cvars, protocol strings, files, progs strings, etc., //and count as 1 char for string functions like strlen, substring, etc. // note: utf8_enable is run-time cvar, could be changed during execution // note: beware that str2chr() could return value bigger than 255 once utf8 is enabled //DP_HALFLIFE_MAP //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //simply indicates that the engine supports HalfLife maps (BSP version 30, NOT the QER RGBA ones which are also version 30). //DP_HALFLIFE_MAP_CVAR //idea: LordHavoc //darkplaces implementation: LordHavoc //cvars: //halflifebsp 0/1 //description: //engine sets this cvar when loading a map to indicate if it is halflife format or not. //DP_HALFLIFE_SPRITE //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //simply indicates that the engine supports HalfLife sprites. //DP_INPUTBUTTONS //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float button3; .float button4; .float button5; .float button6; .float button7; .float button8; .float button9; .float button10; .float button11; .float button12; .float button13; .float button14; .float button15; .float button16; //description: //set to the state of the +button3, +button4, +button5, +button6, +button7, and +button8 buttons from the client, this does not involve protocol changes (the extra 6 button bits were simply not used). //the exact mapping of protocol button bits on the server is: //self.button0 = (bits & 1) != 0; ///* button1 is skipped because mods abuse it as a variable, and accordingly it has no bit */ //self.button2 = (bits & 2) != 0; //self.button3 = (bits & 4) != 0; //self.button4 = (bits & 8) != 0; //self.button5 = (bits & 16) != 0; //self.button6 = (bits & 32) != 0; //self.button7 = (bits & 64) != 0; //self.button8 = (bits & 128) != 0; // DP_LIGHTSTYLE_STATICVALUE // idea: VorteX // darkplaces implementation: VorteX // description: allows alternative 'static' lightstyle syntax : "=value" // examples: "=0.5", "=2.0", "=2.75" // could be used to control switchable lights or making styled lights with brightness > 2 // Warning: this extension is experimental. It safely works in CSQC, but SVQC use is limited by the fact // that other engines (which do not support this extension) could connect to a game and misunderstand this kind of lightstyle syntax //DP_LITSPRITES //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //indicates this engine supports lighting on sprites, any sprite with ! in its filename (both on disk and in the qc) will be lit rather than having forced EF_FULLBRIGHT (EF_FULLBRIGHT on the entity can still force these sprites to not be lit). //DP_LITSUPPORT //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //indicates this engine loads .lit files for any quake1 format .bsp files it loads to enhance maps with colored lighting. //implementation description: these files begin with the header QLIT followed by version number 1 (as little endian 32bit), the rest of the file is a replacement lightmaps lump, except being 3x as large as the lightmaps lump of the map it matches up with (and yes the between-lightmap padding is expanded 3x to keep this consistent), so the lightmap offset in each surface is simply multiplied by 3 during loading to properly index the lit data, and the lit file is loaded instead of the lightmap lump, other renderer changes are needed to display these of course... see the litsupport.zip sample code (almost a tutorial) at http://icculus.org/twilight/darkplaces for more information. //DP_MONSTERWALK //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //MOVETYPE_WALK is permitted on non-clients, so bots can move smoothly, run off ledges, etc, just like a real player. //DP_MOVETYPEBOUNCEMISSILE //idea: id Software //darkplaces implementation: id Software //movetype definitions: //float MOVETYPE_BOUNCEMISSILE = 11; // already in defs.qc //description: //MOVETYPE_BOUNCE but without gravity, and with full reflection (no speed loss like grenades have), in other words - bouncing laser bolts. //DP_MOVETYPEFLYWORLDONLY //idea: Samual //darkplaces implementation: Samual //movetype definitions: float MOVETYPE_FLY_WORLDONLY = 33; //description: //like MOVETYPE_FLY, but does all traces with MOVE_WORLDONLY, and is ignored by MOVETYPE_PUSH. Should only be combined with SOLID_NOT and SOLID_TRIGGER. //DP_NULL_MODEL //idea: Chris //darkplaces implementation: divVerent //definitions: //string dp_null_model = "null"; //description: //setmodel(e, "null"); makes an entity invisible, have a zero bbox, but //networked. useful for shared CSQC entities. //DP_MOVETYPEFOLLOW //idea: id Software, LordHavoc (redesigned) //darkplaces implementation: LordHavoc //movetype definitions: float MOVETYPE_FOLLOW = 12; //description: //MOVETYPE_FOLLOW implemented, this uses existing entity fields in unusual ways: //aiment - the entity this is attached to. //punchangle - the original angles when the follow began. //view_ofs - the relative origin (note that this is un-rotated by punchangle, and that is actually the only purpose of punchangle). //v_angle - the relative angles. //here's an example of how you would set a bullet hole sprite to follow a bmodel it was created on, even if the bmodel rotates: //hole.movetype = MOVETYPE_FOLLOW; // make the hole follow //hole.solid = SOLID_NOT; // MOVETYPE_FOLLOW is always non-solid //hole.aiment = bmodel; // make the hole follow bmodel //hole.punchangle = bmodel.angles; // the original angles of bmodel //hole.view_ofs = hole.origin - bmodel.origin; // relative origin //hole.v_angle = hole.angles - bmodel.angles; // relative angles //DP_QC_ASINACOSATANATAN2TAN //idea: Urre //darkplaces implementation: LordHavoc //constant definitions: float DEG2RAD = 0.0174532925199432957692369076848861271344287188854172545609719144; float RAD2DEG = 57.2957795130823208767981548141051703324054724665643215491602438612; float PI = 3.1415926535897932384626433832795028841971693993751058209749445923; //builtin definitions: float(float s) asin = #471; // returns angle in radians for a given sin() value, the result is in the range -PI*0.5 to PI*0.5 float(float c) acos = #472; // returns angle in radians for a given cos() value, the result is in the range 0 to PI float(float t) atan = #473; // returns angle in radians for a given tan() value, the result is in the range -PI*0.5 to PI*0.5 float(float c, float s) atan2 = #474; // returns angle in radians for a given cos() and sin() value pair, the result is in the range -PI to PI (this is identical to vectoyaw except it returns radians rather than degrees) float(float a) tan = #475; // returns tangent value (which is simply sin(a)/cos(a)) for the given angle in radians, the result is in the range -infinity to +infinity //description: //useful math functions for analyzing vectors, note that these all use angles in radians (just like the cos/sin functions) not degrees unlike makevectors/vectoyaw/vectoangles, so be sure to do the appropriate conversions (multiply by DEG2RAD or RAD2DEG as needed). //note: atan2 can take unnormalized vectors (just like vectoyaw), and the function was included only for completeness (more often you want vectoyaw or vectoangles), atan2(v_x,v_y) * RAD2DEG gives the same result as vectoyaw(v) //DP_QC_AUTOCVARS //idea: divVerent //darkplaces implementation: divVerent //description: //allows QC variables to be bound to cvars //(works for float, string, vector types) //example: // float autocvar_developer; // float autocvar_registered; // string autocvar__cl_name; //NOTE: copying a string-typed autocvar to another variable/field, and then //changing the cvar or returning from progs is UNDEFINED. Writing to autocvar //globals is UNDEFINED. Accessing autocvar globals after changing that cvar in //the same frame by any means other than cvar_set() from the same QC VM is //IMPLEMENTATION DEFINED (an implementation may either yield the previous, or //the current, value). Changing them via cvar_set() in the same QC VM //immediately must reflect on the autocvar globals. Whether autocvar globals, //after restoring a savegame, have the cvar's current value, or the original //value at time of saving, is UNDEFINED. Restoring a savegame however must not //restore the cvar values themselves. //In case the cvar does NOT exist, then it is automatically created with the //value of the autocvar initializer, if given. This is possible with e.g. //frikqcc and fteqcc the following way: // var float autocvar_whatever = 42; //If no initializer is given, the cvar will be initialized to a string //equivalent to the NULL value of the given data type, that is, the empty //string, 0, or '0 0 0'. However, when automatic cvar creation took place, a //warning is printed to the game console. //NOTE: to prevent an ambiguity with float names for vector types, autocvar //names MUST NOT end with _x, _y or _z! //DP_QC_CHANGEPITCH //idea: id Software //darkplaces implementation: id Software //field definitions: .float idealpitch; .float pitch_speed; //builtin definitions: void(entity ent) changepitch = #63; //description: //equivalent to changeyaw, ent is normally self. (this was a Q2 builtin) //DP_QC_COPYENTITY //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(entity from, entity to) copyentity = #400; //description: //copies all data in the entity to another entity. //DP_QC_CRC16 //idea: divVerent //darkplaces implementation: divVerent //Some hash function to build hash tables with. This has to be be the CRC-16-CCITT that is also required for the QuakeWorld download protocol. //When caseinsensitive is set, the CRC is calculated of the lower cased string. float(float caseinsensitive, string s, ...) crc16 = #494; //DP_QC_CVAR_DEFSTRING //idea: id Software (Doom3), LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: string(string s) cvar_defstring = #482; //description: //returns the default value of a cvar, as a tempstring. //DP_QC_CVAR_DESCRIPTION //idea: divVerent //DarkPlaces implementation: divVerent //builtin definitions: string(string name) cvar_description = #518; //description: //returns the description of a cvar //DP_QC_CVAR_STRING //idea: VorteX //DarkPlaces implementation: VorteX, LordHavoc //builtin definitions: string(string s) cvar_string = #448; //description: //returns the value of a cvar, as a tempstring. //DP_QC_CVAR_TYPE //idea: divVerent //DarkPlaces implementation: divVerent //builtin definitions: float(string name) cvar_type = #495; float CVAR_TYPEFLAG_EXISTS = 1; float CVAR_TYPEFLAG_SAVED = 2; float CVAR_TYPEFLAG_PRIVATE = 4; float CVAR_TYPEFLAG_ENGINE = 8; float CVAR_TYPEFLAG_HASDESCRIPTION = 16; float CVAR_TYPEFLAG_READONLY = 32; //DP_QC_DIGEST //idea: motorsep, Spike //DarkPlaces implementation: divVerent //builtin definitions: string(string digest, string data, ...) digest_hex = #639; //description: //returns a given hex digest of given data //the returned digest is always encoded in hexadecimal //only the "MD4" digest is always supported! //if the given digest is not supported, string_null is returned //the digest string is matched case sensitively, use "MD4", not "md4"! //DP_QC_DIGEST_SHA256 //idea: motorsep, Spike //DarkPlaces implementation: divVerent //description: //"SHA256" is also an allowed digest type //DP_QC_EDICT_NUM //idea: 515 //DarkPlaces implementation: LordHavoc //builtin definitions: entity(float entnum) edict_num = #459; float(entity ent) wasfreed = #353; // same as in EXT_CSQC extension //description: //edict_num returns the entity corresponding to a given number, this works even for freed entities, but you should call wasfreed(ent) to see if is currently active. //wasfreed returns whether an entity slot is currently free (entities that have never spawned are free, entities that have had remove called on them are also free). //DP_QC_ENTITYDATA //idea: KrimZon //darkplaces implementation: KrimZon //builtin definitions: float() numentityfields = #496; string(float fieldnum) entityfieldname = #497; float(float fieldnum) entityfieldtype = #498; string(float fieldnum, entity ent) getentityfieldstring = #499; float(float fieldnum, entity ent, string s) putentityfieldstring = #500; //constants: //Returned by entityfieldtype float FIELD_STRING = 1; float FIELD_FLOAT = 2; float FIELD_VECTOR = 3; float FIELD_ENTITY = 4; float FIELD_FUNCTION = 6; //description: //Versatile functions intended for storing data from specific entities between level changes, but can be customized for some kind of partial savegame. //WARNING: .entity fields cannot be saved and restored between map loads as they will leave dangling pointers. //numentityfields returns the number of entity fields. NOT offsets. Vectors comprise 4 fields: v, v_x, v_y and v_z. //entityfieldname returns the name as a string, eg. "origin" or "classname" or whatever. //entityfieldtype returns a value that the constants represent, but the field may be of another type in more exotic progs.dat formats or compilers. //getentityfieldstring returns data as would be written to a savegame, eg... "0.05" (float), "0 0 1" (vector), or "Hello World!" (string). Function names can also be returned. //putentityfieldstring puts the data returned by getentityfieldstring back into the entity. //DP_QC_ENTITYSTRING void(string s) loadfromdata = #529; void(string s) loadfromfile = #530; void(string s) callfunction = #605; void(float fh, entity e) writetofile = #606; float(string s) isfunction = #607; void(entity e, string s) parseentitydata = #608; //DP_QC_ETOS //idea: id Software //darkplaces implementation: id Software //builtin definitions: string(entity ent) etos = #65; //description: //prints "entity 1" or similar into a string. (this was a Q2 builtin) //DP_QC_EXTRESPONSEPACKET //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: string(void) getextresponse = #624; //description: //returns a string of the form "\"ipaddress:port\" data...", or the NULL string //if no packet was found. Packets can be queued into the client/server by //sending a packet starting with "\xFF\xFF\xFF\xFFextResponse " to the //listening port. //DP_QC_FINDCHAIN //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: entity(.string fld, string match) findchain = #402; //description: //similar to find() but returns a chain of entities like findradius. //DP_QC_FINDCHAIN_TOFIELD //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: entity(.string fld, float match, .entity tofield) findradius_tofield = #22; entity(.string fld, string match, .entity tofield) findchain_tofield = #402; entity(.string fld, float match, .entity tofield) findchainflags_tofield = #450; entity(.string fld, float match, .entity tofield) findchainfloat_tofield = #403; //description: //similar to findchain() etc, but stores the chain into .tofield instead of .chain //actually, the .entity tofield is an optional field of the the existing findchain* functions //DP_QC_FINDCHAINFLAGS //idea: Sajt //darkplaces implementation: LordHavoc //builtin definitions: entity(.float fld, float match) findchainflags = #450; //description: //similar to findflags() but returns a chain of entities like findradius. //DP_QC_FINDCHAINFLOAT //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: entity(.entity fld, entity match) findchainentity = #403; entity(.float fld, float match) findchainfloat = #403; //description: //similar to findentity()/findfloat() but returns a chain of entities like findradius. //DP_QC_FINDFLAGS //idea: Sajt //darkplaces implementation: LordHavoc //builtin definitions: entity(entity start, .float fld, float match) findflags = #449; //description: //finds an entity with the specified flag set in the field, similar to find() //DP_QC_FINDFLOAT //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: entity(entity start, .entity fld, entity match) findentity = #98; entity(entity start, .float fld, float match) findfloat = #98; //description: //finds an entity or float field value, similar to find(), but for entity and float fields. //DP_QC_FS_SEARCH //idea: Black //darkplaces implementation: Black //builtin definitions: float(string pattern, float caseinsensitive, float quiet) search_begin = #444; void(float handle) search_end = #445; float(float handle) search_getsize = #446; string(float handle, float num) search_getfilename = #447; //description: //search_begin performs a filename search with the specified pattern (for example "maps/*.bsp") and stores the results in a search slot (minimum of 128 supported by any engine with this extension), the other functions take this returned search slot number, be sure to search_free when done (they are also freed on progs reload). //search_end frees a search slot (also done at progs reload). //search_getsize returns how many filenames were found. //search_getfilename returns a filename from the search. //DP_QC_GETLIGHT //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: vector(vector org) getlight = #92; //description: //returns the lighting at the requested location (in color), 0-255 range (can exceed 255). //DP_QC_GETSURFACE //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: float(entity e, float s) getsurfacenumpoints = #434; vector(entity e, float s, float n) getsurfacepoint = #435; vector(entity e, float s) getsurfacenormal = #436; string(entity e, float s) getsurfacetexture = #437; float(entity e, vector p) getsurfacenearpoint = #438; vector(entity e, float s, vector p) getsurfaceclippedpoint = #439; //description: //functions to query surface information. //DP_QC_GETSURFACEPOINTATTRIBUTE //idea: BlackHC //darkplaces implementation: BlackHC // constants float SPA_POSITION = 0; float SPA_S_AXIS = 1; float SPA_T_AXIS = 2; float SPA_R_AXIS = 3; // same as SPA_NORMAL float SPA_TEXCOORDS0 = 4; float SPA_LIGHTMAP0_TEXCOORDS = 5; float SPA_LIGHTMAP0_COLOR = 6; //builtin definitions: vector(entity e, float s, float n, float a) getsurfacepointattribute = #486; //description: //function to query extended information about a point on a certain surface //DP_QC_GETSURFACETRIANGLE //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: float(entity e, float s) getsurfacenumtriangles = #628; vector(entity e, float s, float n) getsurfacetriangle = #629; //description: //function to query triangles of a surface //DP_QC_GETTAGINFO //idea: VorteX, LordHavoc //DarkPlaces implementation: VorteX //builtin definitions: float(entity ent, string tagname) gettagindex = #451; vector(entity ent, float tagindex) gettaginfo = #452; //description: //gettagindex returns the number of a tag on an entity, this number is the same as set by setattachment (in the .tag_index field), allowing the qc to save a little cpu time by keeping the number around if it wishes (this could already be done by calling setattachment and saving off the tag_index). //gettaginfo returns the origin of the tag in worldspace and sets v_forward, v_right, and v_up to the current orientation of the tag in worldspace, this automatically resolves all dependencies (attachments, including viewmodelforclient), this means you could fire a shot from a tag on a gun entity attached to the view for example. //DP_QC_GETTAGINFO_BONEPROPERTIES //idea: daemon //DarkPlaces implementation: divVerent //global definitions: float gettaginfo_parent; string gettaginfo_name; vector gettaginfo_offset; vector gettaginfo_forward; vector gettaginfo_right; vector gettaginfo_up; //description: //when this extension is present, gettaginfo fills in some globals with info about the bone that had been queried //gettaginfo_parent is set to the number of the parent bone, or 0 if it is a root bone //gettaginfo_name is set to the name of the bone whose index had been specified in gettaginfo //gettaginfo_offset, gettaginfo_forward, gettaginfo_right, gettaginfo_up contain the transformation matrix of the bone relative to its parent. Note that the matrix may contain a scaling component. //DP_QC_GETTIME //idea: tZork //darkplaces implementation: tZork, divVerent //constant definitions: float GETTIME_FRAMESTART = 0; // time of start of frame relative to an arbitrary point in time float GETTIME_REALTIME = 1; // current absolute time (OS specific) float GETTIME_HIRES = 2; // like REALTIME, but may reset between QC invocations and thus can be higher precision float GETTIME_UPTIME = 3; // time of start of frame since start of the engine //builtin definitions: float(float tmr) gettime = #519; //description: //some timers to query... //DP_QC_GETTIME_CDTRACK //idea: divVerent //darkplaces implementation: divVerent //constant definitions: float GETTIME_CDTRACK = 4; //description: //returns the playing time of the current cdtrack when passed to gettime() //see DP_END_GETSOUNDTIME for similar functionality but for entity sound channels //DP_QC_I18N //idea: divVerent //darkplaces implementation: divVerent //description: // //The engine supports translating by gettext compatible .po files. //progs.dat uses progs.dat..po //menu.dat uses menu.dat..po //csprogs.dat uses csprogs.dat..po // //To create a string that can be translated, define it as // string dotranslate_FILENOTFOUND = "File not found"; //Note: if the compiler does constant folding, this will only work if there is //no other "File not found" string in the progs! // //Alternatively, if using the Xonotic patched fteqcc compiler, you can simplify //this by using _("File not found") directly in the source code. // //The language is set by the "prvm_language" cvar: if prvm_language is set to //"de", it will read progs.dat.de.po for translating strings in progs.dat. // //If prvm_language is set to the special name "dump", progs.dat.pot will be //written, which is a translation template to be edited by filling out the //msgstr entries. //DP_QC_LOG //darkplaces implementation: divVerent //builtin definitions: float log(float f) = #532; //description: //logarithm //DP_QC_MINMAXBOUND //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: float(float a, float b, ...) min = #94; float(float a, float b, float c) min3 = #94; float(float a, float b, float c, float d) min4 = #94; float(float a, float b, float c, float d, float e) min5 = #94; float(float a, float b, float c, float d, float e, float f) min6 = #94; float(float a, float b, float c, float d, float e, float f, float g) min7 = #94; float(float a, float b, float c, float d, float e, float f, float g, float h) min8 = #94; float(float a, float b, ...) max = #95; float(float a, float b, float c) max3 = #95; float(float a, float b, float c, float d) max4 = #95; float(float a, float b, float c, float d, float e) max5 = #95; float(float a, float b, float c, float d, float e, float f) max6 = #95; float(float a, float b, float c, float d, float e, float f, float g) max7 = #95; float(float a, float b, float c, float d, float e, float f, float g, float h) max8 = #95; float(float minimum, float val, float maximum) bound = #96; //description: //min returns the lowest of all the supplied numbers. //max returns the highest of all the supplied numbers. //bound clamps the value to the range and returns it. //DP_QC_MULTIPLETEMPSTRINGS //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //this extension makes all builtins returning tempstrings (ftos for example) //cycle through a pool of multiple tempstrings (at least 16), allowing //multiple ftos results to be gathered before putting them to use, normal //quake only had 1 tempstring, causing many headaches. // //Note that for longer term storage (or compatibility on engines having //FRIK_FILE but not this extension) the FRIK_FILE extension's //strzone/strunzone builtins provide similar functionality (slower though). // //NOTE: this extension is superseded by DP_QC_UNLIMITEDTEMPSTRINGS //DP_QC_NUM_FOR_EDICT //idea: Blub\0 //darkplaces implementation: Blub\0 //Function to get the number of an entity - a clean way. float(entity num) num_for_edict = #512; //DP_QC_RANDOMVEC //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: vector() randomvec = #91; //description: //returns a vector of length < 1, much quicker version of this QC: do {v_x = random()*2-1;v_y = random()*2-1;v_z = random()*2-1;} while(vlen(v) > 1) //DP_QC_SINCOSSQRTPOW //idea: id Software, LordHavoc //darkplaces implementation: id Software, LordHavoc //builtin definitions: float(float val) sin = #60; float(float val) cos = #61; float(float val) sqrt = #62; float(float a, float b) pow = #97; //description: //useful math functions, sine of val, cosine of val, square root of val, and raise a to power b, respectively. //DP_QC_SPRINTF //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: string(string format, ...) sprintf = #627; //description: //you know sprintf :P //supported stuff: // % // optional: $ for the argument to format (the arg counter then is not increased) // flags: #0- + // optional: , *, or *$ for the field width (width is read before value and precision) // optional: ., .*, or .*$ for the precision (precision is read before value) // length modifiers: h for forcing a float, l for forcing an entity (by default, %d etc. cast a float to int), ll for forcing an int // conversions: // d takes a float if no length is specified or h is, and an entity if l is specified as length, and an int if ll is specified as length, and cast it to an int // i takes an entity if no length is specified or l is, and a float if h is specified as length, and an int if ll is specified as length, and cast it to an int // ouxXc take a float if no length is specified or h is, and an entity if l is specified as length, and an int if ll is specified as length, and cast it to an unsigned int // eEfFgG take a float if no length is specified or h is, and an entity if l is specified as length, and an int if ll is specified as length, and cast it to a double // s takes a string // vV takes a vector, and processes the three components as if it were a gG for all three components, separated by space // For conversions s and c, the flag # makes precision and width interpreted // as byte count, by default it is interpreted as character count in UTF-8 // enabled engines. No other conversions can create wide characters, and # // has another meaning in these. When in character count mode, color codes // are ignored. To get UTF-8 semantics WITHOUT color code parsing, use // the + flag. //DP_QC_STRFTIME //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: string(float uselocaltime, string format, ...) strftime = #478; //description: //provides the ability to get the local (in your timezone) or world (Universal Coordinated Time) time as a string using the formatting of your choice: //example: "%Y-%m-%d %H:%M:%S" (result looks like: 2007-02-08 01:03:15) //note: "%F %T" gives the same result as "%Y-%m-%d %H:%M:%S" (ISO 8601 date format and 24-hour time) //for more format codes please do a web search for strftime 3 and you should find the man(3) pages for this standard C function. // //practical uses: //changing day/night cycle (shops closing, monsters going on the prowl) in an RPG, for this you probably want to use s = strftime(TRUE, "%H");hour = stof(s); //printing current date/time for competitive multiplayer games, such as the beginning/end of each round in real world time. //activating eastereggs in singleplayer games on certain dates. // //note: some codes such as %x and %X use your locale settings and thus may not make sense to international users, it is not advisable to use these as the server and clients may be in different countries. //note: if you display local time to a player, it would be a good idea to note whether it is local time using a string like "%F %T (local)", and otherwise use "%F %T (UTC)". //note: be aware that if the game is saved and reloaded a week later the date and time will be different, so if activating eastereggs in a singleplayer game or something you may want to only check when a level is loaded and then keep the same easteregg state throughout the level so that the easteregg does not deactivate when reloading from a savegame (also be aware that precaches should not depend on such date/time code because reloading a savegame often scrambles the precaches if so!). //note: this function can return a NULL string (you can check for it with if (!s)) if the localtime/gmtime functions returned NULL in the engine code, such as if those functions don't work on this platform (consoles perhaps?), so be aware that this may return nothing. //DP_QC_STRINGCOLORFUNCTIONS //idea: Dresk //darkplaces implementation: Dresk //builtin definitions: float(string s) strlennocol = #476; // returns how many characters are in a string, minus color codes string(string s) strdecolorize = #477; // returns a string minus the color codes of the string provided //description: //provides additional functionality to strings by supporting functions that isolate and identify strings with color codes //DP_QC_STRING_CASE_FUNCTIONS //idea: Dresk //darkplaces implementation: LordHavoc / Dresk //builtin definitions: string(string s) strtolower = #480; // returns the passed in string in pure lowercase form string(string s) strtoupper = #481; // returns the passed in string in pure uppercase form //description: //provides simple string uppercase and lowercase functions //DP_QC_TOKENIZEBYSEPARATOR //idea: Electro, SavageX, LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: float(string s, string separator1, ...) tokenizebyseparator = #479; //description: //this function returns tokens separated by any of the supplied separator strings, example: //numnumbers = tokenizebyseparator("10.2.3.4", "."); //returns 4 and the tokens are "10" "2" "3" "4" //possibly useful for parsing IPv4 addresses (such as "1.2.3.4") and IPv6 addresses (such as "[1234:5678:9abc:def0:1234:5678:9abc:def0]:26000") //DP_QC_TOKENIZE_CONSOLE //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: float(string s) tokenize_console = #514; float(float i) argv_start_index = #515; float(float i) argv_end_index = #516; //description: //this function returns tokens separated just like the console does //also, functions are provided to get the index of the first and last character of each token in the original string //Passing negative values to them, or to argv, will be treated as indexes from the LAST token (like lists work in Perl). So argv(-1) will return the LAST token. //DP_QC_TRACEBOX //idea: id Software //darkplaces implementation: id Software //builtin definitions: void(vector v1, vector min, vector max, vector v2, float nomonsters, entity forent) tracebox = #90; //description: //similar to traceline but much more useful, traces a box of the size specified (technical note: in quake1 and halflife bsp maps the mins and maxs will be rounded up to one of the hull sizes, quake3 bsp does not have this problem, this is the case with normal moving entities as well). //DP_QC_TRACETOSS //idea: id Software //darkplaces implementation: id Software //builtin definitions: void(entity ent, entity ignore) tracetoss = #64; //description: //simulates movement of the entity as if it is MOVETYPE_TOSS and starting with it's current state (location, velocity, etc), returns relevant trace_ variables (trace_fraction is always 0, all other values are supported - trace_ent, trace_endpos, trace_plane_normal), does not actually alter the entity. //DP_QC_TRACE_MOVETYPE_HITMODEL //idea: LordHavoc //darkplaces implementation: LordHavoc //constant definitions: float MOVE_HITMODEL = 4; //description: //allows traces to hit alias models (not sprites!) instead of entity boxes, use as the nomonsters parameter to trace functions, note that you can hit invisible model entities (alpha < 0 or EF_NODRAW or model "", it only checks modelindex) //DP_QC_TRACE_MOVETYPE_WORLDONLY //idea: LordHavoc //darkplaces implementation: LordHavoc //constant definitions: float MOVE_WORLDONLY = 3; //description: //allows traces to hit only world (ignoring all entities, unlike MOVE_NOMONSTERS which hits all bmodels), use as the nomonsters parameter to trace functions //DP_QC_UNLIMITEDTEMPSTRINGS //idea: divVerent //darkplaces implementation: LordHavoc //description: //this extension alters Quake behavior such that instead of reusing a single //tempstring (or multiple) there are an unlimited number of tempstrings, which //are removed only when a QC function invoked by the engine returns, //eliminating almost all imaginable concerns with string handling in QuakeC. // //in short: //you can now use and abuse tempstrings as much as you like, you still have to //use strzone (FRIK_FILE) for permanent storage however. // // // //implementation notes for other engine coders: //these tempstrings are expected to be stored in a contiguous buffer in memory //which may be fixed size or controlled by a cvar, or automatically grown on //demand (in the case of DarkPlaces). // //this concept is similar to quake's Zone system, however these are all freed //when the QC interpreter returns. // //this is basically a poor man's garbage collection system for strings. //DP_QC_VECTOANGLES_WITH_ROLL //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: vector(vector forward, vector up) vectoangles2 = #51; // same number as vectoangles //description: //variant of vectoangles that takes an up vector to calculate roll angle (also uses this to calculate yaw correctly if the forward is straight up or straight down) //note: just like normal vectoangles you need to negate the pitch of the returned angles if you want to feed them to makevectors or assign to self.v_angle //DP_QC_VECTORVECTORS //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector dir) vectorvectors = #432; //description: //creates v_forward, v_right, and v_up vectors given a forward vector, similar to makevectors except it takes a forward direction vector instead of angles. //DP_QC_WHICHPACK //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: string(string filename) whichpack = #503; //description: //for files in a pak/pk3/whatever, returns the pack's file name in FRIK_FILE name space. //for physical files, returns "". //in case of error, returns string_null. //DP_QC_URI_ESCAPE //idea: divVerent //darkplaces implementation: divVerent //URI::Escape's functionality string(string in) uri_escape = #510; string(string in) uri_unescape = #511; //DP_QC_URI_GET //idea: divVerent //darkplaces implementation: divVerent //loads text from an URL into a string //returns 1 on success of initiation, 0 if there are too many concurrent //connections already or if the URL is invalid //the following callback will receive the data and MUST exist! // void(float id, float status, string data) URI_Get_Callback; //status is either // negative for an internal error, // 0 for success, or // the HTTP response code on server error (e.g. 404) //if 1 is returned by uri_get, the callback will be called in the future float(string url, float id) uri_get = #513; //DP_QC_URI_POST //idea: divVerent //darkplaces implementation: divVerent //loads text from an URL into a string after POSTing via HTTP //works like uri_get, but uri_post sends data with Content-Type: content_type to the server //and uri_post sends the string buffer buf, joined using the delimiter delim float(string url, float id, string content_type, string data) uri_post = #513; float(string url, float id, string content_type, string delim, float buf) uri_postbuf = #513; //DP_SKELETONOBJECTS //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //this extension indicates that FTE_CSQC_SKELETONOBJECTS functionality is available in server QC (as well as CSQC). //DP_SV_SPAWNFUNC_PREFIX //idea: divVerent //darkplaces implementation: divVerent //Make functions whose name start with spawnfunc_ take precedence as spawn function for loading entities. //Useful if you have a field ammo_shells (required in any Quake mod) but want to support spawn functions called ammo_shells (like in Q3A). //Optionally, you can declare a global "float require_spawnfunc_prefix;" which will require ANY spawn function to start with that prefix. //DP_QUAKE2_MODEL //idea: quake community //darkplaces implementation: LordHavoc //description: //shows that the engine supports Quake2 .md2 files. //DP_QUAKE2_SPRITE //idea: LordHavoc //darkplaces implementation: Elric //description: //shows that the engine supports Quake2 .sp2 files. //DP_QUAKE3_MAP //idea: quake community //darkplaces implementation: LordHavoc //description: //shows that the engine supports Quake3 .bsp files. //DP_QUAKE3_MODEL //idea: quake community //darkplaces implementation: LordHavoc //description: //shows that the engine supports Quake3 .md3 files. //DP_REGISTERCVAR //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: float(string name, string value) registercvar = #93; //description: //adds a new console cvar to the server console (in singleplayer this is the player's console), the cvar exists until the mod is unloaded or the game quits. //NOTE: DP_CON_SET is much better. //DP_SND_DIRECTIONLESSATTNNONE //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //make sounds with ATTN_NONE have no spatialization (enabling easy use as music sources). //DP_SND_FAKETRACKS //idea: requested //darkplaces implementation: Elric //description: //the engine plays sound/cdtracks/track001.wav instead of cd track 1 and so on if found, this allows games and mods to have music tracks without using ambientsound. //Note: also plays .ogg with DP_SND_OGGVORBIS extension. //DP_SND_SOUND7_WIP1 //DP_SND_SOUND7_WIP2 //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: void(entity e, float chan, string samp, float vol, float atten, float speed, float flags) sound7 = #8; float SOUNDFLAG_RELIABLE = 1; //description: //plays a sound, with some more flags //extensions to sound(): //- channel may be in the range from -128 to 127; channels -128 to 0 are "auto", // i.e. support multiple sounds at once, but cannot be stopped/restarted //- a value 0 in the speed parameter means no change; otherwise, it is a // percentage of playback speed ("pitch shifting"). 100 is normal pitch, 50 is // half speed, 200 is double speed, etc. (DP_SND_SOUND7_WIP2) //- the flag SOUNDFLAG_RELIABLE can be specified, which makes the sound send // to MSG_ALL (reliable) instead of MSG_BROADCAST (unreliable, default); // similarily, SOUNDFLAG_RELIABLE_TO_ONE sends to MSG_ONE //- channel 0 is controlled by snd_channel0volume; channel 1 and -1 by // snd_channel1volume, etc. (so, a channel shares the cvar with its respective // auto-channel); however, the mod MUST define snd_channel8volume and upwards // in default.cfg if they are to be used, as the engine does not create them // to not litter the cvar list //- this extension applies to CSQC as well; CSQC_Event_Sound will get speed and // flags as extra 7th and 8th argument //- WIP2 ideas: SOUNDFLAG_RELIABLE_TO_ONE, SOUNDFLAG_NOPHS, SOUNDFLAG_FORCELOOP //- NOTE: to check for this, ALSO OR a check with DP_SND_SOUND7 to also support // the finished extension once done //DP_SND_OGGVORBIS //idea: Transfusion //darkplaces implementation: Elric //description: //the engine supports loading Ogg Vorbis sound files. Use either the .ogg filename directly, or a .wav of the same name (will try to load the .wav first and then .ogg). //DP_SND_STEREOWAV //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //the engine supports stereo WAV files. (useful with DP_SND_DIRECTIONLESSATTNNONE for music) //DP_SND_GETSOUNDTIME //idea: VorteX //darkplaces implementation: VorteX //constant definitions: float(entity e, float channel) getsoundtime = #533; // get currently sound playing position on entity channel, -1 if not playing or error float(string sample) soundlength = #534; // returns length of sound sample in seconds, -1 on error (sound not precached, sound system not initialized etc.) //description: provides opportunity to query length of sound samples and realtime tracking of sound playing on entities (similar to DP_GETTIME_CDTRACK) //note: beware dedicated server not running sound engine at all, so in dedicated mode this builtins will not work in server progs //note also: menu progs not supporting getsoundtime() (will give a warning) since it has no sound playing on entities //examples of use: // - QC-driven looped sounds // - QC events when sound playing is finished // - toggleable ambientsounds // - subtitles //DP_VIDEO_DPV //idea: LordHavoc //darkplaces implementation: LordHavoc //console commands: // playvideo - start playing video // stopvideo - stops current video //description: indicated that engine support playing videos in DPV format //DP_VIDEO_SUBTITLES //idea: VorteX //darkplaces implementation: VorteX //cvars: // cl_video_subtitles - toggles subtitles showing // cl_video_subtitles_lines - how many lines to reserve for subtitles // cl_video_subtitles_textsize - font size //console commands: // playvideo - start playing video // stopvideo - stops current video //description: indicates that engine support subtitles on videos //subtitles stored in external text files, each video file has it's default subtitles file ( .dpsubs ) //example: for video/act1.dpv default subtitles file will be video/act1.dpsubs //also video could be played with custom subtitles file by utilizing second parm of playvideo command //syntax of .dpsubs files: each line in .dpsubs file defines 1 subtitle, there are three tokens: // "string" // start: subtitle start time in seconds // end: subtitle time-to-show in seconds, if 0 - subtitle will be showed until next subtitle is started, // if below 0 - show until next subtitles minus this number of seconds // text: subtitle text, color codes (Q3-style and ^xRGB) are allowed //example of subtitle file: // 3 0 "Vengeance! Vengeance for my eternity of suffering!" // 9 0 "Whelp! As if you knew what eternity was!" // 13 0 "Grovel before your true master." // 17 0 "Never!" // 18 7 "I'll hack you from crotch to gizzard and feed what's left of you to your brides..." //DP_SOLIDCORPSE //idea: LordHavoc //darkplaces implementation: LordHavoc //solid definitions: float SOLID_CORPSE = 5; //description: //the entity will not collide with SOLID_CORPSE and SOLID_SLIDEBOX entities (and likewise they will not collide with it), this is useful if you want dead bodies that are shootable but do not obstruct movement by players and monsters, note that if you traceline with a SOLID_SLIDEBOX entity as the ignoreent, it will ignore SOLID_CORPSE entities, this is desirable for visibility and movement traces, but not for bullets, for the traceline to hit SOLID_CORPSE you must temporarily force the player (or whatever) to SOLID_BBOX and then restore to SOLID_SLIDEBOX after the traceline. //DP_SPRITE32 //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //the engine supports .spr32 sprites. //DP_SV_BOTCLIENT //idea: LordHavoc //darkplaces implementation: LordHavoc //constants: float CLIENTTYPE_DISCONNECTED = 0; float CLIENTTYPE_REAL = 1; float CLIENTTYPE_BOT = 2; float CLIENTTYPE_NOTACLIENT = 3; //builtin definitions: entity() spawnclient = #454; // like spawn but for client slots (also calls relevant connect/spawn functions), returns world if no clients available float(entity clent) clienttype = #455; // returns one of the CLIENTTYPE_* constants //description: //spawns a client with no network connection, to allow bots to use client slots with no hacks. //How to use: /* // to spawn a bot local entity oldself; oldself = self; self = spawnclient(); if (!self) { bprint("Can not add bot, server full.\n"); self = oldself; return; } self.netname = "Yoyobot"; self.clientcolors = 12 * 16 + 4; // yellow (12) shirt and red (4) pants ClientConnect(); PutClientInServer(); self = oldself; // to remove all bots local entity head; head = find(world, classname, "player"); while (head) { if (clienttype(head) == CLIENTTYPE_BOT) dropclient(head); head = find(head, classname, "player"); } // to identify if a client is a bot (for example in PlayerPreThink) if (clienttype(self) == CLIENTTYPE_BOT) botthink(); */ //see also DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME DP_SV_DROPCLIENT //How it works: //creates a new client, calls SetNewParms and stores the parms, and returns the client. //this intentionally does not call SV_SendServerinfo to allow the QuakeC a chance to set the netname and clientcolors fields before actually spawning the bot by calling ClientConnect and PutClientInServer manually //on level change ClientConnect and PutClientInServer are called by the engine to spawn in the bot (this is why clienttype() exists to tell you on the next level what type of client this is). //parms work the same on bot clients as they do on real clients, and do carry from level to level //DP_SV_BOUNCEFACTOR //idea: divVerent //darkplaces implementation: divVerent //field definitions: .float bouncefactor; // velocity multiplier after a bounce .float bouncestop; // bounce stops if velocity to bounce plane is < bouncestop * gravity AFTER the bounce //description: //allows qc to customize MOVETYPE_BOUNCE a bit //DP_SV_CLIENTCAMERA //idea: LordHavoc, others //darkplaces implementation: Black //field definitions: .entity clientcamera; // override camera entity //description: //allows another entity to be the camera for a client, for example a remote camera, this is similar to sending svc_setview manually except that it also changes the network culling appropriately. //DP_SV_CLIENTCOLORS //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float clientcolors; // colors of the client (format: pants + shirt * 16) //description: //allows qc to read and modify the client colors associated with a client entity (not particularly useful on other entities), and automatically sends out any appropriate network updates if changed //DP_SV_CLIENTNAME //idea: LordHavoc //darkplaces implementation: LordHavoc //description: //allows qc to modify the client's .netname, and automatically sends out any appropriate network updates if changed //DP_SV_CUSTOMIZEENTITYFORCLIENT //idea: LordHavoc //darkplaces implementation: [515] and LordHavoc //field definitions: .float() customizeentityforclient; // self = this entity, other = client entity //description: //allows qc to modify an entity before it is sent to each client, the function returns TRUE if it should send, FALSE if it should not, and is fully capable of editing the entity's fields, this allows cloaked players to appear less transparent to their teammates, navigation markers to only show to their team, etc //tips on writing customize functions: //it is a good idea to return FALSE early in the function if possible to reduce cpu usage, because this function may be called many thousands of times per frame if there are many customized entities on a 64+ player server. //you are free to change anything in self, but please do not change any other entities (the results may be very inconsistent). //example ideas for use of this extension: //making icons over teammates' heads which are only visible to teammates. for exasmple: float() playericon_customizeentityforclient = {return self.owner.team == other.team;}; //making cloaked players more visible to their teammates than their enemies. for example: float() player_customizeentityforclient = {if (self.items & IT_CLOAKING) {if (self.team == other.team) self.alpha = 0.6;else self.alpha = 0.1;} return TRUE;}; //making explosion models that face the viewer (does not work well with chase_active). for example: float() explosion_customizeentityforclient = {self.angles = vectoangles(other.origin + other.view_ofs - self.origin);self.angles_x = 0 - self.angles_x;}; //implementation notes: //entity customization is done before per-client culling (visibility for instance) because the entity may be doing setorigin to display itself in different locations on different clients, may be altering its .modelindex, .effects and other fields important to culling, so customized entities increase cpu usage (non-customized entities can use all the early culling they want however, as they are not changing on a per client basis). //DP_SV_DISABLECLIENTPREDICTION //idea: LordHavoc, Mario //darkplaces implementation: LordHavoc, Mario //field definitions: .float disableclientprediction; //description: //By default, player entities are enabled for prediction by the engine if the //engine assumes the client can sensibly predict them. As the NQ and DarkPlaces //protocol does not network movetype, this in particular allows for client //prediction only if movetype == MOVETYPE_WALK. //Setting this field to 1 disables prediction in any case - this is useful when //the client cannot sensibly predict the server's idea of how the player moves //(common in case of pure serverside grappling hook or jetpack //implementations). //Setting this field to -1 forces prediction even if the server assumes the //client cannot predict the current movetype of a player entity (obviously, //this then requires matching client-side prediction code in CSQC, as the //engine's own client prediction will sure not handle these cases right due to //not knowing the serverside value of movetype). This is allowed in combination //with the following movetypes: // MOVETYPE_NONE (useful to have full QC control over movement) // MOVETYPE_WALK (redundant but harmless) // MOVETYPE_STEP // MOVETYPE_FLY (useful for spectators) // MOVETYPE_TOSS // MOVETYPE_NOCLIP (useful for spectators) // MOVETYPE_FLYMISSILE // MOVETYPE_BOUNCE // MOVETYPE_BOUNCEMISSILE // MOVETYPE_FLY_WORLDONLY (useful for spectators) //DP_SV_DISCARDABLEDEMO //idea: parasti //darkplaces implementation: parasti //field definitions: .float discardabledemo; //description: //when this field is set to a non-zero value on a player entity, a possibly recorded server-side demo for the player is discarded //Note that this extension only works if: // auto demos are enabled (the cvar sv_autodemo_perclient is set) // discarding demos is enabled (the cvar sv_autodemo_perclient_discardable is set) //DP_SV_DRAWONLYTOCLIENT //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .entity drawonlytoclient; //description: //the entity is only visible to the specified client. //DP_SV_DROPCLIENT //idea: FrikaC //darkplaces implementation: LordHavoc //builtin definitions: void(entity clent) dropclient = #453; //description: //causes the server to immediately drop the client, more reliable than stuffcmd(clent, "disconnect\n"); which could be intentionally ignored by the client engine //DP_SV_EFFECT //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, string modelname, float startframe, float endframe, float framerate) effect = #404; //SVC definitions: //float svc_effect = #52; // [vector] org [byte] modelindex [byte] startframe [byte] framecount [byte] framerate //float svc_effect2 = #53; // [vector] org [short] modelindex [byte] startframe [byte] framecount [byte] framerate //description: //clientside playback of simple custom sprite effects (explosion sprites, etc). //DP_SV_ENTITYCONTENTSTRANSITION //idea: Dresk //darkplaces implementation: Dresk //field definitions: .void(float nOriginalContents, float nNewContents) contentstransition; //description: //This field function, when provided, is triggered on an entity when the contents (ie. liquid / water / etc) is changed. //The first parameter provides the entities original contents, prior to the transition. The second parameter provides the new contents. //NOTE: If this field function is provided on an entity, the standard watersplash sound IS SUPPRESSED to allow for authors to create their own transition sounds. //DP_SV_MOVETYPESTEP_LANDEVENT //idea: Dresk //darkplaces implementation: Dresk //field definitions: .void(vector vImpactVelocity) movetypesteplandevent; //description: //This field function, when provided, is triggered on a MOVETYPE_STEP entity when it experiences "land event". // The standard engine behavior for this event is to play the sv_sound_land CVar sound. //The parameter provides the velocity of the entity at the time of the impact. The z value may therefore be used to calculate how "hard" the entity struck the surface. //NOTE: If this field function is provided on a MOVETYPE_STEP entity, the standard sv_sound_land sound IS SUPPRESSED to allow for authors to create their own feedback. //DP_SV_POINTSOUND //idea: Dresk //darkplaces implementation: Dresk //builtin definitions: void(vector origin, string sample, float volume, float attenuation) pointsound = #483; //description: //Similar to the standard QC sound function, this function takes an origin instead of an entity and omits the channel parameter. // This allows sounds to be played at arbitrary origins without spawning entities. //DP_SV_ONENTITYNOSPAWNFUNCTION //idea: Dresk //darkplaces implementation: Dresk //engine-called QC prototypes: //void() SV_OnEntityNoSpawnFunction; //description: // This function is called whenever an entity on the server has no spawn function, and therefore has no defined QC behavior. // You may as such dictate the behavior as to what happens to the entity. // To mimic the engine's default behavior, simply call remove(self). //DP_SV_ONENTITYPREPOSTSPAWNFUNCTION //idea: divVerent //darkplaces implementation: divVerent //engine-called QC prototypes: //void() SV_OnEntityPreSpawnFunction; //void() SV_OnEntityPostSpawnFunction; //description: // These functions are called BEFORE or AFTER an entity is spawned the regular way. // You may as such dictate the behavior as to what happens to the entity. // SV_OnEntityPreSpawnFunction is called before even looking for the spawn function, so you can even change its classname in there. If it remove()s the entity, the spawn function will not be looked for. // SV_OnEntityPostSpawnFunction is called ONLY after its spawn function or SV_OnEntityNoSpawnFunction was called, and skipped if the entity got removed by either. //DP_SV_MODELFLAGS_AS_EFFECTS //idea: LordHavoc, Dresk //darkplaces implementation: LordHavoc //field definitions: .float modelflags; //constant definitions: float EF_NOMODELFLAGS = 8388608; // ignore any effects in a model file and substitute your own float MF_ROCKET = 1; // leave a trail float MF_GRENADE = 2; // leave a trail float MF_GIB = 4; // leave a trail float MF_ROTATE = 8; // rotate (bonus items) float MF_TRACER = 16; // green split trail float MF_ZOMGIB = 32; // small blood trail float MF_TRACER2 = 64; // orange split trail float MF_TRACER3 = 128; // purple trail //description: //this extension allows model flags to be specified on entities so you can add a rocket trail and glow to any entity, etc. //setting any of these will override the flags the model already has, to disable the model's flags without supplying any of your own you must use EF_NOMODELFLAGS. // //silly example modification #1 to W_FireRocket in weapons.qc: //missile.effects = EF_NOMODELFLAGS; // rocket without a glow/fire trail //silly example modification #2 to W_FireRocket in weapons.qc: //missile.modelflags = MF_GIB; // leave a blood trail instead of glow/fire trail // //note: you can not combine multiple kinds of trail, only one of them will be active, you can combine MF_ROTATE and the other MF_ flags however, and using EF_NOMODELFLAGS along with these does no harm. // //note to engine coders: they are internally encoded in the protocol as extra EF_ flags (shift the values left 24 bits and send them in the protocol that way), so no protocol change was required (however 32bit effects is a protocol extension itself), within the engine they are referred to as EF_ for the 24bit shifted values. //DP_SV_NETADDRESS //idea: Dresk //darkplaces implementation: Dresk //field definitions: .string netaddress; //description: // provides the netaddress of the associated entity (ie. 127.0.0.1) and "null/botclient" if the netconnection of the entity is invalid //DP_SV_NODRAWTOCLIENT //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .entity nodrawtoclient; //description: //the entity is not visible to the specified client. //DP_SV_PING //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float ping; //description: //continuously updated field indicating client's ping (based on average of last 16 packet time differences). //DP_SV_PING_PACKETLOSS //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float ping_packetloss; .float ping_movementloss; //description: //continuously updated field indicating client's packet loss, and movement loss (i.e. packet loss affecting player movement). //DP_SV_POINTPARTICLES //idea: Spike //darkplaces implementation: LordHavoc //function definitions: float(string effectname) particleeffectnum = #335; // same as in CSQC void(entity ent, float effectnum, vector start, vector end) trailparticles = #336; // same as in CSQC void(float effectnum, vector org, vector vel, float howmany) pointparticles = #337; // same as in CSQC //SVC definitions: //float svc_trailparticles = 60; // [short] entnum [short] effectnum [vector] start [vector] end //float svc_pointparticles = 61; // [short] effectnum [vector] start [vector] velocity [short] count //float svc_pointparticles1 = 62; // [short] effectnum [vector] start, same as svc_pointparticles except velocity is zero and count is 1 //description: //provides the ability to spawn non-standard particle effects, typically these are defined in a particle effect information file such as effectinfo.txt in darkplaces. //this is a port of particle effect features from clientside QC (EXT_CSQC) to server QC, as these effects are potentially useful to all games even if they do not make use of EXT_CSQC. //warning: server must have same order of effects in effectinfo.txt as client does or the numbers would not match up, except for standard quake effects which are always the same numbers. //DP_SV_PUNCHVECTOR //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .vector punchvector; //description: //offsets client view in worldspace, similar to view_ofs but all 3 components are used and are sent with at least 8 bits of fraction, this allows the view to be kicked around by damage or hard landings or whatever else, note that unlike punchangle this is not faded over time, it is up to the mod to fade it (see also DP_SV_PLAYERPHYSICS). //DP_SV_PLAYERPHYSICS //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .vector movement; //cvar definitions: //"sv_playerphysicsqc" (0/1, default 1, allows user to disable qc player physics) //engine-called QC prototypes: //void() SV_PlayerPhysics; //description: //.movement vector contains the movement input from the player, allowing QC to do as it wishs with the input, and SV_PlayerPhysics will completely replace the player physics if present (works for all MOVETYPE's), see darkplaces mod source for example of this function (in playermovement.qc, adds HalfLife ladders support, as well as acceleration/deceleration while airborn (rather than the quake sudden-stop while airborn), and simplifies the physics a bit) //DP_PHYSICS_ODE //idea: LordHavoc //darkplaces implementation: LordHavoc //globals: //new movetypes: const float MOVETYPE_PHYSICS = 32; // need to be set before any physics_* builtins applied //new solid types (deprecated): const float SOLID_PHYSICS_BOX = 32; const float SOLID_PHYSICS_SPHERE = 33; const float SOLID_PHYSICS_CAPSULE = 34; const float SOLID_PHYSICS_TRIMESH = 35; const float SOLID_PHYSICS_CYLINDER = 36; //geometry types: const float GEOMTYPE_NONE = -1; // entity will be entirely skipped by ODE const float GEOMTYPE_SOLID = 0; // geometry type will be set based on .solid field const float GEOMTYPE_BOX = 1; // entity bound box const float GEOMTYPE_SPHERE = 2; // sphere with radius picked from x axis of entity bound box const float GEOMTYPE_CAPSULE = 3; // with leading axis automatically determined from longest one, radius is picked as minimal of the rest 2 axes const float GEOMTYPE_TRIMESH = 4; // triangle mesh const float GEOMTYPE_CYLINDER = 5; // like capsule but not capped // note that ODE's builtin cylinder support is experimental, somewhat bugged and unfinished (no cylinder-cylinder collision) // to use properly working cylinder should build ODE with LIBCCD extension const float GEOMTYPE_CAPSULE_X = 6; // capsule with fixed leading axis const float GEOMTYPE_CAPSULE_Y = 7; const float GEOMTYPE_CAPSULE_Z = 8; const float GEOMTYPE_CYLINDER_X = 9; // cylinder with fixed leading axis const float GEOMTYPE_CYLINDER_Y = 10; const float GEOMTYPE_CYLINDER_Z = 11; //joint types: const float JOINTTYPE_NONE = 0; const float JOINTTYPE_POINT = 1; const float JOINTTYPE_HINGE = 2; const float JOINTTYPE_SLIDER = 3; const float JOINTTYPE_UNIVERSAL = 4; const float JOINTTYPE_HINGE2 = 5; const float JOINTTYPE_FIXED = -1; //force types: const float FORCETYPE_NONE = 0; const float FORCETYPE_FORCE = 1; // applied at center of mass const float FORCETYPE_FORCEATPOS = 2; const float FORCETYPE_TORQUE = 3; // common joint properties: // .entity aiment; // connected objects // .entity enemy; // connected objects, forces // .vector movedir; // for a spring: // movedir_x = spring constant (force multiplier, must be > 0) // movedir_y = spring dampening constant to prevent oscillation (must be > 0) // movedir_z = spring stop position (+/-) // for a motor: // movedir_x = desired motor velocity // movedir_y = -1 * max motor force to use // movedir_z = stop position (+/-), set to 0 for no stop // note that ODE does not support both in one anyway // for a force: // force vector to apply //field definitions: .float geomtype; // see GEOMTYPE_*, a more correct way to set collision shape, allows to set SOLID_CORPSE and trimesh collisions .float maxcontacts; // maximum number of contacts to make for this object, lesser = faster (but setting it too low will could make object pass though walls), default is 16, maximum is 32 .float mass; // ODE mass, standart value is 1 .vector massofs; // offsets a mass center out of object center, if not set a center of model bounds is used .float friction; // a friction of object, get multiplied by second objects's friction on contact .float bouncefactor; .float bouncestop; .float jointtype; // type of joint .float forcetype; // type of force .float erp; // error restitution parameter, makes ODE solver attempt to fix errors in contacts, // bringing together 2 joints or fixing object being stuch in other object, // a value of 0.1 will fix slightly, a value of 1.0 attempts to fix whole error in one frame // use with care as high values makes system unstable and likely to explode //builtin definitions: void(entity e, float physics_enabled) physics_enable = #540; // enable or disable physics on object void(entity e, vector force, vector force_pos) physics_addforce = #541; // deprecated, apply a force from certain origin, length of force vector is power of force void(entity e, vector torque) physics_addtorque = #542; // deprecated, add relative torque //description: provides Open Dynamics Engine support, requires extenal dll to be present or engine compiled with statical link option //be sure to checkextension for it to know if library is loaded and ready, also to enable physics set "physics_ode" cvar to 1 //note: this extension is highly experimental and may be unstable //DP_SV_PRINT //idea: id Software (QuakeWorld Server) //darkplaces implementation: Black, LordHavoc void(string s, ...) print = #339; // same number as in EXT_CSQC //description: //this is identical to dprint except that it always prints regardless of the developer cvar. //DP_SV_PRECACHEANYTIME //idea: id Software (Quake2) //darkplaces implementation: LordHavoc //description: //this extension allows precache_model and precache_sound (and any variants) to be used during the game (with automatic messages to clients to precache the new model/sound indices), also setmodel/sound/ambientsound can be called without precaching first (they will cause an automatic precache). //DP_SV_QCSTATUS //idea: divVerent //darkplaces implementation: divVerent //1. A global variable string worldstatus; //Its content is set as "qcstatus" field in the rules. //It may be at most 255 characters, and must not contain newlines or backslashes. //2. A per-client field .string clientstatus; //It is sent instead of the "frags" in status responses. //It should always be set in a way so that stof(player.clientstatus) is a meaningful score value. Other info may be appended. If used this way, the cvar sv_status_use_qcstatus may be set to 1, and then this value will replace the frags in "status". //Currently, qstat does not support this and will not show player info if used and set to anything other than ftos(some integer). //DP_SV_ROTATINGBMODEL //idea: id Software //darkplaces implementation: LordHavoc //description: //this extension merely indicates that MOVETYPE_PUSH supports avelocity, allowing rotating brush models to be created, they rotate around their origin (needs rotation supporting qbsp/light utilities because id ones expected bmodel entity origins to be '0 0 0', recommend setting "origin" key in the entity fields in the map before compiling, there may be other methods depending on your qbsp, most are more complicated however). //tip: level designers can create a func_wall with an origin, and avelocity (for example "avelocity" "0 90 0"), and "nextthink" "99999999" to make a rotating bmodel without any qc modifications, such entities will be solid in stock quake but will not rotate) //DP_SV_SETCOLOR //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(entity ent, float colors) setcolor = #401; //engine called QC functions (optional): //void(float color) SV_ChangeTeam; //description: //setcolor sets the color on a client and updates internal color information accordingly (equivalent to stuffing a "color" command but immediate) //SV_ChangeTeam is called by the engine whenever a "color" command is recieved, it may decide to do anything it pleases with the color passed by the client, including rejecting it (by doing nothing), or calling setcolor to apply it, preventing team changes is one use for this. //the color format is pants + shirt * 16 (0-255 potentially) //DP_SV_SLOWMO //idea: LordHavoc //darkplaces implementation: LordHavoc //cvars: //"slowmo" (0+, default 1) //description: //sets the time scale of the server, mainly intended for use in singleplayer by the player, however potentially useful for mods, so here's an extension for it. //range is 0 to infinite, recommended values to try are 0.1 (very slow, 10% speed), 1 (normal speed), 5 (500% speed). //DP_SV_WRITEPICTURE //idea: divVerent //darkplaces implementation: divVerent //builtin definitions: void(float to, string s, float sz) WritePicture = #501; //description: //writes a picture to the data stream so CSQC can read it using ReadPicture, which has the definition // string(void) ReadPicture = #501; //The picture data is sent as at most sz bytes, by compressing to low quality //JPEG. The data being sent will be equivalent to: // WriteString(to, s); // WriteShort(to, imagesize); // for(i = 0; i < imagesize; ++i) // WriteByte(to, [the i-th byte of the compressed JPEG image]); //DP_SV_WRITEUNTERMINATEDSTRING //idea: FrikaC //darkplaces implementation: Sajt //builtin definitions: void(float to, string s) WriteUnterminatedString = #456; //description: //like WriteString, but does not write a terminating 0 after the string. This means you can include things like a player's netname in the middle of a string sent over the network. Just be sure to end it up with either a call to WriteString (which includes the trailing 0) or WriteByte(0) to terminate it yourself. //A historical note: this extension was suggested by FrikaC years ago, more recently Shadowalker has been badmouthing LordHavoc and Spike for stealing 'his' extension writestring2 which does exactly the same thing but uses a different builtin number and name and extension string, this argument hinges on the idea that it was his idea in the first place, which is incorrect as FrikaC first suggested it and used a rough equivalent of it in his FrikBot mod years ago involving WriteByte calls on each character. //DP_TE_BLOOD //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, vector velocity, float howmany) te_blood = #405; //temp entity definitions: float TE_BLOOD = 50; //protocol: //vector origin //byte xvelocity (-128 to +127) //byte yvelocity (-128 to +127) //byte zvelocity (-128 to +127) //byte count (0 to 255, how much blood) //description: //creates a blood effect. //DP_TE_BLOODSHOWER //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector mincorner, vector maxcorner, float explosionspeed, float howmany) te_bloodshower = #406; //temp entity definitions: //float TE_BLOODSHOWER = 52; //protocol: //vector mins (minimum corner of the cube) //vector maxs (maximum corner of the cube) //coord explosionspeed (velocity of blood particles flying out of the center) //short count (number of blood particles) //description: //creates an exploding shower of blood, for making gibbings more convincing. //DP_TE_CUSTOMFLASH //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, float radius, float lifetime, vector color) te_customflash = #417; //temp entity definitions: //float TE_CUSTOMFLASH = 73; //protocol: //vector origin //byte radius ((MSG_ReadByte() + 1) * 8, meaning 8-2048 unit radius) //byte lifetime ((MSG_ReadByte() + 1) / 256.0, meaning approximately 0-1 second lifetime) //byte red (0.0 to 1.0 converted to 0-255) //byte green (0.0 to 1.0 converted to 0-255) //byte blue (0.0 to 1.0 converted to 0-255) //description: //creates a customized light flash. //DP_TE_EXPLOSIONRGB //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, vector color) te_explosionrgb = #407; //temp entity definitions: //float TE_EXPLOSIONRGB = 53; //protocol: //vector origin //byte red (0.0 to 1.0 converted to 0 to 255) //byte green (0.0 to 1.0 converted to 0 to 255) //byte blue (0.0 to 1.0 converted to 0 to 255) //description: //creates a colored explosion effect. //DP_TE_FLAMEJET //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, vector vel, float howmany) te_flamejet = #457; //temp entity definitions: //float TE_FLAMEJET = 74; //protocol: //vector origin //vector velocity //byte count (0 to 255, how many flame particles) //description: //creates a single puff of flame particles. (not very useful really) //DP_TE_PARTICLECUBE //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter) te_particlecube = #408; //temp entity definitions: //float TE_PARTICLECUBE = 54; //protocol: //vector mins (minimum corner of the cube) //vector maxs (maximum corner of the cube) //vector velocity //short count //byte color (palette color) //byte gravity (TRUE or FALSE, FIXME should this be a scaler instead?) //coord randomvel (how much to jitter the velocity) //description: //creates a cloud of particles, useful for forcefields but quite customizable. //DP_TE_PARTICLERAIN //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlerain = #409; //temp entity definitions: //float TE_PARTICLERAIN = 55; //protocol: //vector mins (minimum corner of the cube) //vector maxs (maximum corner of the cube) //vector velocity (velocity of particles) //short count (number of particles) //byte color (8bit palette color) //description: //creates a shower of rain, the rain will appear either at the top (if falling down) or bottom (if falling up) of the cube. //DP_TE_PARTICLESNOW //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector mincorner, vector maxcorner, vector vel, float howmany, float color) te_particlesnow = #410; //temp entity definitions: //float TE_PARTICLERAIN = 56; //protocol: //vector mins (minimum corner of the cube) //vector maxs (maximum corner of the cube) //vector velocity (velocity of particles) //short count (number of particles) //byte color (8bit palette color) //description: //creates a shower of snow, the snow will appear either at the top (if falling down) or bottom (if falling up) of the cube, low velocities are advisable for convincing snow. //DP_TE_PLASMABURN //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org) te_plasmaburn = #433; //temp entity definitions: //float TE_PLASMABURN = 75; //protocol: //vector origin //description: //creates a small light flash (radius 200, time 0.2) and marks the walls. //DP_TE_QUADEFFECTS1 //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org) te_gunshotquad = #412; void(vector org) te_spikequad = #413; void(vector org) te_superspikequad = #414; void(vector org) te_explosionquad = #415; //temp entity definitions: //float TE_GUNSHOTQUAD = 57; // [vector] origin //float TE_SPIKEQUAD = 58; // [vector] origin //float TE_SUPERSPIKEQUAD = 59; // [vector] origin //float TE_EXPLOSIONQUAD = 70; // [vector] origin //protocol: //vector origin //description: //all of these just take a location, and are equivalent in function (but not appearance :) to the original TE_GUNSHOT, etc. //DP_TE_SMALLFLASH //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org) te_smallflash = #416; //temp entity definitions: //float TE_SMALLFLASH = 72; //protocol: //vector origin //description: //creates a small light flash (radius 200, time 0.2). //DP_TE_SPARK //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org, vector vel, float howmany) te_spark = #411; //temp entity definitions: //float TE_SPARK = 51; //protocol: //vector origin //byte xvelocity (-128 to 127) //byte yvelocity (-128 to 127) //byte zvelocity (-128 to 127) //byte count (number of sparks) //description: //creates a shower of sparks and a smoke puff. //DP_TE_STANDARDEFFECTBUILTINS //idea: LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: void(vector org) te_gunshot = #418; void(vector org) te_spike = #419; void(vector org) te_superspike = #420; void(vector org) te_explosion = #421; void(vector org) te_tarexplosion = #422; void(vector org) te_wizspike = #423; void(vector org) te_knightspike = #424; void(vector org) te_lavasplash = #425; void(vector org) te_teleport = #426; void(vector org, float color, float colorlength) te_explosion2 = #427; void(entity own, vector start, vector end) te_lightning1 = #428; void(entity own, vector start, vector end) te_lightning2 = #429; void(entity own, vector start, vector end) te_lightning3 = #430; void(entity own, vector start, vector end) te_beam = #431; //description: //to make life easier on mod coders. //DP_TRACE_HITCONTENTSMASK_SURFACEINFO //idea: LordHavoc //darkplaces implementation: LordHavoc //globals: .float dphitcontentsmask; // if non-zero on the entity passed to traceline/tracebox/tracetoss this will override the normal collidable contents rules and instead hit these contents values (for example AI can use tracelines that hit DONOTENTER if it wants to, by simply changing this field on the entity passed to traceline), this affects normal movement as well as trace calls float trace_dpstartcontents; // DPCONTENTS_ value at start position of trace float trace_dphitcontents; // DPCONTENTS_ value of impacted surface (not contents at impact point, just contents of the surface that was hit) float trace_dphitq3surfaceflags; // Q3SURFACEFLAG_ value of impacted surface string trace_dphittexturename; // texture name of impacted surface //constants: float DPCONTENTS_SOLID = 1; // hit a bmodel, not a bounding box float DPCONTENTS_WATER = 2; float DPCONTENTS_SLIME = 4; float DPCONTENTS_LAVA = 8; float DPCONTENTS_SKY = 16; float DPCONTENTS_BODY = 32; // hit a bounding box, not a bmodel float DPCONTENTS_CORPSE = 64; // hit a SOLID_CORPSE entity float DPCONTENTS_NODROP = 128; // an area where backpacks should not spawn float DPCONTENTS_PLAYERCLIP = 256; // blocks player movement float DPCONTENTS_MONSTERCLIP = 512; // blocks monster movement float DPCONTENTS_DONOTENTER = 1024; // AI hint brush float DPCONTENTS_LIQUIDSMASK = 14; // WATER | SLIME | LAVA float DPCONTENTS_BOTCLIP = 2048; // AI hint brush float DPCONTENTS_OPAQUE = 4096; // only fully opaque brushes get this (may be useful for line of sight checks) float Q3SURFACEFLAG_NODAMAGE = 1; float Q3SURFACEFLAG_SLICK = 2; // low friction surface float Q3SURFACEFLAG_SKY = 4; // sky surface (also has NOIMPACT and NOMARKS set) float Q3SURFACEFLAG_LADDER = 8; // climbable surface float Q3SURFACEFLAG_NOIMPACT = 16; // projectiles should remove themselves on impact (this is set on sky) float Q3SURFACEFLAG_NOMARKS = 32; // projectiles should not leave marks, such as decals (this is set on sky) float Q3SURFACEFLAG_FLESH = 64; // projectiles should do a fleshy effect (blood?) on impact float Q3SURFACEFLAG_NODRAW = 128; // compiler hint (not important to qc) //float Q3SURFACEFLAG_HINT = 256; // compiler hint (not important to qc) //float Q3SURFACEFLAG_SKIP = 512; // compiler hint (not important to qc) //float Q3SURFACEFLAG_NOLIGHTMAP = 1024; // compiler hint (not important to qc) //float Q3SURFACEFLAG_POINTLIGHT = 2048; // compiler hint (not important to qc) float Q3SURFACEFLAG_METALSTEPS = 4096; // walking on this surface should make metal step sounds float Q3SURFACEFLAG_NOSTEPS = 8192; // walking on this surface should not make footstep sounds float Q3SURFACEFLAG_NONSOLID = 16384; // compiler hint (not important to qc) //float Q3SURFACEFLAG_LIGHTFILTER = 32768; // compiler hint (not important to qc) //float Q3SURFACEFLAG_ALPHASHADOW = 65536; // compiler hint (not important to qc) //float Q3SURFACEFLAG_NODLIGHT = 131072; // compiler hint (not important to qc) //float Q3SURFACEFLAG_DUST = 262144; // translucent 'light beam' effect (not important to qc) //description: //adds additional information after a traceline/tracebox/tracetoss call. //also (very important) sets trace_* globals before calling .touch functions, //this allows them to inspect the nature of the collision (for example //determining if a projectile hit sky), clears trace_* variables for the other //object in a touch event (that is to say, a projectile moving will see the //trace results in its .touch function, but the player it hit will see very //little information in the trace_ variables as it was not moving at the time) //DP_VIEWZOOM //idea: LordHavoc //darkplaces implementation: LordHavoc //field definitions: .float viewzoom; //description: //scales fov and sensitivity of player, valid range is 0 to 1 (intended for sniper rifle zooming, and such) //EXT_BITSHIFT //idea: Spike //darkplaces implementation: [515] //builtin definitions: float(float number, float quantity) bitshift = #218; //description: //multiplies number by a power of 2 corresponding to quantity (0 = *1, 1 = *2, 2 = *4, 3 = *8, -1 = /2, -2 = /4x, etc), and rounds down (due to integer math) like other bit operations do (& and | and the like). //note: it is faster to use multiply if you are shifting by a constant, avoiding the quakec function call cost, however that does not do a floor for you. //FRIK_FILE //idea: FrikaC //darkplaces implementation: LordHavoc //builtin definitions: float(string s) stof = #81; // get numerical value from a string float(string filename, float mode) fopen = #110; // opens a file inside quake/gamedir/data/ (mode is FILE_READ, FILE_APPEND, or FILE_WRITE), returns fhandle >= 0 if successful, or fhandle < 0 if unable to open file for any reason void(float fhandle) fclose = #111; // closes a file string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring void(float fhandle, string s, ...) fputs = #113; // writes a line of text to the end of the file float(string s) strlen = #114; // returns how many characters are in a string string(string s1, string s2, ...) strcat = #115; // concatenates two or more strings (for example "abc", "def" would return "abcdef") and returns as a tempstring string(string s, float start, float length) substring = #116; // returns a section of a string as a tempstring - see FTE_STRINGS for enhanced version vector(string s) stov = #117; // returns vector value from a string string(string s, ...) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to keep around a tempstring for longer periods of time (tempstrings are replaced often) void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again or it may crash!!!) //constants: float FILE_READ = 0; float FILE_APPEND = 1; float FILE_WRITE = 2; //cvars: //pr_zone_min_strings : default 64 (64k), min 64 (64k), max 8192 (8mb) //description: //provides text file access functions and string manipulation functions, note that you may want to set pr_zone_min_strings in the worldspawn function if 64k is not enough string zone space. // //NOTE: strzone functionality is partially superseded by //DP_QC_UNLIMITEDTEMPSTRINGS when longterm storage is not needed //NOTE: substring is upgraded by FTE_STRINGS extension with negative start/length handling identical to php 5.2.0 //FTE_CSQC_SKELETONOBJECTS //idea: Spike, LordHavoc //darkplaces implementation: LordHavoc //builtin definitions: // all skeleton numbers are 1-based (0 being no skeleton) // all bone numbers are 1-based (0 being invalid) float(float modlindex) skel_create = #263; // create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex, as the skeleton uses the hierarchy from the model. float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure float(float skel) skel_get_numbones = #265; // returns how many bones exist in the created skeleton, 0 if skeleton does not exist string(float skel, float bonenum) skel_get_bonename = #266; // returns name of bone (as a tempstring), "" if invalid bonenum (< 1 for example) or skeleton does not exist float(float skel, float bonenum) skel_get_boneparent = #267; // returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this) float(float skel, string tagname) skel_find_bone = #268; // get number of bone with specified name, 0 on failure, bonenum (1-based) on success, same as using gettagindex but takes modelindex instead of entity vector(float skel, float bonenum) skel_get_bonerel = #269; // get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone) vector(float skel, float bonenum) skel_get_boneabs = #270; // get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity) void(float skel, float bonenum, vector org) skel_set_bone = #271; // set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) void(float skel, float bonenum, vector org) skel_mul_bone = #272; // transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone) void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones) void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse void(float skel) skel_delete = #275; // deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work) float(float modlindex, string framename) frameforname = #276; // finds number of a specified frame in the animation, returns -1 if no match found float(float modlindex, float framenum) frameduration = #277; // returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0. //fields: .float skeletonindex; // active skeleton overriding standard animation on model .float frame; // primary framegroup animation (strength = 1 - lerpfrac - lerpfrac3 - lerpfrac4) .float frame2; // secondary framegroup animation (strength = lerpfrac) .float frame3; // tertiary framegroup animation (strength = lerpfrac3) .float frame4; // quaternary framegroup animation (strength = lerpfrac4) .float lerpfrac; // strength of framegroup blend .float lerpfrac3; // strength of framegroup blend .float lerpfrac4; // strength of framegroup blend .float frame1time; // start time of framegroup animation .float frame2time; // start time of framegroup animation .float frame3time; // start time of framegroup animation .float frame4time; // start time of framegroup animation //description: //this extension provides a way to do complex skeletal animation on an entity. // //see also DP_SKELETONOBJECTS (this extension implemented on server as well as client) // //notes: //each model contains its own skeleton, reusing a skeleton with incompatible models will yield garbage (or not render). //each model contains its own animation data, you can use animations from other model files (for example saving out all character animations as separate model files). //if an engine supports loading an animation-only file format such as .md5anim in FTEQW, it can be used to animate any model with a compatible skeleton. //proper use of this extension may require understanding matrix transforms (v_forward, v_right, v_up, origin), and you must keep in mind that v_right is negative for this purpose. // //features include: //multiple animations blended together. //animating a model with animations from another model with a compatible skeleton. //restricting animation blends to certain bones of a model - for example independent animation of legs, torso, head. //custom bone controllers - for example making eyes track a target location. // // // //example code follows... // //this helper function lets you identify (by parentage) what group a bone //belongs to - for example "torso", "leftarm", would return 1 ("torso") for //all children of the bone named "torso", unless they are children of //"leftarm" (which is a child of "torso") which would return 2 instead... float(float skel, float bonenum, string g1, string g2, string g3, string g4, string g5, string g6) example_skel_findbonegroup = { local string bonename; while (bonenum >= 0) { bonename = skel_get_bonename(skel, bonenum); if (bonename == g1) return 1; if (bonename == g2) return 2; if (bonename == g3) return 3; if (bonename == g4) return 4; if (bonename == g5) return 5; if (bonename == g6) return 6; bonenum = skel_get_boneparent(skel, bonenum); } return 0; }; // create a skeletonindex for our player using current modelindex void() example_skel_player_setup = { self.skeletonindex = skel_create(self.modelindex); }; // setup bones of skeleton based on an animation // note: animmodelindex can be a different model than self.modelindex void(float animmodelindex, float framegroup, float framegroupstarttime) example_skel_player_update_begin = { // start with our standard animation self.frame = framegroup; self.frame2 = 0; self.frame3 = 0; self.frame4 = 0; self.frame1time = framegroupstarttime; self.frame2time = 0; self.frame3time = 0; self.frame4time = 0; self.lerpfrac = 0; self.lerpfrac3 = 0; self.lerpfrac4 = 0; skel_build(self.skeletonindex, self, animmodelindex, 0, 0, 100000); }; // apply a different framegroup animation to bones with a specified parent void(float animmodelindex, float framegroup, float framegroupstarttime, float blendalpha, string groupbonename, string excludegroupname1, string excludegroupname2) example_skel_player_update_applyoverride = { local float bonenum; local float numbones; self.frame = framegroup; self.frame2 = 0; self.frame3 = 0; self.frame4 = 0; self.frame1time = framegroupstarttime; self.frame2time = 0; self.frame3time = 0; self.frame4time = 0; self.lerpfrac = 0; self.lerpfrac3 = 0; self.lerpfrac4 = 0; bonenum = 0; numbones = skel_get_numbones(self.skeletonindex); while (bonenum < numbones) { if (example_skel_findbonegroup(self.skeletonindex, bonenum, groupbonename, excludegroupname1, excludegroupname2, "", "", "") == 1) skel_build(self.skeletonindex, self, animmodelindex, 1 - blendalpha, bonenum, bonenum + 1); bonenum = bonenum + 1; } }; // make eyes point at a target location, be sure v_forward, v_right, v_up are set correctly before calling void(vector eyetarget, string bonename) example_skel_player_update_eyetarget = { local float bonenum; local vector ang; local vector oldforward, oldright, oldup; local vector relforward, relright, relup, relorg; local vector boneforward, boneright, boneup, boneorg; local vector parentforward, parentright, parentup, parentorg; local vector u, v; local vector modeleyetarget; bonenum = skel_find_bone(self.skeletonindex, bonename) - 1; if (bonenum < 0) return; oldforward = v_forward; oldright = v_right; oldup = v_up; v = eyetarget - self.origin; modeleyetarget_x = v * v_forward; modeleyetarget_y = 0-v * v_right; modeleyetarget_z = v * v_up; // this is an eyeball, make it point at the target location // first get all the data we can... relorg = skel_get_bonerel(self.skeletonindex, bonenum); relforward = v_forward; relright = v_right; relup = v_up; boneorg = skel_get_boneabs(self.skeletonindex, bonenum); boneforward = v_forward; boneright = v_right; boneup = v_up; parentorg = skel_get_boneabs(self.skeletonindex, skel_get_boneparent(self.skeletonindex, bonenum)); parentforward = v_forward; parentright = v_right; parentup = v_up; // get the vector from the eyeball to the target u = modeleyetarget - boneorg; // now transform it inversely by the parent matrix to produce new rel vectors v_x = u * parentforward; v_y = u * parentright; v_z = u * parentup; ang = vectoangles2(v, relup); ang_x = 0 - ang_x; makevectors(ang); // set the relative bone matrix skel_set_bone(self.skeletonindex, bonenum, relorg); // restore caller's v_ vectors v_forward = oldforward; v_right = oldright; v_up = oldup; }; // delete skeleton when we're done with it // note: skeleton remains valid until next frame when it is really deleted void() example_skel_player_delete = { skel_delete(self.skeletonindex); self.skeletonindex = 0; }; // // END OF EXAMPLES FOR FTE_CSQC_SKELETONOBJECTS // //KRIMZON_SV_PARSECLIENTCOMMAND //idea: KrimZon //darkplaces implementation: KrimZon, LordHavoc //engine-called QC prototypes: //void(string s) SV_ParseClientCommand; //builtin definitions: void(entity e, string s) clientcommand = #440; float(string s) tokenize = #441; string(float n) argv = #442; //description: //provides QC the ability to completely control server interpretation of client commands ("say" and "color" for example, clientcommand is necessary for this and substring (FRIK_FILE) is useful) as well as adding new commands (tokenize, argv, and stof (FRIK_FILE) are useful for this)), whenever a clc_stringcmd is received the QC function is called, and it is up to the QC to decide what (if anything) to do with it //NEH_CMD_PLAY2 //idea: Nehahra //darkplaces implementation: LordHavoc //description: //shows that the engine supports the "play2" console command (plays a sound without spatialization). //NEH_RESTOREGAME //idea: Nehahra //darkplaces implementation: LordHavoc //engine-called QC prototypes: //void() RestoreGame; //description: //when a savegame is loaded, this function is called //NEXUIZ_PLAYERMODEL //idea: Nexuiz //darkplaces implementation: Black //console commands: //playermodel - FIXME: EXAMPLE NEEDED //playerskin - FIXME: EXAMPLE NEEDED //field definitions: .string playermodel; // name of player model sent by client .string playerskin; // name of player skin sent by client //description: //these client properties are used by Nexuiz. //NXQ_GFX_LETTERBOX //idea: nxQuake //darkplaces implementation: LordHavoc //description: //shows that the engine supports the "r_letterbox" console variable, set to values in the range 0-100 this restricts the view vertically (and turns off sbar and crosshair), value is a 0-100 percentage of how much to constrict the view, <=0 = normal view height, 25 = 75% of normal view height, 50 = 50%, 75 = 25%, >=100 = no view //PRYDON_CLIENTCURSOR //idea: FrikaC //darkplaces implementation: LordHavoc //effects bit: float EF_SELECTABLE = 16384; // allows cursor to highlight entity (brighten) //field definitions: .float cursor_active; // true if cl_prydoncursor mode is on .vector cursor_screen; // screen position of cursor as -1 to +1 in _x and _y (_z unused) .vector cursor_trace_start; // position of camera .vector cursor_trace_endpos; // position of cursor in world (as traced from camera) .entity cursor_trace_ent; // entity the cursor is pointing at (server forces this to world if the entity is currently free at time of receipt) //cvar definitions: //cl_prydoncursor (0/1+, default 0, 1 and above use cursors named gfx/prydoncursor%03i.lmp - or .tga and such if DP_GFX_EXTERNALTEXTURES is implemented) //description: //shows that the engine supports the cl_prydoncursor cvar, this puts a clientside mouse pointer on the screen and feeds input to the server for the QuakeC to use as it sees fit. //the mouse pointer triggers button4 if cursor is at left edge of screen, button5 if at right edge of screen, button6 if at top edge of screen, button7 if at bottom edge of screen. //the clientside trace skips transparent entities (except those marked EF_SELECTABLE). //the selected entity highlights only if EF_SELECTABLE is set, a typical selection method would be doubling the brightness of the entity by some means (such as colormod[] *= 2). //intended to be used by Prydon Gate. //TENEBRAE_GFX_DLIGHTS //idea: Tenebrae //darkplaces implementation: LordHavoc //fields: .float light_lev; // radius (does not affect brightness), typical value 350 .vector color; // color (does not affect radius), typical value '1 1 1' (bright white), can be up to '255 255 255' (nuclear blast) .float style; // light style (like normal light entities, flickering torches or switchable, etc) .float pflags; // flags (see PFLAGS_ constants) .vector angles; // orientation of the light .float skin; // cubemap filter number, can be 1-255 (0 is assumed to be none, and tenebrae only allows 16-255), this selects a projective light filter, a value of 1 loads cubemaps/1posx.tga and cubemaps/1negx.tga and posy, negy, posz, and negz, similar to skybox but some sides need to be rotated or flipped //constants: float PFLAGS_NOSHADOW = 1; // light does not cast shadows float PFLAGS_CORONA = 2; // light has a corona flare float PFLAGS_FULLDYNAMIC = 128; // light enable (without this set no light is produced!) //description: //more powerful dynamic light settings //warning: it is best not to use cubemaps on a light entity that has a model, as using a skin number that a model does not have will cause issues in glquake, and produce warnings in darkplaces (use developer 1 to see them) //changes compared to tenebrae (because they're too 'leet' for standards): //note: networking should send entities with PFLAGS_FULLDYNAMIC set even if they have no model (lights in general do not have a model, nor should they) //EF_FULLDYNAMIC effects flag replaced by PFLAGS_FULLDYNAMIC flag (EF_FULLDYNAMIC conflicts with EF_NODRAW) //TW_SV_STEPCONTROL //idea: Transfusion //darkplaces implementation: LordHavoc //cvars: //sv_jumpstep (0/1, default 1) //sv_stepheight (default 18) //description: //sv_jumpstep allows stepping up onto stairs while airborn, sv_stepheight controls how high a single step can be. //FTE_QC_CHECKPVS //idea: Urre //darkplaces implementation: divVerent //builtin definitions: float checkpvs(vector viewpos, entity viewee) = #240; //description: //returns true if viewee can be seen from viewpos according to PVS data //FTE_STRINGS //idea: many //darkplaces implementation: KrimZon //builtin definitions: float(string str, string sub, float startpos) strstrofs = #221; // returns the offset into a string of the matching text, or -1 if not found, case sensitive float(string str, float ofs) str2chr = #222; // returns the character at the specified offset as an integer, or 0 if an invalid index, or byte value - 256 if the engine supports UTF8 and the byte is part of an extended character string(float c, ...) chr2str = #223; // returns a string representing the character given, if the engine supports UTF8 this may be a multi-byte sequence (length may be more than 1) for characters over 127. string(float ccase, float calpha, float cnum, string s, ...) strconv = #224; // reformat a string with special color characters in the font, DO NOT USE THIS ON UTF8 ENGINES (if you are lucky they will emit ^4 and such color codes instead), the parameter values are 0=same/1=lower/2=upper for ccase, 0=same/1=white/2=red/5=alternate/6=alternate-alternate for redalpha, 0=same/1=white/2=red/3=redspecial/4=whitespecial/5=alternate/6=alternate-alternate for rednum. string(float chars, string s, ...) strpad = #225; // pad string with spaces to a specified length, < 0 = left padding, > 0 = right padding string(string info, string key, string value, ...) infoadd = #226; // sets or adds a key/value pair to an infostring - note: forbidden characters are \ and " string(string info, string key) infoget = #227; // gets a key/value pair in an infostring, returns value or null if not found float(string s1, string s2) strcmp = #228; // compare two strings float(string s1, string s2, float len) strncmp = #228; // compare two strings up to the specified number of characters, if their length differs and is within the specified limit the result will be negative, otherwise it is the difference in value of their first non-matching character. float(string s1, string s2) strcasecmp = #229; // compare two strings with case-insensitive matching, characters a-z are considered equivalent to the matching A-Z character, no other differences, and this does not consider special characters equal even if they look similar float(string s1, string s2, float len) strncasecmp = #230; // same as strcasecmp but with a length limit, see strncmp //string(string s, float start, float length) substring = #116; // see note below //description: //various string manipulation functions //note: substring also exists in FRIK_FILE but this extension adds negative start and length as valid cases (see note above), substring is consistent with the php 5.2.0 substr function (not 5.2.3 behavior) //substring returns a section of a string as a tempstring, if given negative // start the start is measured back from the end of the string, if given a // negative length the length is the offset back from the end of the string to // stop at, rather than being relative to start, if start is negative and // larger than length it is treated as 0. // examples of substring: // substring("blah", -3, 3) returns "lah" // substring("blah", 3, 3) returns "h" // substring("blah", -10, 3) returns "bla" // substring("blah", -10, -3) returns "b" //DP_CON_BESTWEAPON //idea: many //darkplaces implementation: divVerent //description: //allows QC to register weapon properties for use by the bestweapon command, for mods that change required ammo count or type for the weapons //it is done using console commands sent via stuffcmd: // register_bestweapon quake // register_bestweapon clear // register_bestweapon //for example, this is what Quake uses: // register_bestweapon 1 1 4096 4096 6 0 // STAT_SHELLS is 6 // register_bestweapon 2 2 1 1 6 1 // register_bestweapon 3 3 2 2 6 1 // register_bestweapon 4 4 4 4 7 1 // STAT_NAILS is 7 // register_bestweapon 5 5 8 8 7 1 // register_bestweapon 6 6 16 16 8 1 // STAT_ROCKETS is 8 // register_bestweapon 7 7 32 32 8 1 // register_bestweapon 8 8 64 64 9 1 // STAT_CELLS is 9 //after each map client initialization, this is reset back to Quake settings. So you should send these data in ClientConnect. //also, this extension introduces a new "cycleweapon" command to the user. //DP_QC_STRINGBUFFERS //idea: ?? //darkplaces implementation: LordHavoc //functions to manage string buffer objects - that is, arbitrary length string arrays that are handled by the engine float() buf_create = #460; void(float bufhandle) buf_del = #461; float(float bufhandle) buf_getsize = #462; void(float bufhandle_from, float bufhandle_to) buf_copy = #463; void(float bufhandle, float sortpower, float backward) buf_sort = #464; string(float bufhandle, string glue) buf_implode = #465; string(float bufhandle, float string_index) bufstr_get = #466; void(float bufhandle, float string_index, string str) bufstr_set = #467; float(float bufhandle, string str, float order) bufstr_add = #468; void(float bufhandle, float string_index) bufstr_free = #469; //DP_QC_STRINGBUFFERS_CVARLIST //idea: divVerent //darkplaces implementation: divVerent //functions to list cvars and store their names into a stringbuffer //cvars that start with pattern but not with antipattern will be stored into the buffer void(float bufhandle, string pattern, string antipattern) buf_cvarlist = #517; //DP_QC_STRINGBUFFERS_EXT_WIP //idea: VorteX //darkplaces implementation: VorteX //constant definitions: const float MATCH_AUTO = 0; const float MATCH_WHOLE = 1; const float MATCH_LEFT = 2; const float MATCH_RIGHT = 3; const float MATCH_MIDDLE = 4; const float MATCH_PATTERN = 5; //builtin definitions: float(string filename, float bufhandle) buf_loadfile = #535; // append each line of file as new buffer string, return 1 if succesful float(float filehandle, float bufhandle, float startpos, float numstrings) buf_writefile = #536; // writes buffer strings as lines, returns 1 if succesful float(float bufhandle, string match, float matchrule, float startpos, float step) bufstr_find = #537; // returns string index float(string s, string pattern, float matchrule) matchpattern = #538; // returns 0/1 float(string s, string pattern, float matchrule, float pos) matchpatternofs = #538; //description: //provides a set of functions to manipulate with string buffers //pattern wildcards: * - any character (or no characters), ? - any 1 character //Warning: This extension is work-in-progress, it may be changed/revamped/removed at any time, dont use it if you dont want any trouble //wip note: UTF8 is not supported yet //DP_QC_STRREPLACE //idea: Sajt //darkplaces implementation: Sajt //builtin definitions: string(string search, string replace, string subject) strreplace = #484; string(string search, string replace, string subject) strireplace = #485; //description: //strreplace replaces all occurrences of 'search' with 'replace' in the string 'subject', and returns the result as a tempstring. //strireplace does the same but uses case-insensitive matching of the 'search' term //DP_SV_SHUTDOWN //idea: divVerent //darkplaces implementation: divVerent //A function that gets called just before progs exit. To save persistent data from. //It is not called on a crash or error. //void SV_Shutdown(); //EXT_CSQC // #232 void(float index, float type, .void field) SV_AddStat (EXT_CSQC) void(float index, float type, ...) addstat = #232; //ZQ_PAUSE //idea: ZQuake //darkplaces implementation: GreEn`mArine //builtin definitions: void(float pause) setpause = #531; //function definitions: //void(float elapsedtime) SV_PausedTic; //description: //during pause the world is not updated (time does not advance), SV_PausedTic is the only function you can be sure will be called at regular intervals during the pause, elapsedtime is the current system time in seconds since the pause started (not affected by slowmo or anything else). // //calling setpause(0) will end a pause immediately. // //Note: it is worth considering that network-related functions may be called during the pause (including customizeentityforclient for example), and it is also important to consider the continued use of the KRIMZON_SV_PARSECLIENTCOMMAND extension while paused (chatting players, etc), players may also join/leave during the pause. In other words, the only things that are not called are think and other time-related functions. //DP_COVERAGE //idea: divVerent //darkplaces implementation: divVerent //function definitions: void coverage() = #642; // Reports a coverage event. The engine counts for each of the calls to this builtin whether it has been called. // EXPERIMENTAL (not finalized) EXTENSIONS: //DP_CRYPTO //idea: divVerent //darkplaces implementation: divVerent //field definitions: (SVQC) .string crypto_keyfp; // fingerprint of CA key the player used to authenticate .string crypto_mykeyfp; // fingerprint of CA key the server used to authenticate to the player .string crypto_idfp; // fingerprint of ID used by the player entity, or string_null if not identified .float crypto_idfp_signed; // set if the player's ID has been signed .string crypto_encryptmethod; // the string "AES128" if encrypting, and string_null if plaintext .string crypto_signmethod; // the string "HMAC-SHA256" if signing, and string_null if plaintext // there is no field crypto_myidfp, as that info contains no additional information the QC may have a use for //builtin definitions: (SVQC) float(string url, float id, string content_type, string delim, float buf, float keyid) crypto_uri_postbuf = #513; //description: //use -1 as buffer handle to justs end delim as postdata //DP_USERMOVETYPES //idea: divVerent //darkplaces implementation: Mario //movetype definitions: float MOVETYPE_USER_FIRST = 128; float MOVETYPE_USER_LAST = 191; //description: //user defined movetypes can be added between the start and end points, without producing unknown movetype warnings